@zylem/game-lib 0.6.2 → 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -16
  3. package/dist/actions.d.ts +30 -21
  4. package/dist/actions.js +628 -145
  5. package/dist/actions.js.map +1 -1
  6. package/dist/behavior/platformer-3d.d.ts +296 -0
  7. package/dist/behavior/platformer-3d.js +518 -0
  8. package/dist/behavior/platformer-3d.js.map +1 -0
  9. package/dist/behavior/ricochet-2d.d.ts +274 -0
  10. package/dist/behavior/ricochet-2d.js +394 -0
  11. package/dist/behavior/ricochet-2d.js.map +1 -0
  12. package/dist/behavior/screen-wrap.d.ts +86 -0
  13. package/dist/behavior/screen-wrap.js +195 -0
  14. package/dist/behavior/screen-wrap.js.map +1 -0
  15. package/dist/behavior/thruster.d.ts +10 -0
  16. package/dist/behavior/thruster.js +234 -0
  17. package/dist/behavior/thruster.js.map +1 -0
  18. package/dist/behavior/world-boundary-2d.d.ts +141 -0
  19. package/dist/behavior/world-boundary-2d.js +181 -0
  20. package/dist/behavior/world-boundary-2d.js.map +1 -0
  21. package/dist/behavior-descriptor-BWNWmIjv.d.ts +142 -0
  22. package/dist/{blueprints-Cq3Ko6_G.d.ts → blueprints-BWGz8fII.d.ts} +2 -2
  23. package/dist/camera-B5e4c78l.d.ts +468 -0
  24. package/dist/camera.d.ts +3 -2
  25. package/dist/camera.js +900 -211
  26. package/dist/camera.js.map +1 -1
  27. package/dist/composition-DrzFrbqI.d.ts +218 -0
  28. package/dist/{core-bO8TzV7u.d.ts → core-DAkskq6Y.d.ts} +60 -62
  29. package/dist/core.d.ts +10 -6
  30. package/dist/core.js +6896 -5020
  31. package/dist/core.js.map +1 -1
  32. package/dist/{entities-DvByhMGU.d.ts → entities-DC9ce_vx.d.ts} +113 -3
  33. package/dist/entities.d.ts +5 -3
  34. package/dist/entities.js +3727 -3167
  35. package/dist/entities.js.map +1 -1
  36. package/dist/entity-BpbZqg19.d.ts +1100 -0
  37. package/dist/global-change-Dc8uCKi2.d.ts +25 -0
  38. package/dist/main.d.ts +418 -15
  39. package/dist/main.js +11384 -8515
  40. package/dist/main.js.map +1 -1
  41. package/dist/{stage-types-Bd-KtcYT.d.ts → stage-types-BFsm3qsZ.d.ts} +205 -13
  42. package/dist/stage.d.ts +10 -7
  43. package/dist/stage.js +5311 -3880
  44. package/dist/stage.js.map +1 -1
  45. package/dist/thruster-DhRaJnoL.d.ts +172 -0
  46. package/dist/world-Be5m1XC1.d.ts +31 -0
  47. package/package.json +29 -13
  48. package/dist/behaviors.d.ts +0 -854
  49. package/dist/behaviors.js +0 -1209
  50. package/dist/behaviors.js.map +0 -1
  51. package/dist/camera-CeJPAgGg.d.ts +0 -116
  52. package/dist/moveable-B_vyA6cw.d.ts +0 -67
  53. package/dist/transformable-CUhvyuYO.d.ts +0 -67
  54. package/dist/world-C8tQ7Plj.d.ts +0 -774
package/dist/camera.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/lib/camera/camera.ts
2
- import { Vector2 as Vector23, Vector3 as Vector34 } from "three";
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, WebGLRenderer as WebGLRenderer3 } from "three";
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
  }
