emeraldengine 3.0.0 → 3.1.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 (103) hide show
  1. package/README.md +1498 -1659
  2. package/dist/types/index.d.ts +4 -1
  3. package/dist/types/src/Animator.d.ts +1 -1
  4. package/dist/types/src/CollisionLayers.d.ts +2 -2
  5. package/dist/types/src/Color.d.ts +1 -0
  6. package/dist/types/src/Drawable.d.ts +1 -1
  7. package/dist/types/src/EmeraldDB.d.ts +2 -2
  8. package/dist/types/src/InstancedTexture.d.ts +17 -2
  9. package/dist/types/src/Material.d.ts +2 -2
  10. package/dist/types/src/MathUtils.d.ts +2 -1
  11. package/dist/types/src/ParticleEmitter.d.ts +1 -1
  12. package/dist/types/src/Physics.d.ts +148 -18
  13. package/dist/types/src/Scene.d.ts +1 -1
  14. package/dist/types/src/Shaders.d.ts +2 -2
  15. package/dist/types/src/Tilemap.d.ts +1 -1
  16. package/dist/types/src/UI.d.ts +1 -1
  17. package/dist/types/src/components/Behaviour.d.ts +2 -2
  18. package/dist/types/src/components/Collider.d.ts +7 -1
  19. package/dist/types/src/components/GameObject.d.ts +2 -2
  20. package/dist/types/src/components/PolygonCollider.d.ts +33 -0
  21. package/dist/types/src/components/RigidBody.d.ts +281 -8
  22. package/dist/types/src/importers/Aseprite.d.ts +2 -2
  23. package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
  24. package/dist/types/src/importers/TiledMap.d.ts +1 -1
  25. package/dist/types/src/managers/EventManager.d.ts +1 -1
  26. package/dist/types/src/managers/Gamepad.d.ts +102 -0
  27. package/dist/types/src/managers/InputManager.d.ts +93 -2
  28. package/dist/types/src/managers/NetworkManager.d.ts +2 -2
  29. package/dist/types/src/managers/RenderStats.d.ts +1 -1
  30. package/dist/types/src/managers/TextureManager.d.ts +1 -1
  31. package/dist/types/src/physics/AABB.d.ts +92 -0
  32. package/dist/types/src/physics/Body.d.ts +435 -0
  33. package/dist/types/src/physics/BodyType.d.ts +6 -0
  34. package/dist/types/src/physics/BroadPhase.d.ts +210 -0
  35. package/dist/types/src/physics/Collision.d.ts +102 -0
  36. package/dist/types/src/physics/Contact.d.ts +206 -0
  37. package/dist/types/src/physics/ContactSolver.d.ts +108 -0
  38. package/dist/types/src/physics/Distance.d.ts +54 -0
  39. package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
  40. package/dist/types/src/physics/Fixture.d.ts +221 -0
  41. package/dist/types/src/physics/Island.d.ts +52 -0
  42. package/dist/types/src/physics/Joint.d.ts +59 -0
  43. package/dist/types/src/physics/Math2D.d.ts +371 -0
  44. package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
  45. package/dist/types/src/physics/Settings.d.ts +22 -0
  46. package/dist/types/src/physics/Shapes.d.ts +207 -0
  47. package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
  48. package/dist/types/src/physics/World.d.ts +274 -0
  49. package/dist/types/src/physics/index.d.ts +34 -0
  50. package/index.js +6 -0
  51. package/package.json +2 -3
  52. package/src/Animator.js +1 -1
  53. package/src/CollisionLayers.js +3 -3
  54. package/src/Color.js +8 -0
  55. package/src/Drawable.js +1 -1
  56. package/src/Emerald.js +1 -1
  57. package/src/EmeraldDB.js +2 -2
  58. package/src/InstancedTexture.js +57 -9
  59. package/src/Material.js +2 -2
  60. package/src/MathUtils.js +2 -1
  61. package/src/ParticleEmitter.js +1 -1
  62. package/src/Physics.js +270 -60
  63. package/src/Scene.js +1 -1
  64. package/src/Shaders.js +20 -20
  65. package/src/Tilemap.js +1 -1
  66. package/src/UI.js +1 -1
  67. package/src/components/Behaviour.js +2 -2
  68. package/src/components/BoxCollider.js +7 -9
  69. package/src/components/BoxColliderDebug.js +3 -4
  70. package/src/components/CircleCollider.js +7 -9
  71. package/src/components/CircleColliderDebug.js +3 -2
  72. package/src/components/Collider.js +13 -3
  73. package/src/components/GameObject.js +2 -2
  74. package/src/components/PolygonCollider.js +55 -0
  75. package/src/components/RigidBody.js +441 -14
  76. package/src/importers/Aseprite.js +2 -2
  77. package/src/importers/ForgeLevel.js +581 -0
  78. package/src/importers/TiledMap.js +1 -1
  79. package/src/managers/EventManager.js +1 -1
  80. package/src/managers/Gamepad.js +126 -0
  81. package/src/managers/InputManager.js +129 -3
  82. package/src/managers/NetworkManager.js +2 -2
  83. package/src/managers/RenderStats.js +1 -1
  84. package/src/managers/TextureManager.js +1 -1
  85. package/src/physics/AABB.js +207 -0
  86. package/src/physics/Body.js +862 -0
  87. package/src/physics/BodyType.js +16 -0
  88. package/src/physics/BroadPhase.js +641 -0
  89. package/src/physics/Collision.js +534 -0
  90. package/src/physics/Contact.js +500 -0
  91. package/src/physics/ContactSolver.js +526 -0
  92. package/src/physics/Distance.js +403 -0
  93. package/src/physics/DistanceJoint.js +227 -0
  94. package/src/physics/Fixture.js +346 -0
  95. package/src/physics/Island.js +203 -0
  96. package/src/physics/Joint.js +78 -0
  97. package/src/physics/Math2D.js +573 -0
  98. package/src/physics/RevoluteJoint.js +278 -0
  99. package/src/physics/Settings.js +78 -0
  100. package/src/physics/Shapes.js +549 -0
  101. package/src/physics/TimeOfImpact.js +87 -0
  102. package/src/physics/World.js +731 -0
  103. package/src/physics/index.js +79 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @function timeOfImpact
