@zylem/game-lib 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/actions.d.ts +255 -8
- package/dist/actions.js +1 -936
- package/dist/audio.d.ts +13 -0
- package/dist/audio.js +1 -0
- package/dist/base-node-life-cycle-CnJ-7zX-.d.ts +143 -0
- package/dist/behavior.d.ts +3 -0
- package/dist/behavior.js +1 -0
- package/dist/chunk-4QLJUQSJ.js +1 -0
- package/dist/chunk-73VZQZUL.js +1 -0
- package/dist/chunk-A34ZG3L4.js +1 -0
- package/dist/chunk-CDPY4VOQ.js +1 -0
- package/dist/chunk-DR6XXZE7.js +19 -0
- package/dist/chunk-FM5TBEPO.js +1 -0
- package/dist/chunk-G2IIMSUH.js +1 -0
- package/dist/chunk-MSC2BEO5.js +1 -0
- package/dist/chunk-NIH5CONO.js +1 -0
- package/dist/chunk-ONWXGBWB.js +1 -0
- package/dist/chunk-QEDQDR27.js +1 -0
- package/dist/chunk-QXVH6POE.js +1 -0
- package/dist/chunk-RXDTAWI2.js +29 -0
- package/dist/chunk-WKHW2QO2.js +1 -0
- package/dist/chunk-XRP4JJXL.js +1 -0
- package/dist/chunk-YHEWKN7N.js +1 -0
- package/dist/chunk-YHKXQ2ZN.js +1 -0
- package/dist/core.d.ts +206 -14
- package/dist/core.js +24 -12424
- package/dist/debug.d.ts +24 -0
- package/dist/debug.js +1 -0
- package/dist/default-controls-BGc61yYl.d.ts +111 -0
- package/dist/{entity-vj-HTjzU.d.ts → entity-DNkYKfdp.d.ts} +452 -578
- package/dist/entity-type-map-C94h2TDW.d.ts +781 -0
- package/dist/entity.d.ts +265 -0
- package/dist/entity.js +1 -0
- package/dist/events.d.ts +172 -0
- package/dist/events.js +1 -0
- package/dist/game-Cuj5gy7U.d.ts +1590 -0
- package/dist/game-default-PZLLHYL7.js +1 -0
- package/dist/game-interfaces-tfpM5NBk.d.ts +90 -0
- package/dist/globals.d.ts +104 -0
- package/dist/globals.js +1 -0
- package/dist/graphics.d.ts +83 -0
- package/dist/graphics.js +1 -0
- package/dist/input-ui.d.ts +34 -0
- package/dist/input-ui.js +1 -0
- package/dist/input.d.ts +72 -0
- package/dist/input.js +1 -0
- package/dist/material-Wz83vTTp.d.ts +151 -0
- package/dist/runtime.d.ts +433 -0
- package/dist/runtime.js +1 -0
- package/dist/web-components.d.ts +67 -0
- package/dist/web-components.js +9 -0
- package/dist/world-C9zdyl5d.d.ts +103 -0
- package/dist/{camera-4XO5gbQH.d.ts → zylem-stage-runtime-Cs-UDEVU.d.ts} +201 -329
- package/package.json +52 -63
- 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.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-Bq_eNEDI.d.ts +0 -28
- 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/camera.js
DELETED
|
@@ -1,1993 +0,0 @@
|
|
|
1
|
-
// src/lib/camera/camera.ts
|
|
2
|
-
import { Vector2 as Vector23, Vector3 as Vector37 } from "three";
|
|
3
|
-
|
|
4
|
-
// src/lib/camera/zylem-camera.ts
|
|
5
|
-
import { PerspectiveCamera as PerspectiveCamera2, Vector3 as Vector36, OrthographicCamera, WebGLRenderTarget as WebGLRenderTarget2, LinearFilter } from "three";
|
|
6
|
-
|
|
7
|
-
// src/lib/camera/perspective.ts
|
|
8
|
-
var Perspectives = {
|
|
9
|
-
FirstPerson: "first-person",
|
|
10
|
-
ThirdPerson: "third-person",
|
|
11
|
-
Isometric: "isometric",
|
|
12
|
-
Flat2D: "flat-2d",
|
|
13
|
-
Fixed2D: "fixed-2d",
|
|
14
|
-
TopDown: "top-down"
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// src/lib/camera/camera-debug-delegate.ts
|
|
18
|
-
import { Vector3 } from "three";
|
|
19
|
-
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
20
|
-
var CameraOrbitController = class {
|
|
21
|
-
camera;
|
|
22
|
-
domElement;
|
|
23
|
-
cameraRig = null;
|
|
24
|
-
sceneRef = null;
|
|
25
|
-
orbitControls = null;
|
|
26
|
-
orbitTarget = null;
|
|
27
|
-
orbitTargetWorldPos = new Vector3();
|
|
28
|
-
debugDelegate = null;
|
|
29
|
-
debugUnsubscribe = null;
|
|
30
|
-
debugStateSnapshot = { enabled: false, selected: [] };
|
|
31
|
-
// Saved camera state for restoration when exiting debug mode
|
|
32
|
-
savedCameraPosition = null;
|
|
33
|
-
savedCameraQuaternion = null;
|
|
34
|
-
savedCameraZoom = null;
|
|
35
|
-
savedCameraLocalPosition = null;
|
|
36
|
-
// Saved debug camera state for restoration when re-entering debug mode
|
|
37
|
-
savedDebugCameraPosition = null;
|
|
38
|
-
savedDebugCameraQuaternion = null;
|
|
39
|
-
savedDebugCameraZoom = null;
|
|
40
|
-
savedDebugOrbitTarget = null;
|
|
41
|
-
/** Whether user-configured orbital controls are enabled (independent of debug) */
|
|
42
|
-
_userOrbitEnabled = false;
|
|
43
|
-
constructor(camera, domElement, cameraRig) {
|
|
44
|
-
this.camera = camera;
|
|
45
|
-
this.domElement = domElement;
|
|
46
|
-
this.cameraRig = cameraRig ?? null;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Set the scene reference for adding/removing camera when detaching from rig.
|
|
50
|
-
*/
|
|
51
|
-
setScene(scene) {
|
|
52
|
-
this.sceneRef = scene;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Check if debug mode is currently active (orbit controls enabled via debug system).
|
|
56
|
-
*/
|
|
57
|
-
get isActive() {
|
|
58
|
-
return this.debugStateSnapshot.enabled;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Check if any orbit controls are currently active (debug or user).
|
|
62
|
-
*/
|
|
63
|
-
get isOrbitActive() {
|
|
64
|
-
return this.debugStateSnapshot.enabled || this._userOrbitEnabled;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Update orbit controls each frame.
|
|
68
|
-
* Should be called from the camera's update loop.
|
|
69
|
-
*/
|
|
70
|
-
update() {
|
|
71
|
-
if (this.orbitControls && this.orbitTarget) {
|
|
72
|
-
this.orbitTarget.getWorldPosition(this.orbitTargetWorldPos);
|
|
73
|
-
this.orbitControls.target.copy(this.orbitTargetWorldPos);
|
|
74
|
-
}
|
|
75
|
-
this.orbitControls?.update();
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Enable user-configured orbital controls (not debug mode).
|
|
79
|
-
* These orbit controls persist until explicitly disabled.
|
|
80
|
-
*/
|
|
81
|
-
enableUserOrbitControls() {
|
|
82
|
-
this._userOrbitEnabled = true;
|
|
83
|
-
if (!this.orbitControls) {
|
|
84
|
-
this.enableOrbitControls();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Disable user-configured orbital controls.
|
|
89
|
-
* Will not disable orbit controls if debug mode is active.
|
|
90
|
-
*/
|
|
91
|
-
disableUserOrbitControls() {
|
|
92
|
-
this._userOrbitEnabled = false;
|
|
93
|
-
if (!this.debugStateSnapshot.enabled) {
|
|
94
|
-
this.disableOrbitControls();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Attach a delegate to react to debug state changes.
|
|
99
|
-
*/
|
|
100
|
-
setDebugDelegate(delegate) {
|
|
101
|
-
if (this.debugDelegate === delegate) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
this.detachDebugDelegate();
|
|
105
|
-
this.debugDelegate = delegate;
|
|
106
|
-
if (!delegate) {
|
|
107
|
-
this.applyDebugState({ enabled: false, selected: [] });
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const unsubscribe = delegate.subscribe((state) => {
|
|
111
|
-
this.applyDebugState(state);
|
|
112
|
-
});
|
|
113
|
-
this.debugUnsubscribe = () => {
|
|
114
|
-
unsubscribe?.();
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Clean up resources.
|
|
119
|
-
*/
|
|
120
|
-
dispose() {
|
|
121
|
-
this.disableOrbitControls();
|
|
122
|
-
this.detachDebugDelegate();
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Get the current debug state snapshot.
|
|
126
|
-
*/
|
|
127
|
-
get debugState() {
|
|
128
|
-
return this.debugStateSnapshot;
|
|
129
|
-
}
|
|
130
|
-
applyDebugState(state) {
|
|
131
|
-
const wasEnabled = this.debugStateSnapshot.enabled;
|
|
132
|
-
this.debugStateSnapshot = {
|
|
133
|
-
enabled: state.enabled,
|
|
134
|
-
selected: [...state.selected]
|
|
135
|
-
};
|
|
136
|
-
if (state.enabled && !wasEnabled) {
|
|
137
|
-
this.saveCameraState();
|
|
138
|
-
this.detachCameraFromRig();
|
|
139
|
-
this.enableOrbitControls();
|
|
140
|
-
this.restoreDebugCameraState();
|
|
141
|
-
this.updateOrbitTargetFromSelection(state.selected);
|
|
142
|
-
} else if (!state.enabled && wasEnabled) {
|
|
143
|
-
this.saveDebugCameraState();
|
|
144
|
-
this.orbitTarget = null;
|
|
145
|
-
if (!this._userOrbitEnabled) {
|
|
146
|
-
this.disableOrbitControls();
|
|
147
|
-
}
|
|
148
|
-
this.reattachCameraToRig();
|
|
149
|
-
this.restoreCameraState();
|
|
150
|
-
} else if (state.enabled) {
|
|
151
|
-
this.updateOrbitTargetFromSelection(state.selected);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
enableOrbitControls() {
|
|
155
|
-
if (this.orbitControls) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
this.orbitControls = new OrbitControls(this.camera, this.domElement);
|
|
159
|
-
this.orbitControls.enableDamping = true;
|
|
160
|
-
this.orbitControls.dampingFactor = 0.05;
|
|
161
|
-
this.orbitControls.screenSpacePanning = false;
|
|
162
|
-
this.orbitControls.minDistance = 1;
|
|
163
|
-
this.orbitControls.maxDistance = 500;
|
|
164
|
-
this.orbitControls.maxPolarAngle = Math.PI / 2;
|
|
165
|
-
this.orbitControls.target.set(0, 0, 0);
|
|
166
|
-
}
|
|
167
|
-
disableOrbitControls() {
|
|
168
|
-
if (!this.orbitControls) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
this.orbitControls.dispose();
|
|
172
|
-
this.orbitControls = null;
|
|
173
|
-
}
|
|
174
|
-
updateOrbitTargetFromSelection(selected) {
|
|
175
|
-
if (!this.debugDelegate || selected.length === 0) {
|
|
176
|
-
this.orbitTarget = null;
|
|
177
|
-
if (this.orbitControls) {
|
|
178
|
-
this.orbitControls.target.set(0, 0, 0);
|
|
179
|
-
}
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
for (let i = selected.length - 1; i >= 0; i -= 1) {
|
|
183
|
-
const uuid = selected[i];
|
|
184
|
-
const targetObject = this.debugDelegate.resolveTarget(uuid);
|
|
185
|
-
if (targetObject) {
|
|
186
|
-
this.orbitTarget = targetObject;
|
|
187
|
-
if (this.orbitControls) {
|
|
188
|
-
targetObject.getWorldPosition(this.orbitTargetWorldPos);
|
|
189
|
-
this.orbitControls.target.copy(this.orbitTargetWorldPos);
|
|
190
|
-
}
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
this.orbitTarget = null;
|
|
195
|
-
}
|
|
196
|
-
detachDebugDelegate() {
|
|
197
|
-
if (this.debugUnsubscribe) {
|
|
198
|
-
try {
|
|
199
|
-
this.debugUnsubscribe();
|
|
200
|
-
} catch {
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
this.debugUnsubscribe = null;
|
|
204
|
-
this.debugDelegate = null;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Save camera position, rotation, and zoom before entering debug mode.
|
|
208
|
-
*/
|
|
209
|
-
saveCameraState() {
|
|
210
|
-
this.savedCameraPosition = this.camera.position.clone();
|
|
211
|
-
this.savedCameraQuaternion = this.camera.quaternion.clone();
|
|
212
|
-
if ("zoom" in this.camera) {
|
|
213
|
-
this.savedCameraZoom = this.camera.zoom;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Restore camera position, rotation, and zoom when exiting debug mode.
|
|
218
|
-
*/
|
|
219
|
-
restoreCameraState() {
|
|
220
|
-
if (this.savedCameraPosition) {
|
|
221
|
-
this.camera.position.copy(this.savedCameraPosition);
|
|
222
|
-
this.savedCameraPosition = null;
|
|
223
|
-
}
|
|
224
|
-
if (this.savedCameraQuaternion) {
|
|
225
|
-
this.camera.quaternion.copy(this.savedCameraQuaternion);
|
|
226
|
-
this.savedCameraQuaternion = null;
|
|
227
|
-
}
|
|
228
|
-
if (this.savedCameraZoom !== null && "zoom" in this.camera) {
|
|
229
|
-
this.camera.zoom = this.savedCameraZoom;
|
|
230
|
-
this.camera.updateProjectionMatrix?.();
|
|
231
|
-
this.savedCameraZoom = null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Save debug camera state when exiting debug mode.
|
|
236
|
-
*/
|
|
237
|
-
saveDebugCameraState() {
|
|
238
|
-
this.savedDebugCameraPosition = this.camera.position.clone();
|
|
239
|
-
this.savedDebugCameraQuaternion = this.camera.quaternion.clone();
|
|
240
|
-
if ("zoom" in this.camera) {
|
|
241
|
-
this.savedDebugCameraZoom = this.camera.zoom;
|
|
242
|
-
}
|
|
243
|
-
if (this.orbitControls) {
|
|
244
|
-
this.savedDebugOrbitTarget = this.orbitControls.target.clone();
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Restore debug camera state when re-entering debug mode.
|
|
249
|
-
*/
|
|
250
|
-
restoreDebugCameraState() {
|
|
251
|
-
if (this.savedDebugCameraPosition) {
|
|
252
|
-
this.camera.position.copy(this.savedDebugCameraPosition);
|
|
253
|
-
}
|
|
254
|
-
if (this.savedDebugCameraQuaternion) {
|
|
255
|
-
this.camera.quaternion.copy(this.savedDebugCameraQuaternion);
|
|
256
|
-
}
|
|
257
|
-
if (this.savedDebugCameraZoom !== null && "zoom" in this.camera) {
|
|
258
|
-
this.camera.zoom = this.savedDebugCameraZoom;
|
|
259
|
-
this.camera.updateProjectionMatrix?.();
|
|
260
|
-
}
|
|
261
|
-
if (this.savedDebugOrbitTarget && this.orbitControls) {
|
|
262
|
-
this.orbitControls.target.copy(this.savedDebugOrbitTarget);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Detach camera from its rig to allow free orbit movement in debug mode.
|
|
267
|
-
* Preserves the camera's world position.
|
|
268
|
-
*/
|
|
269
|
-
detachCameraFromRig() {
|
|
270
|
-
if (!this.cameraRig || this.camera.parent !== this.cameraRig) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
this.savedCameraLocalPosition = this.camera.position.clone();
|
|
274
|
-
const worldPos = new Vector3();
|
|
275
|
-
this.camera.getWorldPosition(worldPos);
|
|
276
|
-
this.cameraRig.remove(this.camera);
|
|
277
|
-
if (this.sceneRef) {
|
|
278
|
-
this.sceneRef.add(this.camera);
|
|
279
|
-
}
|
|
280
|
-
this.camera.position.copy(worldPos);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Reattach camera to its rig when exiting debug mode.
|
|
284
|
-
* Restores the camera's local position relative to the rig.
|
|
285
|
-
*/
|
|
286
|
-
reattachCameraToRig() {
|
|
287
|
-
if (!this.cameraRig || this.camera.parent === this.cameraRig) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (this.sceneRef && this.camera.parent === this.sceneRef) {
|
|
291
|
-
this.sceneRef.remove(this.camera);
|
|
292
|
-
}
|
|
293
|
-
this.cameraRig.add(this.camera);
|
|
294
|
-
if (this.savedCameraLocalPosition) {
|
|
295
|
-
this.camera.position.copy(this.savedCameraLocalPosition);
|
|
296
|
-
this.savedCameraLocalPosition = null;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
// src/lib/camera/renderer-manager.ts
|
|
302
|
-
import { Vector2, WebGLRenderer as WebGLRenderer2 } from "three";
|
|
303
|
-
import { WebGPURenderer } from "three/webgpu";
|
|
304
|
-
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
305
|
-
|
|
306
|
-
// src/lib/graphics/render-pass.ts
|
|
307
|
-
import * as THREE from "three";
|
|
308
|
-
|
|
309
|
-
// src/lib/graphics/shaders/vertex/object.shader.ts
|
|
310
|
-
var objectVertexShader = `
|
|
311
|
-
uniform vec2 uvScale;
|
|
312
|
-
varying vec2 vUv;
|
|
313
|
-
|
|
314
|
-
void main() {
|
|
315
|
-
vUv = uv;
|
|
316
|
-
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
317
|
-
gl_Position = projectionMatrix * mvPosition;
|
|
318
|
-
}
|
|
319
|
-
`;
|
|
320
|
-
|
|
321
|
-
// src/lib/graphics/shaders/standard.shader.ts
|
|
322
|
-
var fragment = `
|
|
323
|
-
uniform sampler2D tDiffuse;
|
|
324
|
-
varying vec2 vUv;
|
|
325
|
-
|
|
326
|
-
void main() {
|
|
327
|
-
vec4 texel = texture2D( tDiffuse, vUv );
|
|
328
|
-
|
|
329
|
-
gl_FragColor = texel;
|
|
330
|
-
}
|
|
331
|
-
`;
|
|
332
|
-
var standardShader = {
|
|
333
|
-
vertex: objectVertexShader,
|
|
334
|
-
fragment
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
// src/lib/graphics/render-pass.ts
|
|
338
|
-
import { WebGLRenderTarget } from "three";
|
|
339
|
-
import { Pass, FullScreenQuad } from "three/addons/postprocessing/Pass.js";
|
|
340
|
-
var RenderPass = class extends Pass {
|
|
341
|
-
fsQuad;
|
|
342
|
-
resolution;
|
|
343
|
-
scene;
|
|
344
|
-
camera;
|
|
345
|
-
rgbRenderTarget;
|
|
346
|
-
normalRenderTarget;
|
|
347
|
-
normalMaterial;
|
|
348
|
-
constructor(resolution, scene, camera) {
|
|
349
|
-
super();
|
|
350
|
-
this.resolution = resolution;
|
|
351
|
-
this.fsQuad = new FullScreenQuad(this.material());
|
|
352
|
-
this.scene = scene;
|
|
353
|
-
this.camera = camera;
|
|
354
|
-
this.rgbRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
|
|
355
|
-
this.normalRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
|
|
356
|
-
this.normalMaterial = new THREE.MeshNormalMaterial();
|
|
357
|
-
}
|
|
358
|
-
render(renderer, writeBuffer) {
|
|
359
|
-
renderer.setRenderTarget(this.rgbRenderTarget);
|
|
360
|
-
renderer.render(this.scene, this.camera);
|
|
361
|
-
const overrideMaterial_old = this.scene.overrideMaterial;
|
|
362
|
-
renderer.setRenderTarget(this.normalRenderTarget);
|
|
363
|
-
this.scene.overrideMaterial = this.normalMaterial;
|
|
364
|
-
renderer.render(this.scene, this.camera);
|
|
365
|
-
this.scene.overrideMaterial = overrideMaterial_old;
|
|
366
|
-
const uniforms = this.fsQuad.material.uniforms;
|
|
367
|
-
uniforms.tDiffuse.value = this.rgbRenderTarget.texture;
|
|
368
|
-
uniforms.tDepth.value = this.rgbRenderTarget.depthTexture;
|
|
369
|
-
uniforms.tNormal.value = this.normalRenderTarget.texture;
|
|
370
|
-
uniforms.iTime.value += 0.01;
|
|
371
|
-
if (this.renderToScreen) {
|
|
372
|
-
renderer.setRenderTarget(null);
|
|
373
|
-
} else {
|
|
374
|
-
renderer.setRenderTarget(writeBuffer);
|
|
375
|
-
}
|
|
376
|
-
this.fsQuad.render(renderer);
|
|
377
|
-
}
|
|
378
|
-
material() {
|
|
379
|
-
return new THREE.ShaderMaterial({
|
|
380
|
-
uniforms: {
|
|
381
|
-
iTime: { value: 0 },
|
|
382
|
-
tDiffuse: { value: null },
|
|
383
|
-
tDepth: { value: null },
|
|
384
|
-
tNormal: { value: null },
|
|
385
|
-
resolution: {
|
|
386
|
-
value: new THREE.Vector4(
|
|
387
|
-
this.resolution.x,
|
|
388
|
-
this.resolution.y,
|
|
389
|
-
1 / this.resolution.x,
|
|
390
|
-
1 / this.resolution.y
|
|
391
|
-
)
|
|
392
|
-
}
|
|
393
|
-
},
|
|
394
|
-
vertexShader: standardShader.vertex,
|
|
395
|
-
fragmentShader: standardShader.fragment
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
dispose() {
|
|
399
|
-
try {
|
|
400
|
-
this.fsQuad?.dispose?.();
|
|
401
|
-
} catch {
|
|
402
|
-
}
|
|
403
|
-
try {
|
|
404
|
-
this.rgbRenderTarget?.dispose?.();
|
|
405
|
-
this.normalRenderTarget?.dispose?.();
|
|
406
|
-
} catch {
|
|
407
|
-
}
|
|
408
|
-
try {
|
|
409
|
-
this.normalMaterial?.dispose?.();
|
|
410
|
-
} catch {
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
// src/lib/camera/renderer-manager.ts
|
|
416
|
-
var DEFAULT_VIEWPORT = { x: 0, y: 0, width: 1, height: 1 };
|
|
417
|
-
async function isWebGPUSupported() {
|
|
418
|
-
if (!("gpu" in navigator)) return false;
|
|
419
|
-
try {
|
|
420
|
-
const adapter = await navigator.gpu.requestAdapter();
|
|
421
|
-
return adapter !== null;
|
|
422
|
-
} catch {
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
var RendererManager = class {
|
|
427
|
-
renderer;
|
|
428
|
-
composer;
|
|
429
|
-
screenResolution;
|
|
430
|
-
rendererType;
|
|
431
|
-
_isWebGPU = false;
|
|
432
|
-
_initialized = false;
|
|
433
|
-
_sceneRef = null;
|
|
434
|
-
_lastAnimationTimestamp = null;
|
|
435
|
-
constructor(screenResolution, rendererType = "webgl") {
|
|
436
|
-
this.screenResolution = screenResolution || new Vector2(window.innerWidth, window.innerHeight);
|
|
437
|
-
this.rendererType = rendererType;
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Check if the renderer has been initialized
|
|
441
|
-
*/
|
|
442
|
-
get initialized() {
|
|
443
|
-
return this._initialized;
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Check if using WebGPU renderer
|
|
447
|
-
*/
|
|
448
|
-
get isWebGPU() {
|
|
449
|
-
return this._isWebGPU;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Initialize the renderer (must be called before rendering).
|
|
453
|
-
* Async because WebGPU requires async initialization.
|
|
454
|
-
*/
|
|
455
|
-
async initRenderer() {
|
|
456
|
-
if (this._initialized) return;
|
|
457
|
-
let useWebGPU = false;
|
|
458
|
-
if (this.rendererType === "webgpu") {
|
|
459
|
-
useWebGPU = true;
|
|
460
|
-
} else if (this.rendererType === "auto") {
|
|
461
|
-
useWebGPU = await isWebGPUSupported();
|
|
462
|
-
}
|
|
463
|
-
if (useWebGPU) {
|
|
464
|
-
try {
|
|
465
|
-
this.renderer = new WebGPURenderer({ antialias: true });
|
|
466
|
-
await this.renderer.init();
|
|
467
|
-
this._isWebGPU = true;
|
|
468
|
-
console.log("RendererManager: Using WebGPU renderer");
|
|
469
|
-
} catch (e) {
|
|
470
|
-
console.warn("RendererManager: WebGPU init failed, falling back to WebGL", e);
|
|
471
|
-
this.renderer = new WebGLRenderer2({ antialias: false, alpha: true });
|
|
472
|
-
this._isWebGPU = false;
|
|
473
|
-
}
|
|
474
|
-
} else {
|
|
475
|
-
this.renderer = new WebGLRenderer2({ antialias: false, alpha: true });
|
|
476
|
-
this._isWebGPU = false;
|
|
477
|
-
console.log("RendererManager: Using WebGL renderer");
|
|
478
|
-
}
|
|
479
|
-
this.renderer.setSize(this.screenResolution.x, this.screenResolution.y);
|
|
480
|
-
if (this.renderer instanceof WebGLRenderer2) {
|
|
481
|
-
this.renderer.shadowMap.enabled = true;
|
|
482
|
-
}
|
|
483
|
-
if (!this._isWebGPU) {
|
|
484
|
-
this.composer = new EffectComposer(this.renderer);
|
|
485
|
-
}
|
|
486
|
-
this._initialized = true;
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Set the current scene reference for rendering.
|
|
490
|
-
*/
|
|
491
|
-
setScene(scene) {
|
|
492
|
-
this._sceneRef = scene;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Setup post-processing render pass for a camera (WebGL only).
|
|
496
|
-
*/
|
|
497
|
-
setupRenderPass(scene, camera) {
|
|
498
|
-
if (this._isWebGPU || !this.composer) return;
|
|
499
|
-
if (this.composer.passes.length > 0) {
|
|
500
|
-
this.composer.passes.forEach((p) => {
|
|
501
|
-
try {
|
|
502
|
-
p.dispose?.();
|
|
503
|
-
} catch {
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
this.composer.passes.length = 0;
|
|
507
|
-
}
|
|
508
|
-
const renderResolution = this.screenResolution.clone().divideScalar(2);
|
|
509
|
-
renderResolution.x |= 0;
|
|
510
|
-
renderResolution.y |= 0;
|
|
511
|
-
const pass = new RenderPass(renderResolution, scene, camera);
|
|
512
|
-
this.composer.addPass(pass);
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Start the render loop. Calls the provided callback each frame.
|
|
516
|
-
*/
|
|
517
|
-
startRenderLoop(onFrame) {
|
|
518
|
-
this._lastAnimationTimestamp = null;
|
|
519
|
-
this.renderer.setAnimationLoop((timestamp) => {
|
|
520
|
-
const deltaSeconds = this._lastAnimationTimestamp === null ? 0 : Math.max(0, (timestamp - this._lastAnimationTimestamp) / 1e3);
|
|
521
|
-
this._lastAnimationTimestamp = timestamp;
|
|
522
|
-
onFrame(deltaSeconds);
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Stop the render loop.
|
|
527
|
-
*/
|
|
528
|
-
stopRenderLoop() {
|
|
529
|
-
this._lastAnimationTimestamp = null;
|
|
530
|
-
try {
|
|
531
|
-
this.renderer.setAnimationLoop(null);
|
|
532
|
-
} catch {
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Render a scene from a single camera's perspective.
|
|
537
|
-
* Sets the viewport based on the camera's viewport config.
|
|
538
|
-
*/
|
|
539
|
-
renderCamera(scene, camera) {
|
|
540
|
-
const vp = camera.viewport;
|
|
541
|
-
const w = this.screenResolution.x;
|
|
542
|
-
const h = this.screenResolution.y;
|
|
543
|
-
const pixelX = Math.floor(vp.x * w);
|
|
544
|
-
const pixelY = Math.floor(vp.y * h);
|
|
545
|
-
const pixelW = Math.floor(vp.width * w);
|
|
546
|
-
const pixelH = Math.floor(vp.height * h);
|
|
547
|
-
if (this.renderer instanceof WebGLRenderer2) {
|
|
548
|
-
this.renderer.setViewport(pixelX, pixelY, pixelW, pixelH);
|
|
549
|
-
this.renderer.setScissor(pixelX, pixelY, pixelW, pixelH);
|
|
550
|
-
this.renderer.setScissorTest(true);
|
|
551
|
-
}
|
|
552
|
-
if (this._isWebGPU) {
|
|
553
|
-
this.renderer.render(scene, camera.camera);
|
|
554
|
-
} else if (this.composer) {
|
|
555
|
-
this.composer.render(0);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Render a camera to its offscreen render target (WebGL only).
|
|
560
|
-
* Bypasses the EffectComposer since post-processing is not needed
|
|
561
|
-
* for render-to-texture output.
|
|
562
|
-
*
|
|
563
|
-
* The camera must have a non-null renderTarget.
|
|
564
|
-
*/
|
|
565
|
-
renderCameraToTarget(scene, camera) {
|
|
566
|
-
if (!camera.renderTarget) return;
|
|
567
|
-
if (this.renderer instanceof WebGLRenderer2) {
|
|
568
|
-
const prevTarget = this.renderer.getRenderTarget();
|
|
569
|
-
this.renderer.setRenderTarget(camera.renderTarget);
|
|
570
|
-
this.renderer.clear();
|
|
571
|
-
this.renderer.render(scene, camera.camera);
|
|
572
|
-
this.renderer.setRenderTarget(prevTarget);
|
|
573
|
-
} else {
|
|
574
|
-
console.warn("RendererManager: Render-to-texture is not yet supported for WebGPU");
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Render a scene from multiple cameras, each with their own viewport.
|
|
579
|
-
* Cameras are rendered in order (first = bottom layer, last = top layer).
|
|
580
|
-
*/
|
|
581
|
-
renderCameras(scene, cameras) {
|
|
582
|
-
if (!scene || cameras.length === 0) return;
|
|
583
|
-
if (this.renderer instanceof WebGLRenderer2) {
|
|
584
|
-
this.renderer.setScissorTest(false);
|
|
585
|
-
this.renderer.clear();
|
|
586
|
-
}
|
|
587
|
-
for (const cam of cameras) {
|
|
588
|
-
this.renderCamera(scene, cam);
|
|
589
|
-
}
|
|
590
|
-
if (this.renderer instanceof WebGLRenderer2) {
|
|
591
|
-
this.renderer.setScissorTest(false);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Simple single-camera render (backwards compatible).
|
|
596
|
-
* Uses the full viewport for a single camera.
|
|
597
|
-
*/
|
|
598
|
-
render(scene, camera) {
|
|
599
|
-
if (this._isWebGPU) {
|
|
600
|
-
this.renderer.render(scene, camera);
|
|
601
|
-
} else if (this.composer) {
|
|
602
|
-
this.composer.render(0);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Resize the renderer and update resolution.
|
|
607
|
-
*/
|
|
608
|
-
resize(width, height) {
|
|
609
|
-
this.screenResolution.set(width, height);
|
|
610
|
-
this.renderer.setSize(width, height, false);
|
|
611
|
-
if (this.composer) {
|
|
612
|
-
this.composer.setSize(width, height);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Update renderer pixel ratio (DPR).
|
|
617
|
-
*/
|
|
618
|
-
setPixelRatio(dpr) {
|
|
619
|
-
const safe = Math.max(1, Number.isFinite(dpr) ? dpr : 1);
|
|
620
|
-
this.renderer.setPixelRatio(safe);
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Get the DOM element for the renderer.
|
|
624
|
-
*/
|
|
625
|
-
getDomElement() {
|
|
626
|
-
return this.renderer.domElement;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Dispose renderer, composer, and related resources.
|
|
630
|
-
*/
|
|
631
|
-
dispose() {
|
|
632
|
-
this.stopRenderLoop();
|
|
633
|
-
try {
|
|
634
|
-
this.composer?.passes?.forEach((p) => p.dispose?.());
|
|
635
|
-
this.composer?.dispose?.();
|
|
636
|
-
} catch {
|
|
637
|
-
}
|
|
638
|
-
try {
|
|
639
|
-
this.renderer.dispose();
|
|
640
|
-
} catch {
|
|
641
|
-
}
|
|
642
|
-
this._sceneRef = null;
|
|
643
|
-
this._initialized = false;
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
// src/lib/camera/smoothing.ts
|
|
648
|
-
import { Vector3 as Vector32, Quaternion as Quaternion2, MathUtils } from "three";
|
|
649
|
-
function defaultPose() {
|
|
650
|
-
return {
|
|
651
|
-
position: new Vector32(0, 0, 10),
|
|
652
|
-
rotation: new Quaternion2(),
|
|
653
|
-
fov: 75,
|
|
654
|
-
zoom: 1,
|
|
655
|
-
near: 0.1,
|
|
656
|
-
far: 1e3
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
function clonePose(pose) {
|
|
660
|
-
return {
|
|
661
|
-
position: pose.position.clone(),
|
|
662
|
-
rotation: pose.rotation.clone(),
|
|
663
|
-
fov: pose.fov,
|
|
664
|
-
zoom: pose.zoom,
|
|
665
|
-
near: pose.near,
|
|
666
|
-
far: pose.far,
|
|
667
|
-
lookAt: pose.lookAt?.clone()
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
function applyDelta(pose, delta) {
|
|
671
|
-
const result = clonePose(pose);
|
|
672
|
-
if (delta.position) {
|
|
673
|
-
result.position.add(delta.position);
|
|
674
|
-
}
|
|
675
|
-
if (delta.rotation) {
|
|
676
|
-
result.rotation.multiply(delta.rotation);
|
|
677
|
-
}
|
|
678
|
-
if (delta.fov != null && result.fov != null) {
|
|
679
|
-
result.fov += delta.fov;
|
|
680
|
-
}
|
|
681
|
-
if (delta.zoom != null && result.zoom != null) {
|
|
682
|
-
result.zoom += delta.zoom;
|
|
683
|
-
}
|
|
684
|
-
return result;
|
|
685
|
-
}
|
|
686
|
-
function smoothPose(current, target, damping, dt) {
|
|
687
|
-
const t = 1 - Math.pow(1 - MathUtils.clamp(damping, 0, 1), dt * 60);
|
|
688
|
-
const result = clonePose(current);
|
|
689
|
-
result.position.lerp(target.position, t);
|
|
690
|
-
result.rotation.slerp(target.rotation, t);
|
|
691
|
-
if (target.fov != null && result.fov != null) {
|
|
692
|
-
result.fov = MathUtils.lerp(result.fov, target.fov, t);
|
|
693
|
-
}
|
|
694
|
-
if (target.zoom != null && result.zoom != null) {
|
|
695
|
-
result.zoom = MathUtils.lerp(result.zoom, target.zoom, t);
|
|
696
|
-
}
|
|
697
|
-
if (target.near != null) {
|
|
698
|
-
result.near = target.near;
|
|
699
|
-
}
|
|
700
|
-
if (target.far != null) {
|
|
701
|
-
result.far = target.far;
|
|
702
|
-
}
|
|
703
|
-
result.lookAt = target.lookAt?.clone();
|
|
704
|
-
return result;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// src/lib/camera/camera-pipeline.ts
|
|
708
|
-
var CameraPipeline = class {
|
|
709
|
-
/** Active perspective (exactly one at a time). */
|
|
710
|
-
perspective = null;
|
|
711
|
-
/** Keyed behaviors, sorted by priority each frame. */
|
|
712
|
-
behaviors = /* @__PURE__ */ new Map();
|
|
713
|
-
/** Active transient actions. Expired actions are auto-removed. */
|
|
714
|
-
actions = [];
|
|
715
|
-
/** The desired pose after perspective + behaviors + actions. */
|
|
716
|
-
_desiredPose = defaultPose();
|
|
717
|
-
/** The smoothed final pose committed to the Three.js camera. */
|
|
718
|
-
_finalPose = defaultPose();
|
|
719
|
-
/** Whether the pipeline has run at least once (prevents lerp from origin on first frame). */
|
|
720
|
-
_initialized = false;
|
|
721
|
-
/** Smoothing factor: 0 = no movement, 1 = instant snap. */
|
|
722
|
-
damping = 0.15;
|
|
723
|
-
constructor(perspective) {
|
|
724
|
-
if (perspective) {
|
|
725
|
-
this.perspective = perspective;
|
|
726
|
-
if (perspective.defaults?.damping != null) {
|
|
727
|
-
this.damping = perspective.defaults.damping;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Run the full pipeline for one frame.
|
|
733
|
-
* Returns the final pose that should be committed to the Three.js camera.
|
|
734
|
-
*/
|
|
735
|
-
run(ctx) {
|
|
736
|
-
let pose = this.perspective ? this.perspective.getBasePose(ctx) : defaultPose();
|
|
737
|
-
const sorted = this.getSortedBehaviors();
|
|
738
|
-
for (const behavior of sorted) {
|
|
739
|
-
if (behavior.enabled === false) continue;
|
|
740
|
-
pose = behavior.update(ctx, pose);
|
|
741
|
-
}
|
|
742
|
-
this._desiredPose = clonePose(pose);
|
|
743
|
-
for (let i = this.actions.length - 1; i >= 0; i--) {
|
|
744
|
-
const action = this.actions[i];
|
|
745
|
-
const delta = action.update(ctx);
|
|
746
|
-
pose = applyDelta(pose, delta);
|
|
747
|
-
if (action.isDone(ctx)) {
|
|
748
|
-
this.actions.splice(i, 1);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
if (!this._initialized) {
|
|
752
|
-
this._finalPose = clonePose(pose);
|
|
753
|
-
this._initialized = true;
|
|
754
|
-
} else {
|
|
755
|
-
this._finalPose = smoothPose(this._finalPose, pose, this.damping, ctx.dt);
|
|
756
|
-
}
|
|
757
|
-
return this._finalPose;
|
|
758
|
-
}
|
|
759
|
-
// ─── Behavior management ───────────────────────────────────────────────
|
|
760
|
-
/**
|
|
761
|
-
* Add or replace a behavior by key (idempotent).
|
|
762
|
-
* Calls onDetach on the old behavior and onAttach on the new one.
|
|
763
|
-
*/
|
|
764
|
-
addBehavior(key, behavior, ctx) {
|
|
765
|
-
const existing = this.behaviors.get(key);
|
|
766
|
-
if (existing?.onDetach && ctx) {
|
|
767
|
-
existing.onDetach(ctx);
|
|
768
|
-
}
|
|
769
|
-
this.behaviors.set(key, behavior);
|
|
770
|
-
if (behavior.onAttach && ctx) {
|
|
771
|
-
behavior.onAttach(ctx);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Remove a behavior by key. Calls onDetach if a context is provided.
|
|
776
|
-
*/
|
|
777
|
-
removeBehavior(key, ctx) {
|
|
778
|
-
const existing = this.behaviors.get(key);
|
|
779
|
-
if (!existing) return false;
|
|
780
|
-
if (existing.onDetach && ctx) {
|
|
781
|
-
existing.onDetach(ctx);
|
|
782
|
-
}
|
|
783
|
-
return this.behaviors.delete(key);
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Check whether a behavior with the given key exists.
|
|
787
|
-
*/
|
|
788
|
-
hasBehavior(key) {
|
|
789
|
-
return this.behaviors.has(key);
|
|
790
|
-
}
|
|
791
|
-
// ─── Action management ─────────────────────────────────────────────────
|
|
792
|
-
/**
|
|
793
|
-
* Add a transient action. Actions self-expire via isDone().
|
|
794
|
-
*/
|
|
795
|
-
addAction(action) {
|
|
796
|
-
this.actions.push(action);
|
|
797
|
-
}
|
|
798
|
-
// ─── State inspection ──────────────────────────────────────────────────
|
|
799
|
-
/**
|
|
800
|
-
* Return a debug snapshot of the pipeline state.
|
|
801
|
-
*/
|
|
802
|
-
getState() {
|
|
803
|
-
return {
|
|
804
|
-
perspectiveId: this.perspective?.id ?? null,
|
|
805
|
-
desiredPose: this._desiredPose ? clonePose(this._desiredPose) : null,
|
|
806
|
-
finalPose: this._finalPose ? clonePose(this._finalPose) : null,
|
|
807
|
-
activeBehaviors: Array.from(this.behaviors.keys()),
|
|
808
|
-
activeActionCount: this.actions.length
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Set a new perspective. Resets the pipeline initialization flag so the
|
|
813
|
-
* first frame with the new perspective snaps instead of lerping from the old pose.
|
|
814
|
-
*/
|
|
815
|
-
setPerspective(perspective) {
|
|
816
|
-
this.perspective = perspective;
|
|
817
|
-
this._initialized = false;
|
|
818
|
-
if (perspective.defaults?.damping != null) {
|
|
819
|
-
this.damping = perspective.defaults.damping;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
// ─── Internal helpers ──────────────────────────────────────────────────
|
|
823
|
-
getSortedBehaviors() {
|
|
824
|
-
return Array.from(this.behaviors.values()).sort(
|
|
825
|
-
(a, b) => (a.priority ?? 0) - (b.priority ?? 0)
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
|
|
830
|
-
// src/lib/camera/perspectives/third-person-perspective.ts
|
|
831
|
-
import { Vector3 as Vector33, Quaternion as Quaternion3 } from "three";
|
|
832
|
-
var DEFAULTS = {
|
|
833
|
-
distance: 8,
|
|
834
|
-
height: 5,
|
|
835
|
-
shoulderOffset: 0,
|
|
836
|
-
targetKey: "primary",
|
|
837
|
-
fov: 75,
|
|
838
|
-
paddingFactor: 1.5,
|
|
839
|
-
minDistance: 5
|
|
840
|
-
};
|
|
841
|
-
var ThirdPersonPerspective = class {
|
|
842
|
-
id = "third-person";
|
|
843
|
-
defaults = { damping: 0.15 };
|
|
844
|
-
opts;
|
|
845
|
-
initialPosition;
|
|
846
|
-
initialLookAt;
|
|
847
|
-
constructor(options) {
|
|
848
|
-
const { initialPosition, initialLookAt, ...rest } = options ?? {};
|
|
849
|
-
this.opts = { ...DEFAULTS, ...rest };
|
|
850
|
-
this.initialPosition = initialPosition;
|
|
851
|
-
this.initialLookAt = initialLookAt;
|
|
852
|
-
}
|
|
853
|
-
getBasePose(ctx) {
|
|
854
|
-
const targetKeys = Object.keys(ctx.targets);
|
|
855
|
-
const primary = ctx.targets[this.opts.targetKey];
|
|
856
|
-
if (targetKeys.length === 0 || !primary) {
|
|
857
|
-
return this.staticPose();
|
|
858
|
-
}
|
|
859
|
-
if (targetKeys.length === 1) {
|
|
860
|
-
return this.singleTargetPose(primary.position);
|
|
861
|
-
}
|
|
862
|
-
return this.multiTargetPose(ctx);
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* No targets: use the user-specified initial position if available,
|
|
866
|
-
* otherwise fall back to a default pose behind the origin.
|
|
867
|
-
*/
|
|
868
|
-
staticPose() {
|
|
869
|
-
if (this.initialPosition) {
|
|
870
|
-
return {
|
|
871
|
-
position: this.initialPosition.clone(),
|
|
872
|
-
rotation: new Quaternion3(),
|
|
873
|
-
fov: this.opts.fov,
|
|
874
|
-
zoom: 1,
|
|
875
|
-
near: 0.1,
|
|
876
|
-
far: 1e3,
|
|
877
|
-
lookAt: this.initialLookAt?.clone() ?? new Vector33(0, 0, 0)
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
const position = new Vector33(
|
|
881
|
-
this.opts.shoulderOffset,
|
|
882
|
-
this.opts.height,
|
|
883
|
-
this.opts.distance
|
|
884
|
-
);
|
|
885
|
-
return {
|
|
886
|
-
position,
|
|
887
|
-
rotation: new Quaternion3(),
|
|
888
|
-
fov: this.opts.fov,
|
|
889
|
-
zoom: 1,
|
|
890
|
-
near: 0.1,
|
|
891
|
-
far: 1e3,
|
|
892
|
-
lookAt: new Vector33(0, 0, 0)
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Single target: position = target + offset, lookAt = target.
|
|
897
|
-
*/
|
|
898
|
-
singleTargetPose(targetPos) {
|
|
899
|
-
const position = new Vector33(
|
|
900
|
-
targetPos.x + this.opts.shoulderOffset,
|
|
901
|
-
targetPos.y + this.opts.height,
|
|
902
|
-
targetPos.z + this.opts.distance
|
|
903
|
-
);
|
|
904
|
-
return {
|
|
905
|
-
position,
|
|
906
|
-
rotation: new Quaternion3(),
|
|
907
|
-
fov: this.opts.fov,
|
|
908
|
-
zoom: 1,
|
|
909
|
-
near: 0.1,
|
|
910
|
-
far: 1e3,
|
|
911
|
-
lookAt: targetPos.clone()
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
/**
|
|
915
|
-
* Multi-target: compute centroid and adjust distance to frame all targets.
|
|
916
|
-
*/
|
|
917
|
-
multiTargetPose(ctx) {
|
|
918
|
-
const targets = Object.values(ctx.targets);
|
|
919
|
-
const centroid = new Vector33();
|
|
920
|
-
for (const t of targets) {
|
|
921
|
-
centroid.add(t.position);
|
|
922
|
-
}
|
|
923
|
-
centroid.divideScalar(targets.length);
|
|
924
|
-
let maxDist = 0;
|
|
925
|
-
for (const t of targets) {
|
|
926
|
-
const d = centroid.distanceTo(t.position);
|
|
927
|
-
if (d > maxDist) maxDist = d;
|
|
928
|
-
}
|
|
929
|
-
const dynamicDistance = Math.max(
|
|
930
|
-
maxDist * this.opts.paddingFactor,
|
|
931
|
-
this.opts.minDistance
|
|
932
|
-
);
|
|
933
|
-
const baseOffset = new Vector33(
|
|
934
|
-
this.opts.shoulderOffset,
|
|
935
|
-
this.opts.height,
|
|
936
|
-
this.opts.distance
|
|
937
|
-
);
|
|
938
|
-
const baseLen = baseOffset.length();
|
|
939
|
-
const dir = baseLen > 0 ? baseOffset.clone().normalize() : new Vector33(0, 0.5, 1).normalize();
|
|
940
|
-
const position = centroid.clone().add(dir.multiplyScalar(dynamicDistance));
|
|
941
|
-
if (baseLen > 0) {
|
|
942
|
-
const heightRatio = this.opts.height / baseLen;
|
|
943
|
-
position.y = centroid.y + dynamicDistance * heightRatio;
|
|
944
|
-
}
|
|
945
|
-
return {
|
|
946
|
-
position,
|
|
947
|
-
rotation: new Quaternion3(),
|
|
948
|
-
fov: this.opts.fov,
|
|
949
|
-
zoom: 1,
|
|
950
|
-
near: 0.1,
|
|
951
|
-
far: 1e3,
|
|
952
|
-
lookAt: centroid.clone()
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
// src/lib/camera/perspectives/fixed-2d-perspective.ts
|
|
958
|
-
import { Vector3 as Vector34, Quaternion as Quaternion4 } from "three";
|
|
959
|
-
var DEFAULTS2 = {
|
|
960
|
-
position: { x: 0, y: 0, z: 10 },
|
|
961
|
-
zoom: 10
|
|
962
|
-
};
|
|
963
|
-
var Fixed2DPerspective = class {
|
|
964
|
-
id = "fixed-2d";
|
|
965
|
-
defaults = { damping: 1 };
|
|
966
|
-
opts;
|
|
967
|
-
constructor(options) {
|
|
968
|
-
this.opts = { ...DEFAULTS2, ...options };
|
|
969
|
-
}
|
|
970
|
-
getBasePose(_ctx) {
|
|
971
|
-
return {
|
|
972
|
-
position: new Vector34(
|
|
973
|
-
this.opts.position.x,
|
|
974
|
-
this.opts.position.y,
|
|
975
|
-
this.opts.position.z
|
|
976
|
-
),
|
|
977
|
-
rotation: new Quaternion4(),
|
|
978
|
-
zoom: this.opts.zoom,
|
|
979
|
-
near: 1,
|
|
980
|
-
far: 1e3,
|
|
981
|
-
lookAt: new Vector34(this.opts.position.x, this.opts.position.y, 0)
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
|
|
986
|
-
// src/lib/camera/perspectives/first-person-perspective.ts
|
|
987
|
-
import { Vector3 as Vector35, Quaternion as Quaternion5, Euler, MathUtils as MathUtils2 } from "three";
|
|
988
|
-
var DEFAULTS3 = {
|
|
989
|
-
eyeHeight: 1.7,
|
|
990
|
-
defaultFov: 75,
|
|
991
|
-
pitchLimit: Math.PI / 2 - 0.01,
|
|
992
|
-
lookAtLerpSpeed: 5,
|
|
993
|
-
fovLerpSpeed: 8,
|
|
994
|
-
targetKey: "primary"
|
|
995
|
-
};
|
|
996
|
-
var FirstPersonPerspective = class {
|
|
997
|
-
id = "first-person";
|
|
998
|
-
defaults = { damping: 1 };
|
|
999
|
-
opts;
|
|
1000
|
-
/** Fallback position when no target entity is attached. Mutate directly for manual movement. */
|
|
1001
|
-
initialPosition;
|
|
1002
|
-
initialLookAt;
|
|
1003
|
-
// --- Look state ---
|
|
1004
|
-
_yaw = 0;
|
|
1005
|
-
_pitch = 0;
|
|
1006
|
-
// --- FOV zoom state ---
|
|
1007
|
-
_currentFov;
|
|
1008
|
-
_targetFov;
|
|
1009
|
-
// --- Look-at target state ---
|
|
1010
|
-
_lookAtTarget = null;
|
|
1011
|
-
_lookAtLerpSpeed;
|
|
1012
|
-
_currentRotation = new Quaternion5();
|
|
1013
|
-
_rotationInitialized = false;
|
|
1014
|
-
constructor(options) {
|
|
1015
|
-
const { initialPosition, initialLookAt, ...rest } = options ?? {};
|
|
1016
|
-
this.opts = { ...DEFAULTS3, ...rest };
|
|
1017
|
-
this.initialPosition = initialPosition;
|
|
1018
|
-
this.initialLookAt = initialLookAt;
|
|
1019
|
-
this._currentFov = this.opts.defaultFov;
|
|
1020
|
-
this._targetFov = this.opts.defaultFov;
|
|
1021
|
-
this._lookAtLerpSpeed = this.opts.lookAtLerpSpeed;
|
|
1022
|
-
if (initialPosition && initialLookAt) {
|
|
1023
|
-
this.deriveYawPitchFromLookAt(initialPosition, initialLookAt);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
// --- Public API (called by game code / FPS behavior) ---
|
|
1027
|
-
/** Accumulate yaw and pitch deltas. Pitch is clamped to the configured limit. */
|
|
1028
|
-
look(deltaYaw, deltaPitch) {
|
|
1029
|
-
this._yaw += deltaYaw;
|
|
1030
|
-
this._pitch = MathUtils2.clamp(
|
|
1031
|
-
this._pitch + deltaPitch,
|
|
1032
|
-
-this.opts.pitchLimit,
|
|
1033
|
-
this.opts.pitchLimit
|
|
1034
|
-
);
|
|
1035
|
-
}
|
|
1036
|
-
/** Set absolute yaw and pitch. Pitch is clamped to the configured limit. */
|
|
1037
|
-
setLook(yaw, pitch) {
|
|
1038
|
-
this._yaw = yaw;
|
|
1039
|
-
this._pitch = MathUtils2.clamp(pitch, -this.opts.pitchLimit, this.opts.pitchLimit);
|
|
1040
|
-
}
|
|
1041
|
-
/** Current yaw in radians. */
|
|
1042
|
-
get yaw() {
|
|
1043
|
-
return this._yaw;
|
|
1044
|
-
}
|
|
1045
|
-
/** Current pitch in radians. */
|
|
1046
|
-
get pitch() {
|
|
1047
|
-
return this._pitch;
|
|
1048
|
-
}
|
|
1049
|
-
/** Set the target FOV for a smooth zoom transition (e.g. sniper scope). */
|
|
1050
|
-
zoom(fov) {
|
|
1051
|
-
this._targetFov = fov;
|
|
1052
|
-
}
|
|
1053
|
-
/** Return to the default FOV. */
|
|
1054
|
-
resetZoom() {
|
|
1055
|
-
this._targetFov = this.opts.defaultFov;
|
|
1056
|
-
}
|
|
1057
|
-
/** Current field of view. */
|
|
1058
|
-
get currentFov() {
|
|
1059
|
-
return this._currentFov;
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Enable smooth look-at toward a world position.
|
|
1063
|
-
* The camera will slerp from the current rotation toward the look-at direction.
|
|
1064
|
-
*/
|
|
1065
|
-
lookAt(target, lerpSpeed) {
|
|
1066
|
-
this._lookAtTarget = target;
|
|
1067
|
-
if (lerpSpeed != null) {
|
|
1068
|
-
this._lookAtLerpSpeed = lerpSpeed;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
/** Disable look-at and return to manual yaw/pitch control. */
|
|
1072
|
-
clearLookAt() {
|
|
1073
|
-
if (this._lookAtTarget) {
|
|
1074
|
-
this.deriveYawPitchFromQuaternion(this._currentRotation);
|
|
1075
|
-
}
|
|
1076
|
-
this._lookAtTarget = null;
|
|
1077
|
-
}
|
|
1078
|
-
// --- CameraPerspective interface ---
|
|
1079
|
-
getBasePose(ctx) {
|
|
1080
|
-
const position = this.computePosition(ctx);
|
|
1081
|
-
const rotation = this.computeRotation(position, ctx.dt);
|
|
1082
|
-
this._currentFov = this.lerpFov(ctx.dt);
|
|
1083
|
-
return {
|
|
1084
|
-
position,
|
|
1085
|
-
rotation,
|
|
1086
|
-
fov: this._currentFov,
|
|
1087
|
-
near: 0.1,
|
|
1088
|
-
far: 1e3
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
// --- Private helpers ---
|
|
1092
|
-
computePosition(ctx) {
|
|
1093
|
-
const target = ctx.targets[this.opts.targetKey];
|
|
1094
|
-
if (target) {
|
|
1095
|
-
return new Vector35(
|
|
1096
|
-
target.position.x,
|
|
1097
|
-
target.position.y + this.opts.eyeHeight,
|
|
1098
|
-
target.position.z
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
if (this.initialPosition) {
|
|
1102
|
-
return this.initialPosition.clone();
|
|
1103
|
-
}
|
|
1104
|
-
return new Vector35(0, this.opts.eyeHeight, 0);
|
|
1105
|
-
}
|
|
1106
|
-
computeRotation(eyePosition, dt) {
|
|
1107
|
-
const yawPitchQuat = new Quaternion5().setFromEuler(
|
|
1108
|
-
new Euler(this._pitch, this._yaw, 0, "YXZ")
|
|
1109
|
-
);
|
|
1110
|
-
if (!this._rotationInitialized) {
|
|
1111
|
-
this._currentRotation.copy(yawPitchQuat);
|
|
1112
|
-
this._rotationInitialized = true;
|
|
1113
|
-
}
|
|
1114
|
-
if (this._lookAtTarget) {
|
|
1115
|
-
const dir = _vec3.copy(this._lookAtTarget).sub(eyePosition);
|
|
1116
|
-
if (dir.lengthSq() > 1e-4) {
|
|
1117
|
-
dir.normalize();
|
|
1118
|
-
const desiredYaw = Math.atan2(-dir.x, -dir.z);
|
|
1119
|
-
const desiredPitch = Math.asin(MathUtils2.clamp(dir.y, -1, 1));
|
|
1120
|
-
const desiredQuat = _quat.setFromEuler(
|
|
1121
|
-
_euler.set(desiredPitch, desiredYaw, 0, "YXZ")
|
|
1122
|
-
);
|
|
1123
|
-
const t = 1 - Math.pow(1 - Math.min(this._lookAtLerpSpeed * dt, 1), 1);
|
|
1124
|
-
this._currentRotation.slerp(desiredQuat, MathUtils2.clamp(t, 0, 1));
|
|
1125
|
-
return this._currentRotation.clone();
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
this._currentRotation.copy(yawPitchQuat);
|
|
1129
|
-
return yawPitchQuat;
|
|
1130
|
-
}
|
|
1131
|
-
lerpFov(dt) {
|
|
1132
|
-
if (Math.abs(this._currentFov - this._targetFov) < 0.01) {
|
|
1133
|
-
return this._targetFov;
|
|
1134
|
-
}
|
|
1135
|
-
const t = 1 - Math.pow(1 - Math.min(this.opts.fovLerpSpeed * dt, 1), 1);
|
|
1136
|
-
return MathUtils2.lerp(this._currentFov, this._targetFov, MathUtils2.clamp(t, 0, 1));
|
|
1137
|
-
}
|
|
1138
|
-
deriveYawPitchFromLookAt(from, to) {
|
|
1139
|
-
const dir = _vec3.copy(to).sub(from).normalize();
|
|
1140
|
-
this._yaw = Math.atan2(-dir.x, -dir.z);
|
|
1141
|
-
this._pitch = Math.asin(MathUtils2.clamp(dir.y, -1, 1));
|
|
1142
|
-
}
|
|
1143
|
-
deriveYawPitchFromQuaternion(q) {
|
|
1144
|
-
const euler = _euler.setFromQuaternion(q, "YXZ");
|
|
1145
|
-
this._yaw = euler.y;
|
|
1146
|
-
this._pitch = MathUtils2.clamp(euler.x, -this.opts.pitchLimit, this.opts.pitchLimit);
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
var _vec3 = new Vector35();
|
|
1150
|
-
var _quat = new Quaternion5();
|
|
1151
|
-
var _euler = new Euler();
|
|
1152
|
-
|
|
1153
|
-
// src/lib/camera/perspectives/index.ts
|
|
1154
|
-
function createPerspective(type, options) {
|
|
1155
|
-
switch (type) {
|
|
1156
|
-
case "third-person":
|
|
1157
|
-
return new ThirdPersonPerspective(options);
|
|
1158
|
-
case "first-person":
|
|
1159
|
-
return new FirstPersonPerspective(options);
|
|
1160
|
-
case "isometric":
|
|
1161
|
-
return new ThirdPersonPerspective({
|
|
1162
|
-
distance: 10,
|
|
1163
|
-
height: 10,
|
|
1164
|
-
...options
|
|
1165
|
-
});
|
|
1166
|
-
case "flat-2d":
|
|
1167
|
-
return new Fixed2DPerspective(options);
|
|
1168
|
-
case "fixed-2d":
|
|
1169
|
-
return new Fixed2DPerspective(options);
|
|
1170
|
-
default:
|
|
1171
|
-
return new ThirdPersonPerspective(options);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
// src/lib/camera/zylem-camera.ts
|
|
1176
|
-
var ZylemCamera = class {
|
|
1177
|
-
/**
|
|
1178
|
-
* @deprecated No longer used. Kept as null for backward compatibility
|
|
1179
|
-
* with code that checks `camera.cameraRig` (e.g. scene graph insertion).
|
|
1180
|
-
*/
|
|
1181
|
-
cameraRig = null;
|
|
1182
|
-
camera;
|
|
1183
|
-
screenResolution;
|
|
1184
|
-
_perspective;
|
|
1185
|
-
frustumSize = 10;
|
|
1186
|
-
rendererType;
|
|
1187
|
-
sceneRef = null;
|
|
1188
|
-
/** Name for camera manager lookup. */
|
|
1189
|
-
name = "";
|
|
1190
|
-
/**
|
|
1191
|
-
* Viewport in normalized coordinates (0-1).
|
|
1192
|
-
* Default is fullscreen: { x: 0, y: 0, width: 1, height: 1 }
|
|
1193
|
-
*/
|
|
1194
|
-
viewport = { ...DEFAULT_VIEWPORT };
|
|
1195
|
-
/**
|
|
1196
|
-
* Multiple targets for the camera to follow/frame.
|
|
1197
|
-
*/
|
|
1198
|
-
targets = [];
|
|
1199
|
-
/**
|
|
1200
|
-
* The camera pose pipeline.
|
|
1201
|
-
* Exposed so CameraWrapper can delegate addBehavior/addAction/getState.
|
|
1202
|
-
*/
|
|
1203
|
-
pipeline;
|
|
1204
|
-
/**
|
|
1205
|
-
* Offscreen render target for render-to-texture (RTT) cameras.
|
|
1206
|
-
* When set, the camera renders to this target instead of the screen viewport.
|
|
1207
|
-
* Created via createRenderTarget() or automatically by setCameraFeed().
|
|
1208
|
-
*/
|
|
1209
|
-
renderTarget = null;
|
|
1210
|
-
/**
|
|
1211
|
-
* @deprecated Use `targets` array instead. Kept for backward compatibility.
|
|
1212
|
-
*/
|
|
1213
|
-
get target() {
|
|
1214
|
-
return this.targets.length > 0 ? this.targets[0] : null;
|
|
1215
|
-
}
|
|
1216
|
-
set target(entity) {
|
|
1217
|
-
if (entity) {
|
|
1218
|
-
if (this.targets.length === 0) {
|
|
1219
|
-
this.targets.push(entity);
|
|
1220
|
-
} else {
|
|
1221
|
-
this.targets[0] = entity;
|
|
1222
|
-
}
|
|
1223
|
-
} else {
|
|
1224
|
-
this.targets = [];
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
// Orbit controls
|
|
1228
|
-
orbitController = null;
|
|
1229
|
-
_useOrbitalControls = false;
|
|
1230
|
-
/** When true, debug-mode orbital controls are not attached to this camera. */
|
|
1231
|
-
_skipDebugOrbit = false;
|
|
1232
|
-
/** Reference to the shared renderer manager (set during setup). */
|
|
1233
|
-
_rendererManager = null;
|
|
1234
|
-
/** Elapsed time tracker for CameraContext. */
|
|
1235
|
-
_elapsedTime = 0;
|
|
1236
|
-
constructor(perspective, screenResolution, frustumSize = 10, rendererType = "webgl") {
|
|
1237
|
-
this._perspective = perspective;
|
|
1238
|
-
this.screenResolution = screenResolution;
|
|
1239
|
-
this.frustumSize = frustumSize;
|
|
1240
|
-
this.rendererType = rendererType;
|
|
1241
|
-
const aspectRatio = screenResolution.x / screenResolution.y;
|
|
1242
|
-
this.camera = this.createCameraForPerspective(aspectRatio);
|
|
1243
|
-
this.camera.position.set(0, 0, 10);
|
|
1244
|
-
this.camera.lookAt(new Vector36(0, 0, 0));
|
|
1245
|
-
const perspectiveImpl = createPerspective(perspective);
|
|
1246
|
-
this.pipeline = new CameraPipeline(perspectiveImpl);
|
|
1247
|
-
}
|
|
1248
|
-
/**
|
|
1249
|
-
* Setup the camera with a scene and renderer manager.
|
|
1250
|
-
*/
|
|
1251
|
-
async setup(scene, rendererManager) {
|
|
1252
|
-
this.sceneRef = scene;
|
|
1253
|
-
if (rendererManager) {
|
|
1254
|
-
this._rendererManager = rendererManager;
|
|
1255
|
-
}
|
|
1256
|
-
if (this._rendererManager && !this._rendererManager.initialized) {
|
|
1257
|
-
await this._rendererManager.initRenderer();
|
|
1258
|
-
}
|
|
1259
|
-
if (this._rendererManager) {
|
|
1260
|
-
this.orbitController = new CameraOrbitController(
|
|
1261
|
-
this.camera,
|
|
1262
|
-
this._rendererManager.renderer.domElement,
|
|
1263
|
-
null
|
|
1264
|
-
// no camera rig
|
|
1265
|
-
);
|
|
1266
|
-
this.orbitController.setScene(scene);
|
|
1267
|
-
if (this._useOrbitalControls) {
|
|
1268
|
-
this.orbitController.enableUserOrbitControls();
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Legacy setup method for backward compatibility.
|
|
1274
|
-
* Creates a temporary RendererManager internally.
|
|
1275
|
-
* @deprecated Use setup(scene, rendererManager) instead.
|
|
1276
|
-
*/
|
|
1277
|
-
async setupLegacy(scene) {
|
|
1278
|
-
if (!this._rendererManager) {
|
|
1279
|
-
this._rendererManager = new RendererManager(this.screenResolution, this.rendererType);
|
|
1280
|
-
await this._rendererManager.initRenderer();
|
|
1281
|
-
this._rendererManager.setupRenderPass(scene, this.camera);
|
|
1282
|
-
this._rendererManager.startRenderLoop((delta) => {
|
|
1283
|
-
this.update(delta);
|
|
1284
|
-
if (this._rendererManager && this.sceneRef) {
|
|
1285
|
-
this._rendererManager.render(this.sceneRef, this.camera);
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
await this.setup(scene, this._rendererManager);
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Update the camera each frame.
|
|
1293
|
-
*
|
|
1294
|
-
* When orbit/debug controls are active, the pipeline is skipped and
|
|
1295
|
-
* orbit controls manage the camera directly. Otherwise, the pipeline
|
|
1296
|
-
* runs: Perspective -> Behaviors -> Actions -> Smoothing -> Commit.
|
|
1297
|
-
*/
|
|
1298
|
-
update(delta) {
|
|
1299
|
-
this.orbitController?.update();
|
|
1300
|
-
if (this.isDebugModeActive() || this._useOrbitalControls) {
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
this._elapsedTime += delta;
|
|
1304
|
-
const ctx = this.buildContext(delta);
|
|
1305
|
-
const finalPose = this.pipeline.run(ctx);
|
|
1306
|
-
this.commitPose(finalPose);
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Check if debug mode is active (orbit controls taking over camera).
|
|
1310
|
-
*/
|
|
1311
|
-
isDebugModeActive() {
|
|
1312
|
-
return this.orbitController?.isActive ?? false;
|
|
1313
|
-
}
|
|
1314
|
-
/**
|
|
1315
|
-
* Enable user-configured orbital controls (not debug mode).
|
|
1316
|
-
*/
|
|
1317
|
-
enableOrbitalControls() {
|
|
1318
|
-
this._useOrbitalControls = true;
|
|
1319
|
-
this.orbitController?.enableUserOrbitControls();
|
|
1320
|
-
}
|
|
1321
|
-
/**
|
|
1322
|
-
* Disable user-configured orbital controls.
|
|
1323
|
-
*/
|
|
1324
|
-
disableOrbitalControls() {
|
|
1325
|
-
this._useOrbitalControls = false;
|
|
1326
|
-
this.orbitController?.disableUserOrbitControls();
|
|
1327
|
-
}
|
|
1328
|
-
/**
|
|
1329
|
-
* Whether user orbital controls are enabled.
|
|
1330
|
-
*/
|
|
1331
|
-
get useOrbitalControls() {
|
|
1332
|
-
return this._useOrbitalControls;
|
|
1333
|
-
}
|
|
1334
|
-
// ─── Target management ──────────────────────────────────────────────────
|
|
1335
|
-
/**
|
|
1336
|
-
* Add a target entity for the camera to follow/frame.
|
|
1337
|
-
*/
|
|
1338
|
-
addTarget(entity) {
|
|
1339
|
-
if (!this.targets.includes(entity)) {
|
|
1340
|
-
this.targets.push(entity);
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Remove a target entity.
|
|
1345
|
-
*/
|
|
1346
|
-
removeTarget(entity) {
|
|
1347
|
-
const index = this.targets.indexOf(entity);
|
|
1348
|
-
if (index !== -1) {
|
|
1349
|
-
this.targets.splice(index, 1);
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Clear all targets.
|
|
1354
|
-
*/
|
|
1355
|
-
clearTargets() {
|
|
1356
|
-
this.targets = [];
|
|
1357
|
-
}
|
|
1358
|
-
// ─── Viewport & resize ──────────────────────────────────────────────────
|
|
1359
|
-
/**
|
|
1360
|
-
* Set the viewport for this camera (normalized 0-1 coordinates).
|
|
1361
|
-
*/
|
|
1362
|
-
setViewport(x, y, width, height) {
|
|
1363
|
-
this.viewport = { x, y, width, height };
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
|
-
* Resize camera projection.
|
|
1367
|
-
*/
|
|
1368
|
-
resize(width, height) {
|
|
1369
|
-
this.screenResolution.set(width, height);
|
|
1370
|
-
if (this.camera instanceof PerspectiveCamera2) {
|
|
1371
|
-
this.camera.aspect = width / height;
|
|
1372
|
-
this.camera.updateProjectionMatrix();
|
|
1373
|
-
}
|
|
1374
|
-
if (this.camera instanceof OrthographicCamera) {
|
|
1375
|
-
const aspect = width / height;
|
|
1376
|
-
this.camera.left = this.frustumSize * aspect / -2;
|
|
1377
|
-
this.camera.right = this.frustumSize * aspect / 2;
|
|
1378
|
-
this.camera.top = this.frustumSize / 2;
|
|
1379
|
-
this.camera.bottom = this.frustumSize / -2;
|
|
1380
|
-
this.camera.updateProjectionMatrix();
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
// ─── Render-to-texture ─────────────────────────────────────────────────
|
|
1384
|
-
/**
|
|
1385
|
-
* Create an offscreen render target for this camera.
|
|
1386
|
-
* When a render target is present, CameraManager renders this camera
|
|
1387
|
-
* to the target instead of the screen viewport.
|
|
1388
|
-
*
|
|
1389
|
-
* @param width Texture width in pixels (default 512)
|
|
1390
|
-
* @param height Texture height in pixels (default 512)
|
|
1391
|
-
*/
|
|
1392
|
-
createRenderTarget(width = 512, height = 512) {
|
|
1393
|
-
if (this.renderTarget) {
|
|
1394
|
-
this.renderTarget.dispose();
|
|
1395
|
-
}
|
|
1396
|
-
this.renderTarget = new WebGLRenderTarget2(width, height, {
|
|
1397
|
-
minFilter: LinearFilter,
|
|
1398
|
-
magFilter: LinearFilter
|
|
1399
|
-
});
|
|
1400
|
-
return this.renderTarget;
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Get the texture from the render target (for applying to a mesh material).
|
|
1404
|
-
* Returns null if no render target has been created.
|
|
1405
|
-
*/
|
|
1406
|
-
getRenderTexture() {
|
|
1407
|
-
return this.renderTarget?.texture ?? null;
|
|
1408
|
-
}
|
|
1409
|
-
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
1410
|
-
/**
|
|
1411
|
-
* Dispose camera resources.
|
|
1412
|
-
*/
|
|
1413
|
-
destroy() {
|
|
1414
|
-
try {
|
|
1415
|
-
this.orbitController?.dispose();
|
|
1416
|
-
} catch {
|
|
1417
|
-
}
|
|
1418
|
-
try {
|
|
1419
|
-
this.renderTarget?.dispose();
|
|
1420
|
-
} catch {
|
|
1421
|
-
}
|
|
1422
|
-
this.renderTarget = null;
|
|
1423
|
-
this.sceneRef = null;
|
|
1424
|
-
this.targets = [];
|
|
1425
|
-
this._rendererManager = null;
|
|
1426
|
-
}
|
|
1427
|
-
// ─── Debug delegate ─────────────────────────────────────────────────────
|
|
1428
|
-
/**
|
|
1429
|
-
* Attach a delegate to react to debug state changes.
|
|
1430
|
-
* Skipped when _skipDebugOrbit is true so the pipeline always runs.
|
|
1431
|
-
*/
|
|
1432
|
-
setDebugDelegate(delegate) {
|
|
1433
|
-
if (this._skipDebugOrbit) return;
|
|
1434
|
-
this.orbitController?.setDebugDelegate(delegate);
|
|
1435
|
-
}
|
|
1436
|
-
// ─── Movement helpers (backward compat) ─────────────────────────────────
|
|
1437
|
-
/**
|
|
1438
|
-
* Directly set the camera position.
|
|
1439
|
-
*/
|
|
1440
|
-
move(position) {
|
|
1441
|
-
if (this._perspective === Perspectives.Flat2D || this._perspective === Perspectives.Fixed2D) {
|
|
1442
|
-
this.frustumSize = position.z;
|
|
1443
|
-
}
|
|
1444
|
-
this.camera.position.set(position.x, position.y, position.z);
|
|
1445
|
-
}
|
|
1446
|
-
/**
|
|
1447
|
-
* Apply incremental rotation to the camera.
|
|
1448
|
-
*/
|
|
1449
|
-
rotate(pitch, yaw, roll) {
|
|
1450
|
-
this.camera.rotateX(pitch);
|
|
1451
|
-
this.camera.rotateY(yaw);
|
|
1452
|
-
this.camera.rotateZ(roll);
|
|
1453
|
-
}
|
|
1454
|
-
// ─── Renderer manager access ────────────────────────────────────────────
|
|
1455
|
-
/**
|
|
1456
|
-
* Get the DOM element for the renderer.
|
|
1457
|
-
* @deprecated Access via RendererManager instead.
|
|
1458
|
-
*/
|
|
1459
|
-
getDomElement() {
|
|
1460
|
-
if (this._rendererManager) {
|
|
1461
|
-
return this._rendererManager.getDomElement();
|
|
1462
|
-
}
|
|
1463
|
-
throw new Error("ZylemCamera: No renderer manager available. Call setup() first.");
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Get the renderer manager reference.
|
|
1467
|
-
*/
|
|
1468
|
-
getRendererManager() {
|
|
1469
|
-
return this._rendererManager;
|
|
1470
|
-
}
|
|
1471
|
-
/**
|
|
1472
|
-
* Set the renderer manager reference (used by CameraManager during setup).
|
|
1473
|
-
*/
|
|
1474
|
-
setRendererManager(manager) {
|
|
1475
|
-
this._rendererManager = manager;
|
|
1476
|
-
}
|
|
1477
|
-
// ─── Legacy compatibility methods ────────────────────────────────────────
|
|
1478
|
-
/** @deprecated Renderer is now owned by RendererManager */
|
|
1479
|
-
get renderer() {
|
|
1480
|
-
return this._rendererManager?.renderer;
|
|
1481
|
-
}
|
|
1482
|
-
/** @deprecated Composer is now owned by RendererManager */
|
|
1483
|
-
get composer() {
|
|
1484
|
-
return this._rendererManager?.composer;
|
|
1485
|
-
}
|
|
1486
|
-
/** @deprecated Use RendererManager.setPixelRatio() instead */
|
|
1487
|
-
setPixelRatio(dpr) {
|
|
1488
|
-
this._rendererManager?.setPixelRatio(dpr);
|
|
1489
|
-
}
|
|
1490
|
-
// ─── Private helpers ────────────────────────────────────────────────────
|
|
1491
|
-
/**
|
|
1492
|
-
* Build a CameraContext from current ZylemCamera state.
|
|
1493
|
-
* Converts StageEntity[] targets into Record<string, TransformLike>.
|
|
1494
|
-
*/
|
|
1495
|
-
buildContext(delta) {
|
|
1496
|
-
const targets = {};
|
|
1497
|
-
for (let i = 0; i < this.targets.length; i++) {
|
|
1498
|
-
const entity = this.targets[i];
|
|
1499
|
-
const key = i === 0 ? "primary" : `target_${i}`;
|
|
1500
|
-
if (entity.group) {
|
|
1501
|
-
targets[key] = {
|
|
1502
|
-
position: entity.group.position,
|
|
1503
|
-
rotation: entity.group.quaternion
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
return {
|
|
1508
|
-
dt: delta,
|
|
1509
|
-
time: this._elapsedTime,
|
|
1510
|
-
viewport: {
|
|
1511
|
-
width: this.screenResolution.x,
|
|
1512
|
-
height: this.screenResolution.y,
|
|
1513
|
-
aspect: this.screenResolution.x / this.screenResolution.y
|
|
1514
|
-
},
|
|
1515
|
-
targets
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* Apply the final pipeline pose to the Three.js camera.
|
|
1520
|
-
*/
|
|
1521
|
-
commitPose(pose) {
|
|
1522
|
-
this.camera.position.copy(pose.position);
|
|
1523
|
-
if (pose.lookAt) {
|
|
1524
|
-
this.camera.lookAt(pose.lookAt);
|
|
1525
|
-
} else {
|
|
1526
|
-
this.camera.quaternion.copy(pose.rotation);
|
|
1527
|
-
}
|
|
1528
|
-
if (this.camera instanceof PerspectiveCamera2) {
|
|
1529
|
-
if (pose.fov != null) this.camera.fov = pose.fov;
|
|
1530
|
-
if (pose.near != null) this.camera.near = pose.near;
|
|
1531
|
-
if (pose.far != null) this.camera.far = pose.far;
|
|
1532
|
-
this.camera.updateProjectionMatrix();
|
|
1533
|
-
}
|
|
1534
|
-
if (this.camera instanceof OrthographicCamera) {
|
|
1535
|
-
if (pose.zoom != null) {
|
|
1536
|
-
const aspect = this.screenResolution.x / this.screenResolution.y;
|
|
1537
|
-
const size = pose.zoom;
|
|
1538
|
-
this.camera.left = -size * aspect / 2;
|
|
1539
|
-
this.camera.right = size * aspect / 2;
|
|
1540
|
-
this.camera.top = size / 2;
|
|
1541
|
-
this.camera.bottom = -size / 2;
|
|
1542
|
-
}
|
|
1543
|
-
if (pose.near != null) this.camera.near = pose.near;
|
|
1544
|
-
if (pose.far != null) this.camera.far = pose.far;
|
|
1545
|
-
this.camera.updateProjectionMatrix();
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
/**
|
|
1549
|
-
* Create a Three.js camera based on perspective type.
|
|
1550
|
-
*/
|
|
1551
|
-
createCameraForPerspective(aspectRatio) {
|
|
1552
|
-
switch (this._perspective) {
|
|
1553
|
-
case Perspectives.ThirdPerson:
|
|
1554
|
-
return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
|
|
1555
|
-
case Perspectives.FirstPerson:
|
|
1556
|
-
return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
|
|
1557
|
-
case Perspectives.Isometric:
|
|
1558
|
-
return new OrthographicCamera(
|
|
1559
|
-
this.frustumSize * aspectRatio / -2,
|
|
1560
|
-
this.frustumSize * aspectRatio / 2,
|
|
1561
|
-
this.frustumSize / 2,
|
|
1562
|
-
this.frustumSize / -2,
|
|
1563
|
-
1,
|
|
1564
|
-
1e3
|
|
1565
|
-
);
|
|
1566
|
-
case Perspectives.Flat2D:
|
|
1567
|
-
case Perspectives.Fixed2D:
|
|
1568
|
-
return new OrthographicCamera(
|
|
1569
|
-
this.frustumSize * aspectRatio / -2,
|
|
1570
|
-
this.frustumSize * aspectRatio / 2,
|
|
1571
|
-
this.frustumSize / 2,
|
|
1572
|
-
this.frustumSize / -2,
|
|
1573
|
-
1,
|
|
1574
|
-
1e3
|
|
1575
|
-
);
|
|
1576
|
-
default:
|
|
1577
|
-
return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
};
|
|
1581
|
-
|
|
1582
|
-
// src/lib/camera/camera.ts
|
|
1583
|
-
var CameraWrapper = class {
|
|
1584
|
-
cameraRef;
|
|
1585
|
-
constructor(camera) {
|
|
1586
|
-
this.cameraRef = camera;
|
|
1587
|
-
}
|
|
1588
|
-
// ─── Target management ──────────────────────────────────────────────────
|
|
1589
|
-
/**
|
|
1590
|
-
* Add a target entity for the camera to follow/frame.
|
|
1591
|
-
* With multiple targets, the camera auto-frames to include all of them.
|
|
1592
|
-
*/
|
|
1593
|
-
addTarget(entity) {
|
|
1594
|
-
this.cameraRef.addTarget(entity);
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* Remove a target entity from the camera.
|
|
1598
|
-
*/
|
|
1599
|
-
removeTarget(entity) {
|
|
1600
|
-
this.cameraRef.removeTarget(entity);
|
|
1601
|
-
}
|
|
1602
|
-
/**
|
|
1603
|
-
* Clear all targets. Camera will look at world origin.
|
|
1604
|
-
*/
|
|
1605
|
-
clearTargets() {
|
|
1606
|
-
this.cameraRef.clearTargets();
|
|
1607
|
-
}
|
|
1608
|
-
// ─── Orbital controls ───────────────────────────────────────────────────
|
|
1609
|
-
/**
|
|
1610
|
-
* Enable orbital controls for this camera.
|
|
1611
|
-
* Allows the user to orbit, pan, and zoom the camera.
|
|
1612
|
-
*/
|
|
1613
|
-
enableOrbitalControls() {
|
|
1614
|
-
this.cameraRef.enableOrbitalControls();
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Disable orbital controls for this camera.
|
|
1618
|
-
*/
|
|
1619
|
-
disableOrbitalControls() {
|
|
1620
|
-
this.cameraRef.disableOrbitalControls();
|
|
1621
|
-
}
|
|
1622
|
-
// ─── Viewport ───────────────────────────────────────────────────────────
|
|
1623
|
-
/**
|
|
1624
|
-
* Set the viewport for this camera (normalized 0-1 coordinates).
|
|
1625
|
-
* @param x Left edge (0 = left of canvas)
|
|
1626
|
-
* @param y Bottom edge (0 = bottom of canvas)
|
|
1627
|
-
* @param width Width as fraction of canvas
|
|
1628
|
-
* @param height Height as fraction of canvas
|
|
1629
|
-
*/
|
|
1630
|
-
setViewport(x, y, width, height) {
|
|
1631
|
-
this.cameraRef.setViewport(x, y, width, height);
|
|
1632
|
-
}
|
|
1633
|
-
// ─── Pipeline: Behaviors ────────────────────────────────────────────────
|
|
1634
|
-
/**
|
|
1635
|
-
* Add or replace a behavior by key (idempotent).
|
|
1636
|
-
* Behaviors modify the desired camera pose each frame.
|
|
1637
|
-
*
|
|
1638
|
-
* @param key Unique key for this behavior (used for replacement/removal).
|
|
1639
|
-
* @param behavior The CameraBehavior implementation.
|
|
1640
|
-
*/
|
|
1641
|
-
addBehavior(key, behavior) {
|
|
1642
|
-
this.cameraRef.pipeline.addBehavior(key, behavior);
|
|
1643
|
-
}
|
|
1644
|
-
/**
|
|
1645
|
-
* Remove a behavior by key.
|
|
1646
|
-
*/
|
|
1647
|
-
removeBehavior(key) {
|
|
1648
|
-
return this.cameraRef.pipeline.removeBehavior(key);
|
|
1649
|
-
}
|
|
1650
|
-
// ─── Pipeline: Actions ──────────────────────────────────────────────────
|
|
1651
|
-
/**
|
|
1652
|
-
* Add a transient action (screenshake, recoil, etc.).
|
|
1653
|
-
* Actions apply additive deltas and self-expire when isDone() returns true.
|
|
1654
|
-
*/
|
|
1655
|
-
addAction(action) {
|
|
1656
|
-
this.cameraRef.pipeline.addAction(action);
|
|
1657
|
-
}
|
|
1658
|
-
// ─── Pipeline: Perspective ──────────────────────────────────────────────
|
|
1659
|
-
/**
|
|
1660
|
-
* Switch the camera's active perspective at runtime.
|
|
1661
|
-
* The first frame after switching snaps to the new pose (no lerp).
|
|
1662
|
-
*
|
|
1663
|
-
* @param type Perspective type string (e.g. Perspectives.ThirdPerson).
|
|
1664
|
-
* @param options Perspective-specific options (distance, height, zoom, etc.).
|
|
1665
|
-
*/
|
|
1666
|
-
setPerspective(type, options) {
|
|
1667
|
-
this.cameraRef.pipeline.setPerspective(createPerspective(type, options));
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* Retrieve the active perspective instance, cast to the desired type.
|
|
1671
|
-
* Useful for calling perspective-specific methods (e.g. FirstPersonPerspective.look()).
|
|
1672
|
-
*
|
|
1673
|
-
* @example
|
|
1674
|
-
* const fps = camera.getPerspective<FirstPersonPerspective>();
|
|
1675
|
-
* fps.look(dx, dy);
|
|
1676
|
-
*/
|
|
1677
|
-
getPerspective() {
|
|
1678
|
-
return this.cameraRef.pipeline.perspective;
|
|
1679
|
-
}
|
|
1680
|
-
// ─── Pipeline: Debug state ──────────────────────────────────────────────
|
|
1681
|
-
/**
|
|
1682
|
-
* Return a debug snapshot of the camera pipeline state.
|
|
1683
|
-
* Includes: active perspective, desired/final pose, behavior keys, action count.
|
|
1684
|
-
*/
|
|
1685
|
-
getState() {
|
|
1686
|
-
return this.cameraRef.pipeline.getState();
|
|
1687
|
-
}
|
|
1688
|
-
// ─── Render-to-texture ─────────────────────────────────────────────────
|
|
1689
|
-
/**
|
|
1690
|
-
* Get the offscreen render texture for this camera.
|
|
1691
|
-
* Returns null if the camera was not created with renderToTexture.
|
|
1692
|
-
* Use with setCameraFeed() or apply directly to a mesh material.
|
|
1693
|
-
*/
|
|
1694
|
-
getRenderTexture() {
|
|
1695
|
-
return this.cameraRef.getRenderTexture();
|
|
1696
|
-
}
|
|
1697
|
-
};
|
|
1698
|
-
function createCamera(options) {
|
|
1699
|
-
const screenResolution = options.screenResolution || new Vector23(window.innerWidth, window.innerHeight);
|
|
1700
|
-
let frustumSize = 10;
|
|
1701
|
-
if (options.perspective === "fixed-2d") {
|
|
1702
|
-
frustumSize = options.zoom || 10;
|
|
1703
|
-
}
|
|
1704
|
-
const zylemCamera = new ZylemCamera(
|
|
1705
|
-
options.perspective || "third-person",
|
|
1706
|
-
screenResolution,
|
|
1707
|
-
frustumSize,
|
|
1708
|
-
options.rendererType || "webgl"
|
|
1709
|
-
);
|
|
1710
|
-
if (options.name) {
|
|
1711
|
-
zylemCamera.name = options.name;
|
|
1712
|
-
}
|
|
1713
|
-
const position = options.position ? options.position instanceof Vector37 ? options.position : new Vector37(options.position.x, options.position.y, options.position.z) : new Vector37(0, 0, 0);
|
|
1714
|
-
const target = options.target ? options.target instanceof Vector37 ? options.target : new Vector37(options.target.x, options.target.y, options.target.z) : new Vector37(0, 0, 0);
|
|
1715
|
-
zylemCamera.move(position);
|
|
1716
|
-
zylemCamera.camera.lookAt(target);
|
|
1717
|
-
const perspType = options.perspective || "third-person";
|
|
1718
|
-
if (perspType === "fixed-2d" || perspType === "flat-2d") {
|
|
1719
|
-
zylemCamera.pipeline.setPerspective(
|
|
1720
|
-
createPerspective(perspType, { zoom: frustumSize })
|
|
1721
|
-
);
|
|
1722
|
-
} else {
|
|
1723
|
-
zylemCamera.pipeline.setPerspective(
|
|
1724
|
-
createPerspective(perspType, {
|
|
1725
|
-
initialPosition: position.clone(),
|
|
1726
|
-
initialLookAt: target.clone()
|
|
1727
|
-
})
|
|
1728
|
-
);
|
|
1729
|
-
}
|
|
1730
|
-
if (options.viewport) {
|
|
1731
|
-
zylemCamera.viewport = { ...options.viewport };
|
|
1732
|
-
}
|
|
1733
|
-
if (options.useOrbitalControls) {
|
|
1734
|
-
zylemCamera._useOrbitalControls = true;
|
|
1735
|
-
}
|
|
1736
|
-
if (options.skipDebugOrbit) {
|
|
1737
|
-
zylemCamera._skipDebugOrbit = true;
|
|
1738
|
-
}
|
|
1739
|
-
if (options.damping != null) {
|
|
1740
|
-
zylemCamera.pipeline.damping = options.damping;
|
|
1741
|
-
}
|
|
1742
|
-
if (options.behaviors) {
|
|
1743
|
-
for (const [key, behavior] of Object.entries(options.behaviors)) {
|
|
1744
|
-
zylemCamera.pipeline.addBehavior(key, behavior);
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
if (options.renderToTexture) {
|
|
1748
|
-
const { width = 512, height = 512 } = options.renderToTexture;
|
|
1749
|
-
zylemCamera.createRenderTarget(width, height);
|
|
1750
|
-
}
|
|
1751
|
-
return new CameraWrapper(zylemCamera);
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
// src/lib/camera/camera-manager.ts
|
|
1755
|
-
import { Vector2 as Vector24 } from "three";
|
|
1756
|
-
var CameraManager = class {
|
|
1757
|
-
/** Named camera registry */
|
|
1758
|
-
cameras = /* @__PURE__ */ new Map();
|
|
1759
|
-
/** Currently active cameras, ordered by render layer (first = bottom) */
|
|
1760
|
-
_activeCameras = [];
|
|
1761
|
-
/** Auto-created debug camera with orbit controls */
|
|
1762
|
-
_debugCamera = null;
|
|
1763
|
-
/** Reference to the shared renderer manager */
|
|
1764
|
-
_rendererManager = null;
|
|
1765
|
-
/** Scene reference */
|
|
1766
|
-
_sceneRef = null;
|
|
1767
|
-
/** Counter for auto-generated camera names */
|
|
1768
|
-
_autoNameCounter = 0;
|
|
1769
|
-
constructor() {
|
|
1770
|
-
}
|
|
1771
|
-
/**
|
|
1772
|
-
* Get the list of currently active cameras.
|
|
1773
|
-
*/
|
|
1774
|
-
get activeCameras() {
|
|
1775
|
-
return this._activeCameras;
|
|
1776
|
-
}
|
|
1777
|
-
/**
|
|
1778
|
-
* Get the primary active camera (first in the active list).
|
|
1779
|
-
*/
|
|
1780
|
-
get primaryCamera() {
|
|
1781
|
-
return this._activeCameras.length > 0 ? this._activeCameras[0] : null;
|
|
1782
|
-
}
|
|
1783
|
-
/**
|
|
1784
|
-
* Get the debug camera.
|
|
1785
|
-
*/
|
|
1786
|
-
get debugCamera() {
|
|
1787
|
-
return this._debugCamera;
|
|
1788
|
-
}
|
|
1789
|
-
/**
|
|
1790
|
-
* Get all registered cameras.
|
|
1791
|
-
*/
|
|
1792
|
-
get allCameras() {
|
|
1793
|
-
return Array.from(this.cameras.values());
|
|
1794
|
-
}
|
|
1795
|
-
/**
|
|
1796
|
-
* Add a camera to the manager.
|
|
1797
|
-
* If no name is provided, one is auto-generated.
|
|
1798
|
-
* The first camera added becomes the active camera.
|
|
1799
|
-
*
|
|
1800
|
-
* @param camera The ZylemCamera instance to add
|
|
1801
|
-
* @param name Optional name for lookup
|
|
1802
|
-
* @returns The assigned name
|
|
1803
|
-
*/
|
|
1804
|
-
addCamera(camera, name) {
|
|
1805
|
-
const resolvedName = name || camera.name || `camera_${this._autoNameCounter++}`;
|
|
1806
|
-
camera.name = resolvedName;
|
|
1807
|
-
this.cameras.set(resolvedName, camera);
|
|
1808
|
-
if (this._activeCameras.length === 0) {
|
|
1809
|
-
this._activeCameras.push(camera);
|
|
1810
|
-
}
|
|
1811
|
-
return resolvedName;
|
|
1812
|
-
}
|
|
1813
|
-
/**
|
|
1814
|
-
* Remove a camera by name or reference.
|
|
1815
|
-
* Cannot remove the debug camera via this method.
|
|
1816
|
-
*/
|
|
1817
|
-
removeCamera(nameOrRef) {
|
|
1818
|
-
let name;
|
|
1819
|
-
if (typeof nameOrRef === "string") {
|
|
1820
|
-
name = nameOrRef;
|
|
1821
|
-
} else {
|
|
1822
|
-
for (const [key, cam] of this.cameras.entries()) {
|
|
1823
|
-
if (cam === nameOrRef) {
|
|
1824
|
-
name = key;
|
|
1825
|
-
break;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
if (!name) return false;
|
|
1830
|
-
const camera = this.cameras.get(name);
|
|
1831
|
-
if (!camera) return false;
|
|
1832
|
-
if (camera === this._debugCamera) {
|
|
1833
|
-
console.warn("CameraManager: Cannot remove the debug camera");
|
|
1834
|
-
return false;
|
|
1835
|
-
}
|
|
1836
|
-
this.cameras.delete(name);
|
|
1837
|
-
const activeIndex = this._activeCameras.indexOf(camera);
|
|
1838
|
-
if (activeIndex !== -1) {
|
|
1839
|
-
this._activeCameras.splice(activeIndex, 1);
|
|
1840
|
-
}
|
|
1841
|
-
return true;
|
|
1842
|
-
}
|
|
1843
|
-
/**
|
|
1844
|
-
* Set a camera as the primary active camera (replaces all active cameras
|
|
1845
|
-
* except additional viewport cameras).
|
|
1846
|
-
*
|
|
1847
|
-
* @param nameOrRef Camera name or reference to activate
|
|
1848
|
-
*/
|
|
1849
|
-
setActiveCamera(nameOrRef) {
|
|
1850
|
-
const camera = this.resolveCamera(nameOrRef);
|
|
1851
|
-
if (!camera) {
|
|
1852
|
-
console.warn(`CameraManager: Camera not found: ${nameOrRef}`);
|
|
1853
|
-
return false;
|
|
1854
|
-
}
|
|
1855
|
-
const pipCameras = this._activeCameras.filter((c) => {
|
|
1856
|
-
return c !== this._activeCameras[0] && c.viewport.width < 1;
|
|
1857
|
-
});
|
|
1858
|
-
this._activeCameras = [camera, ...pipCameras];
|
|
1859
|
-
return true;
|
|
1860
|
-
}
|
|
1861
|
-
/**
|
|
1862
|
-
* Add a camera as an additional active camera (for split-screen or PiP).
|
|
1863
|
-
*/
|
|
1864
|
-
addActiveCamera(nameOrRef) {
|
|
1865
|
-
const camera = this.resolveCamera(nameOrRef);
|
|
1866
|
-
if (!camera) return false;
|
|
1867
|
-
if (!this._activeCameras.includes(camera)) {
|
|
1868
|
-
this._activeCameras.push(camera);
|
|
1869
|
-
}
|
|
1870
|
-
return true;
|
|
1871
|
-
}
|
|
1872
|
-
/**
|
|
1873
|
-
* Remove a camera from the active render list (does not remove from registry).
|
|
1874
|
-
*/
|
|
1875
|
-
deactivateCamera(nameOrRef) {
|
|
1876
|
-
const camera = this.resolveCamera(nameOrRef);
|
|
1877
|
-
if (!camera) return false;
|
|
1878
|
-
const index = this._activeCameras.indexOf(camera);
|
|
1879
|
-
if (index !== -1) {
|
|
1880
|
-
this._activeCameras.splice(index, 1);
|
|
1881
|
-
return true;
|
|
1882
|
-
}
|
|
1883
|
-
return false;
|
|
1884
|
-
}
|
|
1885
|
-
/**
|
|
1886
|
-
* Get a camera by name.
|
|
1887
|
-
*/
|
|
1888
|
-
getCamera(name) {
|
|
1889
|
-
return this.cameras.get(name) ?? null;
|
|
1890
|
-
}
|
|
1891
|
-
/**
|
|
1892
|
-
* Setup all cameras with the given scene and renderer manager.
|
|
1893
|
-
* Also creates the debug camera.
|
|
1894
|
-
*/
|
|
1895
|
-
async setup(scene, rendererManager) {
|
|
1896
|
-
this._sceneRef = scene;
|
|
1897
|
-
this._rendererManager = rendererManager;
|
|
1898
|
-
this.createDebugCamera(rendererManager.screenResolution);
|
|
1899
|
-
for (const camera of this.cameras.values()) {
|
|
1900
|
-
camera.setRendererManager(rendererManager);
|
|
1901
|
-
await camera.setup(scene, rendererManager);
|
|
1902
|
-
}
|
|
1903
|
-
if (this._debugCamera) {
|
|
1904
|
-
this._debugCamera.setRendererManager(rendererManager);
|
|
1905
|
-
await this._debugCamera.setup(scene, rendererManager);
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
/**
|
|
1909
|
-
* Update all active cameras' controllers.
|
|
1910
|
-
*/
|
|
1911
|
-
update(delta) {
|
|
1912
|
-
for (const camera of this._activeCameras) {
|
|
1913
|
-
camera.update(delta);
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
/**
|
|
1917
|
-
* Render all active cameras through the renderer manager.
|
|
1918
|
-
* RTT cameras (those with a renderTarget) are rendered first to their
|
|
1919
|
-
* offscreen textures, then viewport cameras are rendered to the screen.
|
|
1920
|
-
*/
|
|
1921
|
-
render(scene) {
|
|
1922
|
-
if (!this._rendererManager || this._activeCameras.length === 0) return;
|
|
1923
|
-
const rttCameras = [];
|
|
1924
|
-
const viewportCameras = [];
|
|
1925
|
-
for (const cam of this._activeCameras) {
|
|
1926
|
-
if (cam.renderTarget) {
|
|
1927
|
-
rttCameras.push(cam);
|
|
1928
|
-
} else {
|
|
1929
|
-
viewportCameras.push(cam);
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
for (const cam of rttCameras) {
|
|
1933
|
-
this._rendererManager.renderCameraToTarget(scene, cam);
|
|
1934
|
-
}
|
|
1935
|
-
if (viewportCameras.length > 0) {
|
|
1936
|
-
this._rendererManager.renderCameras(scene, viewportCameras);
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Create a default third-person camera if no cameras have been added.
|
|
1941
|
-
*/
|
|
1942
|
-
ensureDefaultCamera() {
|
|
1943
|
-
if (this.cameras.size === 0 || this._activeCameras.length === 0) {
|
|
1944
|
-
const screenRes = this._rendererManager?.screenResolution || new Vector24(window.innerWidth, window.innerHeight);
|
|
1945
|
-
const defaultCam = new ZylemCamera(Perspectives.ThirdPerson, screenRes);
|
|
1946
|
-
this.addCamera(defaultCam, "default");
|
|
1947
|
-
return defaultCam;
|
|
1948
|
-
}
|
|
1949
|
-
return this._activeCameras[0];
|
|
1950
|
-
}
|
|
1951
|
-
/**
|
|
1952
|
-
* Dispose all cameras and cleanup.
|
|
1953
|
-
*/
|
|
1954
|
-
dispose() {
|
|
1955
|
-
for (const camera of this.cameras.values()) {
|
|
1956
|
-
camera.destroy();
|
|
1957
|
-
}
|
|
1958
|
-
this._debugCamera?.destroy();
|
|
1959
|
-
this.cameras.clear();
|
|
1960
|
-
this._activeCameras = [];
|
|
1961
|
-
this._debugCamera = null;
|
|
1962
|
-
this._rendererManager = null;
|
|
1963
|
-
this._sceneRef = null;
|
|
1964
|
-
}
|
|
1965
|
-
/**
|
|
1966
|
-
* Create the always-available debug camera with orbit controls.
|
|
1967
|
-
*/
|
|
1968
|
-
createDebugCamera(screenResolution) {
|
|
1969
|
-
this._debugCamera = new ZylemCamera(Perspectives.ThirdPerson, screenResolution);
|
|
1970
|
-
this._debugCamera.name = "__debug__";
|
|
1971
|
-
this._debugCamera.enableOrbitalControls();
|
|
1972
|
-
}
|
|
1973
|
-
/**
|
|
1974
|
-
* Resolve a camera from a name or reference.
|
|
1975
|
-
*/
|
|
1976
|
-
resolveCamera(nameOrRef) {
|
|
1977
|
-
if (typeof nameOrRef === "string") {
|
|
1978
|
-
return this.cameras.get(nameOrRef) ?? null;
|
|
1979
|
-
}
|
|
1980
|
-
for (const cam of this.cameras.values()) {
|
|
1981
|
-
if (cam === nameOrRef) return cam;
|
|
1982
|
-
}
|
|
1983
|
-
return null;
|
|
1984
|
-
}
|
|
1985
|
-
};
|
|
1986
|
-
export {
|
|
1987
|
-
CameraManager,
|
|
1988
|
-
CameraWrapper,
|
|
1989
|
-
Perspectives,
|
|
1990
|
-
createCamera,
|
|
1991
|
-
isWebGPUSupported
|
|
1992
|
-
};
|
|
1993
|
-
//# sourceMappingURL=camera.js.map
|