emeraldengine 2.2.0 → 3.0.0

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.
Files changed (162) hide show
  1. package/README.md +2359 -968
  2. package/dist/types/index.d.ts +72 -32
  3. package/dist/types/src/Animator.d.ts +50 -0
  4. package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
  5. package/dist/types/src/Camera.d.ts +122 -0
  6. package/dist/types/src/CameraController.d.ts +107 -0
  7. package/dist/types/src/CanvasText.d.ts +91 -0
  8. package/dist/types/src/CollisionLayers.d.ts +58 -0
  9. package/dist/types/{Color.d.ts → src/Color.d.ts} +5 -6
  10. package/dist/types/src/Coroutine.d.ts +111 -0
  11. package/dist/types/src/DebugOverlay.d.ts +86 -0
  12. package/dist/types/src/Drawable.d.ts +271 -0
  13. package/dist/types/src/Easing.d.ts +22 -0
  14. package/dist/types/src/Emerald.d.ts +420 -0
  15. package/dist/types/src/EmeraldDB.d.ts +159 -0
  16. package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
  17. package/dist/types/src/GLUtils.d.ts +4 -0
  18. package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
  19. package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +60 -23
  20. package/dist/types/src/Interpolator.d.ts +66 -0
  21. package/dist/types/src/Material.d.ts +87 -0
  22. package/dist/types/src/MathUtils.d.ts +80 -0
  23. package/dist/types/src/ParticleEmitter.d.ts +131 -0
  24. package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
  25. package/dist/types/src/Pool.d.ts +53 -0
  26. package/dist/types/src/PostEffects.d.ts +68 -0
  27. package/dist/types/src/PostProcessor.d.ts +124 -0
  28. package/dist/types/src/RenderTarget.d.ts +56 -0
  29. package/dist/types/src/Scene.d.ts +62 -0
  30. package/dist/types/src/ScreenEffects.d.ts +111 -0
  31. package/dist/types/src/Serializer.d.ts +86 -0
  32. package/dist/types/src/Shaders.d.ts +2 -0
  33. package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
  34. package/dist/types/src/SpatialGrid.d.ts +50 -0
  35. package/dist/types/src/SpriteBatch.d.ts +89 -0
  36. package/dist/types/src/StateMachine.d.ts +59 -0
  37. package/dist/types/src/Storage.d.ts +89 -0
  38. package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
  39. package/dist/types/src/TextureAtlas.d.ts +55 -0
  40. package/dist/types/src/Tilemap.d.ts +91 -0
  41. package/dist/types/src/Time.d.ts +53 -0
  42. package/dist/types/src/Timer.d.ts +54 -0
  43. package/dist/types/src/Transform.d.ts +94 -0
  44. package/dist/types/src/Tween.d.ts +81 -0
  45. package/dist/types/src/UI.d.ts +121 -0
  46. package/dist/types/src/components/Behaviour.d.ts +71 -0
  47. package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
  48. package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
  49. package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
  50. package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
  51. package/dist/types/src/components/Collider.d.ts +96 -0
  52. package/dist/types/src/components/GameObject.d.ts +135 -0
  53. package/dist/types/src/components/RigidBody.d.ts +183 -0
  54. package/dist/types/src/importers/Aseprite.d.ts +79 -0
  55. package/dist/types/src/importers/TiledMap.d.ts +62 -0
  56. package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
  57. package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
  58. package/dist/types/src/managers/AssetManager.d.ts +116 -0
  59. package/dist/types/src/managers/AudioManager.d.ts +259 -0
  60. package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
  61. package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
  62. package/dist/types/src/managers/GLManager.d.ts +84 -0
  63. package/dist/types/src/managers/GLState.d.ts +37 -0
  64. package/dist/types/src/managers/IDManager.d.ts +31 -0
  65. package/dist/types/src/managers/InputManager.d.ts +290 -0
  66. package/dist/types/src/managers/NetworkManager.d.ts +93 -0
  67. package/dist/types/src/managers/RenderStats.d.ts +34 -0
  68. package/dist/types/src/managers/SceneManager.d.ts +44 -0
  69. package/dist/types/src/managers/ShaderManager.d.ts +55 -0
  70. package/dist/types/src/managers/TextureManager.d.ts +102 -0
  71. package/dist/types/src/particlesystem/Particle.d.ts +64 -0
  72. package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
  73. package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
  74. package/index.js +72 -0
  75. package/package.json +74 -60
  76. package/src/Animator.js +95 -0
  77. package/src/BitmapText.js +6 -5
  78. package/src/Camera.js +183 -0
  79. package/src/CameraController.js +192 -0
  80. package/src/CanvasText.js +281 -0
  81. package/src/CollisionLayers.js +86 -0
  82. package/src/Color.js +18 -18
  83. package/src/Coroutine.js +259 -0
  84. package/src/DebugOverlay.js +246 -0
  85. package/src/Drawable.js +842 -555
  86. package/src/Easing.js +57 -0
  87. package/src/Emerald.js +1150 -459
  88. package/src/EmeraldDB.js +328 -0
  89. package/src/FPSCounter.js +43 -43
  90. package/src/GLUtils.js +60 -67
  91. package/src/Instance.js +41 -5
  92. package/src/InstancedTexture.js +251 -118
  93. package/src/Interpolator.js +124 -0
  94. package/src/Material.js +202 -0
  95. package/src/MathUtils.js +133 -0
  96. package/src/ParticleEmitter.js +284 -0
  97. package/src/Physics.js +186 -5
  98. package/src/Pool.js +85 -0
  99. package/src/PostEffects.js +296 -0
  100. package/src/PostProcessor.js +304 -0
  101. package/src/RenderTarget.js +134 -0
  102. package/src/Scene.js +115 -83
  103. package/src/ScreenEffects.js +266 -0
  104. package/src/Serializer.js +131 -0
  105. package/src/Shaders.js +150 -165
  106. package/src/Shapes.js +118 -129
  107. package/src/SpatialGrid.js +111 -0
  108. package/src/SpriteBatch.js +299 -0
  109. package/src/StateMachine.js +82 -0
  110. package/src/Storage.js +175 -47
  111. package/src/Texture.js +58 -67
  112. package/src/TextureAtlas.js +96 -0
  113. package/src/Tilemap.js +274 -0
  114. package/src/Time.js +51 -6
  115. package/src/Timer.js +99 -0
  116. package/src/Transform.js +100 -7
  117. package/src/Tween.js +160 -0
  118. package/src/UI.js +394 -0
  119. package/src/components/Behaviour.js +90 -0
  120. package/src/components/BoxCollider.js +22 -3
  121. package/src/components/CircleCollider.js +19 -3
  122. package/src/components/CircleColliderDebug.js +24 -24
  123. package/src/components/Collider.js +140 -34
  124. package/src/components/GameObject.js +130 -21
  125. package/src/components/RigidBody.js +123 -2
  126. package/src/importers/Aseprite.js +142 -0
  127. package/src/importers/TiledMap.js +158 -0
  128. package/src/lights/DirectionalLight.js +6 -15
  129. package/src/lights/PointLight.js +4 -4
  130. package/src/managers/AssetManager.js +239 -0
  131. package/src/managers/AudioManager.js +565 -146
  132. package/src/managers/EventManager.js +488 -477
  133. package/src/managers/GLManager.js +57 -0
  134. package/src/managers/GLState.js +70 -0
  135. package/src/managers/IDManager.js +24 -2
  136. package/src/managers/InputManager.js +779 -0
  137. package/src/managers/NetworkManager.js +178 -0
  138. package/src/managers/RenderStats.js +34 -0
  139. package/src/managers/SceneManager.js +30 -0
  140. package/src/managers/ShaderManager.js +0 -2
  141. package/src/managers/TextureManager.js +218 -0
  142. package/src/particlesystem/Particle.js +82 -7
  143. package/src/particlesystem/ParticleSettings.js +21 -3
  144. package/src/particlesystem/Particles.js +80 -31
  145. package/dist/types/Drawable.d.ts +0 -157
  146. package/dist/types/Emerald.d.ts +0 -73
  147. package/dist/types/GLUtils.d.ts +0 -6
  148. package/dist/types/Scene.d.ts +0 -39
  149. package/dist/types/Shaders.d.ts +0 -4
  150. package/dist/types/Storage.d.ts +0 -46
  151. package/dist/types/Time.d.ts +0 -22
  152. package/dist/types/Transform.d.ts +0 -41
  153. package/dist/types/components/BoxColliderDebug.d.ts +0 -19
  154. package/dist/types/components/CircleColliderDebug.d.ts +0 -19
  155. package/dist/types/components/Collider.d.ts +0 -53
  156. package/dist/types/components/GameObject.d.ts +0 -72
  157. package/dist/types/components/RigidBody.d.ts +0 -104
  158. package/dist/types/managers/AudioManager.d.ts +0 -60
  159. package/dist/types/managers/GLManager.d.ts +0 -47
  160. package/dist/types/managers/IDManager.d.ts +0 -21
  161. package/dist/types/managers/SceneManager.d.ts +0 -22
  162. package/dist/types/particlesystem/Particle.d.ts +0 -42
