@zylem/game-lib 0.5.1 → 0.6.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/LICENSE +21 -0
- package/dist/behaviors.d.ts +1 -3
- package/dist/behaviors.js.map +1 -1
- package/dist/blueprints-BOCc3Wve.d.ts +26 -0
- package/dist/{camera-Dk-fOVZE.d.ts → camera-CpbDr4-V.d.ts} +19 -45
- package/dist/camera.d.ts +2 -2
- package/dist/camera.js +217 -113
- package/dist/camera.js.map +1 -1
- package/dist/{core-C2mjetAd.d.ts → core-CZhozNRH.d.ts} +77 -42
- package/dist/core.d.ts +6 -5
- package/dist/core.js +1676 -695
- package/dist/core.js.map +1 -1
- package/dist/entities-BAxfJOkk.d.ts +307 -0
- package/dist/entities.d.ts +4 -267
- package/dist/entities.js +213 -90
- package/dist/entities.js.map +1 -1
- package/dist/entity-Bq_eNEDI.d.ts +28 -0
- package/dist/{entity-bQElAdpo.d.ts → entity-COvRtFNG.d.ts} +68 -20
- package/dist/main.d.ts +88 -12
- package/dist/main.js +1811 -810
- package/dist/main.js.map +1 -1
- package/dist/{stage-CrmY7V0i.d.ts → stage-types-CD21XoIU.d.ts} +90 -39
- package/dist/stage.d.ts +40 -20
- package/dist/stage.js +1210 -674
- package/dist/stage.js.map +1 -1
- package/package.json +10 -10
- package/dist/entity-spawner-DNnLYnZq.d.ts +0 -11
package/dist/stage.js
CHANGED
|
@@ -1,12 +1,56 @@
|
|
|
1
1
|
// src/lib/stage/zylem-stage.ts
|
|
2
2
|
import { addComponent, addEntity, createWorld as createECS, removeEntity } from "bitecs";
|
|
3
|
-
import { Color as
|
|
3
|
+
import { Color as Color7, Vector3 as Vector311 } from "three";
|
|
4
4
|
|
|
5
5
|
// src/lib/collision/world.ts
|
|
6
6
|
import RAPIER from "@dimforge/rapier3d-compat";
|
|
7
7
|
|
|
8
8
|
// src/lib/game/game-state.ts
|
|
9
9
|
import { proxy, subscribe } from "valtio/vanilla";
|
|
10
|
+
|
|
11
|
+
// src/lib/game/game-event-bus.ts
|
|
12
|
+
var GameEventBus = class {
|
|
13
|
+
listeners = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Subscribe to an event type.
|
|
16
|
+
*/
|
|
17
|
+
on(event, callback) {
|
|
18
|
+
if (!this.listeners.has(event)) {
|
|
19
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
20
|
+
}
|
|
21
|
+
this.listeners.get(event).add(callback);
|
|
22
|
+
return () => this.off(event, callback);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Unsubscribe from an event type.
|
|
26
|
+
*/
|
|
27
|
+
off(event, callback) {
|
|
28
|
+
this.listeners.get(event)?.delete(callback);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Emit an event to all subscribers.
|
|
32
|
+
*/
|
|
33
|
+
emit(event, payload) {
|
|
34
|
+
const callbacks = this.listeners.get(event);
|
|
35
|
+
if (!callbacks) return;
|
|
36
|
+
for (const cb of callbacks) {
|
|
37
|
+
try {
|
|
38
|
+
cb(payload);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error(`Error in event handler for ${event}`, e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clear all listeners.
|
|
46
|
+
*/
|
|
47
|
+
dispose() {
|
|
48
|
+
this.listeners.clear();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var gameEventBus = new GameEventBus();
|
|
52
|
+
|
|
53
|
+
// src/lib/game/game-state.ts
|
|
10
54
|
var state = proxy({
|
|
11
55
|
id: "",
|
|
12
56
|
globals: {},
|
|
@@ -28,7 +72,8 @@ import {
|
|
|
28
72
|
defineSystem,
|
|
29
73
|
defineQuery,
|
|
30
74
|
defineComponent,
|
|
31
|
-
Types
|
|
75
|
+
Types,
|
|
76
|
+
removeQuery
|
|
32
77
|
} from "bitecs";
|
|
33
78
|
import { Quaternion } from "three";
|
|
34
79
|
var position = defineComponent({
|
|
@@ -47,52 +92,49 @@ var scale = defineComponent({
|
|
|
47
92
|
y: Types.f32,
|
|
48
93
|
z: Types.f32
|
|
49
94
|
});
|
|
95
|
+
var _tempQuaternion = new Quaternion();
|
|
50
96
|
function createTransformSystem(stage) {
|
|
51
|
-
const
|
|
97
|
+
const queryTerms = [position, rotation];
|
|
98
|
+
const transformQuery = defineQuery(queryTerms);
|
|
52
99
|
const stageEntities = stage._childrenMap;
|
|
53
|
-
|
|
100
|
+
const system = defineSystem((world) => {
|
|
54
101
|
const entities = transformQuery(world);
|
|
55
102
|
if (stageEntities === void 0) {
|
|
56
103
|
return world;
|
|
57
104
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const id = entities[key];
|
|
61
|
-
const stageEntity = value;
|
|
62
|
-
if (stageEntity === void 0 || !stageEntity?.body || stageEntity.markedForRemoval) {
|
|
105
|
+
for (const [key, stageEntity] of stageEntities) {
|
|
106
|
+
if (!stageEntity?.body || stageEntity.markedForRemoval) {
|
|
63
107
|
continue;
|
|
64
108
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
109
|
+
const id = entities[key];
|
|
110
|
+
const body = stageEntity.body;
|
|
111
|
+
const target = stageEntity.group ?? stageEntity.mesh;
|
|
112
|
+
const translation = body.translation();
|
|
113
|
+
position.x[id] = translation.x;
|
|
114
|
+
position.y[id] = translation.y;
|
|
115
|
+
position.z[id] = translation.z;
|
|
116
|
+
if (target) {
|
|
117
|
+
target.position.set(translation.x, translation.y, translation.z);
|
|
73
118
|
}
|
|
74
119
|
if (stageEntity.controlledRotation) {
|
|
75
120
|
continue;
|
|
76
121
|
}
|
|
77
|
-
const
|
|
78
|
-
rotation.x[id] =
|
|
79
|
-
rotation.y[id] =
|
|
80
|
-
rotation.z[id] =
|
|
81
|
-
rotation.w[id] =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
rotation.z[id],
|
|
86
|
-
rotation.w[id]
|
|
87
|
-
);
|
|
88
|
-
if (stageEntity.group) {
|
|
89
|
-
stageEntity.group.setRotationFromQuaternion(newRotation);
|
|
90
|
-
} else if (stageEntity.mesh) {
|
|
91
|
-
stageEntity.mesh.setRotationFromQuaternion(newRotation);
|
|
122
|
+
const rot = body.rotation();
|
|
123
|
+
rotation.x[id] = rot.x;
|
|
124
|
+
rotation.y[id] = rot.y;
|
|
125
|
+
rotation.z[id] = rot.z;
|
|
126
|
+
rotation.w[id] = rot.w;
|
|
127
|
+
if (target) {
|
|
128
|
+
_tempQuaternion.set(rot.x, rot.y, rot.z, rot.w);
|
|
129
|
+
target.setRotationFromQuaternion(_tempQuaternion);
|
|
92
130
|
}
|
|
93
131
|
}
|
|
94
132
|
return world;
|
|
95
133
|
});
|
|
134
|
+
const destroy = (world) => {
|
|
135
|
+
removeQuery(world, transformQuery);
|
|
136
|
+
};
|
|
137
|
+
return { system, destroy };
|
|
96
138
|
}
|
|
97
139
|
|
|
98
140
|
// src/lib/core/flags.ts
|
|
@@ -108,21 +150,76 @@ var BaseNode = class _BaseNode {
|
|
|
108
150
|
uuid = "";
|
|
109
151
|
name = "";
|
|
110
152
|
markedForRemoval = false;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Lifecycle callback arrays - use onSetup(), onUpdate(), etc. to add callbacks
|
|
155
|
+
*/
|
|
156
|
+
lifecycleCallbacks = {
|
|
157
|
+
setup: [],
|
|
158
|
+
loaded: [],
|
|
159
|
+
update: [],
|
|
160
|
+
destroy: [],
|
|
161
|
+
cleanup: []
|
|
120
162
|
};
|
|
121
163
|
constructor(args = []) {
|
|
122
164
|
const options = args.filter((arg) => !(arg instanceof _BaseNode)).reduce((acc, opt) => ({ ...acc, ...opt }), {});
|
|
123
165
|
this.options = options;
|
|
124
166
|
this.uuid = nanoid();
|
|
125
167
|
}
|
|
168
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
169
|
+
// Fluent API for adding lifecycle callbacks
|
|
170
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Add setup callbacks to be executed in order during nodeSetup
|
|
173
|
+
*/
|
|
174
|
+
onSetup(...callbacks) {
|
|
175
|
+
this.lifecycleCallbacks.setup.push(...callbacks);
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Add loaded callbacks to be executed in order during nodeLoaded
|
|
180
|
+
*/
|
|
181
|
+
onLoaded(...callbacks) {
|
|
182
|
+
this.lifecycleCallbacks.loaded.push(...callbacks);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Add update callbacks to be executed in order during nodeUpdate
|
|
187
|
+
*/
|
|
188
|
+
onUpdate(...callbacks) {
|
|
189
|
+
this.lifecycleCallbacks.update.push(...callbacks);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Add destroy callbacks to be executed in order during nodeDestroy
|
|
194
|
+
*/
|
|
195
|
+
onDestroy(...callbacks) {
|
|
196
|
+
this.lifecycleCallbacks.destroy.push(...callbacks);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Add cleanup callbacks to be executed in order during nodeCleanup
|
|
201
|
+
*/
|
|
202
|
+
onCleanup(...callbacks) {
|
|
203
|
+
this.lifecycleCallbacks.cleanup.push(...callbacks);
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Prepend setup callbacks (run before existing ones)
|
|
208
|
+
*/
|
|
209
|
+
prependSetup(...callbacks) {
|
|
210
|
+
this.lifecycleCallbacks.setup.unshift(...callbacks);
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Prepend update callbacks (run before existing ones)
|
|
215
|
+
*/
|
|
216
|
+
prependUpdate(...callbacks) {
|
|
217
|
+
this.lifecycleCallbacks.update.unshift(...callbacks);
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
221
|
+
// Tree structure
|
|
222
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
126
223
|
setParent(parent) {
|
|
127
224
|
this.parent = parent;
|
|
128
225
|
}
|
|
@@ -146,6 +243,9 @@ var BaseNode = class _BaseNode {
|
|
|
146
243
|
isComposite() {
|
|
147
244
|
return this.children.length > 0;
|
|
148
245
|
}
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
// Node lifecycle execution - runs internal + callback arrays
|
|
248
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
149
249
|
nodeSetup(params) {
|
|
150
250
|
if (DEBUG_FLAG) {
|
|
151
251
|
}
|
|
@@ -153,8 +253,8 @@ var BaseNode = class _BaseNode {
|
|
|
153
253
|
if (typeof this._setup === "function") {
|
|
154
254
|
this._setup(params);
|
|
155
255
|
}
|
|
156
|
-
|
|
157
|
-
|
|
256
|
+
for (const callback of this.lifecycleCallbacks.setup) {
|
|
257
|
+
callback(params);
|
|
158
258
|
}
|
|
159
259
|
this.children.forEach((child) => child.nodeSetup(params));
|
|
160
260
|
}
|
|
@@ -165,21 +265,40 @@ var BaseNode = class _BaseNode {
|
|
|
165
265
|
if (typeof this._update === "function") {
|
|
166
266
|
this._update(params);
|
|
167
267
|
}
|
|
168
|
-
|
|
169
|
-
|
|
268
|
+
for (const callback of this.lifecycleCallbacks.update) {
|
|
269
|
+
callback(params);
|
|
170
270
|
}
|
|
171
271
|
this.children.forEach((child) => child.nodeUpdate(params));
|
|
172
272
|
}
|
|
173
273
|
nodeDestroy(params) {
|
|
174
274
|
this.children.forEach((child) => child.nodeDestroy(params));
|
|
175
|
-
|
|
176
|
-
|
|
275
|
+
for (const callback of this.lifecycleCallbacks.destroy) {
|
|
276
|
+
callback(params);
|
|
177
277
|
}
|
|
178
278
|
if (typeof this._destroy === "function") {
|
|
179
279
|
this._destroy(params);
|
|
180
280
|
}
|
|
181
281
|
this.markedForRemoval = true;
|
|
182
282
|
}
|
|
283
|
+
async nodeLoaded(params) {
|
|
284
|
+
if (typeof this._loaded === "function") {
|
|
285
|
+
await this._loaded(params);
|
|
286
|
+
}
|
|
287
|
+
for (const callback of this.lifecycleCallbacks.loaded) {
|
|
288
|
+
callback(params);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async nodeCleanup(params) {
|
|
292
|
+
for (const callback of this.lifecycleCallbacks.cleanup) {
|
|
293
|
+
callback(params);
|
|
294
|
+
}
|
|
295
|
+
if (typeof this._cleanup === "function") {
|
|
296
|
+
await this._cleanup(params);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
300
|
+
// Options
|
|
301
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
183
302
|
getOptions() {
|
|
184
303
|
return this.options;
|
|
185
304
|
}
|
|
@@ -201,11 +320,6 @@ var GameEntity = class extends BaseNode {
|
|
|
201
320
|
custom = {};
|
|
202
321
|
debugInfo = {};
|
|
203
322
|
debugMaterial;
|
|
204
|
-
lifeCycleDelegate = {
|
|
205
|
-
setup: [],
|
|
206
|
-
update: [],
|
|
207
|
-
destroy: []
|
|
208
|
-
};
|
|
209
323
|
collisionDelegate = {
|
|
210
324
|
collision: []
|
|
211
325
|
};
|
|
@@ -230,67 +344,40 @@ var GameEntity = class extends BaseNode {
|
|
|
230
344
|
this.name = this.options.name || "";
|
|
231
345
|
return this;
|
|
232
346
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
...this.lifeCycleDelegate,
|
|
237
|
-
setup: combineCallbacks
|
|
238
|
-
};
|
|
239
|
-
return this;
|
|
240
|
-
}
|
|
241
|
-
onUpdate(...callbacks) {
|
|
242
|
-
const combineCallbacks = [...this.lifeCycleDelegate.update ?? [], ...callbacks];
|
|
243
|
-
this.lifeCycleDelegate = {
|
|
244
|
-
...this.lifeCycleDelegate,
|
|
245
|
-
update: combineCallbacks
|
|
246
|
-
};
|
|
247
|
-
return this;
|
|
248
|
-
}
|
|
249
|
-
onDestroy(...callbacks) {
|
|
250
|
-
this.lifeCycleDelegate = {
|
|
251
|
-
...this.lifeCycleDelegate,
|
|
252
|
-
destroy: callbacks.length > 0 ? callbacks : void 0
|
|
253
|
-
};
|
|
254
|
-
return this;
|
|
255
|
-
}
|
|
347
|
+
/**
|
|
348
|
+
* Add collision callbacks
|
|
349
|
+
*/
|
|
256
350
|
onCollision(...callbacks) {
|
|
257
|
-
this.collisionDelegate
|
|
258
|
-
|
|
259
|
-
};
|
|
351
|
+
const existing = this.collisionDelegate.collision ?? [];
|
|
352
|
+
this.collisionDelegate.collision = [...existing, ...callbacks];
|
|
260
353
|
return this;
|
|
261
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Entity-specific setup - runs behavior callbacks
|
|
357
|
+
* (User callbacks are handled by BaseNode's lifecycleCallbacks.setup)
|
|
358
|
+
*/
|
|
262
359
|
_setup(params) {
|
|
263
360
|
this.behaviorCallbackMap.setup.forEach((callback) => {
|
|
264
361
|
callback({ ...params, me: this });
|
|
265
362
|
});
|
|
266
|
-
if (this.lifeCycleDelegate.setup?.length) {
|
|
267
|
-
const callbacks = this.lifeCycleDelegate.setup;
|
|
268
|
-
callbacks.forEach((callback) => {
|
|
269
|
-
callback({ ...params, me: this });
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
363
|
}
|
|
273
364
|
async _loaded(_params) {
|
|
274
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Entity-specific update - updates materials and runs behavior callbacks
|
|
368
|
+
* (User callbacks are handled by BaseNode's lifecycleCallbacks.update)
|
|
369
|
+
*/
|
|
275
370
|
_update(params) {
|
|
276
371
|
this.updateMaterials(params);
|
|
277
|
-
if (this.lifeCycleDelegate.update?.length) {
|
|
278
|
-
const callbacks = this.lifeCycleDelegate.update;
|
|
279
|
-
callbacks.forEach((callback) => {
|
|
280
|
-
callback({ ...params, me: this });
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
372
|
this.behaviorCallbackMap.update.forEach((callback) => {
|
|
284
373
|
callback({ ...params, me: this });
|
|
285
374
|
});
|
|
286
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Entity-specific destroy - runs behavior callbacks
|
|
378
|
+
* (User callbacks are handled by BaseNode's lifecycleCallbacks.destroy)
|
|
379
|
+
*/
|
|
287
380
|
_destroy(params) {
|
|
288
|
-
if (this.lifeCycleDelegate.destroy?.length) {
|
|
289
|
-
const callbacks = this.lifeCycleDelegate.destroy;
|
|
290
|
-
callbacks.forEach((callback) => {
|
|
291
|
-
callback({ ...params, me: this });
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
381
|
this.behaviorCallbackMap.destroy.forEach((callback) => {
|
|
295
382
|
callback({ ...params, me: this });
|
|
296
383
|
});
|
|
@@ -481,6 +568,23 @@ var AnimationDelegate = class {
|
|
|
481
568
|
this._currentAction = action;
|
|
482
569
|
this._currentKey = key;
|
|
483
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Dispose of all animation resources
|
|
573
|
+
*/
|
|
574
|
+
dispose() {
|
|
575
|
+
Object.values(this._actions).forEach((action) => {
|
|
576
|
+
action.stop();
|
|
577
|
+
});
|
|
578
|
+
if (this._mixer) {
|
|
579
|
+
this._mixer.stopAllAction();
|
|
580
|
+
this._mixer.uncacheRoot(this.target);
|
|
581
|
+
this._mixer = null;
|
|
582
|
+
}
|
|
583
|
+
this._actions = {};
|
|
584
|
+
this._animations = [];
|
|
585
|
+
this._currentAction = null;
|
|
586
|
+
this._currentKey = "";
|
|
587
|
+
}
|
|
484
588
|
get currentAnimationKey() {
|
|
485
589
|
return this._currentKey;
|
|
486
590
|
}
|
|
@@ -506,7 +610,7 @@ var actorDefaults = {
|
|
|
506
610
|
animations: [],
|
|
507
611
|
models: []
|
|
508
612
|
};
|
|
509
|
-
var ACTOR_TYPE = Symbol("Actor");
|
|
613
|
+
var ACTOR_TYPE = /* @__PURE__ */ Symbol("Actor");
|
|
510
614
|
var ZylemActor = class extends GameEntity {
|
|
511
615
|
static type = ACTOR_TYPE;
|
|
512
616
|
_object = null;
|
|
@@ -517,9 +621,7 @@ var ZylemActor = class extends GameEntity {
|
|
|
517
621
|
constructor(options) {
|
|
518
622
|
super();
|
|
519
623
|
this.options = { ...actorDefaults, ...options };
|
|
520
|
-
this.
|
|
521
|
-
update: [this.actorUpdate.bind(this)]
|
|
522
|
-
};
|
|
624
|
+
this.prependUpdate(this.actorUpdate.bind(this));
|
|
523
625
|
this.controlledRotation = true;
|
|
524
626
|
}
|
|
525
627
|
async load() {
|
|
@@ -539,6 +641,34 @@ var ZylemActor = class extends GameEntity {
|
|
|
539
641
|
async actorUpdate(params) {
|
|
540
642
|
this._animationDelegate?.update(params.delta);
|
|
541
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Clean up actor resources including animations, models, and groups
|
|
646
|
+
*/
|
|
647
|
+
actorDestroy() {
|
|
648
|
+
if (this._animationDelegate) {
|
|
649
|
+
this._animationDelegate.dispose();
|
|
650
|
+
this._animationDelegate = null;
|
|
651
|
+
}
|
|
652
|
+
if (this._object) {
|
|
653
|
+
this._object.traverse((child) => {
|
|
654
|
+
if (child.isMesh) {
|
|
655
|
+
const mesh = child;
|
|
656
|
+
mesh.geometry?.dispose();
|
|
657
|
+
if (Array.isArray(mesh.material)) {
|
|
658
|
+
mesh.material.forEach((m) => m.dispose());
|
|
659
|
+
} else if (mesh.material) {
|
|
660
|
+
mesh.material.dispose();
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
this._object = null;
|
|
665
|
+
}
|
|
666
|
+
if (this.group) {
|
|
667
|
+
this.group.clear();
|
|
668
|
+
this.group = null;
|
|
669
|
+
}
|
|
670
|
+
this._modelFileNames = [];
|
|
671
|
+
}
|
|
542
672
|
async loadModels() {
|
|
543
673
|
if (this._modelFileNames.length === 0) return;
|
|
544
674
|
const promises = this._modelFileNames.map((file) => this._assetLoader.loadFile(file));
|
|
@@ -596,12 +726,10 @@ var ZylemActor = class extends GameEntity {
|
|
|
596
726
|
}
|
|
597
727
|
};
|
|
598
728
|
|
|
599
|
-
// src/lib/collision/
|
|
729
|
+
// src/lib/collision/world.ts
|
|
600
730
|
function isCollisionHandlerDelegate(obj) {
|
|
601
731
|
return typeof obj?.handlePostCollision === "function" && typeof obj?.handleIntersectionEvent === "function";
|
|
602
732
|
}
|
|
603
|
-
|
|
604
|
-
// src/lib/collision/world.ts
|
|
605
733
|
var ZylemWorld = class {
|
|
606
734
|
type = "World";
|
|
607
735
|
world;
|
|
@@ -827,7 +955,11 @@ var ZylemScene = class {
|
|
|
827
955
|
* Setup camera with the scene
|
|
828
956
|
*/
|
|
829
957
|
setupCamera(scene, camera) {
|
|
830
|
-
|
|
958
|
+
if (camera.cameraRig) {
|
|
959
|
+
scene.add(camera.cameraRig);
|
|
960
|
+
} else {
|
|
961
|
+
scene.add(camera.camera);
|
|
962
|
+
}
|
|
831
963
|
camera.setup(scene);
|
|
832
964
|
}
|
|
833
965
|
/**
|
|
@@ -922,6 +1054,9 @@ var ZylemBlueColor = new Color4("#0333EC");
|
|
|
922
1054
|
var Vec0 = new Vector34(0, 0, 0);
|
|
923
1055
|
var Vec1 = new Vector34(1, 1, 1);
|
|
924
1056
|
|
|
1057
|
+
// src/lib/stage/zylem-stage.ts
|
|
1058
|
+
import { subscribe as subscribe4 } from "valtio/vanilla";
|
|
1059
|
+
|
|
925
1060
|
// src/lib/core/lifecycle-base.ts
|
|
926
1061
|
var LifeCycleBase = class {
|
|
927
1062
|
update = () => {
|
|
@@ -959,67 +1094,334 @@ var LifeCycleBase = class {
|
|
|
959
1094
|
// src/lib/stage/zylem-stage.ts
|
|
960
1095
|
import { nanoid as nanoid2 } from "nanoid";
|
|
961
1096
|
|
|
962
|
-
// src/lib/
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
ThirdPerson: "third-person",
|
|
966
|
-
Isometric: "isometric",
|
|
967
|
-
Flat2D: "flat-2d",
|
|
968
|
-
Fixed2D: "fixed-2d"
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
// src/lib/camera/camera.ts
|
|
972
|
-
import { Vector2 as Vector23, Vector3 as Vector37 } from "three";
|
|
973
|
-
|
|
974
|
-
// src/lib/camera/zylem-camera.ts
|
|
975
|
-
import { PerspectiveCamera, Vector3 as Vector36, Object3D as Object3D4, OrthographicCamera, WebGLRenderer as WebGLRenderer3 } from "three";
|
|
976
|
-
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
1097
|
+
// src/lib/stage/stage-debug-delegate.ts
|
|
1098
|
+
import { Ray } from "@dimforge/rapier3d-compat";
|
|
1099
|
+
import { BufferAttribute, BufferGeometry as BufferGeometry2, LineBasicMaterial as LineBasicMaterial2, LineSegments as LineSegments2, Raycaster, Vector2 } from "three";
|
|
977
1100
|
|
|
978
|
-
// src/lib/
|
|
979
|
-
import {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1101
|
+
// src/lib/stage/debug-entity-cursor.ts
|
|
1102
|
+
import {
|
|
1103
|
+
Box3,
|
|
1104
|
+
BoxGeometry,
|
|
1105
|
+
Color as Color5,
|
|
1106
|
+
EdgesGeometry,
|
|
1107
|
+
Group as Group3,
|
|
1108
|
+
LineBasicMaterial,
|
|
1109
|
+
LineSegments,
|
|
1110
|
+
Mesh as Mesh2,
|
|
1111
|
+
MeshBasicMaterial,
|
|
1112
|
+
Vector3 as Vector35
|
|
1113
|
+
} from "three";
|
|
1114
|
+
var DebugEntityCursor = class {
|
|
1115
|
+
scene;
|
|
1116
|
+
container;
|
|
1117
|
+
fillMesh;
|
|
1118
|
+
edgeLines;
|
|
1119
|
+
currentColor = new Color5(65280);
|
|
1120
|
+
bbox = new Box3();
|
|
1121
|
+
size = new Vector35();
|
|
1122
|
+
center = new Vector35();
|
|
1123
|
+
constructor(scene) {
|
|
996
1124
|
this.scene = scene;
|
|
997
|
-
|
|
1125
|
+
const initialGeometry = new BoxGeometry(1, 1, 1);
|
|
1126
|
+
this.fillMesh = new Mesh2(
|
|
1127
|
+
initialGeometry,
|
|
1128
|
+
new MeshBasicMaterial({
|
|
1129
|
+
color: this.currentColor,
|
|
1130
|
+
transparent: true,
|
|
1131
|
+
opacity: 0.12,
|
|
1132
|
+
depthWrite: false
|
|
1133
|
+
})
|
|
1134
|
+
);
|
|
1135
|
+
const edges = new EdgesGeometry(initialGeometry);
|
|
1136
|
+
this.edgeLines = new LineSegments(
|
|
1137
|
+
edges,
|
|
1138
|
+
new LineBasicMaterial({ color: this.currentColor, linewidth: 1 })
|
|
1139
|
+
);
|
|
1140
|
+
this.container = new Group3();
|
|
1141
|
+
this.container.name = "DebugEntityCursor";
|
|
1142
|
+
this.container.add(this.fillMesh);
|
|
1143
|
+
this.container.add(this.edgeLines);
|
|
1144
|
+
this.container.visible = false;
|
|
1145
|
+
this.scene.add(this.container);
|
|
1146
|
+
}
|
|
1147
|
+
setColor(color) {
|
|
1148
|
+
this.currentColor.set(color);
|
|
1149
|
+
this.fillMesh.material.color.set(this.currentColor);
|
|
1150
|
+
this.edgeLines.material.color.set(this.currentColor);
|
|
998
1151
|
}
|
|
999
1152
|
/**
|
|
1000
|
-
* Update the
|
|
1153
|
+
* Update the cursor to enclose the provided Object3D using a world-space AABB.
|
|
1001
1154
|
*/
|
|
1002
|
-
|
|
1003
|
-
if (!
|
|
1155
|
+
updateFromObject(object) {
|
|
1156
|
+
if (!object) {
|
|
1157
|
+
this.hide();
|
|
1004
1158
|
return;
|
|
1005
1159
|
}
|
|
1006
|
-
|
|
1007
|
-
this.
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
/**
|
|
1011
|
-
* Handle resize events
|
|
1012
|
-
*/
|
|
1013
|
-
resize(width, height) {
|
|
1014
|
-
if (this.screenResolution) {
|
|
1015
|
-
this.screenResolution.set(width, height);
|
|
1160
|
+
this.bbox.setFromObject(object);
|
|
1161
|
+
if (!isFinite(this.bbox.min.x) || !isFinite(this.bbox.max.x)) {
|
|
1162
|
+
this.hide();
|
|
1163
|
+
return;
|
|
1016
1164
|
}
|
|
1165
|
+
this.bbox.getSize(this.size);
|
|
1166
|
+
this.bbox.getCenter(this.center);
|
|
1167
|
+
const newGeom = new BoxGeometry(
|
|
1168
|
+
Math.max(this.size.x, 1e-6),
|
|
1169
|
+
Math.max(this.size.y, 1e-6),
|
|
1170
|
+
Math.max(this.size.z, 1e-6)
|
|
1171
|
+
);
|
|
1172
|
+
this.fillMesh.geometry.dispose();
|
|
1173
|
+
this.fillMesh.geometry = newGeom;
|
|
1174
|
+
const newEdges = new EdgesGeometry(newGeom);
|
|
1175
|
+
this.edgeLines.geometry.dispose();
|
|
1176
|
+
this.edgeLines.geometry = newEdges;
|
|
1177
|
+
this.container.position.copy(this.center);
|
|
1178
|
+
this.container.visible = true;
|
|
1017
1179
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
this.
|
|
1180
|
+
hide() {
|
|
1181
|
+
this.container.visible = false;
|
|
1182
|
+
}
|
|
1183
|
+
dispose() {
|
|
1184
|
+
this.scene.remove(this.container);
|
|
1185
|
+
this.fillMesh.geometry.dispose();
|
|
1186
|
+
this.fillMesh.material.dispose();
|
|
1187
|
+
this.edgeLines.geometry.dispose();
|
|
1188
|
+
this.edgeLines.material.dispose();
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
// src/lib/stage/stage-debug-delegate.ts
|
|
1193
|
+
var SELECT_TOOL_COLOR = 2293538;
|
|
1194
|
+
var DELETE_TOOL_COLOR = 16724787;
|
|
1195
|
+
var StageDebugDelegate = class {
|
|
1196
|
+
stage;
|
|
1197
|
+
options;
|
|
1198
|
+
mouseNdc = new Vector2(-2, -2);
|
|
1199
|
+
raycaster = new Raycaster();
|
|
1200
|
+
isMouseDown = false;
|
|
1201
|
+
disposeFns = [];
|
|
1202
|
+
debugCursor = null;
|
|
1203
|
+
debugLines = null;
|
|
1204
|
+
constructor(stage, options) {
|
|
1205
|
+
this.stage = stage;
|
|
1206
|
+
this.options = {
|
|
1207
|
+
maxRayDistance: options?.maxRayDistance ?? 5e3,
|
|
1208
|
+
addEntityFactory: options?.addEntityFactory ?? null
|
|
1209
|
+
};
|
|
1210
|
+
if (this.stage.scene) {
|
|
1211
|
+
this.debugLines = new LineSegments2(
|
|
1212
|
+
new BufferGeometry2(),
|
|
1213
|
+
new LineBasicMaterial2({ vertexColors: true })
|
|
1214
|
+
);
|
|
1215
|
+
this.stage.scene.scene.add(this.debugLines);
|
|
1216
|
+
this.debugLines.visible = true;
|
|
1217
|
+
this.debugCursor = new DebugEntityCursor(this.stage.scene.scene);
|
|
1218
|
+
}
|
|
1219
|
+
this.attachDomListeners();
|
|
1220
|
+
}
|
|
1221
|
+
update() {
|
|
1222
|
+
if (!debugState.enabled) return;
|
|
1223
|
+
if (!this.stage.scene || !this.stage.world || !this.stage.cameraRef) return;
|
|
1224
|
+
const { world, cameraRef } = this.stage;
|
|
1225
|
+
if (this.debugLines) {
|
|
1226
|
+
const { vertices, colors } = world.world.debugRender();
|
|
1227
|
+
this.debugLines.geometry.setAttribute("position", new BufferAttribute(vertices, 3));
|
|
1228
|
+
this.debugLines.geometry.setAttribute("color", new BufferAttribute(colors, 4));
|
|
1229
|
+
}
|
|
1230
|
+
const tool = getDebugTool();
|
|
1231
|
+
const isCursorTool = tool === "select" || tool === "delete";
|
|
1232
|
+
this.raycaster.setFromCamera(this.mouseNdc, cameraRef.camera);
|
|
1233
|
+
const origin = this.raycaster.ray.origin.clone();
|
|
1234
|
+
const direction = this.raycaster.ray.direction.clone().normalize();
|
|
1235
|
+
const rapierRay = new Ray(
|
|
1236
|
+
{ x: origin.x, y: origin.y, z: origin.z },
|
|
1237
|
+
{ x: direction.x, y: direction.y, z: direction.z }
|
|
1238
|
+
);
|
|
1239
|
+
const hit = world.world.castRay(rapierRay, this.options.maxRayDistance, true);
|
|
1240
|
+
if (hit && isCursorTool) {
|
|
1241
|
+
const rigidBody = hit.collider?._parent;
|
|
1242
|
+
const hoveredUuid2 = rigidBody?.userData?.uuid;
|
|
1243
|
+
if (hoveredUuid2) {
|
|
1244
|
+
const entity = this.stage._debugMap.get(hoveredUuid2);
|
|
1245
|
+
if (entity) setHoveredEntity(entity);
|
|
1246
|
+
} else {
|
|
1247
|
+
resetHoveredEntity();
|
|
1248
|
+
}
|
|
1249
|
+
if (this.isMouseDown) {
|
|
1250
|
+
this.handleActionOnHit(hoveredUuid2 ?? null, origin, direction, hit.toi);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
this.isMouseDown = false;
|
|
1254
|
+
const hoveredUuid = getHoveredEntity();
|
|
1255
|
+
if (!hoveredUuid) {
|
|
1256
|
+
this.debugCursor?.hide();
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const hoveredEntity = this.stage._debugMap.get(`${hoveredUuid}`);
|
|
1260
|
+
const targetObject = hoveredEntity?.group ?? hoveredEntity?.mesh ?? null;
|
|
1261
|
+
if (!targetObject) {
|
|
1262
|
+
this.debugCursor?.hide();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
switch (tool) {
|
|
1266
|
+
case "select":
|
|
1267
|
+
this.debugCursor?.setColor(SELECT_TOOL_COLOR);
|
|
1268
|
+
break;
|
|
1269
|
+
case "delete":
|
|
1270
|
+
this.debugCursor?.setColor(DELETE_TOOL_COLOR);
|
|
1271
|
+
break;
|
|
1272
|
+
default:
|
|
1273
|
+
this.debugCursor?.setColor(16777215);
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
this.debugCursor?.updateFromObject(targetObject);
|
|
1277
|
+
}
|
|
1278
|
+
dispose() {
|
|
1279
|
+
this.disposeFns.forEach((fn) => fn());
|
|
1280
|
+
this.disposeFns = [];
|
|
1281
|
+
this.debugCursor?.dispose();
|
|
1282
|
+
if (this.debugLines && this.stage.scene) {
|
|
1283
|
+
this.stage.scene.scene.remove(this.debugLines);
|
|
1284
|
+
this.debugLines.geometry.dispose();
|
|
1285
|
+
this.debugLines.material.dispose();
|
|
1286
|
+
this.debugLines = null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
handleActionOnHit(hoveredUuid, origin, direction, toi) {
|
|
1290
|
+
const tool = getDebugTool();
|
|
1291
|
+
switch (tool) {
|
|
1292
|
+
case "select": {
|
|
1293
|
+
if (hoveredUuid) {
|
|
1294
|
+
const entity = this.stage._debugMap.get(hoveredUuid);
|
|
1295
|
+
if (entity) setSelectedEntity(entity);
|
|
1296
|
+
}
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
case "delete": {
|
|
1300
|
+
if (hoveredUuid) {
|
|
1301
|
+
this.stage.removeEntityByUuid(hoveredUuid);
|
|
1302
|
+
}
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
case "scale": {
|
|
1306
|
+
if (!this.options.addEntityFactory) break;
|
|
1307
|
+
const hitPosition = origin.clone().add(direction.clone().multiplyScalar(toi));
|
|
1308
|
+
const newNode = this.options.addEntityFactory({ position: hitPosition });
|
|
1309
|
+
if (newNode) {
|
|
1310
|
+
Promise.resolve(newNode).then((node) => {
|
|
1311
|
+
if (node) this.stage.spawnEntity(node);
|
|
1312
|
+
}).catch(() => {
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
break;
|
|
1316
|
+
}
|
|
1317
|
+
default:
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
attachDomListeners() {
|
|
1322
|
+
const canvas = this.stage.cameraRef?.renderer.domElement ?? this.stage.scene?.zylemCamera.renderer.domElement;
|
|
1323
|
+
if (!canvas) return;
|
|
1324
|
+
const onMouseMove = (e) => {
|
|
1325
|
+
const rect = canvas.getBoundingClientRect();
|
|
1326
|
+
const x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
1327
|
+
const y = -((e.clientY - rect.top) / rect.height * 2 - 1);
|
|
1328
|
+
this.mouseNdc.set(x, y);
|
|
1329
|
+
};
|
|
1330
|
+
const onMouseDown = (e) => {
|
|
1331
|
+
this.isMouseDown = true;
|
|
1332
|
+
};
|
|
1333
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
1334
|
+
canvas.addEventListener("mousedown", onMouseDown);
|
|
1335
|
+
this.disposeFns.push(() => canvas.removeEventListener("mousemove", onMouseMove));
|
|
1336
|
+
this.disposeFns.push(() => canvas.removeEventListener("mousedown", onMouseDown));
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
// src/lib/stage/stage-camera-debug-delegate.ts
|
|
1341
|
+
import { subscribe as subscribe3 } from "valtio/vanilla";
|
|
1342
|
+
var StageCameraDebugDelegate = class {
|
|
1343
|
+
stage;
|
|
1344
|
+
constructor(stage) {
|
|
1345
|
+
this.stage = stage;
|
|
1346
|
+
}
|
|
1347
|
+
subscribe(listener) {
|
|
1348
|
+
const notify = () => listener(this.snapshot());
|
|
1349
|
+
notify();
|
|
1350
|
+
return subscribe3(debugState, notify);
|
|
1351
|
+
}
|
|
1352
|
+
resolveTarget(uuid) {
|
|
1353
|
+
const entity = this.stage._debugMap.get(uuid) || this.stage.world?.collisionMap.get(uuid) || null;
|
|
1354
|
+
const target = entity?.group ?? entity?.mesh ?? null;
|
|
1355
|
+
return target ?? null;
|
|
1356
|
+
}
|
|
1357
|
+
snapshot() {
|
|
1358
|
+
return {
|
|
1359
|
+
enabled: debugState.enabled,
|
|
1360
|
+
selected: debugState.selectedEntity ? [debugState.selectedEntity.uuid] : []
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
// src/lib/stage/stage-camera-delegate.ts
|
|
1366
|
+
import { Vector2 as Vector24 } from "three";
|
|
1367
|
+
|
|
1368
|
+
// src/lib/camera/zylem-camera.ts
|
|
1369
|
+
import { PerspectiveCamera, Vector3 as Vector39, Object3D as Object3D6, OrthographicCamera, WebGLRenderer as WebGLRenderer3 } from "three";
|
|
1370
|
+
|
|
1371
|
+
// src/lib/camera/perspective.ts
|
|
1372
|
+
var Perspectives = {
|
|
1373
|
+
FirstPerson: "first-person",
|
|
1374
|
+
ThirdPerson: "third-person",
|
|
1375
|
+
Isometric: "isometric",
|
|
1376
|
+
Flat2D: "flat-2d",
|
|
1377
|
+
Fixed2D: "fixed-2d"
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// src/lib/camera/third-person.ts
|
|
1381
|
+
import { Vector3 as Vector37 } from "three";
|
|
1382
|
+
var ThirdPersonCamera = class {
|
|
1383
|
+
distance;
|
|
1384
|
+
screenResolution = null;
|
|
1385
|
+
renderer = null;
|
|
1386
|
+
scene = null;
|
|
1387
|
+
cameraRef = null;
|
|
1388
|
+
constructor() {
|
|
1389
|
+
this.distance = new Vector37(0, 5, 8);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Setup the third person camera controller
|
|
1393
|
+
*/
|
|
1394
|
+
setup(params) {
|
|
1395
|
+
const { screenResolution, renderer, scene, camera } = params;
|
|
1396
|
+
this.screenResolution = screenResolution;
|
|
1397
|
+
this.renderer = renderer;
|
|
1398
|
+
this.scene = scene;
|
|
1399
|
+
this.cameraRef = camera;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Update the third person camera
|
|
1403
|
+
*/
|
|
1404
|
+
update(delta) {
|
|
1405
|
+
if (!this.cameraRef.target) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const desiredCameraPosition = this.cameraRef.target.group.position.clone().add(this.distance);
|
|
1409
|
+
this.cameraRef.camera.position.lerp(desiredCameraPosition, 0.1);
|
|
1410
|
+
this.cameraRef.camera.lookAt(this.cameraRef.target.group.position);
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Handle resize events
|
|
1414
|
+
*/
|
|
1415
|
+
resize(width, height) {
|
|
1416
|
+
if (this.screenResolution) {
|
|
1417
|
+
this.screenResolution.set(width, height);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Set the distance from the target
|
|
1422
|
+
*/
|
|
1423
|
+
setDistance(distance) {
|
|
1424
|
+
this.distance = distance;
|
|
1023
1425
|
}
|
|
1024
1426
|
};
|
|
1025
1427
|
|
|
@@ -1146,109 +1548,42 @@ var RenderPass = class extends Pass {
|
|
|
1146
1548
|
}
|
|
1147
1549
|
};
|
|
1148
1550
|
|
|
1149
|
-
// src/lib/camera/
|
|
1150
|
-
|
|
1151
|
-
|
|
1551
|
+
// src/lib/camera/camera-debug-delegate.ts
|
|
1552
|
+
import { Vector3 as Vector38 } from "three";
|
|
1553
|
+
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
1554
|
+
var CameraOrbitController = class {
|
|
1152
1555
|
camera;
|
|
1153
|
-
|
|
1154
|
-
renderer;
|
|
1155
|
-
composer;
|
|
1156
|
-
_perspective;
|
|
1556
|
+
domElement;
|
|
1157
1557
|
orbitControls = null;
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
frustumSize = 10;
|
|
1161
|
-
// Perspective controller delegation
|
|
1162
|
-
perspectiveController = null;
|
|
1558
|
+
orbitTarget = null;
|
|
1559
|
+
orbitTargetWorldPos = new Vector38();
|
|
1163
1560
|
debugDelegate = null;
|
|
1164
1561
|
debugUnsubscribe = null;
|
|
1165
1562
|
debugStateSnapshot = { enabled: false, selected: [] };
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
this.
|
|
1172
|
-
this.
|
|
1173
|
-
this.renderer.setSize(screenResolution.x, screenResolution.y);
|
|
1174
|
-
this.renderer.shadowMap.enabled = true;
|
|
1175
|
-
this.composer = new EffectComposer(this.renderer);
|
|
1176
|
-
const aspectRatio = screenResolution.x / screenResolution.y;
|
|
1177
|
-
this.camera = this.createCameraForPerspective(aspectRatio);
|
|
1178
|
-
this.cameraRig = new Object3D4();
|
|
1179
|
-
this.cameraRig.position.set(0, 3, 10);
|
|
1180
|
-
this.cameraRig.add(this.camera);
|
|
1181
|
-
this.camera.lookAt(new Vector36(0, 2, 0));
|
|
1182
|
-
this.initializePerspectiveController();
|
|
1563
|
+
// Saved camera state for restoration when exiting debug mode
|
|
1564
|
+
savedCameraPosition = null;
|
|
1565
|
+
savedCameraQuaternion = null;
|
|
1566
|
+
savedCameraZoom = null;
|
|
1567
|
+
constructor(camera, domElement) {
|
|
1568
|
+
this.camera = camera;
|
|
1569
|
+
this.domElement = domElement;
|
|
1183
1570
|
}
|
|
1184
1571
|
/**
|
|
1185
|
-
*
|
|
1572
|
+
* Check if debug mode is currently active (orbit controls enabled).
|
|
1186
1573
|
*/
|
|
1187
|
-
|
|
1188
|
-
this.
|
|
1189
|
-
if (this.orbitControls === null) {
|
|
1190
|
-
this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
1191
|
-
this.orbitControls.enableDamping = true;
|
|
1192
|
-
this.orbitControls.dampingFactor = 0.05;
|
|
1193
|
-
this.orbitControls.screenSpacePanning = false;
|
|
1194
|
-
this.orbitControls.minDistance = 1;
|
|
1195
|
-
this.orbitControls.maxDistance = 500;
|
|
1196
|
-
this.orbitControls.maxPolarAngle = Math.PI / 2;
|
|
1197
|
-
}
|
|
1198
|
-
let renderResolution = this.screenResolution.clone().divideScalar(2);
|
|
1199
|
-
renderResolution.x |= 0;
|
|
1200
|
-
renderResolution.y |= 0;
|
|
1201
|
-
const pass = new RenderPass(renderResolution, scene, this.camera);
|
|
1202
|
-
this.composer.addPass(pass);
|
|
1203
|
-
if (this.perspectiveController) {
|
|
1204
|
-
this.perspectiveController.setup({
|
|
1205
|
-
screenResolution: this.screenResolution,
|
|
1206
|
-
renderer: this.renderer,
|
|
1207
|
-
scene,
|
|
1208
|
-
camera: this
|
|
1209
|
-
});
|
|
1210
|
-
}
|
|
1211
|
-
this.renderer.setAnimationLoop((delta) => {
|
|
1212
|
-
this.update(delta || 0);
|
|
1213
|
-
});
|
|
1574
|
+
get isActive() {
|
|
1575
|
+
return this.debugStateSnapshot.enabled;
|
|
1214
1576
|
}
|
|
1215
1577
|
/**
|
|
1216
|
-
* Update
|
|
1578
|
+
* Update orbit controls each frame.
|
|
1579
|
+
* Should be called from the camera's update loop.
|
|
1217
1580
|
*/
|
|
1218
|
-
update(
|
|
1581
|
+
update() {
|
|
1219
1582
|
if (this.orbitControls && this.orbitTarget) {
|
|
1220
1583
|
this.orbitTarget.getWorldPosition(this.orbitTargetWorldPos);
|
|
1221
1584
|
this.orbitControls.target.copy(this.orbitTargetWorldPos);
|
|
1222
1585
|
}
|
|
1223
1586
|
this.orbitControls?.update();
|
|
1224
|
-
if (this.perspectiveController) {
|
|
1225
|
-
this.perspectiveController.update(delta);
|
|
1226
|
-
}
|
|
1227
|
-
this.composer.render(delta);
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Dispose renderer, composer, controls, and detach from scene
|
|
1231
|
-
*/
|
|
1232
|
-
destroy() {
|
|
1233
|
-
try {
|
|
1234
|
-
this.renderer.setAnimationLoop(null);
|
|
1235
|
-
} catch {
|
|
1236
|
-
}
|
|
1237
|
-
try {
|
|
1238
|
-
this.disableOrbitControls();
|
|
1239
|
-
} catch {
|
|
1240
|
-
}
|
|
1241
|
-
try {
|
|
1242
|
-
this.composer?.passes?.forEach((p) => p.dispose?.());
|
|
1243
|
-
this.composer?.dispose?.();
|
|
1244
|
-
} catch {
|
|
1245
|
-
}
|
|
1246
|
-
try {
|
|
1247
|
-
this.renderer.dispose();
|
|
1248
|
-
} catch {
|
|
1249
|
-
}
|
|
1250
|
-
this.detachDebugDelegate();
|
|
1251
|
-
this.sceneRef = null;
|
|
1252
1587
|
}
|
|
1253
1588
|
/**
|
|
1254
1589
|
* Attach a delegate to react to debug state changes.
|
|
@@ -1271,135 +1606,48 @@ var ZylemCamera = class {
|
|
|
1271
1606
|
};
|
|
1272
1607
|
}
|
|
1273
1608
|
/**
|
|
1274
|
-
*
|
|
1609
|
+
* Clean up resources.
|
|
1275
1610
|
*/
|
|
1276
|
-
|
|
1277
|
-
this.
|
|
1278
|
-
this.
|
|
1279
|
-
this.composer.setSize(width, height);
|
|
1280
|
-
if (this.camera instanceof PerspectiveCamera) {
|
|
1281
|
-
this.camera.aspect = width / height;
|
|
1282
|
-
this.camera.updateProjectionMatrix();
|
|
1283
|
-
}
|
|
1284
|
-
if (this.perspectiveController) {
|
|
1285
|
-
this.perspectiveController.resize(width, height);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* Update renderer pixel ratio (DPR)
|
|
1290
|
-
*/
|
|
1291
|
-
setPixelRatio(dpr) {
|
|
1292
|
-
const safe = Math.max(1, Number.isFinite(dpr) ? dpr : 1);
|
|
1293
|
-
this.renderer.setPixelRatio(safe);
|
|
1294
|
-
}
|
|
1295
|
-
/**
|
|
1296
|
-
* Create camera based on perspective type
|
|
1297
|
-
*/
|
|
1298
|
-
createCameraForPerspective(aspectRatio) {
|
|
1299
|
-
switch (this._perspective) {
|
|
1300
|
-
case Perspectives.ThirdPerson:
|
|
1301
|
-
return this.createThirdPersonCamera(aspectRatio);
|
|
1302
|
-
case Perspectives.FirstPerson:
|
|
1303
|
-
return this.createFirstPersonCamera(aspectRatio);
|
|
1304
|
-
case Perspectives.Isometric:
|
|
1305
|
-
return this.createIsometricCamera(aspectRatio);
|
|
1306
|
-
case Perspectives.Flat2D:
|
|
1307
|
-
return this.createFlat2DCamera(aspectRatio);
|
|
1308
|
-
case Perspectives.Fixed2D:
|
|
1309
|
-
return this.createFixed2DCamera(aspectRatio);
|
|
1310
|
-
default:
|
|
1311
|
-
return this.createThirdPersonCamera(aspectRatio);
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
/**
|
|
1315
|
-
* Initialize perspective-specific controller
|
|
1316
|
-
*/
|
|
1317
|
-
initializePerspectiveController() {
|
|
1318
|
-
switch (this._perspective) {
|
|
1319
|
-
case Perspectives.ThirdPerson:
|
|
1320
|
-
this.perspectiveController = new ThirdPersonCamera();
|
|
1321
|
-
break;
|
|
1322
|
-
case Perspectives.Fixed2D:
|
|
1323
|
-
this.perspectiveController = new Fixed2DCamera();
|
|
1324
|
-
break;
|
|
1325
|
-
default:
|
|
1326
|
-
this.perspectiveController = new ThirdPersonCamera();
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
createThirdPersonCamera(aspectRatio) {
|
|
1330
|
-
return new PerspectiveCamera(75, aspectRatio, 0.1, 1e3);
|
|
1331
|
-
}
|
|
1332
|
-
createFirstPersonCamera(aspectRatio) {
|
|
1333
|
-
return new PerspectiveCamera(75, aspectRatio, 0.1, 1e3);
|
|
1334
|
-
}
|
|
1335
|
-
createIsometricCamera(aspectRatio) {
|
|
1336
|
-
return new OrthographicCamera(
|
|
1337
|
-
this.frustumSize * aspectRatio / -2,
|
|
1338
|
-
this.frustumSize * aspectRatio / 2,
|
|
1339
|
-
this.frustumSize / 2,
|
|
1340
|
-
this.frustumSize / -2,
|
|
1341
|
-
1,
|
|
1342
|
-
1e3
|
|
1343
|
-
);
|
|
1344
|
-
}
|
|
1345
|
-
createFlat2DCamera(aspectRatio) {
|
|
1346
|
-
return new OrthographicCamera(
|
|
1347
|
-
this.frustumSize * aspectRatio / -2,
|
|
1348
|
-
this.frustumSize * aspectRatio / 2,
|
|
1349
|
-
this.frustumSize / 2,
|
|
1350
|
-
this.frustumSize / -2,
|
|
1351
|
-
1,
|
|
1352
|
-
1e3
|
|
1353
|
-
);
|
|
1354
|
-
}
|
|
1355
|
-
createFixed2DCamera(aspectRatio) {
|
|
1356
|
-
return this.createFlat2DCamera(aspectRatio);
|
|
1357
|
-
}
|
|
1358
|
-
// Movement methods
|
|
1359
|
-
moveCamera(position2) {
|
|
1360
|
-
if (this._perspective === Perspectives.Flat2D || this._perspective === Perspectives.Fixed2D) {
|
|
1361
|
-
this.frustumSize = position2.z;
|
|
1362
|
-
}
|
|
1363
|
-
this.cameraRig.position.set(position2.x, position2.y, position2.z);
|
|
1364
|
-
}
|
|
1365
|
-
move(position2) {
|
|
1366
|
-
this.moveCamera(position2);
|
|
1367
|
-
}
|
|
1368
|
-
rotate(pitch, yaw, roll) {
|
|
1369
|
-
this.cameraRig.rotateX(pitch);
|
|
1370
|
-
this.cameraRig.rotateY(yaw);
|
|
1371
|
-
this.cameraRig.rotateZ(roll);
|
|
1611
|
+
dispose() {
|
|
1612
|
+
this.disableOrbitControls();
|
|
1613
|
+
this.detachDebugDelegate();
|
|
1372
1614
|
}
|
|
1373
1615
|
/**
|
|
1374
|
-
* Get the
|
|
1616
|
+
* Get the current debug state snapshot.
|
|
1375
1617
|
*/
|
|
1376
|
-
|
|
1377
|
-
return this.
|
|
1618
|
+
get debugState() {
|
|
1619
|
+
return this.debugStateSnapshot;
|
|
1378
1620
|
}
|
|
1379
1621
|
applyDebugState(state2) {
|
|
1622
|
+
const wasEnabled = this.debugStateSnapshot.enabled;
|
|
1380
1623
|
this.debugStateSnapshot = {
|
|
1381
1624
|
enabled: state2.enabled,
|
|
1382
1625
|
selected: [...state2.selected]
|
|
1383
1626
|
};
|
|
1384
|
-
if (state2.enabled) {
|
|
1627
|
+
if (state2.enabled && !wasEnabled) {
|
|
1628
|
+
this.saveCameraState();
|
|
1385
1629
|
this.enableOrbitControls();
|
|
1386
1630
|
this.updateOrbitTargetFromSelection(state2.selected);
|
|
1387
|
-
} else {
|
|
1631
|
+
} else if (!state2.enabled && wasEnabled) {
|
|
1388
1632
|
this.orbitTarget = null;
|
|
1389
1633
|
this.disableOrbitControls();
|
|
1634
|
+
this.restoreCameraState();
|
|
1635
|
+
} else if (state2.enabled) {
|
|
1636
|
+
this.updateOrbitTargetFromSelection(state2.selected);
|
|
1390
1637
|
}
|
|
1391
1638
|
}
|
|
1392
1639
|
enableOrbitControls() {
|
|
1393
1640
|
if (this.orbitControls) {
|
|
1394
1641
|
return;
|
|
1395
1642
|
}
|
|
1396
|
-
this.orbitControls = new OrbitControls(this.camera, this.
|
|
1643
|
+
this.orbitControls = new OrbitControls(this.camera, this.domElement);
|
|
1397
1644
|
this.orbitControls.enableDamping = true;
|
|
1398
1645
|
this.orbitControls.dampingFactor = 0.05;
|
|
1399
1646
|
this.orbitControls.screenSpacePanning = false;
|
|
1400
1647
|
this.orbitControls.minDistance = 1;
|
|
1401
1648
|
this.orbitControls.maxDistance = 500;
|
|
1402
1649
|
this.orbitControls.maxPolarAngle = Math.PI / 2;
|
|
1650
|
+
this.orbitControls.target.set(0, 0, 0);
|
|
1403
1651
|
}
|
|
1404
1652
|
disableOrbitControls() {
|
|
1405
1653
|
if (!this.orbitControls) {
|
|
@@ -1411,6 +1659,9 @@ var ZylemCamera = class {
|
|
|
1411
1659
|
updateOrbitTargetFromSelection(selected) {
|
|
1412
1660
|
if (!this.debugDelegate || selected.length === 0) {
|
|
1413
1661
|
this.orbitTarget = null;
|
|
1662
|
+
if (this.orbitControls) {
|
|
1663
|
+
this.orbitControls.target.set(0, 0, 0);
|
|
1664
|
+
}
|
|
1414
1665
|
return;
|
|
1415
1666
|
}
|
|
1416
1667
|
for (let i = selected.length - 1; i >= 0; i -= 1) {
|
|
@@ -1437,259 +1688,453 @@ var ZylemCamera = class {
|
|
|
1437
1688
|
this.debugUnsubscribe = null;
|
|
1438
1689
|
this.debugDelegate = null;
|
|
1439
1690
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
this.
|
|
1691
|
+
/**
|
|
1692
|
+
* Save camera position, rotation, and zoom before entering debug mode.
|
|
1693
|
+
*/
|
|
1694
|
+
saveCameraState() {
|
|
1695
|
+
this.savedCameraPosition = this.camera.position.clone();
|
|
1696
|
+
this.savedCameraQuaternion = this.camera.quaternion.clone();
|
|
1697
|
+
if ("zoom" in this.camera) {
|
|
1698
|
+
this.savedCameraZoom = this.camera.zoom;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Restore camera position, rotation, and zoom when exiting debug mode.
|
|
1703
|
+
*/
|
|
1704
|
+
restoreCameraState() {
|
|
1705
|
+
if (this.savedCameraPosition) {
|
|
1706
|
+
this.camera.position.copy(this.savedCameraPosition);
|
|
1707
|
+
this.savedCameraPosition = null;
|
|
1708
|
+
}
|
|
1709
|
+
if (this.savedCameraQuaternion) {
|
|
1710
|
+
this.camera.quaternion.copy(this.savedCameraQuaternion);
|
|
1711
|
+
this.savedCameraQuaternion = null;
|
|
1712
|
+
}
|
|
1713
|
+
if (this.savedCameraZoom !== null && "zoom" in this.camera) {
|
|
1714
|
+
this.camera.zoom = this.savedCameraZoom;
|
|
1715
|
+
this.camera.updateProjectionMatrix?.();
|
|
1716
|
+
this.savedCameraZoom = null;
|
|
1717
|
+
}
|
|
1447
1718
|
}
|
|
1448
1719
|
};
|
|
1449
1720
|
|
|
1450
|
-
// src/lib/
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
})
|
|
1487
|
-
);
|
|
1488
|
-
const edges = new EdgesGeometry(initialGeometry);
|
|
1489
|
-
this.edgeLines = new LineSegments(
|
|
1490
|
-
edges,
|
|
1491
|
-
new LineBasicMaterial({ color: this.currentColor, linewidth: 1 })
|
|
1492
|
-
);
|
|
1493
|
-
this.container = new Group3();
|
|
1494
|
-
this.container.name = "DebugEntityCursor";
|
|
1495
|
-
this.container.add(this.fillMesh);
|
|
1496
|
-
this.container.add(this.edgeLines);
|
|
1497
|
-
this.container.visible = false;
|
|
1498
|
-
this.scene.add(this.container);
|
|
1499
|
-
}
|
|
1500
|
-
setColor(color) {
|
|
1501
|
-
this.currentColor.set(color);
|
|
1502
|
-
this.fillMesh.material.color.set(this.currentColor);
|
|
1503
|
-
this.edgeLines.material.color.set(this.currentColor);
|
|
1721
|
+
// src/lib/camera/zylem-camera.ts
|
|
1722
|
+
var ZylemCamera = class {
|
|
1723
|
+
cameraRig = null;
|
|
1724
|
+
camera;
|
|
1725
|
+
screenResolution;
|
|
1726
|
+
renderer;
|
|
1727
|
+
composer;
|
|
1728
|
+
_perspective;
|
|
1729
|
+
target = null;
|
|
1730
|
+
sceneRef = null;
|
|
1731
|
+
frustumSize = 10;
|
|
1732
|
+
// Perspective controller delegation
|
|
1733
|
+
perspectiveController = null;
|
|
1734
|
+
// Debug/orbit controls delegation
|
|
1735
|
+
orbitController = null;
|
|
1736
|
+
constructor(perspective, screenResolution, frustumSize = 10) {
|
|
1737
|
+
this._perspective = perspective;
|
|
1738
|
+
this.screenResolution = screenResolution;
|
|
1739
|
+
this.frustumSize = frustumSize;
|
|
1740
|
+
this.renderer = new WebGLRenderer3({ antialias: false, alpha: true });
|
|
1741
|
+
this.renderer.setSize(screenResolution.x, screenResolution.y);
|
|
1742
|
+
this.renderer.shadowMap.enabled = true;
|
|
1743
|
+
this.composer = new EffectComposer(this.renderer);
|
|
1744
|
+
const aspectRatio = screenResolution.x / screenResolution.y;
|
|
1745
|
+
this.camera = this.createCameraForPerspective(aspectRatio);
|
|
1746
|
+
if (this.needsRig()) {
|
|
1747
|
+
this.cameraRig = new Object3D6();
|
|
1748
|
+
this.cameraRig.position.set(0, 3, 10);
|
|
1749
|
+
this.cameraRig.add(this.camera);
|
|
1750
|
+
this.camera.lookAt(new Vector39(0, 2, 0));
|
|
1751
|
+
} else {
|
|
1752
|
+
this.camera.position.set(0, 0, 10);
|
|
1753
|
+
this.camera.lookAt(new Vector39(0, 0, 0));
|
|
1754
|
+
}
|
|
1755
|
+
this.initializePerspectiveController();
|
|
1756
|
+
this.orbitController = new CameraOrbitController(this.camera, this.renderer.domElement);
|
|
1504
1757
|
}
|
|
1505
1758
|
/**
|
|
1506
|
-
*
|
|
1759
|
+
* Setup the camera with a scene
|
|
1507
1760
|
*/
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
this.
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1761
|
+
async setup(scene) {
|
|
1762
|
+
this.sceneRef = scene;
|
|
1763
|
+
let renderResolution = this.screenResolution.clone().divideScalar(2);
|
|
1764
|
+
renderResolution.x |= 0;
|
|
1765
|
+
renderResolution.y |= 0;
|
|
1766
|
+
const pass = new RenderPass(renderResolution, scene, this.camera);
|
|
1767
|
+
this.composer.addPass(pass);
|
|
1768
|
+
if (this.perspectiveController) {
|
|
1769
|
+
this.perspectiveController.setup({
|
|
1770
|
+
screenResolution: this.screenResolution,
|
|
1771
|
+
renderer: this.renderer,
|
|
1772
|
+
scene,
|
|
1773
|
+
camera: this
|
|
1774
|
+
});
|
|
1517
1775
|
}
|
|
1518
|
-
this.
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
Math.max(this.size.x, 1e-6),
|
|
1522
|
-
Math.max(this.size.y, 1e-6),
|
|
1523
|
-
Math.max(this.size.z, 1e-6)
|
|
1524
|
-
);
|
|
1525
|
-
this.fillMesh.geometry.dispose();
|
|
1526
|
-
this.fillMesh.geometry = newGeom;
|
|
1527
|
-
const newEdges = new EdgesGeometry(newGeom);
|
|
1528
|
-
this.edgeLines.geometry.dispose();
|
|
1529
|
-
this.edgeLines.geometry = newEdges;
|
|
1530
|
-
this.container.position.copy(this.center);
|
|
1531
|
-
this.container.visible = true;
|
|
1776
|
+
this.renderer.setAnimationLoop((delta) => {
|
|
1777
|
+
this.update(delta || 0);
|
|
1778
|
+
});
|
|
1532
1779
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1780
|
+
/**
|
|
1781
|
+
* Update camera and render
|
|
1782
|
+
*/
|
|
1783
|
+
update(delta) {
|
|
1784
|
+
this.orbitController?.update();
|
|
1785
|
+
if (this.perspectiveController && !this.isDebugModeActive()) {
|
|
1786
|
+
this.perspectiveController.update(delta);
|
|
1787
|
+
}
|
|
1788
|
+
this.composer.render(delta);
|
|
1535
1789
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
this.
|
|
1541
|
-
this.edgeLines.material.dispose();
|
|
1790
|
+
/**
|
|
1791
|
+
* Check if debug mode is active (orbit controls taking over camera)
|
|
1792
|
+
*/
|
|
1793
|
+
isDebugModeActive() {
|
|
1794
|
+
return this.orbitController?.isActive ?? false;
|
|
1542
1795
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
options;
|
|
1551
|
-
mouseNdc = new Vector24(-2, -2);
|
|
1552
|
-
raycaster = new Raycaster();
|
|
1553
|
-
isMouseDown = false;
|
|
1554
|
-
disposeFns = [];
|
|
1555
|
-
debugCursor = null;
|
|
1556
|
-
debugLines = null;
|
|
1557
|
-
constructor(stage, options) {
|
|
1558
|
-
this.stage = stage;
|
|
1559
|
-
this.options = {
|
|
1560
|
-
maxRayDistance: options?.maxRayDistance ?? 5e3,
|
|
1561
|
-
addEntityFactory: options?.addEntityFactory ?? null
|
|
1562
|
-
};
|
|
1563
|
-
if (this.stage.scene) {
|
|
1564
|
-
this.debugLines = new LineSegments2(
|
|
1565
|
-
new BufferGeometry2(),
|
|
1566
|
-
new LineBasicMaterial2({ vertexColors: true })
|
|
1567
|
-
);
|
|
1568
|
-
this.stage.scene.scene.add(this.debugLines);
|
|
1569
|
-
this.debugLines.visible = true;
|
|
1570
|
-
this.debugCursor = new DebugEntityCursor(this.stage.scene.scene);
|
|
1796
|
+
/**
|
|
1797
|
+
* Dispose renderer, composer, controls, and detach from scene
|
|
1798
|
+
*/
|
|
1799
|
+
destroy() {
|
|
1800
|
+
try {
|
|
1801
|
+
this.renderer.setAnimationLoop(null);
|
|
1802
|
+
} catch {
|
|
1571
1803
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
if (!debugState.enabled) return;
|
|
1576
|
-
if (!this.stage.scene || !this.stage.world || !this.stage.cameraRef) return;
|
|
1577
|
-
const { world, cameraRef } = this.stage;
|
|
1578
|
-
if (this.debugLines) {
|
|
1579
|
-
const { vertices, colors } = world.world.debugRender();
|
|
1580
|
-
this.debugLines.geometry.setAttribute("position", new BufferAttribute(vertices, 3));
|
|
1581
|
-
this.debugLines.geometry.setAttribute("color", new BufferAttribute(colors, 4));
|
|
1804
|
+
try {
|
|
1805
|
+
this.orbitController?.dispose();
|
|
1806
|
+
} catch {
|
|
1582
1807
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
const direction = this.raycaster.ray.direction.clone().normalize();
|
|
1588
|
-
const rapierRay = new Ray(
|
|
1589
|
-
{ x: origin.x, y: origin.y, z: origin.z },
|
|
1590
|
-
{ x: direction.x, y: direction.y, z: direction.z }
|
|
1591
|
-
);
|
|
1592
|
-
const hit = world.world.castRay(rapierRay, this.options.maxRayDistance, true);
|
|
1593
|
-
if (hit && isCursorTool) {
|
|
1594
|
-
const rigidBody = hit.collider?._parent;
|
|
1595
|
-
const hoveredUuid2 = rigidBody?.userData?.uuid;
|
|
1596
|
-
if (hoveredUuid2) {
|
|
1597
|
-
const entity = this.stage._debugMap.get(hoveredUuid2);
|
|
1598
|
-
if (entity) setHoveredEntity(entity);
|
|
1599
|
-
} else {
|
|
1600
|
-
resetHoveredEntity();
|
|
1601
|
-
}
|
|
1602
|
-
if (this.isMouseDown) {
|
|
1603
|
-
this.handleActionOnHit(hoveredUuid2 ?? null, origin, direction, hit.toi);
|
|
1604
|
-
}
|
|
1808
|
+
try {
|
|
1809
|
+
this.composer?.passes?.forEach((p) => p.dispose?.());
|
|
1810
|
+
this.composer?.dispose?.();
|
|
1811
|
+
} catch {
|
|
1605
1812
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
this.debugCursor?.hide();
|
|
1610
|
-
return;
|
|
1813
|
+
try {
|
|
1814
|
+
this.renderer.dispose();
|
|
1815
|
+
} catch {
|
|
1611
1816
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1817
|
+
this.sceneRef = null;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Attach a delegate to react to debug state changes.
|
|
1821
|
+
*/
|
|
1822
|
+
setDebugDelegate(delegate) {
|
|
1823
|
+
this.orbitController?.setDebugDelegate(delegate);
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Resize camera and renderer
|
|
1827
|
+
*/
|
|
1828
|
+
resize(width, height) {
|
|
1829
|
+
this.screenResolution.set(width, height);
|
|
1830
|
+
this.renderer.setSize(width, height, false);
|
|
1831
|
+
this.composer.setSize(width, height);
|
|
1832
|
+
if (this.camera instanceof PerspectiveCamera) {
|
|
1833
|
+
this.camera.aspect = width / height;
|
|
1834
|
+
this.camera.updateProjectionMatrix();
|
|
1617
1835
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1836
|
+
if (this.perspectiveController) {
|
|
1837
|
+
this.perspectiveController.resize(width, height);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Update renderer pixel ratio (DPR)
|
|
1842
|
+
*/
|
|
1843
|
+
setPixelRatio(dpr) {
|
|
1844
|
+
const safe = Math.max(1, Number.isFinite(dpr) ? dpr : 1);
|
|
1845
|
+
this.renderer.setPixelRatio(safe);
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Create camera based on perspective type
|
|
1849
|
+
*/
|
|
1850
|
+
createCameraForPerspective(aspectRatio) {
|
|
1851
|
+
switch (this._perspective) {
|
|
1852
|
+
case Perspectives.ThirdPerson:
|
|
1853
|
+
return this.createThirdPersonCamera(aspectRatio);
|
|
1854
|
+
case Perspectives.FirstPerson:
|
|
1855
|
+
return this.createFirstPersonCamera(aspectRatio);
|
|
1856
|
+
case Perspectives.Isometric:
|
|
1857
|
+
return this.createIsometricCamera(aspectRatio);
|
|
1858
|
+
case Perspectives.Flat2D:
|
|
1859
|
+
return this.createFlat2DCamera(aspectRatio);
|
|
1860
|
+
case Perspectives.Fixed2D:
|
|
1861
|
+
return this.createFixed2DCamera(aspectRatio);
|
|
1862
|
+
default:
|
|
1863
|
+
return this.createThirdPersonCamera(aspectRatio);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Initialize perspective-specific controller
|
|
1868
|
+
*/
|
|
1869
|
+
initializePerspectiveController() {
|
|
1870
|
+
switch (this._perspective) {
|
|
1871
|
+
case Perspectives.ThirdPerson:
|
|
1872
|
+
this.perspectiveController = new ThirdPersonCamera();
|
|
1624
1873
|
break;
|
|
1625
|
-
|
|
1626
|
-
this.
|
|
1874
|
+
case Perspectives.Fixed2D:
|
|
1875
|
+
this.perspectiveController = new Fixed2DCamera();
|
|
1627
1876
|
break;
|
|
1877
|
+
default:
|
|
1878
|
+
this.perspectiveController = new ThirdPersonCamera();
|
|
1628
1879
|
}
|
|
1629
|
-
this.debugCursor?.updateFromObject(targetObject);
|
|
1630
1880
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
this.
|
|
1881
|
+
createThirdPersonCamera(aspectRatio) {
|
|
1882
|
+
return new PerspectiveCamera(75, aspectRatio, 0.1, 1e3);
|
|
1883
|
+
}
|
|
1884
|
+
createFirstPersonCamera(aspectRatio) {
|
|
1885
|
+
return new PerspectiveCamera(75, aspectRatio, 0.1, 1e3);
|
|
1886
|
+
}
|
|
1887
|
+
createIsometricCamera(aspectRatio) {
|
|
1888
|
+
return new OrthographicCamera(
|
|
1889
|
+
this.frustumSize * aspectRatio / -2,
|
|
1890
|
+
this.frustumSize * aspectRatio / 2,
|
|
1891
|
+
this.frustumSize / 2,
|
|
1892
|
+
this.frustumSize / -2,
|
|
1893
|
+
1,
|
|
1894
|
+
1e3
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
createFlat2DCamera(aspectRatio) {
|
|
1898
|
+
return new OrthographicCamera(
|
|
1899
|
+
this.frustumSize * aspectRatio / -2,
|
|
1900
|
+
this.frustumSize * aspectRatio / 2,
|
|
1901
|
+
this.frustumSize / 2,
|
|
1902
|
+
this.frustumSize / -2,
|
|
1903
|
+
1,
|
|
1904
|
+
1e3
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
createFixed2DCamera(aspectRatio) {
|
|
1908
|
+
return this.createFlat2DCamera(aspectRatio);
|
|
1909
|
+
}
|
|
1910
|
+
// Movement methods
|
|
1911
|
+
moveCamera(position2) {
|
|
1912
|
+
if (this._perspective === Perspectives.Flat2D || this._perspective === Perspectives.Fixed2D) {
|
|
1913
|
+
this.frustumSize = position2.z;
|
|
1914
|
+
}
|
|
1915
|
+
if (this.cameraRig) {
|
|
1916
|
+
this.cameraRig.position.set(position2.x, position2.y, position2.z);
|
|
1917
|
+
} else {
|
|
1918
|
+
this.camera.position.set(position2.x, position2.y, position2.z);
|
|
1640
1919
|
}
|
|
1641
1920
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
this.stage.removeEntityByUuid(hoveredUuid);
|
|
1655
|
-
}
|
|
1656
|
-
break;
|
|
1657
|
-
}
|
|
1658
|
-
case "scale": {
|
|
1659
|
-
if (!this.options.addEntityFactory) break;
|
|
1660
|
-
const hitPosition = origin.clone().add(direction.clone().multiplyScalar(toi));
|
|
1661
|
-
const newNode = this.options.addEntityFactory({ position: hitPosition });
|
|
1662
|
-
if (newNode) {
|
|
1663
|
-
Promise.resolve(newNode).then((node) => {
|
|
1664
|
-
if (node) this.stage.spawnEntity(node);
|
|
1665
|
-
}).catch(() => {
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1668
|
-
break;
|
|
1669
|
-
}
|
|
1670
|
-
default:
|
|
1671
|
-
break;
|
|
1921
|
+
move(position2) {
|
|
1922
|
+
this.moveCamera(position2);
|
|
1923
|
+
}
|
|
1924
|
+
rotate(pitch, yaw, roll) {
|
|
1925
|
+
if (this.cameraRig) {
|
|
1926
|
+
this.cameraRig.rotateX(pitch);
|
|
1927
|
+
this.cameraRig.rotateY(yaw);
|
|
1928
|
+
this.cameraRig.rotateZ(roll);
|
|
1929
|
+
} else {
|
|
1930
|
+
this.camera.rotateX(pitch);
|
|
1931
|
+
this.camera.rotateY(yaw);
|
|
1932
|
+
this.camera.rotateZ(roll);
|
|
1672
1933
|
}
|
|
1673
1934
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1935
|
+
/**
|
|
1936
|
+
* Check if this perspective type needs a camera rig
|
|
1937
|
+
*/
|
|
1938
|
+
needsRig() {
|
|
1939
|
+
return this._perspective === Perspectives.ThirdPerson;
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Get the DOM element for the renderer
|
|
1943
|
+
*/
|
|
1944
|
+
getDomElement() {
|
|
1945
|
+
return this.renderer.domElement;
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
|
|
1949
|
+
// src/lib/stage/stage-camera-delegate.ts
|
|
1950
|
+
var StageCameraDelegate = class {
|
|
1951
|
+
stage;
|
|
1952
|
+
constructor(stage) {
|
|
1953
|
+
this.stage = stage;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Create a default third-person camera based on window size.
|
|
1957
|
+
*/
|
|
1958
|
+
createDefaultCamera() {
|
|
1959
|
+
const width = window.innerWidth;
|
|
1960
|
+
const height = window.innerHeight;
|
|
1961
|
+
const screenResolution = new Vector24(width, height);
|
|
1962
|
+
return new ZylemCamera(Perspectives.ThirdPerson, screenResolution);
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Resolve the camera to use for the stage.
|
|
1966
|
+
* Uses the provided camera, stage camera wrapper, or creates a default.
|
|
1967
|
+
*
|
|
1968
|
+
* @param cameraOverride Optional camera override
|
|
1969
|
+
* @param cameraWrapper Optional camera wrapper from stage options
|
|
1970
|
+
* @returns The resolved ZylemCamera instance
|
|
1971
|
+
*/
|
|
1972
|
+
resolveCamera(cameraOverride, cameraWrapper) {
|
|
1973
|
+
if (cameraOverride) {
|
|
1974
|
+
return cameraOverride;
|
|
1975
|
+
}
|
|
1976
|
+
if (cameraWrapper) {
|
|
1977
|
+
return cameraWrapper.cameraRef;
|
|
1978
|
+
}
|
|
1979
|
+
return this.createDefaultCamera();
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
// src/lib/stage/stage-loading-delegate.ts
|
|
1984
|
+
var StageLoadingDelegate = class {
|
|
1985
|
+
loadingHandlers = [];
|
|
1986
|
+
stageName;
|
|
1987
|
+
stageIndex;
|
|
1988
|
+
/**
|
|
1989
|
+
* Set stage context for event bus emissions.
|
|
1990
|
+
*/
|
|
1991
|
+
setStageContext(stageName, stageIndex) {
|
|
1992
|
+
this.stageName = stageName;
|
|
1993
|
+
this.stageIndex = stageIndex;
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Subscribe to loading events.
|
|
1997
|
+
*
|
|
1998
|
+
* @param callback Invoked for each loading event (start, progress, complete)
|
|
1999
|
+
* @returns Unsubscribe function
|
|
2000
|
+
*/
|
|
2001
|
+
onLoading(callback) {
|
|
2002
|
+
this.loadingHandlers.push(callback);
|
|
2003
|
+
return () => {
|
|
2004
|
+
this.loadingHandlers = this.loadingHandlers.filter((h) => h !== callback);
|
|
1682
2005
|
};
|
|
1683
|
-
|
|
1684
|
-
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Emit a loading event to all subscribers and to the game event bus.
|
|
2009
|
+
*
|
|
2010
|
+
* @param event The loading event to broadcast
|
|
2011
|
+
*/
|
|
2012
|
+
emit(event) {
|
|
2013
|
+
for (const handler of this.loadingHandlers) {
|
|
2014
|
+
try {
|
|
2015
|
+
handler(event);
|
|
2016
|
+
} catch (e) {
|
|
2017
|
+
console.error("Loading handler failed", e);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
const payload = {
|
|
2021
|
+
...event,
|
|
2022
|
+
stageName: this.stageName,
|
|
2023
|
+
stageIndex: this.stageIndex
|
|
1685
2024
|
};
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
2025
|
+
if (event.type === "start") {
|
|
2026
|
+
gameEventBus.emit("stage:loading:start", payload);
|
|
2027
|
+
} else if (event.type === "progress") {
|
|
2028
|
+
gameEventBus.emit("stage:loading:progress", payload);
|
|
2029
|
+
} else if (event.type === "complete") {
|
|
2030
|
+
gameEventBus.emit("stage:loading:complete", payload);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Emit a start loading event.
|
|
2035
|
+
*/
|
|
2036
|
+
emitStart(message = "Loading stage...") {
|
|
2037
|
+
this.emit({ type: "start", message, progress: 0 });
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Emit a progress loading event.
|
|
2041
|
+
*/
|
|
2042
|
+
emitProgress(message, current, total) {
|
|
2043
|
+
const progress = total > 0 ? current / total : 0;
|
|
2044
|
+
this.emit({ type: "progress", message, progress, current, total });
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Emit a complete loading event.
|
|
2048
|
+
*/
|
|
2049
|
+
emitComplete(message = "Stage loaded") {
|
|
2050
|
+
this.emit({ type: "complete", message, progress: 1 });
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Clear all loading handlers.
|
|
2054
|
+
*/
|
|
2055
|
+
dispose() {
|
|
2056
|
+
this.loadingHandlers = [];
|
|
1690
2057
|
}
|
|
1691
2058
|
};
|
|
1692
2059
|
|
|
2060
|
+
// src/lib/stage/stage-config.ts
|
|
2061
|
+
import { Vector3 as Vector310 } from "three";
|
|
2062
|
+
|
|
2063
|
+
// src/lib/core/utility/options-parser.ts
|
|
2064
|
+
function isBaseNode(item) {
|
|
2065
|
+
return !!item && typeof item === "object" && typeof item.create === "function";
|
|
2066
|
+
}
|
|
2067
|
+
function isThenable(item) {
|
|
2068
|
+
return !!item && typeof item.then === "function";
|
|
2069
|
+
}
|
|
2070
|
+
function isCameraWrapper(item) {
|
|
2071
|
+
return !!item && typeof item === "object" && item.constructor?.name === "CameraWrapper";
|
|
2072
|
+
}
|
|
2073
|
+
function isConfigObject(item) {
|
|
2074
|
+
if (!item || typeof item !== "object") return false;
|
|
2075
|
+
if (isBaseNode(item)) return false;
|
|
2076
|
+
if (isCameraWrapper(item)) return false;
|
|
2077
|
+
if (isThenable(item)) return false;
|
|
2078
|
+
if (typeof item.then === "function") return false;
|
|
2079
|
+
return item.constructor === Object || item.constructor?.name === "Object";
|
|
2080
|
+
}
|
|
2081
|
+
function isEntityInput(item) {
|
|
2082
|
+
if (!item) return false;
|
|
2083
|
+
if (isBaseNode(item)) return true;
|
|
2084
|
+
if (typeof item === "function") return true;
|
|
2085
|
+
if (isThenable(item)) return true;
|
|
2086
|
+
return false;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// src/lib/stage/stage-config.ts
|
|
2090
|
+
var StageConfig = class {
|
|
2091
|
+
constructor(inputs, backgroundColor, backgroundImage, gravity, variables) {
|
|
2092
|
+
this.inputs = inputs;
|
|
2093
|
+
this.backgroundColor = backgroundColor;
|
|
2094
|
+
this.backgroundImage = backgroundImage;
|
|
2095
|
+
this.gravity = gravity;
|
|
2096
|
+
this.variables = variables;
|
|
2097
|
+
}
|
|
2098
|
+
};
|
|
2099
|
+
function createDefaultStageConfig() {
|
|
2100
|
+
return new StageConfig(
|
|
2101
|
+
{
|
|
2102
|
+
p1: ["gamepad-1", "keyboard-1"],
|
|
2103
|
+
p2: ["gamepad-2", "keyboard-2"]
|
|
2104
|
+
},
|
|
2105
|
+
ZylemBlueColor,
|
|
2106
|
+
null,
|
|
2107
|
+
new Vector310(0, 0, 0),
|
|
2108
|
+
{}
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
function parseStageOptions(options = []) {
|
|
2112
|
+
const defaults = createDefaultStageConfig();
|
|
2113
|
+
let config = {};
|
|
2114
|
+
const entities = [];
|
|
2115
|
+
const asyncEntities = [];
|
|
2116
|
+
let camera;
|
|
2117
|
+
for (const item of options) {
|
|
2118
|
+
if (isCameraWrapper(item)) {
|
|
2119
|
+
camera = item;
|
|
2120
|
+
} else if (isBaseNode(item)) {
|
|
2121
|
+
entities.push(item);
|
|
2122
|
+
} else if (isEntityInput(item) && !isBaseNode(item)) {
|
|
2123
|
+
asyncEntities.push(item);
|
|
2124
|
+
} else if (isConfigObject(item)) {
|
|
2125
|
+
config = { ...config, ...item };
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
const resolvedConfig = new StageConfig(
|
|
2129
|
+
config.inputs ?? defaults.inputs,
|
|
2130
|
+
config.backgroundColor ?? defaults.backgroundColor,
|
|
2131
|
+
config.backgroundImage ?? defaults.backgroundImage,
|
|
2132
|
+
config.gravity ?? defaults.gravity,
|
|
2133
|
+
config.variables ?? defaults.variables
|
|
2134
|
+
);
|
|
2135
|
+
return { config: resolvedConfig, entities, asyncEntities, camera };
|
|
2136
|
+
}
|
|
2137
|
+
|
|
1693
2138
|
// src/lib/stage/zylem-stage.ts
|
|
1694
2139
|
var STAGE_TYPE = "Stage";
|
|
1695
2140
|
var ZylemStage = class extends LifeCycleBase {
|
|
@@ -1701,7 +2146,7 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1701
2146
|
p1: ["gamepad-1", "keyboard"],
|
|
1702
2147
|
p2: ["gamepad-2", "keyboard"]
|
|
1703
2148
|
},
|
|
1704
|
-
gravity: new
|
|
2149
|
+
gravity: new Vector311(0, 0, 0),
|
|
1705
2150
|
variables: {},
|
|
1706
2151
|
entities: []
|
|
1707
2152
|
};
|
|
@@ -1716,16 +2161,19 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1716
2161
|
isLoaded = false;
|
|
1717
2162
|
_debugMap = /* @__PURE__ */ new Map();
|
|
1718
2163
|
entityAddedHandlers = [];
|
|
1719
|
-
loadingHandlers = [];
|
|
1720
2164
|
ecs = createECS();
|
|
1721
2165
|
testSystem = null;
|
|
1722
2166
|
transformSystem = null;
|
|
1723
2167
|
debugDelegate = null;
|
|
1724
2168
|
cameraDebugDelegate = null;
|
|
2169
|
+
debugStateUnsubscribe = null;
|
|
1725
2170
|
uuid;
|
|
1726
2171
|
wrapperRef = null;
|
|
1727
2172
|
camera;
|
|
1728
2173
|
cameraRef = null;
|
|
2174
|
+
// Delegates
|
|
2175
|
+
cameraDelegate;
|
|
2176
|
+
loadingDelegate;
|
|
1729
2177
|
/**
|
|
1730
2178
|
* Create a new stage.
|
|
1731
2179
|
* @param options Stage options: partial config, camera, and initial entities or factories
|
|
@@ -1735,49 +2183,22 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1735
2183
|
this.world = null;
|
|
1736
2184
|
this.scene = null;
|
|
1737
2185
|
this.uuid = nanoid2();
|
|
1738
|
-
|
|
1739
|
-
this.
|
|
1740
|
-
|
|
1741
|
-
this.
|
|
1742
|
-
this.
|
|
1743
|
-
this.
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
entities.push(item);
|
|
1755
|
-
} else if (this.isEntityInput(item)) {
|
|
1756
|
-
asyncEntities.push(item);
|
|
1757
|
-
} else if (this.isZylemStageConfig(item)) {
|
|
1758
|
-
config = { ...config, ...item };
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
return { config, entities, asyncEntities, camera };
|
|
1762
|
-
}
|
|
1763
|
-
isZylemStageConfig(item) {
|
|
1764
|
-
return item && typeof item === "object" && !(item instanceof BaseNode) && !(item instanceof CameraWrapper);
|
|
1765
|
-
}
|
|
1766
|
-
isBaseNode(item) {
|
|
1767
|
-
return item && typeof item === "object" && typeof item.create === "function";
|
|
1768
|
-
}
|
|
1769
|
-
isCameraWrapper(item) {
|
|
1770
|
-
return item && typeof item === "object" && item.constructor.name === "CameraWrapper";
|
|
1771
|
-
}
|
|
1772
|
-
isEntityInput(item) {
|
|
1773
|
-
if (!item) return false;
|
|
1774
|
-
if (this.isBaseNode(item)) return true;
|
|
1775
|
-
if (typeof item === "function") return true;
|
|
1776
|
-
if (typeof item === "object" && typeof item.then === "function") return true;
|
|
1777
|
-
return false;
|
|
1778
|
-
}
|
|
1779
|
-
isThenable(value) {
|
|
1780
|
-
return !!value && typeof value.then === "function";
|
|
2186
|
+
this.cameraDelegate = new StageCameraDelegate(this);
|
|
2187
|
+
this.loadingDelegate = new StageLoadingDelegate();
|
|
2188
|
+
const parsed = parseStageOptions(options);
|
|
2189
|
+
this.camera = parsed.camera;
|
|
2190
|
+
this.children = parsed.entities;
|
|
2191
|
+
this.pendingEntities = parsed.asyncEntities;
|
|
2192
|
+
this.saveState({
|
|
2193
|
+
...this.state,
|
|
2194
|
+
inputs: parsed.config.inputs,
|
|
2195
|
+
backgroundColor: parsed.config.backgroundColor,
|
|
2196
|
+
backgroundImage: parsed.config.backgroundImage,
|
|
2197
|
+
gravity: parsed.config.gravity,
|
|
2198
|
+
variables: parsed.config.variables,
|
|
2199
|
+
entities: []
|
|
2200
|
+
});
|
|
2201
|
+
this.gravity = parsed.config.gravity ?? new Vector311(0, 0, 0);
|
|
1781
2202
|
}
|
|
1782
2203
|
handleEntityImmediatelyOrQueue(entity) {
|
|
1783
2204
|
if (this.isLoaded) {
|
|
@@ -1798,42 +2219,47 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1798
2219
|
}
|
|
1799
2220
|
setState() {
|
|
1800
2221
|
const { backgroundColor, backgroundImage } = this.state;
|
|
1801
|
-
const color = backgroundColor instanceof
|
|
2222
|
+
const color = backgroundColor instanceof Color7 ? backgroundColor : new Color7(backgroundColor);
|
|
1802
2223
|
setStageBackgroundColor(color);
|
|
1803
2224
|
setStageBackgroundImage(backgroundImage);
|
|
1804
2225
|
setStageVariables(this.state.variables ?? {});
|
|
1805
2226
|
}
|
|
1806
2227
|
/**
|
|
1807
2228
|
* Load and initialize the stage's scene and world.
|
|
2229
|
+
* Uses generator pattern to yield control to event loop for real-time progress.
|
|
1808
2230
|
* @param id DOM element id for the renderer container
|
|
1809
2231
|
* @param camera Optional camera override
|
|
1810
2232
|
*/
|
|
1811
2233
|
async load(id, camera) {
|
|
1812
2234
|
this.setState();
|
|
1813
|
-
const zylemCamera =
|
|
2235
|
+
const zylemCamera = this.cameraDelegate.resolveCamera(camera, this.camera);
|
|
1814
2236
|
this.cameraRef = zylemCamera;
|
|
1815
2237
|
this.scene = new ZylemScene(id, zylemCamera, this.state);
|
|
1816
|
-
const physicsWorld = await ZylemWorld.loadPhysics(this.gravity ?? new
|
|
2238
|
+
const physicsWorld = await ZylemWorld.loadPhysics(this.gravity ?? new Vector311(0, 0, 0));
|
|
1817
2239
|
this.world = new ZylemWorld(physicsWorld);
|
|
1818
2240
|
this.scene.setup();
|
|
1819
|
-
this.
|
|
2241
|
+
this.loadingDelegate.emitStart();
|
|
2242
|
+
await this.runEntityLoadGenerator();
|
|
2243
|
+
this.transformSystem = createTransformSystem(this);
|
|
2244
|
+
this.isLoaded = true;
|
|
2245
|
+
this.loadingDelegate.emitComplete();
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Generator that yields between entity loads for real-time progress updates.
|
|
2249
|
+
*/
|
|
2250
|
+
*entityLoadGenerator() {
|
|
1820
2251
|
const total = this.children.length + this.pendingEntities.length + this.pendingPromises.length;
|
|
1821
2252
|
let current = 0;
|
|
1822
|
-
for (
|
|
2253
|
+
for (const child of this.children) {
|
|
1823
2254
|
this.spawnEntity(child);
|
|
1824
2255
|
current++;
|
|
1825
|
-
|
|
1826
|
-
type: "progress",
|
|
1827
|
-
message: `Loaded entity ${child.name || "unknown"}`,
|
|
1828
|
-
progress: current / total,
|
|
1829
|
-
current,
|
|
1830
|
-
total
|
|
1831
|
-
});
|
|
2256
|
+
yield { current, total, name: child.name || "unknown" };
|
|
1832
2257
|
}
|
|
1833
2258
|
if (this.pendingEntities.length) {
|
|
1834
2259
|
this.enqueue(...this.pendingEntities);
|
|
1835
2260
|
current += this.pendingEntities.length;
|
|
1836
2261
|
this.pendingEntities = [];
|
|
2262
|
+
yield { current, total, name: "pending entities" };
|
|
1837
2263
|
}
|
|
1838
2264
|
if (this.pendingPromises.length) {
|
|
1839
2265
|
for (const promise of this.pendingPromises) {
|
|
@@ -1843,24 +2269,44 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1843
2269
|
}
|
|
1844
2270
|
current += this.pendingPromises.length;
|
|
1845
2271
|
this.pendingPromises = [];
|
|
2272
|
+
yield { current, total, name: "async entities" };
|
|
1846
2273
|
}
|
|
1847
|
-
this.transformSystem = createTransformSystem(this);
|
|
1848
|
-
this.isLoaded = true;
|
|
1849
|
-
this.emitLoading({ type: "complete", message: "Stage loaded", progress: 1 });
|
|
1850
2274
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
2275
|
+
/**
|
|
2276
|
+
* Runs the entity load generator, yielding to the event loop between loads.
|
|
2277
|
+
* This allows the browser to process events and update the UI in real-time.
|
|
2278
|
+
*/
|
|
2279
|
+
async runEntityLoadGenerator() {
|
|
2280
|
+
const gen = this.entityLoadGenerator();
|
|
2281
|
+
for (const progress of gen) {
|
|
2282
|
+
this.loadingDelegate.emitProgress(`Loaded ${progress.name}`, progress.current, progress.total);
|
|
2283
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2284
|
+
}
|
|
1856
2285
|
}
|
|
1857
2286
|
_setup(params) {
|
|
1858
2287
|
if (!this.scene || !this.world) {
|
|
1859
2288
|
this.logMissingEntities();
|
|
1860
2289
|
return;
|
|
1861
2290
|
}
|
|
1862
|
-
|
|
2291
|
+
this.updateDebugDelegate();
|
|
2292
|
+
this.debugStateUnsubscribe = subscribe4(debugState, () => {
|
|
2293
|
+
this.updateDebugDelegate();
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
updateDebugDelegate() {
|
|
2297
|
+
if (debugState.enabled && !this.debugDelegate && this.scene && this.world) {
|
|
1863
2298
|
this.debugDelegate = new StageDebugDelegate(this);
|
|
2299
|
+
if (this.cameraRef && !this.cameraDebugDelegate) {
|
|
2300
|
+
this.cameraDebugDelegate = new StageCameraDebugDelegate(this);
|
|
2301
|
+
this.cameraRef.setDebugDelegate(this.cameraDebugDelegate);
|
|
2302
|
+
}
|
|
2303
|
+
} else if (!debugState.enabled && this.debugDelegate) {
|
|
2304
|
+
this.debugDelegate.dispose();
|
|
2305
|
+
this.debugDelegate = null;
|
|
2306
|
+
if (this.cameraRef) {
|
|
2307
|
+
this.cameraRef.setDebugDelegate(null);
|
|
2308
|
+
}
|
|
2309
|
+
this.cameraDebugDelegate = null;
|
|
1864
2310
|
}
|
|
1865
2311
|
}
|
|
1866
2312
|
_update(params) {
|
|
@@ -1870,7 +2316,7 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1870
2316
|
return;
|
|
1871
2317
|
}
|
|
1872
2318
|
this.world.update(params);
|
|
1873
|
-
this.transformSystem(this.ecs);
|
|
2319
|
+
this.transformSystem?.system(this.ecs);
|
|
1874
2320
|
this._childrenMap.forEach((child, eid) => {
|
|
1875
2321
|
child.nodeUpdate({
|
|
1876
2322
|
...params,
|
|
@@ -1904,13 +2350,20 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1904
2350
|
this._debugMap.clear();
|
|
1905
2351
|
this.world?.destroy();
|
|
1906
2352
|
this.scene?.destroy();
|
|
2353
|
+
if (this.debugStateUnsubscribe) {
|
|
2354
|
+
this.debugStateUnsubscribe();
|
|
2355
|
+
this.debugStateUnsubscribe = null;
|
|
2356
|
+
}
|
|
1907
2357
|
this.debugDelegate?.dispose();
|
|
2358
|
+
this.debugDelegate = null;
|
|
1908
2359
|
this.cameraRef?.setDebugDelegate(null);
|
|
1909
2360
|
this.cameraDebugDelegate = null;
|
|
1910
2361
|
this.isLoaded = false;
|
|
1911
2362
|
this.world = null;
|
|
1912
2363
|
this.scene = null;
|
|
1913
2364
|
this.cameraRef = null;
|
|
2365
|
+
this.transformSystem?.destroy(this.ecs);
|
|
2366
|
+
this.transformSystem = null;
|
|
1914
2367
|
resetStageVariables();
|
|
1915
2368
|
clearVariables(this);
|
|
1916
2369
|
}
|
|
@@ -1991,13 +2444,7 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
1991
2444
|
};
|
|
1992
2445
|
}
|
|
1993
2446
|
onLoading(callback) {
|
|
1994
|
-
this.
|
|
1995
|
-
return () => {
|
|
1996
|
-
this.loadingHandlers = this.loadingHandlers.filter((h) => h !== callback);
|
|
1997
|
-
};
|
|
1998
|
-
}
|
|
1999
|
-
emitLoading(event) {
|
|
2000
|
-
this.loadingHandlers.forEach((h) => h(event));
|
|
2447
|
+
return this.loadingDelegate.onLoading(callback);
|
|
2001
2448
|
}
|
|
2002
2449
|
/**
|
|
2003
2450
|
* Remove an entity and its resources by its UUID.
|
|
@@ -2054,16 +2501,16 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
2054
2501
|
enqueue(...items) {
|
|
2055
2502
|
for (const item of items) {
|
|
2056
2503
|
if (!item) continue;
|
|
2057
|
-
if (
|
|
2504
|
+
if (isBaseNode(item)) {
|
|
2058
2505
|
this.handleEntityImmediatelyOrQueue(item);
|
|
2059
2506
|
continue;
|
|
2060
2507
|
}
|
|
2061
2508
|
if (typeof item === "function") {
|
|
2062
2509
|
try {
|
|
2063
2510
|
const result = item();
|
|
2064
|
-
if (
|
|
2511
|
+
if (isBaseNode(result)) {
|
|
2065
2512
|
this.handleEntityImmediatelyOrQueue(result);
|
|
2066
|
-
} else if (
|
|
2513
|
+
} else if (isThenable(result)) {
|
|
2067
2514
|
this.handlePromiseWithSpawnOnResolve(result);
|
|
2068
2515
|
}
|
|
2069
2516
|
} catch (error) {
|
|
@@ -2071,16 +2518,25 @@ var ZylemStage = class extends LifeCycleBase {
|
|
|
2071
2518
|
}
|
|
2072
2519
|
continue;
|
|
2073
2520
|
}
|
|
2074
|
-
if (
|
|
2521
|
+
if (isThenable(item)) {
|
|
2075
2522
|
this.handlePromiseWithSpawnOnResolve(item);
|
|
2076
2523
|
}
|
|
2077
2524
|
}
|
|
2078
2525
|
}
|
|
2079
2526
|
};
|
|
2080
2527
|
|
|
2528
|
+
// src/lib/camera/camera.ts
|
|
2529
|
+
import { Vector2 as Vector26, Vector3 as Vector312 } from "three";
|
|
2530
|
+
var CameraWrapper = class {
|
|
2531
|
+
cameraRef;
|
|
2532
|
+
constructor(camera) {
|
|
2533
|
+
this.cameraRef = camera;
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
|
|
2081
2537
|
// src/lib/stage/stage-default.ts
|
|
2082
2538
|
import { proxy as proxy4 } from "valtio/vanilla";
|
|
2083
|
-
import { Vector3 as
|
|
2539
|
+
import { Vector3 as Vector313 } from "three";
|
|
2084
2540
|
var initialDefaults = {
|
|
2085
2541
|
backgroundColor: ZylemBlueColor,
|
|
2086
2542
|
backgroundImage: null,
|
|
@@ -2088,7 +2544,7 @@ var initialDefaults = {
|
|
|
2088
2544
|
p1: ["gamepad-1", "keyboard"],
|
|
2089
2545
|
p2: ["gamepad-2", "keyboard"]
|
|
2090
2546
|
},
|
|
2091
|
-
gravity: new
|
|
2547
|
+
gravity: new Vector313(0, 0, 0),
|
|
2092
2548
|
variables: {}
|
|
2093
2549
|
};
|
|
2094
2550
|
var stageDefaultsState = proxy4({
|
|
@@ -2117,30 +2573,58 @@ function getStageDefaultConfig() {
|
|
|
2117
2573
|
var Stage = class {
|
|
2118
2574
|
wrappedStage;
|
|
2119
2575
|
options = [];
|
|
2120
|
-
//
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2576
|
+
// Entities added after construction, consumed on each load
|
|
2577
|
+
_pendingEntities = [];
|
|
2578
|
+
// Lifecycle callback arrays
|
|
2579
|
+
setupCallbacks = [];
|
|
2580
|
+
updateCallbacks = [];
|
|
2581
|
+
destroyCallbacks = [];
|
|
2582
|
+
pendingLoadingCallbacks = [];
|
|
2127
2583
|
constructor(options) {
|
|
2128
2584
|
this.options = options;
|
|
2129
2585
|
this.wrappedStage = null;
|
|
2130
2586
|
}
|
|
2131
2587
|
async load(id, camera) {
|
|
2132
2588
|
stageState.entities = [];
|
|
2133
|
-
|
|
2589
|
+
const loadOptions = [...this.options, ...this._pendingEntities];
|
|
2590
|
+
this._pendingEntities = [];
|
|
2591
|
+
this.wrappedStage = new ZylemStage(loadOptions);
|
|
2134
2592
|
this.wrappedStage.wrapperRef = this;
|
|
2593
|
+
this.pendingLoadingCallbacks.forEach((cb) => {
|
|
2594
|
+
this.wrappedStage.onLoading(cb);
|
|
2595
|
+
});
|
|
2596
|
+
this.pendingLoadingCallbacks = [];
|
|
2135
2597
|
const zylemCamera = camera instanceof CameraWrapper ? camera.cameraRef : camera;
|
|
2136
2598
|
await this.wrappedStage.load(id, zylemCamera);
|
|
2137
2599
|
this.wrappedStage.onEntityAdded((child) => {
|
|
2138
2600
|
const next = this.wrappedStage.buildEntityState(child);
|
|
2139
2601
|
stageState.entities = [...stageState.entities, next];
|
|
2140
2602
|
}, { replayExisting: true });
|
|
2603
|
+
this.applyLifecycleCallbacks();
|
|
2604
|
+
}
|
|
2605
|
+
applyLifecycleCallbacks() {
|
|
2606
|
+
if (!this.wrappedStage) return;
|
|
2607
|
+
if (this.setupCallbacks.length > 0) {
|
|
2608
|
+
this.wrappedStage.setup = (params) => {
|
|
2609
|
+
const extended = { ...params, stage: this };
|
|
2610
|
+
this.setupCallbacks.forEach((cb) => cb(extended));
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
if (this.updateCallbacks.length > 0) {
|
|
2614
|
+
this.wrappedStage.update = (params) => {
|
|
2615
|
+
const extended = { ...params, stage: this };
|
|
2616
|
+
this.updateCallbacks.forEach((cb) => cb(extended));
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
if (this.destroyCallbacks.length > 0) {
|
|
2620
|
+
this.wrappedStage.destroy = (params) => {
|
|
2621
|
+
const extended = { ...params, stage: this };
|
|
2622
|
+
this.destroyCallbacks.forEach((cb) => cb(extended));
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2141
2625
|
}
|
|
2142
2626
|
async addEntities(entities) {
|
|
2143
|
-
this.
|
|
2627
|
+
this._pendingEntities.push(...entities);
|
|
2144
2628
|
if (!this.wrappedStage) {
|
|
2145
2629
|
return;
|
|
2146
2630
|
}
|
|
@@ -2165,28 +2649,57 @@ var Stage = class {
|
|
|
2165
2649
|
start(params) {
|
|
2166
2650
|
this.wrappedStage?.nodeSetup(params);
|
|
2167
2651
|
}
|
|
2652
|
+
// Fluent API for adding lifecycle callbacks
|
|
2168
2653
|
onUpdate(...callbacks) {
|
|
2169
|
-
|
|
2170
|
-
|
|
2654
|
+
this.updateCallbacks.push(...callbacks);
|
|
2655
|
+
if (this.wrappedStage) {
|
|
2656
|
+
this.wrappedStage.update = (params) => {
|
|
2657
|
+
const extended = { ...params, stage: this };
|
|
2658
|
+
this.updateCallbacks.forEach((cb) => cb(extended));
|
|
2659
|
+
};
|
|
2171
2660
|
}
|
|
2172
|
-
this
|
|
2173
|
-
const extended = { ...params, stage: this };
|
|
2174
|
-
callbacks.forEach((cb) => cb(extended));
|
|
2175
|
-
};
|
|
2661
|
+
return this;
|
|
2176
2662
|
}
|
|
2177
|
-
onSetup(
|
|
2178
|
-
this.
|
|
2663
|
+
onSetup(...callbacks) {
|
|
2664
|
+
this.setupCallbacks.push(...callbacks);
|
|
2665
|
+
if (this.wrappedStage) {
|
|
2666
|
+
this.wrappedStage.setup = (params) => {
|
|
2667
|
+
const extended = { ...params, stage: this };
|
|
2668
|
+
this.setupCallbacks.forEach((cb) => cb(extended));
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
return this;
|
|
2179
2672
|
}
|
|
2180
|
-
onDestroy(
|
|
2181
|
-
this.
|
|
2673
|
+
onDestroy(...callbacks) {
|
|
2674
|
+
this.destroyCallbacks.push(...callbacks);
|
|
2675
|
+
if (this.wrappedStage) {
|
|
2676
|
+
this.wrappedStage.destroy = (params) => {
|
|
2677
|
+
const extended = { ...params, stage: this };
|
|
2678
|
+
this.destroyCallbacks.forEach((cb) => cb(extended));
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
return this;
|
|
2182
2682
|
}
|
|
2183
2683
|
onLoading(callback) {
|
|
2184
2684
|
if (!this.wrappedStage) {
|
|
2685
|
+
this.pendingLoadingCallbacks.push(callback);
|
|
2185
2686
|
return () => {
|
|
2687
|
+
this.pendingLoadingCallbacks = this.pendingLoadingCallbacks.filter((c) => c !== callback);
|
|
2186
2688
|
};
|
|
2187
2689
|
}
|
|
2188
2690
|
return this.wrappedStage.onLoading(callback);
|
|
2189
2691
|
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Find an entity by name on the current stage.
|
|
2694
|
+
* @param name The name of the entity to find
|
|
2695
|
+
* @param type Optional type symbol for type inference (e.g., TEXT_TYPE, SPRITE_TYPE)
|
|
2696
|
+
* @returns The entity if found, or undefined
|
|
2697
|
+
* @example stage.getEntityByName('scoreText', TEXT_TYPE)
|
|
2698
|
+
*/
|
|
2699
|
+
getEntityByName(name, type) {
|
|
2700
|
+
const entity = this.wrappedStage?.children.find((c) => c.name === name);
|
|
2701
|
+
return entity;
|
|
2702
|
+
}
|
|
2190
2703
|
};
|
|
2191
2704
|
function createStage(...options) {
|
|
2192
2705
|
const _options = getStageOptions(options);
|
|
@@ -2194,7 +2707,7 @@ function createStage(...options) {
|
|
|
2194
2707
|
}
|
|
2195
2708
|
|
|
2196
2709
|
// src/lib/stage/entity-spawner.ts
|
|
2197
|
-
import { Euler, Quaternion as
|
|
2710
|
+
import { Euler, Quaternion as Quaternion3, Vector2 as Vector27 } from "three";
|
|
2198
2711
|
function entitySpawner(factory) {
|
|
2199
2712
|
return {
|
|
2200
2713
|
spawn: async (stage, x, y) => {
|
|
@@ -2202,7 +2715,7 @@ function entitySpawner(factory) {
|
|
|
2202
2715
|
stage.add(instance);
|
|
2203
2716
|
return instance;
|
|
2204
2717
|
},
|
|
2205
|
-
spawnRelative: async (source, stage, offset = new
|
|
2718
|
+
spawnRelative: async (source, stage, offset = new Vector27(0, 1)) => {
|
|
2206
2719
|
if (!source.body) {
|
|
2207
2720
|
console.warn("body missing for entity during spawnRelative");
|
|
2208
2721
|
return void 0;
|
|
@@ -2211,7 +2724,7 @@ function entitySpawner(factory) {
|
|
|
2211
2724
|
let rz = source._rotation2DAngle ?? 0;
|
|
2212
2725
|
try {
|
|
2213
2726
|
const r = source.body.rotation();
|
|
2214
|
-
const q = new
|
|
2727
|
+
const q = new Quaternion3(r.x, r.y, r.z, r.w);
|
|
2215
2728
|
const e = new Euler().setFromQuaternion(q, "XYZ");
|
|
2216
2729
|
rz = e.z;
|
|
2217
2730
|
} catch {
|
|
@@ -2224,8 +2737,31 @@ function entitySpawner(factory) {
|
|
|
2224
2737
|
}
|
|
2225
2738
|
};
|
|
2226
2739
|
}
|
|
2740
|
+
|
|
2741
|
+
// src/lib/stage/stage-events.ts
|
|
2742
|
+
import { subscribe as subscribe5 } from "valtio/vanilla";
|
|
2743
|
+
var STAGE_STATE_CHANGE = "STAGE_STATE_CHANGE";
|
|
2744
|
+
function initStageStateDispatcher() {
|
|
2745
|
+
return subscribe5(stageState, () => {
|
|
2746
|
+
const detail = {
|
|
2747
|
+
entities: stageState.entities,
|
|
2748
|
+
variables: stageState.variables
|
|
2749
|
+
};
|
|
2750
|
+
window.dispatchEvent(new CustomEvent(STAGE_STATE_CHANGE, { detail }));
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
function dispatchStageState() {
|
|
2754
|
+
const detail = {
|
|
2755
|
+
entities: stageState.entities,
|
|
2756
|
+
variables: stageState.variables
|
|
2757
|
+
};
|
|
2758
|
+
window.dispatchEvent(new CustomEvent(STAGE_STATE_CHANGE, { detail }));
|
|
2759
|
+
}
|
|
2227
2760
|
export {
|
|
2761
|
+
STAGE_STATE_CHANGE,
|
|
2228
2762
|
createStage,
|
|
2229
|
-
|
|
2763
|
+
dispatchStageState,
|
|
2764
|
+
entitySpawner,
|
|
2765
|
+
initStageStateDispatcher
|
|
2230
2766
|
};
|
|
2231
2767
|
//# sourceMappingURL=stage.js.map
|