3
+ * @description Finds the first fraction of a step at which two moving shapes
4
+ * touch, by conservative advancement: measure the gap, work out the fastest the
5
+ * shapes could possibly close it, and skip ahead by exactly that much time,
6
+ * never further. Repeating this converges on the impact without ever stepping
7
+ * past it, which is what stops a fast body from tunnelling through a wall.
8
+ *
9
+ * @param {Object} input - `{ proxyA, proxyB, sweepA, sweepB, tMax }`
10
+ * @returns {{state: string, t: number}} - The outcome and the impact fraction
11
+ */
12
+ export function timeOfImpact(input: any): {
13
+ state: string;
14
+ t: number;
15
+ };
16
+ export type TOIState = TOIState;
17
+ export namespace TOIState {
18
+ let SEPARATED: string;
19
+ let TOUCHING: string;
20
+ let OVERLAPPED: string;
21
+ let FAILED: string;
22
+ }
@@ -0,0 +1,274 @@
1
+ export default World;
2
+ /**
3
+ * @class World
4
+ * @description The physics world: it owns the bodies, finds which ones touch,
5
+ * and advances them all by one time step. This is Emerald's own rigid-body
6
+ * engine: broadphase, narrowphase, an impulse solver and continuous collision
7
+ * detection, with no external physics dependency.
8
+ *
9
+ * Games normally reach it through {@link Physics}, which adds the pixel/meter
10
+ * conversion, a fixed-timestep accumulator and collision routing to
11
+ * {@link Behaviour} components.
12
+ *
13
+ * @param {Object} [def] - `{ gravity, allowSleep, warmStarting,
14
+ * continuousPhysics, velocityThreshold, velocityIterations,
15
+ * positionIterations }`
16
+ */
17
+ declare class World {
18
+ constructor(def?: {});
19
+ gravity: Vec2;
20
+ broadPhase: BroadPhase;
21
+ contactManager: ContactManager;
22
+ /** Head of the body list; walk it with {@link Body#getNext}. @private */
23
+ private bodyList;
24
+ bodyCount: number;
25
+ /** Head of the joint list. @private */
26
+ private jointList;
27
+ jointCount: number;
28
+ allowSleep: boolean;
29
+ warmStarting: boolean;
30
+ continuousPhysics: boolean;
31
+ autoClearForces: boolean;
32
+ velocityThreshold: any;
33
+ velocityIterations: any;
34
+ positionIterations: any;
35
+ /** Optional `(fixtureA, fixtureB) => boolean` veto on new contacts. */
36
+ contactFilter: any;
37
+ /** @private */
38
+ private newContacts;
39
+ /** @private */
40
+ private locked;
41
+ /** @private */
42
+ private island;
43
+ /** @private */
44
+ private listeners;
45
+ /** @private */
46
+ private _toiProxyA;
47
+ /** @private */
48
+ private _toiProxyB;
49
+ /** What the last time-of-impact search hit. @private */
50
+ private _toiHitBody;
51
+ /**
52
+ * @method on
53
+ * @description Subscribes to a world event. Supported events:
54
+ * `begin-contact`, `end-contact`, `pre-solve`, `post-solve`, `remove-body`
55
+ * and `remove-fixture`.
56
+ * @param {string} name - Event name
57
+ * @param {Function} listener - Called with the contact (or body/fixture)
58
+ * @returns {World} - this
59
+ */
60
+ on(name: string, listener: Function): World;
61
+ /**
62
+ * @method off
63
+ * @description Unsubscribes a listener.
64
+ * @param {string} name
65
+ * @param {Function} listener
66
+ * @returns {World} - this
67
+ */
68
+ off(name: string, listener: Function): World;
69
+ /**
70
+ * @method dispatch
71
+ * @description Fires a world event. Listener errors are contained so one bad
72
+ * callback can't halt the simulation mid-step.
73
+ * @param {string} name
74
+ * @param {...*} args
75
+ */
76
+ dispatch(name: string, ...args: any[]): void;
77
+ /**
78
+ * @method createBody
79
+ * @description Creates a body in this world.
80
+ * @param {Object} [def] - See {@link Body}
81
+ * @returns {Body}
82
+ */
83
+ createBody(def?: any): Body;
84
+ /**
85
+ * @method destroyBody
86
+ * @description Removes a body, its fixtures and all its contacts.
87
+ * @param {Body} body
88
+ */
89
+ destroyBody(body: Body): void;
90
+ /**
91
+ * @method getBodyList
92
+ * @description Returns the first body; walk the rest with
93
+ * {@link Body#getNext}.
94
+ * @returns {Body|null}
95
+ */
96
+ getBodyList(): Body | null;
97
+ /**
98
+ * @method getBodyCount
99
+ * @description Number of bodies in the world.
100
+ * @returns {number}
101
+ */
102
+ getBodyCount(): number;
103
+ /**
104
+ * @method createJoint
105
+ * @description Creates a constraint between two bodies, a
106
+ * {@link DistanceJoint} or a {@link RevoluteJoint}, chosen by `def.type`
107
+ * (see {@link JointType}). Wakes both bodies, since a joint pulling on a
108
+ * sleeping body needs it simulating again immediately.
109
+ * @param {Object} def - `{ type, bodyA, bodyB, ... }`, forwarded to the
110
+ * chosen joint's constructor
111
+ * @returns {Joint}
112
+ */
113
+ createJoint(def: any): Joint;
114
+ /**
115
+ * @method destroyJoint
116
+ * @description Removes a joint. Safe to call as part of destroying one of
117
+ * its bodies (see {@link World#destroyBody}).
118
+ * @param {Joint} joint
119
+ */
120
+ destroyJoint(joint: Joint): void;
121
+ /**
122
+ * @method getJointList
123
+ * @description Returns the first joint; walk the rest with `joint.next`.
124
+ * @returns {Joint|null}
125
+ */
126
+ getJointList(): Joint | null;
127
+ /**
128
+ * @method getJointCount
129
+ * @returns {number}
130
+ */
131
+ getJointCount(): number;
132
+ /**
133
+ * @method getContactList
134
+ * @description Returns every live contact (touching or merely close enough
135
+ * for their bounding boxes to overlap).
136
+ * @returns {Array<Contact>}
137
+ */
138
+ getContactList(): Array<Contact>;
139
+ /**
140
+ * @method getContactCount
141
+ * @description Number of live contacts.
142
+ * @returns {number}
143
+ */
144
+ getContactCount(): number;
145
+ /**
146
+ * @method setGravity
147
+ * @description Sets world gravity in physics units per second squared.
148
+ * @param {Object} gravity - `{ x, y }`
149
+ */
150
+ setGravity(gravity: any): void;
151
+ /**
152
+ * @method getGravity
153
+ * @description Returns world gravity.
154
+ * @returns {Vec2}
155
+ */
156
+ getGravity(): Vec2;
157
+ /**
158
+ * @method isLocked
159
+ * @description True while a step is in progress, when the body/fixture graph
160
+ * must not be modified.
161
+ * @returns {boolean}
162
+ */
163
+ isLocked(): boolean;
164
+ /**
165
+ * @method setAllowSleeping
166
+ * @description Enables or disables sleeping world-wide.
167
+ * @param {boolean} flag
168
+ */
169
+ setAllowSleeping(flag: boolean): void;
170
+ /**
171
+ * @method step
172
+ * @description Advances the simulation by `dt` seconds: refresh contacts,
173
+ * solve the islands, then sweep any continuous (bullet) bodies.
174
+ * @param {number} dt - Time step in seconds
175
+ * @param {number} [velocityIterations] - Solver velocity iterations
176
+ * @param {number} [positionIterations] - Solver position iterations
177
+ */
178
+ step(dt: number, velocityIterations?: number, positionIterations?: number): void;
179
+ /**
180
+ * @method solve
181
+ * @description Groups the awake bodies into islands of things that touch and
182
+ * solves each island on its own.
183
+ * @param {Object} step
184
+ * @private
185
+ */
186
+ private solve;
187
+ /**
188
+ * @method solveContinuous
189
+ * @description Sweeps every bullet body against the static and kinematic
190
+ * geometry it passed through this step, and rewinds it to the first impact.
191
+ * Without this, a body moving further than its own size in one step would
192
+ * simply appear on the far side of a thin wall.
193
+ * @private
194
+ */
195
+ private solveContinuous;
196
+ /**
197
+ * @method _absorbImpactVelocity
198
+ * @description Takes the excess approach speed out of a body that a rewind
199
+ * stopped short of a *moving* obstacle, leaving it travelling with whatever
200
+ * it landed on.
201
+ *
202
+ * The rewind puts the body where the two first touched, but the obstacle
203
+ * carries on to the end of the step, so the body is left hovering by up to
204
+ * one step of the obstacle's travel, too far apart for the discrete solver
205
+ * to see a contact next step. Keeping the body's original velocity as well
206
+ * means it arrives even faster the following step, is rewound again, and
207
+ * never lands: a rider on a descending platform would fall for ever without
208
+ * ever standing on it. Dropping the approach speed to the obstacle's own
209
+ * closes that gap within a step or two, which is where the discrete solver
210
+ * takes over and the two stay in contact.
211
+ *
212
+ * Static obstacles are left alone: they do not run away from the body, so
213
+ * the overlap the rewind aims for survives into the next step and the
214
+ * discrete solver already handles them.
215
+ *
216
+ * @param {Body} body - The body that was rewound
217
+ * @param {number} dx - How far it swept this step, before the rewind
218
+ * @param {number} dy
219
+ * @private
220
+ */
221
+ private _absorbImpactVelocity;
222
+ /**
223
+ * @method _findTimeOfImpact
224
+ * @description Earliest fraction of this step at which `body` first touches
225
+ * non-dynamic geometry, or null when it hits nothing. The body that was hit
226
+ * is left in `_toiHitBody`.
227
+ * @param {Body} body
228
+ * @returns {number|null}
229
+ * @private
230
+ */
231
+ private _findTimeOfImpact;
232
+ /**
233
+ * @method clearForces
234
+ * @description Zeroes the accumulated forces and torques on every body.
235
+ * Called automatically at the end of each step.
236
+ */
237
+ clearForces(): void;
238
+ /**
239
+ * @method rayCast
240
+ * @description Casts a ray from `p1` to `p2`, calling
241
+ * `callback(fixture, point, normal, fraction)` for every fixture hit.
242
+ *
243
+ * The callback controls what happens next, exactly like a filter: return the
244
+ * `fraction` to clip the ray there and keep looking for closer hits (the
245
+ * usual "closest hit" behaviour), `0` to stop immediately, `1` to keep the
246
+ * full ray and collect every hit, or `-1` to ignore this fixture entirely.
247
+ *
248
+ * @param {Object} p1 - Ray start `{ x, y }` in physics units
249
+ * @param {Object} p2 - Ray end `{ x, y }` in physics units
250
+ * @param {Function} callback
251
+ */
252
+ rayCast(p1: any, p2: any, callback: Function): void;
253
+ /**
254
+ * @method queryAABB
255
+ * @description Calls `callback(fixture)` for every fixture whose bounding box
256
+ * overlaps `aabb`. Returning false from the callback ends the query.
257
+ * @param {AABB} aabb
258
+ * @param {Function} callback
259
+ */
260
+ queryAABB(aabb: AABB, callback: Function): void;
261
+ /**
262
+ * @method queryPoint
263
+ * @description Calls `callback(fixture)` for every fixture containing a
264
+ * world-space point.
265
+ * @param {Object} point - `{ x, y }` in physics units
266
+ * @param {Function} callback
267
+ */
268
+ queryPoint(point: any, callback: Function): void;
269
+ }
270
+ import { Vec2 } from "./Math2D.js";
271
+ import { BroadPhase } from "./BroadPhase.js";
272
+ import { ContactManager } from "./Contact.js";
273
+ import { Body } from "./Body.js";
274
+ import AABB from "./AABB.js";
@@ -0,0 +1,34 @@
1
+ import World from "./World.js";
2
+ import { Body } from "./Body.js";
3
+ import { BodyType } from "./Body.js";
4
+ import { Fixture } from "./Fixture.js";
5
+ import { Contact } from "./Contact.js";
6
+ import { ContactManager } from "./Contact.js";
7
+ import { shouldCollide } from "./Contact.js";
8
+ import { Shape } from "./Shapes.js";
9
+ import { ShapeType } from "./Shapes.js";
10
+ import { CircleShape } from "./Shapes.js";
11
+ import { PolygonShape } from "./Shapes.js";
12
+ import { Box } from "./Shapes.js";
13
+ import { Circle } from "./Shapes.js";
14
+ import { Vec2 } from "./Math2D.js";
15
+ import { Rot } from "./Math2D.js";
16
+ import { Transform2 } from "./Math2D.js";
17
+ import { Sweep } from "./Math2D.js";
18
+ import AABB from "./AABB.js";
19
+ import Settings from "./Settings.js";
20
+ import { Manifold } from "./Collision.js";
21
+ import { ManifoldType } from "./Collision.js";
22
+ import { WorldManifold } from "./Collision.js";
23
+ import { DistanceProxy } from "./Distance.js";
24
+ import { distance } from "./Distance.js";
25
+ import { testOverlap } from "./Distance.js";
26
+ import { timeOfImpact } from "./TimeOfImpact.js";
27
+ import { TOIState } from "./TimeOfImpact.js";
28
+ import { BroadPhase } from "./BroadPhase.js";
29
+ import { DynamicTree } from "./BroadPhase.js";
30
+ import { Joint } from "./Joint.js";
31
+ import { JointType } from "./Joint.js";
32
+ import DistanceJoint from "./DistanceJoint.js";
33
+ import RevoluteJoint from "./RevoluteJoint.js";
34
+ export { World, Body, BodyType, Fixture, Contact, ContactManager, shouldCollide, Shape, ShapeType, CircleShape, PolygonShape, Box, Circle, Vec2, Rot, Transform2, Sweep, AABB, Settings, Manifold, ManifoldType, WorldManifold, DistanceProxy, distance, testOverlap, timeOfImpact, TOIState, BroadPhase, DynamicTree, Joint, JointType, DistanceJoint, RevoluteJoint };
package/index.js CHANGED
@@ -36,6 +36,7 @@ import ParticleEmitter from "./src/ParticleEmitter.js";
36
36
  import CollisionLayers from "./src/CollisionLayers.js";
