@zylem/game-lib 0.6.3 → 0.7.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 (97) hide show
  1. package/README.md +3 -1
  2. package/dist/actions.d.ts +255 -8
  3. package/dist/actions.js +1 -772
  4. package/dist/audio.d.ts +13 -0
  5. package/dist/audio.js +1 -0
  6. package/dist/base-node-life-cycle-CnJ-7zX-.d.ts +143 -0
  7. package/dist/behavior.d.ts +3 -0
  8. package/dist/behavior.js +1 -0
  9. package/dist/chunk-4QLJUQSJ.js +1 -0
  10. package/dist/chunk-73VZQZUL.js +1 -0
  11. package/dist/chunk-A34ZG3L4.js +1 -0
  12. package/dist/chunk-CDPY4VOQ.js +1 -0
  13. package/dist/chunk-DR6XXZE7.js +19 -0
  14. package/dist/chunk-FM5TBEPO.js +1 -0
  15. package/dist/chunk-G2IIMSUH.js +1 -0
  16. package/dist/chunk-MSC2BEO5.js +1 -0
  17. package/dist/chunk-NIH5CONO.js +1 -0
  18. package/dist/chunk-ONWXGBWB.js +1 -0
  19. package/dist/chunk-QEDQDR27.js +1 -0
  20. package/dist/chunk-QXVH6POE.js +1 -0
  21. package/dist/chunk-RXDTAWI2.js +29 -0
  22. package/dist/chunk-WKHW2QO2.js +1 -0
  23. package/dist/chunk-XRP4JJXL.js +1 -0
  24. package/dist/chunk-YHEWKN7N.js +1 -0
  25. package/dist/chunk-YHKXQ2ZN.js +1 -0
  26. package/dist/core.d.ts +206 -13
  27. package/dist/core.js +24 -9160
  28. package/dist/debug.d.ts +24 -0
  29. package/dist/debug.js +1 -0
  30. package/dist/default-controls-BGc61yYl.d.ts +111 -0
  31. package/dist/entity-DNkYKfdp.d.ts +1043 -0
  32. package/dist/entity-type-map-C94h2TDW.d.ts +781 -0
  33. package/dist/entity.d.ts +265 -0
  34. package/dist/entity.js +1 -0
  35. package/dist/events.d.ts +172 -0
  36. package/dist/events.js +1 -0
  37. package/dist/game-Cuj5gy7U.d.ts +1590 -0
  38. package/dist/game-default-PZLLHYL7.js +1 -0
  39. package/dist/game-interfaces-tfpM5NBk.d.ts +90 -0
  40. package/dist/globals.d.ts +104 -0
  41. package/dist/globals.js +1 -0
  42. package/dist/graphics.d.ts +83 -0
  43. package/dist/graphics.js +1 -0
  44. package/dist/input-ui.d.ts +34 -0
  45. package/dist/input-ui.js +1 -0
  46. package/dist/input.d.ts +72 -0
  47. package/dist/input.js +1 -0
  48. package/dist/material-Wz83vTTp.d.ts +151 -0
  49. package/dist/runtime.d.ts +433 -0
  50. package/dist/runtime.js +1 -0
  51. package/dist/web-components.d.ts +67 -0
  52. package/dist/web-components.js +9 -0
  53. package/dist/world-C9zdyl5d.d.ts +103 -0
  54. package/dist/zylem-stage-runtime-Cs-UDEVU.d.ts +777 -0
  55. package/package.json +54 -41
  56. package/dist/actions.js.map +0 -1
  57. package/dist/behavior/platformer-3d.d.ts +0 -296
  58. package/dist/behavior/platformer-3d.js +0 -518
  59. package/dist/behavior/platformer-3d.js.map +0 -1
  60. package/dist/behavior/ricochet-2d.d.ts +0 -274
  61. package/dist/behavior/ricochet-2d.js +0 -394
  62. package/dist/behavior/ricochet-2d.js.map +0 -1
  63. package/dist/behavior/screen-wrap.d.ts +0 -86
  64. package/dist/behavior/screen-wrap.js +0 -195
  65. package/dist/behavior/screen-wrap.js.map +0 -1
  66. package/dist/behavior/thruster.d.ts +0 -10
  67. package/dist/behavior/thruster.js +0 -234
  68. package/dist/behavior/thruster.js.map +0 -1
  69. package/dist/behavior/world-boundary-2d.d.ts +0 -141
  70. package/dist/behavior/world-boundary-2d.js +0 -181
  71. package/dist/behavior/world-boundary-2d.js.map +0 -1
  72. package/dist/behavior-descriptor-BWNWmIjv.d.ts +0 -142
  73. package/dist/blueprints-BWGz8fII.d.ts +0 -26
  74. package/dist/camera-B5e4c78l.d.ts +0 -468
  75. package/dist/camera.d.ts +0 -6
  76. package/dist/camera.js +0 -1406
  77. package/dist/camera.js.map +0 -1
  78. package/dist/composition-DrzFrbqI.d.ts +0 -218
  79. package/dist/core-DAkskq6Y.d.ts +0 -498
  80. package/dist/core.js.map +0 -1
  81. package/dist/entities-DC9ce_vx.d.ts +0 -416
  82. package/dist/entities.d.ts +0 -9
  83. package/dist/entities.js +0 -4069
  84. package/dist/entities.js.map +0 -1
  85. package/dist/entity-BpbZqg19.d.ts +0 -1100
  86. package/dist/entity-Bq_eNEDI.d.ts +0 -28
  87. package/dist/entity-types-DAu8sGJH.d.ts +0 -26
  88. package/dist/global-change-Dc8uCKi2.d.ts +0 -25
  89. package/dist/main.d.ts +0 -654
  90. package/dist/main.js +0 -13355
  91. package/dist/main.js.map +0 -1
  92. package/dist/stage-types-BFsm3qsZ.d.ts +0 -567
  93. package/dist/stage.d.ts +0 -51
  94. package/dist/stage.js +0 -5738
  95. package/dist/stage.js.map +0 -1
  96. package/dist/thruster-DhRaJnoL.d.ts +0 -172
  97. package/dist/world-Be5m1XC1.d.ts +0 -31
