@jiant/canvable 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.d.mts CHANGED
@@ -204,15 +204,18 @@ declare class Scene extends Node {
204
204
  camera: Camera;
205
205
  inputManager: InputManager;
206
206
  ctx: CanvasRenderingContext2D;
207
+ private animationFrameId;
208
+ private running;
207
209
  private lastTime;
208
210
  private totalTime;
209
211
  constructor(ctx: CanvasRenderingContext2D, opts?: SceneOptions);
210
212
  addObject(object: Node): void;
211
213
  setup(): void;
212
214
  resizeCanvas(): void;
213
- update(deltaTime: number): void;
214
- draw(ctx: CanvasRenderingContext2D): void;
215
- gameLoop(callback?: (deltaTime: number, totalTime: number) => void, targetFPS?: number): void;
215
+ updateFrame(deltaTime: number): void;
216
+ drawFrame(ctx: CanvasRenderingContext2D): void;
217
+ run(callback?: (deltaTime: number, totalTime: number) => void, targetFPS?: number): void;
218
+ stop(): void;
216
219
  }
217
220
 
218
221
  interface NodeConfig {
@@ -225,39 +228,114 @@ declare class Node {
225
228
  id: string;
226
229
  children: Node[];
227
230
  scene?: Scene;
231
+ boundingBox?: BoundingBox;
232
+ update?(dt: number): void;
233
+ draw?(ctx: CanvasRenderingContext2D, selected?: boolean): void;
228
234
  constructor(scene?: Scene, config?: NodeConfig);
229
235
  addObject(object: Node): void;
230
236
  removeObject(object: Node): void;
231
237
  private generateID;
232
238
  }
233
239
 
240
+ interface CollisionResult {
241
+ isColliding: boolean;
242
+ mtv?: Vector2D;
243
+ }
234
244
  declare abstract class BoundingBox extends Node {
235
- abstract checkCollision(other: BoundingBox): boolean;
245
+ abstract checkCollision(other: BoundingBox): CollisionResult;
236
246
  abstract isPointInside(_point: Vector2D): boolean;
237
247
  }
238
248
 
249
+ declare class CircleBoundingBox extends BoundingBox {
250
+ radius: number;
251
+ constructor(scene?: Scene, radius?: number);
252
+ checkCollision(other: BoundingBox): CollisionResult;
253
+ isPointInside(_point: Vector2D): boolean;
254
+ }
255
+
256
+ declare class SquareBoundingBox extends BoundingBox {
257
+ size: Vector2D;
258
+ constructor(scene?: Scene, size?: Vector2D);
259
+ checkCollision(other: BoundingBox): CollisionResult;
260
+ isPointInside(_point: Vector2D): boolean;
261
+ }
262
+
263
+ interface KinematicBodyConfig {
264
+ speed?: number;
265
+ friction?: number;
266
+ maxSpeed?: number;
267
+ }
268
+ declare class KinematicBody extends Node {
269
+ shape: Node;
270
+ velocity: Vector2D;
271
+ speed: number;
272
+ friction: number;
273
+ maxSpeed: number;
274
+ isColliding: boolean;
275
+ constructor(scene: Scene, shape: Node, config?: KinematicBodyConfig);
276
+ applyForce(x: number, y: number, dt: number): void;
277
+ update(dt: number): void;
278
+ private resolveCollisions;
279
+ draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
280
+ }
281
+
282
+ declare class StaticBody extends Node {
283
+ shape: Node;
284
+ constructor(scene: Scene, shape: Node);
285
+ update(_dt: number): void;
286
+ draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
287
+ }
288
+
239
289
  interface CircleConfig {
240
290
  vel?: Vector2D;
241
291
  size?: number;
242
292
  friction?: number;
243
293
  startsWithBoundingBox?: boolean;
294
+ style?: {
295
+ fillStyle?: string;
296
+ strokeStyle?: string;
297
+ lineWidth?: number;
298
+ };
244
299
  }
245
300
  declare class Circle extends Node {
246
301
  vel: Vector2D;
247
302
  size: number;
248
303
  friction: number;
249
- boundingBox: BoundingBox;
304
+ style: {
305
+ fillStyle?: string;
306
+ strokeStyle?: string;
307
+ lineWidth?: number;
308
+ };
250
309
  constructor(scene?: Scene, config?: CircleConfig);
310
+ update(_deltaTime: number): void;
251
311
  draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
252
312
  }
253
313
 
254
- declare class CircleBoundingBox extends BoundingBox {
255
- radius: number;
256
- constructor(scene?: Scene, radius?: number);
257
- checkCollision(other: BoundingBox): boolean;
258
- isPointInside(_point: Vector2D): boolean;
314
+ interface SquareConfig {
315
+ vel?: Vector2D;
316
+ size?: Vector2D;
317
+ friction?: number;
318
+ startsWithBoundingBox?: boolean;
319
+ style?: {
320
+ fillStyle?: string;
321
+ strokeStyle?: string;
322
+ lineWidth?: number;
323
+ };
324
+ }
325
+ declare class Square extends Node {
326
+ vel: Vector2D;
327
+ size: Vector2D;
328
+ friction: number;
329
+ style: {
330
+ fillStyle?: string;
331
+ strokeStyle?: string;
332
+ lineWidth?: number;
333
+ };
334
+ constructor(scene?: Scene, config?: SquareConfig);
335
+ update(_deltaTime: number): void;
336
+ draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
259
337
  }
260
338
 
261
339
  declare function getCanvas(id: string): HTMLCanvasElement;
262
340
 
263
- export { BoundingBox, Camera, Circle, CircleBoundingBox, type CircleConfig, type Drawable, Node, type NodeConfig, Scene, type SceneOptions, TWO_PI, type Updatable, Vector2D, getCanvas };
341
+ export { BoundingBox, Camera, Circle, CircleBoundingBox, type CircleConfig, type CollisionResult, type Drawable, KinematicBody, type KinematicBodyConfig, Node, type NodeConfig, Scene, type SceneOptions, Square, SquareBoundingBox, type SquareConfig, StaticBody, TWO_PI, type Updatable, Vector2D, getCanvas };
package/dist/main.d.ts CHANGED
@@ -204,15 +204,18 @@ declare class Scene extends Node {
204
204
  camera: Camera;
205
205
  inputManager: InputManager;
206
206
  ctx: CanvasRenderingContext2D;
207
+ private animationFrameId;
208
+ private running;
207
209
  private lastTime;
208
210
  private totalTime;
209
211
  constructor(ctx: CanvasRenderingContext2D, opts?: SceneOptions);
210
212
  addObject(object: Node): void;
211
213
  setup(): void;
212
214
  resizeCanvas(): void;
213
- update(deltaTime: number): void;
214
- draw(ctx: CanvasRenderingContext2D): void;
215
- gameLoop(callback?: (deltaTime: number, totalTime: number) => void, targetFPS?: number): void;
215
+ updateFrame(deltaTime: number): void;
216
+ drawFrame(ctx: CanvasRenderingContext2D): void;
217
+ run(callback?: (deltaTime: number, totalTime: number) => void, targetFPS?: number): void;
218
+ stop(): void;
216
219
  }
217
220
 
218
221
  interface NodeConfig {
@@ -225,39 +228,114 @@ declare class Node {
225
228
  id: string;
226
229
  children: Node[];
227
230
  scene?: Scene;
231
+ boundingBox?: BoundingBox;
232
+ update?(dt: number): void;
233
+ draw?(ctx: CanvasRenderingContext2D, selected?: boolean): void;
228
234
  constructor(scene?: Scene, config?: NodeConfig);
229
235
  addObject(object: Node): void;
230
236
  removeObject(object: Node): void;
231
237
  private generateID;
232
238
  }
233
239
 
240
+ interface CollisionResult {
241
+ isColliding: boolean;
242
+ mtv?: Vector2D;
243
+ }
234
244
  declare abstract class BoundingBox extends Node {
235
- abstract checkCollision(other: BoundingBox): boolean;
245
+ abstract checkCollision(other: BoundingBox): CollisionResult;
236
246
  abstract isPointInside(_point: Vector2D): boolean;
237
247
  }
238
248
 
249
+ declare class CircleBoundingBox extends BoundingBox {
250
+ radius: number;
251
+ constructor(scene?: Scene, radius?: number);
252
+ checkCollision(other: BoundingBox): CollisionResult;
253
+ isPointInside(_point: Vector2D): boolean;
254
+ }
255
+
256
+ declare class SquareBoundingBox extends BoundingBox {
257
+ size: Vector2D;
258
+ constructor(scene?: Scene, size?: Vector2D);
259
+ checkCollision(other: BoundingBox): CollisionResult;
260
+ isPointInside(_point: Vector2D): boolean;
261
+ }
262
+
263
+ interface KinematicBodyConfig {
264
+ speed?: number;
265
+ friction?: number;
266
+ maxSpeed?: number;
267
+ }
268
+ declare class KinematicBody extends Node {
269
+ shape: Node;
270
+ velocity: Vector2D;
271
+ speed: number;
272
+ friction: number;
273
+ maxSpeed: number;
274
+ isColliding: boolean;
275
+ constructor(scene: Scene, shape: Node, config?: KinematicBodyConfig);
276
+ applyForce(x: number, y: number, dt: number): void;
277
+ update(dt: number): void;
278
+ private resolveCollisions;
279
+ draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
280
+ }
281
+
282
+ declare class StaticBody extends Node {
283
+ shape: Node;
284
+ constructor(scene: Scene, shape: Node);
285
+ update(_dt: number): void;
286
+ draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
287
+ }
288
+
239
289
  interface CircleConfig {
240
290
  vel?: Vector2D;
241
291
  size?: number;
242
292
  friction?: number;
243
293
  startsWithBoundingBox?: boolean;
294
+ style?: {
295
+ fillStyle?: string;
296
+ strokeStyle?: string;
297
+ lineWidth?: number;
298
+ };
244
299
  }
245
300
  declare class Circle extends Node {
246
301
  vel: Vector2D;
247
302
  size: number;
248
303
  friction: number;
249
- boundingBox: BoundingBox;
304
+ style: {
305
+ fillStyle?: string;
306
+ strokeStyle?: string;
307
+ lineWidth?: number;
308
+ };
250
309
  constructor(scene?: Scene, config?: CircleConfig);
310
+ update(_deltaTime: number): void;
251
311
  draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
252
312
  }
253
313
 
254
- declare class CircleBoundingBox extends BoundingBox {
255
- radius: number;
256
- constructor(scene?: Scene, radius?: number);
257
- checkCollision(other: BoundingBox): boolean;
258
- isPointInside(_point: Vector2D): boolean;
314
+ interface SquareConfig {
315
+ vel?: Vector2D;
316
+ size?: Vector2D;
317
+ friction?: number;
318
+ startsWithBoundingBox?: boolean;
319
+ style?: {
320
+ fillStyle?: string;
321
+ strokeStyle?: string;
322
+ lineWidth?: number;
323
+ };
324
+ }
325
+ declare class Square extends Node {
326
+ vel: Vector2D;
327
+ size: Vector2D;
328
+ friction: number;
329
+ style: {
330
+ fillStyle?: string;
331
+ strokeStyle?: string;
332
+ lineWidth?: number;
333
+ };
334
+ constructor(scene?: Scene, config?: SquareConfig);
335
+ update(_deltaTime: number): void;
336
+ draw(ctx: CanvasRenderingContext2D, selected?: boolean): void;
259
337
  }
260
338
 
261
339
  declare function getCanvas(id: string): HTMLCanvasElement;
262
340
 
263
- export { BoundingBox, Camera, Circle, CircleBoundingBox, type CircleConfig, type Drawable, Node, type NodeConfig, Scene, type SceneOptions, TWO_PI, type Updatable, Vector2D, getCanvas };
341
+ export { BoundingBox, Camera, Circle, CircleBoundingBox, type CircleConfig, type CollisionResult, type Drawable, KinematicBody, type KinematicBodyConfig, Node, type NodeConfig, Scene, type SceneOptions, Square, SquareBoundingBox, type SquareConfig, StaticBody, TWO_PI, type Updatable, Vector2D, getCanvas };
package/dist/main.js CHANGED
@@ -24,8 +24,12 @@ __export(main_exports, {
24
24
  Camera: () => Camera,
25
25
  Circle: () => Circle,
26
26
  CircleBoundingBox: () => CircleBoundingBox,
27
+ KinematicBody: () => KinematicBody,
27
28
  Node: () => Node,
28
- Scene: () => Scene2,
29
+ Scene: () => Scene,
30
+ Square: () => Square,
31
+ SquareBoundingBox: () => SquareBoundingBox,
32
+ StaticBody: () => StaticBody,
29
33
  TWO_PI: () => TWO_PI,
30
34
  Vector2D: () => Vector2D,
31
35
  getCanvas: () => getCanvas
@@ -289,13 +293,14 @@ var Vector2D = class _Vector2D {
289
293
  }
290
294
  };
291
295
 
292
- // src/nodes/Node.ts
296
+ // src/nodes/shapes/Node.ts
293
297
  var import_uuid = require("uuid");
294
298
  var Node = class {
295
299
  pos;
296
300
  id;
297
301
  children = [];
298
302
  scene;
303
+ boundingBox;
299
304
  constructor(scene, config) {
300
305
  this.scene = scene;
301
306
  const { id, pos } = config || {};
@@ -322,25 +327,54 @@ var Node = class {
322
327
  }
323
328
  };
324
329
 
325
- // src/nodes/BoundingBox.ts
330
+ // src/nodes/boundingBox/BoundingBox.ts
326
331
  var BoundingBox = class extends Node {
327
332
  };
328
333
 
329
- // src/nodes/Camera.ts
330
- var Camera = class extends Node {
331
- zoom;
332
- constructor(zoom = 1) {
333
- super();
334
- this.zoom = zoom;
334
+ // src/nodes/boundingBox/SquareBoundingBox.ts
335
+ var SquareBoundingBox = class _SquareBoundingBox extends BoundingBox {
336
+ size;
337
+ constructor(scene, size) {
338
+ super(scene);
339
+ this.size = size || new Vector2D(10, 10);
335
340
  }
336
- update(deltaTime) {
341
+ checkCollision(other) {
342
+ if (other instanceof _SquareBoundingBox) {
343
+ const thisPoint = this.scene?.camera.transformCoordinates(new Vector2D(this.pos.x, this.pos.y));
344
+ const otherPoint = this.scene?.camera.transformCoordinates(new Vector2D(other.pos.x, other.pos.y));
345
+ if (!thisPoint || !otherPoint) return { isColliding: false };
346
+ const dx = Math.abs(thisPoint.x - otherPoint.x);
347
+ const dy = Math.abs(thisPoint.y - otherPoint.y);
348
+ const halfWidths = (this.size.x + other.size.x) / 2;
349
+ const halfHeights = (this.size.y + other.size.y) / 2;
350
+ if (dx < halfWidths && dy < halfHeights) {
351
+ const overlapX = halfWidths - dx;
352
+ const overlapY = halfHeights - dy;
353
+ if (overlapX < overlapY) {
354
+ const mtvX = thisPoint.x < otherPoint.x ? -overlapX : overlapX;
355
+ return { isColliding: true, mtv: new Vector2D(mtvX, 0) };
356
+ } else {
357
+ const mtvY = thisPoint.y < otherPoint.y ? -overlapY : overlapY;
358
+ return { isColliding: true, mtv: new Vector2D(0, mtvY) };
359
+ }
360
+ }
361
+ }
362
+ if (other instanceof CircleBoundingBox) {
363
+ const result = other.checkCollision(this);
364
+ if (result.isColliding && result.mtv) {
365
+ return { isColliding: true, mtv: new Vector2D(-result.mtv.x, -result.mtv.y) };
366
+ }
367
+ }
368
+ return { isColliding: false };
337
369
  }
338
- transformCoordinates(position) {
339
- return new Vector2D((position.x - this.pos.x) * this.zoom, (position.y - this.pos.y) * this.zoom);
370
+ isPointInside(_point) {
371
+ const x_inside = _point.x >= this.pos.x - this.size.x && _point.x <= this.pos.x + this.size.x;
372
+ const y_inside = _point.y >= this.pos.y - this.size.y && _point.y <= this.pos.y + this.size.y;
373
+ return x_inside && y_inside;
340
374
  }
341
375
  };
342
376
 
343
- // src/nodes/CircleBoundingBox.ts
377
+ // src/nodes/boundingBox/CircleBoundingBox.ts
344
378
  var CircleBoundingBox = class _CircleBoundingBox extends BoundingBox {
345
379
  radius;
346
380
  constructor(scene, radius) {
@@ -349,15 +383,28 @@ var CircleBoundingBox = class _CircleBoundingBox extends BoundingBox {
349
383
  }
350
384
  checkCollision(other) {
351
385
  if (other instanceof _CircleBoundingBox) {
352
- const thisPoint = this.scene?.camera.transformCoordinates(new Vector2D(this.pos.x, this.pos.y));
353
- const otherPoint = this.scene?.camera.transformCoordinates(new Vector2D(other.pos.x, other.pos.y));
354
- if (!thisPoint || !otherPoint) return false;
355
- const dx = thisPoint.x - otherPoint.x;
356
- const dy = thisPoint.y - otherPoint.y;
386
+ const dx = this.pos.x - other.pos.x;
387
+ const dy = this.pos.y - other.pos.y;
357
388
  const distance = Math.sqrt(dx * dx + dy * dy);
358
- return distance < this.radius + other.radius;
389
+ if (distance < this.radius + other.radius) {
390
+ const overlap = this.radius + other.radius - distance;
391
+ const mtv = distance === 0 ? new Vector2D(overlap, 0) : new Vector2D(dx / distance * overlap, dy / distance * overlap);
392
+ return { isColliding: true, mtv };
393
+ }
359
394
  }
360
- return false;
395
+ if (other instanceof SquareBoundingBox) {
396
+ const closestX = Math.max(other.pos.x - other.size.x / 2, Math.min(this.pos.x, other.pos.x + other.size.x / 2));
397
+ const closestY = Math.max(other.pos.y - other.size.y / 2, Math.min(this.pos.y, other.pos.y + other.size.y / 2));
398
+ const dx = this.pos.x - closestX;
399
+ const dy = this.pos.y - closestY;
400
+ const distance = Math.sqrt(dx * dx + dy * dy);
401
+ if (distance < this.radius) {
402
+ const overlap = this.radius - distance;
403
+ const mtv = distance === 0 ? new Vector2D(overlap, 0) : new Vector2D(dx / distance * overlap, dy / distance * overlap);
404
+ return { isColliding: true, mtv };
405
+ }
406
+ }
407
+ return { isColliding: false };
361
408
  }
362
409
  isPointInside(_point) {
363
410
  const point = this.scene?.camera.transformCoordinates(_point);
@@ -369,40 +416,104 @@ var CircleBoundingBox = class _CircleBoundingBox extends BoundingBox {
369
416
  }
370
417
  };
371
418
 
372
- // src/nodes/Circle.ts
373
- var Circle = class extends Node {
374
- vel = new Vector2D();
375
- size = 10;
376
- friction = 0.1;
377
- boundingBox = new CircleBoundingBox(this.scene, this.size);
378
- constructor(scene, config) {
379
- super(scene);
380
- this.vel = config?.vel || new Vector2D();
381
- this.size = config?.size || 10;
382
- this.friction = config?.friction || 0.1;
383
- if (config?.startsWithBoundingBox) {
384
- this.boundingBox = new CircleBoundingBox(scene, this.size);
385
- this.addObject(this.boundingBox);
419
+ // src/nodes/Camera.ts
420
+ var Camera = class extends Node {
421
+ zoom;
422
+ constructor(zoom = 1) {
423
+ super();
424
+ this.zoom = zoom;
425
+ }
426
+ update(deltaTime) {
427
+ }
428
+ transformCoordinates(position) {
429
+ return new Vector2D((position.x - this.pos.x) * this.zoom, (position.y - this.pos.y) * this.zoom);
430
+ }
431
+ };
432
+
433
+ // src/nodes/physics/StaticBody.ts
434
+ var StaticBody = class extends Node {
435
+ shape;
436
+ constructor(scene, shape) {
437
+ super(scene, { pos: shape.pos });
438
+ this.shape = shape;
439
+ this.pos = shape.pos;
440
+ }
441
+ update(_dt) {
442
+ if (this.shape.update && typeof this.shape.update === "function") {
443
+ this.shape.update(_dt);
386
444
  }
387
445
  }
388
446
  draw(ctx, selected) {
389
- const cameraPos = this.scene?.camera.pos || new Vector2D();
390
- const cameraScale = this.scene?.camera.zoom || 1;
391
- const relativePos = new Vector2D((this.pos.x - cameraPos.x) * cameraScale, (this.pos.y - cameraPos.y) * cameraScale);
392
- if (selected) {
393
- ctx.beginPath();
394
- ctx.arc(relativePos.x, relativePos.y, this.size + 5, 0, Math.PI * 2);
395
- ctx.fillStyle = "transparent";
396
- ctx.lineWidth = 3;
397
- ctx.strokeStyle = "white";
398
- ctx.stroke();
399
- ctx.closePath();
447
+ if (this.shape.draw) this.shape.draw(ctx, selected);
448
+ }
449
+ };
450
+
451
+ // src/nodes/physics/KinematicBody.ts
452
+ var KinematicBody = class _KinematicBody extends Node {
453
+ shape;
454
+ velocity = new Vector2D();
455
+ speed = 1500;
456
+ friction = 0.92;
457
+ maxSpeed = 800;
458
+ isColliding = false;
459
+ constructor(scene, shape, config) {
460
+ super(scene, { pos: shape.pos });
461
+ this.shape = shape;
462
+ if (config?.speed) this.speed = config.speed;
463
+ if (config?.friction) this.friction = config.friction;
464
+ if (config?.maxSpeed) this.maxSpeed = config.maxSpeed;
465
+ }
466
+ applyForce(x, y, dt) {
467
+ const len = Math.sqrt(x * x + y * y);
468
+ if (len > 0) {
469
+ const scale = this.speed * dt / len;
470
+ this.velocity.x += x * scale;
471
+ this.velocity.y += y * scale;
472
+ }
473
+ }
474
+ update(dt) {
475
+ this.velocity.x *= this.friction;
476
+ this.velocity.y *= this.friction;
477
+ const currentSpeed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y);
478
+ if (currentSpeed > this.maxSpeed) {
479
+ const scale = this.maxSpeed / currentSpeed;
480
+ this.velocity.x *= scale;
481
+ this.velocity.y *= scale;
482
+ }
483
+ if (Math.abs(this.velocity.x) < 0.1) this.velocity.x = 0;
484
+ if (Math.abs(this.velocity.y) < 0.1) this.velocity.y = 0;
485
+ this.pos.x += this.velocity.x * dt;
486
+ this.pos.y += this.velocity.y * dt;
487
+ this.shape.pos = this.pos;
488
+ if (this.shape.update) this.shape.update(dt);
489
+ this.resolveCollisions();
490
+ }
491
+ resolveCollisions() {
492
+ this.isColliding = false;
493
+ if (!this.shape.boundingBox) return;
494
+ if (!this.scene) return;
495
+ for (const obj of this.scene.children) {
496
+ if (obj === this) continue;
497
+ let otherBBox = null;
498
+ if (obj instanceof StaticBody || obj instanceof _KinematicBody) {
499
+ if (obj.shape.boundingBox) otherBBox = obj.shape.boundingBox;
500
+ } else {
501
+ if (obj.boundingBox) otherBBox = obj.boundingBox;
502
+ }
503
+ if (otherBBox) {
504
+ const result = this.shape.boundingBox.checkCollision(otherBBox);
505
+ if (result.isColliding && result.mtv) {
506
+ this.isColliding = true;
507
+ this.pos.x += result.mtv.x;
508
+ this.pos.y += result.mtv.y;
509
+ this.shape.pos = this.pos;
510
+ if (this.shape.boundingBox) this.shape.boundingBox.pos = this.pos;
511
+ }
512
+ }
400
513
  }
401
- ctx.beginPath();
402
- ctx.arc(relativePos.x, relativePos.y, this.size, 0, Math.PI * 2);
403
- ctx.fillStyle = "blue";
404
- ctx.fill();
405
- ctx.closePath();
514
+ }
515
+ draw(ctx, selected) {
516
+ if (this.shape.draw) this.shape.draw(ctx, selected);
406
517
  }
407
518
  };
408
519
 
@@ -428,14 +539,14 @@ var InputManager = class extends Node {
428
539
  this.mousePos = new Vector2D(event.clientX, event.clientY);
429
540
  }
430
541
  handleKeyDown(event) {
431
- const key = event.key;
542
+ const key = event.code;
432
543
  if (!this.keyState.get(key)) {
433
544
  this.keyState.set(key, true);
434
545
  this.keyJustPressed.add(key);
435
546
  }
436
547
  }
437
548
  handleKeyUp(event) {
438
- const key = event.key;
549
+ const key = event.code;
439
550
  this.keyState.set(key, false);
440
551
  this.keyJustPressed.delete(key);
441
552
  }
@@ -474,10 +585,12 @@ var InputManager = class extends Node {
474
585
  };
475
586
 
476
587
  // src/nodes/Scene.ts
477
- var Scene2 = class extends Node {
588
+ var Scene = class extends Node {
478
589
  camera;
479
590
  inputManager;
480
591
  ctx;
592
+ animationFrameId = null;
593
+ running = false;
481
594
  lastTime = 0;
482
595
  totalTime = 0;
483
596
  constructor(ctx, opts) {
@@ -502,7 +615,7 @@ var Scene2 = class extends Node {
502
615
  canvas.width = width;
503
616
  canvas.height = height;
504
617
  }
505
- update(deltaTime) {
618
+ updateFrame(deltaTime) {
506
619
  this.camera.update(deltaTime);
507
620
  this.children.forEach((child) => {
508
621
  if (isUpdatable(child)) {
@@ -510,16 +623,20 @@ var Scene2 = class extends Node {
510
623
  }
511
624
  });
512
625
  }
513
- draw(ctx) {
514
- ctx.fillStyle = ctx.canvas.style.backgroundColor;
626
+ drawFrame(ctx) {
627
+ ctx.fillStyle = ctx.canvas.style.backgroundColor || "black";
515
628
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
629
+ ctx.save();
630
+ ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
516
631
  this.children.forEach((child) => {
517
632
  if (isDrawable(child)) {
518
633
  child.draw(ctx);
519
634
  }
520
635
  });
636
+ ctx.restore();
521
637
  }
522
- gameLoop(callback, targetFPS = 60) {
638
+ run(callback, targetFPS = 60) {
639
+ this.running = true;
523
640
  const frameInterval = 1e3 / targetFPS;
524
641
  let lastFrameTime = 0;
525
642
  const loop = (currentTime) => {
@@ -530,8 +647,8 @@ var Scene2 = class extends Node {
530
647
  this.lastTime = currentTime;
531
648
  this.totalTime += deltaTime;
532
649
  lastFrameTime = currentTime - elapsed % frameInterval;
533
- this.update(deltaTime);
534
- this.draw(this.ctx);
650
+ this.updateFrame(deltaTime);
651
+ this.drawFrame(this.ctx);
535
652
  if (callback) {
536
653
  callback(deltaTime, this.totalTime);
537
654
  }
@@ -543,7 +660,14 @@ var Scene2 = class extends Node {
543
660
  requestAnimationFrame(loop);
544
661
  };
545
662
  this.lastTime = performance.now();
546
- requestAnimationFrame(loop);
663
+ this.animationFrameId = requestAnimationFrame(loop);
664
+ }
665
+ stop() {
666
+ if (this.animationFrameId) {
667
+ cancelAnimationFrame(this.animationFrameId);
668
+ this.animationFrameId = null;
669
+ }
670
+ this.running = false;
547
671
  }
548
672
  };
549
673
  function isUpdatable(obj) {
@@ -553,6 +677,118 @@ function isDrawable(obj) {
553
677
  return "draw" in obj;
554
678
  }
555
679
 
680
+ // src/nodes/shapes/Circle.ts
681
+ var Circle = class extends Node {
682
+ vel = new Vector2D();
683
+ size = 10;
684
+ friction = 0.1;
685
+ style = {
686
+ fillStyle: "blue"
687
+ };
688
+ constructor(scene, config) {
689
+ super(scene);
690
+ this.vel = config?.vel || new Vector2D();
691
+ this.size = config?.size || 10;
692
+ this.friction = config?.friction || 0.1;
693
+ this.style = { ...this.style, ...config?.style };
694
+ if (config?.startsWithBoundingBox) {
695
+ this.boundingBox = new CircleBoundingBox(scene, this.size);
696
+ this.addObject(this.boundingBox);
697
+ }
698
+ }
699
+ update(_deltaTime) {
700
+ if (this.boundingBox) {
701
+ this.boundingBox.pos = this.pos;
702
+ }
703
+ }
704
+ draw(ctx, selected) {
705
+ const cameraPos = this.scene?.camera.pos || new Vector2D();
706
+ const cameraScale = this.scene?.camera.zoom || 1;
707
+ const relativePos = new Vector2D((this.pos.x - cameraPos.x) * cameraScale, (this.pos.y - cameraPos.y) * cameraScale);
708
+ if (selected) {
709
+ ctx.beginPath();
710
+ ctx.arc(relativePos.x, relativePos.y, this.size + 5, 0, Math.PI * 2);
711
+ ctx.fillStyle = "transparent";
712
+ ctx.lineWidth = 3;
713
+ ctx.strokeStyle = "white";
714
+ ctx.stroke();
715
+ ctx.closePath();
716
+ }
717
+ ctx.beginPath();
718
+ ctx.arc(relativePos.x, relativePos.y, this.size, 0, Math.PI * 2);
719
+ ctx.fillStyle = this.style.fillStyle || "blue";
720
+ ctx.fill();
721
+ if (this.style.strokeStyle) {
722
+ ctx.strokeStyle = this.style.strokeStyle;
723
+ ctx.lineWidth = this.style.lineWidth || 1;
724
+ ctx.stroke();
725
+ }
726
+ ctx.closePath();
727
+ }
728
+ };
729
+
730
+ // src/nodes/shapes/Square.ts
731
+ var Square = class extends Node {
732
+ vel = new Vector2D();
733
+ size;
734
+ friction = 0.1;
735
+ style = {
736
+ fillStyle: "orange"
737
+ };
738
+ constructor(scene, config) {
739
+ super(scene);
740
+ this.vel = config?.vel || new Vector2D();
741
+ this.size = config?.size || new Vector2D(10, 10);
742
+ this.friction = config?.friction || 0.1;
743
+ this.style = { ...this.style, ...config?.style };
744
+ if (config?.startsWithBoundingBox) {
745
+ this.boundingBox = new SquareBoundingBox(scene, this.size);
746
+ this.addObject(this.boundingBox);
747
+ }
748
+ }
749
+ update(_deltaTime) {
750
+ if (this.boundingBox) {
751
+ this.boundingBox.pos = this.pos;
752
+ }
753
+ }
754
+ draw(ctx, selected) {
755
+ const cameraPos = this.scene?.camera.pos || new Vector2D();
756
+ const cameraScale = this.scene?.camera.zoom || 1;
757
+ const relativePos = new Vector2D(
758
+ (this.pos.x - cameraPos.x) * cameraScale,
759
+ (this.pos.y - cameraPos.y) * cameraScale
760
+ );
761
+ const scaledSize = new Vector2D(this.size.x * cameraScale, this.size.y * cameraScale);
762
+ if (selected) {
763
+ ctx.strokeStyle = "white";
764
+ ctx.lineWidth = 3;
765
+ ctx.strokeRect(
766
+ relativePos.x - scaledSize.x / 2 - 5,
767
+ relativePos.y - scaledSize.y / 2 - 5,
768
+ scaledSize.x + 10,
769
+ scaledSize.y + 10
770
+ );
771
+ }
772
+ ctx.fillStyle = this.style.fillStyle || "orange";
773
+ ctx.fillRect(
774
+ relativePos.x - scaledSize.x / 2,
775
+ relativePos.y - scaledSize.y / 2,
776
+ scaledSize.x,
777
+ scaledSize.y
778
+ );
779
+ if (this.style.strokeStyle) {
780
+ ctx.strokeStyle = this.style.strokeStyle;
781
+ ctx.lineWidth = this.style.lineWidth || 1;
782
+ ctx.strokeRect(
783
+ relativePos.x - scaledSize.x / 2,
784
+ relativePos.y - scaledSize.y / 2,
785
+ scaledSize.x,
786
+ scaledSize.y
787
+ );
788
+ }
789
+ }
790
+ };
791
+
556
792
  // src/main.ts
557
793
  function getCanvas(id) {
558
794
  const canvas = document.getElementById(id);
@@ -567,8 +803,12 @@ function getCanvas(id) {
567
803
  Camera,
568
804
  Circle,
569
805
  CircleBoundingBox,
806
+ KinematicBody,
570
807
  Node,
571
808
  Scene,
809
+ Square,
810
+ SquareBoundingBox,
811
+ StaticBody,
572
812
  TWO_PI,
573
813
  Vector2D,
574
814
  getCanvas
package/dist/main.mjs CHANGED
@@ -255,13 +255,14 @@ var Vector2D = class _Vector2D {
255
255
  }
256
256
  };
257
257
 
258
- // src/nodes/Node.ts
258
+ // src/nodes/shapes/Node.ts
259
259
  import { v4 as uuidv4 } from "uuid";
260
260
  var Node = class {
261
261
  pos;
262
262
  id;
263
263
  children = [];
264
264
  scene;
265
+ boundingBox;
265
266
  constructor(scene, config) {
266
267
  this.scene = scene;
267
268
  const { id, pos } = config || {};
@@ -288,25 +289,54 @@ var Node = class {
288
289
  }
289
290
  };
290
291
 
291
- // src/nodes/BoundingBox.ts
292
+ // src/nodes/boundingBox/BoundingBox.ts
292
293
  var BoundingBox = class extends Node {
293
294
  };
294
295
 
295
- // src/nodes/Camera.ts
296
- var Camera = class extends Node {
297
- zoom;
298
- constructor(zoom = 1) {
299
- super();
300
- this.zoom = zoom;
296
+ // src/nodes/boundingBox/SquareBoundingBox.ts
297
+ var SquareBoundingBox = class _SquareBoundingBox extends BoundingBox {
298
+ size;
299
+ constructor(scene, size) {
300
+ super(scene);
301
+ this.size = size || new Vector2D(10, 10);
301
302
  }
302
- update(deltaTime) {
303
+ checkCollision(other) {
304
+ if (other instanceof _SquareBoundingBox) {
305
+ const thisPoint = this.scene?.camera.transformCoordinates(new Vector2D(this.pos.x, this.pos.y));
306
+ const otherPoint = this.scene?.camera.transformCoordinates(new Vector2D(other.pos.x, other.pos.y));
307
+ if (!thisPoint || !otherPoint) return { isColliding: false };
308
+ const dx = Math.abs(thisPoint.x - otherPoint.x);
309
+ const dy = Math.abs(thisPoint.y - otherPoint.y);
310
+ const halfWidths = (this.size.x + other.size.x) / 2;
311
+ const halfHeights = (this.size.y + other.size.y) / 2;
312
+ if (dx < halfWidths && dy < halfHeights) {
313
+ const overlapX = halfWidths - dx;
314
+ const overlapY = halfHeights - dy;
315
+ if (overlapX < overlapY) {
316
+ const mtvX = thisPoint.x < otherPoint.x ? -overlapX : overlapX;
317
+ return { isColliding: true, mtv: new Vector2D(mtvX, 0) };
318
+ } else {
319
+ const mtvY = thisPoint.y < otherPoint.y ? -overlapY : overlapY;
320
+ return { isColliding: true, mtv: new Vector2D(0, mtvY) };
321
+ }
322
+ }
323
+ }
324
+ if (other instanceof CircleBoundingBox) {
325
+ const result = other.checkCollision(this);
326
+ if (result.isColliding && result.mtv) {
327
+ return { isColliding: true, mtv: new Vector2D(-result.mtv.x, -result.mtv.y) };
328
+ }
329
+ }
330
+ return { isColliding: false };
303
331
  }
304
- transformCoordinates(position) {
305
- return new Vector2D((position.x - this.pos.x) * this.zoom, (position.y - this.pos.y) * this.zoom);
332
+ isPointInside(_point) {
333
+ const x_inside = _point.x >= this.pos.x - this.size.x && _point.x <= this.pos.x + this.size.x;
334
+ const y_inside = _point.y >= this.pos.y - this.size.y && _point.y <= this.pos.y + this.size.y;
335
+ return x_inside && y_inside;
306
336
  }
307
337
  };
308
338
 
309
- // src/nodes/CircleBoundingBox.ts
339
+ // src/nodes/boundingBox/CircleBoundingBox.ts
310
340
  var CircleBoundingBox = class _CircleBoundingBox extends BoundingBox {
311
341
  radius;
312
342
  constructor(scene, radius) {
@@ -315,15 +345,28 @@ var CircleBoundingBox = class _CircleBoundingBox extends BoundingBox {
315
345
  }
316
346
  checkCollision(other) {
317
347
  if (other instanceof _CircleBoundingBox) {
318
- const thisPoint = this.scene?.camera.transformCoordinates(new Vector2D(this.pos.x, this.pos.y));
319
- const otherPoint = this.scene?.camera.transformCoordinates(new Vector2D(other.pos.x, other.pos.y));
320
- if (!thisPoint || !otherPoint) return false;
321
- const dx = thisPoint.x - otherPoint.x;
322
- const dy = thisPoint.y - otherPoint.y;
348
+ const dx = this.pos.x - other.pos.x;
349
+ const dy = this.pos.y - other.pos.y;
323
350
  const distance = Math.sqrt(dx * dx + dy * dy);
324
- return distance < this.radius + other.radius;
351
+ if (distance < this.radius + other.radius) {
352
+ const overlap = this.radius + other.radius - distance;
353
+ const mtv = distance === 0 ? new Vector2D(overlap, 0) : new Vector2D(dx / distance * overlap, dy / distance * overlap);
354
+ return { isColliding: true, mtv };
355
+ }
325
356
  }
326
- return false;
357
+ if (other instanceof SquareBoundingBox) {
358
+ const closestX = Math.max(other.pos.x - other.size.x / 2, Math.min(this.pos.x, other.pos.x + other.size.x / 2));
359
+ const closestY = Math.max(other.pos.y - other.size.y / 2, Math.min(this.pos.y, other.pos.y + other.size.y / 2));
360
+ const dx = this.pos.x - closestX;
361
+ const dy = this.pos.y - closestY;
362
+ const distance = Math.sqrt(dx * dx + dy * dy);
363
+ if (distance < this.radius) {
364
+ const overlap = this.radius - distance;
365
+ const mtv = distance === 0 ? new Vector2D(overlap, 0) : new Vector2D(dx / distance * overlap, dy / distance * overlap);
366
+ return { isColliding: true, mtv };
367
+ }
368
+ }
369
+ return { isColliding: false };
327
370
  }
328
371
  isPointInside(_point) {
329
372
  const point = this.scene?.camera.transformCoordinates(_point);
@@ -335,40 +378,104 @@ var CircleBoundingBox = class _CircleBoundingBox extends BoundingBox {
335
378
  }
336
379
  };
337
380
 
338
- // src/nodes/Circle.ts
339
- var Circle = class extends Node {
340
- vel = new Vector2D();
341
- size = 10;
342
- friction = 0.1;
343
- boundingBox = new CircleBoundingBox(this.scene, this.size);
344
- constructor(scene, config) {
345
- super(scene);
346
- this.vel = config?.vel || new Vector2D();
347
- this.size = config?.size || 10;
348
- this.friction = config?.friction || 0.1;
349
- if (config?.startsWithBoundingBox) {
350
- this.boundingBox = new CircleBoundingBox(scene, this.size);
351
- this.addObject(this.boundingBox);
381
+ // src/nodes/Camera.ts
382
+ var Camera = class extends Node {
383
+ zoom;
384
+ constructor(zoom = 1) {
385
+ super();
386
+ this.zoom = zoom;
387
+ }
388
+ update(deltaTime) {
389
+ }
390
+ transformCoordinates(position) {
391
+ return new Vector2D((position.x - this.pos.x) * this.zoom, (position.y - this.pos.y) * this.zoom);
392
+ }
393
+ };
394
+
395
+ // src/nodes/physics/StaticBody.ts
396
+ var StaticBody = class extends Node {
397
+ shape;
398
+ constructor(scene, shape) {
399
+ super(scene, { pos: shape.pos });
400
+ this.shape = shape;
401
+ this.pos = shape.pos;
402
+ }
403
+ update(_dt) {
404
+ if (this.shape.update && typeof this.shape.update === "function") {
405
+ this.shape.update(_dt);
352
406
  }
353
407
  }
354
408
  draw(ctx, selected) {
355
- const cameraPos = this.scene?.camera.pos || new Vector2D();
356
- const cameraScale = this.scene?.camera.zoom || 1;
357
- const relativePos = new Vector2D((this.pos.x - cameraPos.x) * cameraScale, (this.pos.y - cameraPos.y) * cameraScale);
358
- if (selected) {
359
- ctx.beginPath();
360
- ctx.arc(relativePos.x, relativePos.y, this.size + 5, 0, Math.PI * 2);
361
- ctx.fillStyle = "transparent";
362
- ctx.lineWidth = 3;
363
- ctx.strokeStyle = "white";
364
- ctx.stroke();
365
- ctx.closePath();
409
+ if (this.shape.draw) this.shape.draw(ctx, selected);
410
+ }
411
+ };
412
+
413
+ // src/nodes/physics/KinematicBody.ts
414
+ var KinematicBody = class _KinematicBody extends Node {
415
+ shape;
416
+ velocity = new Vector2D();
417
+ speed = 1500;
418
+ friction = 0.92;
419
+ maxSpeed = 800;
420
+ isColliding = false;
421
+ constructor(scene, shape, config) {
422
+ super(scene, { pos: shape.pos });
423
+ this.shape = shape;
424
+ if (config?.speed) this.speed = config.speed;
425
+ if (config?.friction) this.friction = config.friction;
426
+ if (config?.maxSpeed) this.maxSpeed = config.maxSpeed;
427
+ }
428
+ applyForce(x, y, dt) {
429
+ const len = Math.sqrt(x * x + y * y);
430
+ if (len > 0) {
431
+ const scale = this.speed * dt / len;
432
+ this.velocity.x += x * scale;
433
+ this.velocity.y += y * scale;
434
+ }
435
+ }
436
+ update(dt) {
437
+ this.velocity.x *= this.friction;
438
+ this.velocity.y *= this.friction;
439
+ const currentSpeed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y);
440
+ if (currentSpeed > this.maxSpeed) {
441
+ const scale = this.maxSpeed / currentSpeed;
442
+ this.velocity.x *= scale;
443
+ this.velocity.y *= scale;
444
+ }
445
+ if (Math.abs(this.velocity.x) < 0.1) this.velocity.x = 0;
446
+ if (Math.abs(this.velocity.y) < 0.1) this.velocity.y = 0;
447
+ this.pos.x += this.velocity.x * dt;
448
+ this.pos.y += this.velocity.y * dt;
449
+ this.shape.pos = this.pos;
450
+ if (this.shape.update) this.shape.update(dt);
451
+ this.resolveCollisions();
452
+ }
453
+ resolveCollisions() {
454
+ this.isColliding = false;
455
+ if (!this.shape.boundingBox) return;
456
+ if (!this.scene) return;
457
+ for (const obj of this.scene.children) {
458
+ if (obj === this) continue;
459
+ let otherBBox = null;
460
+ if (obj instanceof StaticBody || obj instanceof _KinematicBody) {
461
+ if (obj.shape.boundingBox) otherBBox = obj.shape.boundingBox;
462
+ } else {
463
+ if (obj.boundingBox) otherBBox = obj.boundingBox;
464
+ }
465
+ if (otherBBox) {
466
+ const result = this.shape.boundingBox.checkCollision(otherBBox);
467
+ if (result.isColliding && result.mtv) {
468
+ this.isColliding = true;
469
+ this.pos.x += result.mtv.x;
470
+ this.pos.y += result.mtv.y;
471
+ this.shape.pos = this.pos;
472
+ if (this.shape.boundingBox) this.shape.boundingBox.pos = this.pos;
473
+ }
474
+ }
366
475
  }
367
- ctx.beginPath();
368
- ctx.arc(relativePos.x, relativePos.y, this.size, 0, Math.PI * 2);
369
- ctx.fillStyle = "blue";
370
- ctx.fill();
371
- ctx.closePath();
476
+ }
477
+ draw(ctx, selected) {
478
+ if (this.shape.draw) this.shape.draw(ctx, selected);
372
479
  }
373
480
  };
374
481
 
@@ -394,14 +501,14 @@ var InputManager = class extends Node {
394
501
  this.mousePos = new Vector2D(event.clientX, event.clientY);
395
502
  }
396
503
  handleKeyDown(event) {
397
- const key = event.key;
504
+ const key = event.code;
398
505
  if (!this.keyState.get(key)) {
399
506
  this.keyState.set(key, true);
400
507
  this.keyJustPressed.add(key);
401
508
  }
402
509
  }
403
510
  handleKeyUp(event) {
404
- const key = event.key;
511
+ const key = event.code;
405
512
  this.keyState.set(key, false);
406
513
  this.keyJustPressed.delete(key);
407
514
  }
@@ -440,10 +547,12 @@ var InputManager = class extends Node {
440
547
  };
441
548
 
442
549
  // src/nodes/Scene.ts
443
- var Scene2 = class extends Node {
550
+ var Scene = class extends Node {
444
551
  camera;
445
552
  inputManager;
446
553
  ctx;
554
+ animationFrameId = null;
555
+ running = false;
447
556
  lastTime = 0;
448
557
  totalTime = 0;
449
558
  constructor(ctx, opts) {
@@ -468,7 +577,7 @@ var Scene2 = class extends Node {
468
577
  canvas.width = width;
469
578
  canvas.height = height;
470
579
  }
471
- update(deltaTime) {
580
+ updateFrame(deltaTime) {
472
581
  this.camera.update(deltaTime);
473
582
  this.children.forEach((child) => {
474
583
  if (isUpdatable(child)) {
@@ -476,16 +585,20 @@ var Scene2 = class extends Node {
476
585
  }
477
586
  });
478
587
  }
479
- draw(ctx) {
480
- ctx.fillStyle = ctx.canvas.style.backgroundColor;
588
+ drawFrame(ctx) {
589
+ ctx.fillStyle = ctx.canvas.style.backgroundColor || "black";
481
590
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
591
+ ctx.save();
592
+ ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
482
593
  this.children.forEach((child) => {
483
594
  if (isDrawable(child)) {
484
595
  child.draw(ctx);
485
596
  }
486
597
  });
598
+ ctx.restore();
487
599
  }
488
- gameLoop(callback, targetFPS = 60) {
600
+ run(callback, targetFPS = 60) {
601
+ this.running = true;
489
602
  const frameInterval = 1e3 / targetFPS;
490
603
  let lastFrameTime = 0;
491
604
  const loop = (currentTime) => {
@@ -496,8 +609,8 @@ var Scene2 = class extends Node {
496
609
  this.lastTime = currentTime;
497
610
  this.totalTime += deltaTime;
498
611
  lastFrameTime = currentTime - elapsed % frameInterval;
499
- this.update(deltaTime);
500
- this.draw(this.ctx);
612
+ this.updateFrame(deltaTime);
613
+ this.drawFrame(this.ctx);
501
614
  if (callback) {
502
615
  callback(deltaTime, this.totalTime);
503
616
  }
@@ -509,7 +622,14 @@ var Scene2 = class extends Node {
509
622
  requestAnimationFrame(loop);
510
623
  };
511
624
  this.lastTime = performance.now();
512
- requestAnimationFrame(loop);
625
+ this.animationFrameId = requestAnimationFrame(loop);
626
+ }
627
+ stop() {
628
+ if (this.animationFrameId) {
629
+ cancelAnimationFrame(this.animationFrameId);
630
+ this.animationFrameId = null;
631
+ }
632
+ this.running = false;
513
633
  }
514
634
  };
515
635
  function isUpdatable(obj) {
@@ -519,6 +639,118 @@ function isDrawable(obj) {
519
639
  return "draw" in obj;
520
640
  }
521
641
 
642
+ // src/nodes/shapes/Circle.ts
643
+ var Circle = class extends Node {
644
+ vel = new Vector2D();
645
+ size = 10;
646
+ friction = 0.1;
647
+ style = {
648
+ fillStyle: "blue"
649
+ };
650
+ constructor(scene, config) {
651
+ super(scene);
652
+ this.vel = config?.vel || new Vector2D();
653
+ this.size = config?.size || 10;
654
+ this.friction = config?.friction || 0.1;
655
+ this.style = { ...this.style, ...config?.style };
656
+ if (config?.startsWithBoundingBox) {
657
+ this.boundingBox = new CircleBoundingBox(scene, this.size);
658
+ this.addObject(this.boundingBox);
659
+ }
660
+ }
661
+ update(_deltaTime) {
662
+ if (this.boundingBox) {
663
+ this.boundingBox.pos = this.pos;
664
+ }
665
+ }
666
+ draw(ctx, selected) {
667
+ const cameraPos = this.scene?.camera.pos || new Vector2D();
668
+ const cameraScale = this.scene?.camera.zoom || 1;
669
+ const relativePos = new Vector2D((this.pos.x - cameraPos.x) * cameraScale, (this.pos.y - cameraPos.y) * cameraScale);
670
+ if (selected) {
671
+ ctx.beginPath();
672
+ ctx.arc(relativePos.x, relativePos.y, this.size + 5, 0, Math.PI * 2);
673
+ ctx.fillStyle = "transparent";
674
+ ctx.lineWidth = 3;
675
+ ctx.strokeStyle = "white";
676
+ ctx.stroke();
677
+ ctx.closePath();
678
+ }
679
+ ctx.beginPath();
680
+ ctx.arc(relativePos.x, relativePos.y, this.size, 0, Math.PI * 2);
681
+ ctx.fillStyle = this.style.fillStyle || "blue";
682
+ ctx.fill();
683
+ if (this.style.strokeStyle) {
684
+ ctx.strokeStyle = this.style.strokeStyle;
685
+ ctx.lineWidth = this.style.lineWidth || 1;
686
+ ctx.stroke();
687
+ }
688
+ ctx.closePath();
689
+ }
690
+ };
691
+
692
+ // src/nodes/shapes/Square.ts
693
+ var Square = class extends Node {
694
+ vel = new Vector2D();
695
+ size;
696
+ friction = 0.1;
697
+ style = {
698
+ fillStyle: "orange"
699
+ };
700
+ constructor(scene, config) {
701
+ super(scene);
702
+ this.vel = config?.vel || new Vector2D();
703
+ this.size = config?.size || new Vector2D(10, 10);
704
+ this.friction = config?.friction || 0.1;
705
+ this.style = { ...this.style, ...config?.style };
706
+ if (config?.startsWithBoundingBox) {
707
+ this.boundingBox = new SquareBoundingBox(scene, this.size);
708
+ this.addObject(this.boundingBox);
709
+ }
710
+ }
711
+ update(_deltaTime) {
712
+ if (this.boundingBox) {
713
+ this.boundingBox.pos = this.pos;
714
+ }
715
+ }
716
+ draw(ctx, selected) {
717
+ const cameraPos = this.scene?.camera.pos || new Vector2D();
718
+ const cameraScale = this.scene?.camera.zoom || 1;
719
+ const relativePos = new Vector2D(
720
+ (this.pos.x - cameraPos.x) * cameraScale,
721
+ (this.pos.y - cameraPos.y) * cameraScale
722
+ );
723
+ const scaledSize = new Vector2D(this.size.x * cameraScale, this.size.y * cameraScale);
724
+ if (selected) {
725
+ ctx.strokeStyle = "white";
726
+ ctx.lineWidth = 3;
727
+ ctx.strokeRect(
728
+ relativePos.x - scaledSize.x / 2 - 5,
729
+ relativePos.y - scaledSize.y / 2 - 5,
730
+ scaledSize.x + 10,
731
+ scaledSize.y + 10
732
+ );
733
+ }
734
+ ctx.fillStyle = this.style.fillStyle || "orange";
735
+ ctx.fillRect(
736
+ relativePos.x - scaledSize.x / 2,
737
+ relativePos.y - scaledSize.y / 2,
738
+ scaledSize.x,
739
+ scaledSize.y
740
+ );
741
+ if (this.style.strokeStyle) {
742
+ ctx.strokeStyle = this.style.strokeStyle;
743
+ ctx.lineWidth = this.style.lineWidth || 1;
744
+ ctx.strokeRect(
745
+ relativePos.x - scaledSize.x / 2,
746
+ relativePos.y - scaledSize.y / 2,
747
+ scaledSize.x,
748
+ scaledSize.y
749
+ );
750
+ }
751
+ }
752
+ };
753
+
522
754
  // src/main.ts
523
755
  function getCanvas(id) {
524
756
  const canvas = document.getElementById(id);
@@ -532,8 +764,12 @@ export {
532
764
  Camera,
533
765
  Circle,
534
766
  CircleBoundingBox,
767
+ KinematicBody,
535
768
  Node,
536
- Scene2 as Scene,
769
+ Scene,
770
+ Square,
771
+ SquareBoundingBox,
772
+ StaticBody,
537
773
  TWO_PI,
538
774
  Vector2D,
539
775
  getCanvas
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiant/canvable",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "A tool for building canvas apps",
5
5
  "main": "./dist/main.js",
6
6
  "module": "./dist/main.mjs",