@@ -0,0 +1,284 @@
1
+ import GameObject from "./components/GameObject.js";
2
+ import Texture from "./Texture.js";
3
+ import Color from "./Color.js";
4
+ import { Vector2, Vector3 } from "./Physics.js";
5
+
6
+ const DEFAULT_CAPACITY = 256;
7
+ const PARK_Y = -1e9;
8
+
9
+ /**
10
+ * @class ParticleEmitter
11
+ * @description A reliable, allocation-free particle system built from a fixed
12
+ * pool of ordinary textured GameObjects. Each live particle's transform, tint
13
+ * and opacity are driven by hand every frame — there is no instanced-draw /
14
+ * dynamic-buffer lifecycle to desync, so it keeps drawing for the whole session
15
+ * (unlike the InstancedTexture-based `Particles`, which can stop emitting after
16
+ * heavy reuse on some GPUs).
17
+ *
18
+ * Spawn with `burst(n, cfg)` or `emit(cfg)`; advance with `update(dt)` once per
19
+ * frame. Every `cfg` field is optional and has a sensible default, so the
20
+ * minimal call is `emitter.burst(8, { x, y })`.
21
+ *
22
+ * @example
23
+ * const fx = new ParticleEmitter(scene, { texture: smokeImg, layer: 50 });
24
+ * fx.burst(12, {
25
+ * x: 100, y: 200, dir: -Math.PI / 2, spread: Math.PI, speed: 180,
26
+ * gy: -300, drag: 2, life: 0.4, size: 8, sFrom: 1, sTo: 0.1,
27
+ * aFrom: 0.7, aTo: 0, cr: 255, cg: 220, cb: 120, additive: true,
28
+ * shape: "circle", radius: 6,
29
+ * });
30
+ * // in the loop: fx.update(dt);
31
+ */
32
+ class ParticleEmitter {
33
+ /**
34
+ * @param {Scene} scene - Scene the particle GameObjects are added to
35
+ * @param {Object} [options]
36
+ * @param {string|HTMLImageElement} [options.texture] - Particle sprite source
37
+ * @param {number} [options.capacity=256] - Pool size (max live particles)
38
+ * @param {number} [options.layer=0] - Render layer for every particle
39
+ * @param {boolean} [options.pixelart=false] - Pixel-art sampling for the sprite
40
+ * @param {Function} [options.spriteFactory] - `(texture, options) => Drawable`
41
+ * override used to build each particle's drawable (handy for tests / custom
42
+ * shaders). Defaults to a non-lit `Texture`.
43
+ */
44
+ constructor(scene, options = {}) {
45
+ this.scene = scene;
46
+ this.capacity = options.capacity ?? DEFAULT_CAPACITY;
47
+ this.layer = options.layer ?? 0;
48
+
49
+ const texture = options.texture ?? options.texturePath ?? null;
50
+ const makeSprite =
51
+ options.spriteFactory ||
52
+ ((tex) =>
53
+ new Texture(
54
+ tex,
55
+ 0,
56
+ 0,
57
+ 1,
58
+ 1,
59
+ 0,
60
+ false,
61
+ options.pixelart ?? false,
62
+ false
63
+ ));
64
+
65
+ this.parts = [];
66
+ this.free = [];
67
+
68
+ for (let i = 0; i < this.capacity; i++) {
69
+ const go = new GameObject(
70
+ "particle",
71
+ new Vector3(0, PARK_Y, 0),
72
+ 0,
73
+ new Vector2(4, 4)
74
+ );
75
+ const drawable = makeSprite(texture, options);
76
+ if (drawable && typeof drawable.setUseLighting === "function") {
77
+ drawable.setUseLighting(false);
78
+ }
79
+ go.addComponent(drawable);
80
+ go.setLayer(this.layer);
81
+ go.setOpacity(0);
82
+ if (scene && typeof scene.add === "function") scene.add(go);
83
+
84
+ const p = {
85
+ go,
86
+ drawable,
87
+ color: new Color(255, 255, 255, 255),
88
+ alive: false,
89
+ x: 0,
90
+ y: 0,
91
+ vx: 0,
92
+ vy: 0,
93
+ gx: 0,
94
+ gy: 0,
95
+ drag: 0,
96
+ age: 0,
97
+ life: 1,
98
+ base: 4,
99
+ sFrom: 1,
100
+ sTo: 1,
101
+ aFrom: 1,
102
+ aTo: 0,
103
+ rot: 0,
104
+ rotSpeed: 0,
105
+ additive: false,
106
+ };
107
+ this.parts.push(p);
108
+ this.free.push(p);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * @member activeCount
114
+ * @description Number of particles currently alive.
115
+ */
116
+ get activeCount() {
117
+ return this.capacity - this.free.length;
118
+ }
119
+
120
+ /**
121
+ * @method emit
122
+ * @description Spawns a single particle. Returns the emitter for chaining.
123
+ * @param {Object} cfg - Spawn config (see class example; all fields optional)
124
+ */
125
+ emit(cfg = {}) {
126
+ this._spawn(cfg);
127
+ return this;
128
+ }
129
+
130
+ /**
131
+ * @method burst
132
+ * @description Spawns `n` particles with the same config.
133
+ * @param {number} n - How many to spawn
134
+ * @param {Object} cfg - Spawn config
135
+ */
136
+ burst(n, cfg = {}) {
137
+ for (let i = 0; i < n; i++) this._spawn(cfg);
138
+ return this;
139
+ }
140
+
141
+ /** @private */
142
+ _spawn(cfg) {
143
+ const p = this.free.pop();
144
+ if (!p) return;
145
+
146
+ p.alive = true;
147
+
148
+ const x = cfg.x || 0;
149
+ const y = cfg.y || 0;
150
+ let ox = 0;
151
+ let oy = 0;
152
+ const shape = cfg.shape;
153
+ if (shape === "ring") {
154
+ const a = Math.random() * Math.PI * 2;
155
+ const r = cfg.radius || 0;
156
+ ox = Math.cos(a) * r;
157
+ oy = Math.sin(a) * r;
158
+ } else if (shape === "circle") {
159
+ const a = Math.random() * Math.PI * 2;
160
+ const r = Math.sqrt(Math.random()) * (cfg.radius || 0);
161
+ ox = Math.cos(a) * r;
162
+ oy = Math.sin(a) * r;
163
+ } else if (shape === "box") {
164
+ ox = (Math.random() - 0.5) * (cfg.boxw || 0);
165
+ oy = (Math.random() - 0.5) * (cfg.boxh || 0);
166
+ }
167
+ p.x = x + ox;
168
+ p.y = y + oy;
169
+
170
+ const dir = cfg.dir || 0;
171
+ const spread = cfg.spread || 0;
172
+ const speed = cfg.speed || 0;
173
+ const ang = dir + (Math.random() - 0.5) * spread;
174
+ const spd = speed * (0.6 + Math.random() * 0.4);
175
+ p.vx = Math.cos(ang) * spd;
176
+ p.vy = Math.sin(ang) * spd;
177
+
178
+ p.gx = cfg.gx || 0;
179
+ p.gy = cfg.gy || 0;
180
+ p.drag = cfg.drag || 0;
181
+ p.age = 0;
182
+ p.life = cfg.life || 0.5;
183
+ p.base = cfg.size ?? 6;
184
+ p.sFrom = cfg.sFrom ?? 1;
185
+ p.sTo = cfg.sTo ?? 0;
186
+ p.aFrom = cfg.aFrom ?? 1;
187
+ p.aTo = cfg.aTo ?? 0;
188
+ p.rot = cfg.rot ?? Math.random() * Math.PI * 2;
189
+ p.rotSpeed = cfg.rotSpeed || 0;
190
+
191
+ p.color.r = cfg.cr ?? 255;
192
+ p.color.g = cfg.cg ?? 255;
193
+ p.color.b = cfg.cb ?? 255;
194
+ p.color.a = 255;
195
+ if (p.drawable) {
196
+ if (typeof p.drawable.setColor === "function")
197
+ p.drawable.setColor(p.color);
198
+ if (typeof p.drawable.setBlendMode === "function") {
199
+ p.drawable.setBlendMode(cfg.additive ? "additive" : "normal");
200
+ }
201
+ }
202
+
203
+ const tr = p.go.transform;
204
+ tr.position.x = p.x;
205
+ tr.position.y = p.y;
206
+ tr.scale.x = p.base * p.sFrom;
207
+ tr.scale.y = p.base * p.sFrom;
208
+ tr.rotation = p.rot;
209
+ p.go.setOpacity(p.aFrom);
210
+ }
211
+
212
+ /**
213
+ * @method update
214
+ * @description Advances and renders every live particle. Call once per frame.
215
+ * @param {number} dt - Seconds since the last frame
216
+ */
217
+ update(dt) {
218
+ for (let i = 0; i < this.parts.length; i++) {
219
+ const p = this.parts[i];
220
+ if (!p.alive) continue;
221
+
222
+ p.age += dt;
223
+ if (p.age >= p.life) {
224
+ p.alive = false;
225
+ p.go.setOpacity(0);
226
+ p.go.transform.position.y = PARK_Y;
227
+ this.free.push(p);
228
+ continue;
229
+ }
230
+
231
+ const t = p.age / p.life;
232
+ p.vx += p.gx * dt;
233
+ p.vy += p.gy * dt;
234
+ if (p.drag) {
235
+ const d = Math.max(0, 1 - p.drag * dt);
236
+ p.vx *= d;
237
+ p.vy *= d;
238
+ }
239
+ p.x += p.vx * dt;
240
+ p.y += p.vy * dt;
241
+ p.rot += p.rotSpeed * dt;
242
+
243
+ const sc = p.base * (p.sFrom + (p.sTo - p.sFrom) * t);
244
+ const tr = p.go.transform;
245
+ tr.position.x = p.x;
246
+ tr.position.y = p.y;
247
+ tr.scale.x = sc;
248
+ tr.scale.y = sc;
249
+ tr.rotation = p.rot;
250
+ p.go.setOpacity(p.aFrom + (p.aTo - p.aFrom) * t);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * @method reset
256
+ * @description Kills every live particle immediately (no fade).
257
+ */
258
+ reset() {
259
+ for (let i = 0; i < this.parts.length; i++) {
260
+ const p = this.parts[i];
261
+ if (!p.alive) continue;
262
+ p.alive = false;
263
+ p.go.setOpacity(0);
264
+ p.go.transform.position.y = PARK_Y;
265
+ }
266
+ this.free = this.parts.slice();
267
+ }
268
+
269
+ /**
270
+ * @method destroy
271
+ * @description Removes every particle GameObject from the scene.
272
+ */
273
+ destroy() {
274
+ if (this.scene && typeof this.scene.remove === "function") {
275
+ for (let i = 0; i < this.parts.length; i++) {
276
+ this.scene.remove(this.parts[i].go);
277
+ }
278
+ }
279
+ this.parts = [];
280
+ this.free = [];
281
+ }
282
+ }
283
+
284
+ export default ParticleEmitter;
package/src/Physics.js CHANGED
@@ -1,5 +1,26 @@
1
1
  import * as planck from "planck";
2
2
 
3
+ /**
4
+ * @function dispatchCollision
5
+ * @description Calls onCollisionEnter/onCollisionExit on an object and its
6
+ * component list (Behaviours), passing the other object. Duck-typed so this
7
+ * module doesn't need to import the component classes.
8
+ * @private
9
+ */
10
+ function dispatchCollision(object, other, contact, type) {
11
+ if (!object) return;
12
+ if (typeof object[type] === "function") {
13
+ object[type](other, contact);
14
+ }
15
+ if (Array.isArray(object.components)) {
16
+ for (const comp of object.components) {
17
+ if (comp && typeof comp[type] === "function") {
18
+ comp[type](other, contact);
19
+ }
20
+ }
21
+ }
22
+ }
23
+
3
24
  /**
4
25
  * @class Physics
5
26
  * @description Represents the physics engine
@@ -15,6 +36,101 @@ class Physics {
15
36
  planck.Settings.velocityThreshold = velocityThreshold;
16
37
  this.gravity = gravity;
17
38
  this.scale = scale;
39
+
40
+ this.fixedTimeStep = 1 / 60;
41
+ this.maxSubSteps = 5;
42
+ /** @private */
43
+ this._accumulator = 0;
44
+
45
+ this._dispatchContacts();
46
+ }
47
+
48
+ /**
49
+ * @method _dispatchContacts
50
+ * @description Routes planck contacts to the owning objects so Behaviour
51
+ * components receive onCollisionEnter/onCollisionExit. Bodies created through
52
+ * RigidBody carry the owner via userData.
53
+ * @private
54
+ */
55
+ _dispatchContacts() {
56
+ const fire = (contact, type) => {
57
+ const a = contact.getFixtureA().getBody().getUserData();
58
+ const b = contact.getFixtureB().getBody().getUserData();
59
+ const objA = a && a.parentObject ? a.parentObject : null;
60
+ const objB = b && b.parentObject ? b.parentObject : null;
61
+ dispatchCollision(objA, objB, contact, type);
62
+ dispatchCollision(objB, objA, contact, type);
63
+ };
64
+ this.world.on("begin-contact", (contact) =>
65
+ fire(contact, "onCollisionEnter")
66
+ );
67
+ this.world.on("end-contact", (contact) => fire(contact, "onCollisionExit"));
68
+ }
69
+
70
+ /**
71
+ * @method raycast
72
+ * @description Casts a ray through the world and returns the closest hit.
73
+ * @param {Object} origin - World-space { x, y } start point
74
+ * @param {Object} direction - Ray direction { x, y } (need not be normalized)
75
+ * @param {number} maxDistance - Max ray length in world units
76
+ * @returns {{object, rigidBody, point, normal, fraction}|null}
77
+ */
78
+ raycast(origin, direction, maxDistance) {
79
+ const scale = this.scale;
80
+ const len = Math.hypot(direction.x, direction.y) || 1;
81
+ const dx = direction.x / len;
82
+ const dy = direction.y / len;
83
+ const p1 = new planck.Vec2(origin.x / scale, origin.y / scale);
84
+ const p2 = new planck.Vec2(
85
+ (origin.x + dx * maxDistance) / scale,
86
+ (origin.y + dy * maxDistance) / scale
87
+ );
88
+
89
+ let result = null;
90
+ this.world.rayCast(p1, p2, (fixture, point, normal, fraction) => {
91
+ const rb = fixture.getBody().getUserData();
92
+ result = {
93
+ object: rb && rb.parentObject ? rb.parentObject : null,
94
+ rigidBody: rb || null,
95
+ point: { x: point.x * scale, y: point.y * scale },
96
+ normal: { x: normal.x, y: normal.y },
97
+ fraction,
98
+ };
99
+ return fraction;
100
+ });
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * @method queryPoint
106
+ * @description Returns the objects whose colliders contain a world-space point.
107
+ * @param {Object} point - World-space { x, y }
108
+ * @returns {Array} - Owning objects at the point
109
+ */
110
+ queryPoint(point) {
111
+ const p = new planck.Vec2(point.x / this.scale, point.y / this.scale);
112
+ const results = [];
113
+ for (let body = this.world.getBodyList(); body; body = body.getNext()) {
114
+ for (let f = body.getFixtureList(); f; f = f.getNext()) {
115
+ if (f.testPoint(p)) {
116
+ const rb = body.getUserData();
117
+ if (rb && rb.parentObject) results.push(rb.parentObject);
118
+ break;
119
+ }
120
+ }
121
+ }
122
+ return results;
123
+ }
124
+
125
+ /**
126
+ * @method setFixedTimeStep
127
+ * @description Sets the fixed physics step (seconds) and optional substep cap.
128
+ * @param {number} step - Fixed step in seconds (e.g. 1/60)
129
+ * @param {number} [maxSubSteps] - Max steps per process() call (spiral guard)
130
+ */
131
+ setFixedTimeStep(step, maxSubSteps = this.maxSubSteps) {
132
+ if (step > 0) this.fixedTimeStep = step;
133
+ this.maxSubSteps = maxSubSteps;
18
134
  }
19
135
 
20
136
  /**
@@ -100,7 +216,22 @@ class Physics {
100
216
  * @param {number} dt - The delta time
101
217
  */
102
218
  process(dt) {
103
- this.world.step(dt);
219
+ if (!isFinite(dt) || dt <= 0) return;
220
+
221
+ this._accumulator += dt;
222
+ let steps = 0;
223
+ while (
224
+ this._accumulator >= this.fixedTimeStep &&
225
+ steps < this.maxSubSteps
226
+ ) {
227
+ this.world.step(this.fixedTimeStep);
228
+ this._accumulator -= this.fixedTimeStep;
229
+ steps++;
230
+ }
231
+
232
+ if (steps === this.maxSubSteps) {
233
+ this._accumulator = 0;
234
+ }
104
235
  }
105
236
 
106
237
  /**
@@ -111,6 +242,7 @@ class Physics {
111
242
  this.world = new planck.World({
112
243
  gravity: new planck.Vec2(0, this.gravity),
113
244
  });
245
+ this._accumulator = 0;
114
246
  }
115
247
 
116
248
  /**
@@ -152,8 +284,31 @@ class Physics {
152
284
  * @param {number} y - The y coordinate of the vector
153
285
  */
154
286
  class Vector2 {
155
- constructor(x, y) {
156
- return new planck.Vec2(x, y);
287
+ constructor(x = 0, y = 0) {
288
+ this.x = x;
289
+ this.y = y;
290
+ }
291
+
292
+ /**
293
+ * @method set
294
+ * @description Sets the x and y coordinates in place
295
+ * @param {number} x - The x coordinate
296
+ * @param {number} y - The y coordinate
297
+ * @returns {Vector2} - This vector
298
+ */
299
+ set(x, y) {
300
+ this.x = x;
301
+ this.y = y;
302
+ return this;
303
+ }
304
+
305
+ /**
306
+ * @method clone
307
+ * @description Returns a copy of the vector
308
+ * @returns {Vector2} - The cloned vector
309
+ */
310
+ clone() {
311
+ return new Vector2(this.x, this.y);
157
312
  }
158
313
 
159
314
  /**
@@ -195,8 +350,34 @@ class Vector2 {
195
350
  * @param {number} z - The z coordinate of the vector
196
351
  */
197
352
  class Vector3 {
198
- constructor(x, y, z) {
199
- return new planck.Vec3(x, y, z);
353
+ constructor(x = 0, y = 0, z = 0) {
354
+ this.x = x;
355
+ this.y = y;
356
+ this.z = z;
357
+ }
358
+
359
+ /**
360
+ * @method set
361
+ * @description Sets the x, y and z coordinates in place
362
+ * @param {number} x - The x coordinate
363
+ * @param {number} y - The y coordinate
364
+ * @param {number} z - The z coordinate
365
+ * @returns {Vector3} - This vector
366
+ */
367
+ set(x, y, z) {
368
+ this.x = x;
369
+ this.y = y;
370
+ this.z = z;
371
+ return this;
372
+ }
373
+
374
+ /**
375
+ * @method clone
376
+ * @description Returns a copy of the vector
377
+ * @returns {Vector3} - The cloned vector
378
+ */
379
+ clone() {
380
+ return new Vector3(this.x, this.y, this.z);
200
381
  }
201
382
 
202
383
  /**
package/src/Pool.js ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @class Pool
3
+ * @description Generic object pool to avoid per-frame allocations for churny
4
+ * objects (bullets, particles, enemies). Provide a factory and an optional
5
+ * reset function; acquire reused objects and release them when done.
6
+ *
7
+ * @example
8
+ * const bullets = new Pool(() => new Bullet(), (b, x, y) => b.spawn(x, y), 50);
9
+ * const b = bullets.acquire(px, py);
10
+ * // later...
11
+ * bullets.release(b);
12
+ */
13
+ class Pool {
14
+ /**
15
+ * @param {Function} factory - Creates a new pooled object
16
+ * @param {Function} [reset] - Called on acquire as reset(obj, ...args)
17
+ * @param {number} [initialSize] - Number of objects to pre-create
18
+ */
19
+ constructor(factory, reset = null, initialSize = 0) {
20
+ if (typeof factory !== "function") {
21
+ throw new Error("[Pool] > factory must be a function");
22
+ }
23
+ this.factory = factory;
24
+ this.reset = reset;
25
+ this.available = [];
26
+ this.active = new Set();
27
+
28
+ for (let i = 0; i < initialSize; i++) {
29
+ this.available.push(factory());
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @method acquire
35
+ * @description Returns a pooled object (reused or freshly created). Extra
36
+ * arguments are forwarded to the reset function.
37
+ * @returns {*} - The acquired object
38
+ */
39
+ acquire(...args) {
40
+ const obj = this.available.length ? this.available.pop() : this.factory();
41
+ this.active.add(obj);
42
+ if (this.reset) this.reset(obj, ...args);
43
+ return obj;
44
+ }
45
+
46
+ /**
47
+ * @method release
48
+ * @description Returns an object to the pool.
49
+ * @param {*} obj - The object to release
50
+ */
51
+ release(obj) {
52
+ if (this.active.delete(obj)) {
53
+ this.available.push(obj);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * @method releaseAll
59
+ * @description Returns every active object to the pool.
60
+ */
61
+ releaseAll() {
62
+ for (const obj of this.active) {
63
+ this.available.push(obj);
64
+ }
65
+ this.active.clear();
66
+ }
67
+
68
+ /**
69
+ * @member activeCount
70
+ * @description Number of currently-acquired objects.
71
+ */
72
+ get activeCount() {
73
+ return this.active.size;
74
+ }
75
+
76
+ /**
77
+ * @member size
78
+ * @description Total objects managed by the pool (active + available).
79
+ */
80
+ get size() {
81
+ return this.available.length + this.active.size;
82
+ }
83
+ }
84
+
85
+ export default Pool;