package/dist/entities.js DELETED
@@ -1,4069 +0,0 @@
1
- // src/lib/entities/box.ts
2
- import { Vector3 as Vector38 } from "three";
3
-
4
- // src/lib/entities/entity.ts
5
- import { Mesh, ShaderMaterial, Group } from "three";
6
-
7
- // src/lib/systems/transformable.system.ts
8
- import {
9
- defineSystem,
10
- defineQuery,
11
- defineComponent,
12
- Types,
13
- removeQuery
14
- } from "bitecs";
15
- import { Quaternion } from "three";
16
- var position = defineComponent({
17
- x: Types.f32,
18
- y: Types.f32,
19
- z: Types.f32
20
- });
21
- var rotation = defineComponent({
22
- x: Types.f32,
23
- y: Types.f32,
24
- z: Types.f32,
25
- w: Types.f32
26
- });
27
- var scale = defineComponent({
28
- x: Types.f32,
29
- y: Types.f32,
30
- z: Types.f32
31
- });
32
- var _tempQuaternion = new Quaternion();
33
-
34
- // src/lib/core/flags.ts
35
- var DEBUG_FLAG = import.meta.env.VITE_DEBUG_FLAG === "true";
36
-
37
- // src/lib/core/base-node.ts
38
- import { nanoid } from "nanoid";
39
- var BaseNode = class _BaseNode {
40
- parent = null;
41
- children = [];
42
- options;
43
- eid = 0;
44
- uuid = "";
45
- name = "";
46
- markedForRemoval = false;
47
- /**
48
- * Lifecycle callback arrays - use onSetup(), onUpdate(), etc. to add callbacks
49
- * Uses `any` for the type parameter to avoid invariance issues when subclasses
50
- * are assigned to BaseNode references. Type safety is enforced by the public
51
- * onSetup/onUpdate/etc. methods which are typed with `this`.
52
- */
53
- lifecycleCallbacks = {
54
- setup: [],
55
- loaded: [],
56
- update: [],
57
- destroy: [],
58
- cleanup: []
59
- };
60
- constructor(args = []) {
61
- const options = args.filter((arg) => !(arg instanceof _BaseNode)).reduce((acc, opt) => ({ ...acc, ...opt }), {});
62
- this.options = options;
63
- this.uuid = nanoid();
64
- }
65
- // ─────────────────────────────────────────────────────────────────────────────
66
- // Fluent API for adding lifecycle callbacks
67
- // ─────────────────────────────────────────────────────────────────────────────
68
- /**
69
- * Add setup callbacks to be executed in order during nodeSetup
70
- */
71
- onSetup(...callbacks) {
72
- this.lifecycleCallbacks.setup.push(...callbacks);
73
- return this;
74
- }
75
- /**
76
- * Add loaded callbacks to be executed in order during nodeLoaded
77
- */
78
- onLoaded(...callbacks) {
79
- this.lifecycleCallbacks.loaded.push(...callbacks);
80
- return this;
81
- }
82
- /**
83
- * Add update callbacks to be executed in order during nodeUpdate
84
- */
85
- onUpdate(...callbacks) {
86
- this.lifecycleCallbacks.update.push(...callbacks);
87
- return this;
88
- }
89
- /**
90
- * Add destroy callbacks to be executed in order during nodeDestroy
91
- */
92
- onDestroy(...callbacks) {
93
- this.lifecycleCallbacks.destroy.push(...callbacks);
94
- return this;
95
- }
96
- /**
97
- * Add cleanup callbacks to be executed in order during nodeCleanup
98
- */
99
- onCleanup(...callbacks) {
100
- this.lifecycleCallbacks.cleanup.push(...callbacks);
101
- return this;
102
- }
103
- /**
104
- * Prepend setup callbacks (run before existing ones)
105
- */
106
- prependSetup(...callbacks) {
107
- this.lifecycleCallbacks.setup.unshift(...callbacks);
108
- return this;
109
- }
110
- /**
111
- * Prepend update callbacks (run before existing ones)
112
- */
113
- prependUpdate(...callbacks) {
114
- this.lifecycleCallbacks.update.unshift(...callbacks);
115
- return this;
116
- }
117
- // ─────────────────────────────────────────────────────────────────────────────
118
- // Tree structure
119
- // ─────────────────────────────────────────────────────────────────────────────
120
- setParent(parent) {
121
- this.parent = parent;
122
- }
123
- getParent() {
124
- return this.parent;
125
- }
126
- add(baseNode) {
127
- this.children.push(baseNode);
128
- baseNode.setParent(this);
129
- }
130
- remove(baseNode) {
131
- const index = this.children.indexOf(baseNode);
132
- if (index !== -1) {
133
- this.children.splice(index, 1);
134
- baseNode.setParent(null);
135
- }
136
- }
137
- getChildren() {
138
- return this.children;
139
- }
140
- isComposite() {
141
- return this.children.length > 0;
142
- }
143
- // ─────────────────────────────────────────────────────────────────────────────
144
- // Node lifecycle execution - runs internal + callback arrays
145
- // ─────────────────────────────────────────────────────────────────────────────
146
- nodeSetup(params) {
147
- if (DEBUG_FLAG) {
148
- }
149
- this.markedForRemoval = false;
150
- if (typeof this._setup === "function") {
151
- this._setup(params);
152
- }
153
- for (const callback of this.lifecycleCallbacks.setup) {
154
- callback(params);
155
- }
156
- this.children.forEach((child) => child.nodeSetup(params));
157
- }
158
- nodeUpdate(params) {
159
- if (this.markedForRemoval) {
160
- return;
161
- }
162
- if (typeof this._update === "function") {
163
- this._update(params);
164
- }
165
- if (typeof this._tickActions === "function") {
166
- this._tickActions(params.delta);
167
- }
168
- for (const callback of this.lifecycleCallbacks.update) {
169
- callback(params);
170
- }
171
- this.children.forEach((child) => child.nodeUpdate(params));
172
- }
173
- nodeDestroy(params) {
174
- if (this.markedForRemoval) return;
175
- this.children.forEach((child) => child.nodeDestroy(params));
176
- for (const callback of this.lifecycleCallbacks.destroy) {
177
- callback(params);
178
- }
179
- if (typeof this._destroy === "function") {
180
- this._destroy(params);
181
- }
182
- this.markedForRemoval = true;
183
- for (const callback of this.lifecycleCallbacks.cleanup) {
184
- callback(params);
185
- }
186
- if (typeof this._cleanup === "function") {
187
- this._cleanup(params);
188
- }
189
- }
190
- async nodeLoaded(params) {
191
- if (typeof this._loaded === "function") {
192
- await this._loaded(params);
193
- }
194
- for (const callback of this.lifecycleCallbacks.loaded) {
195
- callback(params);
196
- }
197
- }
198
- nodeCleanup(params) {
199
- this.children.forEach((child) => child.nodeCleanup?.(params));
200
- for (const callback of this.lifecycleCallbacks.cleanup) {
201
- callback(params);
202
- }
203
- if (typeof this._cleanup === "function") {
204
- this._cleanup(params);
205
- }
206
- }
207
- // ─────────────────────────────────────────────────────────────────────────────
208
- // Options
209
- // ─────────────────────────────────────────────────────────────────────────────
210
- getOptions() {
211
- return this.options;
212
- }
213
- setOptions(options) {
214
- this.options = { ...this.options, ...options };
215
- }
216
- };
217
-
218
- // ../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs
219
- function mitt_default(n) {
220
- return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) {
221
- var i = n.get(t);
222
- i ? i.push(e) : n.set(t, [e]);
223
- }, off: function(t, e) {
224
- var i = n.get(t);
225
- i && (e ? i.splice(i.indexOf(e) >>> 0, 1) : n.set(t, []));
226
- }, emit: function(t, e) {
227
- var i = n.get(t);
228
- i && i.slice().map(function(n2) {
229
- n2(e);
230
- }), (i = n.get("*")) && i.slice().map(function(n2) {
231
- n2(t, e);
232
- });
233
- } };
234
- }
235
-
236
- // src/lib/events/event-emitter-delegate.ts
237
- var EventEmitterDelegate = class {
238
- emitter;
239
- unsubscribes = [];
240
- constructor() {
241
- this.emitter = mitt_default();
242
- }
243
- /**
244
- * Dispatch an event to all listeners.
245
- */
246
- dispatch(event, payload) {
247
- this.emitter.emit(event, payload);
248
- }
249
- /**
250
- * Subscribe to an event. Returns an unsubscribe function.
251
- */
252
- listen(event, handler) {
253
- this.emitter.on(event, handler);
254
- const unsub = () => this.emitter.off(event, handler);
255
- this.unsubscribes.push(unsub);
256
- return unsub;
257
- }
258
- /**
259
- * Subscribe to all events.
260
- */
261
- listenAll(handler) {
262
- this.emitter.on("*", handler);
263
- const unsub = () => this.emitter.off("*", handler);
264
- this.unsubscribes.push(unsub);
265
- return unsub;
266
- }
267
- /**
268
- * Clean up all subscriptions.
269
- */
270
- dispose() {
271
- this.unsubscribes.forEach((fn) => fn());
272
- this.unsubscribes = [];
273
- this.emitter.all.clear();
274
- }
275
- };
276
-
277
- // src/lib/events/zylem-events.ts
278
- var zylemEventBus = mitt_default();
279
-
280
- // src/lib/actions/capabilities/transform-store.ts
281
- import { proxy } from "valtio";
282
- function createTransformStore(initial) {
283
- const defaultState = {
284
- position: { x: 0, y: 0, z: 0 },
285
- rotation: { x: 0, y: 0, z: 0, w: 1 },
286
- velocity: { x: 0, y: 0, z: 0 },
287
- angularVelocity: { x: 0, y: 0, z: 0 },
288
- dirty: {
289
- position: false,
290
- rotation: false,
291
- velocity: false,
292
- angularVelocity: false
293
- }
294
- };
295
- return proxy({
296
- ...defaultState,
297
- ...initial,
298
- // Ensure dirty flags are properly initialized even if partial initial state
299
- dirty: {
300
- ...defaultState.dirty,
301
- ...initial?.dirty
302
- }
303
- });
304
- }
305
-
306
- // src/lib/actions/capabilities/moveable.ts
307
- import { Vector3 } from "three";
308
- function moveX(entity, delta) {
309
- if (!entity.transformStore) return;
310
- entity.transformStore.velocity.x = delta;
311
- entity.transformStore.dirty.velocity = true;
312
- }
313
- function moveY(entity, delta) {
314
- if (!entity.transformStore) return;
315
- entity.transformStore.velocity.y = delta;
316
- entity.transformStore.dirty.velocity = true;
317
- }
318
- function moveZ(entity, delta) {
319
- if (!entity.transformStore) return;
320
- entity.transformStore.velocity.z = delta;
321
- entity.transformStore.dirty.velocity = true;
322
- }
323
- function moveXY(entity, deltaX, deltaY) {
324
- if (!entity.transformStore) return;
325
- entity.transformStore.velocity.x = deltaX;
326
- entity.transformStore.velocity.y = deltaY;
327
- entity.transformStore.dirty.velocity = true;
328
- }
329
- function moveXZ(entity, deltaX, deltaZ) {
330
- if (!entity.transformStore) return;
331
- entity.transformStore.velocity.x = deltaX;
332
- entity.transformStore.velocity.z = deltaZ;
333
- entity.transformStore.dirty.velocity = true;
334
- }
335
- function move(entity, vector) {
336
- if (!entity.transformStore) return;
337
- entity.transformStore.velocity.x += vector.x;
338
- entity.transformStore.velocity.y += vector.y;
339
- entity.transformStore.velocity.z += vector.z;
340
- entity.transformStore.dirty.velocity = true;
341
- }
342
- function resetVelocity(entity) {
343
- if (!entity.body) return;
344
- entity.body.setLinvel(new Vector3(0, 0, 0), true);
345
- entity.body.setLinearDamping(5);
346
- }
347
- function moveForwardXY(entity, delta, rotation2DAngle) {
348
- const deltaX = Math.sin(-rotation2DAngle) * delta;
349
- const deltaY = Math.cos(-rotation2DAngle) * delta;
350
- moveXY(entity, deltaX, deltaY);
351
- }
352
- function getPosition(entity) {
353
- if (!entity.body) return null;
354
- return entity.body.translation();
355
- }
356
- function getVelocity(entity) {
357
- if (!entity.body) return null;
358
- return entity.body.linvel();
359
- }
360
- function setPosition(entity, x, y, z) {
361
- if (!entity.body) return;
362
- entity.body.setTranslation({ x, y, z }, true);
363
- }
364
- function setPositionX(entity, x) {
365
- if (!entity.body) return;
366
- const { y, z } = entity.body.translation();
367
- entity.body.setTranslation({ x, y, z }, true);
368
- }
369
- function setPositionY(entity, y) {
370
- if (!entity.body) return;
371
- const { x, z } = entity.body.translation();
372
- entity.body.setTranslation({ x, y, z }, true);
373
- }
374
- function setPositionZ(entity, z) {
375
- if (!entity.body) return;
376
- const { x, y } = entity.body.translation();
377
- entity.body.setTranslation({ x, y, z }, true);
378
- }
379
- function wrapAroundXY(entity, boundsX, boundsY) {
380
- const position2 = getPosition(entity);
381
- if (!position2) return;
382
- const { x, y } = position2;
383
- const newX = x > boundsX ? -boundsX : x < -boundsX ? boundsX : x;
384
- const newY = y > boundsY ? -boundsY : y < -boundsY ? boundsY : y;
385
- if (newX !== x || newY !== y) {
386
- setPosition(entity, newX, newY, 0);
387
- }
388
- }
389
- function wrapAround3D(entity, boundsX, boundsY, boundsZ) {
390
- const position2 = getPosition(entity);
391
- if (!position2) return;
392
- const { x, y, z } = position2;
393
- const newX = x > boundsX ? -boundsX : x < -boundsX ? boundsX : x;
394
- const newY = y > boundsY ? -boundsY : y < -boundsY ? boundsY : y;
395
- const newZ = z > boundsZ ? -boundsZ : z < -boundsZ ? boundsZ : z;
396
- if (newX !== x || newY !== y || newZ !== z) {
397
- setPosition(entity, newX, newY, newZ);
398
- }
399
- }
400
- function makeMoveable(entity) {
401
- const moveable = entity;
402
- if (!moveable.transformStore) {
403
- moveable.transformStore = createTransformStore();
404
- }
405
- moveable.moveX = (delta) => moveX(entity, delta);
406
- moveable.moveY = (delta) => moveY(entity, delta);
407
- moveable.moveZ = (delta) => moveZ(entity, delta);
408
- moveable.moveXY = (deltaX, deltaY) => moveXY(entity, deltaX, deltaY);
409
- moveable.moveXZ = (deltaX, deltaZ) => moveXZ(entity, deltaX, deltaZ);
410
- moveable.move = (vector) => move(entity, vector);
411
- moveable.resetVelocity = () => resetVelocity(entity);
412
- moveable.moveForwardXY = (delta, rotation2DAngle) => moveForwardXY(entity, delta, rotation2DAngle);
413
- moveable.getPosition = () => getPosition(entity);
414
- moveable.getVelocity = () => getVelocity(entity);
415
- moveable.setPosition = (x, y, z) => setPosition(entity, x, y, z);
416
- moveable.setPositionX = (x) => setPositionX(entity, x);
417
- moveable.setPositionY = (y) => setPositionY(entity, y);
418
- moveable.setPositionZ = (z) => setPositionZ(entity, z);
419
- moveable.wrapAroundXY = (boundsX, boundsY) => wrapAroundXY(entity, boundsX, boundsY);
420
- moveable.wrapAround3D = (boundsX, boundsY, boundsZ) => wrapAround3D(entity, boundsX, boundsY, boundsZ);
421
- return moveable;
422
- }
423
-
424
- // src/lib/actions/capabilities/rotatable.ts
425
- import { Euler, Vector3 as Vector32, MathUtils, Quaternion as Quaternion2 } from "three";
426
- function rotateInDirection(entity, moveVector) {
427
- if (!entity.body) return;
428
- const rotate = Math.atan2(-moveVector.x, moveVector.z);
429
- rotateYEuler(entity, rotate);
430
- }
431
- function rotateYEuler(entity, amount) {
432
- rotateEuler(entity, new Vector32(0, -amount, 0));
433
- }
434
- function rotateEuler(entity, rotation2) {
435
- if (!entity.group) return;
436
- const euler = new Euler(rotation2.x, rotation2.y, rotation2.z);
437
- entity.group.setRotationFromEuler(euler);
438
- }
439
- function rotateY(entity, delta) {
440
- if (!entity.transformStore) return;
441
- const halfAngle = delta / 2;
442
- const deltaW = Math.cos(halfAngle);
443
- const deltaY = Math.sin(halfAngle);
444
- const q = entity.transformStore.rotation;
445
- const newW = q.w * deltaW - q.y * deltaY;
446
- const newX = q.x * deltaW + q.z * deltaY;
447
- const newY = q.y * deltaW + q.w * deltaY;
448
- const newZ = q.z * deltaW - q.x * deltaY;
449
- entity.transformStore.rotation.w = newW;
450
- entity.transformStore.rotation.x = newX;
451
- entity.transformStore.rotation.y = newY;
452
- entity.transformStore.rotation.z = newZ;
453
- entity.transformStore.dirty.rotation = true;
454
- }
455
- function rotateX(entity, delta) {
456
- if (!entity.transformStore) return;
457
- const halfAngle = delta / 2;
458
- const deltaW = Math.cos(halfAngle);
459
- const deltaX = Math.sin(halfAngle);
460
- const q = entity.transformStore.rotation;
461
- const newW = q.w * deltaW - q.x * deltaX;
462
- const newX = q.x * deltaW + q.w * deltaX;
463
- const newY = q.y * deltaW + q.z * deltaX;
464
- const newZ = q.z * deltaW - q.y * deltaX;
465
- entity.transformStore.rotation.w = newW;
466
- entity.transformStore.rotation.x = newX;
467
- entity.transformStore.rotation.y = newY;
468
- entity.transformStore.rotation.z = newZ;
469
- entity.transformStore.dirty.rotation = true;
470
- }
471
- function rotateZ(entity, delta) {
472
- if (!entity.transformStore) return;
473
- const halfAngle = delta / 2;
474
- const deltaW = Math.cos(halfAngle);
475
- const deltaZ = Math.sin(halfAngle);
476
- const q = entity.transformStore.rotation;
477
- const newW = q.w * deltaW - q.z * deltaZ;
478
- const newX = q.x * deltaW - q.y * deltaZ;
479
- const newY = q.y * deltaW + q.x * deltaZ;
480
- const newZ = q.z * deltaW + q.w * deltaZ;
481
- entity.transformStore.rotation.w = newW;
482
- entity.transformStore.rotation.x = newX;
483
- entity.transformStore.rotation.y = newY;
484
- entity.transformStore.rotation.z = newZ;
485
- entity.transformStore.dirty.rotation = true;
486
- }
487
- function setRotationY(entity, y) {
488
- if (!entity.transformStore) return;
489
- const halfAngle = y / 2;
490
- const w = Math.cos(halfAngle);
491
- const yComponent = Math.sin(halfAngle);
492
- entity.transformStore.rotation.w = w;
493
- entity.transformStore.rotation.x = 0;
494
- entity.transformStore.rotation.y = yComponent;
495
- entity.transformStore.rotation.z = 0;
496
- entity.transformStore.dirty.rotation = true;
497
- }
498
- function setRotationDegreesY(entity, y) {
499
- if (!entity.body) return;
500
- setRotationY(entity, MathUtils.degToRad(y));
501
- }
502
- function setRotationX(entity, x) {
503
- if (!entity.transformStore) return;
504
- const halfAngle = x / 2;
505
- const w = Math.cos(halfAngle);
506
- const xComponent = Math.sin(halfAngle);
507
- entity.transformStore.rotation.w = w;
508
- entity.transformStore.rotation.x = xComponent;
509
- entity.transformStore.rotation.y = 0;
510
- entity.transformStore.rotation.z = 0;
511
- entity.transformStore.dirty.rotation = true;
512
- }
513
- function setRotationDegreesX(entity, x) {
514
- if (!entity.body) return;
515
- setRotationX(entity, MathUtils.degToRad(x));
516
- }
517
- function setRotationZ(entity, z) {
518
- if (!entity.transformStore) return;
519
- const halfAngle = z / 2;
520
- const w = Math.cos(halfAngle);
521
- const zComponent = Math.sin(halfAngle);
522
- entity.transformStore.rotation.w = w;
523
- entity.transformStore.rotation.x = 0;
524
- entity.transformStore.rotation.y = 0;
525
- entity.transformStore.rotation.z = zComponent;
526
- entity.transformStore.dirty.rotation = true;
527
- }
528
- function setRotationDegreesZ(entity, z) {
529
- if (!entity.body) return;
530
- setRotationZ(entity, MathUtils.degToRad(z));
531
- }
532
- function setRotation(entity, x, y, z) {
533
- if (!entity.transformStore) return;
534
- const quat = new Quaternion2().setFromEuler(new Euler(x, y, z));
535
- entity.transformStore.rotation.w = quat.w;
536
- entity.transformStore.rotation.x = quat.x;
537
- entity.transformStore.rotation.y = quat.y;
538
- entity.transformStore.rotation.z = quat.z;
539
- entity.transformStore.dirty.rotation = true;
540
- }
541
- function setRotationDegrees(entity, x, y, z) {
542
- if (!entity.body) return;
543
- setRotation(entity, MathUtils.degToRad(x), MathUtils.degToRad(y), MathUtils.degToRad(z));
544
- }
545
- function getRotation(entity) {
546
- if (!entity.body) return null;
547
- return entity.body.rotation();
548
- }
549
- function makeRotatable(entity) {
550
- const rotatableEntity = entity;
551
- if (!rotatableEntity.transformStore) {
552
- rotatableEntity.transformStore = createTransformStore();
553
- }
554
- rotatableEntity.rotateInDirection = (moveVector) => rotateInDirection(entity, moveVector);
555
- rotatableEntity.rotateYEuler = (amount) => rotateYEuler(entity, amount);
556
- rotatableEntity.rotateEuler = (rotation2) => rotateEuler(entity, rotation2);
557
- rotatableEntity.rotateX = (delta) => rotateX(entity, delta);
558
- rotatableEntity.rotateY = (delta) => rotateY(entity, delta);
559
- rotatableEntity.rotateZ = (delta) => rotateZ(entity, delta);
560
- rotatableEntity.setRotationY = (y) => setRotationY(entity, y);
561
- rotatableEntity.setRotationX = (x) => setRotationX(entity, x);
562
- rotatableEntity.setRotationZ = (z) => setRotationZ(entity, z);
563
- rotatableEntity.setRotationDegreesY = (y) => setRotationDegreesY(entity, y);
564
- rotatableEntity.setRotationDegreesX = (x) => setRotationDegreesX(entity, x);
565
- rotatableEntity.setRotationDegreesZ = (z) => setRotationDegreesZ(entity, z);
566
- rotatableEntity.setRotationDegrees = (x, y, z) => setRotationDegrees(entity, x, y, z);
567
- rotatableEntity.setRotation = (x, y, z) => setRotation(entity, x, y, z);
568
- rotatableEntity.getRotation = () => getRotation(entity);
569
- return rotatableEntity;
570
- }
571
-
572
- // src/lib/actions/capabilities/transformable.ts
573
- function makeTransformable(entity) {
574
- const withMovement = makeMoveable(entity);
575
- const withRotation = makeRotatable(withMovement);
576
- return withRotation;
577
- }
578
-
579
- // src/lib/entities/parts/collision-factories.ts
580
- import {
581
- ActiveCollisionTypes as ActiveCollisionTypes2,
582
- ColliderDesc as ColliderDesc2,
583
- RigidBodyDesc as RigidBodyDesc2,
584
- RigidBodyType as RigidBodyType2
585
- } from "@dimforge/rapier3d-compat";
586
- import { Vector3 as Vector34 } from "three";
587
-
588
- // src/lib/collision/collision-builder.ts
589
- import { ActiveCollisionTypes, ColliderDesc, RigidBodyDesc, RigidBodyType, Vector3 as Vector33 } from "@dimforge/rapier3d-compat";
590
- var typeToGroup = /* @__PURE__ */ new Map();
591
- var nextGroupId = 0;
592
- function getOrCreateCollisionGroupId(type) {
593
- let groupId = typeToGroup.get(type);
594
- if (groupId === void 0) {
595
- groupId = nextGroupId++ % 16;
596
- typeToGroup.set(type, groupId);
597
- }
598
- return groupId;
599
- }
600
- function createCollisionFilter(allowedTypes) {
601
- let filter = 0;
602
- allowedTypes.forEach((type) => {
603
- const groupId = getOrCreateCollisionGroupId(type);
604
- filter |= 1 << groupId;
605
- });
606
- return filter;
607
- }
608
- var CollisionBuilder = class {
609
- static = false;
610
- sensor = false;
611
- gravity = new Vector33(0, 0, 0);
612
- build(options) {
613
- const bodyDesc = this.bodyDesc({
614
- isDynamicBody: !this.static
615
- });
616
- const collider = this.collider(options);
617
- const type = options.collisionType;
618
- if (type) {
619
- let groupId = getOrCreateCollisionGroupId(type);
620
- let filter = 65535;
621
- if (options.collisionFilter) {
622
- filter = createCollisionFilter(options.collisionFilter);
623
- }
624
- collider.setCollisionGroups(groupId << 16 | filter);
625
- }
626
- const { KINEMATIC_FIXED, DEFAULT } = ActiveCollisionTypes;
627
- collider.activeCollisionTypes = this.sensor ? KINEMATIC_FIXED : DEFAULT;
628
- return [bodyDesc, collider];
629
- }
630
- withCollision(collisionOptions) {
631
- this.sensor = collisionOptions?.sensor ?? this.sensor;
632
- this.static = collisionOptions?.static ?? this.static;
633
- return this;
634
- }
635
- collider(options) {
636
- const size = options.size ?? new Vector33(1, 1, 1);
637
- const half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };
638
- let colliderDesc = ColliderDesc.cuboid(half.x, half.y, half.z);
639
- return colliderDesc;
640
- }
641
- bodyDesc({ isDynamicBody = true }) {
642
- const type = isDynamicBody ? RigidBodyType.Dynamic : RigidBodyType.Fixed;
643
- const bodyDesc = new RigidBodyDesc(type).setTranslation(0, 0, 0).setGravityScale(1).setCanSleep(false).setCcdEnabled(true);
644
- return bodyDesc;
645
- }
646
- };
647
-
648
- // src/lib/entities/parts/collision-factories.ts
649
- function isCollisionComponent(obj) {
650
- return obj && obj.__kind === "collision";
651
- }
652
- function buildBodyDesc(isStatic) {
653
- const type = isStatic ? RigidBodyType2.Fixed : RigidBodyType2.Dynamic;
654
- return new RigidBodyDesc2(type).setTranslation(0, 0, 0).setGravityScale(1).setCanSleep(false).setCcdEnabled(true);
655
- }
656
- function applyCollisionOptions(colliderDesc, opts) {
657
- if (opts.offset) {
658
- colliderDesc.setTranslation(opts.offset.x, opts.offset.y, opts.offset.z);
659
- }
660
- if (opts.sensor) {
661
- colliderDesc.setSensor(true);
662
- colliderDesc.activeCollisionTypes = ActiveCollisionTypes2.KINEMATIC_FIXED;
663
- }
664
- if (opts.collisionType) {
665
- const groupId = getOrCreateCollisionGroupId(opts.collisionType);
666
- let filter = 65535;
667
- if (opts.collisionFilter) {
668
- filter = createCollisionFilter(opts.collisionFilter);
669
- }
670
- colliderDesc.setCollisionGroups(groupId << 16 | filter);
671
- }
672
- }
673
- function makeComponent(colliderDesc, opts) {
674
- applyCollisionOptions(colliderDesc, opts);
675
- return {
676
- __kind: "collision",
677
- bodyDesc: buildBodyDesc(opts.static ?? false),
678
- colliderDesc
679
- };
680
- }
681
- function boxCollision(opts = {}) {
682
- const size = opts.size ?? { x: 1, y: 1, z: 1 };
683
- const desc = ColliderDesc2.cuboid(size.x / 2, size.y / 2, size.z / 2);
684
- return makeComponent(desc, opts);
685
- }
686
- function sphereCollision(opts = {}) {
687
- const desc = ColliderDesc2.ball(opts.radius ?? 1);
688
- return makeComponent(desc, opts);
689
- }
690
- function coneCollision(opts = {}) {
691
- const radius = opts.radius ?? 1;
692
- const height = opts.height ?? 2;
693
- const desc = ColliderDesc2.cone(height / 2, radius);
694
- return makeComponent(desc, opts);
695
- }
696
- function pyramidCollision(opts = {}) {
697
- const radius = opts.radius ?? 1;
698
- const height = opts.height ?? 2;
699
- const desc = ColliderDesc2.cone(height / 2, radius);
700
- return makeComponent(desc, opts);
701
- }
702
- function cylinderCollision(opts = {}) {
703
- const radius = Math.max(opts.radiusTop ?? 1, opts.radiusBottom ?? 1);
704
- const height = opts.height ?? 2;
705
- const desc = ColliderDesc2.cylinder(height / 2, radius);
706
- return makeComponent(desc, opts);
707
- }
708
- function pillCollision(opts = {}) {
709
- const radius = opts.radius ?? 0.5;
710
- const length = opts.length ?? 1;
711
- const desc = ColliderDesc2.capsule(length / 2, radius);
712
- return makeComponent(desc, opts);
713
- }
714
- function zoneCollision(opts = {}) {
715
- const size = opts.size ?? { x: 1, y: 1, z: 1 };
716
- const desc = ColliderDesc2.cuboid(size.x / 2, size.y / 2, size.z / 2);
717
- desc.setSensor(true);
718
- desc.activeCollisionTypes = ActiveCollisionTypes2.KINEMATIC_FIXED;
719
- return makeComponent(desc, { ...opts, static: opts.static ?? true, sensor: true });
720
- }
721
-
722
- // src/lib/entities/common.ts
723
- import { Color, Vector3 as Vector35 } from "three";
724
-
725
- // src/lib/graphics/shaders/vertex/object.shader.ts
726
- var objectVertexShader = `
727
- uniform vec2 uvScale;
728
- varying vec2 vUv;
729
-
730
- void main() {
731
- vUv = uv;
732
- vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
733
- gl_Position = projectionMatrix * mvPosition;
734
- }
735
- `;
736
-
737
- // src/lib/graphics/shaders/standard.shader.ts
738
- var fragment = `
739
- uniform sampler2D tDiffuse;
740
- varying vec2 vUv;
741
-
742
- void main() {
743
- vec4 texel = texture2D( tDiffuse, vUv );
744
-
745
- gl_FragColor = texel;
746
- }
747
- `;
748
- var standardShader = {
749
- vertex: objectVertexShader,
750
- fragment
751
- };
752
-
753
- // src/lib/entities/common.ts
754
- var commonDefaults = {
755
- position: new Vector35(0, 0, 0),
756
- material: {
757
- color: new Color("#ffffff"),
758
- shader: standardShader
759
- },
760
- collision: {
761
- static: false
762
- }
763
- };
764
- function mergeArgs(args, defaults) {
765
- const configArgs = args.filter(
766
- (arg) => !(arg instanceof BaseNode)
767
- );
768
- let merged = { ...defaults };
769
- for (const opt of configArgs) {
770
- merged = { ...merged, ...opt };
771
- }
772
- return merged;
773
- }
774
-
775
- // src/lib/entities/entity.ts
776
- var GameEntity = class extends BaseNode {
777
- behaviors = [];
778
- group;
779
- mesh;
780
- materials;
781
- bodyDesc = null;
782
- body = null;
783
- colliderDesc;
784
- collider;
785
- custom = {};
786
- // Compound entity support: multiple colliders and meshes
787
- /** All collider descriptions for this entity (including primary) */
788
- colliderDescs = [];
789
- /** All colliders attached to this entity's rigid body */
790
- colliders = [];
791
- /** Additional meshes for compound visual entities (added via add()) */
792
- compoundMeshes = [];
793
- debugInfo = {};
794
- debugMaterial;
795
- collisionDelegate = {
796
- collision: []
797
- };
798
- collisionType;
799
- // Instancing support
800
- /** Batch key for instanced rendering (null if not instanced) */
801
- batchKey = null;
802
- /** Index within the instanced mesh batch */
803
- instanceId = -1;
804
- /** Whether this entity uses instanced rendering */
805
- isInstanced = false;
806
- // Event delegate for dispatch/listen API
807
- eventDelegate = new EventEmitterDelegate();
808
- // Behavior references (new ECS pattern)
809
- behaviorRefs = [];
810
- // Transform store for batched physics updates (auto-created in create())
811
- transformStore;
812
- // Movement & rotation methods are assigned at runtime by makeTransformable.
813
- // The `implements` clause above ensures the type contract; declarations below
814
- // satisfy the compiler while the constructor fills them in.
815
- moveX;
816
- moveY;
817
- moveZ;
818
- moveXY;
819
- moveXZ;
820
- move;
821
- resetVelocity;
822
- moveForwardXY;
823
- getPosition;
824
- getVelocity;
825
- setPosition;
826
- setPositionX;
827
- setPositionY;
828
- setPositionZ;
829
- wrapAroundXY;
830
- wrapAround3D;
831
- rotateInDirection;
832
- rotateYEuler;
833
- rotateEuler;
834
- rotateX;
835
- rotateY;
836
- rotateZ;
837
- setRotationY;
838
- setRotationX;
839
- setRotationZ;
840
- setRotationDegrees;
841
- setRotationDegreesY;
842
- setRotationDegreesX;
843
- setRotationDegreesZ;
844
- setRotation;
845
- getRotation;
846
- constructor() {
847
- super();
848
- this.transformStore = createTransformStore();
849
- makeTransformable(this);
850
- }
851
- // ─────────────────────────────────────────────────────────────────────────────
852
- // Composable add() - accepts Mesh, CollisionComponent, or child entities
853
- // ─────────────────────────────────────────────────────────────────────────────
854
- /**
855
- * Add meshes, collision components, or child entities to this entity.
856
- * Supports fluent chaining: `entity.add(boxMesh()).add(boxCollision())`.
857
- *
858
- * - `Mesh`: First mesh becomes the primary mesh; subsequent meshes are
859
- * compound meshes grouped together.
860
- * - `CollisionComponent`: First sets bodyDesc + colliderDesc; subsequent
861
- * add extra colliders to the same rigid body.
862
- * - `NodeInterface`: Added as a child entity (delegates to BaseNode.add).
863
- */
864
- add(...components) {
865
- for (const component of components) {
866
- if (component instanceof Mesh) {
867
- this.addMeshComponent(component);
868
- } else if (isCollisionComponent(component)) {
869
- this.addCollisionComponent(component);
870
- } else {
871
- super.add(component);
872
- }
873
- }
874
- return this;
875
- }
876
- addMeshComponent(mesh) {
877
- if (!this.mesh) {
878
- this.mesh = mesh;
879
- if (!this.materials) {
880
- this.materials = [];
881
- }
882
- if (mesh.material) {
883
- const mat = mesh.material;
884
- if (Array.isArray(mat)) {
885
- this.materials.push(...mat);
886
- } else {
887
- this.materials.push(mat);
888
- }
889
- }
890
- } else {
891
- this.compoundMeshes.push(mesh);
892
- if (!this.group) {
893
- this.group = new Group();
894
- this.group.add(this.mesh);
895
- }
896
- this.group.add(mesh);
897
- }
898
- }
899
- addCollisionComponent(collision) {
900
- if (!this.bodyDesc) {
901
- this.bodyDesc = collision.bodyDesc;
902
- this.colliderDesc = collision.colliderDesc;
903
- this.colliderDescs.push(collision.colliderDesc);
904
- const pos = this.options?.position ?? { x: 0, y: 0, z: 0 };
905
- this.bodyDesc.setTranslation(pos.x, pos.y, pos.z);
906
- } else {
907
- this.colliderDescs.push(collision.colliderDesc);
908
- }
909
- }
910
- // ─────────────────────────────────────────────────────────────────────────────
911
- // Actions API -- entity-scoped, self-contained stateful actions
912
- // ─────────────────────────────────────────────────────────────────────────────
913
- _actions = [];
914
- /**
915
- * Run a fire-and-forget action. Auto-removed when done.
916
- * @example `me.runAction(moveBy({ x: 10, duration: 0.3 }))`
917
- */
918
- runAction(action) {
919
- this._actions.push(action);
920
- return action;
921
- }
922
- /**
923
- * Register a persistent action (throttle, onPress). Not removed when done.
924
- * @example `const press = entity.action(onPress())`
925
- */
926
- action(action) {
927
- action.persistent = true;
928
- this._actions.push(action);
929
- return action;
930
- }
931
- /**
932
- * Tick all registered actions. Called automatically before user onUpdate callbacks.
933
- *
934
- * Resets velocity/angularVelocity accumulation before ticking so that
935
- * actions can compose via `+=` without cross-frame build-up.
936
- * (The existing move helpers like `moveXY` use `=` which doesn't accumulate,
937
- * but action composition needs additive writes on a clean slate each frame.)
938
- */
939
- _tickActions(delta) {
940
- if (this._actions.length === 0) return;
941
- const store = this.transformStore;
942
- store.velocity.x = 0;
943
- store.velocity.y = 0;
944
- store.velocity.z = 0;
945
- store.dirty.velocity = false;
946
- store.angularVelocity.x = 0;
947
- store.angularVelocity.y = 0;
948
- store.angularVelocity.z = 0;
949
- store.dirty.angularVelocity = false;
950
- for (let i = this._actions.length - 1; i >= 0; i--) {
951
- const act = this._actions[i];
952
- act.tick(this, delta);
953
- if (act.done && !act.persistent) {
954
- this._actions.splice(i, 1);
955
- }
956
- }
957
- if (this._actions.length === 0) {
958
- store.velocity.x = 0;
959
- store.velocity.y = 0;
960
- store.velocity.z = 0;
961
- store.dirty.velocity = false;
962
- store.angularVelocity.x = 0;
963
- store.angularVelocity.y = 0;
964
- store.angularVelocity.z = 0;
965
- store.dirty.angularVelocity = false;
966
- }
967
- }
968
- create() {
969
- const { position: setupPosition } = this.options;
970
- const { x, y, z } = setupPosition || { x: 0, y: 0, z: 0 };
971
- this.behaviors = [
972
- { component: position, values: { x, y, z } },
973
- { component: scale, values: { x: 0, y: 0, z: 0 } },
974
- { component: rotation, values: { x: 0, y: 0, z: 0, w: 0 } }
975
- ];
976
- this.name = this.options.name || "";
977
- return this;
978
- }
979
- /**
980
- * Add collision callbacks
981
- */
982
- onCollision(...callbacks) {
983
- const existing = this.collisionDelegate.collision ?? [];
984
- this.collisionDelegate.collision = [...existing, ...callbacks];
985
- return this;
986
- }
987
- /**
988
- * Use a behavior on this entity via typed descriptor.
989
- * Behaviors will be auto-registered as systems when the entity is spawned.
990
- * @param descriptor The behavior descriptor (import from behaviors module)
991
- * @param options Optional overrides for the behavior's default options
992
- * @returns BehaviorHandle with behavior-specific methods for lazy FSM access
993
- */
994
- use(descriptor, options) {
995
- const behaviorRef = {
996
- descriptor,
997
- options: { ...descriptor.defaultOptions, ...options }
998
- };
999
- this.behaviorRefs.push(behaviorRef);
1000
- const baseHandle = {
1001
- getFSM: () => behaviorRef.fsm ?? null,
1002
- getOptions: () => behaviorRef.options,
1003
- ref: behaviorRef
1004
- };
1005
- const customMethods = descriptor.createHandle?.(behaviorRef) ?? {};
1006
- return {
1007
- ...baseHandle,
1008
- ...customMethods
1009
- };
1010
- }
1011
- /**
1012
- * Get all behavior references attached to this entity.
1013
- * Used by the stage to auto-register required systems.
1014
- */
1015
- getBehaviorRefs() {
1016
- return this.behaviorRefs;
1017
- }
1018
- /**
1019
- * Entity-specific setup - resets actions for a fresh stage session.
1020
- * (User callbacks are handled by BaseNode's lifecycleCallbacks.setup)
1021
- */
1022
- _setup(params) {
1023
- for (let i = this._actions.length - 1; i >= 0; i--) {
1024
- const act = this._actions[i];
1025
- if (act.done && !act.persistent) {
1026
- this._actions.splice(i, 1);
1027
- } else {
1028
- act.reset();
1029
- }
1030
- }
1031
- }
1032
- async _loaded(_params) {
1033
- }
1034
- /**
1035
- * Entity-specific update - updates materials.
1036
- * Transform changes are applied by the stage after all update callbacks complete.
1037
- * (User callbacks are handled by BaseNode's lifecycleCallbacks.update)
1038
- */
1039
- _update(params) {
1040
- this.updateMaterials(params);
1041
- }
1042
- /**
1043
- * Entity-specific destroy -- reserved for consumer game logic.
1044
- * Engine-internal resource disposal runs in _cleanup() instead.
1045
- */
1046
- _destroy(params) {
1047
- }
1048
- /**
1049
- * Engine-internal resource cleanup -- runs automatically after destroy.
1050
- * Disposes GPU/DOM resources (meshes, materials, debug material).
1051
- *
1052
- * Note: actions, collision callbacks, and behavior refs are intentionally
1053
- * NOT cleared here -- they are registered by consumer code at module level
1054
- * and must persist across stage reloads. Actions are reset in _setup().
1055
- */
1056
- _cleanup(_params) {
1057
- this.disposeEvents();
1058
- for (const m of this.compoundMeshes) {
1059
- m.geometry?.dispose();
1060
- if (Array.isArray(m.material)) {
1061
- m.material.forEach((mat) => mat.dispose());
1062
- } else {
1063
- m.material?.dispose();
1064
- }
1065
- }
1066
- this.compoundMeshes.length = 0;
1067
- this.debugMaterial?.dispose();
1068
- this.debugMaterial = void 0;
1069
- this.group?.removeFromParent();
1070
- }
1071
- _collision(other, globals) {
1072
- if (this.collisionDelegate.collision?.length) {
1073
- const callbacks = this.collisionDelegate.collision;
1074
- callbacks.forEach((callback) => {
1075
- callback({ entity: this, other, globals });
1076
- });
1077
- }
1078
- }
1079
- updateMaterials(params) {
1080
- if (!this.materials?.length) {
1081
- return;
1082
- }
1083
- for (const material of this.materials) {
1084
- if (material instanceof ShaderMaterial) {
1085
- if (material.uniforms) {
1086
- material.uniforms.iTime && (material.uniforms.iTime.value += params.delta);
1087
- }
1088
- }
1089
- }
1090
- }
1091
- buildInfo() {
1092
- const info = {};
1093
- info.name = this.name;
1094
- info.uuid = this.uuid;
1095
- info.eid = this.eid.toString();
1096
- return info;
1097
- }
1098
- // ─────────────────────────────────────────────────────────────────────────────
1099
- // Events API
1100
- // ─────────────────────────────────────────────────────────────────────────────
1101
- /**
1102
- * Dispatch an event from this entity.
1103
- * Events are emitted both locally and to the global event bus.
1104
- */
1105
- dispatch(event, payload) {
1106
- this.eventDelegate.dispatch(event, payload);
1107
- zylemEventBus.emit(event, payload);
1108
- }
1109
- /**
1110
- * Listen for events on this entity instance.
1111
- * @returns Unsubscribe function
1112
- */
1113
- listen(event, handler) {
1114
- return this.eventDelegate.listen(event, handler);
1115
- }
1116
- /**
1117
- * Clean up entity event subscriptions.
1118
- */
1119
- disposeEvents() {
1120
- this.eventDelegate.dispose();
1121
- }
1122
- };
1123
-
1124
- // src/lib/entities/delegates/debug.ts
1125
- import { MeshStandardMaterial, MeshBasicMaterial, MeshPhongMaterial } from "three";
1126
- function hasDebugInfo(obj) {
1127
- return obj && typeof obj.getDebugInfo === "function";
1128
- }
1129
- var DebugDelegate = class {
1130
- entity;
1131
- constructor(entity) {
1132
- this.entity = entity;
1133
- }
1134
- /**
1135
- * Get formatted position string
1136
- */
1137
- getPositionString() {
1138
- if (this.entity.mesh) {
1139
- const { x: x2, y: y2, z: z2 } = this.entity.mesh.position;
1140
- return `${x2.toFixed(2)}, ${y2.toFixed(2)}, ${z2.toFixed(2)}`;
1141
- }
1142
- const { x, y, z } = this.entity.options.position || { x: 0, y: 0, z: 0 };
1143
- return `${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)}`;
1144
- }
1145
- /**
1146
- * Get formatted rotation string (in degrees)
1147
- */
1148
- getRotationString() {
1149
- if (this.entity.mesh) {
1150
- const { x: x2, y: y2, z: z2 } = this.entity.mesh.rotation;
1151
- const toDeg2 = (rad) => (rad * 180 / Math.PI).toFixed(1);
1152
- return `${toDeg2(x2)}\xB0, ${toDeg2(y2)}\xB0, ${toDeg2(z2)}\xB0`;
1153
- }
1154
- const { x, y, z } = this.entity.options.rotation || { x: 0, y: 0, z: 0 };
1155
- const toDeg = (rad) => (rad * 180 / Math.PI).toFixed(1);
1156
- return `${toDeg(x)}\xB0, ${toDeg(y)}\xB0, ${toDeg(z)}\xB0`;
1157
- }
1158
- /**
1159
- * Get material information
1160
- */
1161
- getMaterialInfo() {
1162
- if (!this.entity.mesh || !this.entity.mesh.material) {
1163
- return { type: "none" };
1164
- }
1165
- const material = Array.isArray(this.entity.mesh.material) ? this.entity.mesh.material[0] : this.entity.mesh.material;
1166
- const info = {
1167
- type: material.type
1168
- };
1169
- if (material instanceof MeshStandardMaterial || material instanceof MeshBasicMaterial || material instanceof MeshPhongMaterial) {
1170
- info.color = `#${material.color.getHexString()}`;
1171
- info.opacity = material.opacity;
1172
- info.transparent = material.transparent;
1173
- }
1174
- if ("roughness" in material) {
1175
- info.roughness = material.roughness;
1176
- }
1177
- if ("metalness" in material) {
1178
- info.metalness = material.metalness;
1179
- }
1180
- return info;
1181
- }
1182
- getPhysicsInfo() {
1183
- if (!this.entity.body) {
1184
- return null;
1185
- }
1186
- const info = {
1187
- type: this.entity.body.bodyType(),
1188
- mass: this.entity.body.mass(),
1189
- isEnabled: this.entity.body.isEnabled(),
1190
- isSleeping: this.entity.body.isSleeping()
1191
- };
1192
- const velocity = this.entity.body.linvel();
1193
- info.velocity = `${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)}, ${velocity.z.toFixed(2)}`;
1194
- return info;
1195
- }
1196
- buildDebugInfo() {
1197
- const defaultInfo = {
1198
- name: this.entity.name || this.entity.uuid,
1199
- uuid: this.entity.uuid,
1200
- position: this.getPositionString(),
1201
- rotation: this.getRotationString(),
1202
- material: this.getMaterialInfo()
1203
- };
1204
- const physicsInfo = this.getPhysicsInfo();
1205
- if (physicsInfo) {
1206
- defaultInfo.physics = physicsInfo;
1207
- }
1208
- if (this.entity.behaviors.length > 0) {
1209
- defaultInfo.behaviors = this.entity.behaviors.map((b) => b.constructor.name);
1210
- }
1211
- if (hasDebugInfo(this.entity)) {
1212
- const customInfo = this.entity.getDebugInfo();
1213
- return { ...defaultInfo, ...customInfo };
1214
- }
1215
- return defaultInfo;
1216
- }
1217
- };
1218
-
1219
- // src/lib/entities/parts/mesh-factories.ts
1220
- import {
1221
- BoxGeometry,
1222
- CapsuleGeometry,
1223
- ConeGeometry,
1224
- CylinderGeometry,
1225
- Mesh as Mesh2,
1226
- MeshStandardMaterial as MeshStandardMaterial3,
1227
- SphereGeometry
1228
- } from "three";
1229
-
1230
- // src/lib/graphics/material.ts
1231
- import { Color as Color3, Vector2 as Vector22, Vector3 as Vector37 } from "three";
1232
- import {
1233
- MeshPhongMaterial as MeshPhongMaterial2,
1234
- MeshStandardMaterial as MeshStandardMaterial2,
1235
- RepeatWrapping as RepeatWrapping2,
1236
- ShaderMaterial as ShaderMaterial2
1237
- } from "three";
1238
- import {
1239
- MeshBasicNodeMaterial,
1240
- MeshStandardNodeMaterial
1241
- } from "three/webgpu";
1242
-
1243
- // src/lib/core/utility/strings.ts
1244
- function sortedStringify(obj) {
1245
- const sortedObj = Object.keys(obj).sort().reduce((acc, key) => {
1246
- acc[key] = obj[key];
1247
- return acc;
1248
- }, {});
1249
- return JSON.stringify(sortedObj);
1250
- }
1251
- function shortHash(objString) {
1252
- let hash = 0;
1253
- for (let i = 0; i < objString.length; i++) {
1254
- hash = Math.imul(31, hash) + objString.charCodeAt(i) | 0;
1255
- }
1256
- return hash.toString(36);
1257
- }
1258
-
1259
- // src/lib/core/asset-manager.ts
1260
- import { LoadingManager, Cache } from "three";
1261
-
1262
- // src/lib/core/loaders/texture-loader.ts
1263
- import { TextureLoader, RepeatWrapping } from "three";
1264
- var TextureLoaderAdapter = class {
1265
- loader;
1266
- constructor() {
1267
- this.loader = new TextureLoader();
1268
- }
1269
- isSupported(url) {
1270
- const ext = url.split(".").pop()?.toLowerCase();
1271
- return ["png", "jpg", "jpeg", "gif", "webp", "bmp", "tga"].includes(ext || "");
1272
- }
1273
- async load(url, options) {
1274
- const texture = await this.loader.loadAsync(url, (event) => {
1275
- if (options?.onProgress && event.lengthComputable) {
1276
- options.onProgress(event.loaded / event.total);
1277
- }
1278
- });
1279
- if (options?.repeat) {
1280
- texture.repeat.copy(options.repeat);
1281
- }
1282
- texture.wrapS = options?.wrapS ?? RepeatWrapping;
1283
- texture.wrapT = options?.wrapT ?? RepeatWrapping;
1284
- return texture;
1285
- }
1286
- /**
1287
- * Clone a texture for independent usage
1288
- */
1289
- clone(texture) {
1290
- const cloned = texture.clone();
1291
- cloned.needsUpdate = true;
1292
- return cloned;
1293
- }
1294
- };
1295
-
1296
- // src/lib/core/loaders/gltf-loader.ts
1297
- import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
1298
- var GLTFLoaderAdapter = class {
1299
- loader;
1300
- constructor() {
1301
- this.loader = new GLTFLoader();
1302
- }
1303
- isSupported(url) {
1304
- const ext = url.split(".").pop()?.toLowerCase();
1305
- return ["gltf", "glb"].includes(ext || "");
1306
- }
1307
- async load(url, options) {
1308
- if (options?.useAsyncFetch) {
1309
- return this.loadWithAsyncFetch(url, options);
1310
- }
1311
- return this.loadMainThread(url, options);
1312
- }
1313
- /**
1314
- * Load using native fetch + parseAsync
1315
- * Both fetch and parsing are async, keeping the main thread responsive
1316
- */
1317
- async loadWithAsyncFetch(url, options) {
1318
- try {
1319
- const response = await fetch(url);
1320
- if (!response.ok) {
1321
- throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1322
- }
1323
- const buffer = await response.arrayBuffer();
1324
- const gltf = await this.loader.parseAsync(buffer, url);
1325
- return {
1326
- object: gltf.scene,
1327
- animations: gltf.animations,
1328
- gltf
1329
- };
1330
- } catch (error) {
1331
- console.error(`Async fetch GLTF load failed for ${url}, falling back to loader.load():`, error);
1332
- return this.loadMainThread(url, options);
1333
- }
1334
- }
1335
- async loadMainThread(url, options) {
1336
- return new Promise((resolve, reject) => {
1337
- this.loader.load(
1338
- url,
1339
- (gltf) => {
1340
- resolve({
1341
- object: gltf.scene,
1342
- animations: gltf.animations,
1343
- gltf
1344
- });
1345
- },
1346
- (event) => {
1347
- if (options?.onProgress && event.lengthComputable) {
1348
- options.onProgress(event.loaded / event.total);
1349
- }
1350
- },
1351
- (error) => reject(error)
1352
- );
1353
- });
1354
- }
1355
- /**
1356
- * Clone a loaded GLTF scene for reuse
1357
- */
1358
- clone(result) {
1359
- return {
1360
- object: result.object.clone(),
1361
- animations: result.animations?.map((anim) => anim.clone()),
1362
- gltf: result.gltf
1363
- };
1364
- }
1365
- };
1366
-
1367
- // src/lib/core/loaders/fbx-loader.ts
1368
- import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
1369
- var FBXLoaderAdapter = class {
1370
- loader;
1371
- constructor() {
1372
- this.loader = new FBXLoader();
1373
- }
1374
- isSupported(url) {
1375
- const ext = url.split(".").pop()?.toLowerCase();
1376
- return ext === "fbx";
1377
- }
1378
- async load(url, options) {
1379
- if (options?.useAsyncFetch) {
1380
- return this.loadWithAsyncFetch(url, options);
1381
- }
1382
- return this.loadMainThread(url, options);
1383
- }
1384
- /**
1385
- * Load using native fetch + parse
1386
- */
1387
- async loadWithAsyncFetch(url, _options) {
1388
- try {
1389
- const response = await fetch(url);
1390
- if (!response.ok) {
1391
- throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1392
- }
1393
- const buffer = await response.arrayBuffer();
1394
- const object = this.loader.parse(buffer, url);
1395
- return {
1396
- object,
1397
- animations: object.animations || []
1398
- };
1399
- } catch (error) {
1400
- console.error(`Async fetch FBX load failed for ${url}, falling back to loader.load():`, error);
1401
- return this.loadMainThread(url, _options);
1402
- }
1403
- }
1404
- async loadMainThread(url, options) {
1405
- return new Promise((resolve, reject) => {
1406
- this.loader.load(
1407
- url,
1408
- (object) => {
1409
- resolve({
1410
- object,
1411
- animations: object.animations || []
1412
- });
1413
- },
1414
- (event) => {
1415
- if (options?.onProgress && event.lengthComputable) {
1416
- options.onProgress(event.loaded / event.total);
1417
- }
1418
- },
1419
- (error) => reject(error)
1420
- );
1421
- });
1422
- }
1423
- /**
1424
- * Clone a loaded FBX object for reuse
1425
- */
1426
- clone(result) {
1427
- return {
1428
- object: result.object.clone(),
1429
- animations: result.animations?.map((anim) => anim.clone())
1430
- };
1431
- }
1432
- };
1433
-
1434
- // src/lib/core/loaders/obj-loader.ts
1435
- import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
1436
- import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
1437
- var OBJLoaderAdapter = class {
1438
- loader;
1439
- mtlLoader;
1440
- constructor() {
1441
- this.loader = new OBJLoader();
1442
- this.mtlLoader = new MTLLoader();
1443
- }
1444
- isSupported(url) {
1445
- const ext = url.split(".").pop()?.toLowerCase();
1446
- return ext === "obj";
1447
- }
1448
- async load(url, options) {
1449
- if (options?.mtlPath) {
1450
- await this.loadMTL(options.mtlPath);
1451
- }
1452
- return new Promise((resolve, reject) => {
1453
- this.loader.load(
1454
- url,
1455
- (object) => {
1456
- resolve({
1457
- object,
1458
- animations: []
1459
- });
1460
- },
1461
- (event) => {
1462
- if (options?.onProgress && event.lengthComputable) {
1463
- options.onProgress(event.loaded / event.total);
1464
- }
1465
- },
1466
- (error) => reject(error)
1467
- );
1468
- });
1469
- }
1470
- async loadMTL(url) {
1471
- return new Promise((resolve, reject) => {
1472
- this.mtlLoader.load(
1473
- url,
1474
- (materials) => {
1475
- materials.preload();
1476
- this.loader.setMaterials(materials);
1477
- resolve();
1478
- },
1479
- void 0,
1480
- (error) => reject(error)
1481
- );
1482
- });
1483
- }
1484
- /**
1485
- * Clone a loaded OBJ object for reuse
1486
- */
1487
- clone(result) {
1488
- return {
1489
- object: result.object.clone(),
1490
- animations: []
1491
- };
1492
- }
1493
- };
1494
-
1495
- // src/lib/core/loaders/audio-loader.ts
1496
- import { AudioLoader } from "three";
1497
- var AudioLoaderAdapter = class {
1498
- loader;
1499
- constructor() {
1500
- this.loader = new AudioLoader();
1501
- }
1502
- isSupported(url) {
1503
- const ext = url.split(".").pop()?.toLowerCase();
1504
- return ["mp3", "ogg", "wav", "flac", "aac", "m4a"].includes(ext || "");
1505
- }
1506
- async load(url, options) {
1507
- return new Promise((resolve, reject) => {
1508
- this.loader.load(
1509
- url,
1510
- (buffer) => resolve(buffer),
1511
- (event) => {
1512
- if (options?.onProgress && event.lengthComputable) {
1513
- options.onProgress(event.loaded / event.total);
1514
- }
1515
- },
1516
- (error) => reject(error)
1517
- );
1518
- });
1519
- }
1520
- };
1521
-
1522
- // src/lib/core/loaders/file-loader.ts
1523
- import { FileLoader } from "three";
1524
- var FileLoaderAdapter = class {
1525
- loader;
1526
- constructor() {
1527
- this.loader = new FileLoader();
1528
- }
1529
- isSupported(_url) {
1530
- return true;
1531
- }
1532
- async load(url, options) {
1533
- const responseType = options?.responseType ?? "text";
1534
- this.loader.setResponseType(responseType);
1535
- return new Promise((resolve, reject) => {
1536
- this.loader.load(
1537
- url,
1538
- (data) => resolve(data),
1539
- (event) => {
1540
- if (options?.onProgress && event.lengthComputable) {
1541
- options.onProgress(event.loaded / event.total);
1542
- }
1543
- },
1544
- (error) => reject(error)
1545
- );
1546
- });
1547
- }
1548
- };
1549
- var JsonLoaderAdapter = class {
1550
- fileLoader;
1551
- constructor() {
1552
- this.fileLoader = new FileLoaderAdapter();
1553
- }
1554
- isSupported(url) {
1555
- const ext = url.split(".").pop()?.toLowerCase();
1556
- return ext === "json";
1557
- }
1558
- async load(url, options) {
1559
- const data = await this.fileLoader.load(url, { ...options, responseType: "json" });
1560
- return data;
1561
- }
1562
- };
1563
-
1564
- // src/lib/core/asset-manager.ts
1565
- var AssetManager = class _AssetManager {
1566
- static instance = null;
1567
- // Caches for different asset types
1568
- textureCache = /* @__PURE__ */ new Map();
1569
- modelCache = /* @__PURE__ */ new Map();
1570
- audioCache = /* @__PURE__ */ new Map();
1571
- fileCache = /* @__PURE__ */ new Map();
1572
- jsonCache = /* @__PURE__ */ new Map();
1573
- // Loaders
1574
- textureLoader;
1575
- gltfLoader;
1576
- fbxLoader;
1577
- objLoader;
1578
- audioLoader;
1579
- fileLoader;
1580
- jsonLoader;
1581
- // Loading manager for progress tracking
1582
- loadingManager;
1583
- // Event emitter
1584
- events;
1585
- // Stats
1586
- stats = {
1587
- texturesLoaded: 0,
1588
- modelsLoaded: 0,
1589
- audioLoaded: 0,
1590
- filesLoaded: 0,
1591
- cacheHits: 0,
1592
- cacheMisses: 0
1593
- };
1594
- constructor() {
1595
- this.textureLoader = new TextureLoaderAdapter();
1596
- this.gltfLoader = new GLTFLoaderAdapter();
1597
- this.fbxLoader = new FBXLoaderAdapter();
1598
- this.objLoader = new OBJLoaderAdapter();
1599
- this.audioLoader = new AudioLoaderAdapter();
1600
- this.fileLoader = new FileLoaderAdapter();
1601
- this.jsonLoader = new JsonLoaderAdapter();
1602
- this.loadingManager = new LoadingManager();
1603
- this.loadingManager.onProgress = (url, loaded, total) => {
1604
- this.events.emit("batch:progress", { loaded, total });
1605
- };
1606
- this.events = mitt_default();
1607
- Cache.enabled = true;
1608
- }
1609
- /**
1610
- * Get the singleton instance
1611
- */
1612
- static getInstance() {
1613
- if (!_AssetManager.instance) {
1614
- _AssetManager.instance = new _AssetManager();
1615
- }
1616
- return _AssetManager.instance;
1617
- }
1618
- /**
1619
- * Reset the singleton (useful for testing)
1620
- */
1621
- static resetInstance() {
1622
- if (_AssetManager.instance) {
1623
- _AssetManager.instance.clearCache();
1624
- _AssetManager.instance = null;
1625
- }
1626
- }
1627
- // ==================== TEXTURE LOADING ====================
1628
- /**
1629
- * Load a texture with caching
1630
- */
1631
- async loadTexture(url, options) {
1632
- return this.loadWithCache(
1633
- url,
1634
- "texture" /* TEXTURE */,
1635
- this.textureCache,
1636
- () => this.textureLoader.load(url, options),
1637
- options,
1638
- (texture) => {
1639
- if (!options?.clone) return texture;
1640
- const cloned = this.textureLoader.clone(texture);
1641
- if (options.repeat) {
1642
- cloned.repeat.copy(options.repeat);
1643
- }
1644
- if (options.wrapS !== void 0) {
1645
- cloned.wrapS = options.wrapS;
1646
- }
1647
- if (options.wrapT !== void 0) {
1648
- cloned.wrapT = options.wrapT;
1649
- }
1650
- return cloned;
1651
- }
1652
- );
1653
- }
1654
- // ==================== MODEL LOADING ====================
1655
- /**
1656
- * Load a GLTF/GLB model with caching
1657
- */
1658
- async loadGLTF(url, options) {
1659
- return this.loadWithCache(
1660
- url,
1661
- "gltf" /* GLTF */,
1662
- this.modelCache,
1663
- () => this.gltfLoader.load(url, options),
1664
- options,
1665
- (result) => options?.clone ? this.gltfLoader.clone(result) : result
1666
- );
1667
- }
1668
- /**
1669
- * Load an FBX model with caching
1670
- */
1671
- async loadFBX(url, options) {
1672
- return this.loadWithCache(
1673
- url,
1674
- "fbx" /* FBX */,
1675
- this.modelCache,
1676
- () => this.fbxLoader.load(url, options),
1677
- options,
1678
- (result) => options?.clone ? this.fbxLoader.clone(result) : result
1679
- );
1680
- }
1681
- /**
1682
- * Load an OBJ model with caching
1683
- */
1684
- async loadOBJ(url, options) {
1685
- const cacheKey = options?.mtlPath ? `${url}:${options.mtlPath}` : url;
1686
- return this.loadWithCache(
1687
- cacheKey,
1688
- "obj" /* OBJ */,
1689
- this.modelCache,
1690
- () => this.objLoader.load(url, options),
1691
- options,
1692
- (result) => options?.clone ? this.objLoader.clone(result) : result
1693
- );
1694
- }
1695
- /**
1696
- * Auto-detect model type and load
1697
- */
1698
- async loadModel(url, options) {
1699
- const ext = url.split(".").pop()?.toLowerCase();
1700
- switch (ext) {
1701
- case "gltf":
1702
- case "glb":
1703
- return this.loadGLTF(url, options);
1704
- case "fbx":
1705
- return this.loadFBX(url, options);
1706
- case "obj":
1707
- return this.loadOBJ(url, options);
1708
- default:
1709
- throw new Error(`Unsupported model format: ${ext}`);
1710
- }
1711
- }
1712
- // ==================== AUDIO LOADING ====================
1713
- /**
1714
- * Load an audio buffer with caching
1715
- */
1716
- async loadAudio(url, options) {
1717
- return this.loadWithCache(
1718
- url,
1719
- "audio" /* AUDIO */,
1720
- this.audioCache,
1721
- () => this.audioLoader.load(url, options),
1722
- options
1723
- );
1724
- }
1725
- // ==================== FILE LOADING ====================
1726
- /**
1727
- * Load a raw file with caching
1728
- */
1729
- async loadFile(url, options) {
1730
- const cacheKey = options?.responseType ? `${url}:${options.responseType}` : url;
1731
- return this.loadWithCache(
1732
- cacheKey,
1733
- "file" /* FILE */,
1734
- this.fileCache,
1735
- () => this.fileLoader.load(url, options),
1736
- options
1737
- );
1738
- }
1739
- /**
1740
- * Load a JSON file with caching
1741
- */
1742
- async loadJSON(url, options) {
1743
- return this.loadWithCache(
1744
- url,
1745
- "json" /* JSON */,
1746
- this.jsonCache,
1747
- () => this.jsonLoader.load(url, options),
1748
- options
1749
- );
1750
- }
1751
- // ==================== BATCH LOADING ====================
1752
- /**
1753
- * Load multiple assets in parallel
1754
- */
1755
- async loadBatch(items) {
1756
- const results = /* @__PURE__ */ new Map();
1757
- const promises = items.map(async (item) => {
1758
- try {
1759
- let result;
1760
- switch (item.type) {
1761
- case "texture" /* TEXTURE */:
1762
- result = await this.loadTexture(item.url, item.options);
1763
- break;
1764
- case "gltf" /* GLTF */:
1765
- result = await this.loadGLTF(item.url, item.options);
1766
- break;
1767
- case "fbx" /* FBX */:
1768
- result = await this.loadFBX(item.url, item.options);
1769
- break;
1770
- case "obj" /* OBJ */:
1771
- result = await this.loadOBJ(item.url, item.options);
1772
- break;
1773
- case "audio" /* AUDIO */:
1774
- result = await this.loadAudio(item.url, item.options);
1775
- break;
1776
- case "file" /* FILE */:
1777
- result = await this.loadFile(item.url, item.options);
1778
- break;
1779
- case "json" /* JSON */:
1780
- result = await this.loadJSON(item.url, item.options);
1781
- break;
1782
- default:
1783
- throw new Error(`Unknown asset type: ${item.type}`);
1784
- }
1785
- results.set(item.url, result);
1786
- } catch (error) {
1787
- this.events.emit("asset:error", {
1788
- url: item.url,
1789
- type: item.type,
1790
- error
1791
- });
1792
- throw error;
1793
- }
1794
- });
1795
- await Promise.all(promises);
1796
- this.events.emit("batch:complete", { urls: items.map((i) => i.url) });
1797
- return results;
1798
- }
1799
- /**
1800
- * Preload assets without returning results
1801
- */
1802
- async preload(items) {
1803
- await this.loadBatch(items);
1804
- }
1805
- // ==================== CACHE MANAGEMENT ====================
1806
- /**
1807
- * Check if an asset is cached
1808
- */
1809
- isCached(url) {
1810
- return this.textureCache.has(url) || this.modelCache.has(url) || this.audioCache.has(url) || this.fileCache.has(url) || this.jsonCache.has(url);
1811
- }
1812
- /**
1813
- * Clear all caches or a specific URL
1814
- */
1815
- clearCache(url) {
1816
- if (url) {
1817
- this.textureCache.delete(url);
1818
- this.modelCache.delete(url);
1819
- this.audioCache.delete(url);
1820
- this.fileCache.delete(url);
1821
- this.jsonCache.delete(url);
1822
- } else {
1823
- this.textureCache.clear();
1824
- this.modelCache.clear();
1825
- this.audioCache.clear();
1826
- this.fileCache.clear();
1827
- this.jsonCache.clear();
1828
- Cache.clear();
1829
- }
1830
- }
1831
- /**
1832
- * Get cache statistics
1833
- */
1834
- getStats() {
1835
- return { ...this.stats };
1836
- }
1837
- // ==================== EVENTS ====================
1838
- /**
1839
- * Subscribe to asset manager events
1840
- */
1841
- on(event, handler) {
1842
- this.events.on(event, handler);
1843
- }
1844
- /**
1845
- * Unsubscribe from asset manager events
1846
- */
1847
- off(event, handler) {
1848
- this.events.off(event, handler);
1849
- }
1850
- // ==================== PRIVATE HELPERS ====================
1851
- /**
1852
- * Generic cache wrapper for loading assets
1853
- */
1854
- async loadWithCache(url, type, cache, loader, options, cloner) {
1855
- if (options?.forceReload) {
1856
- cache.delete(url);
1857
- }
1858
- const cached = cache.get(url);
1859
- if (cached) {
1860
- this.stats.cacheHits++;
1861
- this.events.emit("asset:loaded", { url, type, fromCache: true });
1862
- const result = await cached.promise;
1863
- return cloner ? cloner(result) : result;
1864
- }
1865
- this.stats.cacheMisses++;
1866
- this.events.emit("asset:loading", { url, type });
1867
- const promise = loader();
1868
- const entry = {
1869
- promise,
1870
- loadedAt: Date.now()
1871
- };
1872
- cache.set(url, entry);
1873
- try {
1874
- const result = await promise;
1875
- entry.result = result;
1876
- this.updateStats(type);
1877
- this.events.emit("asset:loaded", { url, type, fromCache: false });
1878
- return cloner ? cloner(result) : result;
1879
- } catch (error) {
1880
- cache.delete(url);
1881
- this.events.emit("asset:error", { url, type, error });
1882
- throw error;
1883
- }
1884
- }
1885
- updateStats(type) {
1886
- switch (type) {
1887
- case "texture" /* TEXTURE */:
1888
- this.stats.texturesLoaded++;
1889
- break;
1890
- case "gltf" /* GLTF */:
1891
- case "fbx" /* FBX */:
1892
- case "obj" /* OBJ */:
1893
- this.stats.modelsLoaded++;
1894
- break;
1895
- case "audio" /* AUDIO */:
1896
- this.stats.audioLoaded++;
1897
- break;
1898
- case "file" /* FILE */:
1899
- case "json" /* JSON */:
1900
- this.stats.filesLoaded++;
1901
- break;
1902
- }
1903
- }
1904
- };
1905
- var assetManager = AssetManager.getInstance();
1906
-
1907
- // src/lib/graphics/material.ts
1908
- import {
1909
- uniform,
1910
- uv,
1911
- time,
1912
- vec3,
1913
- vec4,
1914
- float,
1915
- Fn
1916
- } from "three/tsl";
1917
- function isTSLShader(shader) {
1918
- return "colorNode" in shader;
1919
- }
1920
- function isGLSLShader(shader) {
1921
- return "fragment" in shader && "vertex" in shader;
1922
- }
1923
- var MaterialBuilder = class _MaterialBuilder {
1924
- static batchMaterialMap = /* @__PURE__ */ new Map();
1925
- materials = [];
1926
- /** Whether to use TSL/NodeMaterial (for WebGPU compatibility) */
1927
- useTSL;
1928
- constructor(useTSL = false) {
1929
- this.useTSL = useTSL;
1930
- }
1931
- batchMaterial(options, entityType) {
1932
- const batchKey = shortHash(sortedStringify(options));
1933
- const mappedObject = _MaterialBuilder.batchMaterialMap.get(batchKey);
1934
- if (mappedObject) {
1935
- const count = mappedObject.geometryMap.get(entityType);
1936
- if (count) {
1937
- mappedObject.geometryMap.set(entityType, count + 1);
1938
- } else {
1939
- mappedObject.geometryMap.set(entityType, 1);
1940
- }
1941
- } else {
1942
- _MaterialBuilder.batchMaterialMap.set(batchKey, {
1943
- geometryMap: /* @__PURE__ */ new Map([[entityType, 1]]),
1944
- material: this.materials[0]
1945
- });
1946
- }
1947
- }
1948
- build(options, entityType) {
1949
- const { path, normalMap, repeat, color, shader, useTSL } = options;
1950
- const shouldUseTSL = useTSL ?? this.useTSL;
1951
- if (shader) {
1952
- if (isTSLShader(shader)) {
1953
- this.setTSLShader(shader);
1954
- } else if (isGLSLShader(shader)) {
1955
- if (shouldUseTSL) {
1956
- console.warn("MaterialBuilder: GLSL shader provided but TSL mode requested. Using GLSL.");
1957
- }
1958
- this.setShader(shader);
1959
- }
1960
- } else if (path) {
1961
- this.setTexture(path, repeat, shouldUseTSL);
1962
- }
1963
- if (color) {
1964
- this.withColor(color, shouldUseTSL);
1965
- }
1966
- if (this.materials.length === 0) {
1967
- this.setColor(new Color3("#ffffff"), shouldUseTSL);
1968
- }
1969
- if (normalMap && this.materials.length > 0) {
1970
- this.setNormalMap(normalMap, repeat);
1971
- }
1972
- this.batchMaterial(options, entityType);
1973
- }
1974
- withColor(color, useTSL = false) {
1975
- this.setColor(color, useTSL);
1976
- return this;
1977
- }
1978
- withShader(shader) {
1979
- this.setShader(shader);
1980
- return this;
1981
- }
1982
- withTSLShader(shader) {
1983
- this.setTSLShader(shader);
1984
- return this;
1985
- }
1986
- /**
1987
- * Set texture - loads in background (deferred).
1988
- * Material is created immediately with null map, texture applies when loaded.
1989
- */
1990
- setTexture(texturePath = null, repeat = new Vector22(1, 1), useTSL = false) {
1991
- if (!texturePath) {
1992
- return;
1993
- }
1994
- if (useTSL) {
1995
- const material = new MeshStandardNodeMaterial();
1996
- this.materials.push(material);
1997
- assetManager.loadTexture(texturePath, {
1998
- clone: true,
1999
- repeat
2000
- }).then((texture) => {
2001
- texture.wrapS = RepeatWrapping2;
2002
- texture.wrapT = RepeatWrapping2;
2003
- material.map = texture;
2004
- material.needsUpdate = true;
2005
- });
2006
- } else {
2007
- const material = new MeshPhongMaterial2({
2008
- map: null
2009
- });
2010
- this.materials.push(material);
2011
- assetManager.loadTexture(texturePath, {
2012
- clone: true,
2013
- repeat
2014
- }).then((texture) => {
2015
- texture.wrapS = RepeatWrapping2;
2016
- texture.wrapT = RepeatWrapping2;
2017
- material.map = texture;
2018
- material.needsUpdate = true;
2019
- });
2020
- }
2021
- }
2022
- /**
2023
- * Set normal map for the current material
2024
- */
2025
- setNormalMap(normalMapPath, repeat = new Vector22(1, 1)) {
2026
- const material = this.materials[this.materials.length - 1];
2027
- if (!material) return;
2028
- assetManager.loadTexture(normalMapPath, {
2029
- clone: true,
2030
- repeat
2031
- }).then((texture) => {
2032
- texture.wrapS = RepeatWrapping2;
2033
- texture.wrapT = RepeatWrapping2;
2034
- if (material instanceof MeshStandardMaterial2 || material instanceof MeshPhongMaterial2 || material instanceof MeshStandardNodeMaterial) {
2035
- material.normalMap = texture;
2036
- material.needsUpdate = true;
2037
- } else if (material instanceof ShaderMaterial2) {
2038
- if (material.uniforms.tNormal) {
2039
- material.uniforms.tNormal.value = texture;
2040
- material.needsUpdate = true;
2041
- }
2042
- if (material.uniforms.normalMap) {
2043
- material.uniforms.normalMap.value = texture;
2044
- material.needsUpdate = true;
2045
- }
2046
- }
2047
- });
2048
- }
2049
- setColor(color, useTSL = false) {
2050
- if (useTSL) {
2051
- const material = new MeshStandardNodeMaterial();
2052
- material.color = color;
2053
- this.materials.push(material);
2054
- } else {
2055
- const material = new MeshStandardMaterial2({
2056
- color,
2057
- emissiveIntensity: 0.5,
2058
- lightMapIntensity: 0.5,
2059
- fog: true
2060
- });
2061
- this.materials.push(material);
2062
- }
2063
- }
2064
- /**
2065
- * Set GLSL shader (WebGL only)
2066
- */
2067
- setShader(customShader) {
2068
- const { fragment: fragment2, vertex } = customShader ?? standardShader;
2069
- const shader = new ShaderMaterial2({
2070
- uniforms: {
2071
- iResolution: { value: new Vector37(1, 1, 1) },
2072
- iTime: { value: 0 },
2073
- tDiffuse: { value: null },
2074
- tDepth: { value: null },
2075
- tNormal: { value: null },
2076
- normalMap: { value: null },
2077
- lightDir: { value: new Vector37(1, 1, 1) },
2078
- normalStrength: { value: 1 }
2079
- },
2080
- vertexShader: vertex,
2081
- fragmentShader: fragment2,
2082
- transparent: true
2083
- });
2084
- this.materials.push(shader);
2085
- }
2086
- /**
2087
- * Set TSL shader (WebGPU compatible)
2088
- */
2089
- setTSLShader(tslShader) {
2090
- const material = new MeshBasicNodeMaterial();
2091
- material.colorNode = tslShader.colorNode;
2092
- if (tslShader.transparent) {
2093
- material.transparent = true;
2094
- }
2095
- this.materials.push(material);
2096
- }
2097
- };
2098
-
2099
- // src/lib/entities/parts/mesh-factories.ts
2100
- function buildMaterial(opts, entityType = /* @__PURE__ */ Symbol("mesh")) {
2101
- if (opts.material || opts.color) {
2102
- const builder = new MaterialBuilder();
2103
- builder.build(
2104
- { ...opts.material, color: opts.color ?? opts.material?.color },
2105
- entityType
2106
- );
2107
- if (builder.materials.length > 0) {
2108
- return builder.materials;
2109
- }
2110
- }
2111
- return [new MeshStandardMaterial3()];
2112
- }
2113
- function finalizeMesh(mesh, opts) {
2114
- if (opts.position) {
2115
- mesh.position.set(opts.position.x, opts.position.y, opts.position.z);
2116
- }
2117
- mesh.castShadow = true;
2118
- mesh.receiveShadow = true;
2119
- return mesh;
2120
- }
2121
- function boxMesh(opts = {}) {
2122
- const size = opts.size ?? { x: 1, y: 1, z: 1 };
2123
- const geometry = new BoxGeometry(size.x, size.y, size.z);
2124
- const materials = buildMaterial(opts);
2125
- return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
2126
- }
2127
- function sphereMesh(opts = {}) {
2128
- const geometry = new SphereGeometry(opts.radius ?? 1);
2129
- const materials = buildMaterial(opts);
2130
- return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
2131
- }
2132
- function coneMesh(opts = {}) {
2133
- const geometry = new ConeGeometry(
2134
- opts.radius ?? 1,
2135
- opts.height ?? 2,
2136
- opts.radialSegments ?? 32
2137
- );
2138
- const materials = buildMaterial(opts);
2139
- return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
2140
- }
2141
- function pyramidMesh(opts = {}) {
2142
- const geometry = new ConeGeometry(opts.radius ?? 1, opts.height ?? 2, 4);
2143
- const materials = buildMaterial(opts);
2144
- return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
2145
- }
2146
- function cylinderMesh(opts = {}) {
2147
- const geometry = new CylinderGeometry(
2148
- opts.radiusTop ?? 1,
2149
- opts.radiusBottom ?? 1,
2150
- opts.height ?? 2,
2151
- opts.radialSegments ?? 32
2152
- );
2153
- const materials = buildMaterial(opts);
2154
- return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
2155
- }
2156
- function pillMesh(opts = {}) {
2157
- const geometry = new CapsuleGeometry(
2158
- opts.radius ?? 0.5,
2159
- opts.length ?? 1,
2160
- opts.capSegments ?? 10,
2161
- opts.radialSegments ?? 20
2162
- );
2163
- const materials = buildMaterial(opts);
2164
- return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
2165
- }
2166
-
2167
- // src/lib/entities/box.ts
2168
- var boxDefaults = {
2169
- ...commonDefaults,
2170
- size: new Vector38(1, 1, 1)
2171
- };
2172
- var BOX_TYPE = /* @__PURE__ */ Symbol("Box");
2173
- var ZylemBox = class _ZylemBox extends GameEntity {
2174
- static type = BOX_TYPE;
2175
- constructor(options) {
2176
- super();
2177
- this.options = { ...boxDefaults, ...options };
2178
- }
2179
- buildInfo() {
2180
- const delegate = new DebugDelegate(this);
2181
- const baseInfo = delegate.buildDebugInfo();
2182
- const { x: sizeX, y: sizeY, z: sizeZ } = this.options.size ?? { x: 1, y: 1, z: 1 };
2183
- return {
2184
- ...baseInfo,
2185
- type: String(_ZylemBox.type),
2186
- size: `${sizeX}, ${sizeY}, ${sizeZ}`
2187
- };
2188
- }
2189
- };
2190
- function createBox(...args) {
2191
- const options = mergeArgs(args, boxDefaults);
2192
- const entity = new ZylemBox(options);
2193
- entity.add(
2194
- boxMesh({ size: options.size, material: options.material, color: options.color }),
2195
- boxCollision({
2196
- size: options.size,
2197
- static: options.collision?.static,
2198
- sensor: options.collision?.sensor,
2199
- collisionType: options.collisionType,
2200
- collisionFilter: options.collisionFilter
2201
- })
2202
- );
2203
- return entity;
2204
- }
2205
-
2206
- // src/lib/entities/sphere.ts
2207
- var sphereDefaults = {
2208
- ...commonDefaults,
2209
- radius: 1
2210
- };
2211
- var SPHERE_TYPE = /* @__PURE__ */ Symbol("Sphere");
2212
- var ZylemSphere = class _ZylemSphere extends GameEntity {
2213
- static type = SPHERE_TYPE;
2214
- constructor(options) {
2215
- super();
2216
- this.options = { ...sphereDefaults, ...options };
2217
- }
2218
- buildInfo() {
2219
- const delegate = new DebugDelegate(this);
2220
- const baseInfo = delegate.buildDebugInfo();
2221
- const radius = this.options.radius ?? 1;
2222
- return {
2223
- ...baseInfo,
2224
- type: String(_ZylemSphere.type),
2225
- radius: radius.toFixed(2)
2226
- };
2227
- }
2228
- };
2229
- function createSphere(...args) {
2230
- const options = mergeArgs(args, sphereDefaults);
2231
- const entity = new ZylemSphere(options);
2232
- entity.add(
2233
- sphereMesh({ radius: options.radius, material: options.material, color: options.color }),
2234
- sphereCollision({
2235
- radius: options.radius,
2236
- static: options.collision?.static,
2237
- sensor: options.collision?.sensor,
2238
- collisionType: options.collisionType,
2239
- collisionFilter: options.collisionFilter
2240
- })
2241
- );
2242
- return entity;
2243
- }
2244
-
2245
- // src/lib/entities/sprite.ts
2246
- import { ColliderDesc as ColliderDesc3 } from "@dimforge/rapier3d-compat";
2247
- import { Euler as Euler2, Group as Group3, Quaternion as Quaternion3, Vector3 as Vector39 } from "three";
2248
- import {
2249
- TextureLoader as TextureLoader2,
2250
- SpriteMaterial,
2251
- Sprite as ThreeSprite
2252
- } from "three";
2253
-
2254
- // src/lib/entities/builder.ts
2255
- import { BufferGeometry as BufferGeometry2, Mesh as Mesh4, Color as Color4 } from "three";
2256
-
2257
- // src/lib/graphics/mesh.ts
2258
- import { Mesh as Mesh3 } from "three";
2259
- var MeshBuilder = class {
2260
- _build(meshOptions, geometry, materials) {
2261
- const { batched, material } = meshOptions;
2262
- if (batched) {
2263
- console.warn("warning: mesh batching is not implemented");
2264
- }
2265
- const mesh = new Mesh3(geometry, materials.at(-1));
2266
- mesh.position.set(0, 0, 0);
2267
- mesh.castShadow = true;
2268
- mesh.receiveShadow = true;
2269
- return mesh;
2270
- }
2271
- _postBuild() {
2272
- return;
2273
- }
2274
- };
2275
-
2276
- // src/lib/entities/builder.ts
2277
- var EntityCollisionBuilder = class extends CollisionBuilder {
2278
- };
2279
- var EntityMeshBuilder = class extends MeshBuilder {
2280
- build(options) {
2281
- return new BufferGeometry2();
2282
- }
2283
- postBuild() {
2284
- return;
2285
- }
2286
- };
2287
- var EntityBuilder = class {
2288
- meshBuilder;
2289
- collisionBuilder;
2290
- materialBuilder;
2291
- options;
2292
- entity;
2293
- constructor(options, entity, meshBuilder, collisionBuilder) {
2294
- this.options = options;
2295
- this.entity = entity;
2296
- this.meshBuilder = meshBuilder;
2297
- this.collisionBuilder = collisionBuilder;
2298
- this.materialBuilder = new MaterialBuilder();
2299
- const builders = {
2300
- meshBuilder: this.meshBuilder,
2301
- collisionBuilder: this.collisionBuilder,
2302
- materialBuilder: this.materialBuilder
2303
- };
2304
- this.options._builders = builders;
2305
- }
2306
- withPosition(setupPosition) {
2307
- this.options.position = setupPosition;
2308
- return this;
2309
- }
2310
- withMaterial(options, entityType) {
2311
- if (this.materialBuilder) {
2312
- this.materialBuilder.build(options, entityType);
2313
- }
2314
- return this;
2315
- }
2316
- applyMaterialToGroup(group, materials) {
2317
- group.traverse((child) => {
2318
- if (child instanceof Mesh4) {
2319
- if (child.type === "SkinnedMesh" && materials[0] && !child.material.map) {
2320
- child.material = materials[0];
2321
- }
2322
- }
2323
- child.castShadow = true;
2324
- child.receiveShadow = true;
2325
- });
2326
- }
2327
- build() {
2328
- const entity = this.entity;
2329
- if (this.materialBuilder) {
2330
- entity.materials = this.materialBuilder.materials;
2331
- }
2332
- if (this.meshBuilder && entity.materials) {
2333
- const geometry = this.meshBuilder.build(this.options);
2334
- entity.mesh = this.meshBuilder._build(this.options, geometry, entity.materials);
2335
- this.meshBuilder.postBuild();
2336
- }
2337
- if (entity.group && entity.materials) {
2338
- this.applyMaterialToGroup(entity.group, entity.materials);
2339
- }
2340
- if (this.collisionBuilder) {
2341
- this.collisionBuilder.withCollision(this.options?.collision || {});
2342
- const [bodyDesc, colliderDesc] = this.collisionBuilder.build(this.options);
2343
- entity.bodyDesc = bodyDesc;
2344
- entity.colliderDesc = colliderDesc;
2345
- entity.colliderDescs.push(colliderDesc);
2346
- const { x, y, z } = this.options.position || { x: 0, y: 0, z: 0 };
2347
- entity.bodyDesc.setTranslation(x, y, z);
2348
- }
2349
- if (this.options.collisionType) {
2350
- entity.collisionType = this.options.collisionType;
2351
- }
2352
- if (this.options.color instanceof Color4) {
2353
- const applyColor = (material) => {
2354
- const anyMat = material;
2355
- if (anyMat && anyMat.color && anyMat.color.set) {
2356
- anyMat.color.set(this.options.color);
2357
- }
2358
- };
2359
- if (entity.materials?.length) {
2360
- for (const mat of entity.materials) applyColor(mat);
2361
- }
2362
- if (entity.mesh && entity.mesh.material) {
2363
- const mat = entity.mesh.material;
2364
- if (Array.isArray(mat)) mat.forEach(applyColor);
2365
- else applyColor(mat);
2366
- }
2367
- if (entity.group) {
2368
- entity.group.traverse((child) => {
2369
- if (child instanceof Mesh4 && child.material) {
2370
- const mat = child.material;
2371
- if (Array.isArray(mat)) mat.forEach(applyColor);
2372
- else applyColor(mat);
2373
- }
2374
- });
2375
- }
2376
- }
2377
- return entity;
2378
- }
2379
- };
2380
-
2381
- // src/lib/entities/delegates/loader.ts
2382
- function isLoadable(obj) {
2383
- return typeof obj?.load === "function" && typeof obj?.data === "function";
2384
- }
2385
- var EntityLoader = class {
2386
- entityReference;
2387
- constructor(entity) {
2388
- this.entityReference = entity;
2389
- }
2390
- load() {
2391
- if (this.entityReference.load) {
2392
- this.entityReference.load();
2393
- }
2394
- }
2395
- data() {
2396
- if (this.entityReference.data) {
2397
- return this.entityReference.data();
2398
- }
2399
- return null;
2400
- }
2401
- };
2402
-
2403
- // src/lib/entities/create.ts
2404
- function createEntity(params) {
2405
- const {
2406
- args,
2407
- defaultConfig,
2408
- EntityClass,
2409
- BuilderClass,
2410
- entityType,
2411
- MeshBuilderClass,
2412
- CollisionBuilderClass
2413
- } = params;
2414
- let builder = null;
2415
- let configuration;
2416
- const configurationIndex = args.findIndex((node) => !(node instanceof BaseNode));
2417
- if (configurationIndex !== -1) {
2418
- const subArgs = args.splice(configurationIndex, 1);
2419
- configuration = subArgs.find((node) => !(node instanceof BaseNode));
2420
- }
2421
- const mergedConfiguration = configuration ? { ...defaultConfig, ...configuration } : defaultConfig;
2422
- args.push(mergedConfiguration);
2423
- for (const arg of args) {
2424
- if (arg instanceof BaseNode) {
2425
- continue;
2426
- }
2427
- let entityData = null;
2428
- const entity = new EntityClass(arg);
2429
- try {
2430
- if (isLoadable(entity)) {
2431
- const loader = new EntityLoader(entity);
2432
- loader.load();
2433
- entityData = loader.data();
2434
- }
2435
- } catch (error) {
2436
- console.error("Error creating entity with loader:", error);
2437
- }
2438
- builder = new BuilderClass(
2439
- arg,
2440
- entity,
2441
- MeshBuilderClass ? new MeshBuilderClass(entityData) : null,
2442
- CollisionBuilderClass ? new CollisionBuilderClass(entityData) : null
2443
- );
2444
- if (arg.material) {
2445
- builder.withMaterial(arg.material, entityType);
2446
- }
2447
- }
2448
- if (!builder) {
2449
- throw new Error(`missing options for ${String(entityType)}, builder is not initialized.`);
2450
- }
2451
- return builder.build();
2452
- }
2453
-
2454
- // src/lib/entities/sprite.ts
2455
- var spriteDefaults = {
2456
- ...commonDefaults,
2457
- size: new Vector39(1, 1, 1),
2458
- images: [],
2459
- animations: []
2460
- };
2461
- var SpriteCollisionBuilder = class extends EntityCollisionBuilder {
2462
- collider(options) {
2463
- const size = options.collisionSize || options.size || new Vector39(1, 1, 1);
2464
- const half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };
2465
- let colliderDesc = ColliderDesc3.cuboid(half.x, half.y, half.z);
2466
- return colliderDesc;
2467
- }
2468
- };
2469
- var SpriteBuilder = class extends EntityBuilder {
2470
- createEntity(options) {
2471
- return new ZylemSprite(options);
2472
- }
2473
- };
2474
- var SPRITE_TYPE = /* @__PURE__ */ Symbol("Sprite");
2475
- var ZylemSprite = class _ZylemSprite extends GameEntity {
2476
- static type = SPRITE_TYPE;
2477
- sprites = [];
2478
- spriteMap = /* @__PURE__ */ new Map();
2479
- currentSpriteIndex = 0;
2480
- animations = /* @__PURE__ */ new Map();
2481
- currentAnimation = null;
2482
- currentAnimationFrame = "";
2483
- currentAnimationIndex = 0;
2484
- currentAnimationTime = 0;
2485
- constructor(options) {
2486
- super();
2487
- this.options = { ...spriteDefaults, ...options };
2488
- this.prependUpdate(this.spriteUpdate.bind(this));
2489
- this.onCleanup(this.spriteDestroy.bind(this));
2490
- }
2491
- create() {
2492
- this.sprites = [];
2493
- this.spriteMap.clear();
2494
- this.animations.clear();
2495
- this.currentAnimation = null;
2496
- this.currentAnimationFrame = "";
2497
- this.currentAnimationIndex = 0;
2498
- this.currentAnimationTime = 0;
2499
- this.group = void 0;
2500
- this.createSpritesFromImages(this.options?.images || []);
2501
- this.createAnimations(this.options?.animations || []);
2502
- return super.create();
2503
- }
2504
- createSpritesFromImages(images) {
2505
- const textureLoader = new TextureLoader2();
2506
- images.forEach((image, index) => {
2507
- const spriteMap = textureLoader.load(image.file);
2508
- const material = new SpriteMaterial({
2509
- map: spriteMap,
2510
- transparent: true
2511
- });
2512
- const _sprite = new ThreeSprite(material);
2513
- _sprite.position.normalize();
2514
- this.sprites.push(_sprite);
2515
- this.spriteMap.set(image.name, index);
2516
- });
2517
- this.group = new Group3();
2518
- this.group.add(...this.sprites);
2519
- }
2520
- createAnimations(animations) {
2521
- animations.forEach((animation) => {
2522
- const { name, frames, loop = false, speed = 1 } = animation;
2523
- const internalAnimation = {
2524
- frames: frames.map((frame, index) => ({
2525
- key: frame,
2526
- index,
2527
- time: (typeof speed === "number" ? speed : speed[index]) * (index + 1),
2528
- duration: typeof speed === "number" ? speed : speed[index]
2529
- })),
2530
- loop
2531
- };
2532
- this.animations.set(name, internalAnimation);
2533
- });
2534
- }
2535
- setSprite(key) {
2536
- const spriteIndex = this.spriteMap.get(key);
2537
- const useIndex = spriteIndex ?? 0;
2538
- this.currentSpriteIndex = useIndex;
2539
- this.sprites.forEach((_sprite, i) => {
2540
- _sprite.visible = this.currentSpriteIndex === i;
2541
- });
2542
- }
2543
- setAnimation(name, delta) {
2544
- const animation = this.animations.get(name);
2545
- if (!animation) return;
2546
- const { loop, frames } = animation;
2547
- const frame = frames[this.currentAnimationIndex];
2548
- if (name === this.currentAnimation) {
2549
- this.currentAnimationFrame = frame.key;
2550
- this.currentAnimationTime += delta;
2551
- this.setSprite(this.currentAnimationFrame);
2552
- } else {
2553
- this.currentAnimation = name;
2554
- }
2555
- if (this.currentAnimationTime > frame.time) {
2556
- this.currentAnimationIndex++;
2557
- }
2558
- if (this.currentAnimationIndex >= frames.length) {
2559
- if (loop) {
2560
- this.currentAnimationIndex = 0;
2561
- this.currentAnimationTime = 0;
2562
- } else {
2563
- this.currentAnimationTime = frames[this.currentAnimationIndex].time;
2564
- }
2565
- }
2566
- }
2567
- spriteUpdate(params) {
2568
- this.sprites.forEach((_sprite) => {
2569
- if (_sprite.material) {
2570
- const q = this.body?.rotation();
2571
- if (q) {
2572
- const quat = new Quaternion3(q.x, q.y, q.z, q.w);
2573
- const euler = new Euler2().setFromQuaternion(quat, "XYZ");
2574
- _sprite.material.rotation = euler.z;
2575
- }
2576
- _sprite.scale.set(this.options.size?.x ?? 1, this.options.size?.y ?? 1, this.options.size?.z ?? 1);
2577
- }
2578
- });
2579
- }
2580
- spriteDestroy(params) {
2581
- this.sprites.forEach((_sprite) => {
2582
- _sprite.removeFromParent();
2583
- });
2584
- this.group?.remove(...this.sprites);
2585
- this.group?.removeFromParent();
2586
- }
2587
- buildInfo() {
2588
- const delegate = new DebugDelegate(this);
2589
- const baseInfo = delegate.buildDebugInfo();
2590
- return {
2591
- ...baseInfo,
2592
- type: String(_ZylemSprite.type)
2593
- };
2594
- }
2595
- };
2596
- function createSprite(...args) {
2597
- return createEntity({
2598
- args,
2599
- defaultConfig: spriteDefaults,
2600
- EntityClass: ZylemSprite,
2601
- BuilderClass: SpriteBuilder,
2602
- CollisionBuilderClass: SpriteCollisionBuilder,
2603
- entityType: ZylemSprite.type
2604
- });
2605
- }
2606
-
2607
- // src/lib/entities/plane.ts
2608
- import { ColliderDesc as ColliderDesc4 } from "@dimforge/rapier3d-compat";
2609
- import { PlaneGeometry, Vector2 as Vector23, Vector3 as Vector310 } from "three";
2610
-
2611
- // src/lib/graphics/geometries/XZPlaneGeometry.ts
2612
- import { BufferGeometry as BufferGeometry3, Float32BufferAttribute } from "three";
2613
- var XZPlaneGeometry = class _XZPlaneGeometry extends BufferGeometry3 {
2614
- constructor(width = 1, height = 1, widthSegments = 1, heightSegments = 1) {
2615
- super();
2616
- this.type = "XZPlaneGeometry";
2617
- this.parameters = {
2618
- width,
2619
- height,
2620
- widthSegments,
2621
- heightSegments
2622
- };
2623
- const width_half = width / 2;
2624
- const height_half = height / 2;
2625
- const gridX = Math.floor(widthSegments);
2626
- const gridY = Math.floor(heightSegments);
2627
- const gridX1 = gridX + 1;
2628
- const gridY1 = gridY + 1;
2629
- const segment_width = width / gridX;
2630
- const segment_height = height / gridY;
2631
- const indices = [];
2632
- const vertices = [];
2633
- const normals = [];
2634
- const uvs = [];
2635
- for (let iy = 0; iy < gridY1; iy++) {
2636
- const z = iy * segment_height - height_half;
2637
- for (let ix = 0; ix < gridX1; ix++) {
2638
- const x = ix * segment_width - width_half;
2639
- vertices.push(x, 0, z);
2640
- normals.push(0, 1, 0);
2641
- uvs.push(ix / gridX);
2642
- uvs.push(1 - iy / gridY);
2643
- }
2644
- }
2645
- for (let iy = 0; iy < gridY; iy++) {
2646
- for (let ix = 0; ix < gridX; ix++) {
2647
- const a = ix + gridX1 * iy;
2648
- const b = ix + gridX1 * (iy + 1);
2649
- const c = ix + 1 + gridX1 * (iy + 1);
2650
- const d = ix + 1 + gridX1 * iy;
2651
- indices.push(a, b, d);
2652
- indices.push(b, c, d);
2653
- }
2654
- }
2655
- this.setIndex(indices);
2656
- this.setAttribute("position", new Float32BufferAttribute(vertices, 3));
2657
- this.setAttribute("normal", new Float32BufferAttribute(normals, 3));
2658
- this.setAttribute("uv", new Float32BufferAttribute(uvs, 2));
2659
- }
2660
- copy(source) {
2661
- super.copy(source);
2662
- this.parameters = Object.assign({}, source.parameters);
2663
- return this;
2664
- }
2665
- static fromJSON(data) {
2666
- return new _XZPlaneGeometry(data.width, data.height, data.widthSegments, data.heightSegments);
2667
- }
2668
- };
2669
-
2670
- // src/lib/entities/plane.ts
2671
- var DEFAULT_SUBDIVISIONS = 4;
2672
- var planeDefaults = {
2673
- ...commonDefaults,
2674
- tile: new Vector23(10, 10),
2675
- repeat: new Vector23(1, 1),
2676
- collision: {
2677
- static: true
2678
- },
2679
- subdivisions: DEFAULT_SUBDIVISIONS,
2680
- randomizeHeight: false,
2681
- heightScale: 1
2682
- };
2683
- var PlaneCollisionBuilder = class extends EntityCollisionBuilder {
2684
- collider(options) {
2685
- const tile = options.tile ?? new Vector23(1, 1);
2686
- const subdivisions = options.subdivisions ?? DEFAULT_SUBDIVISIONS;
2687
- const size = new Vector310(tile.x, 1, tile.y);
2688
- const heightData = options._builders?.meshBuilder?.heightData;
2689
- const scale2 = new Vector310(size.x, 1, size.z);
2690
- let colliderDesc = ColliderDesc4.heightfield(
2691
- subdivisions,
2692
- subdivisions,
2693
- heightData,
2694
- scale2
2695
- );
2696
- return colliderDesc;
2697
- }
2698
- };
2699
- var PlaneMeshBuilder = class extends EntityMeshBuilder {
2700
- heightData = new Float32Array();
2701
- columnsRows = /* @__PURE__ */ new Map();
2702
- subdivisions = DEFAULT_SUBDIVISIONS;
2703
- build(options) {
2704
- const tile = options.tile ?? new Vector23(1, 1);
2705
- const subdivisions = options.subdivisions ?? DEFAULT_SUBDIVISIONS;
2706
- this.subdivisions = subdivisions;
2707
- const size = new Vector310(tile.x, 1, tile.y);
2708
- const heightScale = options.heightScale ?? 1;
2709
- const geometry = new XZPlaneGeometry(size.x, size.z, subdivisions, subdivisions);
2710
- const vertexGeometry = new PlaneGeometry(size.x, size.z, subdivisions, subdivisions);
2711
- const dx = size.x / subdivisions;
2712
- const dy = size.z / subdivisions;
2713
- const originalVertices = geometry.attributes.position.array;
2714
- const vertices = vertexGeometry.attributes.position.array;
2715
- const columsRows = /* @__PURE__ */ new Map();
2716
- const heightMapData = options.heightMap;
2717
- const useRandomHeight = options.randomizeHeight ?? false;
2718
- for (let i = 0; i < vertices.length; i += 3) {
2719
- const vertexIndex = i / 3;
2720
- let row = Math.floor(Math.abs(vertices[i] + size.x / 2) / dx);
2721
- let column = Math.floor(Math.abs(vertices[i + 1] - size.z / 2) / dy);
2722
- let height = 0;
2723
- if (heightMapData && heightMapData.length > 0) {
2724
- const heightIndex = vertexIndex % heightMapData.length;
2725
- height = heightMapData[heightIndex] * heightScale;
2726
- } else if (useRandomHeight) {
2727
- height = Math.random() * 4 * heightScale;
2728
- }
2729
- vertices[i + 2] = height;
2730
- originalVertices[i + 1] = height;
2731
- if (!columsRows.get(column)) {
2732
- columsRows.set(column, /* @__PURE__ */ new Map());
2733
- }
2734
- columsRows.get(column).set(row, height);
2735
- }
2736
- this.columnsRows = columsRows;
2737
- return geometry;
2738
- }
2739
- postBuild() {
2740
- const heights = [];
2741
- for (let i = 0; i <= this.subdivisions; ++i) {
2742
- for (let j = 0; j <= this.subdivisions; ++j) {
2743
- const row = this.columnsRows.get(j);
2744
- if (!row) {
2745
- heights.push(0);
2746
- continue;
2747
- }
2748
- const data = row.get(i);
2749
- heights.push(data ?? 0);
2750
- }
2751
- }
2752
- this.heightData = new Float32Array(heights);
2753
- }
2754
- };
2755
- var PlaneBuilder = class extends EntityBuilder {
2756
- createEntity(options) {
2757
- return new ZylemPlane(options);
2758
- }
2759
- };
2760
- var PLANE_TYPE = /* @__PURE__ */ Symbol("Plane");
2761
- var ZylemPlane = class extends GameEntity {
2762
- static type = PLANE_TYPE;
2763
- constructor(options) {
2764
- super();
2765
- this.options = { ...planeDefaults, ...options };
2766
- }
2767
- };
2768
- function createPlane(...args) {
2769
- return createEntity({
2770
- args,
2771
- defaultConfig: planeDefaults,
2772
- EntityClass: ZylemPlane,
2773
- BuilderClass: PlaneBuilder,
2774
- MeshBuilderClass: PlaneMeshBuilder,
2775
- CollisionBuilderClass: PlaneCollisionBuilder,
2776
- entityType: ZylemPlane.type
2777
- });
2778
- }
2779
-
2780
- // src/lib/entities/zone.ts
2781
- import { Vector3 as Vector311 } from "three";
2782
-
2783
- // src/lib/game/game-state.ts
2784
- import { proxy as proxy2, subscribe } from "valtio/vanilla";
2785
- var state = proxy2({
2786
- id: "",
2787
- globals: {},
2788
- time: 0
2789
- });
2790
-
2791
- // src/lib/entities/zone.ts
2792
- var zoneDefaults = {
2793
- ...commonDefaults,
2794
- size: new Vector311(1, 1, 1),
2795
- collision: {
2796
- static: true
2797
- }
2798
- };
2799
- var ZONE_TYPE = /* @__PURE__ */ Symbol("Zone");
2800
- var ZylemZone = class extends GameEntity {
2801
- static type = ZONE_TYPE;
2802
- _enteredZone = /* @__PURE__ */ new Map();
2803
- _exitedZone = /* @__PURE__ */ new Map();
2804
- _zoneEntities = /* @__PURE__ */ new Map();
2805
- constructor(options) {
2806
- super();
2807
- this.options = { ...zoneDefaults, ...options };
2808
- }
2809
- handlePostCollision({ delta }) {
2810
- this._enteredZone.forEach((val, key) => {
2811
- this.exited(delta, key);
2812
- });
2813
- return this._enteredZone.size > 0;
2814
- }
2815
- handleIntersectionEvent({ other, delta }) {
2816
- const hasEntered = this._enteredZone.get(other.uuid);
2817
- if (!hasEntered) {
2818
- this.entered(other);
2819
- this._zoneEntities.set(other.uuid, other);
2820
- } else {
2821
- this.held(delta, other);
2822
- }
2823
- }
2824
- onEnter(callback) {
2825
- this.options.onEnter = callback;
2826
- return this;
2827
- }
2828
- onHeld(callback) {
2829
- this.options.onHeld = callback;
2830
- return this;
2831
- }
2832
- onExit(callback) {
2833
- this.options.onExit = callback;
2834
- return this;
2835
- }
2836
- entered(other) {
2837
- this._enteredZone.set(other.uuid, 1);
2838
- if (this.options.onEnter) {
2839
- this.options.onEnter({
2840
- self: this,
2841
- visitor: other,
2842
- globals: state.globals
2843
- });
2844
- }
2845
- }
2846
- exited(delta, key) {
2847
- const hasExited = this._exitedZone.get(key);
2848
- if (hasExited && hasExited > 1 + delta) {
2849
- this._exitedZone.delete(key);
2850
- this._enteredZone.delete(key);
2851
- const other = this._zoneEntities.get(key);
2852
- if (this.options.onExit) {
2853
- this.options.onExit({
2854
- self: this,
2855
- visitor: other,
2856
- globals: state.globals
2857
- });
2858
- }
2859
- return;
2860
- }
2861
- this._exitedZone.set(key, 1 + delta);
2862
- }
2863
- held(delta, other) {
2864
- const heldTime = this._enteredZone.get(other.uuid) ?? 0;
2865
- this._enteredZone.set(other.uuid, heldTime + delta);
2866
- this._exitedZone.set(other.uuid, 1);
2867
- if (this.options.onHeld) {
2868
- this.options.onHeld({
2869
- delta,
2870
- self: this,
2871
- visitor: other,
2872
- globals: state.globals,
2873
- heldTime
2874
- });
2875
- }
2876
- }
2877
- };
2878
- function createZone(...args) {
2879
- const options = mergeArgs(args, zoneDefaults);
2880
- const entity = new ZylemZone(options);
2881
- entity.add(
2882
- zoneCollision({
2883
- size: options.size,
2884
- static: options.collision?.static ?? true,
2885
- collisionType: options.collisionType,
2886
- collisionFilter: options.collisionFilter
2887
- })
2888
- );
2889
- return entity;
2890
- }
2891
-
2892
- // src/lib/entities/actor.ts
2893
- import { ActiveCollisionTypes as ActiveCollisionTypes3, ColliderDesc as ColliderDesc5 } from "@dimforge/rapier3d-compat";
2894
- import { MeshStandardMaterial as MeshStandardMaterial4, Group as Group4, Vector3 as Vector312 } from "three";
2895
-
2896
- // src/lib/core/entity-asset-loader.ts
2897
- var EntityAssetLoader = class {
2898
- /**
2899
- * Load a model file (FBX, GLTF, GLB, OBJ) using the asset manager
2900
- */
2901
- async loadFile(file) {
2902
- const ext = file.split(".").pop()?.toLowerCase();
2903
- switch (ext) {
2904
- case "fbx": {
2905
- const result = await assetManager.loadFBX(file);
2906
- return {
2907
- object: result.object,
2908
- animation: result.animations?.[0]
2909
- };
2910
- }
2911
- case "gltf":
2912
- case "glb": {
2913
- const result = await assetManager.loadGLTF(file);
2914
- return {
2915
- object: result.object,
2916
- gltf: result.gltf
2917
- };
2918
- }
2919
- case "obj": {
2920
- const result = await assetManager.loadOBJ(file);
2921
- return {
2922
- object: result.object
2923
- };
2924
- }
2925
- default:
2926
- throw new Error(`Unsupported file type: ${file}`);
2927
- }
2928
- }
2929
- };
2930
-
2931
- // src/lib/entities/delegates/animation.ts
2932
- import {
2933
- AnimationMixer,
2934
- LoopOnce,
2935
- LoopRepeat
2936
- } from "three";
2937
- var AnimationDelegate = class {
2938
- constructor(target) {
2939
- this.target = target;
2940
- }
2941
- _mixer = null;
2942
- _actions = {};
2943
- _animations = [];
2944
- _currentAction = null;
2945
- _pauseAtPercentage = 0;
2946
- _isPaused = false;
2947
- _queuedKey = null;
2948
- _fadeDuration = 0.5;
2949
- _currentKey = "";
2950
- _assetLoader = new EntityAssetLoader();
2951
- async loadAnimations(animations) {
2952
- if (!animations.length) return;
2953
- const results = await Promise.all(animations.map((a) => this._assetLoader.loadFile(a.path)));
2954
- const loadedAnimations = [];
2955
- results.forEach((result, i) => {
2956
- if (result.animation) {
2957
- loadedAnimations.push({
2958
- key: animations[i].key || i.toString(),
2959
- clip: result.animation
2960
- });
2961
- }
2962
- });
2963
- if (!loadedAnimations.length) return;
2964
- this._animations = loadedAnimations.map((a) => a.clip);
2965
- this._mixer = new AnimationMixer(this.target);
2966
- loadedAnimations.forEach(({ key, clip }) => {
2967
- this._actions[key] = this._mixer.clipAction(clip);
2968
- });
2969
- this.playAnimation({ key: loadedAnimations[0].key });
2970
- }
2971
- update(delta) {
2972
- if (!this._mixer || !this._currentAction) return;
2973
- this._mixer.update(delta);
2974
- const pauseAtTime = this._currentAction.getClip().duration * (this._pauseAtPercentage / 100);
2975
- if (!this._isPaused && this._pauseAtPercentage > 0 && this._currentAction.time >= pauseAtTime) {
2976
- this._currentAction.time = pauseAtTime;
2977
- this._currentAction.paused = true;
2978
- this._isPaused = true;
2979
- if (this._queuedKey !== null) {
2980
- const next = this._actions[this._queuedKey];
2981
- next.reset().play();
2982
- this._currentAction.crossFadeTo(next, this._fadeDuration, false);
2983
- this._currentAction = next;
2984
- this._currentKey = this._queuedKey;
2985
- this._queuedKey = null;
2986
- }
2987
- }
2988
- }
2989
- playAnimation(opts) {
2990
- if (!this._mixer) return;
2991
- const { key, pauseAtPercentage = 0, pauseAtEnd = false, fadeToKey, fadeDuration = 0.5 } = opts;
2992
- if (key === this._currentKey) return;
2993
- const action = this._actions[key];
2994
- if (!action) return;
2995
- this._queuedKey = fadeToKey || null;
2996
- this._fadeDuration = fadeDuration;
2997
- this._pauseAtPercentage = pauseAtEnd ? 100 : pauseAtPercentage;
2998
- this._isPaused = false;
2999
- const prev = this._currentAction;
3000
- if (this._pauseAtPercentage > 0) {
3001
- action.setLoop(LoopOnce, Infinity);
3002
- action.clampWhenFinished = true;
3003
- } else {
3004
- action.setLoop(LoopRepeat, Infinity);
3005
- action.clampWhenFinished = false;
3006
- }
3007
- action.reset().play();
3008
- if (prev) {
3009
- prev.crossFadeTo(action, fadeDuration, false);
3010
- }
3011
- this._currentAction = action;
3012
- this._currentKey = key;
3013
- }
3014
- /**
3015
- * Dispose of all animation resources
3016
- */
3017
- dispose() {
3018
- Object.values(this._actions).forEach((action) => {
3019
- action.stop();
3020
- });
3021
- if (this._mixer) {
3022
- this._mixer.stopAllAction();
3023
- this._mixer.uncacheRoot(this.target);
3024
- this._mixer = null;
3025
- }
3026
- this._actions = {};
3027
- this._animations = [];
3028
- this._currentAction = null;
3029
- this._currentKey = "";
3030
- }
3031
- get currentAnimationKey() {
3032
- return this._currentKey;
3033
- }
3034
- get animations() {
3035
- return this._animations;
3036
- }
3037
- };
3038
-
3039
- // src/lib/entities/actor.ts
3040
- var actorDefaults = {
3041
- ...commonDefaults,
3042
- collision: {
3043
- static: false,
3044
- size: new Vector312(0.5, 0.5, 0.5),
3045
- position: new Vector312(0, 0, 0)
3046
- },
3047
- material: {
3048
- shader: standardShader
3049
- },
3050
- animations: [],
3051
- models: [],
3052
- collisionShape: "capsule"
3053
- };
3054
- var ActorCollisionBuilder = class extends EntityCollisionBuilder {
3055
- objectModel = null;
3056
- collisionShape = "capsule";
3057
- constructor(data) {
3058
- super();
3059
- this.objectModel = data.objectModel;
3060
- this.collisionShape = data.collisionShape ?? "capsule";
3061
- }
3062
- collider(options) {
3063
- if (this.collisionShape === "model") {
3064
- return this.createColliderFromModel(this.objectModel, options);
3065
- }
3066
- return this.createCapsuleCollider(options);
3067
- }
3068
- /**
3069
- * Create a capsule collider based on size options (character controller style).
3070
- */
3071
- createCapsuleCollider(options) {
3072
- const size = options.collision?.size ?? options.size ?? { x: 0.5, y: 1, z: 0.5 };
3073
- const halfHeight = size.y || 1;
3074
- const radius = Math.max(size.x || 0.5, size.z || 0.5);
3075
- let colliderDesc = ColliderDesc5.capsule(halfHeight, radius);
3076
- colliderDesc.setSensor(false);
3077
- colliderDesc.setTranslation(0, halfHeight + radius, 0);
3078
- colliderDesc.activeCollisionTypes = ActiveCollisionTypes3.DEFAULT;
3079
- return colliderDesc;
3080
- }
3081
- /**
3082
- * Create a collider based on model geometry (works with Mesh and SkinnedMesh).
3083
- * If collision.size and collision.position are provided, use those instead of computing from geometry.
3084
- */
3085
- createColliderFromModel(objectModel, options) {
3086
- const collisionSize = options.collision?.size;
3087
- const collisionPosition = options.collision?.position;
3088
- if (collisionSize) {
3089
- const halfWidth = collisionSize.x / 2;
3090
- const halfHeight = collisionSize.y / 2;
3091
- const halfDepth = collisionSize.z / 2;
3092
- let colliderDesc2 = ColliderDesc5.cuboid(halfWidth, halfHeight, halfDepth);
3093
- colliderDesc2.setSensor(false);
3094
- const posX = collisionPosition ? collisionPosition.x : 0;
3095
- const posY = collisionPosition ? collisionPosition.y : halfHeight;
3096
- const posZ = collisionPosition ? collisionPosition.z : 0;
3097
- colliderDesc2.setTranslation(posX, posY, posZ);
3098
- colliderDesc2.activeCollisionTypes = ActiveCollisionTypes3.DEFAULT;
3099
- return colliderDesc2;
3100
- }
3101
- if (!objectModel) return this.createCapsuleCollider(options);
3102
- let foundGeometry = null;
3103
- objectModel.traverse((child) => {
3104
- if (!foundGeometry && child.isMesh) {
3105
- foundGeometry = child.geometry;
3106
- }
3107
- });
3108
- if (!foundGeometry) return this.createCapsuleCollider(options);
3109
- const geometry = foundGeometry;
3110
- geometry.computeBoundingBox();
3111
- const box = geometry.boundingBox;
3112
- if (!box) return this.createCapsuleCollider(options);
3113
- const height = box.max.y - box.min.y;
3114
- const width = box.max.x - box.min.x;
3115
- const depth = box.max.z - box.min.z;
3116
- let colliderDesc = ColliderDesc5.cuboid(width / 2, height / 2, depth / 2);
3117
- colliderDesc.setSensor(false);
3118
- const centerY = (box.max.y + box.min.y) / 2;
3119
- colliderDesc.setTranslation(0, centerY, 0);
3120
- colliderDesc.activeCollisionTypes = ActiveCollisionTypes3.DEFAULT;
3121
- return colliderDesc;
3122
- }
3123
- };
3124
- var ActorBuilder = class extends EntityBuilder {
3125
- createEntity(options) {
3126
- return new ZylemActor(options);
3127
- }
3128
- };
3129
- var ACTOR_TYPE = /* @__PURE__ */ Symbol("Actor");
3130
- var ZylemActor = class extends GameEntity {
3131
- static type = ACTOR_TYPE;
3132
- _object = null;
3133
- _animationDelegate = null;
3134
- _modelFileNames = [];
3135
- _assetLoader = new EntityAssetLoader();
3136
- controlledRotation = false;
3137
- constructor(options) {
3138
- super();
3139
- this.options = { ...actorDefaults, ...options };
3140
- this.prependUpdate(this.actorUpdate.bind(this));
3141
- this.controlledRotation = true;
3142
- }
3143
- /**
3144
- * Initiates model and animation loading in background (deferred).
3145
- * Call returns immediately; assets will be ready on subsequent updates.
3146
- */
3147
- load() {
3148
- this._modelFileNames = this.options.models || [];
3149
- this.loadModelsDeferred();
3150
- }
3151
- /**
3152
- * Returns current data synchronously.
3153
- * May return null values if loading is still in progress.
3154
- */
3155
- data() {
3156
- return {
3157
- animations: this._animationDelegate?.animations,
3158
- objectModel: this._object,
3159
- collisionShape: this.options.collisionShape
3160
- };
3161
- }
3162
- actorUpdate(params) {
3163
- this._animationDelegate?.update(params.delta);
3164
- }
3165
- /**
3166
- * Clean up actor resources including animations, models, and groups
3167
- */
3168
- actorDestroy() {
3169
- if (this._animationDelegate) {
3170
- this._animationDelegate.dispose();
3171
- this._animationDelegate = null;
3172
- }
3173
- if (this._object) {
3174
- this._object.traverse((child) => {
3175
- if (child.isMesh) {
3176
- const mesh = child;
3177
- mesh.geometry?.dispose();
3178
- if (Array.isArray(mesh.material)) {
3179
- mesh.material.forEach((m) => m.dispose());
3180
- } else if (mesh.material) {
3181
- mesh.material.dispose();
3182
- }
3183
- }
3184
- });
3185
- this._object = null;
3186
- }
3187
- if (this.group) {
3188
- this.group.clear();
3189
- this.group = null;
3190
- }
3191
- this._modelFileNames = [];
3192
- }
3193
- /**
3194
- * Deferred loading - starts async load and updates entity when complete.
3195
- * Called by synchronous load() method.
3196
- */
3197
- loadModelsDeferred() {
3198
- if (this._modelFileNames.length === 0) return;
3199
- this.dispatch("entity:model:loading", {
3200
- entityId: this.uuid,
3201
- files: this._modelFileNames
3202
- });
3203
- const promises = this._modelFileNames.map((file) => this._assetLoader.loadFile(file));
3204
- Promise.all(promises).then((results) => {
3205
- if (results[0]?.object) {
3206
- this._object = results[0].object;
3207
- }
3208
- let meshCount = 0;
3209
- if (this._object) {
3210
- this._object.traverse((child) => {
3211
- if (child.isMesh) meshCount++;
3212
- });
3213
- this.group = new Group4();
3214
- this.group.attach(this._object);
3215
- this.group.scale.set(
3216
- this.options.scale?.x || 1,
3217
- this.options.scale?.y || 1,
3218
- this.options.scale?.z || 1
3219
- );
3220
- this.applyMaterialOverrides();
3221
- this._animationDelegate = new AnimationDelegate(this._object);
3222
- this._animationDelegate.loadAnimations(this.options.animations || []).then(() => {
3223
- this.dispatch("entity:animation:loaded", {
3224
- entityId: this.uuid,
3225
- animationCount: this.options.animations?.length || 0
3226
- });
3227
- });
3228
- }
3229
- this.dispatch("entity:model:loaded", {
3230
- entityId: this.uuid,
3231
- success: !!this._object,
3232
- meshCount
3233
- });
3234
- });
3235
- }
3236
- playAnimation(animationOptions) {
3237
- this._animationDelegate?.playAnimation(animationOptions);
3238
- }
3239
- /**
3240
- * Apply material overrides from options to all meshes in the loaded model.
3241
- * Only applies if material options are explicitly specified (not just defaults).
3242
- */
3243
- applyMaterialOverrides() {
3244
- const materialOptions = this.options.material;
3245
- if (!materialOptions || !materialOptions.color && !materialOptions.path) {
3246
- return;
3247
- }
3248
- if (!this._object) return;
3249
- this._object.traverse((child) => {
3250
- if (child.isMesh) {
3251
- const mesh = child;
3252
- if (materialOptions.color) {
3253
- const newMaterial = new MeshStandardMaterial4({
3254
- color: materialOptions.color,
3255
- emissiveIntensity: 0.5,
3256
- lightMapIntensity: 0.5,
3257
- fog: true
3258
- });
3259
- mesh.castShadow = true;
3260
- mesh.receiveShadow = true;
3261
- mesh.material = newMaterial;
3262
- }
3263
- }
3264
- });
3265
- }
3266
- get object() {
3267
- return this._object;
3268
- }
3269
- /**
3270
- * Provide custom debug information for the actor
3271
- * This will be merged with the default debug information
3272
- */
3273
- getDebugInfo() {
3274
- const debugInfo = {
3275
- type: "Actor",
3276
- models: this._modelFileNames.length > 0 ? this._modelFileNames : "none",
3277
- modelLoaded: !!this._object,
3278
- scale: this.options.scale ? `${this.options.scale.x}, ${this.options.scale.y}, ${this.options.scale.z}` : "1, 1, 1"
3279
- };
3280
- if (this._animationDelegate) {
3281
- debugInfo.currentAnimation = this._animationDelegate.currentAnimationKey || "none";
3282
- debugInfo.animationsCount = this.options.animations?.length || 0;
3283
- }
3284
- if (this._object) {
3285
- let meshCount = 0;
3286
- let vertexCount = 0;
3287
- this._object.traverse((child) => {
3288
- if (child.isMesh) {
3289
- meshCount++;
3290
- const geometry = child.geometry;
3291
- if (geometry && geometry.attributes.position) {
3292
- vertexCount += geometry.attributes.position.count;
3293
- }
3294
- }
3295
- });
3296
- debugInfo.meshCount = meshCount;
3297
- debugInfo.vertexCount = vertexCount;
3298
- }
3299
- return debugInfo;
3300
- }
3301
- };
3302
- function createActor(...args) {
3303
- return createEntity({
3304
- args,
3305
- defaultConfig: actorDefaults,
3306
- EntityClass: ZylemActor,
3307
- BuilderClass: ActorBuilder,
3308
- CollisionBuilderClass: ActorCollisionBuilder,
3309
- entityType: ZylemActor.type
3310
- });
3311
- }
3312
-
3313
- // src/lib/entities/text.ts
3314
- import { Color as Color6, Group as Group5, Sprite as ThreeSprite2, SpriteMaterial as SpriteMaterial2, CanvasTexture, LinearFilter, Vector2 as Vector24, ClampToEdgeWrapping } from "three";
3315
- var textDefaults = {
3316
- position: void 0,
3317
- text: "",
3318
- fontFamily: 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
3319
- fontSize: 18,
3320
- fontColor: "#FFFFFF",
3321
- backgroundColor: null,
3322
- padding: 4,
3323
- stickToViewport: true,
3324
- screenPosition: new Vector24(24, 24),
3325
- zDistance: 1
3326
- };
3327
- var TextBuilder = class extends EntityBuilder {
3328
- createEntity(options) {
3329
- return new ZylemText(options);
3330
- }
3331
- };
3332
- var TEXT_TYPE = /* @__PURE__ */ Symbol("Text");
3333
- var ZylemText = class _ZylemText extends GameEntity {
3334
- static type = TEXT_TYPE;
3335
- _sprite = null;
3336
- _texture = null;
3337
- _canvas = null;
3338
- _ctx = null;
3339
- _cameraRef = null;
3340
- _lastCanvasW = 0;
3341
- _lastCanvasH = 0;
3342
- constructor(options) {
3343
- super();
3344
- this.options = { ...textDefaults, ...options };
3345
- this.prependSetup(this.textSetup.bind(this));
3346
- this.prependUpdate(this.textUpdate.bind(this));
3347
- this.onCleanup(this.textDestroy.bind(this));
3348
- }
3349
- create() {
3350
- this._sprite = null;
3351
- this._texture = null;
3352
- this._canvas = null;
3353
- this._ctx = null;
3354
- this._lastCanvasW = 0;
3355
- this._lastCanvasH = 0;
3356
- this.group = new Group5();
3357
- this.createSprite();
3358
- return super.create();
3359
- }
3360
- createSprite() {
3361
- this._canvas = document.createElement("canvas");
3362
- this._ctx = this._canvas.getContext("2d");
3363
- this._texture = new CanvasTexture(this._canvas);
3364
- this._texture.minFilter = LinearFilter;
3365
- this._texture.magFilter = LinearFilter;
3366
- const material = new SpriteMaterial2({
3367
- map: this._texture,
3368
- transparent: true,
3369
- depthTest: false,
3370
- depthWrite: false,
3371
- alphaTest: 0.5
3372
- });
3373
- this._sprite = new ThreeSprite2(material);
3374
- this.group?.add(this._sprite);
3375
- this.redrawText(this.options.text ?? "");
3376
- }
3377
- measureAndResizeCanvas(text, fontSize, fontFamily, padding) {
3378
- if (!this._canvas || !this._ctx) return { sizeChanged: false };
3379
- this._ctx.font = `${fontSize}px ${fontFamily}`;
3380
- const metrics = this._ctx.measureText(text);
3381
- const textWidth = Math.ceil(metrics.width);
3382
- const textHeight = Math.ceil(fontSize * 1.4);
3383
- const nextW = Math.max(2, textWidth + padding * 2);
3384
- const nextH = Math.max(2, textHeight + padding * 2);
3385
- const sizeChanged = nextW !== this._lastCanvasW || nextH !== this._lastCanvasH;
3386
- this._canvas.width = nextW;
3387
- this._canvas.height = nextH;
3388
- this._lastCanvasW = nextW;
3389
- this._lastCanvasH = nextH;
3390
- return { sizeChanged };
3391
- }
3392
- drawCenteredText(text, fontSize, fontFamily) {
3393
- if (!this._canvas || !this._ctx) return;
3394
- this._ctx.font = `${fontSize}px ${fontFamily}`;
3395
- this._ctx.textAlign = "center";
3396
- this._ctx.textBaseline = "middle";
3397
- this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
3398
- if (this.options.backgroundColor) {
3399
- this._ctx.fillStyle = this.toCssColor(this.options.backgroundColor);
3400
- this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
3401
- }
3402
- this._ctx.fillStyle = this.toCssColor(this.options.fontColor ?? "#FFFFFF");
3403
- this._ctx.fillText(text, this._canvas.width / 2, this._canvas.height / 2);
3404
- }
3405
- updateTexture(sizeChanged) {
3406
- if (!this._texture || !this._canvas) return;
3407
- if (sizeChanged) {
3408
- this._texture.dispose();
3409
- this._texture = new CanvasTexture(this._canvas);
3410
- this._texture.minFilter = LinearFilter;
3411
- this._texture.magFilter = LinearFilter;
3412
- this._texture.wrapS = ClampToEdgeWrapping;
3413
- this._texture.wrapT = ClampToEdgeWrapping;
3414
- }
3415
- this._texture.image = this._canvas;
3416
- this._texture.needsUpdate = true;
3417
- if (this._sprite && this._sprite.material) {
3418
- this._sprite.material.map = this._texture;
3419
- this._sprite.material.needsUpdate = true;
3420
- }
3421
- }
3422
- redrawText(_text) {
3423
- if (!this._canvas || !this._ctx) return;
3424
- const fontSize = this.options.fontSize ?? 18;
3425
- const fontFamily = this.options.fontFamily ?? textDefaults.fontFamily;
3426
- const padding = this.options.padding ?? 4;
3427
- const { sizeChanged } = this.measureAndResizeCanvas(_text, fontSize, fontFamily, padding);
3428
- this.drawCenteredText(_text, fontSize, fontFamily);
3429
- this.updateTexture(Boolean(sizeChanged));
3430
- if (this.options.stickToViewport && this._cameraRef) {
3431
- this.updateStickyTransform();
3432
- }
3433
- }
3434
- toCssColor(color) {
3435
- if (typeof color === "string") return color;
3436
- const c = color instanceof Color6 ? color : new Color6(color);
3437
- return `#${c.getHexString()}`;
3438
- }
3439
- textSetup(params) {
3440
- this._cameraRef = params.camera;
3441
- if (this.options.stickToViewport && this._cameraRef) {
3442
- this._cameraRef.camera.add(this.group);
3443
- this.updateStickyTransform();
3444
- }
3445
- }
3446
- textUpdate(params) {
3447
- if (!this._sprite) return;
3448
- if (this.options.stickToViewport && this._cameraRef) {
3449
- this.updateStickyTransform();
3450
- }
3451
- }
3452
- getResolution() {
3453
- return {
3454
- width: this._cameraRef?.screenResolution.x ?? 1,
3455
- height: this._cameraRef?.screenResolution.y ?? 1
3456
- };
3457
- }
3458
- getScreenPixels(sp, width, height) {
3459
- const isPercentX = sp.x >= 0 && sp.x <= 1;
3460
- const isPercentY = sp.y >= 0 && sp.y <= 1;
3461
- return {
3462
- px: isPercentX ? sp.x * width : sp.x,
3463
- py: isPercentY ? sp.y * height : sp.y
3464
- };
3465
- }
3466
- computeWorldExtents(camera, zDist) {
3467
- let worldHalfW = 1;
3468
- let worldHalfH = 1;
3469
- if (camera.isPerspectiveCamera) {
3470
- const pc = camera;
3471
- const halfH = Math.tan(pc.fov * Math.PI / 180 / 2) * zDist;
3472
- const halfW = halfH * pc.aspect;
3473
- worldHalfW = halfW;
3474
- worldHalfH = halfH;
3475
- } else if (camera.isOrthographicCamera) {
3476
- const oc = camera;
3477
- worldHalfW = (oc.right - oc.left) / 2;
3478
- worldHalfH = (oc.top - oc.bottom) / 2;
3479
- }
3480
- return { worldHalfW, worldHalfH };
3481
- }
3482
- updateSpriteScale(worldHalfH, viewportHeight) {
3483
- if (!this._canvas || !this._sprite) return;
3484
- const planeH = worldHalfH * 2;
3485
- const unitsPerPixel = planeH / viewportHeight;
3486
- const pixelH = this._canvas.height;
3487
- const scaleY = Math.max(1e-4, pixelH * unitsPerPixel);
3488
- const aspect = this._canvas.width / this._canvas.height;
3489
- const scaleX = scaleY * aspect;
3490
- this._sprite.scale.set(scaleX, scaleY, 1);
3491
- }
3492
- updateStickyTransform() {
3493
- if (!this._sprite || !this._cameraRef) return;
3494
- const camera = this._cameraRef.camera;
3495
- const { width, height } = this.getResolution();
3496
- const sp = this.options.screenPosition ?? new Vector24(24, 24);
3497
- const { px, py } = this.getScreenPixels(sp, width, height);
3498
- const zDist = Math.max(1e-3, this.options.zDistance ?? 1);
3499
- const { worldHalfW, worldHalfH } = this.computeWorldExtents(camera, zDist);
3500
- const ndcX = px / width * 2 - 1;
3501
- const ndcY = 1 - py / height * 2;
3502
- const localX = ndcX * worldHalfW;
3503
- const localY = ndcY * worldHalfH;
3504
- this.group?.position.set(localX, localY, -zDist);
3505
- this.updateSpriteScale(worldHalfH, height);
3506
- }
3507
- updateText(_text) {
3508
- this.options.text = _text;
3509
- this.redrawText(_text);
3510
- if (this.options.stickToViewport && this._cameraRef) {
3511
- this.updateStickyTransform();
3512
- }
3513
- }
3514
- buildInfo() {
3515
- const delegate = new DebugDelegate(this);
3516
- const baseInfo = delegate.buildDebugInfo();
3517
- return {
3518
- ...baseInfo,
3519
- type: String(_ZylemText.type),
3520
- text: this.options.text ?? "",
3521
- sticky: this.options.stickToViewport
3522
- };
3523
- }
3524
- /**
3525
- * Dispose of Three.js resources when the entity is destroyed.
3526
- */
3527
- textDestroy() {
3528
- this._texture?.dispose();
3529
- if (this._sprite?.material) {
3530
- this._sprite.material.dispose();
3531
- }
3532
- if (this._sprite) {
3533
- this._sprite.removeFromParent();
3534
- }
3535
- this.group?.removeFromParent();
3536
- this._sprite = null;
3537
- this._texture = null;
3538
- this._canvas = null;
3539
- this._ctx = null;
3540
- this._cameraRef = null;
3541
- }
3542
- };
3543
- function createText(...args) {
3544
- return createEntity({
3545
- args,
3546
- defaultConfig: { ...textDefaults },
3547
- EntityClass: ZylemText,
3548
- BuilderClass: TextBuilder,
3549
- entityType: ZylemText.type
3550
- });
3551
- }
3552
-
3553
- // src/lib/entities/rect.ts
3554
- import { Color as Color7, Group as Group6, Sprite as ThreeSprite3, SpriteMaterial as SpriteMaterial3, CanvasTexture as CanvasTexture2, LinearFilter as LinearFilter2, Vector2 as Vector25, ClampToEdgeWrapping as ClampToEdgeWrapping2, ShaderMaterial as ShaderMaterial3, Mesh as Mesh6, PlaneGeometry as PlaneGeometry2, Vector3 as Vector313 } from "three";
3555
- var rectDefaults = {
3556
- position: void 0,
3557
- width: 120,
3558
- height: 48,
3559
- fillColor: "#FFFFFF",
3560
- strokeColor: null,
3561
- strokeWidth: 0,
3562
- radius: 0,
3563
- padding: 0,
3564
- stickToViewport: true,
3565
- screenPosition: new Vector25(24, 24),
3566
- zDistance: 1,
3567
- anchor: new Vector25(0, 0)
3568
- };
3569
- var RectBuilder = class extends EntityBuilder {
3570
- createEntity(options) {
3571
- return new ZylemRect(options);
3572
- }
3573
- };
3574
- var RECT_TYPE = /* @__PURE__ */ Symbol("Rect");
3575
- var ZylemRect = class _ZylemRect extends GameEntity {
3576
- static type = RECT_TYPE;
3577
- _sprite = null;
3578
- _mesh = null;
3579
- _texture = null;
3580
- _canvas = null;
3581
- _ctx = null;
3582
- _cameraRef = null;
3583
- _lastCanvasW = 0;
3584
- _lastCanvasH = 0;
3585
- constructor(options) {
3586
- super();
3587
- this.options = { ...rectDefaults, ...options };
3588
- this.group = new Group6();
3589
- this.createSprite();
3590
- this.prependSetup(this.rectSetup.bind(this));
3591
- this.prependUpdate(this.rectUpdate.bind(this));
3592
- }
3593
- createSprite() {
3594
- this._canvas = document.createElement("canvas");
3595
- this._ctx = this._canvas.getContext("2d");
3596
- this._texture = new CanvasTexture2(this._canvas);
3597
- this._texture.minFilter = LinearFilter2;
3598
- this._texture.magFilter = LinearFilter2;
3599
- const material = new SpriteMaterial3({
3600
- map: this._texture,
3601
- transparent: true,
3602
- depthTest: false,
3603
- depthWrite: false,
3604
- alphaTest: 0.5
3605
- });
3606
- this._sprite = new ThreeSprite3(material);
3607
- this.group?.add(this._sprite);
3608
- this.redrawRect();
3609
- }
3610
- redrawRect() {
3611
- if (!this._canvas || !this._ctx) return;
3612
- const width = Math.max(2, Math.floor(this.options.width ?? 120));
3613
- const height = Math.max(2, Math.floor(this.options.height ?? 48));
3614
- const padding = this.options.padding ?? 0;
3615
- const strokeWidth = this.options.strokeWidth ?? 0;
3616
- const totalW = width + padding * 2 + strokeWidth;
3617
- const totalH = height + padding * 2 + strokeWidth;
3618
- const nextW = Math.max(2, totalW);
3619
- const nextH = Math.max(2, totalH);
3620
- const sizeChanged = nextW !== this._lastCanvasW || nextH !== this._lastCanvasH;
3621
- this._canvas.width = nextW;
3622
- this._canvas.height = nextH;
3623
- this._lastCanvasW = nextW;
3624
- this._lastCanvasH = nextH;
3625
- this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
3626
- const radius = Math.max(0, this.options.radius ?? 0);
3627
- const rectX = Math.floor(padding + strokeWidth / 2);
3628
- const rectY = Math.floor(padding + strokeWidth / 2);
3629
- const rectW = Math.floor(width);
3630
- const rectH = Math.floor(height);
3631
- this._ctx.beginPath();
3632
- if (radius > 0) {
3633
- this.roundedRectPath(this._ctx, rectX, rectY, rectW, rectH, radius);
3634
- } else {
3635
- this._ctx.rect(rectX, rectY, rectW, rectH);
3636
- }
3637
- if (this.options.fillColor) {
3638
- this._ctx.fillStyle = this.toCssColor(this.options.fillColor);
3639
- this._ctx.fill();
3640
- }
3641
- if (this.options.strokeColor && strokeWidth > 0) {
3642
- this._ctx.lineWidth = strokeWidth;
3643
- this._ctx.strokeStyle = this.toCssColor(this.options.strokeColor);
3644
- this._ctx.stroke();
3645
- }
3646
- if (this._texture) {
3647
- if (sizeChanged) {
3648
- this._texture.dispose();
3649
- this._texture = new CanvasTexture2(this._canvas);
3650
- this._texture.minFilter = LinearFilter2;
3651
- this._texture.magFilter = LinearFilter2;
3652
- this._texture.wrapS = ClampToEdgeWrapping2;
3653
- this._texture.wrapT = ClampToEdgeWrapping2;
3654
- if (this._sprite && this._sprite.material instanceof ShaderMaterial3) {
3655
- const shader = this._sprite.material;
3656
- if (shader.uniforms?.tDiffuse) shader.uniforms.tDiffuse.value = this._texture;
3657
- if (shader.uniforms?.iResolution) shader.uniforms.iResolution.value.set(this._canvas.width, this._canvas.height, 1);
3658
- }
3659
- }
3660
- this._texture.image = this._canvas;
3661
- this._texture.needsUpdate = true;
3662
- if (this._sprite && this._sprite.material) {
3663
- this._sprite.material.map = this._texture;
3664
- this._sprite.material.needsUpdate = true;
3665
- }
3666
- }
3667
- }
3668
- getWidth() {
3669
- return this.options.width ?? 0;
3670
- }
3671
- getHeight() {
3672
- return this.options.height ?? 0;
3673
- }
3674
- roundedRectPath(ctx, x, y, w, h, r) {
3675
- const radius = Math.min(r, Math.floor(Math.min(w, h) / 2));
3676
- ctx.moveTo(x + radius, y);
3677
- ctx.lineTo(x + w - radius, y);
3678
- ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
3679
- ctx.lineTo(x + w, y + h - radius);
3680
- ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
3681
- ctx.lineTo(x + radius, y + h);
3682
- ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
3683
- ctx.lineTo(x, y + radius);
3684
- ctx.quadraticCurveTo(x, y, x + radius, y);
3685
- }
3686
- toCssColor(color) {
3687
- if (typeof color === "string") return color;
3688
- const c = color instanceof Color7 ? color : new Color7(color);
3689
- return `#${c.getHexString()}`;
3690
- }
3691
- /**
3692
- * Get the viewport resolution from the renderer DOM element,
3693
- * falling back to the camera's screenResolution if the renderer
3694
- * is not available (e.g. when using the new RendererManager path).
3695
- */
3696
- getResolution() {
3697
- try {
3698
- const dom = this._cameraRef?.renderer?.domElement;
3699
- if (dom) {
3700
- return { width: dom.clientWidth || 1, height: dom.clientHeight || 1 };
3701
- }
3702
- } catch {
3703
- }
3704
- return {
3705
- width: this._cameraRef?.screenResolution?.x ?? 1,
3706
- height: this._cameraRef?.screenResolution?.y ?? 1
3707
- };
3708
- }
3709
- rectSetup(params) {
3710
- this._cameraRef = params.camera;
3711
- if (this.options.stickToViewport && this._cameraRef) {
3712
- this._cameraRef.camera.add(this.group);
3713
- }
3714
- if (this.materials?.length && this._sprite) {
3715
- const mat = this.materials[0];
3716
- if (mat instanceof ShaderMaterial3) {
3717
- mat.transparent = true;
3718
- mat.depthTest = false;
3719
- mat.depthWrite = false;
3720
- if (this._texture) {
3721
- if (mat.uniforms?.tDiffuse) mat.uniforms.tDiffuse.value = this._texture;
3722
- if (mat.uniforms?.iResolution && this._canvas) mat.uniforms.iResolution.value.set(this._canvas.width, this._canvas.height, 1);
3723
- }
3724
- this._mesh = new Mesh6(new PlaneGeometry2(1, 1), mat);
3725
- this.group?.add(this._mesh);
3726
- this._sprite.visible = false;
3727
- }
3728
- }
3729
- }
3730
- rectUpdate(params) {
3731
- if (!this._sprite) return;
3732
- if (this._cameraRef && this.options.bounds) {
3733
- const screen = this.computeScreenBoundsFromOptions(this.options.bounds);
3734
- if (screen) {
3735
- const { x, y, width, height } = screen;
3736
- const desiredW = Math.max(2, Math.floor(width));
3737
- const desiredH = Math.max(2, Math.floor(height));
3738
- const changed = desiredW !== (this.options.width ?? 0) || desiredH !== (this.options.height ?? 0);
3739
- this.options.screenPosition = new Vector25(Math.floor(x), Math.floor(y));
3740
- this.options.width = desiredW;
3741
- this.options.height = desiredH;
3742
- this.options.anchor = new Vector25(0, 0);
3743
- if (changed) {
3744
- this.redrawRect();
3745
- }
3746
- }
3747
- }
3748
- if (this.options.stickToViewport && this._cameraRef) {
3749
- this.updateStickyTransform();
3750
- }
3751
- }
3752
- updateStickyTransform() {
3753
- if (!this._sprite || !this._cameraRef) return;
3754
- const camera = this._cameraRef.camera;
3755
- const { width, height } = this.getResolution();
3756
- const px = (this.options.screenPosition ?? new Vector25(24, 24)).x;
3757
- const py = (this.options.screenPosition ?? new Vector25(24, 24)).y;
3758
- const zDist = Math.max(1e-3, this.options.zDistance ?? 1);
3759
- let worldHalfW = 1;
3760
- let worldHalfH = 1;
3761
- if (camera.isPerspectiveCamera) {
3762
- const pc = camera;
3763
- const halfH = Math.tan(pc.fov * Math.PI / 180 / 2) * zDist;
3764
- const halfW = halfH * pc.aspect;
3765
- worldHalfW = halfW;
3766
- worldHalfH = halfH;
3767
- } else if (camera.isOrthographicCamera) {
3768
- const oc = camera;
3769
- worldHalfW = (oc.right - oc.left) / 2;
3770
- worldHalfH = (oc.top - oc.bottom) / 2;
3771
- }
3772
- const ndcX = px / width * 2 - 1;
3773
- const ndcY = 1 - py / height * 2;
3774
- const localX = ndcX * worldHalfW;
3775
- const localY = ndcY * worldHalfH;
3776
- let scaleX = 1;
3777
- let scaleY = 1;
3778
- if (this._canvas) {
3779
- const planeH = worldHalfH * 2;
3780
- const unitsPerPixel = planeH / height;
3781
- const pixelH = this._canvas.height;
3782
- scaleY = Math.max(1e-4, pixelH * unitsPerPixel);
3783
- const aspect = this._canvas.width / this._canvas.height;
3784
- scaleX = scaleY * aspect;
3785
- this._sprite.scale.set(scaleX, scaleY, 1);
3786
- if (this._mesh) this._mesh.scale.set(scaleX, scaleY, 1);
3787
- }
3788
- const anchor = this.options.anchor ?? new Vector25(0, 0);
3789
- const ax = Math.min(100, Math.max(0, anchor.x)) / 100;
3790
- const ay = Math.min(100, Math.max(0, anchor.y)) / 100;
3791
- const offsetX = (0.5 - ax) * scaleX;
3792
- const offsetY = (ay - 0.5) * scaleY;
3793
- this.group?.position.set(localX + offsetX, localY + offsetY, -zDist);
3794
- }
3795
- worldToScreen(point) {
3796
- if (!this._cameraRef) return { x: 0, y: 0 };
3797
- const camera = this._cameraRef.camera;
3798
- const { width, height } = this.getResolution();
3799
- const v = point.clone().project(camera);
3800
- const x = (v.x + 1) / 2 * width;
3801
- const y = (1 - v.y) / 2 * height;
3802
- return { x, y };
3803
- }
3804
- computeScreenBoundsFromOptions(bounds) {
3805
- if (!this._cameraRef) return null;
3806
- if (bounds.screen) {
3807
- return { ...bounds.screen };
3808
- }
3809
- if (bounds.world) {
3810
- const { left, right, top, bottom, z = 0 } = bounds.world;
3811
- const tl = this.worldToScreen(new Vector313(left, top, z));
3812
- const br = this.worldToScreen(new Vector313(right, bottom, z));
3813
- const x = Math.min(tl.x, br.x);
3814
- const y = Math.min(tl.y, br.y);
3815
- const width = Math.abs(br.x - tl.x);
3816
- const height = Math.abs(br.y - tl.y);
3817
- return { x, y, width, height };
3818
- }
3819
- return null;
3820
- }
3821
- updateRect(options) {
3822
- this.options = { ...this.options, ...options };
3823
- this.redrawRect();
3824
- if (this.options.stickToViewport && this._cameraRef) {
3825
- this.updateStickyTransform();
3826
- }
3827
- }
3828
- buildInfo() {
3829
- const delegate = new DebugDelegate(this);
3830
- const baseInfo = delegate.buildDebugInfo();
3831
- return {
3832
- ...baseInfo,
3833
- type: String(_ZylemRect.type),
3834
- width: this.options.width ?? 0,
3835
- height: this.options.height ?? 0,
3836
- sticky: this.options.stickToViewport
3837
- };
3838
- }
3839
- };
3840
- function createRect(...args) {
3841
- return createEntity({
3842
- args,
3843
- defaultConfig: { ...rectDefaults },
3844
- EntityClass: ZylemRect,
3845
- BuilderClass: RectBuilder,
3846
- entityType: ZylemRect.type
3847
- });
3848
- }
3849
-
3850
- // src/lib/entities/cone.ts
3851
- var coneDefaults = {
3852
- ...commonDefaults,
3853
- radius: 1,
3854
- height: 2,
3855
- radialSegments: 32
3856
- };
3857
- var CONE_TYPE = /* @__PURE__ */ Symbol("Cone");
3858
- var ZylemCone = class _ZylemCone extends GameEntity {
3859
- static type = CONE_TYPE;
3860
- constructor(options) {
3861
- super();
3862
- this.options = { ...coneDefaults, ...options };
3863
- }
3864
- buildInfo() {
3865
- const delegate = new DebugDelegate(this);
3866
- const baseInfo = delegate.buildDebugInfo();
3867
- const radius = this.options.radius ?? 1;
3868
- const height = this.options.height ?? 2;
3869
- return {
3870
- ...baseInfo,
3871
- type: String(_ZylemCone.type),
3872
- radius: radius.toFixed(2),
3873
- height: height.toFixed(2)
3874
- };
3875
- }
3876
- };
3877
- function createCone(...args) {
3878
- const options = mergeArgs(args, coneDefaults);
3879
- const entity = new ZylemCone(options);
3880
- entity.add(
3881
- coneMesh({
3882
- radius: options.radius,
3883
- height: options.height,
3884
- radialSegments: options.radialSegments,
3885
- material: options.material,
3886
- color: options.color
3887
- }),
3888
- coneCollision({
3889
- radius: options.radius,
3890
- height: options.height,
3891
- static: options.collision?.static,
3892
- sensor: options.collision?.sensor,
3893
- collisionType: options.collisionType,
3894
- collisionFilter: options.collisionFilter
3895
- })
3896
- );
3897
- return entity;
3898
- }
3899
-
3900
- // src/lib/entities/pyramid.ts
3901
- var pyramidDefaults = {
3902
- ...commonDefaults,
3903
- radius: 1,
3904
- height: 2
3905
- };
3906
- var PYRAMID_TYPE = /* @__PURE__ */ Symbol("Pyramid");
3907
- var ZylemPyramid = class _ZylemPyramid extends GameEntity {
3908
- static type = PYRAMID_TYPE;
3909
- constructor(options) {
3910
- super();
3911
- this.options = { ...pyramidDefaults, ...options };
3912
- }
3913
- buildInfo() {
3914
- const delegate = new DebugDelegate(this);
3915
- const baseInfo = delegate.buildDebugInfo();
3916
- const radius = this.options.radius ?? 1;
3917
- const height = this.options.height ?? 2;
3918
- return {
3919
- ...baseInfo,
3920
- type: String(_ZylemPyramid.type),
3921
- radius: radius.toFixed(2),
3922
- height: height.toFixed(2)
3923
- };
3924
- }
3925
- };
3926
- function createPyramid(...args) {
3927
- const options = mergeArgs(args, pyramidDefaults);
3928
- const entity = new ZylemPyramid(options);
3929
- entity.add(
3930
- pyramidMesh({
3931
- radius: options.radius,
3932
- height: options.height,
3933
- material: options.material,
3934
- color: options.color
3935
- }),
3936
- pyramidCollision({
3937
- radius: options.radius,
3938
- height: options.height,
3939
- static: options.collision?.static,
3940
- sensor: options.collision?.sensor,
3941
- collisionType: options.collisionType,
3942
- collisionFilter: options.collisionFilter
3943
- })
3944
- );
3945
- return entity;
3946
- }
3947
-
3948
- // src/lib/entities/cylinder.ts
3949
- var cylinderDefaults = {
3950
- ...commonDefaults,
3951
- radiusTop: 1,
3952
- radiusBottom: 1,
3953
- height: 2,
3954
- radialSegments: 32
3955
- };
3956
- var CYLINDER_TYPE = /* @__PURE__ */ Symbol("Cylinder");
3957
- var ZylemCylinder = class _ZylemCylinder extends GameEntity {
3958
- static type = CYLINDER_TYPE;
3959
- constructor(options) {
3960
- super();
3961
- this.options = { ...cylinderDefaults, ...options };
3962
- }
3963
- buildInfo() {
3964
- const delegate = new DebugDelegate(this);
3965
- const baseInfo = delegate.buildDebugInfo();
3966
- const radiusTop = this.options.radiusTop ?? 1;
3967
- const radiusBottom = this.options.radiusBottom ?? 1;
3968
- const height = this.options.height ?? 2;
3969
- return {
3970
- ...baseInfo,
3971
- type: String(_ZylemCylinder.type),
3972
- radiusTop: radiusTop.toFixed(2),
3973
- radiusBottom: radiusBottom.toFixed(2),
3974
- height: height.toFixed(2)
3975
- };
3976
- }
3977
- };
3978
- function createCylinder(...args) {
3979
- const options = mergeArgs(args, cylinderDefaults);
3980
- const entity = new ZylemCylinder(options);
3981
- entity.add(
3982
- cylinderMesh({
3983
- radiusTop: options.radiusTop,
3984
- radiusBottom: options.radiusBottom,
3985
- height: options.height,
3986
- radialSegments: options.radialSegments,
3987
- material: options.material,
3988
- color: options.color
3989
- }),
3990
- cylinderCollision({
3991
- radiusTop: options.radiusTop,
3992
- radiusBottom: options.radiusBottom,
3993
- height: options.height,
3994
- static: options.collision?.static,
3995
- sensor: options.collision?.sensor,
3996
- collisionType: options.collisionType,
3997
- collisionFilter: options.collisionFilter
3998
- })
3999
- );
4000
- return entity;
4001
- }
4002
-
4003
- // src/lib/entities/pill.ts
4004
- var pillDefaults = {
4005
- ...commonDefaults,
4006
- radius: 0.5,
4007
- length: 1,
4008
- capSegments: 10,
4009
- radialSegments: 20
4010
- };
4011
- var PILL_TYPE = /* @__PURE__ */ Symbol("Pill");
4012
- var ZylemPill = class _ZylemPill extends GameEntity {
4013
- static type = PILL_TYPE;
4014
- constructor(options) {
4015
- super();
4016
- this.options = { ...pillDefaults, ...options };
4017
- }
4018
- buildInfo() {
4019
- const delegate = new DebugDelegate(this);
4020
- const baseInfo = delegate.buildDebugInfo();
4021
- const radius = this.options.radius ?? 0.5;
4022
- const length = this.options.length ?? 1;
4023
- return {
4024
- ...baseInfo,
4025
- type: String(_ZylemPill.type),
4026
- radius: radius.toFixed(2),
4027
- length: length.toFixed(2)
4028
- };
4029
- }
4030
- };
4031
- function createPill(...args) {
4032
- const options = mergeArgs(args, pillDefaults);
4033
- const entity = new ZylemPill(options);
4034
- entity.add(
4035
- pillMesh({
4036
- radius: options.radius,
4037
- length: options.length,
4038
- capSegments: options.capSegments,
4039
- radialSegments: options.radialSegments,
4040
- material: options.material,
4041
- color: options.color
4042
- }),
4043
- pillCollision({
4044
- radius: options.radius,
4045
- length: options.length,
4046
- static: options.collision?.static,
4047
- sensor: options.collision?.sensor,
4048
- collisionType: options.collisionType,
4049
- collisionFilter: options.collisionFilter
4050
- })
4051
- );
4052
- return entity;
4053
- }
4054
- export {
4055
- ZylemBox,
4056
- createActor,
4057
- createBox,
4058
- createCone,
4059
- createCylinder,
4060
- createPill,
4061
- createPlane,
4062
- createPyramid,
4063
- createRect,
4064
- createSphere,
4065
- createSprite,
4066
- createText,
4067
- createZone
4068
- };
4069
- //# sourceMappingURL=entities.js.map