@@ -35,15 +41,59 @@ var ThirdPersonCamera = class {
35
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.target) {
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
- const desiredCameraPosition = this.cameraRef.target.group.position.clone().add(this.distance);
45
- this.cameraRef.camera.position.lerp(desiredCameraPosition, 0.1);
46
- this.cameraRef.camera.lookAt(this.cameraRef.target.group.position);
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;
@@ -97,118 +147,6 @@ 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/vertex/object.shader.ts
107
- var objectVertexShader = `
108
- uniform vec2 uvScale;
109
- varying vec2 vUv;
110
-
111
- void main() {
112
- vUv = uv;
113
- vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
114
- gl_Position = projectionMatrix * mvPosition;
115
- }
116
- `;
117
-
118
- // src/lib/graphics/shaders/standard.shader.ts
119
- var fragment = `
120
- uniform sampler2D tDiffuse;
121
- varying vec2 vUv;
122
-
123
- void main() {
124
- vec4 texel = texture2D( tDiffuse, vUv );
125
-
126
- gl_FragColor = texel;
127
- }
128
- `;
129
- var standardShader = {
130
- vertex: objectVertexShader,
131
- fragment
132
- };
133
-
134
- // src/lib/graphics/render-pass.ts
135
- import { WebGLRenderTarget } from "three";
136
- import { Pass, FullScreenQuad } from "three/addons/postprocessing/Pass.js";
137
- var RenderPass = class extends Pass {
138
- fsQuad;
139
- resolution;
140
- scene;
141
- camera;
142
- rgbRenderTarget;
143
- normalRenderTarget;
144
- normalMaterial;
145
- constructor(resolution, scene, camera) {
146
- super();
147
- this.resolution = resolution;
148
- this.fsQuad = new FullScreenQuad(this.material());
149
- this.scene = scene;
150
- this.camera = camera;
151
- this.rgbRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
152
- this.normalRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
153
- this.normalMaterial = new THREE.MeshNormalMaterial();
154
- }
155
- render(renderer, writeBuffer) {
156
- renderer.setRenderTarget(this.rgbRenderTarget);
157
- renderer.render(this.scene, this.camera);
158
- const overrideMaterial_old = this.scene.overrideMaterial;
159
- renderer.setRenderTarget(this.normalRenderTarget);
160
- this.scene.overrideMaterial = this.normalMaterial;
161
- renderer.render(this.scene, this.camera);
162
- this.scene.overrideMaterial = overrideMaterial_old;
163
- const uniforms = this.fsQuad.material.uniforms;
164
- uniforms.tDiffuse.value = this.rgbRenderTarget.texture;
165
- uniforms.tDepth.value = this.rgbRenderTarget.depthTexture;
166
- uniforms.tNormal.value = this.normalRenderTarget.texture;
167
- uniforms.iTime.value += 0.01;
168
- if (this.renderToScreen) {
169
- renderer.setRenderTarget(null);
170
- } else {
171
- renderer.setRenderTarget(writeBuffer);
172
- }
173
- this.fsQuad.render(renderer);
174
- }
175
- material() {
176
- return new THREE.ShaderMaterial({
177
- uniforms: {
178
- iTime: { value: 0 },
179
- tDiffuse: { value: null },
180
- tDepth: { value: null },
181
- tNormal: { value: null },
182
- resolution: {
183
- value: new THREE.Vector4(
184
- this.resolution.x,
185
- this.resolution.y,
186
- 1 / this.resolution.x,
187
- 1 / this.resolution.y
188
- )
189
- }
190
- },
191
- vertexShader: standardShader.vertex,
192
- fragmentShader: standardShader.fragment
193
- });
194
- }
195
- dispose() {
196
- try {
197
- this.fsQuad?.dispose?.();
198
- } catch {
199
- }
200
- try {
201
- this.rgbRenderTarget?.dispose?.();
202
- this.normalRenderTarget?.dispose?.();
203
- } catch {
204
- }
205
- try {
206
- this.normalMaterial?.dispose?.();
207
- } catch {
208
- }
209
- }
210
- };
211
-
212
150
  // src/lib/camera/camera-debug-delegate.ts
213
151
  import { Vector3 as Vector32 } from "three";
214
152
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@@ -233,6 +171,8 @@ var CameraOrbitController = class {
233
171
  savedDebugCameraQuaternion = null;
234
172
  savedDebugCameraZoom = null;
235
173
  savedDebugOrbitTarget = null;
174
+ /** Whether user-configured orbital controls are enabled (independent of debug) */
175
+ _userOrbitEnabled = false;
236
176
  constructor(camera, domElement, cameraRig) {
237
177
  this.camera = camera;
238
178
  this.domElement = domElement;
@@ -245,11 +185,17 @@ var CameraOrbitController = class {
245
185
  this.sceneRef = scene;
246
186
  }
247
187
  /**
248
- * Check if debug mode is currently active (orbit controls enabled).
188
+ * Check if debug mode is currently active (orbit controls enabled via debug system).
249
189
  */
250
190
  get isActive() {
251
191
  return this.debugStateSnapshot.enabled;
252
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
+ }
253
199
  /**
254
200
  * Update orbit controls each frame.
255
201
  * Should be called from the camera's update loop.
@@ -261,6 +207,26 @@ var CameraOrbitController = class {
261
207
  }
262
208
  this.orbitControls?.update();
263
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
+ }
264
230
  /**
265
231
  * Attach a delegate to react to debug state changes.
266
232
  */
@@ -309,7 +275,9 @@ var CameraOrbitController = class {
309
275
  } else if (!state.enabled && wasEnabled) {
310
276
  this.saveDebugCameraState();
311
277
  this.orbitTarget = null;
312
- this.disableOrbitControls();
278
+ if (!this._userOrbitEnabled) {
279
+ this.disableOrbitControls();
280
+ }
313
281
  this.reattachCameraToRig();
314
282
  this.restoreCameraState();
315
283
  } else if (state.enabled) {
@@ -463,94 +431,314 @@ var CameraOrbitController = class {
463
431
  }
464
432
  };
465
433
 
466
- // src/lib/camera/zylem-camera.ts
467
- var ZylemCamera = class {
468
- cameraRig = null;
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;
469
477
  camera;
470
- screenResolution;
471
- renderer;
472
- composer;
473
- _perspective;
474
- target = null;
475
- sceneRef = null;
476
- frustumSize = 10;
477
- // Perspective controller delegation
478
- perspectiveController = null;
479
- // Debug/orbit controls delegation
480
- orbitController = null;
481
- constructor(perspective, screenResolution, frustumSize = 10) {
482
- this._perspective = perspective;
483
- this.screenResolution = screenResolution;
484
- this.frustumSize = frustumSize;
485
- this.renderer = new WebGLRenderer3({ antialias: false, alpha: true });
486
- this.renderer.setSize(screenResolution.x, screenResolution.y);
487
- this.renderer.shadowMap.enabled = true;
488
- this.composer = new EffectComposer(this.renderer);
489
- const aspectRatio = screenResolution.x / screenResolution.y;
490
- this.camera = this.createCameraForPerspective(aspectRatio);
491
- if (this.needsRig()) {
492
- this.cameraRig = new Object3D2();
493
- this.cameraRig.position.set(0, 3, 10);
494
- this.cameraRig.add(this.camera);
495
- this.camera.lookAt(new Vector33(0, 2, 0));
496
- } else {
497
- this.camera.position.set(0, 0, 10);
498
- this.camera.lookAt(new Vector33(0, 0, 0));
499
- }
500
- this.initializePerspectiveController();
501
- this.orbitController = new CameraOrbitController(this.camera, this.renderer.domElement, this.cameraRig);
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();
502
490
  }
503
- /**
504
- * Setup the camera with a scene
505
- */
506
- async setup(scene) {
507
- this.sceneRef = scene;
508
- let renderResolution = this.screenResolution.clone().divideScalar(2);
509
- renderResolution.x |= 0;
510
- renderResolution.y |= 0;
511
- const pass = new RenderPass(renderResolution, scene, this.camera);
512
- this.composer.addPass(pass);
513
- if (this.perspectiveController) {
514
- this.perspectiveController.setup({
515
- screenResolution: this.screenResolution,
516
- renderer: this.renderer,
517
- scene,
518
- camera: this
519
- });
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);
520
508
  }
521
- this.orbitController?.setScene(scene);
522
- this.renderer.setAnimationLoop((delta) => {
523
- this.update(delta || 0);
524
- });
509
+ this.fsQuad.render(renderer);
525
510
  }
526
- /**
527
- * Update camera and render
528
- */
529
- update(delta) {
530
- this.orbitController?.update();
531
- if (this.perspectiveController && !this.isDebugModeActive()) {
532
- this.perspectiveController.update(delta);
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();
533
594
  }
534
- this.composer.render(delta);
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;
535
619
  }
536
620
  /**
537
- * Check if debug mode is active (orbit controls taking over camera)
621
+ * Set the current scene reference for rendering.
538
622
  */
539
- isDebugModeActive() {
540
- return this.orbitController?.isActive ?? false;
623
+ setScene(scene) {
624
+ this._sceneRef = scene;
541
625
  }
542
626
  /**
543
- * Dispose renderer, composer, controls, and detach from scene
627
+ * Setup post-processing render pass for a camera (WebGL only).
544
628
  */
545
- destroy() {
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() {
546
658
  try {
547
659
  this.renderer.setAnimationLoop(null);
548
660
  } catch {
549
661
  }
550
- try {
551
- this.orbitController?.dispose();
552
- } catch {
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);
553
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();
554
742
  try {
555
743
  this.composer?.passes?.forEach((p) => p.dispose?.());
556
744
  this.composer?.dispose?.();
@@ -560,7 +748,195 @@ var ZylemCamera = class {
560
748
  this.renderer.dispose();
561
749
  } catch {
562
750
  }
751
+ this._sceneRef = null;
752
+ this._initialized = false;
753
+ }
754
+ };
755
+
756
+ // src/lib/camera/zylem-camera.ts
757
+ var ZylemCamera = class {
758
+ cameraRig = null;
759
+ camera;
760
+ screenResolution;
761
+ _perspective;
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
+ }
794
+ // Perspective controller delegation
795
+ perspectiveController = null;
796
+ // Orbit controls
797
+ orbitController = null;
798
+ _useOrbitalControls = false;
799
+ /** Reference to the shared renderer manager (set during setup) */
800
+ _rendererManager = null;
801
+ constructor(perspective, screenResolution, frustumSize = 10, rendererType = "webgl") {
802
+ this._perspective = perspective;
803
+ this.screenResolution = screenResolution;
804
+ this.frustumSize = frustumSize;
805
+ this.rendererType = rendererType;
806
+ const aspectRatio = screenResolution.x / screenResolution.y;
807
+ this.camera = this.createCameraForPerspective(aspectRatio);
808
+ if (this.needsRig()) {
809
+ this.cameraRig = new Object3D2();
810
+ this.cameraRig.position.set(0, 3, 10);
811
+ this.cameraRig.add(this.camera);
812
+ this.camera.lookAt(new Vector33(0, 2, 0));
813
+ } else {
814
+ this.camera.position.set(0, 0, 10);
815
+ this.camera.lookAt(new Vector33(0, 0, 0));
816
+ }
817
+ this.initializePerspectiveController();
818
+ }
819
+ /**
820
+ * Setup the camera with a scene and renderer manager.
821
+ * The renderer manager provides shared rendering infrastructure.
822
+ */
823
+ async setup(scene, rendererManager) {
824
+ this.sceneRef = scene;
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) {
832
+ this.perspectiveController.setup({
833
+ screenResolution: this.screenResolution,
834
+ renderer: this._rendererManager.renderer,
835
+ scene,
836
+ camera: this
837
+ });
838
+ }
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);
869
+ }
870
+ /**
871
+ * Update camera controllers (called each frame).
872
+ * Does NOT render -- rendering is handled by RendererManager.
873
+ */
874
+ update(delta) {
875
+ this.orbitController?.update();
876
+ if (this.perspectiveController && !this.isDebugModeActive() && !this._useOrbitalControls) {
877
+ this.perspectiveController.update(delta);
878
+ }
879
+ }
880
+ /**
881
+ * Check if debug mode is active (orbit controls taking over camera)
882
+ */
883
+ isDebugModeActive() {
884
+ return this.orbitController?.isActive ?? false;
885
+ }
886
+ /**
887
+ * Enable user-configured orbital controls (not debug mode).
888
+ */
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);
912
+ }
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);
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() {
933
+ try {
934
+ this.orbitController?.dispose();
935
+ } catch {
936
+ }
563
937
  this.sceneRef = null;
938
+ this.targets = [];
939
+ this._rendererManager = null;
564
940
  }
