emeraldengine 2.2.1 → 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 -582
  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,86 @@
1
+ /**
2
+ * @class CollisionLayers
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
5
+ * "players collide with ground and enemies, but not with each other" without
6
+ * juggling raw bitmasks.
7
+ *
8
+ * Two fixtures collide only if each one's category is in the other's mask, so
9
+ * filtering is symmetric by construction. Up to 16 distinct layers are
10
+ * supported (planck filter bits are 16-bit).
11
+ *
12
+ * @example
13
+ * CollisionLayers.define("ground", "player", "enemy", "pickup");
14
+ * playerCollider.setCategory("player");
15
+ * playerCollider.setCollidesWith(["ground", "enemy", "pickup"]);
16
+ * enemyCollider.setCategory("enemy");
17
+ * enemyCollider.setCollidesWith(["ground", "player"]); // enemies ignore each other
18
+ */
19
+ const ALL_BITS = 0xffff;
20
+
21
+ class CollisionLayers {
22
+ /**
23
+ * @method define
24
+ * @description Registers one or more named layers (in order), assigning each
25
+ * the next free category bit. Re-defining an existing name returns its
26
+ * existing bit. Returns the bit of the last name passed.
27
+ * @param {...string} names
28
+ * @returns {number}
29
+ */
30
+ static define(...names) {
31
+ let last = 0;
32
+ for (const name of names) last = CollisionLayers.bit(name);
33
+ return last;
34
+ }
35
+
36
+ /**
37
+ * @method bit
38
+ * @description Returns the category bit for a layer name, registering it on
39
+ * first use.
40
+ * @param {string} name
41
+ * @returns {number}
42
+ */
43
+ static bit(name) {
44
+ if (CollisionLayers._bits.has(name)) return CollisionLayers._bits.get(name);
45
+ if (CollisionLayers._next > 15) {
46
+ throw new Error(
47
+ "[CollisionLayers] Exceeded 16 collision layers (planck filter limit)."
48
+ );
49
+ }
50
+ const bit = 1 << CollisionLayers._next++;
51
+ CollisionLayers._bits.set(name, bit);
52
+ return bit;
53
+ }
54
+
55
+ /**
56
+ * @method mask
57
+ * @description Computes a combined mask from layer names. Accepts a single
58
+ * name, an array of names, a number (passed through), or "all"/null for
59
+ * "collide with everything".
60
+ * @param {string|string[]|number|null} layers
61
+ * @returns {number}
62
+ */
63
+ static mask(layers) {
64
+ if (layers == null || layers === "all") return ALL_BITS;
65
+ if (typeof layers === "number") return layers;
66
+ const list = Array.isArray(layers) ? layers : [layers];
67
+ let m = 0;
68
+ for (const name of list) m |= CollisionLayers.bit(name);
69
+ return m;
70
+ }
71
+
72
+ /**
73
+ * @method reset
74
+ * @description Clears all registered layers (mainly for tests).
75
+ */
76
+ static reset() {
77
+ CollisionLayers._bits.clear();
78
+ CollisionLayers._next = 0;
79
+ }
80
+ }
81
+
82
+ CollisionLayers._bits = new Map();
83
+ CollisionLayers._next = 0;
84
+ CollisionLayers.ALL = ALL_BITS;
85
+
86
+ export default CollisionLayers;
package/src/Color.js CHANGED
@@ -1,18 +1,18 @@
1
- /**
2
- * @class Color
3
- * @description Represents a color
4
- * @param {number} r - The red value
5
- * @param {number} g - The green value
6
- * @param {number} b - The blue value
7
- * @param {number} a - The alpha value
8
- */
9
- class Color {
10
- constructor(r, g, b, a = 255) {
11
- this.r = r;
12
- this.g = g;
13
- this.b = b;
14
- this.a = a;
15
- }
16
- }
17
-
18
- export default Color;
1
+ /**
2
+ * @class Color
3
+ * @description Represents a color
4
+ * @param {number} r - The red value
5
+ * @param {number} g - The green value
6
+ * @param {number} b - The blue value
7
+ * @param {number} a - The alpha value
8
+ */
9
+ class Color {
10
+ constructor(r, g, b, a = 255) {
11
+ this.r = r;
12
+ this.g = g;
13
+ this.b = b;
14
+ this.a = a;
15
+ }
16
+ }
17
+
18
+ export default Color;
@@ -0,0 +1,259 @@
1
+ /**
2
+ * @class Coroutine
3
+ * @description Generator-based sequencing for time-based logic, layered on top of
4
+ * the same per-frame delta the rest of the engine uses. Write a generator that
5
+ * `yield`s instructions, and the runner advances it each frame:
6
+ *
7
+ * - `yield seconds` (a number) -> wait that many seconds
8
+ * - `yield Coroutine.waitFrames(n)` -> wait n frames
9
+ * - `yield Coroutine.waitUntil(fn)` -> wait until fn() is truthy
10
+ * - `yield Coroutine.waitWhile(fn)` -> wait while fn() is truthy
11
+ * - `yield somePromise` -> wait for the promise to resolve
12
+ * - `yield anotherCoroutine` -> wait for a nested coroutine to finish
13
+ * - `yield Coroutine.tween(...)` -> drive a value over time, then continue
14
+ *
15
+ * Coroutines are driven by `Coroutine.update(dt)` (called automatically from
16
+ * `Emerald.drawScene`, so they honor pause/slow-mo via Time.timeScale).
17
+ *
18
+ * @example
19
+ * Coroutine.start(function* () {
20
+ * door.open();
21
+ * yield 1.5; // wait 1.5s
22
+ * yield Coroutine.waitUntil(() => player.isReady);
23
+ * boss.spawn();
24
+ * });
25
+ */
26
+ class Coroutine {
27
+ /**
28
+ * @method start
29
+ * @description Starts a coroutine from a generator function (or an existing
30
+ * generator/iterator). Returns a handle with `cancel()` and `done`.
31
+ * @param {GeneratorFunction|Generator} gen - The generator (function) to run
32
+ * @param {...*} args - Arguments forwarded to the generator function
33
+ * @returns {Object} - A handle: { cancel(), done, isRunning() }
34
+ */
35
+ static start(gen, ...args) {
36
+ const iterator = typeof gen === "function" ? gen(...args) : gen;
37
+ const routine = {
38
+ iterator,
39
+ wait: null,
40
+ cancelled: false,
41
+ done: false,
42
+ _resolve: null,
43
+ };
44
+ routine.promise = new Promise((resolve) => (routine._resolve = resolve));
45
+ routine.cancel = () => {
46
+ routine.cancelled = true;
47
+ };
48
+ routine.isRunning = () => !routine.done && !routine.cancelled;
49
+ Coroutine._routines.push(routine);
50
+ Coroutine._advance(routine, undefined, 0);
51
+ return routine;
52
+ }
53
+
54
+ /**
55
+ * @method waitFrames
56
+ * @description Yieldable that waits a number of frames.
57
+ * @param {number} count - Frames to wait
58
+ */
59
+ static waitFrames(count) {
60
+ return { __coroutine: "frames", count: Math.max(0, count | 0) };
61
+ }
62
+
63
+ /**
64
+ * @method waitUntil
65
+ * @description Yieldable that blocks until the predicate returns truthy.
66
+ * @param {Function} predicate
67
+ */
68
+ static waitUntil(predicate) {
69
+ return { __coroutine: "until", predicate };
70
+ }
71
+
72
+ /**
73
+ * @method waitWhile
74
+ * @description Yieldable that blocks while the predicate returns truthy.
75
+ * @param {Function} predicate
76
+ */
77
+ static waitWhile(predicate) {
78
+ return { __coroutine: "until", predicate: () => !predicate() };
79
+ }
80
+
81
+ /**
82
+ * @method tween
83
+ * @description Yieldable that interpolates from `from` to `to` over `duration`
84
+ * seconds, calling `onUpdate(value)` each frame, then resumes the coroutine.
85
+ * @param {number} from
86
+ * @param {number} to
87
+ * @param {number} duration - Seconds
88
+ * @param {Function} onUpdate - Receives the current value each step
89
+ * @param {Function} [easing] - Optional easing t -> t
90
+ */
91
+ static tween(from, to, duration, onUpdate, easing) {
92
+ return {
93
+ __coroutine: "tween",
94
+ from,
95
+ to,
96
+ duration: Math.max(0, duration),
97
+ onUpdate,
98
+ easing: easing || ((t) => t),
99
+ elapsed: 0,
100
+ };
101
+ }
102
+
103
+ /**
104
+ * @method update
105
+ * @description Advances all running coroutines by dt seconds. Driven by
106
+ * Emerald.drawScene.
107
+ * @param {number} dt - Seconds since the last frame (time-scaled)
108
+ */
109
+ static update(dt) {
110
+ const routines = Coroutine._routines;
111
+ for (let i = routines.length - 1; i >= 0; i--) {
112
+ const routine = routines[i];
113
+ if (routine.cancelled || routine.done) {
114
+ Coroutine._finish(routine);
115
+ routines.splice(i, 1);
116
+ continue;
117
+ }
118
+ Coroutine._step(routine, dt);
119
+ if (routine.cancelled || routine.done) {
120
+ Coroutine._finish(routine);
121
+ routines.splice(i, 1);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @method clearAll
128
+ * @description Cancels and removes every running coroutine.
129
+ */
130
+ static clearAll() {
131
+ for (const r of Coroutine._routines) r.cancelled = true;
132
+ Coroutine._routines.length = 0;
133
+ }
134
+
135
+ /**
136
+ * @method count
137
+ * @returns {number} - Number of currently running coroutines
138
+ */
139
+ static count() {
140
+ return Coroutine._routines.length;
141
+ }
142
+
143
+ /** @private */
144
+ static _finish(routine) {
145
+ if (routine._resolve) {
146
+ const resolve = routine._resolve;
147
+ routine._resolve = null;
148
+ resolve();
149
+ }
150
+ }
151
+
152
+ /** @private */
153
+ static _step(routine, dt) {
154
+ const wait = routine.wait;
155
+ if (!wait) {
156
+ Coroutine._advance(routine, undefined, dt);
157
+ return;
158
+ }
159
+
160
+ switch (wait.kind) {
161
+ case "time":
162
+ wait.remaining -= dt;
163
+ if (wait.remaining <= 0) {
164
+ const overflow = -wait.remaining;
165
+ Coroutine._advance(routine, undefined, overflow);
166
+ }
167
+ break;
168
+ case "frames":
169
+ wait.remaining -= 1;
170
+ if (wait.remaining <= 0) Coroutine._advance(routine, undefined, 0);
171
+ break;
172
+ case "until":
173
+ if (wait.predicate()) Coroutine._advance(routine, undefined, 0);
174
+ break;
175
+ case "tween": {
176
+ const t = wait.spec;
177
+ t.elapsed += dt;
178
+ const k = t.duration <= 0 ? 1 : Math.min(1, t.elapsed / t.duration);
179
+ const eased = t.easing(k);
180
+ t.onUpdate(t.from + (t.to - t.from) * eased);
181
+ if (k >= 1) Coroutine._advance(routine, undefined, 0);
182
+ break;
183
+ }
184
+ case "promise":
185
+ if (wait.resolved) {
186
+ Coroutine._advance(routine, wait.value, 0);
187
+ }
188
+ break;
189
+ case "nested":
190
+ if (wait.routine.done || wait.routine.cancelled) {
191
+ Coroutine._advance(routine, undefined, 0);
192
+ }
193
+ break;
194
+ default:
195
+ Coroutine._advance(routine, undefined, dt);
196
+ }
197
+ }
198
+
199
+ /** @private */
200
+ static _advance(routine, sendValue, leftoverDt) {
201
+ if (routine.cancelled || routine.done) return;
202
+ let result;
203
+ try {
204
+ result = routine.iterator.next(sendValue);
205
+ } catch (err) {
206
+ console.error("[Coroutine] Error in coroutine:", err);
207
+ routine.done = true;
208
+ return;
209
+ }
210
+ if (result.done) {
211
+ routine.done = true;
212
+ return;
213
+ }
214
+ routine.wait = Coroutine._interpret(result.value, routine);
215
+ if (routine.wait && routine.wait.kind === "time" && leftoverDt > 0) {
216
+ Coroutine._step(routine, leftoverDt);
217
+ }
218
+ }
219
+
220
+ /** @private */
221
+ static _interpret(value, routine) {
222
+ if (value == null) {
223
+ return { kind: "time", remaining: 0 };
224
+ }
225
+ if (typeof value === "number") {
226
+ return { kind: "time", remaining: value };
227
+ }
228
+ if (typeof value.then === "function") {
229
+ const wait = { kind: "promise", resolved: false, value: undefined };
230
+ value.then(
231
+ (v) => {
232
+ wait.resolved = true;
233
+ wait.value = v;
234
+ },
235
+ () => {
236
+ wait.resolved = true;
237
+ }
238
+ );
239
+ return wait;
240
+ }
241
+ if (value.iterator && typeof value.cancel === "function") {
242
+ return { kind: "nested", routine: value };
243
+ }
244
+ switch (value.__coroutine) {
245
+ case "frames":
246
+ return { kind: "frames", remaining: value.count };
247
+ case "until":
248
+ return { kind: "until", predicate: value.predicate };
249
+ case "tween":
250
+ return { kind: "tween", spec: value };
251
+ default:
252
+ return { kind: "time", remaining: 0 };
253
+ }
254
+ }
255
+ }
256
+
257
+ Coroutine._routines = [];
258
+
259
+ export default Coroutine;
@@ -0,0 +1,246 @@
1
+ import RigidBody from "./components/RigidBody.js";
2
+
3
+ /**
4
+ * @class DebugOverlay
5
+ * @description A DOM panel that profiles the running game: FPS, frame-time stats
6
+ * with a live sparkline graph, object/camera counts, the primary camera's
7
+ * position/zoom, JS heap usage (where available), and any extra metrics you
8
+ * push. It can also toggle physics collider debug shapes on a scene.
9
+ *
10
+ * Call `update(emerald, scene)` each frame; toggle visibility with `setVisible`.
11
+ *
12
+ * @example
13
+ * const debug = new DebugOverlay();
14
+ * // each frame, after drawScene:
15
+ * debug.update(emerald, scene);
16
+ * debug.setMetric("draws", spriteBatch.drawCalls);
17
+ * debug.showColliders(scene, true); // visualize physics colliders
18
+ */
19
+ class DebugOverlay {
20
+ constructor(options = {}) {
21
+ this.element = document.createElement("div");
22
+ Object.assign(this.element.style, {
23
+ position: "absolute",
24
+ top: options.top || "10px",
25
+ right: options.right || "10px",
26
+ padding: "8px 10px",
27
+ background: "rgba(0,0,0,0.6)",
28
+ color: "#9be7c4",
29
+ font: "12px/1.5 monospace",
30
+ whiteSpace: "pre",
31
+ borderRadius: "6px",
32
+ pointerEvents: "none",
33
+ zIndex: "9999",
34
+ });
35
+
36
+ /** @private */
37
+ this._graph = document.createElement("canvas");
38
+ this._graph.width = options.graphWidth || 160;
39
+ this._graph.height = options.graphHeight || 36;
40
+ this._graph.style.display = "block";
41
+ this._graph.style.marginTop = "6px";
42
+ this._graph.style.borderRadius = "3px";
43
+ /** @private */
44
+ this._gctx = this._graph.getContext("2d");
45
+
46
+ /** @private */
47
+ this._text = document.createElement("div");
48
+ this.element.appendChild(this._text);
49
+ this.element.appendChild(this._graph);
50
+
51
+ (options.parent || document.body).appendChild(this.element);
52
+
53
+ /** @private */
54
+ this._last = performance.now();
55
+ /** @private */
56
+ this._frames = 0;
57
+ /** @private */
58
+ this._accum = 0;
59
+ this.fps = 0;
60
+
61
+ /** @private */
62
+ this._frameTimes = [];
63
+ /** @private */
64
+ this._maxSamples = this._graph.width;
65
+ /** @private */
66
+ this._dtMs = 0;
67
+ /** @private */
68
+ this._minMs = 0;
69
+ /** @private */
70
+ this._maxMs = 0;
71
+ /** @private */
72
+ this._avgMs = 0;
73
+
74
+ /** @private */
75
+ this._metrics = {};
76
+ /** @private */
77
+ this._colliderScene = null;
78
+ /** @private */
79
+ this._collidersShown = false;
80
+ }
81
+
82
+ /**
83
+ * @method setMetric
84
+ * @description Adds/updates a custom metric line shown in the panel.
85
+ * @param {string} key
86
+ * @param {*} value
87
+ */
88
+ setMetric(key, value) {
89
+ this._metrics[key] = value;
90
+ }
91
+
92
+ /**
93
+ * @method update
94
+ * @description Updates the overlay. Pass the engine and current scene.
95
+ * @param {Emerald} emerald
96
+ * @param {Scene} scene
97
+ */
98
+ update(emerald, scene) {
99
+ const now = performance.now();
100
+ const dt = now - this._last;
101
+ this._last = now;
102
+ this._dtMs = dt;
103
+ this._accum += dt;
104
+ this._frames++;
105
+
106
+ this._frameTimes.push(dt);
107
+ if (this._frameTimes.length > this._maxSamples) this._frameTimes.shift();
108
+
109
+ if (this._accum >= 500) {
110
+ this.fps = Math.round((this._frames * 1000) / this._accum);
111
+ this._frames = 0;
112
+ this._accum = 0;
113
+
114
+ let min = Infinity;
115
+ let max = 0;
116
+ let sum = 0;
117
+ for (const t of this._frameTimes) {
118
+ if (t < min) min = t;
119
+ if (t > max) max = t;
120
+ sum += t;
121
+ }
122
+ this._minMs = min === Infinity ? 0 : min;
123
+ this._maxMs = max;
124
+ this._avgMs = this._frameTimes.length ? sum / this._frameTimes.length : 0;
125
+ }
126
+
127
+ const objectCount = scene && scene.objects ? scene.objects.length : 0;
128
+ const cameras = emerald && emerald.cameras ? emerald.cameras.length : 0;
129
+ let camLine = "";
130
+ if (emerald && emerald.camera && emerald.camera.getPosition) {
131
+ const p = emerald.camera.getPosition();
132
+ const z = emerald.camera.getZoom ? emerald.camera.getZoom() : 1;
133
+ camLine = `\ncam ${p.x.toFixed(0)}, ${p.y.toFixed(0)} zoom ${z.toFixed(2)}`;
134
+ }
135
+
136
+ let renderLines = "";
137
+ if (emerald && typeof emerald.getRenderStats === "function") {
138
+ const rs = emerald.getRenderStats();
139
+ renderLines =
140
+ `\ndraws ${rs.drawCalls} quads ${rs.quads}` +
141
+ `\nbinds ${rs.textureBinds}`;
142
+ }
143
+
144
+ let memLine = "";
145
+ if (performance && performance.memory) {
146
+ const mb = performance.memory.usedJSHeapSize / (1024 * 1024);
147
+ memLine = `\nheap ${mb.toFixed(1)} MB`;
148
+ }
149
+
150
+ let metricLines = "";
151
+ for (const key in this._metrics) {
152
+ metricLines += `\n${key.padEnd(5)} ${this._metrics[key]}`;
153
+ }
154
+
155
+ this._text.textContent =
156
+ `FPS ${this.fps}` +
157
+ `\nframe ${this._dtMs.toFixed(1)}ms` +
158
+ `\nmin/avg/max ${this._minMs.toFixed(1)}/${this._avgMs.toFixed(1)}/${this._maxMs.toFixed(1)}` +
159
+ `\nobjs ${objectCount}` +
160
+ `\ncams ${cameras}` +
161
+ renderLines +
162
+ camLine +
163
+ memLine +
164
+ metricLines;
165
+
166
+ this._drawGraph();
167
+ }
168
+
169
+ /** @private */
170
+ _drawGraph() {
171
+ const ctx = this._gctx;
172
+ const w = this._graph.width;
173
+ const h = this._graph.height;
174
+ ctx.clearRect(0, 0, w, h);
175
+ ctx.fillStyle = "rgba(255,255,255,0.05)";
176
+ ctx.fillRect(0, 0, w, h);
177
+
178
+ const scale = h / 50;
179
+ ctx.strokeStyle = "rgba(120,220,160,0.35)";
180
+ ctx.beginPath();
181
+ ctx.moveTo(0, h - 16.7 * scale);
182
+ ctx.lineTo(w, h - 16.7 * scale);
183
+ ctx.stroke();
184
+ ctx.strokeStyle = "rgba(220,180,120,0.3)";
185
+ ctx.beginPath();
186
+ ctx.moveTo(0, h - 33.3 * scale);
187
+ ctx.lineTo(w, h - 33.3 * scale);
188
+ ctx.stroke();
189
+
190
+ ctx.strokeStyle = "#7fdcff";
191
+ ctx.beginPath();
192
+ const n = this._frameTimes.length;
193
+ for (let i = 0; i < n; i++) {
194
+ const x = w - n + i;
195
+ const y = h - Math.min(h, this._frameTimes[i] * scale);
196
+ if (i === 0) ctx.moveTo(x, y);
197
+ else ctx.lineTo(x, y);
198
+ }
199
+ ctx.stroke();
200
+ }
201
+
202
+ /**
203
+ * @method showColliders
204
+ * @description Toggles physics collider debug shapes for every RigidBody in a
205
+ * scene's objects (uses the lazy debug shapes on each collider).
206
+ * @param {Scene} scene
207
+ * @param {boolean} show
208
+ */
209
+ showColliders(scene, show) {
210
+ this._collidersShown = show;
211
+ this._colliderScene = scene;
212
+ if (!scene || !scene.objects) return;
213
+ for (const obj of scene.objects) {
214
+ if (!obj.getComponent) continue;
215
+ const rb = obj.getComponent(RigidBody);
216
+ if (!rb) continue;
217
+ const collider = rb.getCollider();
218
+ if (!collider) continue;
219
+ if (show && typeof collider.showDebugShape === "function") {
220
+ collider.showDebugShape();
221
+ } else if (!show && typeof collider.hideDebugShape === "function") {
222
+ collider.hideDebugShape();
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * @method setVisible
229
+ * @param {boolean} visible
230
+ */
231
+ setVisible(visible) {
232
+ this.element.style.display = visible ? "block" : "none";
233
+ }
234
+
235
+ /**
236
+ * @method destroy
237
+ * @description Removes the overlay from the DOM.
238
+ */
239
+ destroy() {
240
+ if (this.element.parentNode) {
241
+ this.element.parentNode.removeChild(this.element);
242
+ }
243
+ }
244
+ }
245
+
246
+ export default DebugOverlay;