@jiant/canvable 0.0.3 → 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.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