565
941
  /**
566
942
  * Attach a delegate to react to debug state changes.
@@ -569,13 +945,11 @@ var ZylemCamera = class {
569
945
  this.orbitController?.setDebugDelegate(delegate);
570
946
  }
571
947
  /**
572
- * Resize camera and renderer
948
+ * Resize camera projection.
573
949
  */
574
950
  resize(width, height) {
575
951
  this.screenResolution.set(width, height);
576
- this.renderer.setSize(width, height, false);
577
- this.composer.setSize(width, height);
578
- if (this.camera instanceof PerspectiveCamera) {
952
+ if (this.camera instanceof PerspectiveCamera2) {
579
953
  this.camera.aspect = width / height;
580
954
  this.camera.updateProjectionMatrix();
581
955
  }
@@ -584,11 +958,10 @@ var ZylemCamera = class {
584
958
  }
585
959
  }
586
960
  /**
587
- * Update renderer pixel ratio (DPR)
961
+ * Set the viewport for this camera (normalized 0-1 coordinates).
588
962
  */
589
- setPixelRatio(dpr) {
590
- const safe = Math.max(1, Number.isFinite(dpr) ? dpr : 1);
591
- this.renderer.setPixelRatio(safe);
963
+ setViewport(x, y, width, height) {
964
+ this.viewport = { x, y, width, height };
592
965
  }
593
966
  /**
594
967
  * Create camera based on perspective type
@@ -625,10 +998,10 @@ var ZylemCamera = class {
625
998
  }
626
999
  }
627
1000
  createThirdPersonCamera(aspectRatio) {
628
- return new PerspectiveCamera(75, aspectRatio, 0.1, 1e3);
1001
+ return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
629
1002
  }
630
1003
  createFirstPersonCamera(aspectRatio) {
631
- return new PerspectiveCamera(75, aspectRatio, 0.1, 1e3);
1004
+ return new PerspectiveCamera2(75, aspectRatio, 0.1, 1e3);
632
1005
  }
633
1006
  createIsometricCamera(aspectRatio) {
634
1007
  return new OrthographicCamera(
@@ -685,10 +1058,45 @@ var ZylemCamera = class {
685
1058
  return this._perspective === Perspectives.ThirdPerson;
686
1059
  }
687
1060
  /**
688
- * Get the DOM element for the renderer
1061
+ * Get the DOM element for the renderer.
1062
+ * @deprecated Access via RendererManager instead.
689
1063
  */
690
1064
  getDomElement() {
691
- return this.renderer.domElement;
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);
692
1100
  }
693
1101
  };
