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