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