@zylem/game-lib 0.6.0 → 0.6.3
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.
- package/README.md +9 -16
- package/dist/actions.d.ts +30 -21
- package/dist/actions.js +628 -145
- package/dist/actions.js.map +1 -1
- package/dist/behavior/platformer-3d.d.ts +296 -0
- package/dist/behavior/platformer-3d.js +518 -0
- package/dist/behavior/platformer-3d.js.map +1 -0
- package/dist/behavior/ricochet-2d.d.ts +274 -0
- package/dist/behavior/ricochet-2d.js +394 -0
- package/dist/behavior/ricochet-2d.js.map +1 -0
- package/dist/behavior/screen-wrap.d.ts +86 -0
- package/dist/behavior/screen-wrap.js +195 -0
- package/dist/behavior/screen-wrap.js.map +1 -0
- package/dist/behavior/thruster.d.ts +10 -0
- package/dist/behavior/thruster.js +234 -0
- package/dist/behavior/thruster.js.map +1 -0
- package/dist/behavior/world-boundary-2d.d.ts +141 -0
- package/dist/behavior/world-boundary-2d.js +181 -0
- package/dist/behavior/world-boundary-2d.js.map +1 -0
- package/dist/behavior-descriptor-BWNWmIjv.d.ts +142 -0
- package/dist/{blueprints-BOCc3Wve.d.ts → blueprints-BWGz8fII.d.ts} +2 -2
- package/dist/camera-B5e4c78l.d.ts +468 -0
- package/dist/camera.d.ts +3 -2
- package/dist/camera.js +962 -166
- package/dist/camera.js.map +1 -1
- package/dist/composition-DrzFrbqI.d.ts +218 -0
- package/dist/{core-CZhozNRH.d.ts → core-DAkskq6Y.d.ts} +97 -65
- package/dist/core.d.ts +12 -6
- package/dist/core.js +4449 -1052
- package/dist/core.js.map +1 -1
- package/dist/{entities-BAxfJOkk.d.ts → entities-DC9ce_vx.d.ts} +154 -45
- package/dist/entities.d.ts +5 -2
- package/dist/entities.js +2505 -722
- package/dist/entities.js.map +1 -1
- package/dist/entity-BpbZqg19.d.ts +1100 -0
- package/dist/entity-types-DAu8sGJH.d.ts +26 -0
- package/dist/global-change-Dc8uCKi2.d.ts +25 -0
- package/dist/main.d.ts +472 -29
- package/dist/main.js +11877 -6124
- package/dist/main.js.map +1 -1
- package/dist/{stage-types-CD21XoIU.d.ts → stage-types-BFsm3qsZ.d.ts} +255 -26
- package/dist/stage.d.ts +11 -6
- package/dist/stage.js +3462 -491
- package/dist/stage.js.map +1 -1
- package/dist/thruster-DhRaJnoL.d.ts +172 -0
- package/dist/world-Be5m1XC1.d.ts +31 -0
- package/package.json +21 -4
- package/dist/behaviors.d.ts +0 -106
- package/dist/behaviors.js +0 -398
- package/dist/behaviors.js.map +0 -1
- package/dist/camera-CpbDr4-V.d.ts +0 -116
- package/dist/entity-COvRtFNG.d.ts +0 -395
- package/dist/moveable-B_vyA6cw.d.ts +0 -67
- package/dist/transformable-CUhvyuYO.d.ts +0 -67
package/dist/entities.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// src/lib/entities/box.ts
|
|
2
|
-
import {
|
|
3
|
-
import { BoxGeometry, Color as Color4 } from "three";
|
|
4
|
-
import { Vector3 as Vector33 } from "three";
|
|
2
|
+
import { Vector3 as Vector38 } from "three";
|
|
5
3
|
|
|
6
4
|
// src/lib/entities/entity.ts
|
|
7
|
-
import { ShaderMaterial } from "three";
|
|
5
|
+
import { Mesh, ShaderMaterial, Group } from "three";
|
|
8
6
|
|
|
9
7
|
// src/lib/systems/transformable.system.ts
|
|
10
8
|
import {
|
|
@@ -48,6 +46,9 @@ var BaseNode = class _BaseNode {
|
|
|
48
46
|
markedForRemoval = false;
|
|
49
47
|
/**
|
|
50
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`.
|
|
51
52
|
*/
|
|
52
53
|
lifecycleCallbacks = {
|
|
53
54
|
setup: [],
|
|
@@ -161,12 +162,16 @@ var BaseNode = class _BaseNode {
|
|
|
161
162
|
if (typeof this._update === "function") {
|
|
162
163
|
this._update(params);
|
|
163
164
|
}
|
|
165
|
+
if (typeof this._tickActions === "function") {
|
|
166
|
+
this._tickActions(params.delta);
|
|
167
|
+
}
|
|
164
168
|
for (const callback of this.lifecycleCallbacks.update) {
|
|
165
169
|
callback(params);
|
|
166
170
|
}
|
|
167
171
|
this.children.forEach((child) => child.nodeUpdate(params));
|
|
168
172
|
}
|
|
169
173
|
nodeDestroy(params) {
|
|
174
|
+
if (this.markedForRemoval) return;
|
|
170
175
|
this.children.forEach((child) => child.nodeDestroy(params));
|
|
171
176
|
for (const callback of this.lifecycleCallbacks.destroy) {
|
|
172
177
|
callback(params);
|
|
@@ -175,6 +180,12 @@ var BaseNode = class _BaseNode {
|
|
|
175
180
|
this._destroy(params);
|
|
176
181
|
}
|
|
177
182
|
this.markedForRemoval = true;
|
|
183
|
+
for (const callback of this.lifecycleCallbacks.cleanup) {
|
|
184
|
+
callback(params);
|
|
185
|
+
}
|
|
186
|
+
if (typeof this._cleanup === "function") {
|
|
187
|
+
this._cleanup(params);
|
|
188
|
+
}
|
|
178
189
|
}
|
|
179
190
|
async nodeLoaded(params) {
|
|
180
191
|
if (typeof this._loaded === "function") {
|
|
@@ -184,12 +195,13 @@ var BaseNode = class _BaseNode {
|
|
|
184
195
|
callback(params);
|
|
185
196
|
}
|
|
186
197
|
}
|
|
187
|
-
|
|
198
|
+
nodeCleanup(params) {
|
|
199
|
+
this.children.forEach((child) => child.nodeCleanup?.(params));
|
|
188
200
|
for (const callback of this.lifecycleCallbacks.cleanup) {
|
|
189
201
|
callback(params);
|
|
190
202
|
}
|
|
191
203
|
if (typeof this._cleanup === "function") {
|
|
192
|
-
|
|
204
|
+
this._cleanup(params);
|
|
193
205
|
}
|
|
194
206
|
}
|
|
195
207
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -203,136 +215,378 @@ var BaseNode = class _BaseNode {
|
|
|
203
215
|
}
|
|
204
216
|
};
|
|
205
217
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
};
|
|
218
|
+
// ../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs
|
|
219
|
+
function mitt_default(n) {
|
|
220
|
+
return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) {
|
|
221
|
+
var i = n.get(t);
|
|
222
|
+
i ? i.push(e) : n.set(t, [e]);
|
|
223
|
+
}, off: function(t, e) {
|
|
224
|
+
var i = n.get(t);
|
|
225
|
+
i && (e ? i.splice(i.indexOf(e) >>> 0, 1) : n.set(t, []));
|
|
226
|
+
}, emit: function(t, e) {
|
|
227
|
+
var i = n.get(t);
|
|
228
|
+
i && i.slice().map(function(n2) {
|
|
229
|
+
n2(e);
|
|
230
|
+
}), (i = n.get("*")) && i.slice().map(function(n2) {
|
|
231
|
+
n2(t, e);
|
|
232
|
+
});
|
|
233
|
+
} };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/lib/events/event-emitter-delegate.ts
|
|
237
|
+
var EventEmitterDelegate = class {
|
|
238
|
+
emitter;
|
|
239
|
+
unsubscribes = [];
|
|
229
240
|
constructor() {
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
create() {
|
|
233
|
-
const { position: setupPosition } = this.options;
|
|
234
|
-
const { x, y, z } = setupPosition || { x: 0, y: 0, z: 0 };
|
|
235
|
-
this.behaviors = [
|
|
236
|
-
{ component: position, values: { x, y, z } },
|
|
237
|
-
{ component: scale, values: { x: 0, y: 0, z: 0 } },
|
|
238
|
-
{ component: rotation, values: { x: 0, y: 0, z: 0, w: 0 } }
|
|
239
|
-
];
|
|
240
|
-
this.name = this.options.name || "";
|
|
241
|
-
return this;
|
|
241
|
+
this.emitter = mitt_default();
|
|
242
242
|
}
|
|
243
243
|
/**
|
|
244
|
-
*
|
|
244
|
+
* Dispatch an event to all listeners.
|
|
245
245
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.collisionDelegate.collision = [...existing, ...callbacks];
|
|
249
|
-
return this;
|
|
246
|
+
dispatch(event, payload) {
|
|
247
|
+
this.emitter.emit(event, payload);
|
|
250
248
|
}
|
|
251
249
|
/**
|
|
252
|
-
*
|
|
253
|
-
* (User callbacks are handled by BaseNode's lifecycleCallbacks.setup)
|
|
250
|
+
* Subscribe to an event. Returns an unsubscribe function.
|
|
254
251
|
*/
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
async _loaded(_params) {
|
|
252
|
+
listen(event, handler) {
|
|
253
|
+
this.emitter.on(event, handler);
|
|
254
|
+
const unsub = () => this.emitter.off(event, handler);
|
|
255
|
+
this.unsubscribes.push(unsub);
|
|
256
|
+
return unsub;
|
|
261
257
|
}
|
|
262
258
|
/**
|
|
263
|
-
*
|
|
264
|
-
* (User callbacks are handled by BaseNode's lifecycleCallbacks.update)
|
|
259
|
+
* Subscribe to all events.
|
|
265
260
|
*/
|
|
266
|
-
|
|
267
|
-
this.
|
|
268
|
-
this.
|
|
269
|
-
|
|
270
|
-
|
|
261
|
+
listenAll(handler) {
|
|
262
|
+
this.emitter.on("*", handler);
|
|
263
|
+
const unsub = () => this.emitter.off("*", handler);
|
|
264
|
+
this.unsubscribes.push(unsub);
|
|
265
|
+
return unsub;
|
|
271
266
|
}
|
|
272
267
|
/**
|
|
273
|
-
*
|
|
274
|
-
* (User callbacks are handled by BaseNode's lifecycleCallbacks.destroy)
|
|
268
|
+
* Clean up all subscriptions.
|
|
275
269
|
*/
|
|
276
|
-
|
|
277
|
-
this.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
async _cleanup(_params) {
|
|
282
|
-
}
|
|
283
|
-
_collision(other, globals) {
|
|
284
|
-
if (this.collisionDelegate.collision?.length) {
|
|
285
|
-
const callbacks = this.collisionDelegate.collision;
|
|
286
|
-
callbacks.forEach((callback) => {
|
|
287
|
-
callback({ entity: this, other, globals });
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
this.behaviorCallbackMap.collision.forEach((callback) => {
|
|
291
|
-
callback({ entity: this, other, globals });
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
addBehavior(behaviorCallback) {
|
|
295
|
-
const handler = behaviorCallback.handler;
|
|
296
|
-
if (handler) {
|
|
297
|
-
this.behaviorCallbackMap[behaviorCallback.type].push(handler);
|
|
298
|
-
}
|
|
299
|
-
return this;
|
|
300
|
-
}
|
|
301
|
-
addBehaviors(behaviorCallbacks) {
|
|
302
|
-
behaviorCallbacks.forEach((callback) => {
|
|
303
|
-
const handler = callback.handler;
|
|
304
|
-
if (handler) {
|
|
305
|
-
this.behaviorCallbackMap[callback.type].push(handler);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
return this;
|
|
270
|
+
dispose() {
|
|
271
|
+
this.unsubscribes.forEach((fn) => fn());
|
|
272
|
+
this.unsubscribes = [];
|
|
273
|
+
this.emitter.all.clear();
|
|
309
274
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/lib/events/zylem-events.ts
|
|
278
|
+
var zylemEventBus = mitt_default();
|
|
279
|
+
|
|
280
|
+
// src/lib/actions/capabilities/transform-store.ts
|
|
281
|
+
import { proxy } from "valtio";
|
|
282
|
+
function createTransformStore(initial) {
|
|
283
|
+
const defaultState = {
|
|
284
|
+
position: { x: 0, y: 0, z: 0 },
|
|
285
|
+
rotation: { x: 0, y: 0, z: 0, w: 1 },
|
|
286
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
287
|
+
angularVelocity: { x: 0, y: 0, z: 0 },
|
|
288
|
+
dirty: {
|
|
289
|
+
position: false,
|
|
290
|
+
rotation: false,
|
|
291
|
+
velocity: false,
|
|
292
|
+
angularVelocity: false
|
|
313
293
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
294
|
+
};
|
|
295
|
+
return proxy({
|
|
296
|
+
...defaultState,
|
|
297
|
+
...initial,
|
|
298
|
+
// Ensure dirty flags are properly initialized even if partial initial state
|
|
299
|
+
dirty: {
|
|
300
|
+
...defaultState.dirty,
|
|
301
|
+
...initial?.dirty
|
|
320
302
|
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/lib/actions/capabilities/moveable.ts
|
|
307
|
+
import { Vector3 } from "three";
|
|
308
|
+
function moveX(entity, delta) {
|
|
309
|
+
if (!entity.transformStore) return;
|
|
310
|
+
entity.transformStore.velocity.x = delta;
|
|
311
|
+
entity.transformStore.dirty.velocity = true;
|
|
312
|
+
}
|
|
313
|
+
function moveY(entity, delta) {
|
|
314
|
+
if (!entity.transformStore) return;
|
|
315
|
+
entity.transformStore.velocity.y = delta;
|
|
316
|
+
entity.transformStore.dirty.velocity = true;
|
|
317
|
+
}
|
|
318
|
+
function moveZ(entity, delta) {
|
|
319
|
+
if (!entity.transformStore) return;
|
|
320
|
+
entity.transformStore.velocity.z = delta;
|
|
321
|
+
entity.transformStore.dirty.velocity = true;
|
|
322
|
+
}
|
|
323
|
+
function moveXY(entity, deltaX, deltaY) {
|
|
324
|
+
if (!entity.transformStore) return;
|
|
325
|
+
entity.transformStore.velocity.x = deltaX;
|
|
326
|
+
entity.transformStore.velocity.y = deltaY;
|
|
327
|
+
entity.transformStore.dirty.velocity = true;
|
|
328
|
+
}
|
|
329
|
+
function moveXZ(entity, deltaX, deltaZ) {
|
|
330
|
+
if (!entity.transformStore) return;
|
|
331
|
+
entity.transformStore.velocity.x = deltaX;
|
|
332
|
+
entity.transformStore.velocity.z = deltaZ;
|
|
333
|
+
entity.transformStore.dirty.velocity = true;
|
|
334
|
+
}
|
|
335
|
+
function move(entity, vector) {
|
|
336
|
+
if (!entity.transformStore) return;
|
|
337
|
+
entity.transformStore.velocity.x += vector.x;
|
|
338
|
+
entity.transformStore.velocity.y += vector.y;
|
|
339
|
+
entity.transformStore.velocity.z += vector.z;
|
|
340
|
+
entity.transformStore.dirty.velocity = true;
|
|
341
|
+
}
|
|
342
|
+
function resetVelocity(entity) {
|
|
343
|
+
if (!entity.body) return;
|
|
344
|
+
entity.body.setLinvel(new Vector3(0, 0, 0), true);
|
|
345
|
+
entity.body.setLinearDamping(5);
|
|
346
|
+
}
|
|
347
|
+
function moveForwardXY(entity, delta, rotation2DAngle) {
|
|
348
|
+
const deltaX = Math.sin(-rotation2DAngle) * delta;
|
|
349
|
+
const deltaY = Math.cos(-rotation2DAngle) * delta;
|
|
350
|
+
moveXY(entity, deltaX, deltaY);
|
|
351
|
+
}
|
|
352
|
+
function getPosition(entity) {
|
|
353
|
+
if (!entity.body) return null;
|
|
354
|
+
return entity.body.translation();
|
|
355
|
+
}
|
|
356
|
+
function getVelocity(entity) {
|
|
357
|
+
if (!entity.body) return null;
|
|
358
|
+
return entity.body.linvel();
|
|
359
|
+
}
|
|
360
|
+
function setPosition(entity, x, y, z) {
|
|
361
|
+
if (!entity.body) return;
|
|
362
|
+
entity.body.setTranslation({ x, y, z }, true);
|
|
363
|
+
}
|
|
364
|
+
function setPositionX(entity, x) {
|
|
365
|
+
if (!entity.body) return;
|
|
366
|
+
const { y, z } = entity.body.translation();
|
|
367
|
+
entity.body.setTranslation({ x, y, z }, true);
|
|
368
|
+
}
|
|
369
|
+
function setPositionY(entity, y) {
|
|
370
|
+
if (!entity.body) return;
|
|
371
|
+
const { x, z } = entity.body.translation();
|
|
372
|
+
entity.body.setTranslation({ x, y, z }, true);
|
|
373
|
+
}
|
|
374
|
+
function setPositionZ(entity, z) {
|
|
375
|
+
if (!entity.body) return;
|
|
376
|
+
const { x, y } = entity.body.translation();
|
|
377
|
+
entity.body.setTranslation({ x, y, z }, true);
|
|
378
|
+
}
|
|
379
|
+
function wrapAroundXY(entity, boundsX, boundsY) {
|
|
380
|
+
const position2 = getPosition(entity);
|
|
381
|
+
if (!position2) return;
|
|
382
|
+
const { x, y } = position2;
|
|
383
|
+
const newX = x > boundsX ? -boundsX : x < -boundsX ? boundsX : x;
|
|
384
|
+
const newY = y > boundsY ? -boundsY : y < -boundsY ? boundsY : y;
|
|
385
|
+
if (newX !== x || newY !== y) {
|
|
386
|
+
setPosition(entity, newX, newY, 0);
|
|
321
387
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
388
|
+
}
|
|
389
|
+
function wrapAround3D(entity, boundsX, boundsY, boundsZ) {
|
|
390
|
+
const position2 = getPosition(entity);
|
|
391
|
+
if (!position2) return;
|
|
392
|
+
const { x, y, z } = position2;
|
|
393
|
+
const newX = x > boundsX ? -boundsX : x < -boundsX ? boundsX : x;
|
|
394
|
+
const newY = y > boundsY ? -boundsY : y < -boundsY ? boundsY : y;
|
|
395
|
+
const newZ = z > boundsZ ? -boundsZ : z < -boundsZ ? boundsZ : z;
|
|
396
|
+
if (newX !== x || newY !== y || newZ !== z) {
|
|
397
|
+
setPosition(entity, newX, newY, newZ);
|
|
328
398
|
}
|
|
329
|
-
}
|
|
399
|
+
}
|
|
400
|
+
function makeMoveable(entity) {
|
|
401
|
+
const moveable = entity;
|
|
402
|
+
if (!moveable.transformStore) {
|
|
403
|
+
moveable.transformStore = createTransformStore();
|
|
404
|
+
}
|
|
405
|
+
moveable.moveX = (delta) => moveX(entity, delta);
|
|
406
|
+
moveable.moveY = (delta) => moveY(entity, delta);
|
|
407
|
+
moveable.moveZ = (delta) => moveZ(entity, delta);
|
|
408
|
+
moveable.moveXY = (deltaX, deltaY) => moveXY(entity, deltaX, deltaY);
|
|
409
|
+
moveable.moveXZ = (deltaX, deltaZ) => moveXZ(entity, deltaX, deltaZ);
|
|
410
|
+
moveable.move = (vector) => move(entity, vector);
|
|
411
|
+
moveable.resetVelocity = () => resetVelocity(entity);
|
|
412
|
+
moveable.moveForwardXY = (delta, rotation2DAngle) => moveForwardXY(entity, delta, rotation2DAngle);
|
|
413
|
+
moveable.getPosition = () => getPosition(entity);
|
|
414
|
+
moveable.getVelocity = () => getVelocity(entity);
|
|
415
|
+
moveable.setPosition = (x, y, z) => setPosition(entity, x, y, z);
|
|
416
|
+
moveable.setPositionX = (x) => setPositionX(entity, x);
|
|
417
|
+
moveable.setPositionY = (y) => setPositionY(entity, y);
|
|
418
|
+
moveable.setPositionZ = (z) => setPositionZ(entity, z);
|
|
419
|
+
moveable.wrapAroundXY = (boundsX, boundsY) => wrapAroundXY(entity, boundsX, boundsY);
|
|
420
|
+
moveable.wrapAround3D = (boundsX, boundsY, boundsZ) => wrapAround3D(entity, boundsX, boundsY, boundsZ);
|
|
421
|
+
return moveable;
|
|
422
|
+
}
|
|
330
423
|
|
|
331
|
-
// src/lib/
|
|
332
|
-
import {
|
|
424
|
+
// src/lib/actions/capabilities/rotatable.ts
|
|
425
|
+
import { Euler, Vector3 as Vector32, MathUtils, Quaternion as Quaternion2 } from "three";
|
|
426
|
+
function rotateInDirection(entity, moveVector) {
|
|
427
|
+
if (!entity.body) return;
|
|
428
|
+
const rotate = Math.atan2(-moveVector.x, moveVector.z);
|
|
429
|
+
rotateYEuler(entity, rotate);
|
|
430
|
+
}
|
|
431
|
+
function rotateYEuler(entity, amount) {
|
|
432
|
+
rotateEuler(entity, new Vector32(0, -amount, 0));
|
|
433
|
+
}
|
|
434
|
+
function rotateEuler(entity, rotation2) {
|
|
435
|
+
if (!entity.group) return;
|
|
436
|
+
const euler = new Euler(rotation2.x, rotation2.y, rotation2.z);
|
|
437
|
+
entity.group.setRotationFromEuler(euler);
|
|
438
|
+
}
|
|
439
|
+
function rotateY(entity, delta) {
|
|
440
|
+
if (!entity.transformStore) return;
|
|
441
|
+
const halfAngle = delta / 2;
|
|
442
|
+
const deltaW = Math.cos(halfAngle);
|
|
443
|
+
const deltaY = Math.sin(halfAngle);
|
|
444
|
+
const q = entity.transformStore.rotation;
|
|
445
|
+
const newW = q.w * deltaW - q.y * deltaY;
|
|
446
|
+
const newX = q.x * deltaW + q.z * deltaY;
|
|
447
|
+
const newY = q.y * deltaW + q.w * deltaY;
|
|
448
|
+
const newZ = q.z * deltaW - q.x * deltaY;
|
|
449
|
+
entity.transformStore.rotation.w = newW;
|
|
450
|
+
entity.transformStore.rotation.x = newX;
|
|
451
|
+
entity.transformStore.rotation.y = newY;
|
|
452
|
+
entity.transformStore.rotation.z = newZ;
|
|
453
|
+
entity.transformStore.dirty.rotation = true;
|
|
454
|
+
}
|
|
455
|
+
function rotateX(entity, delta) {
|
|
456
|
+
if (!entity.transformStore) return;
|
|
457
|
+
const halfAngle = delta / 2;
|
|
458
|
+
const deltaW = Math.cos(halfAngle);
|
|
459
|
+
const deltaX = Math.sin(halfAngle);
|
|
460
|
+
const q = entity.transformStore.rotation;
|
|
461
|
+
const newW = q.w * deltaW - q.x * deltaX;
|
|
462
|
+
const newX = q.x * deltaW + q.w * deltaX;
|
|
463
|
+
const newY = q.y * deltaW + q.z * deltaX;
|
|
464
|
+
const newZ = q.z * deltaW - q.y * deltaX;
|
|
465
|
+
entity.transformStore.rotation.w = newW;
|
|
466
|
+
entity.transformStore.rotation.x = newX;
|
|
467
|
+
entity.transformStore.rotation.y = newY;
|
|
468
|
+
entity.transformStore.rotation.z = newZ;
|
|
469
|
+
entity.transformStore.dirty.rotation = true;
|
|
470
|
+
}
|
|
471
|
+
function rotateZ(entity, delta) {
|
|
472
|
+
if (!entity.transformStore) return;
|
|
473
|
+
const halfAngle = delta / 2;
|
|
474
|
+
const deltaW = Math.cos(halfAngle);
|
|
475
|
+
const deltaZ = Math.sin(halfAngle);
|
|
476
|
+
const q = entity.transformStore.rotation;
|
|
477
|
+
const newW = q.w * deltaW - q.z * deltaZ;
|
|
478
|
+
const newX = q.x * deltaW - q.y * deltaZ;
|
|
479
|
+
const newY = q.y * deltaW + q.x * deltaZ;
|
|
480
|
+
const newZ = q.z * deltaW + q.w * deltaZ;
|
|
481
|
+
entity.transformStore.rotation.w = newW;
|
|
482
|
+
entity.transformStore.rotation.x = newX;
|
|
483
|
+
entity.transformStore.rotation.y = newY;
|
|
484
|
+
entity.transformStore.rotation.z = newZ;
|
|
485
|
+
entity.transformStore.dirty.rotation = true;
|
|
486
|
+
}
|
|
487
|
+
function setRotationY(entity, y) {
|
|
488
|
+
if (!entity.transformStore) return;
|
|
489
|
+
const halfAngle = y / 2;
|
|
490
|
+
const w = Math.cos(halfAngle);
|
|
491
|
+
const yComponent = Math.sin(halfAngle);
|
|
492
|
+
entity.transformStore.rotation.w = w;
|
|
493
|
+
entity.transformStore.rotation.x = 0;
|
|
494
|
+
entity.transformStore.rotation.y = yComponent;
|
|
495
|
+
entity.transformStore.rotation.z = 0;
|
|
496
|
+
entity.transformStore.dirty.rotation = true;
|
|
497
|
+
}
|
|
498
|
+
function setRotationDegreesY(entity, y) {
|
|
499
|
+
if (!entity.body) return;
|
|
500
|
+
setRotationY(entity, MathUtils.degToRad(y));
|
|
501
|
+
}
|
|
502
|
+
function setRotationX(entity, x) {
|
|
503
|
+
if (!entity.transformStore) return;
|
|
504
|
+
const halfAngle = x / 2;
|
|
505
|
+
const w = Math.cos(halfAngle);
|
|
506
|
+
const xComponent = Math.sin(halfAngle);
|
|
507
|
+
entity.transformStore.rotation.w = w;
|
|
508
|
+
entity.transformStore.rotation.x = xComponent;
|
|
509
|
+
entity.transformStore.rotation.y = 0;
|
|
510
|
+
entity.transformStore.rotation.z = 0;
|
|
511
|
+
entity.transformStore.dirty.rotation = true;
|
|
512
|
+
}
|
|
513
|
+
function setRotationDegreesX(entity, x) {
|
|
514
|
+
if (!entity.body) return;
|
|
515
|
+
setRotationX(entity, MathUtils.degToRad(x));
|
|
516
|
+
}
|
|
517
|
+
function setRotationZ(entity, z) {
|
|
518
|
+
if (!entity.transformStore) return;
|
|
519
|
+
const halfAngle = z / 2;
|
|
520
|
+
const w = Math.cos(halfAngle);
|
|
521
|
+
const zComponent = Math.sin(halfAngle);
|
|
522
|
+
entity.transformStore.rotation.w = w;
|
|
523
|
+
entity.transformStore.rotation.x = 0;
|
|
524
|
+
entity.transformStore.rotation.y = 0;
|
|
525
|
+
entity.transformStore.rotation.z = zComponent;
|
|
526
|
+
entity.transformStore.dirty.rotation = true;
|
|
527
|
+
}
|
|
528
|
+
function setRotationDegreesZ(entity, z) {
|
|
529
|
+
if (!entity.body) return;
|
|
530
|
+
setRotationZ(entity, MathUtils.degToRad(z));
|
|
531
|
+
}
|
|
532
|
+
function setRotation(entity, x, y, z) {
|
|
533
|
+
if (!entity.transformStore) return;
|
|
534
|
+
const quat = new Quaternion2().setFromEuler(new Euler(x, y, z));
|
|
535
|
+
entity.transformStore.rotation.w = quat.w;
|
|
536
|
+
entity.transformStore.rotation.x = quat.x;
|
|
537
|
+
entity.transformStore.rotation.y = quat.y;
|
|
538
|
+
entity.transformStore.rotation.z = quat.z;
|
|
539
|
+
entity.transformStore.dirty.rotation = true;
|
|
540
|
+
}
|
|
541
|
+
function setRotationDegrees(entity, x, y, z) {
|
|
542
|
+
if (!entity.body) return;
|
|
543
|
+
setRotation(entity, MathUtils.degToRad(x), MathUtils.degToRad(y), MathUtils.degToRad(z));
|
|
544
|
+
}
|
|
545
|
+
function getRotation(entity) {
|
|
546
|
+
if (!entity.body) return null;
|
|
547
|
+
return entity.body.rotation();
|
|
548
|
+
}
|
|
549
|
+
function makeRotatable(entity) {
|
|
550
|
+
const rotatableEntity = entity;
|
|
551
|
+
if (!rotatableEntity.transformStore) {
|
|
552
|
+
rotatableEntity.transformStore = createTransformStore();
|
|
553
|
+
}
|
|
554
|
+
rotatableEntity.rotateInDirection = (moveVector) => rotateInDirection(entity, moveVector);
|
|
555
|
+
rotatableEntity.rotateYEuler = (amount) => rotateYEuler(entity, amount);
|
|
556
|
+
rotatableEntity.rotateEuler = (rotation2) => rotateEuler(entity, rotation2);
|
|
557
|
+
rotatableEntity.rotateX = (delta) => rotateX(entity, delta);
|
|
558
|
+
rotatableEntity.rotateY = (delta) => rotateY(entity, delta);
|
|
559
|
+
rotatableEntity.rotateZ = (delta) => rotateZ(entity, delta);
|
|
560
|
+
rotatableEntity.setRotationY = (y) => setRotationY(entity, y);
|
|
561
|
+
rotatableEntity.setRotationX = (x) => setRotationX(entity, x);
|
|
562
|
+
rotatableEntity.setRotationZ = (z) => setRotationZ(entity, z);
|
|
563
|
+
rotatableEntity.setRotationDegreesY = (y) => setRotationDegreesY(entity, y);
|
|
564
|
+
rotatableEntity.setRotationDegreesX = (x) => setRotationDegreesX(entity, x);
|
|
565
|
+
rotatableEntity.setRotationDegreesZ = (z) => setRotationDegreesZ(entity, z);
|
|
566
|
+
rotatableEntity.setRotationDegrees = (x, y, z) => setRotationDegrees(entity, x, y, z);
|
|
567
|
+
rotatableEntity.setRotation = (x, y, z) => setRotation(entity, x, y, z);
|
|
568
|
+
rotatableEntity.getRotation = () => getRotation(entity);
|
|
569
|
+
return rotatableEntity;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/lib/actions/capabilities/transformable.ts
|
|
573
|
+
function makeTransformable(entity) {
|
|
574
|
+
const withMovement = makeMoveable(entity);
|
|
575
|
+
const withRotation = makeRotatable(withMovement);
|
|
576
|
+
return withRotation;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/lib/entities/parts/collision-factories.ts
|
|
580
|
+
import {
|
|
581
|
+
ActiveCollisionTypes as ActiveCollisionTypes2,
|
|
582
|
+
ColliderDesc as ColliderDesc2,
|
|
583
|
+
RigidBodyDesc as RigidBodyDesc2,
|
|
584
|
+
RigidBodyType as RigidBodyType2
|
|
585
|
+
} from "@dimforge/rapier3d-compat";
|
|
586
|
+
import { Vector3 as Vector34 } from "three";
|
|
333
587
|
|
|
334
588
|
// src/lib/collision/collision-builder.ts
|
|
335
|
-
import { ActiveCollisionTypes, ColliderDesc, RigidBodyDesc, RigidBodyType, Vector3 } from "@dimforge/rapier3d-compat";
|
|
589
|
+
import { ActiveCollisionTypes, ColliderDesc, RigidBodyDesc, RigidBodyType, Vector3 as Vector33 } from "@dimforge/rapier3d-compat";
|
|
336
590
|
var typeToGroup = /* @__PURE__ */ new Map();
|
|
337
591
|
var nextGroupId = 0;
|
|
338
592
|
function getOrCreateCollisionGroupId(type) {
|
|
@@ -354,7 +608,7 @@ function createCollisionFilter(allowedTypes) {
|
|
|
354
608
|
var CollisionBuilder = class {
|
|
355
609
|
static = false;
|
|
356
610
|
sensor = false;
|
|
357
|
-
gravity = new
|
|
611
|
+
gravity = new Vector33(0, 0, 0);
|
|
358
612
|
build(options) {
|
|
359
613
|
const bodyDesc = this.bodyDesc({
|
|
360
614
|
isDynamicBody: !this.static
|
|
@@ -379,7 +633,7 @@ var CollisionBuilder = class {
|
|
|
379
633
|
return this;
|
|
380
634
|
}
|
|
381
635
|
collider(options) {
|
|
382
|
-
const size = options.size ?? new
|
|
636
|
+
const size = options.size ?? new Vector33(1, 1, 1);
|
|
383
637
|
const half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };
|
|
384
638
|
let colliderDesc = ColliderDesc.cuboid(half.x, half.y, half.z);
|
|
385
639
|
return colliderDesc;
|
|
@@ -391,283 +645,484 @@ var CollisionBuilder = class {
|
|
|
391
645
|
}
|
|
392
646
|
};
|
|
393
647
|
|
|
394
|
-
// src/lib/
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
mesh.receiveShadow = true;
|
|
406
|
-
return mesh;
|
|
648
|
+
// src/lib/entities/parts/collision-factories.ts
|
|
649
|
+
function isCollisionComponent(obj) {
|
|
650
|
+
return obj && obj.__kind === "collision";
|
|
651
|
+
}
|
|
652
|
+
function buildBodyDesc(isStatic) {
|
|
653
|
+
const type = isStatic ? RigidBodyType2.Fixed : RigidBodyType2.Dynamic;
|
|
654
|
+
return new RigidBodyDesc2(type).setTranslation(0, 0, 0).setGravityScale(1).setCanSleep(false).setCcdEnabled(true);
|
|
655
|
+
}
|
|
656
|
+
function applyCollisionOptions(colliderDesc, opts) {
|
|
657
|
+
if (opts.offset) {
|
|
658
|
+
colliderDesc.setTranslation(opts.offset.x, opts.offset.y, opts.offset.z);
|
|
407
659
|
}
|
|
408
|
-
|
|
409
|
-
|
|
660
|
+
if (opts.sensor) {
|
|
661
|
+
colliderDesc.setSensor(true);
|
|
662
|
+
colliderDesc.activeCollisionTypes = ActiveCollisionTypes2.KINEMATIC_FIXED;
|
|
410
663
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
RepeatWrapping,
|
|
419
|
-
ShaderMaterial as ShaderMaterial2,
|
|
420
|
-
TextureLoader,
|
|
421
|
-
Vector2,
|
|
422
|
-
Vector3 as Vector32
|
|
423
|
-
} from "three";
|
|
424
|
-
|
|
425
|
-
// src/lib/core/utility/strings.ts
|
|
426
|
-
function sortedStringify(obj) {
|
|
427
|
-
const sortedObj = Object.keys(obj).sort().reduce((acc, key) => {
|
|
428
|
-
acc[key] = obj[key];
|
|
429
|
-
return acc;
|
|
430
|
-
}, {});
|
|
431
|
-
return JSON.stringify(sortedObj);
|
|
432
|
-
}
|
|
433
|
-
function shortHash(objString) {
|
|
434
|
-
let hash = 0;
|
|
435
|
-
for (let i = 0; i < objString.length; i++) {
|
|
436
|
-
hash = Math.imul(31, hash) + objString.charCodeAt(i) | 0;
|
|
664
|
+
if (opts.collisionType) {
|
|
665
|
+
const groupId = getOrCreateCollisionGroupId(opts.collisionType);
|
|
666
|
+
let filter = 65535;
|
|
667
|
+
if (opts.collisionFilter) {
|
|
668
|
+
filter = createCollisionFilter(opts.collisionFilter);
|
|
669
|
+
}
|
|
670
|
+
colliderDesc.setCollisionGroups(groupId << 16 | filter);
|
|
437
671
|
}
|
|
438
|
-
|
|
672
|
+
}
|
|
673
|
+
function makeComponent(colliderDesc, opts) {
|
|
674
|
+
applyCollisionOptions(colliderDesc, opts);
|
|
675
|
+
return {
|
|
676
|
+
__kind: "collision",
|
|
677
|
+
bodyDesc: buildBodyDesc(opts.static ?? false),
|
|
678
|
+
colliderDesc
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function boxCollision(opts = {}) {
|
|
682
|
+
const size = opts.size ?? { x: 1, y: 1, z: 1 };
|
|
683
|
+
const desc = ColliderDesc2.cuboid(size.x / 2, size.y / 2, size.z / 2);
|
|
684
|
+
return makeComponent(desc, opts);
|
|
685
|
+
}
|
|
686
|
+
function sphereCollision(opts = {}) {
|
|
687
|
+
const desc = ColliderDesc2.ball(opts.radius ?? 1);
|
|
688
|
+
return makeComponent(desc, opts);
|
|
689
|
+
}
|
|
690
|
+
function coneCollision(opts = {}) {
|
|
691
|
+
const radius = opts.radius ?? 1;
|
|
692
|
+
const height = opts.height ?? 2;
|
|
693
|
+
const desc = ColliderDesc2.cone(height / 2, radius);
|
|
694
|
+
return makeComponent(desc, opts);
|
|
695
|
+
}
|
|
696
|
+
function pyramidCollision(opts = {}) {
|
|
697
|
+
const radius = opts.radius ?? 1;
|
|
698
|
+
const height = opts.height ?? 2;
|
|
699
|
+
const desc = ColliderDesc2.cone(height / 2, radius);
|
|
700
|
+
return makeComponent(desc, opts);
|
|
701
|
+
}
|
|
702
|
+
function cylinderCollision(opts = {}) {
|
|
703
|
+
const radius = Math.max(opts.radiusTop ?? 1, opts.radiusBottom ?? 1);
|
|
704
|
+
const height = opts.height ?? 2;
|
|
705
|
+
const desc = ColliderDesc2.cylinder(height / 2, radius);
|
|
706
|
+
return makeComponent(desc, opts);
|
|
707
|
+
}
|
|
708
|
+
function pillCollision(opts = {}) {
|
|
709
|
+
const radius = opts.radius ?? 0.5;
|
|
710
|
+
const length = opts.length ?? 1;
|
|
711
|
+
const desc = ColliderDesc2.capsule(length / 2, radius);
|
|
712
|
+
return makeComponent(desc, opts);
|
|
713
|
+
}
|
|
714
|
+
function zoneCollision(opts = {}) {
|
|
715
|
+
const size = opts.size ?? { x: 1, y: 1, z: 1 };
|
|
716
|
+
const desc = ColliderDesc2.cuboid(size.x / 2, size.y / 2, size.z / 2);
|
|
717
|
+
desc.setSensor(true);
|
|
718
|
+
desc.activeCollisionTypes = ActiveCollisionTypes2.KINEMATIC_FIXED;
|
|
719
|
+
return makeComponent(desc, { ...opts, static: opts.static ?? true, sensor: true });
|
|
439
720
|
}
|
|
440
721
|
|
|
441
|
-
// src/lib/
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// src/lib/graphics/shaders/fragment/fire.glsl
|
|
445
|
-
var fire_default = "#include <common>\n \nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 iOffset;\nvarying vec2 vUv;\n\nfloat snoise(vec3 uv, float res)\n{\n const vec3 s = vec3(1e0, 1e2, 1e3);\n \n uv *= res;\n \n vec3 uv0 = floor(mod(uv, res))*s;\n vec3 uv1 = floor(mod(uv+vec3(1.), res))*s;\n \n vec3 f = fract(uv); f = f*f*(3.0-2.0*f);\n\n vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,\n uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);\n\n vec4 r = fract(sin(v*1e-1)*1e3);\n float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);\n \n r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3);\n float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);\n \n return mix(r0, r1, f.z)*2.-1.;\n}\n\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord ) {\n vec2 p = -.5 + fragCoord.xy / iResolution.xy;\n p.x *= iResolution.x/iResolution.y;\n \n float color = 3.0 - (3.*length(2.*p));\n \n vec3 coord = vec3(atan(p.x,p.y)/6.2832+.5, length(p)*.4, .5);\n \n for(int i = 1; i <= 7; i++)\n {\n float power = pow(2.0, float(i));\n color += (1.5 / power) * snoise(coord + vec3(0.,-iTime*.05, iTime*.01), power*16.);\n }\n fragColor = vec4( color, pow(max(color,0.),2.)*0.4, pow(max(color,0.),3.)*0.15 , 1.0);\n}\n\nvoid main() {\n mainImage(gl_FragColor, vUv);\n}";
|
|
722
|
+
// src/lib/entities/common.ts
|
|
723
|
+
import { Color, Vector3 as Vector35 } from "three";
|
|
446
724
|
|
|
447
|
-
// src/lib/graphics/shaders/
|
|
448
|
-
var
|
|
725
|
+
// src/lib/graphics/shaders/vertex/object.shader.ts
|
|
726
|
+
var objectVertexShader = `
|
|
727
|
+
uniform vec2 uvScale;
|
|
728
|
+
varying vec2 vUv;
|
|
449
729
|
|
|
450
|
-
|
|
451
|
-
|
|
730
|
+
void main() {
|
|
731
|
+
vUv = uv;
|
|
732
|
+
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
733
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
734
|
+
}
|
|
735
|
+
`;
|
|
452
736
|
|
|
453
|
-
// src/lib/graphics/shaders/
|
|
454
|
-
var
|
|
737
|
+
// src/lib/graphics/shaders/standard.shader.ts
|
|
738
|
+
var fragment = `
|
|
739
|
+
uniform sampler2D tDiffuse;
|
|
740
|
+
varying vec2 vUv;
|
|
455
741
|
|
|
456
|
-
|
|
457
|
-
|
|
742
|
+
void main() {
|
|
743
|
+
vec4 texel = texture2D( tDiffuse, vUv );
|
|
458
744
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
vertex: object_shader_default
|
|
463
|
-
};
|
|
464
|
-
var fireShader = {
|
|
465
|
-
fragment: fire_default,
|
|
466
|
-
vertex: object_shader_default
|
|
467
|
-
};
|
|
745
|
+
gl_FragColor = texel;
|
|
746
|
+
}
|
|
747
|
+
`;
|
|
468
748
|
var standardShader = {
|
|
469
|
-
|
|
470
|
-
|
|
749
|
+
vertex: objectVertexShader,
|
|
750
|
+
fragment
|
|
471
751
|
};
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
752
|
+
|
|
753
|
+
// src/lib/entities/common.ts
|
|
754
|
+
var commonDefaults = {
|
|
755
|
+
position: new Vector35(0, 0, 0),
|
|
756
|
+
material: {
|
|
757
|
+
color: new Color("#ffffff"),
|
|
758
|
+
shader: standardShader
|
|
759
|
+
},
|
|
760
|
+
collision: {
|
|
761
|
+
static: false
|
|
762
|
+
}
|
|
475
763
|
};
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
764
|
+
function mergeArgs(args, defaults) {
|
|
765
|
+
const configArgs = args.filter(
|
|
766
|
+
(arg) => !(arg instanceof BaseNode)
|
|
767
|
+
);
|
|
768
|
+
let merged = { ...defaults };
|
|
769
|
+
for (const opt of configArgs) {
|
|
770
|
+
merged = { ...merged, ...opt };
|
|
771
|
+
}
|
|
772
|
+
return merged;
|
|
773
|
+
}
|
|
482
774
|
|
|
483
|
-
// src/lib/
|
|
484
|
-
var
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
775
|
+
// src/lib/entities/entity.ts
|
|
776
|
+
var GameEntity = class extends BaseNode {
|
|
777
|
+
behaviors = [];
|
|
778
|
+
group;
|
|
779
|
+
mesh;
|
|
780
|
+
materials;
|
|
781
|
+
bodyDesc = null;
|
|
782
|
+
body = null;
|
|
783
|
+
colliderDesc;
|
|
784
|
+
collider;
|
|
785
|
+
custom = {};
|
|
786
|
+
// Compound entity support: multiple colliders and meshes
|
|
787
|
+
/** All collider descriptions for this entity (including primary) */
|
|
788
|
+
colliderDescs = [];
|
|
789
|
+
/** All colliders attached to this entity's rigid body */
|
|
790
|
+
colliders = [];
|
|
791
|
+
/** Additional meshes for compound visual entities (added via add()) */
|
|
792
|
+
compoundMeshes = [];
|
|
793
|
+
debugInfo = {};
|
|
794
|
+
debugMaterial;
|
|
795
|
+
collisionDelegate = {
|
|
796
|
+
collision: []
|
|
797
|
+
};
|
|
798
|
+
collisionType;
|
|
799
|
+
// Instancing support
|
|
800
|
+
/** Batch key for instanced rendering (null if not instanced) */
|
|
801
|
+
batchKey = null;
|
|
802
|
+
/** Index within the instanced mesh batch */
|
|
803
|
+
instanceId = -1;
|
|
804
|
+
/** Whether this entity uses instanced rendering */
|
|
805
|
+
isInstanced = false;
|
|
806
|
+
// Event delegate for dispatch/listen API
|
|
807
|
+
eventDelegate = new EventEmitterDelegate();
|
|
808
|
+
// Behavior references (new ECS pattern)
|
|
809
|
+
behaviorRefs = [];
|
|
810
|
+
// Transform store for batched physics updates (auto-created in create())
|
|
811
|
+
transformStore;
|
|
812
|
+
// Movement & rotation methods are assigned at runtime by makeTransformable.
|
|
813
|
+
// The `implements` clause above ensures the type contract; declarations below
|
|
814
|
+
// satisfy the compiler while the constructor fills them in.
|
|
815
|
+
moveX;
|
|
816
|
+
moveY;
|
|
817
|
+
moveZ;
|
|
818
|
+
moveXY;
|
|
819
|
+
moveXZ;
|
|
820
|
+
move;
|
|
821
|
+
resetVelocity;
|
|
822
|
+
moveForwardXY;
|
|
823
|
+
getPosition;
|
|
824
|
+
getVelocity;
|
|
825
|
+
setPosition;
|
|
826
|
+
setPositionX;
|
|
827
|
+
setPositionY;
|
|
828
|
+
setPositionZ;
|
|
829
|
+
wrapAroundXY;
|
|
830
|
+
wrapAround3D;
|
|
831
|
+
rotateInDirection;
|
|
832
|
+
rotateYEuler;
|
|
833
|
+
rotateEuler;
|
|
834
|
+
rotateX;
|
|
835
|
+
rotateY;
|
|
836
|
+
rotateZ;
|
|
837
|
+
setRotationY;
|
|
838
|
+
setRotationX;
|
|
839
|
+
setRotationZ;
|
|
840
|
+
setRotationDegrees;
|
|
841
|
+
setRotationDegreesY;
|
|
842
|
+
setRotationDegreesX;
|
|
843
|
+
setRotationDegreesZ;
|
|
844
|
+
setRotation;
|
|
845
|
+
getRotation;
|
|
846
|
+
constructor() {
|
|
847
|
+
super();
|
|
848
|
+
this.transformStore = createTransformStore();
|
|
849
|
+
makeTransformable(this);
|
|
850
|
+
}
|
|
851
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
852
|
+
// Composable add() - accepts Mesh, CollisionComponent, or child entities
|
|
853
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
854
|
+
/**
|
|
855
|
+
* Add meshes, collision components, or child entities to this entity.
|
|
856
|
+
* Supports fluent chaining: `entity.add(boxMesh()).add(boxCollision())`.
|
|
857
|
+
*
|
|
858
|
+
* - `Mesh`: First mesh becomes the primary mesh; subsequent meshes are
|
|
859
|
+
* compound meshes grouped together.
|
|
860
|
+
* - `CollisionComponent`: First sets bodyDesc + colliderDesc; subsequent
|
|
861
|
+
* add extra colliders to the same rigid body.
|
|
862
|
+
* - `NodeInterface`: Added as a child entity (delegates to BaseNode.add).
|
|
863
|
+
*/
|
|
864
|
+
add(...components) {
|
|
865
|
+
for (const component of components) {
|
|
866
|
+
if (component instanceof Mesh) {
|
|
867
|
+
this.addMeshComponent(component);
|
|
868
|
+
} else if (isCollisionComponent(component)) {
|
|
869
|
+
this.addCollisionComponent(component);
|
|
494
870
|
} else {
|
|
495
|
-
|
|
871
|
+
super.add(component);
|
|
496
872
|
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
873
|
+
}
|
|
874
|
+
return this;
|
|
875
|
+
}
|
|
876
|
+
addMeshComponent(mesh) {
|
|
877
|
+
if (!this.mesh) {
|
|
878
|
+
this.mesh = mesh;
|
|
879
|
+
if (!this.materials) {
|
|
880
|
+
this.materials = [];
|
|
881
|
+
}
|
|
882
|
+
if (mesh.material) {
|
|
883
|
+
const mat = mesh.material;
|
|
884
|
+
if (Array.isArray(mat)) {
|
|
885
|
+
this.materials.push(...mat);
|
|
886
|
+
} else {
|
|
887
|
+
this.materials.push(mat);
|
|
503
888
|
}
|
|
504
|
-
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
this.compoundMeshes.push(mesh);
|
|
892
|
+
if (!this.group) {
|
|
893
|
+
this.group = new Group();
|
|
894
|
+
this.group.add(this.mesh);
|
|
895
|
+
}
|
|
896
|
+
this.group.add(mesh);
|
|
505
897
|
}
|
|
506
898
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
this.
|
|
899
|
+
addCollisionComponent(collision) {
|
|
900
|
+
if (!this.bodyDesc) {
|
|
901
|
+
this.bodyDesc = collision.bodyDesc;
|
|
902
|
+
this.colliderDesc = collision.colliderDesc;
|
|
903
|
+
this.colliderDescs.push(collision.colliderDesc);
|
|
904
|
+
const pos = this.options?.position ?? { x: 0, y: 0, z: 0 };
|
|
905
|
+
this.bodyDesc.setTranslation(pos.x, pos.y, pos.z);
|
|
906
|
+
} else {
|
|
907
|
+
this.colliderDescs.push(collision.colliderDesc);
|
|
514
908
|
}
|
|
515
|
-
this.batchMaterial(options, entityType);
|
|
516
909
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
910
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
911
|
+
// Actions API -- entity-scoped, self-contained stateful actions
|
|
912
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
913
|
+
_actions = [];
|
|
914
|
+
/**
|
|
915
|
+
* Run a fire-and-forget action. Auto-removed when done.
|
|
916
|
+
* @example `me.runAction(moveBy({ x: 10, duration: 0.3 }))`
|
|
917
|
+
*/
|
|
918
|
+
runAction(action) {
|
|
919
|
+
this._actions.push(action);
|
|
920
|
+
return action;
|
|
520
921
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
922
|
+
/**
|
|
923
|
+
* Register a persistent action (throttle, onPress). Not removed when done.
|
|
924
|
+
* @example `const press = entity.action(onPress())`
|
|
925
|
+
*/
|
|
926
|
+
action(action) {
|
|
927
|
+
action.persistent = true;
|
|
928
|
+
this._actions.push(action);
|
|
929
|
+
return action;
|
|
524
930
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
931
|
+
/**
|
|
932
|
+
* Tick all registered actions. Called automatically before user onUpdate callbacks.
|
|
933
|
+
*
|
|
934
|
+
* Resets velocity/angularVelocity accumulation before ticking so that
|
|
935
|
+
* actions can compose via `+=` without cross-frame build-up.
|
|
936
|
+
* (The existing move helpers like `moveXY` use `=` which doesn't accumulate,
|
|
937
|
+
* but action composition needs additive writes on a clean slate each frame.)
|
|
938
|
+
*/
|
|
939
|
+
_tickActions(delta) {
|
|
940
|
+
if (this._actions.length === 0) return;
|
|
941
|
+
const store = this.transformStore;
|
|
942
|
+
store.velocity.x = 0;
|
|
943
|
+
store.velocity.y = 0;
|
|
944
|
+
store.velocity.z = 0;
|
|
945
|
+
store.dirty.velocity = false;
|
|
946
|
+
store.angularVelocity.x = 0;
|
|
947
|
+
store.angularVelocity.y = 0;
|
|
948
|
+
store.angularVelocity.z = 0;
|
|
949
|
+
store.dirty.angularVelocity = false;
|
|
950
|
+
for (let i = this._actions.length - 1; i >= 0; i--) {
|
|
951
|
+
const act = this._actions[i];
|
|
952
|
+
act.tick(this, delta);
|
|
953
|
+
if (act.done && !act.persistent) {
|
|
954
|
+
this._actions.splice(i, 1);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (this._actions.length === 0) {
|
|
958
|
+
store.velocity.x = 0;
|
|
959
|
+
store.velocity.y = 0;
|
|
960
|
+
store.velocity.z = 0;
|
|
961
|
+
store.dirty.velocity = false;
|
|
962
|
+
store.angularVelocity.x = 0;
|
|
963
|
+
store.angularVelocity.y = 0;
|
|
964
|
+
store.angularVelocity.z = 0;
|
|
965
|
+
store.dirty.angularVelocity = false;
|
|
528
966
|
}
|
|
529
|
-
const loader = new TextureLoader();
|
|
530
|
-
const texture = await loader.loadAsync(texturePath);
|
|
531
|
-
texture.repeat = repeat;
|
|
532
|
-
texture.wrapS = RepeatWrapping;
|
|
533
|
-
texture.wrapT = RepeatWrapping;
|
|
534
|
-
const material = new MeshPhongMaterial({
|
|
535
|
-
map: texture
|
|
536
|
-
});
|
|
537
|
-
this.materials.push(material);
|
|
538
|
-
}
|
|
539
|
-
setColor(color) {
|
|
540
|
-
const material = new MeshStandardMaterial({
|
|
541
|
-
color,
|
|
542
|
-
emissiveIntensity: 0.5,
|
|
543
|
-
lightMapIntensity: 0.5,
|
|
544
|
-
fog: true
|
|
545
|
-
});
|
|
546
|
-
this.materials.push(material);
|
|
547
|
-
}
|
|
548
|
-
setShader(customShader) {
|
|
549
|
-
const { fragment, vertex } = preset_shader_default.get(customShader) ?? preset_shader_default.get("standard");
|
|
550
|
-
const shader = new ShaderMaterial2({
|
|
551
|
-
uniforms: {
|
|
552
|
-
iResolution: { value: new Vector32(1, 1, 1) },
|
|
553
|
-
iTime: { value: 0 },
|
|
554
|
-
tDiffuse: { value: null },
|
|
555
|
-
tDepth: { value: null },
|
|
556
|
-
tNormal: { value: null }
|
|
557
|
-
},
|
|
558
|
-
vertexShader: vertex,
|
|
559
|
-
fragmentShader: fragment
|
|
560
|
-
});
|
|
561
|
-
this.materials.push(shader);
|
|
562
967
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
968
|
+
create() {
|
|
969
|
+
const { position: setupPosition } = this.options;
|
|
970
|
+
const { x, y, z } = setupPosition || { x: 0, y: 0, z: 0 };
|
|
971
|
+
this.behaviors = [
|
|
972
|
+
{ component: position, values: { x, y, z } },
|
|
973
|
+
{ component: scale, values: { x: 0, y: 0, z: 0 } },
|
|
974
|
+
{ component: rotation, values: { x: 0, y: 0, z: 0, w: 0 } }
|
|
975
|
+
];
|
|
976
|
+
this.name = this.options.name || "";
|
|
977
|
+
return this;
|
|
571
978
|
}
|
|
572
|
-
|
|
573
|
-
|
|
979
|
+
/**
|
|
980
|
+
* Add collision callbacks
|
|
981
|
+
*/
|
|
982
|
+
onCollision(...callbacks) {
|
|
983
|
+
const existing = this.collisionDelegate.collision ?? [];
|
|
984
|
+
this.collisionDelegate.collision = [...existing, ...callbacks];
|
|
985
|
+
return this;
|
|
574
986
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
this.
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
987
|
+
/**
|
|
988
|
+
* Use a behavior on this entity via typed descriptor.
|
|
989
|
+
* Behaviors will be auto-registered as systems when the entity is spawned.
|
|
990
|
+
* @param descriptor The behavior descriptor (import from behaviors module)
|
|
991
|
+
* @param options Optional overrides for the behavior's default options
|
|
992
|
+
* @returns BehaviorHandle with behavior-specific methods for lazy FSM access
|
|
993
|
+
*/
|
|
994
|
+
use(descriptor, options) {
|
|
995
|
+
const behaviorRef = {
|
|
996
|
+
descriptor,
|
|
997
|
+
options: { ...descriptor.defaultOptions, ...options }
|
|
998
|
+
};
|
|
999
|
+
this.behaviorRefs.push(behaviorRef);
|
|
1000
|
+
const baseHandle = {
|
|
1001
|
+
getFSM: () => behaviorRef.fsm ?? null,
|
|
1002
|
+
getOptions: () => behaviorRef.options,
|
|
1003
|
+
ref: behaviorRef
|
|
1004
|
+
};
|
|
1005
|
+
const customMethods = descriptor.createHandle?.(behaviorRef) ?? {};
|
|
1006
|
+
return {
|
|
1007
|
+
...baseHandle,
|
|
1008
|
+
...customMethods
|
|
592
1009
|
};
|
|
593
|
-
this.options._builders = builders;
|
|
594
1010
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1011
|
+
/**
|
|
1012
|
+
* Get all behavior references attached to this entity.
|
|
1013
|
+
* Used by the stage to auto-register required systems.
|
|
1014
|
+
*/
|
|
1015
|
+
getBehaviorRefs() {
|
|
1016
|
+
return this.behaviorRefs;
|
|
598
1017
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1018
|
+
/**
|
|
1019
|
+
* Entity-specific setup - resets actions for a fresh stage session.
|
|
1020
|
+
* (User callbacks are handled by BaseNode's lifecycleCallbacks.setup)
|
|
1021
|
+
*/
|
|
1022
|
+
_setup(params) {
|
|
1023
|
+
for (let i = this._actions.length - 1; i >= 0; i--) {
|
|
1024
|
+
const act = this._actions[i];
|
|
1025
|
+
if (act.done && !act.persistent) {
|
|
1026
|
+
this._actions.splice(i, 1);
|
|
1027
|
+
} else {
|
|
1028
|
+
act.reset();
|
|
1029
|
+
}
|
|
602
1030
|
}
|
|
603
|
-
return this;
|
|
604
1031
|
}
|
|
605
|
-
|
|
606
|
-
group.traverse((child) => {
|
|
607
|
-
if (child instanceof Mesh3) {
|
|
608
|
-
if (child.type === "SkinnedMesh" && materials[0] && !child.material.map) {
|
|
609
|
-
child.material = materials[0];
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
child.castShadow = true;
|
|
613
|
-
child.receiveShadow = true;
|
|
614
|
-
});
|
|
1032
|
+
async _loaded(_params) {
|
|
615
1033
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1034
|
+
/**
|
|
1035
|
+
* Entity-specific update - updates materials.
|
|
1036
|
+
* Transform changes are applied by the stage after all update callbacks complete.
|
|
1037
|
+
* (User callbacks are handled by BaseNode's lifecycleCallbacks.update)
|
|
1038
|
+
*/
|
|
1039
|
+
_update(params) {
|
|
1040
|
+
this.updateMaterials(params);
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Entity-specific destroy -- reserved for consumer game logic.
|
|
1044
|
+
* Engine-internal resource disposal runs in _cleanup() instead.
|
|
1045
|
+
*/
|
|
1046
|
+
_destroy(params) {
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Engine-internal resource cleanup -- runs automatically after destroy.
|
|
1050
|
+
* Disposes GPU/DOM resources (meshes, materials, debug material).
|
|
1051
|
+
*
|
|
1052
|
+
* Note: actions, collision callbacks, and behavior refs are intentionally
|
|
1053
|
+
* NOT cleared here -- they are registered by consumer code at module level
|
|
1054
|
+
* and must persist across stage reloads. Actions are reset in _setup().
|
|
1055
|
+
*/
|
|
1056
|
+
_cleanup(_params) {
|
|
1057
|
+
this.disposeEvents();
|
|
1058
|
+
for (const m of this.compoundMeshes) {
|
|
1059
|
+
m.geometry?.dispose();
|
|
1060
|
+
if (Array.isArray(m.material)) {
|
|
1061
|
+
m.material.forEach((mat) => mat.dispose());
|
|
1062
|
+
} else {
|
|
1063
|
+
m.material?.dispose();
|
|
1064
|
+
}
|
|
628
1065
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
1066
|
+
this.compoundMeshes.length = 0;
|
|
1067
|
+
this.debugMaterial?.dispose();
|
|
1068
|
+
this.debugMaterial = void 0;
|
|
1069
|
+
this.group?.removeFromParent();
|
|
1070
|
+
}
|
|
1071
|
+
_collision(other, globals) {
|
|
1072
|
+
if (this.collisionDelegate.collision?.length) {
|
|
1073
|
+
const callbacks = this.collisionDelegate.collision;
|
|
1074
|
+
callbacks.forEach((callback) => {
|
|
1075
|
+
callback({ entity: this, other, globals });
|
|
1076
|
+
});
|
|
636
1077
|
}
|
|
637
|
-
|
|
638
|
-
|
|
1078
|
+
}
|
|
1079
|
+
updateMaterials(params) {
|
|
1080
|
+
if (!this.materials?.length) {
|
|
1081
|
+
return;
|
|
639
1082
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
anyMat.color.set(this.options.color);
|
|
1083
|
+
for (const material of this.materials) {
|
|
1084
|
+
if (material instanceof ShaderMaterial) {
|
|
1085
|
+
if (material.uniforms) {
|
|
1086
|
+
material.uniforms.iTime && (material.uniforms.iTime.value += params.delta);
|
|
645
1087
|
}
|
|
646
|
-
};
|
|
647
|
-
if (entity.materials?.length) {
|
|
648
|
-
for (const mat of entity.materials) applyColor(mat);
|
|
649
|
-
}
|
|
650
|
-
if (entity.mesh && entity.mesh.material) {
|
|
651
|
-
const mat = entity.mesh.material;
|
|
652
|
-
if (Array.isArray(mat)) mat.forEach(applyColor);
|
|
653
|
-
else applyColor(mat);
|
|
654
|
-
}
|
|
655
|
-
if (entity.group) {
|
|
656
|
-
entity.group.traverse((child) => {
|
|
657
|
-
if (child instanceof Mesh3 && child.material) {
|
|
658
|
-
const mat = child.material;
|
|
659
|
-
if (Array.isArray(mat)) mat.forEach(applyColor);
|
|
660
|
-
else applyColor(mat);
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
1088
|
}
|
|
664
1089
|
}
|
|
665
|
-
|
|
1090
|
+
}
|
|
1091
|
+
buildInfo() {
|
|
1092
|
+
const info = {};
|
|
1093
|
+
info.name = this.name;
|
|
1094
|
+
info.uuid = this.uuid;
|
|
1095
|
+
info.eid = this.eid.toString();
|
|
1096
|
+
return info;
|
|
1097
|
+
}
|
|
1098
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1099
|
+
// Events API
|
|
1100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1101
|
+
/**
|
|
1102
|
+
* Dispatch an event from this entity.
|
|
1103
|
+
* Events are emitted both locally and to the global event bus.
|
|
1104
|
+
*/
|
|
1105
|
+
dispatch(event, payload) {
|
|
1106
|
+
this.eventDelegate.dispatch(event, payload);
|
|
1107
|
+
zylemEventBus.emit(event, payload);
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Listen for events on this entity instance.
|
|
1111
|
+
* @returns Unsubscribe function
|
|
1112
|
+
*/
|
|
1113
|
+
listen(event, handler) {
|
|
1114
|
+
return this.eventDelegate.listen(event, handler);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Clean up entity event subscriptions.
|
|
1118
|
+
*/
|
|
1119
|
+
disposeEvents() {
|
|
1120
|
+
this.eventDelegate.dispose();
|
|
666
1121
|
}
|
|
667
1122
|
};
|
|
668
1123
|
|
|
669
1124
|
// src/lib/entities/delegates/debug.ts
|
|
670
|
-
import { MeshStandardMaterial
|
|
1125
|
+
import { MeshStandardMaterial, MeshBasicMaterial, MeshPhongMaterial } from "three";
|
|
671
1126
|
function hasDebugInfo(obj) {
|
|
672
1127
|
return obj && typeof obj.getDebugInfo === "function";
|
|
673
1128
|
}
|
|
@@ -711,7 +1166,7 @@ var DebugDelegate = class {
|
|
|
711
1166
|
const info = {
|
|
712
1167
|
type: material.type
|
|
713
1168
|
};
|
|
714
|
-
if (material instanceof
|
|
1169
|
+
if (material instanceof MeshStandardMaterial || material instanceof MeshBasicMaterial || material instanceof MeshPhongMaterial) {
|
|
715
1170
|
info.color = `#${material.color.getHexString()}`;
|
|
716
1171
|
info.opacity = material.opacity;
|
|
717
1172
|
info.transparent = material.transparent;
|
|
@@ -761,110 +1216,959 @@ var DebugDelegate = class {
|
|
|
761
1216
|
}
|
|
762
1217
|
};
|
|
763
1218
|
|
|
764
|
-
// src/lib/entities/
|
|
765
|
-
|
|
766
|
-
|
|
1219
|
+
// src/lib/entities/parts/mesh-factories.ts
|
|
1220
|
+
import {
|
|
1221
|
+
BoxGeometry,
|
|
1222
|
+
CapsuleGeometry,
|
|
1223
|
+
ConeGeometry,
|
|
1224
|
+
CylinderGeometry,
|
|
1225
|
+
Mesh as Mesh2,
|
|
1226
|
+
MeshStandardMaterial as MeshStandardMaterial3,
|
|
1227
|
+
SphereGeometry
|
|
1228
|
+
} from "three";
|
|
1229
|
+
|
|
1230
|
+
// src/lib/graphics/material.ts
|
|
1231
|
+
import { Color as Color3, Vector2 as Vector22, Vector3 as Vector37 } from "three";
|
|
1232
|
+
import {
|
|
1233
|
+
MeshPhongMaterial as MeshPhongMaterial2,
|
|
1234
|
+
MeshStandardMaterial as MeshStandardMaterial2,
|
|
1235
|
+
RepeatWrapping as RepeatWrapping2,
|
|
1236
|
+
ShaderMaterial as ShaderMaterial2
|
|
1237
|
+
} from "three";
|
|
1238
|
+
import {
|
|
1239
|
+
MeshBasicNodeMaterial,
|
|
1240
|
+
MeshStandardNodeMaterial
|
|
1241
|
+
} from "three/webgpu";
|
|
1242
|
+
|
|
1243
|
+
// src/lib/core/utility/strings.ts
|
|
1244
|
+
function sortedStringify(obj) {
|
|
1245
|
+
const sortedObj = Object.keys(obj).sort().reduce((acc, key) => {
|
|
1246
|
+
acc[key] = obj[key];
|
|
1247
|
+
return acc;
|
|
1248
|
+
}, {});
|
|
1249
|
+
return JSON.stringify(sortedObj);
|
|
767
1250
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1251
|
+
function shortHash(objString) {
|
|
1252
|
+
let hash = 0;
|
|
1253
|
+
for (let i = 0; i < objString.length; i++) {
|
|
1254
|
+
hash = Math.imul(31, hash) + objString.charCodeAt(i) | 0;
|
|
772
1255
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1256
|
+
return hash.toString(36);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/lib/core/asset-manager.ts
|
|
1260
|
+
import { LoadingManager, Cache } from "three";
|
|
1261
|
+
|
|
1262
|
+
// src/lib/core/loaders/texture-loader.ts
|
|
1263
|
+
import { TextureLoader, RepeatWrapping } from "three";
|
|
1264
|
+
var TextureLoaderAdapter = class {
|
|
1265
|
+
loader;
|
|
1266
|
+
constructor() {
|
|
1267
|
+
this.loader = new TextureLoader();
|
|
777
1268
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1269
|
+
isSupported(url) {
|
|
1270
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1271
|
+
return ["png", "jpg", "jpeg", "gif", "webp", "bmp", "tga"].includes(ext || "");
|
|
1272
|
+
}
|
|
1273
|
+
async load(url, options) {
|
|
1274
|
+
const texture = await this.loader.loadAsync(url, (event) => {
|
|
1275
|
+
if (options?.onProgress && event.lengthComputable) {
|
|
1276
|
+
options.onProgress(event.loaded / event.total);
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
if (options?.repeat) {
|
|
1280
|
+
texture.repeat.copy(options.repeat);
|
|
781
1281
|
}
|
|
782
|
-
|
|
1282
|
+
texture.wrapS = options?.wrapS ?? RepeatWrapping;
|
|
1283
|
+
texture.wrapT = options?.wrapT ?? RepeatWrapping;
|
|
1284
|
+
return texture;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Clone a texture for independent usage
|
|
1288
|
+
*/
|
|
1289
|
+
clone(texture) {
|
|
1290
|
+
const cloned = texture.clone();
|
|
1291
|
+
cloned.needsUpdate = true;
|
|
1292
|
+
return cloned;
|
|
783
1293
|
}
|
|
784
1294
|
};
|
|
785
1295
|
|
|
786
|
-
// src/lib/
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
BuilderClass,
|
|
793
|
-
entityType,
|
|
794
|
-
MeshBuilderClass,
|
|
795
|
-
CollisionBuilderClass
|
|
796
|
-
} = params;
|
|
797
|
-
let builder = null;
|
|
798
|
-
let configuration;
|
|
799
|
-
const configurationIndex = args.findIndex((node) => !(node instanceof BaseNode));
|
|
800
|
-
if (configurationIndex !== -1) {
|
|
801
|
-
const subArgs = args.splice(configurationIndex, 1);
|
|
802
|
-
configuration = subArgs.find((node) => !(node instanceof BaseNode));
|
|
1296
|
+
// src/lib/core/loaders/gltf-loader.ts
|
|
1297
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
1298
|
+
var GLTFLoaderAdapter = class {
|
|
1299
|
+
loader;
|
|
1300
|
+
constructor() {
|
|
1301
|
+
this.loader = new GLTFLoader();
|
|
803
1302
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1303
|
+
isSupported(url) {
|
|
1304
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1305
|
+
return ["gltf", "glb"].includes(ext || "");
|
|
1306
|
+
}
|
|
1307
|
+
async load(url, options) {
|
|
1308
|
+
if (options?.useAsyncFetch) {
|
|
1309
|
+
return this.loadWithAsyncFetch(url, options);
|
|
809
1310
|
}
|
|
810
|
-
|
|
811
|
-
|
|
1311
|
+
return this.loadMainThread(url, options);
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Load using native fetch + parseAsync
|
|
1315
|
+
* Both fetch and parsing are async, keeping the main thread responsive
|
|
1316
|
+
*/
|
|
1317
|
+
async loadWithAsyncFetch(url, options) {
|
|
812
1318
|
try {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
entityData = await loader.data();
|
|
1319
|
+
const response = await fetch(url);
|
|
1320
|
+
if (!response.ok) {
|
|
1321
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
817
1322
|
}
|
|
1323
|
+
const buffer = await response.arrayBuffer();
|
|
1324
|
+
const gltf = await this.loader.parseAsync(buffer, url);
|
|
1325
|
+
return {
|
|
1326
|
+
object: gltf.scene,
|
|
1327
|
+
animations: gltf.animations,
|
|
1328
|
+
gltf
|
|
1329
|
+
};
|
|
818
1330
|
} catch (error) {
|
|
819
|
-
console.error(
|
|
1331
|
+
console.error(`Async fetch GLTF load failed for ${url}, falling back to loader.load():`, error);
|
|
1332
|
+
return this.loadMainThread(url, options);
|
|
820
1333
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1334
|
+
}
|
|
1335
|
+
async loadMainThread(url, options) {
|
|
1336
|
+
return new Promise((resolve, reject) => {
|
|
1337
|
+
this.loader.load(
|
|
1338
|
+
url,
|
|
1339
|
+
(gltf) => {
|
|
1340
|
+
resolve({
|
|
1341
|
+
object: gltf.scene,
|
|
1342
|
+
animations: gltf.animations,
|
|
1343
|
+
gltf
|
|
1344
|
+
});
|
|
1345
|
+
},
|
|
1346
|
+
(event) => {
|
|
1347
|
+
if (options?.onProgress && event.lengthComputable) {
|
|
1348
|
+
options.onProgress(event.loaded / event.total);
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
(error) => reject(error)
|
|
1352
|
+
);
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Clone a loaded GLTF scene for reuse
|
|
1357
|
+
*/
|
|
1358
|
+
clone(result) {
|
|
1359
|
+
return {
|
|
1360
|
+
object: result.object.clone(),
|
|
1361
|
+
animations: result.animations?.map((anim) => anim.clone()),
|
|
1362
|
+
gltf: result.gltf
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
// src/lib/core/loaders/fbx-loader.ts
|
|
1368
|
+
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
|
|
1369
|
+
var FBXLoaderAdapter = class {
|
|
1370
|
+
loader;
|
|
1371
|
+
constructor() {
|
|
1372
|
+
this.loader = new FBXLoader();
|
|
1373
|
+
}
|
|
1374
|
+
isSupported(url) {
|
|
1375
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1376
|
+
return ext === "fbx";
|
|
1377
|
+
}
|
|
1378
|
+
async load(url, options) {
|
|
1379
|
+
if (options?.useAsyncFetch) {
|
|
1380
|
+
return this.loadWithAsyncFetch(url, options);
|
|
829
1381
|
}
|
|
1382
|
+
return this.loadMainThread(url, options);
|
|
830
1383
|
}
|
|
831
|
-
|
|
832
|
-
|
|
1384
|
+
/**
|
|
1385
|
+
* Load using native fetch + parse
|
|
1386
|
+
*/
|
|
1387
|
+
async loadWithAsyncFetch(url, _options) {
|
|
1388
|
+
try {
|
|
1389
|
+
const response = await fetch(url);
|
|
1390
|
+
if (!response.ok) {
|
|
1391
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
1392
|
+
}
|
|
1393
|
+
const buffer = await response.arrayBuffer();
|
|
1394
|
+
const object = this.loader.parse(buffer, url);
|
|
1395
|
+
return {
|
|
1396
|
+
object,
|
|
1397
|
+
animations: object.animations || []
|
|
1398
|
+
};
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
console.error(`Async fetch FBX load failed for ${url}, falling back to loader.load():`, error);
|
|
1401
|
+
return this.loadMainThread(url, _options);
|
|
1402
|
+
}
|
|
833
1403
|
}
|
|
834
|
-
|
|
835
|
-
|
|
1404
|
+
async loadMainThread(url, options) {
|
|
1405
|
+
return new Promise((resolve, reject) => {
|
|
1406
|
+
this.loader.load(
|
|
1407
|
+
url,
|
|
1408
|
+
(object) => {
|
|
1409
|
+
resolve({
|
|
1410
|
+
object,
|
|
1411
|
+
animations: object.animations || []
|
|
1412
|
+
});
|
|
1413
|
+
},
|
|
1414
|
+
(event) => {
|
|
1415
|
+
if (options?.onProgress && event.lengthComputable) {
|
|
1416
|
+
options.onProgress(event.loaded / event.total);
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
(error) => reject(error)
|
|
1420
|
+
);
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Clone a loaded FBX object for reuse
|
|
1425
|
+
*/
|
|
1426
|
+
clone(result) {
|
|
1427
|
+
return {
|
|
1428
|
+
object: result.object.clone(),
|
|
1429
|
+
animations: result.animations?.map((anim) => anim.clone())
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
836
1433
|
|
|
837
|
-
// src/lib/
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1434
|
+
// src/lib/core/loaders/obj-loader.ts
|
|
1435
|
+
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
|
|
1436
|
+
import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
|
|
1437
|
+
var OBJLoaderAdapter = class {
|
|
1438
|
+
loader;
|
|
1439
|
+
mtlLoader;
|
|
1440
|
+
constructor() {
|
|
1441
|
+
this.loader = new OBJLoader();
|
|
1442
|
+
this.mtlLoader = new MTLLoader();
|
|
1443
|
+
}
|
|
1444
|
+
isSupported(url) {
|
|
1445
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1446
|
+
return ext === "obj";
|
|
1447
|
+
}
|
|
1448
|
+
async load(url, options) {
|
|
1449
|
+
if (options?.mtlPath) {
|
|
1450
|
+
await this.loadMTL(options.mtlPath);
|
|
1451
|
+
}
|
|
1452
|
+
return new Promise((resolve, reject) => {
|
|
1453
|
+
this.loader.load(
|
|
1454
|
+
url,
|
|
1455
|
+
(object) => {
|
|
1456
|
+
resolve({
|
|
1457
|
+
object,
|
|
1458
|
+
animations: []
|
|
1459
|
+
});
|
|
1460
|
+
},
|
|
1461
|
+
(event) => {
|
|
1462
|
+
if (options?.onProgress && event.lengthComputable) {
|
|
1463
|
+
options.onProgress(event.loaded / event.total);
|
|
1464
|
+
}
|
|
1465
|
+
},
|
|
1466
|
+
(error) => reject(error)
|
|
1467
|
+
);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
async loadMTL(url) {
|
|
1471
|
+
return new Promise((resolve, reject) => {
|
|
1472
|
+
this.mtlLoader.load(
|
|
1473
|
+
url,
|
|
1474
|
+
(materials) => {
|
|
1475
|
+
materials.preload();
|
|
1476
|
+
this.loader.setMaterials(materials);
|
|
1477
|
+
resolve();
|
|
1478
|
+
},
|
|
1479
|
+
void 0,
|
|
1480
|
+
(error) => reject(error)
|
|
1481
|
+
);
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Clone a loaded OBJ object for reuse
|
|
1486
|
+
*/
|
|
1487
|
+
clone(result) {
|
|
1488
|
+
return {
|
|
1489
|
+
object: result.object.clone(),
|
|
1490
|
+
animations: []
|
|
1491
|
+
};
|
|
847
1492
|
}
|
|
848
1493
|
};
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1494
|
+
|
|
1495
|
+
// src/lib/core/loaders/audio-loader.ts
|
|
1496
|
+
import { AudioLoader } from "three";
|
|
1497
|
+
var AudioLoaderAdapter = class {
|
|
1498
|
+
loader;
|
|
1499
|
+
constructor() {
|
|
1500
|
+
this.loader = new AudioLoader();
|
|
1501
|
+
}
|
|
1502
|
+
isSupported(url) {
|
|
1503
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1504
|
+
return ["mp3", "ogg", "wav", "flac", "aac", "m4a"].includes(ext || "");
|
|
1505
|
+
}
|
|
1506
|
+
async load(url, options) {
|
|
1507
|
+
return new Promise((resolve, reject) => {
|
|
1508
|
+
this.loader.load(
|
|
1509
|
+
url,
|
|
1510
|
+
(buffer) => resolve(buffer),
|
|
1511
|
+
(event) => {
|
|
1512
|
+
if (options?.onProgress && event.lengthComputable) {
|
|
1513
|
+
options.onProgress(event.loaded / event.total);
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
(error) => reject(error)
|
|
1517
|
+
);
|
|
1518
|
+
});
|
|
855
1519
|
}
|
|
856
1520
|
};
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1521
|
+
|
|
1522
|
+
// src/lib/core/loaders/file-loader.ts
|
|
1523
|
+
import { FileLoader } from "three";
|
|
1524
|
+
var FileLoaderAdapter = class {
|
|
1525
|
+
loader;
|
|
1526
|
+
constructor() {
|
|
1527
|
+
this.loader = new FileLoader();
|
|
1528
|
+
}
|
|
1529
|
+
isSupported(_url) {
|
|
1530
|
+
return true;
|
|
1531
|
+
}
|
|
1532
|
+
async load(url, options) {
|
|
1533
|
+
const responseType = options?.responseType ?? "text";
|
|
1534
|
+
this.loader.setResponseType(responseType);
|
|
1535
|
+
return new Promise((resolve, reject) => {
|
|
1536
|
+
this.loader.load(
|
|
1537
|
+
url,
|
|
1538
|
+
(data) => resolve(data),
|
|
1539
|
+
(event) => {
|
|
1540
|
+
if (options?.onProgress && event.lengthComputable) {
|
|
1541
|
+
options.onProgress(event.loaded / event.total);
|
|
1542
|
+
}
|
|
1543
|
+
},
|
|
1544
|
+
(error) => reject(error)
|
|
1545
|
+
);
|
|
1546
|
+
});
|
|
861
1547
|
}
|
|
862
1548
|
};
|
|
863
|
-
var
|
|
864
|
-
|
|
865
|
-
|
|
1549
|
+
var JsonLoaderAdapter = class {
|
|
1550
|
+
fileLoader;
|
|
1551
|
+
constructor() {
|
|
1552
|
+
this.fileLoader = new FileLoaderAdapter();
|
|
1553
|
+
}
|
|
1554
|
+
isSupported(url) {
|
|
1555
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1556
|
+
return ext === "json";
|
|
1557
|
+
}
|
|
1558
|
+
async load(url, options) {
|
|
1559
|
+
const data = await this.fileLoader.load(url, { ...options, responseType: "json" });
|
|
1560
|
+
return data;
|
|
866
1561
|
}
|
|
867
1562
|
};
|
|
1563
|
+
|
|
1564
|
+
// src/lib/core/asset-manager.ts
|
|
1565
|
+
var AssetManager = class _AssetManager {
|
|
1566
|
+
static instance = null;
|
|
1567
|
+
// Caches for different asset types
|
|
1568
|
+
textureCache = /* @__PURE__ */ new Map();
|
|
1569
|
+
modelCache = /* @__PURE__ */ new Map();
|
|
1570
|
+
audioCache = /* @__PURE__ */ new Map();
|
|
1571
|
+
fileCache = /* @__PURE__ */ new Map();
|
|
1572
|
+
jsonCache = /* @__PURE__ */ new Map();
|
|
1573
|
+
// Loaders
|
|
1574
|
+
textureLoader;
|
|
1575
|
+
gltfLoader;
|
|
1576
|
+
fbxLoader;
|
|
1577
|
+
objLoader;
|
|
1578
|
+
audioLoader;
|
|
1579
|
+
fileLoader;
|
|
1580
|
+
jsonLoader;
|
|
1581
|
+
// Loading manager for progress tracking
|
|
1582
|
+
loadingManager;
|
|
1583
|
+
// Event emitter
|
|
1584
|
+
events;
|
|
1585
|
+
// Stats
|
|
1586
|
+
stats = {
|
|
1587
|
+
texturesLoaded: 0,
|
|
1588
|
+
modelsLoaded: 0,
|
|
1589
|
+
audioLoaded: 0,
|
|
1590
|
+
filesLoaded: 0,
|
|
1591
|
+
cacheHits: 0,
|
|
1592
|
+
cacheMisses: 0
|
|
1593
|
+
};
|
|
1594
|
+
constructor() {
|
|
1595
|
+
this.textureLoader = new TextureLoaderAdapter();
|
|
1596
|
+
this.gltfLoader = new GLTFLoaderAdapter();
|
|
1597
|
+
this.fbxLoader = new FBXLoaderAdapter();
|
|
1598
|
+
this.objLoader = new OBJLoaderAdapter();
|
|
1599
|
+
this.audioLoader = new AudioLoaderAdapter();
|
|
1600
|
+
this.fileLoader = new FileLoaderAdapter();
|
|
1601
|
+
this.jsonLoader = new JsonLoaderAdapter();
|
|
1602
|
+
this.loadingManager = new LoadingManager();
|
|
1603
|
+
this.loadingManager.onProgress = (url, loaded, total) => {
|
|
1604
|
+
this.events.emit("batch:progress", { loaded, total });
|
|
1605
|
+
};
|
|
1606
|
+
this.events = mitt_default();
|
|
1607
|
+
Cache.enabled = true;
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Get the singleton instance
|
|
1611
|
+
*/
|
|
1612
|
+
static getInstance() {
|
|
1613
|
+
if (!_AssetManager.instance) {
|
|
1614
|
+
_AssetManager.instance = new _AssetManager();
|
|
1615
|
+
}
|
|
1616
|
+
return _AssetManager.instance;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Reset the singleton (useful for testing)
|
|
1620
|
+
*/
|
|
1621
|
+
static resetInstance() {
|
|
1622
|
+
if (_AssetManager.instance) {
|
|
1623
|
+
_AssetManager.instance.clearCache();
|
|
1624
|
+
_AssetManager.instance = null;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
// ==================== TEXTURE LOADING ====================
|
|
1628
|
+
/**
|
|
1629
|
+
* Load a texture with caching
|
|
1630
|
+
*/
|
|
1631
|
+
async loadTexture(url, options) {
|
|
1632
|
+
return this.loadWithCache(
|
|
1633
|
+
url,
|
|
1634
|
+
"texture" /* TEXTURE */,
|
|
1635
|
+
this.textureCache,
|
|
1636
|
+
() => this.textureLoader.load(url, options),
|
|
1637
|
+
options,
|
|
1638
|
+
(texture) => {
|
|
1639
|
+
if (!options?.clone) return texture;
|
|
1640
|
+
const cloned = this.textureLoader.clone(texture);
|
|
1641
|
+
if (options.repeat) {
|
|
1642
|
+
cloned.repeat.copy(options.repeat);
|
|
1643
|
+
}
|
|
1644
|
+
if (options.wrapS !== void 0) {
|
|
1645
|
+
cloned.wrapS = options.wrapS;
|
|
1646
|
+
}
|
|
1647
|
+
if (options.wrapT !== void 0) {
|
|
1648
|
+
cloned.wrapT = options.wrapT;
|
|
1649
|
+
}
|
|
1650
|
+
return cloned;
|
|
1651
|
+
}
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
// ==================== MODEL LOADING ====================
|
|
1655
|
+
/**
|
|
1656
|
+
* Load a GLTF/GLB model with caching
|
|
1657
|
+
*/
|
|
1658
|
+
async loadGLTF(url, options) {
|
|
1659
|
+
return this.loadWithCache(
|
|
1660
|
+
url,
|
|
1661
|
+
"gltf" /* GLTF */,
|
|
1662
|
+
this.modelCache,
|
|
1663
|
+
() => this.gltfLoader.load(url, options),
|
|
1664
|
+
options,
|
|
1665
|
+
(result) => options?.clone ? this.gltfLoader.clone(result) : result
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Load an FBX model with caching
|
|
1670
|
+
*/
|
|
1671
|
+
async loadFBX(url, options) {
|
|
1672
|
+
return this.loadWithCache(
|
|
1673
|
+
url,
|
|
1674
|
+
"fbx" /* FBX */,
|
|
1675
|
+
this.modelCache,
|
|
1676
|
+
() => this.fbxLoader.load(url, options),
|
|
1677
|
+
options,
|
|
1678
|
+
(result) => options?.clone ? this.fbxLoader.clone(result) : result
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Load an OBJ model with caching
|
|
1683
|
+
*/
|
|
1684
|
+
async loadOBJ(url, options) {
|
|
1685
|
+
const cacheKey = options?.mtlPath ? `${url}:${options.mtlPath}` : url;
|
|
1686
|
+
return this.loadWithCache(
|
|
1687
|
+
cacheKey,
|
|
1688
|
+
"obj" /* OBJ */,
|
|
1689
|
+
this.modelCache,
|
|
1690
|
+
() => this.objLoader.load(url, options),
|
|
1691
|
+
options,
|
|
1692
|
+
(result) => options?.clone ? this.objLoader.clone(result) : result
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Auto-detect model type and load
|
|
1697
|
+
*/
|
|
1698
|
+
async loadModel(url, options) {
|
|
1699
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1700
|
+
switch (ext) {
|
|
1701
|
+
case "gltf":
|
|
1702
|
+
case "glb":
|
|
1703
|
+
return this.loadGLTF(url, options);
|
|
1704
|
+
case "fbx":
|
|
1705
|
+
return this.loadFBX(url, options);
|
|
1706
|
+
case "obj":
|
|
1707
|
+
return this.loadOBJ(url, options);
|
|
1708
|
+
default:
|
|
1709
|
+
throw new Error(`Unsupported model format: ${ext}`);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
// ==================== AUDIO LOADING ====================
|
|
1713
|
+
/**
|
|
1714
|
+
* Load an audio buffer with caching
|
|
1715
|
+
*/
|
|
1716
|
+
async loadAudio(url, options) {
|
|
1717
|
+
return this.loadWithCache(
|
|
1718
|
+
url,
|
|
1719
|
+
"audio" /* AUDIO */,
|
|
1720
|
+
this.audioCache,
|
|
1721
|
+
() => this.audioLoader.load(url, options),
|
|
1722
|
+
options
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
// ==================== FILE LOADING ====================
|
|
1726
|
+
/**
|
|
1727
|
+
* Load a raw file with caching
|
|
1728
|
+
*/
|
|
1729
|
+
async loadFile(url, options) {
|
|
1730
|
+
const cacheKey = options?.responseType ? `${url}:${options.responseType}` : url;
|
|
1731
|
+
return this.loadWithCache(
|
|
1732
|
+
cacheKey,
|
|
1733
|
+
"file" /* FILE */,
|
|
1734
|
+
this.fileCache,
|
|
1735
|
+
() => this.fileLoader.load(url, options),
|
|
1736
|
+
options
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Load a JSON file with caching
|
|
1741
|
+
*/
|
|
1742
|
+
async loadJSON(url, options) {
|
|
1743
|
+
return this.loadWithCache(
|
|
1744
|
+
url,
|
|
1745
|
+
"json" /* JSON */,
|
|
1746
|
+
this.jsonCache,
|
|
1747
|
+
() => this.jsonLoader.load(url, options),
|
|
1748
|
+
options
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
// ==================== BATCH LOADING ====================
|
|
1752
|
+
/**
|
|
1753
|
+
* Load multiple assets in parallel
|
|
1754
|
+
*/
|
|
1755
|
+
async loadBatch(items) {
|
|
1756
|
+
const results = /* @__PURE__ */ new Map();
|
|
1757
|
+
const promises = items.map(async (item) => {
|
|
1758
|
+
try {
|
|
1759
|
+
let result;
|
|
1760
|
+
switch (item.type) {
|
|
1761
|
+
case "texture" /* TEXTURE */:
|
|
1762
|
+
result = await this.loadTexture(item.url, item.options);
|
|
1763
|
+
break;
|
|
1764
|
+
case "gltf" /* GLTF */:
|
|
1765
|
+
result = await this.loadGLTF(item.url, item.options);
|
|
1766
|
+
break;
|
|
1767
|
+
case "fbx" /* FBX */:
|
|
1768
|
+
result = await this.loadFBX(item.url, item.options);
|
|
1769
|
+
break;
|
|
1770
|
+
case "obj" /* OBJ */:
|
|
1771
|
+
result = await this.loadOBJ(item.url, item.options);
|
|
1772
|
+
break;
|
|
1773
|
+
case "audio" /* AUDIO */:
|
|
1774
|
+
result = await this.loadAudio(item.url, item.options);
|
|
1775
|
+
break;
|
|
1776
|
+
case "file" /* FILE */:
|
|
1777
|
+
result = await this.loadFile(item.url, item.options);
|
|
1778
|
+
break;
|
|
1779
|
+
case "json" /* JSON */:
|
|
1780
|
+
result = await this.loadJSON(item.url, item.options);
|
|
1781
|
+
break;
|
|
1782
|
+
default:
|
|
1783
|
+
throw new Error(`Unknown asset type: ${item.type}`);
|
|
1784
|
+
}
|
|
1785
|
+
results.set(item.url, result);
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
this.events.emit("asset:error", {
|
|
1788
|
+
url: item.url,
|
|
1789
|
+
type: item.type,
|
|
1790
|
+
error
|
|
1791
|
+
});
|
|
1792
|
+
throw error;
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
await Promise.all(promises);
|
|
1796
|
+
this.events.emit("batch:complete", { urls: items.map((i) => i.url) });
|
|
1797
|
+
return results;
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Preload assets without returning results
|
|
1801
|
+
*/
|
|
1802
|
+
async preload(items) {
|
|
1803
|
+
await this.loadBatch(items);
|
|
1804
|
+
}
|
|
1805
|
+
// ==================== CACHE MANAGEMENT ====================
|
|
1806
|
+
/**
|
|
1807
|
+
* Check if an asset is cached
|
|
1808
|
+
*/
|
|
1809
|
+
isCached(url) {
|
|
1810
|
+
return this.textureCache.has(url) || this.modelCache.has(url) || this.audioCache.has(url) || this.fileCache.has(url) || this.jsonCache.has(url);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Clear all caches or a specific URL
|
|
1814
|
+
*/
|
|
1815
|
+
clearCache(url) {
|
|
1816
|
+
if (url) {
|
|
1817
|
+
this.textureCache.delete(url);
|
|
1818
|
+
this.modelCache.delete(url);
|
|
1819
|
+
this.audioCache.delete(url);
|
|
1820
|
+
this.fileCache.delete(url);
|
|
1821
|
+
this.jsonCache.delete(url);
|
|
1822
|
+
} else {
|
|
1823
|
+
this.textureCache.clear();
|
|
1824
|
+
this.modelCache.clear();
|
|
1825
|
+
this.audioCache.clear();
|
|
1826
|
+
this.fileCache.clear();
|
|
1827
|
+
this.jsonCache.clear();
|
|
1828
|
+
Cache.clear();
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Get cache statistics
|
|
1833
|
+
*/
|
|
1834
|
+
getStats() {
|
|
1835
|
+
return { ...this.stats };
|
|
1836
|
+
}
|
|
1837
|
+
// ==================== EVENTS ====================
|
|
1838
|
+
/**
|
|
1839
|
+
* Subscribe to asset manager events
|
|
1840
|
+
*/
|
|
1841
|
+
on(event, handler) {
|
|
1842
|
+
this.events.on(event, handler);
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Unsubscribe from asset manager events
|
|
1846
|
+
*/
|
|
1847
|
+
off(event, handler) {
|
|
1848
|
+
this.events.off(event, handler);
|
|
1849
|
+
}
|
|
1850
|
+
// ==================== PRIVATE HELPERS ====================
|
|
1851
|
+
/**
|
|
1852
|
+
* Generic cache wrapper for loading assets
|
|
1853
|
+
*/
|
|
1854
|
+
async loadWithCache(url, type, cache, loader, options, cloner) {
|
|
1855
|
+
if (options?.forceReload) {
|
|
1856
|
+
cache.delete(url);
|
|
1857
|
+
}
|
|
1858
|
+
const cached = cache.get(url);
|
|
1859
|
+
if (cached) {
|
|
1860
|
+
this.stats.cacheHits++;
|
|
1861
|
+
this.events.emit("asset:loaded", { url, type, fromCache: true });
|
|
1862
|
+
const result = await cached.promise;
|
|
1863
|
+
return cloner ? cloner(result) : result;
|
|
1864
|
+
}
|
|
1865
|
+
this.stats.cacheMisses++;
|
|
1866
|
+
this.events.emit("asset:loading", { url, type });
|
|
1867
|
+
const promise = loader();
|
|
1868
|
+
const entry = {
|
|
1869
|
+
promise,
|
|
1870
|
+
loadedAt: Date.now()
|
|
1871
|
+
};
|
|
1872
|
+
cache.set(url, entry);
|
|
1873
|
+
try {
|
|
1874
|
+
const result = await promise;
|
|
1875
|
+
entry.result = result;
|
|
1876
|
+
this.updateStats(type);
|
|
1877
|
+
this.events.emit("asset:loaded", { url, type, fromCache: false });
|
|
1878
|
+
return cloner ? cloner(result) : result;
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
cache.delete(url);
|
|
1881
|
+
this.events.emit("asset:error", { url, type, error });
|
|
1882
|
+
throw error;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
updateStats(type) {
|
|
1886
|
+
switch (type) {
|
|
1887
|
+
case "texture" /* TEXTURE */:
|
|
1888
|
+
this.stats.texturesLoaded++;
|
|
1889
|
+
break;
|
|
1890
|
+
case "gltf" /* GLTF */:
|
|
1891
|
+
case "fbx" /* FBX */:
|
|
1892
|
+
case "obj" /* OBJ */:
|
|
1893
|
+
this.stats.modelsLoaded++;
|
|
1894
|
+
break;
|
|
1895
|
+
case "audio" /* AUDIO */:
|
|
1896
|
+
this.stats.audioLoaded++;
|
|
1897
|
+
break;
|
|
1898
|
+
case "file" /* FILE */:
|
|
1899
|
+
case "json" /* JSON */:
|
|
1900
|
+
this.stats.filesLoaded++;
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
var assetManager = AssetManager.getInstance();
|
|
1906
|
+
|
|
1907
|
+
// src/lib/graphics/material.ts
|
|
1908
|
+
import {
|
|
1909
|
+
uniform,
|
|
1910
|
+
uv,
|
|
1911
|
+
time,
|
|
1912
|
+
vec3,
|
|
1913
|
+
vec4,
|
|
1914
|
+
float,
|
|
1915
|
+
Fn
|
|
1916
|
+
} from "three/tsl";
|
|
1917
|
+
function isTSLShader(shader) {
|
|
1918
|
+
return "colorNode" in shader;
|
|
1919
|
+
}
|
|
1920
|
+
function isGLSLShader(shader) {
|
|
1921
|
+
return "fragment" in shader && "vertex" in shader;
|
|
1922
|
+
}
|
|
1923
|
+
var MaterialBuilder = class _MaterialBuilder {
|
|
1924
|
+
static batchMaterialMap = /* @__PURE__ */ new Map();
|
|
1925
|
+
materials = [];
|
|
1926
|
+
/** Whether to use TSL/NodeMaterial (for WebGPU compatibility) */
|
|
1927
|
+
useTSL;
|
|
1928
|
+
constructor(useTSL = false) {
|
|
1929
|
+
this.useTSL = useTSL;
|
|
1930
|
+
}
|
|
1931
|
+
batchMaterial(options, entityType) {
|
|
1932
|
+
const batchKey = shortHash(sortedStringify(options));
|
|
1933
|
+
const mappedObject = _MaterialBuilder.batchMaterialMap.get(batchKey);
|
|
1934
|
+
if (mappedObject) {
|
|
1935
|
+
const count = mappedObject.geometryMap.get(entityType);
|
|
1936
|
+
if (count) {
|
|
1937
|
+
mappedObject.geometryMap.set(entityType, count + 1);
|
|
1938
|
+
} else {
|
|
1939
|
+
mappedObject.geometryMap.set(entityType, 1);
|
|
1940
|
+
}
|
|
1941
|
+
} else {
|
|
1942
|
+
_MaterialBuilder.batchMaterialMap.set(batchKey, {
|
|
1943
|
+
geometryMap: /* @__PURE__ */ new Map([[entityType, 1]]),
|
|
1944
|
+
material: this.materials[0]
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
build(options, entityType) {
|
|
1949
|
+
const { path, normalMap, repeat, color, shader, useTSL } = options;
|
|
1950
|
+
const shouldUseTSL = useTSL ?? this.useTSL;
|
|
1951
|
+
if (shader) {
|
|
1952
|
+
if (isTSLShader(shader)) {
|
|
1953
|
+
this.setTSLShader(shader);
|
|
1954
|
+
} else if (isGLSLShader(shader)) {
|
|
1955
|
+
if (shouldUseTSL) {
|
|
1956
|
+
console.warn("MaterialBuilder: GLSL shader provided but TSL mode requested. Using GLSL.");
|
|
1957
|
+
}
|
|
1958
|
+
this.setShader(shader);
|
|
1959
|
+
}
|
|
1960
|
+
} else if (path) {
|
|
1961
|
+
this.setTexture(path, repeat, shouldUseTSL);
|
|
1962
|
+
}
|
|
1963
|
+
if (color) {
|
|
1964
|
+
this.withColor(color, shouldUseTSL);
|
|
1965
|
+
}
|
|
1966
|
+
if (this.materials.length === 0) {
|
|
1967
|
+
this.setColor(new Color3("#ffffff"), shouldUseTSL);
|
|
1968
|
+
}
|
|
1969
|
+
if (normalMap && this.materials.length > 0) {
|
|
1970
|
+
this.setNormalMap(normalMap, repeat);
|
|
1971
|
+
}
|
|
1972
|
+
this.batchMaterial(options, entityType);
|
|
1973
|
+
}
|
|
1974
|
+
withColor(color, useTSL = false) {
|
|
1975
|
+
this.setColor(color, useTSL);
|
|
1976
|
+
return this;
|
|
1977
|
+
}
|
|
1978
|
+
withShader(shader) {
|
|
1979
|
+
this.setShader(shader);
|
|
1980
|
+
return this;
|
|
1981
|
+
}
|
|
1982
|
+
withTSLShader(shader) {
|
|
1983
|
+
this.setTSLShader(shader);
|
|
1984
|
+
return this;
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Set texture - loads in background (deferred).
|
|
1988
|
+
* Material is created immediately with null map, texture applies when loaded.
|
|
1989
|
+
*/
|
|
1990
|
+
setTexture(texturePath = null, repeat = new Vector22(1, 1), useTSL = false) {
|
|
1991
|
+
if (!texturePath) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
if (useTSL) {
|
|
1995
|
+
const material = new MeshStandardNodeMaterial();
|
|
1996
|
+
this.materials.push(material);
|
|
1997
|
+
assetManager.loadTexture(texturePath, {
|
|
1998
|
+
clone: true,
|
|
1999
|
+
repeat
|
|
2000
|
+
}).then((texture) => {
|
|
2001
|
+
texture.wrapS = RepeatWrapping2;
|
|
2002
|
+
texture.wrapT = RepeatWrapping2;
|
|
2003
|
+
material.map = texture;
|
|
2004
|
+
material.needsUpdate = true;
|
|
2005
|
+
});
|
|
2006
|
+
} else {
|
|
2007
|
+
const material = new MeshPhongMaterial2({
|
|
2008
|
+
map: null
|
|
2009
|
+
});
|
|
2010
|
+
this.materials.push(material);
|
|
2011
|
+
assetManager.loadTexture(texturePath, {
|
|
2012
|
+
clone: true,
|
|
2013
|
+
repeat
|
|
2014
|
+
}).then((texture) => {
|
|
2015
|
+
texture.wrapS = RepeatWrapping2;
|
|
2016
|
+
texture.wrapT = RepeatWrapping2;
|
|
2017
|
+
material.map = texture;
|
|
2018
|
+
material.needsUpdate = true;
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Set normal map for the current material
|
|
2024
|
+
*/
|
|
2025
|
+
setNormalMap(normalMapPath, repeat = new Vector22(1, 1)) {
|
|
2026
|
+
const material = this.materials[this.materials.length - 1];
|
|
2027
|
+
if (!material) return;
|
|
2028
|
+
assetManager.loadTexture(normalMapPath, {
|
|
2029
|
+
clone: true,
|
|
2030
|
+
repeat
|
|
2031
|
+
}).then((texture) => {
|
|
2032
|
+
texture.wrapS = RepeatWrapping2;
|
|
2033
|
+
texture.wrapT = RepeatWrapping2;
|
|
2034
|
+
if (material instanceof MeshStandardMaterial2 || material instanceof MeshPhongMaterial2 || material instanceof MeshStandardNodeMaterial) {
|
|
2035
|
+
material.normalMap = texture;
|
|
2036
|
+
material.needsUpdate = true;
|
|
2037
|
+
} else if (material instanceof ShaderMaterial2) {
|
|
2038
|
+
if (material.uniforms.tNormal) {
|
|
2039
|
+
material.uniforms.tNormal.value = texture;
|
|
2040
|
+
material.needsUpdate = true;
|
|
2041
|
+
}
|
|
2042
|
+
if (material.uniforms.normalMap) {
|
|
2043
|
+
material.uniforms.normalMap.value = texture;
|
|
2044
|
+
material.needsUpdate = true;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
setColor(color, useTSL = false) {
|
|
2050
|
+
if (useTSL) {
|
|
2051
|
+
const material = new MeshStandardNodeMaterial();
|
|
2052
|
+
material.color = color;
|
|
2053
|
+
this.materials.push(material);
|
|
2054
|
+
} else {
|
|
2055
|
+
const material = new MeshStandardMaterial2({
|
|
2056
|
+
color,
|
|
2057
|
+
emissiveIntensity: 0.5,
|
|
2058
|
+
lightMapIntensity: 0.5,
|
|
2059
|
+
fog: true
|
|
2060
|
+
});
|
|
2061
|
+
this.materials.push(material);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Set GLSL shader (WebGL only)
|
|
2066
|
+
*/
|
|
2067
|
+
setShader(customShader) {
|
|
2068
|
+
const { fragment: fragment2, vertex } = customShader ?? standardShader;
|
|
2069
|
+
const shader = new ShaderMaterial2({
|
|
2070
|
+
uniforms: {
|
|
2071
|
+
iResolution: { value: new Vector37(1, 1, 1) },
|
|
2072
|
+
iTime: { value: 0 },
|
|
2073
|
+
tDiffuse: { value: null },
|
|
2074
|
+
tDepth: { value: null },
|
|
2075
|
+
tNormal: { value: null },
|
|
2076
|
+
normalMap: { value: null },
|
|
2077
|
+
lightDir: { value: new Vector37(1, 1, 1) },
|
|
2078
|
+
normalStrength: { value: 1 }
|
|
2079
|
+
},
|
|
2080
|
+
vertexShader: vertex,
|
|
2081
|
+
fragmentShader: fragment2,
|
|
2082
|
+
transparent: true
|
|
2083
|
+
});
|
|
2084
|
+
this.materials.push(shader);
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Set TSL shader (WebGPU compatible)
|
|
2088
|
+
*/
|
|
2089
|
+
setTSLShader(tslShader) {
|
|
2090
|
+
const material = new MeshBasicNodeMaterial();
|
|
2091
|
+
material.colorNode = tslShader.colorNode;
|
|
2092
|
+
if (tslShader.transparent) {
|
|
2093
|
+
material.transparent = true;
|
|
2094
|
+
}
|
|
2095
|
+
this.materials.push(material);
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
|
|
2099
|
+
// src/lib/entities/parts/mesh-factories.ts
|
|
2100
|
+
function buildMaterial(opts, entityType = /* @__PURE__ */ Symbol("mesh")) {
|
|
2101
|
+
if (opts.material || opts.color) {
|
|
2102
|
+
const builder = new MaterialBuilder();
|
|
2103
|
+
builder.build(
|
|
2104
|
+
{ ...opts.material, color: opts.color ?? opts.material?.color },
|
|
2105
|
+
entityType
|
|
2106
|
+
);
|
|
2107
|
+
if (builder.materials.length > 0) {
|
|
2108
|
+
return builder.materials;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
return [new MeshStandardMaterial3()];
|
|
2112
|
+
}
|
|
2113
|
+
function finalizeMesh(mesh, opts) {
|
|
2114
|
+
if (opts.position) {
|
|
2115
|
+
mesh.position.set(opts.position.x, opts.position.y, opts.position.z);
|
|
2116
|
+
}
|
|
2117
|
+
mesh.castShadow = true;
|
|
2118
|
+
mesh.receiveShadow = true;
|
|
2119
|
+
return mesh;
|
|
2120
|
+
}
|
|
2121
|
+
function boxMesh(opts = {}) {
|
|
2122
|
+
const size = opts.size ?? { x: 1, y: 1, z: 1 };
|
|
2123
|
+
const geometry = new BoxGeometry(size.x, size.y, size.z);
|
|
2124
|
+
const materials = buildMaterial(opts);
|
|
2125
|
+
return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
|
|
2126
|
+
}
|
|
2127
|
+
function sphereMesh(opts = {}) {
|
|
2128
|
+
const geometry = new SphereGeometry(opts.radius ?? 1);
|
|
2129
|
+
const materials = buildMaterial(opts);
|
|
2130
|
+
return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
|
|
2131
|
+
}
|
|
2132
|
+
function coneMesh(opts = {}) {
|
|
2133
|
+
const geometry = new ConeGeometry(
|
|
2134
|
+
opts.radius ?? 1,
|
|
2135
|
+
opts.height ?? 2,
|
|
2136
|
+
opts.radialSegments ?? 32
|
|
2137
|
+
);
|
|
2138
|
+
const materials = buildMaterial(opts);
|
|
2139
|
+
return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
|
|
2140
|
+
}
|
|
2141
|
+
function pyramidMesh(opts = {}) {
|
|
2142
|
+
const geometry = new ConeGeometry(opts.radius ?? 1, opts.height ?? 2, 4);
|
|
2143
|
+
const materials = buildMaterial(opts);
|
|
2144
|
+
return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
|
|
2145
|
+
}
|
|
2146
|
+
function cylinderMesh(opts = {}) {
|
|
2147
|
+
const geometry = new CylinderGeometry(
|
|
2148
|
+
opts.radiusTop ?? 1,
|
|
2149
|
+
opts.radiusBottom ?? 1,
|
|
2150
|
+
opts.height ?? 2,
|
|
2151
|
+
opts.radialSegments ?? 32
|
|
2152
|
+
);
|
|
2153
|
+
const materials = buildMaterial(opts);
|
|
2154
|
+
return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
|
|
2155
|
+
}
|
|
2156
|
+
function pillMesh(opts = {}) {
|
|
2157
|
+
const geometry = new CapsuleGeometry(
|
|
2158
|
+
opts.radius ?? 0.5,
|
|
2159
|
+
opts.length ?? 1,
|
|
2160
|
+
opts.capSegments ?? 10,
|
|
2161
|
+
opts.radialSegments ?? 20
|
|
2162
|
+
);
|
|
2163
|
+
const materials = buildMaterial(opts);
|
|
2164
|
+
return finalizeMesh(new Mesh2(geometry, materials.at(-1)), opts);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// src/lib/entities/box.ts
|
|
2168
|
+
var boxDefaults = {
|
|
2169
|
+
...commonDefaults,
|
|
2170
|
+
size: new Vector38(1, 1, 1)
|
|
2171
|
+
};
|
|
868
2172
|
var BOX_TYPE = /* @__PURE__ */ Symbol("Box");
|
|
869
2173
|
var ZylemBox = class _ZylemBox extends GameEntity {
|
|
870
2174
|
static type = BOX_TYPE;
|
|
@@ -883,107 +2187,282 @@ var ZylemBox = class _ZylemBox extends GameEntity {
|
|
|
883
2187
|
};
|
|
884
2188
|
}
|
|
885
2189
|
};
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
2190
|
+
function createBox(...args) {
|
|
2191
|
+
const options = mergeArgs(args, boxDefaults);
|
|
2192
|
+
const entity = new ZylemBox(options);
|
|
2193
|
+
entity.add(
|
|
2194
|
+
boxMesh({ size: options.size, material: options.material, color: options.color }),
|
|
2195
|
+
boxCollision({
|
|
2196
|
+
size: options.size,
|
|
2197
|
+
static: options.collision?.static,
|
|
2198
|
+
sensor: options.collision?.sensor,
|
|
2199
|
+
collisionType: options.collisionType,
|
|
2200
|
+
collisionFilter: options.collisionFilter
|
|
2201
|
+
})
|
|
2202
|
+
);
|
|
2203
|
+
return entity;
|
|
896
2204
|
}
|
|
897
2205
|
|
|
898
2206
|
// src/lib/entities/sphere.ts
|
|
899
|
-
import { ColliderDesc as ColliderDesc3 } from "@dimforge/rapier3d-compat";
|
|
900
|
-
import { Color as Color5, SphereGeometry } from "three";
|
|
901
|
-
import { Vector3 as Vector34 } from "three";
|
|
902
2207
|
var sphereDefaults = {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
2208
|
+
...commonDefaults,
|
|
2209
|
+
radius: 1
|
|
2210
|
+
};
|
|
2211
|
+
var SPHERE_TYPE = /* @__PURE__ */ Symbol("Sphere");
|
|
2212
|
+
var ZylemSphere = class _ZylemSphere extends GameEntity {
|
|
2213
|
+
static type = SPHERE_TYPE;
|
|
2214
|
+
constructor(options) {
|
|
2215
|
+
super();
|
|
2216
|
+
this.options = { ...sphereDefaults, ...options };
|
|
2217
|
+
}
|
|
2218
|
+
buildInfo() {
|
|
2219
|
+
const delegate = new DebugDelegate(this);
|
|
2220
|
+
const baseInfo = delegate.buildDebugInfo();
|
|
2221
|
+
const radius = this.options.radius ?? 1;
|
|
2222
|
+
return {
|
|
2223
|
+
...baseInfo,
|
|
2224
|
+
type: String(_ZylemSphere.type),
|
|
2225
|
+
radius: radius.toFixed(2)
|
|
2226
|
+
};
|
|
911
2227
|
}
|
|
912
2228
|
};
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
2229
|
+
function createSphere(...args) {
|
|
2230
|
+
const options = mergeArgs(args, sphereDefaults);
|
|
2231
|
+
const entity = new ZylemSphere(options);
|
|
2232
|
+
entity.add(
|
|
2233
|
+
sphereMesh({ radius: options.radius, material: options.material, color: options.color }),
|
|
2234
|
+
sphereCollision({
|
|
2235
|
+
radius: options.radius,
|
|
2236
|
+
static: options.collision?.static,
|
|
2237
|
+
sensor: options.collision?.sensor,
|
|
2238
|
+
collisionType: options.collisionType,
|
|
2239
|
+
collisionFilter: options.collisionFilter
|
|
2240
|
+
})
|
|
2241
|
+
);
|
|
2242
|
+
return entity;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// src/lib/entities/sprite.ts
|
|
2246
|
+
import { ColliderDesc as ColliderDesc3 } from "@dimforge/rapier3d-compat";
|
|
2247
|
+
import { Euler as Euler2, Group as Group3, Quaternion as Quaternion3, Vector3 as Vector39 } from "three";
|
|
2248
|
+
import {
|
|
2249
|
+
TextureLoader as TextureLoader2,
|
|
2250
|
+
SpriteMaterial,
|
|
2251
|
+
Sprite as ThreeSprite
|
|
2252
|
+
} from "three";
|
|
2253
|
+
|
|
2254
|
+
// src/lib/entities/builder.ts
|
|
2255
|
+
import { BufferGeometry as BufferGeometry2, Mesh as Mesh4, Color as Color4 } from "three";
|
|
2256
|
+
|
|
2257
|
+
// src/lib/graphics/mesh.ts
|
|
2258
|
+
import { Mesh as Mesh3 } from "three";
|
|
2259
|
+
var MeshBuilder = class {
|
|
2260
|
+
_build(meshOptions, geometry, materials) {
|
|
2261
|
+
const { batched, material } = meshOptions;
|
|
2262
|
+
if (batched) {
|
|
2263
|
+
console.warn("warning: mesh batching is not implemented");
|
|
2264
|
+
}
|
|
2265
|
+
const mesh = new Mesh3(geometry, materials.at(-1));
|
|
2266
|
+
mesh.position.set(0, 0, 0);
|
|
2267
|
+
mesh.castShadow = true;
|
|
2268
|
+
mesh.receiveShadow = true;
|
|
2269
|
+
return mesh;
|
|
2270
|
+
}
|
|
2271
|
+
_postBuild() {
|
|
2272
|
+
return;
|
|
918
2273
|
}
|
|
919
2274
|
};
|
|
920
|
-
|
|
2275
|
+
|
|
2276
|
+
// src/lib/entities/builder.ts
|
|
2277
|
+
var EntityCollisionBuilder = class extends CollisionBuilder {
|
|
2278
|
+
};
|
|
2279
|
+
var EntityMeshBuilder = class extends MeshBuilder {
|
|
921
2280
|
build(options) {
|
|
922
|
-
|
|
923
|
-
|
|
2281
|
+
return new BufferGeometry2();
|
|
2282
|
+
}
|
|
2283
|
+
postBuild() {
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
var EntityBuilder = class {
|
|
2288
|
+
meshBuilder;
|
|
2289
|
+
collisionBuilder;
|
|
2290
|
+
materialBuilder;
|
|
2291
|
+
options;
|
|
2292
|
+
entity;
|
|
2293
|
+
constructor(options, entity, meshBuilder, collisionBuilder) {
|
|
2294
|
+
this.options = options;
|
|
2295
|
+
this.entity = entity;
|
|
2296
|
+
this.meshBuilder = meshBuilder;
|
|
2297
|
+
this.collisionBuilder = collisionBuilder;
|
|
2298
|
+
this.materialBuilder = new MaterialBuilder();
|
|
2299
|
+
const builders = {
|
|
2300
|
+
meshBuilder: this.meshBuilder,
|
|
2301
|
+
collisionBuilder: this.collisionBuilder,
|
|
2302
|
+
materialBuilder: this.materialBuilder
|
|
2303
|
+
};
|
|
2304
|
+
this.options._builders = builders;
|
|
2305
|
+
}
|
|
2306
|
+
withPosition(setupPosition) {
|
|
2307
|
+
this.options.position = setupPosition;
|
|
2308
|
+
return this;
|
|
2309
|
+
}
|
|
2310
|
+
withMaterial(options, entityType) {
|
|
2311
|
+
if (this.materialBuilder) {
|
|
2312
|
+
this.materialBuilder.build(options, entityType);
|
|
2313
|
+
}
|
|
2314
|
+
return this;
|
|
2315
|
+
}
|
|
2316
|
+
applyMaterialToGroup(group, materials) {
|
|
2317
|
+
group.traverse((child) => {
|
|
2318
|
+
if (child instanceof Mesh4) {
|
|
2319
|
+
if (child.type === "SkinnedMesh" && materials[0] && !child.material.map) {
|
|
2320
|
+
child.material = materials[0];
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
child.castShadow = true;
|
|
2324
|
+
child.receiveShadow = true;
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
build() {
|
|
2328
|
+
const entity = this.entity;
|
|
2329
|
+
if (this.materialBuilder) {
|
|
2330
|
+
entity.materials = this.materialBuilder.materials;
|
|
2331
|
+
}
|
|
2332
|
+
if (this.meshBuilder && entity.materials) {
|
|
2333
|
+
const geometry = this.meshBuilder.build(this.options);
|
|
2334
|
+
entity.mesh = this.meshBuilder._build(this.options, geometry, entity.materials);
|
|
2335
|
+
this.meshBuilder.postBuild();
|
|
2336
|
+
}
|
|
2337
|
+
if (entity.group && entity.materials) {
|
|
2338
|
+
this.applyMaterialToGroup(entity.group, entity.materials);
|
|
2339
|
+
}
|
|
2340
|
+
if (this.collisionBuilder) {
|
|
2341
|
+
this.collisionBuilder.withCollision(this.options?.collision || {});
|
|
2342
|
+
const [bodyDesc, colliderDesc] = this.collisionBuilder.build(this.options);
|
|
2343
|
+
entity.bodyDesc = bodyDesc;
|
|
2344
|
+
entity.colliderDesc = colliderDesc;
|
|
2345
|
+
entity.colliderDescs.push(colliderDesc);
|
|
2346
|
+
const { x, y, z } = this.options.position || { x: 0, y: 0, z: 0 };
|
|
2347
|
+
entity.bodyDesc.setTranslation(x, y, z);
|
|
2348
|
+
}
|
|
2349
|
+
if (this.options.collisionType) {
|
|
2350
|
+
entity.collisionType = this.options.collisionType;
|
|
2351
|
+
}
|
|
2352
|
+
if (this.options.color instanceof Color4) {
|
|
2353
|
+
const applyColor = (material) => {
|
|
2354
|
+
const anyMat = material;
|
|
2355
|
+
if (anyMat && anyMat.color && anyMat.color.set) {
|
|
2356
|
+
anyMat.color.set(this.options.color);
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
if (entity.materials?.length) {
|
|
2360
|
+
for (const mat of entity.materials) applyColor(mat);
|
|
2361
|
+
}
|
|
2362
|
+
if (entity.mesh && entity.mesh.material) {
|
|
2363
|
+
const mat = entity.mesh.material;
|
|
2364
|
+
if (Array.isArray(mat)) mat.forEach(applyColor);
|
|
2365
|
+
else applyColor(mat);
|
|
2366
|
+
}
|
|
2367
|
+
if (entity.group) {
|
|
2368
|
+
entity.group.traverse((child) => {
|
|
2369
|
+
if (child instanceof Mesh4 && child.material) {
|
|
2370
|
+
const mat = child.material;
|
|
2371
|
+
if (Array.isArray(mat)) mat.forEach(applyColor);
|
|
2372
|
+
else applyColor(mat);
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
return entity;
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
|
|
2381
|
+
// src/lib/entities/delegates/loader.ts
|
|
2382
|
+
function isLoadable(obj) {
|
|
2383
|
+
return typeof obj?.load === "function" && typeof obj?.data === "function";
|
|
2384
|
+
}
|
|
2385
|
+
var EntityLoader = class {
|
|
2386
|
+
entityReference;
|
|
2387
|
+
constructor(entity) {
|
|
2388
|
+
this.entityReference = entity;
|
|
2389
|
+
}
|
|
2390
|
+
load() {
|
|
2391
|
+
if (this.entityReference.load) {
|
|
2392
|
+
this.entityReference.load();
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
data() {
|
|
2396
|
+
if (this.entityReference.data) {
|
|
2397
|
+
return this.entityReference.data();
|
|
2398
|
+
}
|
|
2399
|
+
return null;
|
|
924
2400
|
}
|
|
925
2401
|
};
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
2402
|
+
|
|
2403
|
+
// src/lib/entities/create.ts
|
|
2404
|
+
function createEntity(params) {
|
|
2405
|
+
const {
|
|
2406
|
+
args,
|
|
2407
|
+
defaultConfig,
|
|
2408
|
+
EntityClass,
|
|
2409
|
+
BuilderClass,
|
|
2410
|
+
entityType,
|
|
2411
|
+
MeshBuilderClass,
|
|
2412
|
+
CollisionBuilderClass
|
|
2413
|
+
} = params;
|
|
2414
|
+
let builder = null;
|
|
2415
|
+
let configuration;
|
|
2416
|
+
const configurationIndex = args.findIndex((node) => !(node instanceof BaseNode));
|
|
2417
|
+
if (configurationIndex !== -1) {
|
|
2418
|
+
const subArgs = args.splice(configurationIndex, 1);
|
|
2419
|
+
configuration = subArgs.find((node) => !(node instanceof BaseNode));
|
|
929
2420
|
}
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
2421
|
+
const mergedConfiguration = configuration ? { ...defaultConfig, ...configuration } : defaultConfig;
|
|
2422
|
+
args.push(mergedConfiguration);
|
|
2423
|
+
for (const arg of args) {
|
|
2424
|
+
if (arg instanceof BaseNode) {
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
let entityData = null;
|
|
2428
|
+
const entity = new EntityClass(arg);
|
|
2429
|
+
try {
|
|
2430
|
+
if (isLoadable(entity)) {
|
|
2431
|
+
const loader = new EntityLoader(entity);
|
|
2432
|
+
loader.load();
|
|
2433
|
+
entityData = loader.data();
|
|
2434
|
+
}
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
console.error("Error creating entity with loader:", error);
|
|
2437
|
+
}
|
|
2438
|
+
builder = new BuilderClass(
|
|
2439
|
+
arg,
|
|
2440
|
+
entity,
|
|
2441
|
+
MeshBuilderClass ? new MeshBuilderClass(entityData) : null,
|
|
2442
|
+
CollisionBuilderClass ? new CollisionBuilderClass(entityData) : null
|
|
2443
|
+
);
|
|
2444
|
+
if (arg.material) {
|
|
2445
|
+
builder.withMaterial(arg.material, entityType);
|
|
2446
|
+
}
|
|
937
2447
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const baseInfo = delegate.buildDebugInfo();
|
|
941
|
-
const radius = this.options.radius ?? 1;
|
|
942
|
-
return {
|
|
943
|
-
...baseInfo,
|
|
944
|
-
type: String(_ZylemSphere.type),
|
|
945
|
-
radius: radius.toFixed(2)
|
|
946
|
-
};
|
|
2448
|
+
if (!builder) {
|
|
2449
|
+
throw new Error(`missing options for ${String(entityType)}, builder is not initialized.`);
|
|
947
2450
|
}
|
|
948
|
-
|
|
949
|
-
async function sphere(...args) {
|
|
950
|
-
return createEntity({
|
|
951
|
-
args,
|
|
952
|
-
defaultConfig: sphereDefaults,
|
|
953
|
-
EntityClass: ZylemSphere,
|
|
954
|
-
BuilderClass: SphereBuilder,
|
|
955
|
-
MeshBuilderClass: SphereMeshBuilder,
|
|
956
|
-
CollisionBuilderClass: SphereCollisionBuilder,
|
|
957
|
-
entityType: ZylemSphere.type
|
|
958
|
-
});
|
|
2451
|
+
return builder.build();
|
|
959
2452
|
}
|
|
960
2453
|
|
|
961
2454
|
// src/lib/entities/sprite.ts
|
|
962
|
-
import { ColliderDesc as ColliderDesc4 } from "@dimforge/rapier3d-compat";
|
|
963
|
-
import { Color as Color6, Euler, Group as Group3, Quaternion as Quaternion2, Vector3 as Vector35 } from "three";
|
|
964
|
-
import {
|
|
965
|
-
TextureLoader as TextureLoader2,
|
|
966
|
-
SpriteMaterial,
|
|
967
|
-
Sprite as ThreeSprite
|
|
968
|
-
} from "three";
|
|
969
2455
|
var spriteDefaults = {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
collision: {
|
|
973
|
-
static: false
|
|
974
|
-
},
|
|
975
|
-
material: {
|
|
976
|
-
color: new Color6("#ffffff"),
|
|
977
|
-
shader: "standard"
|
|
978
|
-
},
|
|
2456
|
+
...commonDefaults,
|
|
2457
|
+
size: new Vector39(1, 1, 1),
|
|
979
2458
|
images: [],
|
|
980
2459
|
animations: []
|
|
981
2460
|
};
|
|
982
2461
|
var SpriteCollisionBuilder = class extends EntityCollisionBuilder {
|
|
983
2462
|
collider(options) {
|
|
984
|
-
const size = options.collisionSize || options.size || new
|
|
2463
|
+
const size = options.collisionSize || options.size || new Vector39(1, 1, 1);
|
|
985
2464
|
const half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };
|
|
986
|
-
let colliderDesc =
|
|
2465
|
+
let colliderDesc = ColliderDesc3.cuboid(half.x, half.y, half.z);
|
|
987
2466
|
return colliderDesc;
|
|
988
2467
|
}
|
|
989
2468
|
};
|
|
@@ -1007,7 +2486,7 @@ var ZylemSprite = class _ZylemSprite extends GameEntity {
|
|
|
1007
2486
|
super();
|
|
1008
2487
|
this.options = { ...spriteDefaults, ...options };
|
|
1009
2488
|
this.prependUpdate(this.spriteUpdate.bind(this));
|
|
1010
|
-
this.
|
|
2489
|
+
this.onCleanup(this.spriteDestroy.bind(this));
|
|
1011
2490
|
}
|
|
1012
2491
|
create() {
|
|
1013
2492
|
this.sprites = [];
|
|
@@ -1085,20 +2564,20 @@ var ZylemSprite = class _ZylemSprite extends GameEntity {
|
|
|
1085
2564
|
}
|
|
1086
2565
|
}
|
|
1087
2566
|
}
|
|
1088
|
-
|
|
2567
|
+
spriteUpdate(params) {
|
|
1089
2568
|
this.sprites.forEach((_sprite) => {
|
|
1090
2569
|
if (_sprite.material) {
|
|
1091
2570
|
const q = this.body?.rotation();
|
|
1092
2571
|
if (q) {
|
|
1093
|
-
const quat = new
|
|
1094
|
-
const euler = new
|
|
2572
|
+
const quat = new Quaternion3(q.x, q.y, q.z, q.w);
|
|
2573
|
+
const euler = new Euler2().setFromQuaternion(quat, "XYZ");
|
|
1095
2574
|
_sprite.material.rotation = euler.z;
|
|
1096
2575
|
}
|
|
1097
2576
|
_sprite.scale.set(this.options.size?.x ?? 1, this.options.size?.y ?? 1, this.options.size?.z ?? 1);
|
|
1098
2577
|
}
|
|
1099
2578
|
});
|
|
1100
2579
|
}
|
|
1101
|
-
|
|
2580
|
+
spriteDestroy(params) {
|
|
1102
2581
|
this.sprites.forEach((_sprite) => {
|
|
1103
2582
|
_sprite.removeFromParent();
|
|
1104
2583
|
});
|
|
@@ -1114,7 +2593,7 @@ var ZylemSprite = class _ZylemSprite extends GameEntity {
|
|
|
1114
2593
|
};
|
|
1115
2594
|
}
|
|
1116
2595
|
};
|
|
1117
|
-
|
|
2596
|
+
function createSprite(...args) {
|
|
1118
2597
|
return createEntity({
|
|
1119
2598
|
args,
|
|
1120
2599
|
defaultConfig: spriteDefaults,
|
|
@@ -1126,8 +2605,8 @@ async function sprite(...args) {
|
|
|
1126
2605
|
}
|
|
1127
2606
|
|
|
1128
2607
|
// src/lib/entities/plane.ts
|
|
1129
|
-
import { ColliderDesc as
|
|
1130
|
-
import {
|
|
2608
|
+
import { ColliderDesc as ColliderDesc4 } from "@dimforge/rapier3d-compat";
|
|
2609
|
+
import { PlaneGeometry, Vector2 as Vector23, Vector3 as Vector310 } from "three";
|
|
1131
2610
|
|
|
1132
2611
|
// src/lib/graphics/geometries/XZPlaneGeometry.ts
|
|
1133
2612
|
import { BufferGeometry as BufferGeometry3, Float32BufferAttribute } from "three";
|
|
@@ -1191,26 +2670,24 @@ var XZPlaneGeometry = class _XZPlaneGeometry extends BufferGeometry3 {
|
|
|
1191
2670
|
// src/lib/entities/plane.ts
|
|
1192
2671
|
var DEFAULT_SUBDIVISIONS = 4;
|
|
1193
2672
|
var planeDefaults = {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
2673
|
+
...commonDefaults,
|
|
2674
|
+
tile: new Vector23(10, 10),
|
|
2675
|
+
repeat: new Vector23(1, 1),
|
|
1197
2676
|
collision: {
|
|
1198
2677
|
static: true
|
|
1199
2678
|
},
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
},
|
|
1204
|
-
subdivisions: DEFAULT_SUBDIVISIONS
|
|
2679
|
+
subdivisions: DEFAULT_SUBDIVISIONS,
|
|
2680
|
+
randomizeHeight: false,
|
|
2681
|
+
heightScale: 1
|
|
1205
2682
|
};
|
|
1206
2683
|
var PlaneCollisionBuilder = class extends EntityCollisionBuilder {
|
|
1207
2684
|
collider(options) {
|
|
1208
|
-
const tile = options.tile ?? new
|
|
2685
|
+
const tile = options.tile ?? new Vector23(1, 1);
|
|
1209
2686
|
const subdivisions = options.subdivisions ?? DEFAULT_SUBDIVISIONS;
|
|
1210
|
-
const size = new
|
|
2687
|
+
const size = new Vector310(tile.x, 1, tile.y);
|
|
1211
2688
|
const heightData = options._builders?.meshBuilder?.heightData;
|
|
1212
|
-
const scale2 = new
|
|
1213
|
-
let colliderDesc =
|
|
2689
|
+
const scale2 = new Vector310(size.x, 1, size.z);
|
|
2690
|
+
let colliderDesc = ColliderDesc4.heightfield(
|
|
1214
2691
|
subdivisions,
|
|
1215
2692
|
subdivisions,
|
|
1216
2693
|
heightData,
|
|
@@ -1222,10 +2699,13 @@ var PlaneCollisionBuilder = class extends EntityCollisionBuilder {
|
|
|
1222
2699
|
var PlaneMeshBuilder = class extends EntityMeshBuilder {
|
|
1223
2700
|
heightData = new Float32Array();
|
|
1224
2701
|
columnsRows = /* @__PURE__ */ new Map();
|
|
2702
|
+
subdivisions = DEFAULT_SUBDIVISIONS;
|
|
1225
2703
|
build(options) {
|
|
1226
|
-
const tile = options.tile ?? new
|
|
2704
|
+
const tile = options.tile ?? new Vector23(1, 1);
|
|
1227
2705
|
const subdivisions = options.subdivisions ?? DEFAULT_SUBDIVISIONS;
|
|
1228
|
-
|
|
2706
|
+
this.subdivisions = subdivisions;
|
|
2707
|
+
const size = new Vector310(tile.x, 1, tile.y);
|
|
2708
|
+
const heightScale = options.heightScale ?? 1;
|
|
1229
2709
|
const geometry = new XZPlaneGeometry(size.x, size.z, subdivisions, subdivisions);
|
|
1230
2710
|
const vertexGeometry = new PlaneGeometry(size.x, size.z, subdivisions, subdivisions);
|
|
1231
2711
|
const dx = size.x / subdivisions;
|
|
@@ -1233,30 +2713,40 @@ var PlaneMeshBuilder = class extends EntityMeshBuilder {
|
|
|
1233
2713
|
const originalVertices = geometry.attributes.position.array;
|
|
1234
2714
|
const vertices = vertexGeometry.attributes.position.array;
|
|
1235
2715
|
const columsRows = /* @__PURE__ */ new Map();
|
|
2716
|
+
const heightMapData = options.heightMap;
|
|
2717
|
+
const useRandomHeight = options.randomizeHeight ?? false;
|
|
1236
2718
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
2719
|
+
const vertexIndex = i / 3;
|
|
1237
2720
|
let row = Math.floor(Math.abs(vertices[i] + size.x / 2) / dx);
|
|
1238
2721
|
let column = Math.floor(Math.abs(vertices[i + 1] - size.z / 2) / dy);
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
2722
|
+
let height = 0;
|
|
2723
|
+
if (heightMapData && heightMapData.length > 0) {
|
|
2724
|
+
const heightIndex = vertexIndex % heightMapData.length;
|
|
2725
|
+
height = heightMapData[heightIndex] * heightScale;
|
|
2726
|
+
} else if (useRandomHeight) {
|
|
2727
|
+
height = Math.random() * 4 * heightScale;
|
|
2728
|
+
}
|
|
2729
|
+
vertices[i + 2] = height;
|
|
2730
|
+
originalVertices[i + 1] = height;
|
|
1242
2731
|
if (!columsRows.get(column)) {
|
|
1243
2732
|
columsRows.set(column, /* @__PURE__ */ new Map());
|
|
1244
2733
|
}
|
|
1245
|
-
columsRows.get(column).set(row,
|
|
2734
|
+
columsRows.get(column).set(row, height);
|
|
1246
2735
|
}
|
|
1247
2736
|
this.columnsRows = columsRows;
|
|
1248
2737
|
return geometry;
|
|
1249
2738
|
}
|
|
1250
2739
|
postBuild() {
|
|
1251
2740
|
const heights = [];
|
|
1252
|
-
for (let i = 0; i <=
|
|
1253
|
-
for (let j = 0; j <=
|
|
2741
|
+
for (let i = 0; i <= this.subdivisions; ++i) {
|
|
2742
|
+
for (let j = 0; j <= this.subdivisions; ++j) {
|
|
1254
2743
|
const row = this.columnsRows.get(j);
|
|
1255
2744
|
if (!row) {
|
|
2745
|
+
heights.push(0);
|
|
1256
2746
|
continue;
|
|
1257
2747
|
}
|
|
1258
2748
|
const data = row.get(i);
|
|
1259
|
-
heights.push(data);
|
|
2749
|
+
heights.push(data ?? 0);
|
|
1260
2750
|
}
|
|
1261
2751
|
}
|
|
1262
2752
|
this.heightData = new Float32Array(heights);
|
|
@@ -1275,7 +2765,7 @@ var ZylemPlane = class extends GameEntity {
|
|
|
1275
2765
|
this.options = { ...planeDefaults, ...options };
|
|
1276
2766
|
}
|
|
1277
2767
|
};
|
|
1278
|
-
|
|
2768
|
+
function createPlane(...args) {
|
|
1279
2769
|
return createEntity({
|
|
1280
2770
|
args,
|
|
1281
2771
|
defaultConfig: planeDefaults,
|
|
@@ -1288,12 +2778,11 @@ async function plane(...args) {
|
|
|
1288
2778
|
}
|
|
1289
2779
|
|
|
1290
2780
|
// src/lib/entities/zone.ts
|
|
1291
|
-
import {
|
|
1292
|
-
import { Vector3 as Vector37 } from "three";
|
|
2781
|
+
import { Vector3 as Vector311 } from "three";
|
|
1293
2782
|
|
|
1294
2783
|
// src/lib/game/game-state.ts
|
|
1295
|
-
import { proxy, subscribe } from "valtio/vanilla";
|
|
1296
|
-
var state =
|
|
2784
|
+
import { proxy as proxy2, subscribe } from "valtio/vanilla";
|
|
2785
|
+
var state = proxy2({
|
|
1297
2786
|
id: "",
|
|
1298
2787
|
globals: {},
|
|
1299
2788
|
time: 0
|
|
@@ -1301,28 +2790,10 @@ var state = proxy({
|
|
|
1301
2790
|
|
|
1302
2791
|
// src/lib/entities/zone.ts
|
|
1303
2792
|
var zoneDefaults = {
|
|
1304
|
-
|
|
1305
|
-
|
|
2793
|
+
...commonDefaults,
|
|
2794
|
+
size: new Vector311(1, 1, 1),
|
|
1306
2795
|
collision: {
|
|
1307
2796
|
static: true
|
|
1308
|
-
},
|
|
1309
|
-
material: {
|
|
1310
|
-
shader: "standard"
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
var ZoneCollisionBuilder = class extends EntityCollisionBuilder {
|
|
1314
|
-
collider(options) {
|
|
1315
|
-
const size = options.size || new Vector37(1, 1, 1);
|
|
1316
|
-
const half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };
|
|
1317
|
-
let colliderDesc = ColliderDesc6.cuboid(half.x, half.y, half.z);
|
|
1318
|
-
colliderDesc.setSensor(true);
|
|
1319
|
-
colliderDesc.activeCollisionTypes = ActiveCollisionTypes2.KINEMATIC_FIXED;
|
|
1320
|
-
return colliderDesc;
|
|
1321
|
-
}
|
|
1322
|
-
};
|
|
1323
|
-
var ZoneBuilder = class extends EntityBuilder {
|
|
1324
|
-
createEntity(options) {
|
|
1325
|
-
return new ZylemZone(options);
|
|
1326
2797
|
}
|
|
1327
2798
|
};
|
|
1328
2799
|
var ZONE_TYPE = /* @__PURE__ */ Symbol("Zone");
|
|
@@ -1404,78 +2875,56 @@ var ZylemZone = class extends GameEntity {
|
|
|
1404
2875
|
}
|
|
1405
2876
|
}
|
|
1406
2877
|
};
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
2878
|
+
function createZone(...args) {
|
|
2879
|
+
const options = mergeArgs(args, zoneDefaults);
|
|
2880
|
+
const entity = new ZylemZone(options);
|
|
2881
|
+
entity.add(
|
|
2882
|
+
zoneCollision({
|
|
2883
|
+
size: options.size,
|
|
2884
|
+
static: options.collision?.static ?? true,
|
|
2885
|
+
collisionType: options.collisionType,
|
|
2886
|
+
collisionFilter: options.collisionFilter
|
|
2887
|
+
})
|
|
2888
|
+
);
|
|
2889
|
+
return entity;
|
|
1416
2890
|
}
|
|
1417
2891
|
|
|
1418
2892
|
// src/lib/entities/actor.ts
|
|
1419
|
-
import { ActiveCollisionTypes as ActiveCollisionTypes3, ColliderDesc as
|
|
1420
|
-
import {
|
|
2893
|
+
import { ActiveCollisionTypes as ActiveCollisionTypes3, ColliderDesc as ColliderDesc5 } from "@dimforge/rapier3d-compat";
|
|
2894
|
+
import { MeshStandardMaterial as MeshStandardMaterial4, Group as Group4, Vector3 as Vector312 } from "three";
|
|
1421
2895
|
|
|
1422
2896
|
// src/lib/core/entity-asset-loader.ts
|
|
1423
|
-
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
|
|
1424
|
-
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
1425
|
-
var FBXAssetLoader = class {
|
|
1426
|
-
loader = new FBXLoader();
|
|
1427
|
-
isSupported(file) {
|
|
1428
|
-
return file.toLowerCase().endsWith("fbx" /* FBX */);
|
|
1429
|
-
}
|
|
1430
|
-
async load(file) {
|
|
1431
|
-
return new Promise((resolve, reject) => {
|
|
1432
|
-
this.loader.load(
|
|
1433
|
-
file,
|
|
1434
|
-
(object) => {
|
|
1435
|
-
const animation = object.animations[0];
|
|
1436
|
-
resolve({
|
|
1437
|
-
object,
|
|
1438
|
-
animation
|
|
1439
|
-
});
|
|
1440
|
-
},
|
|
1441
|
-
void 0,
|
|
1442
|
-
reject
|
|
1443
|
-
);
|
|
1444
|
-
});
|
|
1445
|
-
}
|
|
1446
|
-
};
|
|
1447
|
-
var GLTFAssetLoader = class {
|
|
1448
|
-
loader = new GLTFLoader();
|
|
1449
|
-
isSupported(file) {
|
|
1450
|
-
return file.toLowerCase().endsWith("gltf" /* GLTF */);
|
|
1451
|
-
}
|
|
1452
|
-
async load(file) {
|
|
1453
|
-
return new Promise((resolve, reject) => {
|
|
1454
|
-
this.loader.load(
|
|
1455
|
-
file,
|
|
1456
|
-
(gltf) => {
|
|
1457
|
-
resolve({
|
|
1458
|
-
object: gltf.scene,
|
|
1459
|
-
gltf
|
|
1460
|
-
});
|
|
1461
|
-
},
|
|
1462
|
-
void 0,
|
|
1463
|
-
reject
|
|
1464
|
-
);
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
};
|
|
1468
2897
|
var EntityAssetLoader = class {
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
];
|
|
2898
|
+
/**
|
|
2899
|
+
* Load a model file (FBX, GLTF, GLB, OBJ) using the asset manager
|
|
2900
|
+
*/
|
|
1473
2901
|
async loadFile(file) {
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
|
|
2902
|
+
const ext = file.split(".").pop()?.toLowerCase();
|
|
2903
|
+
switch (ext) {
|
|
2904
|
+
case "fbx": {
|
|
2905
|
+
const result = await assetManager.loadFBX(file);
|
|
2906
|
+
return {
|
|
2907
|
+
object: result.object,
|
|
2908
|
+
animation: result.animations?.[0]
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
case "gltf":
|
|
2912
|
+
case "glb": {
|
|
2913
|
+
const result = await assetManager.loadGLTF(file);
|
|
2914
|
+
return {
|
|
2915
|
+
object: result.object,
|
|
2916
|
+
gltf: result.gltf
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
case "obj": {
|
|
2920
|
+
const result = await assetManager.loadOBJ(file);
|
|
2921
|
+
return {
|
|
2922
|
+
object: result.object
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
default:
|
|
2926
|
+
throw new Error(`Unsupported file type: ${file}`);
|
|
1477
2927
|
}
|
|
1478
|
-
return loader.load(file);
|
|
1479
2928
|
}
|
|
1480
2929
|
};
|
|
1481
2930
|
|
|
@@ -1502,14 +2951,22 @@ var AnimationDelegate = class {
|
|
|
1502
2951
|
async loadAnimations(animations) {
|
|
1503
2952
|
if (!animations.length) return;
|
|
1504
2953
|
const results = await Promise.all(animations.map((a) => this._assetLoader.loadFile(a.path)));
|
|
1505
|
-
|
|
1506
|
-
|
|
2954
|
+
const loadedAnimations = [];
|
|
2955
|
+
results.forEach((result, i) => {
|
|
2956
|
+
if (result.animation) {
|
|
2957
|
+
loadedAnimations.push({
|
|
2958
|
+
key: animations[i].key || i.toString(),
|
|
2959
|
+
clip: result.animation
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
});
|
|
2963
|
+
if (!loadedAnimations.length) return;
|
|
2964
|
+
this._animations = loadedAnimations.map((a) => a.clip);
|
|
1507
2965
|
this._mixer = new AnimationMixer(this.target);
|
|
1508
|
-
|
|
1509
|
-
const key = animations[i].key || i.toString();
|
|
2966
|
+
loadedAnimations.forEach(({ key, clip }) => {
|
|
1510
2967
|
this._actions[key] = this._mixer.clipAction(clip);
|
|
1511
2968
|
});
|
|
1512
|
-
this.playAnimation({ key:
|
|
2969
|
+
this.playAnimation({ key: loadedAnimations[0].key });
|
|
1513
2970
|
}
|
|
1514
2971
|
update(delta) {
|
|
1515
2972
|
if (!this._mixer || !this._currentAction) return;
|
|
@@ -1533,14 +2990,13 @@ var AnimationDelegate = class {
|
|
|
1533
2990
|
if (!this._mixer) return;
|
|
1534
2991
|
const { key, pauseAtPercentage = 0, pauseAtEnd = false, fadeToKey, fadeDuration = 0.5 } = opts;
|
|
1535
2992
|
if (key === this._currentKey) return;
|
|
2993
|
+
const action = this._actions[key];
|
|
2994
|
+
if (!action) return;
|
|
1536
2995
|
this._queuedKey = fadeToKey || null;
|
|
1537
2996
|
this._fadeDuration = fadeDuration;
|
|
1538
2997
|
this._pauseAtPercentage = pauseAtEnd ? 100 : pauseAtPercentage;
|
|
1539
2998
|
this._isPaused = false;
|
|
1540
2999
|
const prev = this._currentAction;
|
|
1541
|
-
if (prev) prev.stop();
|
|
1542
|
-
const action = this._actions[key];
|
|
1543
|
-
if (!action) return;
|
|
1544
3000
|
if (this._pauseAtPercentage > 0) {
|
|
1545
3001
|
action.setLoop(LoopOnce, Infinity);
|
|
1546
3002
|
action.clampWhenFinished = true;
|
|
@@ -1548,10 +3004,10 @@ var AnimationDelegate = class {
|
|
|
1548
3004
|
action.setLoop(LoopRepeat, Infinity);
|
|
1549
3005
|
action.clampWhenFinished = false;
|
|
1550
3006
|
}
|
|
3007
|
+
action.reset().play();
|
|
1551
3008
|
if (prev) {
|
|
1552
3009
|
prev.crossFadeTo(action, fadeDuration, false);
|
|
1553
3010
|
}
|
|
1554
|
-
action.reset().play();
|
|
1555
3011
|
this._currentAction = action;
|
|
1556
3012
|
this._currentKey = key;
|
|
1557
3013
|
}
|
|
@@ -1582,46 +3038,86 @@ var AnimationDelegate = class {
|
|
|
1582
3038
|
|
|
1583
3039
|
// src/lib/entities/actor.ts
|
|
1584
3040
|
var actorDefaults = {
|
|
1585
|
-
|
|
3041
|
+
...commonDefaults,
|
|
1586
3042
|
collision: {
|
|
1587
3043
|
static: false,
|
|
1588
|
-
size: new
|
|
1589
|
-
position: new
|
|
3044
|
+
size: new Vector312(0.5, 0.5, 0.5),
|
|
3045
|
+
position: new Vector312(0, 0, 0)
|
|
1590
3046
|
},
|
|
1591
3047
|
material: {
|
|
1592
|
-
shader:
|
|
3048
|
+
shader: standardShader
|
|
1593
3049
|
},
|
|
1594
3050
|
animations: [],
|
|
1595
|
-
models: []
|
|
3051
|
+
models: [],
|
|
3052
|
+
collisionShape: "capsule"
|
|
1596
3053
|
};
|
|
1597
3054
|
var ActorCollisionBuilder = class extends EntityCollisionBuilder {
|
|
1598
|
-
height = 1;
|
|
1599
3055
|
objectModel = null;
|
|
3056
|
+
collisionShape = "capsule";
|
|
1600
3057
|
constructor(data) {
|
|
1601
3058
|
super();
|
|
1602
3059
|
this.objectModel = data.objectModel;
|
|
3060
|
+
this.collisionShape = data.collisionShape ?? "capsule";
|
|
1603
3061
|
}
|
|
1604
|
-
|
|
1605
|
-
if (
|
|
1606
|
-
|
|
1607
|
-
const geometry = skinnedMesh.geometry;
|
|
1608
|
-
if (geometry) {
|
|
1609
|
-
geometry.computeBoundingBox();
|
|
1610
|
-
if (geometry.boundingBox) {
|
|
1611
|
-
const maxY = geometry.boundingBox.max.y;
|
|
1612
|
-
const minY = geometry.boundingBox.min.y;
|
|
1613
|
-
this.height = maxY - minY;
|
|
1614
|
-
}
|
|
3062
|
+
collider(options) {
|
|
3063
|
+
if (this.collisionShape === "model") {
|
|
3064
|
+
return this.createColliderFromModel(this.objectModel, options);
|
|
1615
3065
|
}
|
|
1616
|
-
this.
|
|
1617
|
-
|
|
3066
|
+
return this.createCapsuleCollider(options);
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Create a capsule collider based on size options (character controller style).
|
|
3070
|
+
*/
|
|
3071
|
+
createCapsuleCollider(options) {
|
|
3072
|
+
const size = options.collision?.size ?? options.size ?? { x: 0.5, y: 1, z: 0.5 };
|
|
3073
|
+
const halfHeight = size.y || 1;
|
|
3074
|
+
const radius = Math.max(size.x || 0.5, size.z || 0.5);
|
|
3075
|
+
let colliderDesc = ColliderDesc5.capsule(halfHeight, radius);
|
|
1618
3076
|
colliderDesc.setSensor(false);
|
|
1619
|
-
colliderDesc.setTranslation(0,
|
|
3077
|
+
colliderDesc.setTranslation(0, halfHeight + radius, 0);
|
|
1620
3078
|
colliderDesc.activeCollisionTypes = ActiveCollisionTypes3.DEFAULT;
|
|
1621
3079
|
return colliderDesc;
|
|
1622
3080
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
3081
|
+
/**
|
|
3082
|
+
* Create a collider based on model geometry (works with Mesh and SkinnedMesh).
|
|
3083
|
+
* If collision.size and collision.position are provided, use those instead of computing from geometry.
|
|
3084
|
+
*/
|
|
3085
|
+
createColliderFromModel(objectModel, options) {
|
|
3086
|
+
const collisionSize = options.collision?.size;
|
|
3087
|
+
const collisionPosition = options.collision?.position;
|
|
3088
|
+
if (collisionSize) {
|
|
3089
|
+
const halfWidth = collisionSize.x / 2;
|
|
3090
|
+
const halfHeight = collisionSize.y / 2;
|
|
3091
|
+
const halfDepth = collisionSize.z / 2;
|
|
3092
|
+
let colliderDesc2 = ColliderDesc5.cuboid(halfWidth, halfHeight, halfDepth);
|
|
3093
|
+
colliderDesc2.setSensor(false);
|
|
3094
|
+
const posX = collisionPosition ? collisionPosition.x : 0;
|
|
3095
|
+
const posY = collisionPosition ? collisionPosition.y : halfHeight;
|
|
3096
|
+
const posZ = collisionPosition ? collisionPosition.z : 0;
|
|
3097
|
+
colliderDesc2.setTranslation(posX, posY, posZ);
|
|
3098
|
+
colliderDesc2.activeCollisionTypes = ActiveCollisionTypes3.DEFAULT;
|
|
3099
|
+
return colliderDesc2;
|
|
3100
|
+
}
|
|
3101
|
+
if (!objectModel) return this.createCapsuleCollider(options);
|
|
3102
|
+
let foundGeometry = null;
|
|
3103
|
+
objectModel.traverse((child) => {
|
|
3104
|
+
if (!foundGeometry && child.isMesh) {
|
|
3105
|
+
foundGeometry = child.geometry;
|
|
3106
|
+
}
|
|
3107
|
+
});
|
|
3108
|
+
if (!foundGeometry) return this.createCapsuleCollider(options);
|
|
3109
|
+
const geometry = foundGeometry;
|
|
3110
|
+
geometry.computeBoundingBox();
|
|
3111
|
+
const box = geometry.boundingBox;
|
|
3112
|
+
if (!box) return this.createCapsuleCollider(options);
|
|
3113
|
+
const height = box.max.y - box.min.y;
|
|
3114
|
+
const width = box.max.x - box.min.x;
|
|
3115
|
+
const depth = box.max.z - box.min.z;
|
|
3116
|
+
let colliderDesc = ColliderDesc5.cuboid(width / 2, height / 2, depth / 2);
|
|
3117
|
+
colliderDesc.setSensor(false);
|
|
3118
|
+
const centerY = (box.max.y + box.min.y) / 2;
|
|
3119
|
+
colliderDesc.setTranslation(0, centerY, 0);
|
|
3120
|
+
colliderDesc.activeCollisionTypes = ActiveCollisionTypes3.DEFAULT;
|
|
1625
3121
|
return colliderDesc;
|
|
1626
3122
|
}
|
|
1627
3123
|
};
|
|
@@ -1644,21 +3140,26 @@ var ZylemActor = class extends GameEntity {
|
|
|
1644
3140
|
this.prependUpdate(this.actorUpdate.bind(this));
|
|
1645
3141
|
this.controlledRotation = true;
|
|
1646
3142
|
}
|
|
1647
|
-
|
|
3143
|
+
/**
|
|
3144
|
+
* Initiates model and animation loading in background (deferred).
|
|
3145
|
+
* Call returns immediately; assets will be ready on subsequent updates.
|
|
3146
|
+
*/
|
|
3147
|
+
load() {
|
|
1648
3148
|
this._modelFileNames = this.options.models || [];
|
|
1649
|
-
|
|
1650
|
-
if (this._object) {
|
|
1651
|
-
this._animationDelegate = new AnimationDelegate(this._object);
|
|
1652
|
-
await this._animationDelegate.loadAnimations(this.options.animations || []);
|
|
1653
|
-
}
|
|
3149
|
+
this.loadModelsDeferred();
|
|
1654
3150
|
}
|
|
1655
|
-
|
|
3151
|
+
/**
|
|
3152
|
+
* Returns current data synchronously.
|
|
3153
|
+
* May return null values if loading is still in progress.
|
|
3154
|
+
*/
|
|
3155
|
+
data() {
|
|
1656
3156
|
return {
|
|
1657
3157
|
animations: this._animationDelegate?.animations,
|
|
1658
|
-
objectModel: this._object
|
|
3158
|
+
objectModel: this._object,
|
|
3159
|
+
collisionShape: this.options.collisionShape
|
|
1659
3160
|
};
|
|
1660
3161
|
}
|
|
1661
|
-
|
|
3162
|
+
actorUpdate(params) {
|
|
1662
3163
|
this._animationDelegate?.update(params.delta);
|
|
1663
3164
|
}
|
|
1664
3165
|
/**
|
|
@@ -1689,26 +3190,79 @@ var ZylemActor = class extends GameEntity {
|
|
|
1689
3190
|
}
|
|
1690
3191
|
this._modelFileNames = [];
|
|
1691
3192
|
}
|
|
1692
|
-
|
|
3193
|
+
/**
|
|
3194
|
+
* Deferred loading - starts async load and updates entity when complete.
|
|
3195
|
+
* Called by synchronous load() method.
|
|
3196
|
+
*/
|
|
3197
|
+
loadModelsDeferred() {
|
|
1693
3198
|
if (this._modelFileNames.length === 0) return;
|
|
3199
|
+
this.dispatch("entity:model:loading", {
|
|
3200
|
+
entityId: this.uuid,
|
|
3201
|
+
files: this._modelFileNames
|
|
3202
|
+
});
|
|
1694
3203
|
const promises = this._modelFileNames.map((file) => this._assetLoader.loadFile(file));
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
this.
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
this.
|
|
1705
|
-
this.
|
|
1706
|
-
|
|
1707
|
-
|
|
3204
|
+
Promise.all(promises).then((results) => {
|
|
3205
|
+
if (results[0]?.object) {
|
|
3206
|
+
this._object = results[0].object;
|
|
3207
|
+
}
|
|
3208
|
+
let meshCount = 0;
|
|
3209
|
+
if (this._object) {
|
|
3210
|
+
this._object.traverse((child) => {
|
|
3211
|
+
if (child.isMesh) meshCount++;
|
|
3212
|
+
});
|
|
3213
|
+
this.group = new Group4();
|
|
3214
|
+
this.group.attach(this._object);
|
|
3215
|
+
this.group.scale.set(
|
|
3216
|
+
this.options.scale?.x || 1,
|
|
3217
|
+
this.options.scale?.y || 1,
|
|
3218
|
+
this.options.scale?.z || 1
|
|
3219
|
+
);
|
|
3220
|
+
this.applyMaterialOverrides();
|
|
3221
|
+
this._animationDelegate = new AnimationDelegate(this._object);
|
|
3222
|
+
this._animationDelegate.loadAnimations(this.options.animations || []).then(() => {
|
|
3223
|
+
this.dispatch("entity:animation:loaded", {
|
|
3224
|
+
entityId: this.uuid,
|
|
3225
|
+
animationCount: this.options.animations?.length || 0
|
|
3226
|
+
});
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
this.dispatch("entity:model:loaded", {
|
|
3230
|
+
entityId: this.uuid,
|
|
3231
|
+
success: !!this._object,
|
|
3232
|
+
meshCount
|
|
3233
|
+
});
|
|
3234
|
+
});
|
|
1708
3235
|
}
|
|
1709
3236
|
playAnimation(animationOptions) {
|
|
1710
3237
|
this._animationDelegate?.playAnimation(animationOptions);
|
|
1711
3238
|
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Apply material overrides from options to all meshes in the loaded model.
|
|
3241
|
+
* Only applies if material options are explicitly specified (not just defaults).
|
|
3242
|
+
*/
|
|
3243
|
+
applyMaterialOverrides() {
|
|
3244
|
+
const materialOptions = this.options.material;
|
|
3245
|
+
if (!materialOptions || !materialOptions.color && !materialOptions.path) {
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
if (!this._object) return;
|
|
3249
|
+
this._object.traverse((child) => {
|
|
3250
|
+
if (child.isMesh) {
|
|
3251
|
+
const mesh = child;
|
|
3252
|
+
if (materialOptions.color) {
|
|
3253
|
+
const newMaterial = new MeshStandardMaterial4({
|
|
3254
|
+
color: materialOptions.color,
|
|
3255
|
+
emissiveIntensity: 0.5,
|
|
3256
|
+
lightMapIntensity: 0.5,
|
|
3257
|
+
fog: true
|
|
3258
|
+
});
|
|
3259
|
+
mesh.castShadow = true;
|
|
3260
|
+
mesh.receiveShadow = true;
|
|
3261
|
+
mesh.material = newMaterial;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
1712
3266
|
get object() {
|
|
1713
3267
|
return this._object;
|
|
1714
3268
|
}
|
|
@@ -1745,8 +3299,8 @@ var ZylemActor = class extends GameEntity {
|
|
|
1745
3299
|
return debugInfo;
|
|
1746
3300
|
}
|
|
1747
3301
|
};
|
|
1748
|
-
|
|
1749
|
-
return
|
|
3302
|
+
function createActor(...args) {
|
|
3303
|
+
return createEntity({
|
|
1750
3304
|
args,
|
|
1751
3305
|
defaultConfig: actorDefaults,
|
|
1752
3306
|
EntityClass: ZylemActor,
|
|
@@ -1757,7 +3311,7 @@ async function actor(...args) {
|
|
|
1757
3311
|
}
|
|
1758
3312
|
|
|
1759
3313
|
// src/lib/entities/text.ts
|
|
1760
|
-
import { Color as
|
|
3314
|
+
import { Color as Color6, Group as Group5, Sprite as ThreeSprite2, SpriteMaterial as SpriteMaterial2, CanvasTexture, LinearFilter, Vector2 as Vector24, ClampToEdgeWrapping } from "three";
|
|
1761
3315
|
var textDefaults = {
|
|
1762
3316
|
position: void 0,
|
|
1763
3317
|
text: "",
|
|
@@ -1767,7 +3321,7 @@ var textDefaults = {
|
|
|
1767
3321
|
backgroundColor: null,
|
|
1768
3322
|
padding: 4,
|
|
1769
3323
|
stickToViewport: true,
|
|
1770
|
-
screenPosition: new
|
|
3324
|
+
screenPosition: new Vector24(24, 24),
|
|
1771
3325
|
zDistance: 1
|
|
1772
3326
|
};
|
|
1773
3327
|
var TextBuilder = class extends EntityBuilder {
|
|
@@ -1790,7 +3344,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1790
3344
|
this.options = { ...textDefaults, ...options };
|
|
1791
3345
|
this.prependSetup(this.textSetup.bind(this));
|
|
1792
3346
|
this.prependUpdate(this.textUpdate.bind(this));
|
|
1793
|
-
this.
|
|
3347
|
+
this.onCleanup(this.textDestroy.bind(this));
|
|
1794
3348
|
}
|
|
1795
3349
|
create() {
|
|
1796
3350
|
this._sprite = null;
|
|
@@ -1820,10 +3374,10 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1820
3374
|
this.group?.add(this._sprite);
|
|
1821
3375
|
this.redrawText(this.options.text ?? "");
|
|
1822
3376
|
}
|
|
1823
|
-
measureAndResizeCanvas(
|
|
3377
|
+
measureAndResizeCanvas(text, fontSize, fontFamily, padding) {
|
|
1824
3378
|
if (!this._canvas || !this._ctx) return { sizeChanged: false };
|
|
1825
3379
|
this._ctx.font = `${fontSize}px ${fontFamily}`;
|
|
1826
|
-
const metrics = this._ctx.measureText(
|
|
3380
|
+
const metrics = this._ctx.measureText(text);
|
|
1827
3381
|
const textWidth = Math.ceil(metrics.width);
|
|
1828
3382
|
const textHeight = Math.ceil(fontSize * 1.4);
|
|
1829
3383
|
const nextW = Math.max(2, textWidth + padding * 2);
|
|
@@ -1835,7 +3389,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1835
3389
|
this._lastCanvasH = nextH;
|
|
1836
3390
|
return { sizeChanged };
|
|
1837
3391
|
}
|
|
1838
|
-
drawCenteredText(
|
|
3392
|
+
drawCenteredText(text, fontSize, fontFamily) {
|
|
1839
3393
|
if (!this._canvas || !this._ctx) return;
|
|
1840
3394
|
this._ctx.font = `${fontSize}px ${fontFamily}`;
|
|
1841
3395
|
this._ctx.textAlign = "center";
|
|
@@ -1846,7 +3400,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1846
3400
|
this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
|
|
1847
3401
|
}
|
|
1848
3402
|
this._ctx.fillStyle = this.toCssColor(this.options.fontColor ?? "#FFFFFF");
|
|
1849
|
-
this._ctx.fillText(
|
|
3403
|
+
this._ctx.fillText(text, this._canvas.width / 2, this._canvas.height / 2);
|
|
1850
3404
|
}
|
|
1851
3405
|
updateTexture(sizeChanged) {
|
|
1852
3406
|
if (!this._texture || !this._canvas) return;
|
|
@@ -1879,7 +3433,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1879
3433
|
}
|
|
1880
3434
|
toCssColor(color) {
|
|
1881
3435
|
if (typeof color === "string") return color;
|
|
1882
|
-
const c = color instanceof
|
|
3436
|
+
const c = color instanceof Color6 ? color : new Color6(color);
|
|
1883
3437
|
return `#${c.getHexString()}`;
|
|
1884
3438
|
}
|
|
1885
3439
|
textSetup(params) {
|
|
@@ -1939,7 +3493,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1939
3493
|
if (!this._sprite || !this._cameraRef) return;
|
|
1940
3494
|
const camera = this._cameraRef.camera;
|
|
1941
3495
|
const { width, height } = this.getResolution();
|
|
1942
|
-
const sp = this.options.screenPosition ?? new
|
|
3496
|
+
const sp = this.options.screenPosition ?? new Vector24(24, 24);
|
|
1943
3497
|
const { px, py } = this.getScreenPixels(sp, width, height);
|
|
1944
3498
|
const zDist = Math.max(1e-3, this.options.zDistance ?? 1);
|
|
1945
3499
|
const { worldHalfW, worldHalfH } = this.computeWorldExtents(camera, zDist);
|
|
@@ -1970,7 +3524,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1970
3524
|
/**
|
|
1971
3525
|
* Dispose of Three.js resources when the entity is destroyed.
|
|
1972
3526
|
*/
|
|
1973
|
-
|
|
3527
|
+
textDestroy() {
|
|
1974
3528
|
this._texture?.dispose();
|
|
1975
3529
|
if (this._sprite?.material) {
|
|
1976
3530
|
this._sprite.material.dispose();
|
|
@@ -1986,7 +3540,7 @@ var ZylemText = class _ZylemText extends GameEntity {
|
|
|
1986
3540
|
this._cameraRef = null;
|
|
1987
3541
|
}
|
|
1988
3542
|
};
|
|
1989
|
-
|
|
3543
|
+
function createText(...args) {
|
|
1990
3544
|
return createEntity({
|
|
1991
3545
|
args,
|
|
1992
3546
|
defaultConfig: { ...textDefaults },
|
|
@@ -1997,7 +3551,7 @@ async function text(...args) {
|
|
|
1997
3551
|
}
|
|
1998
3552
|
|
|
1999
3553
|
// src/lib/entities/rect.ts
|
|
2000
|
-
import { Color as
|
|
3554
|
+
import { Color as Color7, Group as Group6, Sprite as ThreeSprite3, SpriteMaterial as SpriteMaterial3, CanvasTexture as CanvasTexture2, LinearFilter as LinearFilter2, Vector2 as Vector25, ClampToEdgeWrapping as ClampToEdgeWrapping2, ShaderMaterial as ShaderMaterial3, Mesh as Mesh6, PlaneGeometry as PlaneGeometry2, Vector3 as Vector313 } from "three";
|
|
2001
3555
|
var rectDefaults = {
|
|
2002
3556
|
position: void 0,
|
|
2003
3557
|
width: 120,
|
|
@@ -2008,9 +3562,9 @@ var rectDefaults = {
|
|
|
2008
3562
|
radius: 0,
|
|
2009
3563
|
padding: 0,
|
|
2010
3564
|
stickToViewport: true,
|
|
2011
|
-
screenPosition: new
|
|
3565
|
+
screenPosition: new Vector25(24, 24),
|
|
2012
3566
|
zDistance: 1,
|
|
2013
|
-
anchor: new
|
|
3567
|
+
anchor: new Vector25(0, 0)
|
|
2014
3568
|
};
|
|
2015
3569
|
var RectBuilder = class extends EntityBuilder {
|
|
2016
3570
|
createEntity(options) {
|
|
@@ -2111,6 +3665,12 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2111
3665
|
}
|
|
2112
3666
|
}
|
|
2113
3667
|
}
|
|
3668
|
+
getWidth() {
|
|
3669
|
+
return this.options.width ?? 0;
|
|
3670
|
+
}
|
|
3671
|
+
getHeight() {
|
|
3672
|
+
return this.options.height ?? 0;
|
|
3673
|
+
}
|
|
2114
3674
|
roundedRectPath(ctx, x, y, w, h, r) {
|
|
2115
3675
|
const radius = Math.min(r, Math.floor(Math.min(w, h) / 2));
|
|
2116
3676
|
ctx.moveTo(x + radius, y);
|
|
@@ -2125,9 +3685,27 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2125
3685
|
}
|
|
2126
3686
|
toCssColor(color) {
|
|
2127
3687
|
if (typeof color === "string") return color;
|
|
2128
|
-
const c = color instanceof
|
|
3688
|
+
const c = color instanceof Color7 ? color : new Color7(color);
|
|
2129
3689
|
return `#${c.getHexString()}`;
|
|
2130
3690
|
}
|
|
3691
|
+
/**
|
|
3692
|
+
* Get the viewport resolution from the renderer DOM element,
|
|
3693
|
+
* falling back to the camera's screenResolution if the renderer
|
|
3694
|
+
* is not available (e.g. when using the new RendererManager path).
|
|
3695
|
+
*/
|
|
3696
|
+
getResolution() {
|
|
3697
|
+
try {
|
|
3698
|
+
const dom = this._cameraRef?.renderer?.domElement;
|
|
3699
|
+
if (dom) {
|
|
3700
|
+
return { width: dom.clientWidth || 1, height: dom.clientHeight || 1 };
|
|
3701
|
+
}
|
|
3702
|
+
} catch {
|
|
3703
|
+
}
|
|
3704
|
+
return {
|
|
3705
|
+
width: this._cameraRef?.screenResolution?.x ?? 1,
|
|
3706
|
+
height: this._cameraRef?.screenResolution?.y ?? 1
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
2131
3709
|
rectSetup(params) {
|
|
2132
3710
|
this._cameraRef = params.camera;
|
|
2133
3711
|
if (this.options.stickToViewport && this._cameraRef) {
|
|
@@ -2143,7 +3721,7 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2143
3721
|
if (mat.uniforms?.tDiffuse) mat.uniforms.tDiffuse.value = this._texture;
|
|
2144
3722
|
if (mat.uniforms?.iResolution && this._canvas) mat.uniforms.iResolution.value.set(this._canvas.width, this._canvas.height, 1);
|
|
2145
3723
|
}
|
|
2146
|
-
this._mesh = new
|
|
3724
|
+
this._mesh = new Mesh6(new PlaneGeometry2(1, 1), mat);
|
|
2147
3725
|
this.group?.add(this._mesh);
|
|
2148
3726
|
this._sprite.visible = false;
|
|
2149
3727
|
}
|
|
@@ -2152,17 +3730,16 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2152
3730
|
rectUpdate(params) {
|
|
2153
3731
|
if (!this._sprite) return;
|
|
2154
3732
|
if (this._cameraRef && this.options.bounds) {
|
|
2155
|
-
const dom = this._cameraRef.renderer.domElement;
|
|
2156
3733
|
const screen = this.computeScreenBoundsFromOptions(this.options.bounds);
|
|
2157
3734
|
if (screen) {
|
|
2158
3735
|
const { x, y, width, height } = screen;
|
|
2159
3736
|
const desiredW = Math.max(2, Math.floor(width));
|
|
2160
3737
|
const desiredH = Math.max(2, Math.floor(height));
|
|
2161
3738
|
const changed = desiredW !== (this.options.width ?? 0) || desiredH !== (this.options.height ?? 0);
|
|
2162
|
-
this.options.screenPosition = new
|
|
3739
|
+
this.options.screenPosition = new Vector25(Math.floor(x), Math.floor(y));
|
|
2163
3740
|
this.options.width = desiredW;
|
|
2164
3741
|
this.options.height = desiredH;
|
|
2165
|
-
this.options.anchor = new
|
|
3742
|
+
this.options.anchor = new Vector25(0, 0);
|
|
2166
3743
|
if (changed) {
|
|
2167
3744
|
this.redrawRect();
|
|
2168
3745
|
}
|
|
@@ -2175,11 +3752,9 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2175
3752
|
updateStickyTransform() {
|
|
2176
3753
|
if (!this._sprite || !this._cameraRef) return;
|
|
2177
3754
|
const camera = this._cameraRef.camera;
|
|
2178
|
-
const
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
2181
|
-
const px = (this.options.screenPosition ?? new Vector24(24, 24)).x;
|
|
2182
|
-
const py = (this.options.screenPosition ?? new Vector24(24, 24)).y;
|
|
3755
|
+
const { width, height } = this.getResolution();
|
|
3756
|
+
const px = (this.options.screenPosition ?? new Vector25(24, 24)).x;
|
|
3757
|
+
const py = (this.options.screenPosition ?? new Vector25(24, 24)).y;
|
|
2183
3758
|
const zDist = Math.max(1e-3, this.options.zDistance ?? 1);
|
|
2184
3759
|
let worldHalfW = 1;
|
|
2185
3760
|
let worldHalfH = 1;
|
|
@@ -2210,7 +3785,7 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2210
3785
|
this._sprite.scale.set(scaleX, scaleY, 1);
|
|
2211
3786
|
if (this._mesh) this._mesh.scale.set(scaleX, scaleY, 1);
|
|
2212
3787
|
}
|
|
2213
|
-
const anchor = this.options.anchor ?? new
|
|
3788
|
+
const anchor = this.options.anchor ?? new Vector25(0, 0);
|
|
2214
3789
|
const ax = Math.min(100, Math.max(0, anchor.x)) / 100;
|
|
2215
3790
|
const ay = Math.min(100, Math.max(0, anchor.y)) / 100;
|
|
2216
3791
|
const offsetX = (0.5 - ax) * scaleX;
|
|
@@ -2220,22 +3795,21 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2220
3795
|
worldToScreen(point) {
|
|
2221
3796
|
if (!this._cameraRef) return { x: 0, y: 0 };
|
|
2222
3797
|
const camera = this._cameraRef.camera;
|
|
2223
|
-
const
|
|
3798
|
+
const { width, height } = this.getResolution();
|
|
2224
3799
|
const v = point.clone().project(camera);
|
|
2225
|
-
const x = (v.x + 1) / 2 *
|
|
2226
|
-
const y = (1 - v.y) / 2 *
|
|
3800
|
+
const x = (v.x + 1) / 2 * width;
|
|
3801
|
+
const y = (1 - v.y) / 2 * height;
|
|
2227
3802
|
return { x, y };
|
|
2228
3803
|
}
|
|
2229
3804
|
computeScreenBoundsFromOptions(bounds) {
|
|
2230
3805
|
if (!this._cameraRef) return null;
|
|
2231
|
-
const dom = this._cameraRef.renderer.domElement;
|
|
2232
3806
|
if (bounds.screen) {
|
|
2233
3807
|
return { ...bounds.screen };
|
|
2234
3808
|
}
|
|
2235
3809
|
if (bounds.world) {
|
|
2236
3810
|
const { left, right, top, bottom, z = 0 } = bounds.world;
|
|
2237
|
-
const tl = this.worldToScreen(new
|
|
2238
|
-
const br = this.worldToScreen(new
|
|
3811
|
+
const tl = this.worldToScreen(new Vector313(left, top, z));
|
|
3812
|
+
const br = this.worldToScreen(new Vector313(right, bottom, z));
|
|
2239
3813
|
const x = Math.min(tl.x, br.x);
|
|
2240
3814
|
const y = Math.min(tl.y, br.y);
|
|
2241
3815
|
const width = Math.abs(br.x - tl.x);
|
|
@@ -2263,7 +3837,7 @@ var ZylemRect = class _ZylemRect extends GameEntity {
|
|
|
2263
3837
|
};
|
|
2264
3838
|
}
|
|
2265
3839
|
};
|
|
2266
|
-
|
|
3840
|
+
function createRect(...args) {
|
|
2267
3841
|
return createEntity({
|
|
2268
3842
|
args,
|
|
2269
3843
|
defaultConfig: { ...rectDefaults },
|
|
@@ -2272,15 +3846,224 @@ async function rect(...args) {
|
|
|
2272
3846
|
entityType: ZylemRect.type
|
|
2273
3847
|
});
|
|
2274
3848
|
}
|
|
3849
|
+
|
|
3850
|
+
// src/lib/entities/cone.ts
|
|
3851
|
+
var coneDefaults = {
|
|
3852
|
+
...commonDefaults,
|
|
3853
|
+
radius: 1,
|
|
3854
|
+
height: 2,
|
|
3855
|
+
radialSegments: 32
|
|
3856
|
+
};
|
|
3857
|
+
var CONE_TYPE = /* @__PURE__ */ Symbol("Cone");
|
|
3858
|
+
var ZylemCone = class _ZylemCone extends GameEntity {
|
|
3859
|
+
static type = CONE_TYPE;
|
|
3860
|
+
constructor(options) {
|
|
3861
|
+
super();
|
|
3862
|
+
this.options = { ...coneDefaults, ...options };
|
|
3863
|
+
}
|
|
3864
|
+
buildInfo() {
|
|
3865
|
+
const delegate = new DebugDelegate(this);
|
|
3866
|
+
const baseInfo = delegate.buildDebugInfo();
|
|
3867
|
+
const radius = this.options.radius ?? 1;
|
|
3868
|
+
const height = this.options.height ?? 2;
|
|
3869
|
+
return {
|
|
3870
|
+
...baseInfo,
|
|
3871
|
+
type: String(_ZylemCone.type),
|
|
3872
|
+
radius: radius.toFixed(2),
|
|
3873
|
+
height: height.toFixed(2)
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
};
|
|
3877
|
+
function createCone(...args) {
|
|
3878
|
+
const options = mergeArgs(args, coneDefaults);
|
|
3879
|
+
const entity = new ZylemCone(options);
|
|
3880
|
+
entity.add(
|
|
3881
|
+
coneMesh({
|
|
3882
|
+
radius: options.radius,
|
|
3883
|
+
height: options.height,
|
|
3884
|
+
radialSegments: options.radialSegments,
|
|
3885
|
+
material: options.material,
|
|
3886
|
+
color: options.color
|
|
3887
|
+
}),
|
|
3888
|
+
coneCollision({
|
|
3889
|
+
radius: options.radius,
|
|
3890
|
+
height: options.height,
|
|
3891
|
+
static: options.collision?.static,
|
|
3892
|
+
sensor: options.collision?.sensor,
|
|
3893
|
+
collisionType: options.collisionType,
|
|
3894
|
+
collisionFilter: options.collisionFilter
|
|
3895
|
+
})
|
|
3896
|
+
);
|
|
3897
|
+
return entity;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
// src/lib/entities/pyramid.ts
|
|
3901
|
+
var pyramidDefaults = {
|
|
3902
|
+
...commonDefaults,
|
|
3903
|
+
radius: 1,
|
|
3904
|
+
height: 2
|
|
3905
|
+
};
|
|
3906
|
+
var PYRAMID_TYPE = /* @__PURE__ */ Symbol("Pyramid");
|
|
3907
|
+
var ZylemPyramid = class _ZylemPyramid extends GameEntity {
|
|
3908
|
+
static type = PYRAMID_TYPE;
|
|
3909
|
+
constructor(options) {
|
|
3910
|
+
super();
|
|
3911
|
+
this.options = { ...pyramidDefaults, ...options };
|
|
3912
|
+
}
|
|
3913
|
+
buildInfo() {
|
|
3914
|
+
const delegate = new DebugDelegate(this);
|
|
3915
|
+
const baseInfo = delegate.buildDebugInfo();
|
|
3916
|
+
const radius = this.options.radius ?? 1;
|
|
3917
|
+
const height = this.options.height ?? 2;
|
|
3918
|
+
return {
|
|
3919
|
+
...baseInfo,
|
|
3920
|
+
type: String(_ZylemPyramid.type),
|
|
3921
|
+
radius: radius.toFixed(2),
|
|
3922
|
+
height: height.toFixed(2)
|
|
3923
|
+
};
|
|
3924
|
+
}
|
|
3925
|
+
};
|
|
3926
|
+
function createPyramid(...args) {
|
|
3927
|
+
const options = mergeArgs(args, pyramidDefaults);
|
|
3928
|
+
const entity = new ZylemPyramid(options);
|
|
3929
|
+
entity.add(
|
|
3930
|
+
pyramidMesh({
|
|
3931
|
+
radius: options.radius,
|
|
3932
|
+
height: options.height,
|
|
3933
|
+
material: options.material,
|
|
3934
|
+
color: options.color
|
|
3935
|
+
}),
|
|
3936
|
+
pyramidCollision({
|
|
3937
|
+
radius: options.radius,
|
|
3938
|
+
height: options.height,
|
|
3939
|
+
static: options.collision?.static,
|
|
3940
|
+
sensor: options.collision?.sensor,
|
|
3941
|
+
collisionType: options.collisionType,
|
|
3942
|
+
collisionFilter: options.collisionFilter
|
|
3943
|
+
})
|
|
3944
|
+
);
|
|
3945
|
+
return entity;
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// src/lib/entities/cylinder.ts
|
|
3949
|
+
var cylinderDefaults = {
|
|
3950
|
+
...commonDefaults,
|
|
3951
|
+
radiusTop: 1,
|
|
3952
|
+
radiusBottom: 1,
|
|
3953
|
+
height: 2,
|
|
3954
|
+
radialSegments: 32
|
|
3955
|
+
};
|
|
3956
|
+
var CYLINDER_TYPE = /* @__PURE__ */ Symbol("Cylinder");
|
|
3957
|
+
var ZylemCylinder = class _ZylemCylinder extends GameEntity {
|
|
3958
|
+
static type = CYLINDER_TYPE;
|
|
3959
|
+
constructor(options) {
|
|
3960
|
+
super();
|
|
3961
|
+
this.options = { ...cylinderDefaults, ...options };
|
|
3962
|
+
}
|
|
3963
|
+
buildInfo() {
|
|
3964
|
+
const delegate = new DebugDelegate(this);
|
|
3965
|
+
const baseInfo = delegate.buildDebugInfo();
|
|
3966
|
+
const radiusTop = this.options.radiusTop ?? 1;
|
|
3967
|
+
const radiusBottom = this.options.radiusBottom ?? 1;
|
|
3968
|
+
const height = this.options.height ?? 2;
|
|
3969
|
+
return {
|
|
3970
|
+
...baseInfo,
|
|
3971
|
+
type: String(_ZylemCylinder.type),
|
|
3972
|
+
radiusTop: radiusTop.toFixed(2),
|
|
3973
|
+
radiusBottom: radiusBottom.toFixed(2),
|
|
3974
|
+
height: height.toFixed(2)
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3978
|
+
function createCylinder(...args) {
|
|
3979
|
+
const options = mergeArgs(args, cylinderDefaults);
|
|
3980
|
+
const entity = new ZylemCylinder(options);
|
|
3981
|
+
entity.add(
|
|
3982
|
+
cylinderMesh({
|
|
3983
|
+
radiusTop: options.radiusTop,
|
|
3984
|
+
radiusBottom: options.radiusBottom,
|
|
3985
|
+
height: options.height,
|
|
3986
|
+
radialSegments: options.radialSegments,
|
|
3987
|
+
material: options.material,
|
|
3988
|
+
color: options.color
|
|
3989
|
+
}),
|
|
3990
|
+
cylinderCollision({
|
|
3991
|
+
radiusTop: options.radiusTop,
|
|
3992
|
+
radiusBottom: options.radiusBottom,
|
|
3993
|
+
height: options.height,
|
|
3994
|
+
static: options.collision?.static,
|
|
3995
|
+
sensor: options.collision?.sensor,
|
|
3996
|
+
collisionType: options.collisionType,
|
|
3997
|
+
collisionFilter: options.collisionFilter
|
|
3998
|
+
})
|
|
3999
|
+
);
|
|
4000
|
+
return entity;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
// src/lib/entities/pill.ts
|
|
4004
|
+
var pillDefaults = {
|
|
4005
|
+
...commonDefaults,
|
|
4006
|
+
radius: 0.5,
|
|
4007
|
+
length: 1,
|
|
4008
|
+
capSegments: 10,
|
|
4009
|
+
radialSegments: 20
|
|
4010
|
+
};
|
|
4011
|
+
var PILL_TYPE = /* @__PURE__ */ Symbol("Pill");
|
|
4012
|
+
var ZylemPill = class _ZylemPill extends GameEntity {
|
|
4013
|
+
static type = PILL_TYPE;
|
|
4014
|
+
constructor(options) {
|
|
4015
|
+
super();
|
|
4016
|
+
this.options = { ...pillDefaults, ...options };
|
|
4017
|
+
}
|
|
4018
|
+
buildInfo() {
|
|
4019
|
+
const delegate = new DebugDelegate(this);
|
|
4020
|
+
const baseInfo = delegate.buildDebugInfo();
|
|
4021
|
+
const radius = this.options.radius ?? 0.5;
|
|
4022
|
+
const length = this.options.length ?? 1;
|
|
4023
|
+
return {
|
|
4024
|
+
...baseInfo,
|
|
4025
|
+
type: String(_ZylemPill.type),
|
|
4026
|
+
radius: radius.toFixed(2),
|
|
4027
|
+
length: length.toFixed(2)
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
function createPill(...args) {
|
|
4032
|
+
const options = mergeArgs(args, pillDefaults);
|
|
4033
|
+
const entity = new ZylemPill(options);
|
|
4034
|
+
entity.add(
|
|
4035
|
+
pillMesh({
|
|
4036
|
+
radius: options.radius,
|
|
4037
|
+
length: options.length,
|
|
4038
|
+
capSegments: options.capSegments,
|
|
4039
|
+
radialSegments: options.radialSegments,
|
|
4040
|
+
material: options.material,
|
|
4041
|
+
color: options.color
|
|
4042
|
+
}),
|
|
4043
|
+
pillCollision({
|
|
4044
|
+
radius: options.radius,
|
|
4045
|
+
length: options.length,
|
|
4046
|
+
static: options.collision?.static,
|
|
4047
|
+
sensor: options.collision?.sensor,
|
|
4048
|
+
collisionType: options.collisionType,
|
|
4049
|
+
collisionFilter: options.collisionFilter
|
|
4050
|
+
})
|
|
4051
|
+
);
|
|
4052
|
+
return entity;
|
|
4053
|
+
}
|
|
2275
4054
|
export {
|
|
2276
4055
|
ZylemBox,
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
4056
|
+
createActor,
|
|
4057
|
+
createBox,
|
|
4058
|
+
createCone,
|
|
4059
|
+
createCylinder,
|
|
4060
|
+
createPill,
|
|
4061
|
+
createPlane,
|
|
4062
|
+
createPyramid,
|
|
4063
|
+
createRect,
|
|
4064
|
+
createSphere,
|
|
4065
|
+
createSprite,
|
|
4066
|
+
createText,
|
|
4067
|
+
createZone
|
|
2285
4068
|
};
|
|
2286
4069
|
//# sourceMappingURL=entities.js.map
|