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