@zylem/game-lib 0.6.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -16
- package/dist/actions.d.ts +30 -21
- package/dist/actions.js +628 -145
- package/dist/actions.js.map +1 -1
- package/dist/behavior/platformer-3d.d.ts +296 -0
- package/dist/behavior/platformer-3d.js +518 -0
- package/dist/behavior/platformer-3d.js.map +1 -0
- package/dist/behavior/ricochet-2d.d.ts +274 -0
- package/dist/behavior/ricochet-2d.js +394 -0
- package/dist/behavior/ricochet-2d.js.map +1 -0
- package/dist/behavior/screen-wrap.d.ts +86 -0
- package/dist/behavior/screen-wrap.js +195 -0
- package/dist/behavior/screen-wrap.js.map +1 -0
- package/dist/behavior/thruster.d.ts +10 -0
- package/dist/behavior/thruster.js +234 -0
- package/dist/behavior/thruster.js.map +1 -0
- package/dist/behavior/world-boundary-2d.d.ts +141 -0
- package/dist/behavior/world-boundary-2d.js +181 -0
- package/dist/behavior/world-boundary-2d.js.map +1 -0
- package/dist/behavior-descriptor-BWNWmIjv.d.ts +142 -0
- package/dist/{blueprints-BOCc3Wve.d.ts → blueprints-BWGz8fII.d.ts} +2 -2
- package/dist/camera-B5e4c78l.d.ts +468 -0
- package/dist/camera.d.ts +3 -2
- package/dist/camera.js +962 -166
- package/dist/camera.js.map +1 -1
- package/dist/composition-DrzFrbqI.d.ts +218 -0
- package/dist/{core-CZhozNRH.d.ts → core-DAkskq6Y.d.ts} +97 -65
- package/dist/core.d.ts +12 -6
- package/dist/core.js +4449 -1052
- package/dist/core.js.map +1 -1
- package/dist/{entities-BAxfJOkk.d.ts → entities-DC9ce_vx.d.ts} +154 -45
- package/dist/entities.d.ts +5 -2
- package/dist/entities.js +2505 -722
- package/dist/entities.js.map +1 -1
- package/dist/entity-BpbZqg19.d.ts +1100 -0
- package/dist/entity-types-DAu8sGJH.d.ts +26 -0
- package/dist/global-change-Dc8uCKi2.d.ts +25 -0
- package/dist/main.d.ts +472 -29
- package/dist/main.js +11877 -6124
- package/dist/main.js.map +1 -1
- package/dist/{stage-types-CD21XoIU.d.ts → stage-types-BFsm3qsZ.d.ts} +255 -26
- package/dist/stage.d.ts +11 -6
- package/dist/stage.js +3462 -491
- package/dist/stage.js.map +1 -1
- package/dist/thruster-DhRaJnoL.d.ts +172 -0
- package/dist/world-Be5m1XC1.d.ts +31 -0
- package/package.json +21 -4
- package/dist/behaviors.d.ts +0 -106
- package/dist/behaviors.js +0 -398
- package/dist/behaviors.js.map +0 -1
- package/dist/camera-CpbDr4-V.d.ts +0 -116
- package/dist/entity-COvRtFNG.d.ts +0 -395
- package/dist/moveable-B_vyA6cw.d.ts +0 -67
- package/dist/transformable-CUhvyuYO.d.ts +0 -67
package/dist/camera.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/lib/camera/camera.ts
|
|
2
|
-
import { Vector2 as
|
|
2
|
+
import { Vector2 as Vector24, Vector3 as Vector34 } from "three";
|
|
3
3
|
|
|
4
4
|
// src/lib/camera/zylem-camera.ts
|
|
5
|
-
import { PerspectiveCamera, Vector3 as Vector33, Object3D as Object3D2, OrthographicCamera
|
|
5
|
+
import { PerspectiveCamera as PerspectiveCamera2, Vector3 as Vector33, Object3D as Object3D2, OrthographicCamera } from "three";
|
|
6
6
|
|
|
7
7
|
// src/lib/camera/perspective.ts
|
|
8
8
|
var Perspectives = {
|
|
@@ -21,6 +21,12 @@ var ThirdPersonCamera = class {
|
|
|
21
21
|
renderer = null;
|
|
22
22
|
scene = null;
|
|
23
23
|
cameraRef = null;
|
|
24
|
+
/** Padding multiplier when framing multiple targets. Higher = more zoom out. */
|
|
25
|
+
paddingFactor = 1.5;
|
|
26
|
+
/** Minimum camera distance when multi-framing (prevents extreme zoom-in). */
|
|
27
|
+
minDistance = 5;
|
|
28
|
+
/** Lerp factor for camera position smoothing. */
|
|
29
|
+
lerpFactor = 0.1;
|
|
24
30
|
constructor() {
|
|
25
31
|
this.distance = new Vector3(0, 5, 8);
|
|
26
32
|
}
|
|
@@ -28,22 +34,66 @@ var ThirdPersonCamera = class {
|
|
|
28
34
|
* Setup the third person camera controller
|
|
29
35
|
*/
|
|
30
36
|
setup(params) {
|
|
31
|
-
const { screenResolution, renderer, scene, camera
|
|
37
|
+
const { screenResolution, renderer, scene, camera } = params;
|
|
32
38
|
this.screenResolution = screenResolution;
|
|
33
39
|
this.renderer = renderer;
|
|
34
40
|
this.scene = scene;
|
|
35
|
-
this.cameraRef =
|
|
41
|
+
this.cameraRef = camera;
|
|
36
42
|
}
|
|
37
43
|
/**
|
|
38
|
-
* Update the third person camera
|
|
44
|
+
* Update the third person camera.
|
|
45
|
+
* Handles 0, 1, and multi-target scenarios.
|
|
39
46
|
*/
|
|
40
47
|
update(delta) {
|
|
41
|
-
if (!this.cameraRef
|
|
48
|
+
if (!this.cameraRef) return;
|
|
49
|
+
const targets = this.cameraRef.targets;
|
|
50
|
+
if (targets.length === 0) {
|
|
51
|
+
this.cameraRef.camera.lookAt(new Vector3(0, 0, 0));
|
|
42
52
|
return;
|
|
43
53
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
if (targets.length === 1) {
|
|
55
|
+
this.updateSingleTarget(targets[0]);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.updateMultiTarget(targets);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Classic single-target follow: lerp to target position + offset, lookAt target.
|
|
62
|
+
*/
|
|
63
|
+
updateSingleTarget(target) {
|
|
64
|
+
const useTarget = target.group?.position || new Vector3(0, 0, 0);
|
|
65
|
+
const desiredCameraPosition = useTarget.clone().add(this.distance);
|
|
66
|
+
this.cameraRef.camera.position.lerp(desiredCameraPosition, this.lerpFactor);
|
|
67
|
+
this.cameraRef.camera.lookAt(useTarget);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Multi-target framing: compute centroid, measure spread, zoom out to fit all.
|
|
71
|
+
*/
|
|
72
|
+
updateMultiTarget(targets) {
|
|
73
|
+
const centroid = new Vector3();
|
|
74
|
+
for (const t of targets) {
|
|
75
|
+
centroid.add(t.group.position);
|
|
76
|
+
}
|
|
77
|
+
centroid.divideScalar(targets.length);
|
|
78
|
+
let maxDistFromCentroid = 0;
|
|
79
|
+
for (const t of targets) {
|
|
80
|
+
const dist = centroid.distanceTo(t.group.position);
|
|
81
|
+
if (dist > maxDistFromCentroid) {
|
|
82
|
+
maxDistFromCentroid = dist;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const dynamicDistance = Math.max(maxDistFromCentroid * this.paddingFactor, this.minDistance);
|
|
86
|
+
const offsetDirection = this.distance.clone().normalize();
|
|
87
|
+
const desiredCameraPosition = centroid.clone().add(
|
|
88
|
+
offsetDirection.multiplyScalar(dynamicDistance)
|
|
89
|
+
);
|
|
90
|
+
const baseLen = this.distance.length();
|
|
91
|
+
if (baseLen > 0) {
|
|
92
|
+
const heightRatio = this.distance.y / baseLen;
|
|
93
|
+
desiredCameraPosition.y = centroid.y + dynamicDistance * heightRatio;
|
|
94
|
+
}
|
|
95
|
+
this.cameraRef.camera.position.lerp(desiredCameraPosition, this.lerpFactor);
|
|
96
|
+
this.cameraRef.camera.lookAt(centroid);
|
|
47
97
|
}
|
|
48
98
|
/**
|
|
49
99
|
* Handle resize events
|
|
@@ -54,7 +104,7 @@ var ThirdPersonCamera = class {
|
|
|
54
104
|
}
|
|
55
105
|
}
|
|
56
106
|
/**
|
|
57
|
-
* Set the distance from the target
|
|
107
|
+
* Set the distance offset from the target
|
|
58
108
|
*/
|
|
59
109
|
setDistance(distance) {
|
|
60
110
|
this.distance = distance;
|
|
@@ -73,11 +123,11 @@ var Fixed2DCamera = class {
|
|
|
73
123
|
* Setup the fixed 2D camera controller
|
|
74
124
|
*/
|
|
75
125
|
setup(params) {
|
|
76
|
-
const { screenResolution, renderer, scene, camera
|
|
126
|
+
const { screenResolution, renderer, scene, camera } = params;
|
|
77
127
|
this.screenResolution = screenResolution;
|
|
78
128
|
this.renderer = renderer;
|
|
79
129
|
this.scene = scene;
|
|
80
|
-
this.cameraRef =
|
|
130
|
+
this.cameraRef = camera;
|
|
81
131
|
this.cameraRef.camera.position.set(0, 0, 10);
|
|
82
132
|
this.cameraRef.camera.lookAt(0, 0, 0);
|
|
83
133
|
}
|
|
@@ -97,102 +147,14 @@ var Fixed2DCamera = class {
|
|
|
97
147
|
}
|
|
98
148
|
};
|
|
99
149
|
|
|
100
|
-
// src/lib/camera/zylem-camera.ts
|
|
101
|
-
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
102
|
-
|
|
103
|
-
// src/lib/graphics/render-pass.ts
|
|
104
|
-
import * as THREE from "three";
|
|
105
|
-
|
|
106
|
-
// src/lib/graphics/shaders/fragment/standard.glsl
|
|
107
|
-
var standard_default = "uniform sampler2D tDiffuse;\nvarying vec2 vUv;\n\nvoid main() {\n vec4 texel = texture2D( tDiffuse, vUv );\n\n gl_FragColor = texel;\n}";
|
|
108
|
-
|
|
109
|
-
// src/lib/graphics/shaders/vertex/standard.glsl
|
|
110
|
-
var standard_default2 = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";
|
|
111
|
-
|
|
112
|
-
// src/lib/graphics/render-pass.ts
|
|
113
|
-
import { WebGLRenderTarget } from "three";
|
|
114
|
-
import { Pass, FullScreenQuad } from "three/addons/postprocessing/Pass.js";
|
|
115
|
-
var RenderPass = class extends Pass {
|
|
116
|
-
fsQuad;
|
|
117
|
-
resolution;
|
|
118
|
-
scene;
|
|
119
|
-
camera;
|
|
120
|
-
rgbRenderTarget;
|
|
121
|
-
normalRenderTarget;
|
|
122
|
-
normalMaterial;
|
|
123
|
-
constructor(resolution, scene, camera2) {
|
|
124
|
-
super();
|
|
125
|
-
this.resolution = resolution;
|
|
126
|
-
this.fsQuad = new FullScreenQuad(this.material());
|
|
127
|
-
this.scene = scene;
|
|
128
|
-
this.camera = camera2;
|
|
129
|
-
this.rgbRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
|
|
130
|
-
this.normalRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
|
|
131
|
-
this.normalMaterial = new THREE.MeshNormalMaterial();
|
|
132
|
-
}
|
|
133
|
-
render(renderer, writeBuffer) {
|
|
134
|
-
renderer.setRenderTarget(this.rgbRenderTarget);
|
|
135
|
-
renderer.render(this.scene, this.camera);
|
|
136
|
-
const overrideMaterial_old = this.scene.overrideMaterial;
|
|
137
|
-
renderer.setRenderTarget(this.normalRenderTarget);
|
|
138
|
-
this.scene.overrideMaterial = this.normalMaterial;
|
|
139
|
-
renderer.render(this.scene, this.camera);
|
|
140
|
-
this.scene.overrideMaterial = overrideMaterial_old;
|
|
141
|
-
const uniforms = this.fsQuad.material.uniforms;
|
|
142
|
-
uniforms.tDiffuse.value = this.rgbRenderTarget.texture;
|
|
143
|
-
uniforms.tDepth.value = this.rgbRenderTarget.depthTexture;
|
|
144
|
-
uniforms.tNormal.value = this.normalRenderTarget.texture;
|
|
145
|
-
uniforms.iTime.value += 0.01;
|
|
146
|
-
if (this.renderToScreen) {
|
|
147
|
-
renderer.setRenderTarget(null);
|
|
148
|
-
} else {
|
|
149
|
-
renderer.setRenderTarget(writeBuffer);
|
|
150
|
-
}
|
|
151
|
-
this.fsQuad.render(renderer);
|
|
152
|
-
}
|
|
153
|
-
material() {
|
|
154
|
-
return new THREE.ShaderMaterial({
|
|
155
|
-
uniforms: {
|
|
156
|
-
iTime: { value: 0 },
|
|
157
|
-
tDiffuse: { value: null },
|
|
158
|
-
tDepth: { value: null },
|
|
159
|
-
tNormal: { value: null },
|
|
160
|
-
resolution: {
|
|
161
|
-
value: new THREE.Vector4(
|
|
162
|
-
this.resolution.x,
|
|
163
|
-
this.resolution.y,
|
|
164
|
-
1 / this.resolution.x,
|
|
165
|
-
1 / this.resolution.y
|
|
166
|
-
)
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
vertexShader: standard_default2,
|
|
170
|
-
fragmentShader: standard_default
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
dispose() {
|
|
174
|
-
try {
|
|
175
|
-
this.fsQuad?.dispose?.();
|
|
176
|
-
} catch {
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
this.rgbRenderTarget?.dispose?.();
|
|
180
|
-
this.normalRenderTarget?.dispose?.();
|
|
181
|
-
} catch {
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
this.normalMaterial?.dispose?.();
|
|
185
|
-
} catch {
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
|
|
190
150
|
// src/lib/camera/camera-debug-delegate.ts
|
|
191
151
|
import { Vector3 as Vector32 } from "three";
|
|
192
152
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
193
153
|
var CameraOrbitController = class {
|
|
194
154
|
camera;
|
|
195
155
|
domElement;
|
|
156
|
+
cameraRig = null;
|
|
157
|
+
sceneRef = null;
|
|
196
158
|
orbitControls = null;
|
|
197
159
|
orbitTarget = null;
|
|
198
160
|
orbitTargetWorldPos = new Vector32();
|
|
@@ -203,16 +165,37 @@ var CameraOrbitController = class {
|
|
|
203
165
|
savedCameraPosition = null;
|
|
204
166
|
savedCameraQuaternion = null;
|
|
205
167
|
savedCameraZoom = null;
|
|
206
|
-
|
|
207
|
-
|
|
168
|
+
savedCameraLocalPosition = null;
|
|
169
|
+
// Saved debug camera state for restoration when re-entering debug mode
|
|
170
|
+
savedDebugCameraPosition = null;
|
|
171
|
+
savedDebugCameraQuaternion = null;
|
|
172
|
+
savedDebugCameraZoom = null;
|
|
173
|
+
savedDebugOrbitTarget = null;
|
|
174
|
+
/** Whether user-configured orbital controls are enabled (independent of debug) */
|
|
175
|
+
_userOrbitEnabled = false;
|
|
176
|
+
constructor(camera, domElement, cameraRig) {
|
|
177
|
+
this.camera = camera;
|
|
208
178
|
this.domElement = domElement;
|
|
179
|
+
this.cameraRig = cameraRig ?? null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Set the scene reference for adding/removing camera when detaching from rig.
|
|
183
|
+
*/
|
|
184
|
+
setScene(scene) {
|
|
185
|
+
this.sceneRef = scene;
|
|
209
186
|
}
|
|
210
187
|
/**
|
|
211
|
-
* Check if debug mode is currently active (orbit controls enabled).
|
|
188
|
+
* Check if debug mode is currently active (orbit controls enabled via debug system).
|
|
212
189
|
*/
|
|
213
190
|
get isActive() {
|
|
214
191
|
return this.debugStateSnapshot.enabled;
|
|
215
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Check if any orbit controls are currently active (debug or user).
|
|
195
|
+
*/
|
|
196
|
+
get isOrbitActive() {
|
|
197
|
+
return this.debugStateSnapshot.enabled || this._userOrbitEnabled;
|
|
198
|
+
}
|
|
216
199
|
/**
|
|
217
200
|
* Update orbit controls each frame.
|
|
218
201
|
* Should be called from the camera's update loop.
|
|
@@ -224,6 +207,26 @@ var CameraOrbitController = class {
|
|
|
224
207
|
}
|
|
225
208
|
this.orbitControls?.update();
|
|
226
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Enable user-configured orbital controls (not debug mode).
|
|
212
|
+
* These orbit controls persist until explicitly disabled.
|
|
213
|
+
*/
|
|
214
|
+
enableUserOrbitControls() {
|
|
215
|
+
this._userOrbitEnabled = true;
|
|
216
|
+
if (!this.orbitControls) {
|
|
217
|
+
this.enableOrbitControls();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Disable user-configured orbital controls.
|
|
222
|
+
* Will not disable orbit controls if debug mode is active.
|
|
223
|
+
*/
|
|
224
|
+
disableUserOrbitControls() {
|
|
225
|
+
this._userOrbitEnabled = false;
|
|
226
|
+
if (!this.debugStateSnapshot.enabled) {
|
|
227
|
+
this.disableOrbitControls();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
227
230
|
/**
|
|
228
231
|
* Attach a delegate to react to debug state changes.
|
|
229
232
|
*/
|
|
@@ -265,11 +268,17 @@ var CameraOrbitController = class {
|
|
|
265
268
|
};
|
|
266
269
|
if (state.enabled && !wasEnabled) {
|
|
267
270
|
this.saveCameraState();
|
|
271
|
+
this.detachCameraFromRig();
|
|
268
272
|
this.enableOrbitControls();
|
|
273
|
+
this.restoreDebugCameraState();
|
|
269
274
|
this.updateOrbitTargetFromSelection(state.selected);
|
|
270
275
|
} else if (!state.enabled && wasEnabled) {
|
|
276
|
+
this.saveDebugCameraState();
|
|
271
277
|
this.orbitTarget = null;
|
|
272
|
-
this.
|
|
278
|
+
if (!this._userOrbitEnabled) {
|
|
279
|
+
this.disableOrbitControls();
|
|
280
|
+
}
|
|
281
|
+
this.reattachCameraToRig();
|
|
273
282
|
this.restoreCameraState();
|
|
274
283
|
} else if (state.enabled) {
|
|
275
284
|
this.updateOrbitTargetFromSelection(state.selected);
|
|
@@ -355,6 +364,393 @@ var CameraOrbitController = class {
|
|
|
355
364
|
this.savedCameraZoom = null;
|
|
356
365
|
}
|
|
357
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Save debug camera state when exiting debug mode.
|
|
369
|
+
*/
|
|
370
|
+
saveDebugCameraState() {
|
|
371
|
+
this.savedDebugCameraPosition = this.camera.position.clone();
|
|
372
|
+
this.savedDebugCameraQuaternion = this.camera.quaternion.clone();
|
|
373
|
+
if ("zoom" in this.camera) {
|
|
374
|
+
this.savedDebugCameraZoom = this.camera.zoom;
|
|
375
|
+
}
|
|
376
|
+
if (this.orbitControls) {
|
|
377
|
+
this.savedDebugOrbitTarget = this.orbitControls.target.clone();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Restore debug camera state when re-entering debug mode.
|
|
382
|
+
*/
|
|
383
|
+
restoreDebugCameraState() {
|
|
384
|
+
if (this.savedDebugCameraPosition) {
|
|
385
|
+
this.camera.position.copy(this.savedDebugCameraPosition);
|
|
386
|
+
}
|
|
387
|
+
if (this.savedDebugCameraQuaternion) {
|
|
388
|
+
this.camera.quaternion.copy(this.savedDebugCameraQuaternion);
|
|
389
|
+
}
|
|
390
|
+
if (this.savedDebugCameraZoom !== null && "zoom" in this.camera) {
|
|
391
|
+
this.camera.zoom = this.savedDebugCameraZoom;
|
|
392
|
+
this.camera.updateProjectionMatrix?.();
|
|
393
|
+
}
|
|
394
|
+
if (this.savedDebugOrbitTarget && this.orbitControls) {
|
|
395
|
+
this.orbitControls.target.copy(this.savedDebugOrbitTarget);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Detach camera from its rig to allow free orbit movement in debug mode.
|
|
400
|
+
* Preserves the camera's world position.
|
|
401
|
+
*/
|
|
402
|
+
detachCameraFromRig() {
|
|
403
|
+
if (!this.cameraRig || this.camera.parent !== this.cameraRig) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
this.savedCameraLocalPosition = this.camera.position.clone();
|
|
407
|
+
const worldPos = new Vector32();
|
|
408
|
+
this.camera.getWorldPosition(worldPos);
|
|
409
|
+
this.cameraRig.remove(this.camera);
|
|
410
|
+
if (this.sceneRef) {
|
|
411
|
+
this.sceneRef.add(this.camera);
|
|
412
|
+
}
|
|
413
|
+
this.camera.position.copy(worldPos);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Reattach camera to its rig when exiting debug mode.
|
|
417
|
+
* Restores the camera's local position relative to the rig.
|
|
418
|
+
*/
|
|
419
|
+
reattachCameraToRig() {
|
|
420
|
+
if (!this.cameraRig || this.camera.parent === this.cameraRig) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (this.sceneRef && this.camera.parent === this.sceneRef) {
|
|
424
|
+
this.sceneRef.remove(this.camera);
|
|
425
|
+
}
|
|
426
|
+
this.cameraRig.add(this.camera);
|
|
427
|
+
if (this.savedCameraLocalPosition) {
|
|
428
|
+
this.camera.position.copy(this.savedCameraLocalPosition);
|
|
429
|
+
this.savedCameraLocalPosition = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/lib/camera/renderer-manager.ts
|
|
435
|
+
import { Vector2 as Vector22, WebGLRenderer as WebGLRenderer2 } from "three";
|
|
436
|
+
import { WebGPURenderer } from "three/webgpu";
|
|
437
|
+
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
438
|
+
|
|
439
|
+
// src/lib/graphics/render-pass.ts
|
|
440
|
+
import * as THREE from "three";
|
|
441
|
+
|
|
442
|
+
// src/lib/graphics/shaders/vertex/object.shader.ts
|
|
443
|
+
var objectVertexShader = `
|
|
444
|
+
uniform vec2 uvScale;
|
|
445
|
+
varying vec2 vUv;
|
|
446
|
+
|
|
447
|
+
void main() {
|
|
448
|
+
vUv = uv;
|
|
449
|
+
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
450
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
451
|
+
}
|
|
452
|
+
`;
|
|
453
|
+
|
|
454
|
+
// src/lib/graphics/shaders/standard.shader.ts
|
|
455
|
+
var fragment = `
|
|
456
|
+
uniform sampler2D tDiffuse;
|
|
457
|
+
varying vec2 vUv;
|
|
458
|
+
|
|
459
|
+
void main() {
|
|
460
|
+
vec4 texel = texture2D( tDiffuse, vUv );
|
|
461
|
+
|
|
462
|
+
gl_FragColor = texel;
|
|
463
|
+
}
|
|
464
|
+
`;
|
|
465
|
+
var standardShader = {
|
|
466
|
+
vertex: objectVertexShader,
|
|
467
|
+
fragment
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/lib/graphics/render-pass.ts
|
|
471
|
+
import { WebGLRenderTarget } from "three";
|
|
472
|
+
import { Pass, FullScreenQuad } from "three/addons/postprocessing/Pass.js";
|
|
473
|
+
var RenderPass = class extends Pass {
|
|
474
|
+
fsQuad;
|
|
475
|
+
resolution;
|
|
476
|
+
scene;
|
|
477
|
+
camera;
|
|
478
|
+
rgbRenderTarget;
|
|
479
|
+
normalRenderTarget;
|
|
480
|
+
normalMaterial;
|
|
481
|
+
constructor(resolution, scene, camera) {
|
|
482
|
+
super();
|
|
483
|
+
this.resolution = resolution;
|
|
484
|
+
this.fsQuad = new FullScreenQuad(this.material());
|
|
485
|
+
this.scene = scene;
|
|
486
|
+
this.camera = camera;
|
|
487
|
+
this.rgbRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
|
|
488
|
+
this.normalRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
|
|
489
|
+
this.normalMaterial = new THREE.MeshNormalMaterial();
|
|
490
|
+
}
|
|
491
|
+
render(renderer, writeBuffer) {
|
|
492
|
+
renderer.setRenderTarget(this.rgbRenderTarget);
|
|
493
|
+
renderer.render(this.scene, this.camera);
|
|
494
|
+
const overrideMaterial_old = this.scene.overrideMaterial;
|
|
495
|
+
renderer.setRenderTarget(this.normalRenderTarget);
|
|
496
|
+
this.scene.overrideMaterial = this.normalMaterial;
|
|
497
|
+
renderer.render(this.scene, this.camera);
|
|
498
|
+
this.scene.overrideMaterial = overrideMaterial_old;
|
|
499
|
+
const uniforms = this.fsQuad.material.uniforms;
|
|
500
|
+
uniforms.tDiffuse.value = this.rgbRenderTarget.texture;
|
|
501
|
+
uniforms.tDepth.value = this.rgbRenderTarget.depthTexture;
|
|
502
|
+
uniforms.tNormal.value = this.normalRenderTarget.texture;
|
|
503
|
+
uniforms.iTime.value += 0.01;
|
|
504
|
+
if (this.renderToScreen) {
|
|
505
|
+
renderer.setRenderTarget(null);
|
|
506
|
+
} else {
|
|
507
|
+
renderer.setRenderTarget(writeBuffer);
|
|
508
|
+
}
|
|
509
|
+
this.fsQuad.render(renderer);
|
|
510
|
+
}
|
|
511
|
+
material() {
|
|
512
|
+
return new THREE.ShaderMaterial({
|
|
513
|
+
uniforms: {
|
|
514
|
+
iTime: { value: 0 },
|
|
515
|
+
tDiffuse: { value: null },
|
|
516
|
+
tDepth: { value: null },
|
|
517
|
+
tNormal: { value: null },
|
|
518
|
+
resolution: {
|
|
519
|
+
value: new THREE.Vector4(
|
|
520
|
+
this.resolution.x,
|
|
521
|
+
this.resolution.y,
|
|
522
|
+
1 / this.resolution.x,
|
|
523
|
+
1 / this.resolution.y
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
vertexShader: standardShader.vertex,
|
|
528
|
+
fragmentShader: standardShader.fragment
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
dispose() {
|
|
532
|
+
try {
|
|
533
|
+
this.fsQuad?.dispose?.();
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
this.rgbRenderTarget?.dispose?.();
|
|
538
|
+
this.normalRenderTarget?.dispose?.();
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
this.normalMaterial?.dispose?.();
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// src/lib/camera/renderer-manager.ts
|
|
549
|
+
var DEFAULT_VIEWPORT = { x: 0, y: 0, width: 1, height: 1 };
|
|
550
|
+
async function isWebGPUSupported() {
|
|
551
|
+
if (!("gpu" in navigator)) return false;
|
|
552
|
+
try {
|
|
553
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
554
|
+
return adapter !== null;
|
|
555
|
+
} catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
var RendererManager = class {
|
|
560
|
+
renderer;
|
|
561
|
+
composer;
|
|
562
|
+
screenResolution;
|
|
563
|
+
rendererType;
|
|
564
|
+
_isWebGPU = false;
|
|
565
|
+
_initialized = false;
|
|
566
|
+
_sceneRef = null;
|
|
567
|
+
constructor(screenResolution, rendererType = "webgl") {
|
|
568
|
+
this.screenResolution = screenResolution || new Vector22(window.innerWidth, window.innerHeight);
|
|
569
|
+
this.rendererType = rendererType;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Check if the renderer has been initialized
|
|
573
|
+
*/
|
|
574
|
+
get initialized() {
|
|
575
|
+
return this._initialized;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Check if using WebGPU renderer
|
|
579
|
+
*/
|
|
580
|
+
get isWebGPU() {
|
|
581
|
+
return this._isWebGPU;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Initialize the renderer (must be called before rendering).
|
|
585
|
+
* Async because WebGPU requires async initialization.
|
|
586
|
+
*/
|
|
587
|
+
async initRenderer() {
|
|
588
|
+
if (this._initialized) return;
|
|
589
|
+
let useWebGPU = false;
|
|
590
|
+
if (this.rendererType === "webgpu") {
|
|
591
|
+
useWebGPU = true;
|
|
592
|
+
} else if (this.rendererType === "auto") {
|
|
593
|
+
useWebGPU = await isWebGPUSupported();
|
|
594
|
+
}
|
|
595
|
+
if (useWebGPU) {
|
|
596
|
+
try {
|
|
597
|
+
this.renderer = new WebGPURenderer({ antialias: true });
|
|
598
|
+
await this.renderer.init();
|
|
599
|
+
this._isWebGPU = true;
|
|
600
|
+
console.log("RendererManager: Using WebGPU renderer");
|
|
601
|
+
} catch (e) {
|
|
602
|
+
console.warn("RendererManager: WebGPU init failed, falling back to WebGL", e);
|
|
603
|
+
this.renderer = new WebGLRenderer2({ antialias: false, alpha: true });
|
|
604
|
+
this._isWebGPU = false;
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
this.renderer = new WebGLRenderer2({ antialias: false, alpha: true });
|
|
608
|
+
this._isWebGPU = false;
|
|
609
|
+
console.log("RendererManager: Using WebGL renderer");
|
|
610
|
+
}
|
|
611
|
+
this.renderer.setSize(this.screenResolution.x, this.screenResolution.y);
|
|
612
|
+
if (this.renderer instanceof WebGLRenderer2) {
|
|
613
|
+
this.renderer.shadowMap.enabled = true;
|
|
614
|
+
}
|
|
615
|
+
if (!this._isWebGPU) {
|
|
616
|
+
this.composer = new EffectComposer(this.renderer);
|
|
617
|
+
}
|
|
618
|
+
this._initialized = true;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Set the current scene reference for rendering.
|
|
622
|
+
*/
|
|
623
|
+
setScene(scene) {
|
|
624
|
+
this._sceneRef = scene;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Setup post-processing render pass for a camera (WebGL only).
|
|
628
|
+
*/
|
|
629
|
+
setupRenderPass(scene, camera) {
|
|
630
|
+
if (this._isWebGPU || !this.composer) return;
|
|
631
|
+
if (this.composer.passes.length > 0) {
|
|
632
|
+
this.composer.passes.forEach((p) => {
|
|
633
|
+
try {
|
|
634
|
+
p.dispose?.();
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
this.composer.passes.length = 0;
|
|
639
|
+
}
|
|
640
|
+
const renderResolution = this.screenResolution.clone().divideScalar(2);
|
|
641
|
+
renderResolution.x |= 0;
|
|
642
|
+
renderResolution.y |= 0;
|
|
643
|
+
const pass = new RenderPass(renderResolution, scene, camera);
|
|
644
|
+
this.composer.addPass(pass);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Start the render loop. Calls the provided callback each frame.
|
|
648
|
+
*/
|
|
649
|
+
startRenderLoop(onFrame) {
|
|
650
|
+
this.renderer.setAnimationLoop((delta) => {
|
|
651
|
+
onFrame(delta || 0);
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Stop the render loop.
|
|
656
|
+
*/
|
|
657
|
+
stopRenderLoop() {
|
|
658
|
+
try {
|
|
659
|
+
this.renderer.setAnimationLoop(null);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Render a scene from a single camera's perspective.
|
|
665
|
+
* Sets the viewport based on the camera's viewport config.
|
|
666
|
+
*/
|
|
667
|
+
renderCamera(scene, camera) {
|
|
668
|
+
const vp = camera.viewport;
|
|
669
|
+
const w = this.screenResolution.x;
|
|
670
|
+
const h = this.screenResolution.y;
|
|
671
|
+
const pixelX = Math.floor(vp.x * w);
|
|
672
|
+
const pixelY = Math.floor(vp.y * h);
|
|
673
|
+
const pixelW = Math.floor(vp.width * w);
|
|
674
|
+
const pixelH = Math.floor(vp.height * h);
|
|
675
|
+
if (this.renderer instanceof WebGLRenderer2) {
|
|
676
|
+
this.renderer.setViewport(pixelX, pixelY, pixelW, pixelH);
|
|
677
|
+
this.renderer.setScissor(pixelX, pixelY, pixelW, pixelH);
|
|
678
|
+
this.renderer.setScissorTest(true);
|
|
679
|
+
}
|
|
680
|
+
if (this._isWebGPU) {
|
|
681
|
+
this.renderer.render(scene, camera.camera);
|
|
682
|
+
} else if (this.composer) {
|
|
683
|
+
this.composer.render(0);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Render a scene from multiple cameras, each with their own viewport.
|
|
688
|
+
* Cameras are rendered in order (first = bottom layer, last = top layer).
|
|
689
|
+
*/
|
|
690
|
+
renderCameras(scene, cameras) {
|
|
691
|
+
if (!scene || cameras.length === 0) return;
|
|
692
|
+
if (this.renderer instanceof WebGLRenderer2) {
|
|
693
|
+
this.renderer.setScissorTest(false);
|
|
694
|
+
this.renderer.clear();
|
|
695
|
+
}
|
|
696
|
+
for (const cam of cameras) {
|
|
697
|
+
this.renderCamera(scene, cam);
|
|
698
|
+
}
|
|
699
|
+
if (this.renderer instanceof WebGLRenderer2) {
|
|
700
|
+
this.renderer.setScissorTest(false);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Simple single-camera render (backwards compatible).
|
|
705
|
+
* Uses the full viewport for a single camera.
|
|
706
|
+
*/
|
|
707
|
+
render(scene, camera) {
|
|
708
|
+
if (this._isWebGPU) {
|
|
709
|
+
this.renderer.render(scene, camera);
|
|
710
|
+
} else if (this.composer) {
|
|
711
|
+
this.composer.render(0);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Resize the renderer and update resolution.
|
|
716
|
+
*/
|
|
717
|
+
resize(width, height) {
|
|
718
|
+
this.screenResolution.set(width, height);
|
|
719
|
+
this.renderer.setSize(width, height, false);
|
|
720
|
+
if (this.composer) {
|
|
721
|
+
this.composer.setSize(width, height);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Update renderer pixel ratio (DPR).
|
|
726
|
+
*/
|
|
727
|
+
setPixelRatio(dpr) {
|
|
728
|
+
const safe = Math.max(1, Number.isFinite(dpr) ? dpr : 1);
|
|
729
|
+
this.renderer.setPixelRatio(safe);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Get the DOM element for the renderer.
|
|
733
|
+
*/
|
|
734
|
+
getDomElement() {
|
|
735
|
+
return this.renderer.domElement;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Dispose renderer, composer, and related resources.
|
|
739
|
+
*/
|
|
740
|
+
dispose() {
|
|
741
|
+
this.stopRenderLoop();
|
|
742
|
+
try {
|
|
743
|
+
this.composer?.passes?.forEach((p) => p.dispose?.());
|
|
744
|
+
this.composer?.dispose?.();
|
|
745
|
+
} catch {
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
this.renderer.dispose();
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
this._sceneRef = null;
|
|
752
|
+
this._initialized = false;
|
|
753
|
+
}
|
|
358
754
|
};
|
|
359
755
|
|
|
360
756
|
// src/lib/camera/zylem-camera.ts
|
|
@@ -362,24 +758,51 @@ var ZylemCamera = class {
|
|
|
362
758
|
cameraRig = null;
|
|
363
759
|
camera;
|
|
364
760
|
screenResolution;
|
|
365
|
-
renderer;
|
|
366
|
-
composer;
|
|
367
761
|
_perspective;
|
|
368
|
-
target = null;
|
|
369
|
-
sceneRef = null;
|
|
370
762
|
frustumSize = 10;
|
|
763
|
+
rendererType;
|
|
764
|
+
sceneRef = null;
|
|
765
|
+
/** Name for camera manager lookup */
|
|
766
|
+
name = "";
|
|
767
|
+
/**
|
|
768
|
+
* Viewport in normalized coordinates (0-1).
|
|
769
|
+
* Default is fullscreen: { x: 0, y: 0, width: 1, height: 1 }
|
|
770
|
+
*/
|
|
771
|
+
viewport = { ...DEFAULT_VIEWPORT };
|
|
772
|
+
/**
|
|
773
|
+
* Multiple targets for the camera to follow/frame.
|
|
774
|
+
* Replaces the old single `target` property.
|
|
775
|
+
*/
|
|
776
|
+
targets = [];
|
|
777
|
+
/**
|
|
778
|
+
* @deprecated Use `targets` array instead. This getter/setter is kept for backward compatibility.
|
|
779
|
+
*/
|
|
780
|
+
get target() {
|
|
781
|
+
return this.targets.length > 0 ? this.targets[0] : null;
|
|
782
|
+
}
|
|
783
|
+
set target(entity) {
|
|
784
|
+
if (entity) {
|
|
785
|
+
if (this.targets.length === 0) {
|
|
786
|
+
this.targets.push(entity);
|
|
787
|
+
} else {
|
|
788
|
+
this.targets[0] = entity;
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
this.targets = [];
|
|
792
|
+
}
|
|
793
|
+
}
|
|
371
794
|
// Perspective controller delegation
|
|
372
795
|
perspectiveController = null;
|
|
373
|
-
//
|
|
796
|
+
// Orbit controls
|
|
374
797
|
orbitController = null;
|
|
375
|
-
|
|
798
|
+
_useOrbitalControls = false;
|
|
799
|
+
/** Reference to the shared renderer manager (set during setup) */
|
|
800
|
+
_rendererManager = null;
|
|
801
|
+
constructor(perspective, screenResolution, frustumSize = 10, rendererType = "webgl") {
|
|
376
802
|
this._perspective = perspective;
|
|
377
803
|
this.screenResolution = screenResolution;
|
|
378
804
|
this.frustumSize = frustumSize;
|
|
379
|
-
this.
|
|
380
|
-
this.renderer.setSize(screenResolution.x, screenResolution.y);
|
|
381
|
-
this.renderer.shadowMap.enabled = true;
|
|
382
|
-
this.composer = new EffectComposer(this.renderer);
|
|
805
|
+
this.rendererType = rendererType;
|
|
383
806
|
const aspectRatio = screenResolution.x / screenResolution.y;
|
|
384
807
|
this.camera = this.createCameraForPerspective(aspectRatio);
|
|
385
808
|
if (this.needsRig()) {
|
|
@@ -392,39 +815,67 @@ var ZylemCamera = class {
|
|
|
392
815
|
this.camera.lookAt(new Vector33(0, 0, 0));
|
|
393
816
|
}
|
|
394
817
|
this.initializePerspectiveController();
|
|
395
|
-
this.orbitController = new CameraOrbitController(this.camera, this.renderer.domElement);
|
|
396
818
|
}
|
|
397
819
|
/**
|
|
398
|
-
* Setup the camera with a scene
|
|
820
|
+
* Setup the camera with a scene and renderer manager.
|
|
821
|
+
* The renderer manager provides shared rendering infrastructure.
|
|
399
822
|
*/
|
|
400
|
-
async setup(scene) {
|
|
823
|
+
async setup(scene, rendererManager) {
|
|
401
824
|
this.sceneRef = scene;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
825
|
+
if (rendererManager) {
|
|
826
|
+
this._rendererManager = rendererManager;
|
|
827
|
+
}
|
|
828
|
+
if (this._rendererManager && !this._rendererManager.initialized) {
|
|
829
|
+
await this._rendererManager.initRenderer();
|
|
830
|
+
}
|
|
831
|
+
if (this.perspectiveController && this._rendererManager) {
|
|
408
832
|
this.perspectiveController.setup({
|
|
409
833
|
screenResolution: this.screenResolution,
|
|
410
|
-
renderer: this.renderer,
|
|
834
|
+
renderer: this._rendererManager.renderer,
|
|
411
835
|
scene,
|
|
412
836
|
camera: this
|
|
413
837
|
});
|
|
414
838
|
}
|
|
415
|
-
this.
|
|
416
|
-
this.
|
|
417
|
-
|
|
839
|
+
if (this._rendererManager) {
|
|
840
|
+
this.orbitController = new CameraOrbitController(
|
|
841
|
+
this.camera,
|
|
842
|
+
this._rendererManager.renderer.domElement,
|
|
843
|
+
this.cameraRig
|
|
844
|
+
);
|
|
845
|
+
this.orbitController.setScene(scene);
|
|
846
|
+
if (this._useOrbitalControls) {
|
|
847
|
+
this.orbitController.enableUserOrbitControls();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Legacy setup method for backward compatibility.
|
|
853
|
+
* Creates a temporary RendererManager internally.
|
|
854
|
+
* @deprecated Use setup(scene, rendererManager) instead.
|
|
855
|
+
*/
|
|
856
|
+
async setupLegacy(scene) {
|
|
857
|
+
if (!this._rendererManager) {
|
|
858
|
+
this._rendererManager = new RendererManager(this.screenResolution, this.rendererType);
|
|
859
|
+
await this._rendererManager.initRenderer();
|
|
860
|
+
this._rendererManager.setupRenderPass(scene, this.camera);
|
|
861
|
+
this._rendererManager.startRenderLoop((delta) => {
|
|
862
|
+
this.update(delta);
|
|
863
|
+
if (this._rendererManager && this.sceneRef) {
|
|
864
|
+
this._rendererManager.render(this.sceneRef, this.camera);
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
await this.setup(scene, this._rendererManager);
|
|
418
869
|
}
|
|
419
870
|
/**
|
|
420
|
-
* Update camera
|
|
871
|
+
* Update camera controllers (called each frame).
|
|
872
|
+
* Does NOT render -- rendering is handled by RendererManager.
|
|
421
873
|
*/
|
|
422
874
|
update(delta) {
|
|
423
875
|
this.orbitController?.update();
|
|
424
|
-
if (this.perspectiveController && !this.isDebugModeActive()) {
|
|
876
|
+
if (this.perspectiveController && !this.isDebugModeActive() && !this._useOrbitalControls) {
|
|
425
877
|
this.perspectiveController.update(delta);
|
|
426
878
|
}
|
|
427
|
-
this.composer.render(delta);
|
|
428
879
|
}
|
|
429
880
|
/**
|
|
430
881
|
* Check if debug mode is active (orbit controls taking over camera)
|
|
@@ -433,27 +884,59 @@ var ZylemCamera = class {
|
|
|
433
884
|
return this.orbitController?.isActive ?? false;
|
|
434
885
|
}
|
|
435
886
|
/**
|
|
436
|
-
*
|
|
887
|
+
* Enable user-configured orbital controls (not debug mode).
|
|
437
888
|
*/
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
889
|
+
enableOrbitalControls() {
|
|
890
|
+
this._useOrbitalControls = true;
|
|
891
|
+
this.orbitController?.enableUserOrbitControls();
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Disable user-configured orbital controls.
|
|
895
|
+
*/
|
|
896
|
+
disableOrbitalControls() {
|
|
897
|
+
this._useOrbitalControls = false;
|
|
898
|
+
this.orbitController?.disableUserOrbitControls();
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Whether user orbital controls are enabled.
|
|
902
|
+
*/
|
|
903
|
+
get useOrbitalControls() {
|
|
904
|
+
return this._useOrbitalControls;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Add a target entity for the camera to follow/frame.
|
|
908
|
+
*/
|
|
909
|
+
addTarget(entity) {
|
|
910
|
+
if (!this.targets.includes(entity)) {
|
|
911
|
+
this.targets.push(entity);
|
|
446
912
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Remove a target entity.
|
|
916
|
+
*/
|
|
917
|
+
removeTarget(entity) {
|
|
918
|
+
const index = this.targets.indexOf(entity);
|
|
919
|
+
if (index !== -1) {
|
|
920
|
+
this.targets.splice(index, 1);
|
|
451
921
|
}
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Clear all targets.
|
|
925
|
+
*/
|
|
926
|
+
clearTargets() {
|
|
927
|
+
this.targets = [];
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Dispose camera resources (not the renderer -- that's managed by RendererManager).
|
|
931
|
+
*/
|
|
932
|
+
destroy() {
|
|
452
933
|
try {
|
|
453
|
-
this.
|
|
934
|
+
this.orbitController?.dispose();
|
|
454
935
|
} catch {
|
|
455
936
|
}
|
|
456
937
|
this.sceneRef = null;
|
|
938
|
+
this.targets = [];
|
|
939
|
+
this._rendererManager = null;
|
|
457
940
|
}
|
|
458
941
|
/**
|
|
459
942
|
* Attach a delegate to react to debug state changes.
|
|
@@ -462,13 +945,11 @@ var ZylemCamera = class {
|
|
|
462
945
|
this.orbitController?.setDebugDelegate(delegate);
|
|
463
946
|
}
|
|
464
947
|
/**
|
|
465
|
-
* Resize camera
|
|
948
|
+
* Resize camera projection.
|
|
466
949
|
*/
|
|
467
950
|
resize(width, height) {
|
|
468
951
|
this.screenResolution.set(width, height);
|
|
469
|
-
this.
|
|
470
|
-
this.composer.setSize(width, height);
|
|
471
|
-
if (this.camera instanceof PerspectiveCamera) {
|
|
952
|
+
if (this.camera instanceof PerspectiveCamera2) {
|
|
472
953
|
this.camera.aspect = width / height;
|
|
473
954
|
this.camera.updateProjectionMatrix();
|
|
474
955
|
}
|
|
@@ -477,11 +958,10 @@ var ZylemCamera = class {
|
|
|
477
958
|
}
|
|
478
959
|
}
|
|
479
960
|
/**
|
|
480
|
-
*
|
|
961
|
+
* Set the viewport for this camera (normalized 0-1 coordinates).
|
|
481
962
|
*/
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
this.renderer.setPixelRatio(safe);
|
|
963
|
+
setViewport(x, y, width, height) {
|
|
964
|
+
this.viewport = { x, y, width, height };
|
|
485
965
|
}
|
|
486
966
|
/**
|
|
487
967
|
* Create camera based on perspective type
|
|
@@ -518,10 +998,10 @@ var ZylemCamera = class {
|
|
|
518
998
|
}
|
|
519
999
|
}
|
|
520
1000
|
createThirdPersonCamera(aspectRatio) {
|
|
521
|
-
return new
|
|
1001
|
+
return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
|
|
522
1002
|
}
|
|
523
1003
|
createFirstPersonCamera(aspectRatio) {
|
|
524
|
-
return new
|
|
1004
|
+
return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
|
|
525
1005
|
}
|
|
526
1006
|
createIsometricCamera(aspectRatio) {
|
|
527
1007
|
return new OrthographicCamera(
|
|
@@ -578,33 +1058,349 @@ var ZylemCamera = class {
|
|
|
578
1058
|
return this._perspective === Perspectives.ThirdPerson;
|
|
579
1059
|
}
|
|
580
1060
|
/**
|
|
581
|
-
* Get the DOM element for the renderer
|
|
1061
|
+
* Get the DOM element for the renderer.
|
|
1062
|
+
* @deprecated Access via RendererManager instead.
|
|
582
1063
|
*/
|
|
583
1064
|
getDomElement() {
|
|
584
|
-
|
|
1065
|
+
if (this._rendererManager) {
|
|
1066
|
+
return this._rendererManager.getDomElement();
|
|
1067
|
+
}
|
|
1068
|
+
throw new Error("ZylemCamera: No renderer manager available. Call setup() first.");
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Get the renderer manager reference.
|
|
1072
|
+
*/
|
|
1073
|
+
getRendererManager() {
|
|
1074
|
+
return this._rendererManager;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Set the renderer manager reference (used by CameraManager during setup).
|
|
1078
|
+
*/
|
|
1079
|
+
setRendererManager(manager) {
|
|
1080
|
+
this._rendererManager = manager;
|
|
1081
|
+
}
|
|
1082
|
+
// ─── Legacy compatibility methods ────────────────────────────────────────
|
|
1083
|
+
/**
|
|
1084
|
+
* @deprecated Renderer is now owned by RendererManager
|
|
1085
|
+
*/
|
|
1086
|
+
get renderer() {
|
|
1087
|
+
return this._rendererManager?.renderer;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* @deprecated Composer is now owned by RendererManager
|
|
1091
|
+
*/
|
|
1092
|
+
get composer() {
|
|
1093
|
+
return this._rendererManager?.composer;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* @deprecated Use RendererManager.setPixelRatio() instead
|
|
1097
|
+
*/
|
|
1098
|
+
setPixelRatio(dpr) {
|
|
1099
|
+
this._rendererManager?.setPixelRatio(dpr);
|
|
585
1100
|
}
|
|
586
1101
|
};
|
|
587
1102
|
|
|
588
1103
|
// src/lib/camera/camera.ts
|
|
589
1104
|
var CameraWrapper = class {
|
|
590
1105
|
cameraRef;
|
|
591
|
-
constructor(
|
|
592
|
-
this.cameraRef =
|
|
1106
|
+
constructor(camera) {
|
|
1107
|
+
this.cameraRef = camera;
|
|
1108
|
+
}
|
|
1109
|
+
// ─── Target management ──────────────────────────────────────────────────
|
|
1110
|
+
/**
|
|
1111
|
+
* Add a target entity for the camera to follow/frame.
|
|
1112
|
+
* With multiple targets, the camera auto-frames to include all of them.
|
|
1113
|
+
*/
|
|
1114
|
+
addTarget(entity) {
|
|
1115
|
+
this.cameraRef.addTarget(entity);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Remove a target entity from the camera.
|
|
1119
|
+
*/
|
|
1120
|
+
removeTarget(entity) {
|
|
1121
|
+
this.cameraRef.removeTarget(entity);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Clear all targets. Camera will look at world origin.
|
|
1125
|
+
*/
|
|
1126
|
+
clearTargets() {
|
|
1127
|
+
this.cameraRef.clearTargets();
|
|
1128
|
+
}
|
|
1129
|
+
// ─── Orbital controls ───────────────────────────────────────────────────
|
|
1130
|
+
/**
|
|
1131
|
+
* Enable orbital controls for this camera.
|
|
1132
|
+
* Allows the user to orbit, pan, and zoom the camera.
|
|
1133
|
+
*/
|
|
1134
|
+
enableOrbitalControls() {
|
|
1135
|
+
this.cameraRef.enableOrbitalControls();
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Disable orbital controls for this camera.
|
|
1139
|
+
*/
|
|
1140
|
+
disableOrbitalControls() {
|
|
1141
|
+
this.cameraRef.disableOrbitalControls();
|
|
1142
|
+
}
|
|
1143
|
+
// ─── Viewport ───────────────────────────────────────────────────────────
|
|
1144
|
+
/**
|
|
1145
|
+
* Set the viewport for this camera (normalized 0-1 coordinates).
|
|
1146
|
+
* @param x Left edge (0 = left of canvas)
|
|
1147
|
+
* @param y Bottom edge (0 = bottom of canvas)
|
|
1148
|
+
* @param width Width as fraction of canvas
|
|
1149
|
+
* @param height Height as fraction of canvas
|
|
1150
|
+
*/
|
|
1151
|
+
setViewport(x, y, width, height) {
|
|
1152
|
+
this.cameraRef.setViewport(x, y, width, height);
|
|
593
1153
|
}
|
|
594
1154
|
};
|
|
595
|
-
function
|
|
596
|
-
const screenResolution = options.screenResolution || new
|
|
1155
|
+
function createCamera(options) {
|
|
1156
|
+
const screenResolution = options.screenResolution || new Vector24(window.innerWidth, window.innerHeight);
|
|
597
1157
|
let frustumSize = 10;
|
|
598
1158
|
if (options.perspective === "fixed-2d") {
|
|
599
1159
|
frustumSize = options.zoom || 10;
|
|
600
1160
|
}
|
|
601
|
-
const zylemCamera = new ZylemCamera(
|
|
602
|
-
|
|
603
|
-
|
|
1161
|
+
const zylemCamera = new ZylemCamera(
|
|
1162
|
+
options.perspective || "third-person",
|
|
1163
|
+
screenResolution,
|
|
1164
|
+
frustumSize,
|
|
1165
|
+
options.rendererType || "webgl"
|
|
1166
|
+
);
|
|
1167
|
+
if (options.name) {
|
|
1168
|
+
zylemCamera.name = options.name;
|
|
1169
|
+
}
|
|
1170
|
+
const position = options.position ? options.position instanceof Vector34 ? options.position : new Vector34(options.position.x, options.position.y, options.position.z) : new Vector34(0, 0, 0);
|
|
1171
|
+
const target = options.target ? options.target instanceof Vector34 ? options.target : new Vector34(options.target.x, options.target.y, options.target.z) : new Vector34(0, 0, 0);
|
|
1172
|
+
zylemCamera.move(position);
|
|
1173
|
+
zylemCamera.camera.lookAt(target);
|
|
1174
|
+
if (options.viewport) {
|
|
1175
|
+
zylemCamera.viewport = { ...options.viewport };
|
|
1176
|
+
}
|
|
1177
|
+
if (options.useOrbitalControls) {
|
|
1178
|
+
zylemCamera._useOrbitalControls = true;
|
|
1179
|
+
}
|
|
604
1180
|
return new CameraWrapper(zylemCamera);
|
|
605
1181
|
}
|
|
1182
|
+
|
|
1183
|
+
// src/lib/camera/camera-manager.ts
|
|
1184
|
+
import { Vector2 as Vector25 } from "three";
|
|
1185
|
+
var CameraManager = class {
|
|
1186
|
+
/** Named camera registry */
|
|
1187
|
+
cameras = /* @__PURE__ */ new Map();
|
|
1188
|
+
/** Currently active cameras, ordered by render layer (first = bottom) */
|
|
1189
|
+
_activeCameras = [];
|
|
1190
|
+
/** Auto-created debug camera with orbit controls */
|
|
1191
|
+
_debugCamera = null;
|
|
1192
|
+
/** Reference to the shared renderer manager */
|
|
1193
|
+
_rendererManager = null;
|
|
1194
|
+
/** Scene reference */
|
|
1195
|
+
_sceneRef = null;
|
|
1196
|
+
/** Counter for auto-generated camera names */
|
|
1197
|
+
_autoNameCounter = 0;
|
|
1198
|
+
constructor() {
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Get the list of currently active cameras.
|
|
1202
|
+
*/
|
|
1203
|
+
get activeCameras() {
|
|
1204
|
+
return this._activeCameras;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Get the primary active camera (first in the active list).
|
|
1208
|
+
*/
|
|
1209
|
+
get primaryCamera() {
|
|
1210
|
+
return this._activeCameras.length > 0 ? this._activeCameras[0] : null;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Get the debug camera.
|
|
1214
|
+
*/
|
|
1215
|
+
get debugCamera() {
|
|
1216
|
+
return this._debugCamera;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Get all registered cameras.
|
|
1220
|
+
*/
|
|
1221
|
+
get allCameras() {
|
|
1222
|
+
return Array.from(this.cameras.values());
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Add a camera to the manager.
|
|
1226
|
+
* If no name is provided, one is auto-generated.
|
|
1227
|
+
* The first camera added becomes the active camera.
|
|
1228
|
+
*
|
|
1229
|
+
* @param camera The ZylemCamera instance to add
|
|
1230
|
+
* @param name Optional name for lookup
|
|
1231
|
+
* @returns The assigned name
|
|
1232
|
+
*/
|
|
1233
|
+
addCamera(camera, name) {
|
|
1234
|
+
const resolvedName = name || camera.name || `camera_${this._autoNameCounter++}`;
|
|
1235
|
+
camera.name = resolvedName;
|
|
1236
|
+
this.cameras.set(resolvedName, camera);
|
|
1237
|
+
if (this._activeCameras.length === 0) {
|
|
1238
|
+
this._activeCameras.push(camera);
|
|
1239
|
+
}
|
|
1240
|
+
return resolvedName;
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Remove a camera by name or reference.
|
|
1244
|
+
* Cannot remove the debug camera via this method.
|
|
1245
|
+
*/
|
|
1246
|
+
removeCamera(nameOrRef) {
|
|
1247
|
+
let name;
|
|
1248
|
+
if (typeof nameOrRef === "string") {
|
|
1249
|
+
name = nameOrRef;
|
|
1250
|
+
} else {
|
|
1251
|
+
for (const [key, cam] of this.cameras.entries()) {
|
|
1252
|
+
if (cam === nameOrRef) {
|
|
1253
|
+
name = key;
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (!name) return false;
|
|
1259
|
+
const camera = this.cameras.get(name);
|
|
1260
|
+
if (!camera) return false;
|
|
1261
|
+
if (camera === this._debugCamera) {
|
|
1262
|
+
console.warn("CameraManager: Cannot remove the debug camera");
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
this.cameras.delete(name);
|
|
1266
|
+
const activeIndex = this._activeCameras.indexOf(camera);
|
|
1267
|
+
if (activeIndex !== -1) {
|
|
1268
|
+
this._activeCameras.splice(activeIndex, 1);
|
|
1269
|
+
}
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Set a camera as the primary active camera (replaces all active cameras
|
|
1274
|
+
* except additional viewport cameras).
|
|
1275
|
+
*
|
|
1276
|
+
* @param nameOrRef Camera name or reference to activate
|
|
1277
|
+
*/
|
|
1278
|
+
setActiveCamera(nameOrRef) {
|
|
1279
|
+
const camera = this.resolveCamera(nameOrRef);
|
|
1280
|
+
if (!camera) {
|
|
1281
|
+
console.warn(`CameraManager: Camera not found: ${nameOrRef}`);
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
const pipCameras = this._activeCameras.filter((c) => {
|
|
1285
|
+
return c !== this._activeCameras[0] && c.viewport.width < 1;
|
|
1286
|
+
});
|
|
1287
|
+
this._activeCameras = [camera, ...pipCameras];
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Add a camera as an additional active camera (for split-screen or PiP).
|
|
1292
|
+
*/
|
|
1293
|
+
addActiveCamera(nameOrRef) {
|
|
1294
|
+
const camera = this.resolveCamera(nameOrRef);
|
|
1295
|
+
if (!camera) return false;
|
|
1296
|
+
if (!this._activeCameras.includes(camera)) {
|
|
1297
|
+
this._activeCameras.push(camera);
|
|
1298
|
+
}
|
|
1299
|
+
return true;
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Remove a camera from the active render list (does not remove from registry).
|
|
1303
|
+
*/
|
|
1304
|
+
deactivateCamera(nameOrRef) {
|
|
1305
|
+
const camera = this.resolveCamera(nameOrRef);
|
|
1306
|
+
if (!camera) return false;
|
|
1307
|
+
const index = this._activeCameras.indexOf(camera);
|
|
1308
|
+
if (index !== -1) {
|
|
1309
|
+
this._activeCameras.splice(index, 1);
|
|
1310
|
+
return true;
|
|
1311
|
+
}
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Get a camera by name.
|
|
1316
|
+
*/
|
|
1317
|
+
getCamera(name) {
|
|
1318
|
+
return this.cameras.get(name) ?? null;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Setup all cameras with the given scene and renderer manager.
|
|
1322
|
+
* Also creates the debug camera.
|
|
1323
|
+
*/
|
|
1324
|
+
async setup(scene, rendererManager) {
|
|
1325
|
+
this._sceneRef = scene;
|
|
1326
|
+
this._rendererManager = rendererManager;
|
|
1327
|
+
this.createDebugCamera(rendererManager.screenResolution);
|
|
1328
|
+
for (const camera of this.cameras.values()) {
|
|
1329
|
+
camera.setRendererManager(rendererManager);
|
|
1330
|
+
await camera.setup(scene, rendererManager);
|
|
1331
|
+
}
|
|
1332
|
+
if (this._debugCamera) {
|
|
1333
|
+
this._debugCamera.setRendererManager(rendererManager);
|
|
1334
|
+
await this._debugCamera.setup(scene, rendererManager);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Update all active cameras' controllers.
|
|
1339
|
+
*/
|
|
1340
|
+
update(delta) {
|
|
1341
|
+
for (const camera of this._activeCameras) {
|
|
1342
|
+
camera.update(delta);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Render all active cameras through the renderer manager.
|
|
1347
|
+
*/
|
|
1348
|
+
render(scene) {
|
|
1349
|
+
if (!this._rendererManager || this._activeCameras.length === 0) return;
|
|
1350
|
+
this._rendererManager.renderCameras(scene, this._activeCameras);
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Create a default third-person camera if no cameras have been added.
|
|
1354
|
+
*/
|
|
1355
|
+
ensureDefaultCamera() {
|
|
1356
|
+
if (this.cameras.size === 0 || this._activeCameras.length === 0) {
|
|
1357
|
+
const screenRes = this._rendererManager?.screenResolution || new Vector25(window.innerWidth, window.innerHeight);
|
|
1358
|
+
const defaultCam = new ZylemCamera(Perspectives.ThirdPerson, screenRes);
|
|
1359
|
+
this.addCamera(defaultCam, "default");
|
|
1360
|
+
return defaultCam;
|
|
1361
|
+
}
|
|
1362
|
+
return this._activeCameras[0];
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Dispose all cameras and cleanup.
|
|
1366
|
+
*/
|
|
1367
|
+
dispose() {
|
|
1368
|
+
for (const camera of this.cameras.values()) {
|
|
1369
|
+
camera.destroy();
|
|
1370
|
+
}
|
|
1371
|
+
this._debugCamera?.destroy();
|
|
1372
|
+
this.cameras.clear();
|
|
1373
|
+
this._activeCameras = [];
|
|
1374
|
+
this._debugCamera = null;
|
|
1375
|
+
this._rendererManager = null;
|
|
1376
|
+
this._sceneRef = null;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Create the always-available debug camera with orbit controls.
|
|
1380
|
+
*/
|
|
1381
|
+
createDebugCamera(screenResolution) {
|
|
1382
|
+
this._debugCamera = new ZylemCamera(Perspectives.ThirdPerson, screenResolution);
|
|
1383
|
+
this._debugCamera.name = "__debug__";
|
|
1384
|
+
this._debugCamera.enableOrbitalControls();
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Resolve a camera from a name or reference.
|
|
1388
|
+
*/
|
|
1389
|
+
resolveCamera(nameOrRef) {
|
|
1390
|
+
if (typeof nameOrRef === "string") {
|
|
1391
|
+
return this.cameras.get(nameOrRef) ?? null;
|
|
1392
|
+
}
|
|
1393
|
+
for (const cam of this.cameras.values()) {
|
|
1394
|
+
if (cam === nameOrRef) return cam;
|
|
1395
|
+
}
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
606
1399
|
export {
|
|
1400
|
+
CameraManager,
|
|
1401
|
+
CameraWrapper,
|
|
607
1402
|
Perspectives,
|
|
608
|
-
|
|
1403
|
+
createCamera,
|
|
1404
|
+
isWebGPUSupported
|
|
609
1405
|
};
|
|
610
1406
|
//# sourceMappingURL=camera.js.map
|