37
37
  import TiledMap from "./src/importers/TiledMap.js";
38
38
  import Aseprite from "./src/importers/Aseprite.js";
39
+ import ForgeLevel from "./src/importers/ForgeLevel.js";
39
40
  import RenderTarget from "./src/RenderTarget.js";
40
41
  import RenderStats from "./src/managers/RenderStats.js";
41
42
  import PostProcessor, { PostEffect } from "./src/PostProcessor.js";
@@ -43,6 +44,7 @@ import PostEffects, { BloomEffect } from "./src/PostEffects.js";
43
44
  import Material from "./src/Material.js";
44
45
  import UI from "./src/UI.js";
45
46
  import InputManager from "./src/managers/InputManager.js";
47
+ import Gamepad from "./src/managers/Gamepad.js";
46
48
  import Particle from "./src/particlesystem/Particle.js";
47
49
  import Particles from "./src/particlesystem/Particles.js";
48
50
  import ParticleSettings from "./src/particlesystem/ParticleSettings.js";
@@ -59,6 +61,7 @@ import BoxCollider from "./src/components/BoxCollider.js";
59
61
  import BoxColliderDebug from "./src/components/BoxColliderDebug.js";
60
62
  import CircleCollider from "./src/components/CircleCollider.js";
61
63
  import CircleColliderDebug from "./src/components/CircleColliderDebug.js";