694
1102
 
@@ -698,20 +1106,301 @@ var CameraWrapper = class {
698
1106
  constructor(camera) {
699
1107
  this.cameraRef = camera;
700
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);
1153
+ }
701
1154
  };
702
1155
  function createCamera(options) {
703
- const screenResolution = options.screenResolution || new Vector23(window.innerWidth, window.innerHeight);
1156
+ const screenResolution = options.screenResolution || new Vector24(window.innerWidth, window.innerHeight);
704
1157
  let frustumSize = 10;
705
1158
  if (options.perspective === "fixed-2d") {
706
1159
  frustumSize = options.zoom || 10;
707
1160
  }
708
- const zylemCamera = new ZylemCamera(options.perspective || "third-person", screenResolution, frustumSize);
709
- zylemCamera.move(options.position || new Vector34(0, 0, 0));
710
- zylemCamera.camera.lookAt(options.target || new Vector34(0, 0, 0));
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
+ }
711
1180
  return new CameraWrapper(zylemCamera);
712
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
+ };
713
1399
  export {
1400
+ CameraManager,
1401
+ CameraWrapper,
714
1402
  Perspectives,
715
- createCamera
1403
+ createCamera,
1404
+ isWebGPUSupported
716
1405
  };
717
1406
  //# sourceMappingURL=camera.js.map