64
+ import PolygonCollider from "./src/components/PolygonCollider.js";
62
65
  import Collider from "./src/components/Collider.js";
63
66
  import GameObject from "./src/components/GameObject.js";
64
67
  import RigidBody from "./src/components/RigidBody.js";
@@ -107,6 +110,7 @@ export {
107
110
  CollisionLayers,
108
111
  TiledMap,
109
112
  Aseprite,
113
+ ForgeLevel,
110
114
  RenderTarget,
111
115
  RenderStats,
112
116
  PostProcessor,
@@ -116,6 +120,7 @@ export {
116
120
  Material,
117
121
  UI,
118
122
  InputManager,
123
+ Gamepad,
119
124
  Particle,
120
125
  Particles,
121
126
  ParticleSettings,
@@ -132,6 +137,7 @@ export {
132
137
  BoxColliderDebug,
133
138
  CircleCollider,
134
139
  CircleColliderDebug,
140
+ PolygonCollider,
135
141
  Collider,
136
142
  GameObject,
137
143
  RigidBody,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emeraldengine",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "2D graphics library for Web",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -37,8 +37,7 @@
37
37
  "physics"
38
38
  ],
39
39
  "dependencies": {
40
- "gl-matrix": "^3.4.3",
41
- "planck": "^1.4.2"
40
+ "gl-matrix": "^3.4.3"
42
41
  },
43
42
  "peerDependencies": {
44
43
  "colyseus.js": "^0.16.16"
package/src/Animator.js CHANGED
@@ -5,7 +5,7 @@ import Drawable from "./Drawable.js";
5
5
  * @class Animator
6
6
  * @description A component that manages named animation clips for a Texture (or
7
7
  * any Drawable) on the same GameObject. Register clips once, then switch between
8
- * them by name handy for character states like idle/run/jump.
8
+ * them by name, handy for character states like idle/run/jump.
9
9
  *
10
10
  * @example
11
11
  * const anim = new Animator();
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @class CollisionLayers
3
3
  * @description A small registry that maps human-readable layer names to the
4
- * category bits planck uses for collision filtering, so games can say
4
+ * category bits the physics engine uses for collision filtering, so games can
5
5
  * "players collide with ground and enemies, but not with each other" without
6
6
  * juggling raw bitmasks.
7
7
  *
8
8
  * Two fixtures collide only if each one's category is in the other's mask, so
9
9
  * filtering is symmetric by construction. Up to 16 distinct layers are
10
- * supported (planck filter bits are 16-bit).
10
+ * supported (filter bits are 16-bit).
11
11
  *
12
12
  * @example
13
13
  * CollisionLayers.define("ground", "player", "enemy", "pickup");
@@ -44,7 +44,7 @@ class CollisionLayers {
44
44
  if (CollisionLayers._bits.has(name)) return CollisionLayers._bits.get(name);
45
45
  if (CollisionLayers._next > 15) {
46
46
  throw new Error(
47
- "[CollisionLayers] Exceeded 16 collision layers (planck filter limit)."
47
+ "[CollisionLayers] Exceeded 16 collision layers (filter bit limit)."
48
48
  );
49
49
  }
50
50
  const bit = 1 << CollisionLayers._next++;
package/src/Color.js CHANGED
@@ -13,6 +13,14 @@ class Color {
13
13
  this.b = b;
14
14
  this.a = a;
15
15
  }
16
+
17
+ static fromHex(hex) {
18
+ const value = hex.startsWith("#") ? hex.slice(1) : hex;
19
+ const r = parseInt(value.slice(0, 2), 16);
20
+ const g = parseInt(value.slice(2, 4), 16);
21
+ const b = parseInt(value.slice(4, 6), 16);
22
+ return new Color(r, g, b, 255);
23
+ }
16
24
  }
17
25
 
18
26
  export default Color;
package/src/Drawable.js CHANGED
@@ -370,7 +370,7 @@ class Drawable {
370
370
  * @description Frees this drawable's GPU resources: its vertex/texcoord
371
371
  * buffers and its reference on the shared texture (the GL texture itself is
372
372
  * deleted when the last drawable using it is disposed). Call it when the
373
- * owning object is permanently removed GameObject.destroy() and
373
+ * owning object is permanently removed. GameObject.destroy() and
374
374
  * Scene.remove(obj, { dispose: true }) do it for you. Safe to call twice.
375
375
  */
376
376
  dispose() {
package/src/Emerald.js CHANGED
@@ -215,7 +215,7 @@ class Emerald {
215
215
  e.preventDefault();
216
216
  this._contextLost = true;
217
217
  console.warn(
218
- "[Emerald] > WebGL context lost rendering paused until restore."
218
+ "[Emerald] > WebGL context lost, rendering paused until restore."
219
219
  );
220
220
  for (const h of this._contextLostHandlers) h();
221
221
  };
package/src/EmeraldDB.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @class EmeraldDB
3
- * @description Async game-save storage on IndexedDB the big-world companion
3
+ * @description Async game-save storage on IndexedDB, the big-world companion
4
4
  * to `Storage` (localStorage). Same versioned-envelope semantics (`{v,t,data}`
5
5
  * with a `.bak` backup and forward migration), but with no ~5MB quota and no
6
6
  * JSON round-trip: values are structured-cloned, so large nested world state
@@ -115,7 +115,7 @@ class EmeraldDB {
115
115
 
116
116
  /**
117
117
  * @method set
118
- * @description Stores a value under a key (structured clone objects, Maps,
118
+ * @description Stores a value under a key (structured clone: objects, Maps,
119
119
  * Sets, typed arrays all survive).
120
120
  * @param {string} key
121
121
  * @param {*} value
@@ -93,6 +93,8 @@ class InstancedTexture extends Drawable {
93
93
  this.static = false;
94
94
  /** @private */
95
95
  this._matricesDirty = true;
96
+ /** The animation every instance starts with; set by playAnimation/playAnimationOnce. @private */
97
+ this._defaultAnimation = null;
96
98
  }
97
99
 
98
100
  /**
@@ -127,6 +129,11 @@ class InstancedTexture extends Drawable {
127
129
  if (this.instances.length < this.instanceCount) {
128
130
  this.instances.push(instance);
129
131
  instance.setParent(this);
132
+ if (this._defaultAnimation) {
133
+ const { frames, speed, once } = this._defaultAnimation;
134
+ if (once) instance.playAnimationOnce(frames, speed);
135
+ else instance.playAnimation(frames, speed);
136
+ }
130
137
  this._matricesDirty = true;
131
138
  } else {
132
139
  console.warn("Max instance count reached.");
@@ -962,31 +969,72 @@ class InstancedTexture extends Drawable {
962
969
 
963
970
  /**
964
971
  * @method playAnimation
965
- * @description Plays an animation
972
+ * @description Plays the same looping animation, in lockstep, on every
973
+ * instance: the current ones immediately, and any added later via
974
+ * {@link InstancedTexture#addInstance}. For giving individual instances
975
+ * their own independent animation instead, use
976
+ * {@link InstancedTexture#animateInstance}.
966
977
  * @param {Array} frames - The frames to play
967
- * @param {number} speed - The speed of the animation
978
+ * @param {number} [speed=1000] - Milliseconds per frame
968
979
  */
969
980
  playAnimation(frames, speed = 1000) {
970
- console.warn("playAnimation is not implemented for InstancedTexture");
981
+ if (!frames || frames.length === 0) {
982
+ console.error(
983
+ "[InstancedTexture] playAnimation: frames cannot be empty!"
984
+ );
985
+ return;
986
+ }
987
+ this._defaultAnimation = { frames, speed, once: false };
988
+ for (const instance of this.instances)
989
+ instance.playAnimation(frames, speed);
971
990
  }
972
991
 
973
992
  /**
974
993
  * @method playAnimationOnce
975
- * @description Plays an animation once
994
+ * @description Plays the same animation once, in lockstep, on every
995
+ * instance, then holds each on its last frame: the current instances
996
+ * immediately, and any added later via {@link InstancedTexture#addInstance}.
976
997
  * @param {Array} frames - The frames to play
977
- * @param {number} speed - The speed of the animation
998
+ * @param {number} [speed=1000] - Milliseconds per frame
978
999
  */
979
1000
  playAnimationOnce(frames, speed = 1000) {
980
- console.warn("playAnimationOnce is not implemented for InstancedTexture");
1001
+ if (!frames || frames.length === 0) {
1002
+ console.error(
1003
+ "[InstancedTexture] playAnimationOnce: frames cannot be empty!"
1004
+ );
1005
+ return;
1006
+ }
1007
+ this._defaultAnimation = { frames, speed, once: true };
1008
+ for (const instance of this.instances) {
1009
+ instance.playAnimationOnce(frames, speed);
1010
+ }
1011
+ }
1012
+
1013
+ /**
1014
+ * @method stopAnimation
1015
+ * @description Stops the shared animation started by playAnimation/
1016
+ * playAnimationOnce on every current instance, and clears it so instances
1017
+ * added afterwards no longer start playing it either. Instances animated
1018
+ * individually via {@link InstancedTexture#animateInstance} are unaffected
1019
+ * unless you stop them the same way, through {@link InstancedTexture#stopInstanceAnimation}.
1020
+ * @param {boolean} [revertToOriginal=false] - Whether to reset each
1021
+ * instance back to the frame it had before playAnimation was called
1022
+ */
1023
+ stopAnimation(revertToOriginal = false) {
1024
+ this._defaultAnimation = null;
1025
+ for (const instance of this.instances) {
1026
+ instance.stopAnimation(revertToOriginal);
1027
+ }
981
1028
  }
982
1029
 
983
1030
  /**
984
1031
  * @method getAnimation
985
- * @description Gets the animation
986
- * @returns {Array} - The animation
1032
+ * @description Returns the frames of the shared animation set by
1033
+ * playAnimation/playAnimationOnce, or an empty array if none is playing.
1034
+ * @returns {Array} - The animation frames
987
1035
  */
988
1036
  getAnimation() {
989
- console.warn("getAnimation is not implemented for InstancedTexture");
1037
+ return this._defaultAnimation ? this._defaultAnimation.frames : [];
990
1038
  }
991
1039
  }
992
1040
 
package/src/Material.js CHANGED
@@ -31,7 +31,7 @@ const MATERIAL_VERTEX_HEADER = `
31
31
  * @description A custom shader for a Drawable. By default it reuses the engine's
32
32
  * standard vertex shader (so transforms, the camera, and instancing keep
33
33
  * working) and only overrides the fragment program. Pass `options.vertex` to
34
- * also supply a custom VERTEX program the escape hatch for effects the fixed
34
+ * also supply a custom VERTEX program: the escape hatch for effects the fixed
35
35
  * pipeline can't express (perspective tilt, vertex waves, billboarding, ...).
36
36
  *
37
37
  * The fragment shader always has: `vTexCoord`, `vFragPos`, `vInstanceColor`,
@@ -40,7 +40,7 @@ const MATERIAL_VERTEX_HEADER = `
40
40
  * `uTime`, and must write `vTexCoord` + `gl_Position`.
41
41
  *
42
42
  * Declare extra uniforms and set them via `set(name, value)`. A uniform value
43
- * may be a number, an array (vec2/3/4), or a FUNCTION the function is called
43
+ * may be a number, an array (vec2/3/4), or a FUNCTION: the function is called
44
44
  * each draw and receives the Drawable currently rendering, so a single shared
45
45
  * Material can read PER-OBJECT state (e.g. each card's own tilt angle).
46
46
  *
package/src/MathUtils.js CHANGED
@@ -2,7 +2,8 @@
2
2
  * @class MathUtils
3
3
  * @description Common math helpers for games: interpolation, clamping, angle
4
4
  * conversion, random ranges, and lightweight 2D vector operations that work on
5
- * any `{ x, y }` object (including planck Vec2 and Emerald Vector2).
5
+ * any `{ x, y }` object (including the physics engine's Vec2 and Emerald
6
+ * Vector2).
6
7
  */
7
8
  class MathUtils {
8
9
  /**
@@ -10,7 +10,7 @@ const PARK_Y = -1e9;
10
10
  * @class ParticleEmitter
11
11
  * @description A reliable, allocation-free particle system built from a fixed
12
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 /
13
+ * and opacity are driven by hand every frame; there is no instanced-draw /
14
14
  * dynamic-buffer lifecycle to desync, so it keeps drawing for the whole session
15
15
  * (unlike the InstancedTexture-based `Particles`, which can stop emitting after
16
16
  * heavy reuse on some GPUs).