@flighthq/camera-controls 0.2.1-next.451.d9a4a4e → 0.2.1-next.530.a223ca9

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.
@@ -1,6 +1,10 @@
1
1
  import type { Camera3D, FlyCameraController, FlyCameraControllerOptions } from '@flighthq/types';
2
+ export declare function cloneFlyCameraController(source: Readonly<FlyCameraController>): FlyCameraController;
3
+ export declare function copyFlyCameraController(out: FlyCameraController, source: Readonly<FlyCameraController>): void;
2
4
  export declare function createFlyCameraController(options?: Readonly<FlyCameraControllerOptions>): FlyCameraController;
3
5
  export declare function lookFlyCameraController(controller: FlyCameraController, deltaYaw: number, deltaPitch: number): void;
4
6
  export declare function moveFlyCameraController(controller: FlyCameraController, forward: number, right: number, up: number): void;
7
+ export declare function resetFlyCameraController(controller: FlyCameraController, options?: Readonly<FlyCameraControllerOptions>): void;
8
+ export declare function snapFlyCameraController(controller: FlyCameraController): void;
5
9
  export declare function updateFlyCameraController(controller: FlyCameraController, camera: Camera3D, deltaTime: number): void;
6
10
  //# sourceMappingURL=flyCameraController.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flyCameraController.d.ts","sourceRoot":"","sources":["../src/flyCameraController.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAKjG,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,0BAA0B,CAAC,GAAG,mBAAmB,CAc7G;AAKD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGnH;AAMD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,mBAAmB,EAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GACT,IAAI,CAON;AAMD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAoBpH"}
1
+ {"version":3,"file":"flyCameraController.d.ts","sourceRoot":"","sources":["../src/flyCameraController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAGjG,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAInG;AAID,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAW7G;AAKD,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,0BAA0B,CAAC,GAAG,mBAAmB,CAc7G;AAKD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGnH;AAMD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,mBAAmB,EAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GACT,IAAI,CAON;AAID,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,mBAAmB,EAC/B,OAAO,CAAC,EAAE,QAAQ,CAAC,0BAA0B,CAAC,GAC7C,IAAI,CAcN;AAGD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAG7E;AAMD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAqBpH"}
@@ -1,6 +1,27 @@
1
1
  import { setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
+ import { createEntity } from '@flighthq/entity';
2
3
  import { createVector3 } from '@flighthq/geometry';
3
- import { clamp, damp } from '@flighthq/math';
4
+ import { clamp, damp, deltaAngle } from '@flighthq/math';
5
+ // Allocates an independent Entity containing the same controller value state.
6
+ export function cloneFlyCameraController(source) {
7
+ const clone = createFlyCameraController();
8
+ copyFlyCameraController(clone, source);
9
+ return clone;
10
+ }
11
+ // Copies all controller state without sharing its mutable position. Entity runtime/binding state is
12
+ // deliberately left on `out`; this is a value-state operation, not an ownership transfer.
13
+ export function copyFlyCameraController(out, source) {
14
+ out.goalPitch = source.goalPitch;
15
+ out.goalYaw = source.goalYaw;
16
+ out.maxPitch = source.maxPitch;
17
+ out.minPitch = source.minPitch;
18
+ out.pitch = source.pitch;
19
+ out.position.x = source.position.x;
20
+ out.position.y = source.position.y;
21
+ out.position.z = source.position.z;
22
+ out.smoothTime = source.smoothTime;
23
+ out.yaw = source.yaw;
24
+ }
4
25
  // Allocates a fly / first-person controller. `position` defaults to the origin, `yaw`/`pitch` to 0
5
26
  // (looking down -Z), `pitch` limits to just inside ±90° (no gimbal flip), and `smoothTime` to 0 (the
6
27
  // look angles snap to their goal each update). Current and goal angles start equal.
@@ -8,7 +29,7 @@ export function createFlyCameraController(options) {
8
29
  const yaw = options?.yaw ?? 0;
9
30
  const pitch = options?.pitch ?? 0;
10
31
  const position = options?.position;
11
- return {
32
+ return createEntity({
12
33
  goalPitch: pitch,
13
34
  goalYaw: yaw,
14
35
  maxPitch: options?.maxPitch ?? DEFAULT_MAX_PITCH,
@@ -17,7 +38,7 @@ export function createFlyCameraController(options) {
17
38
  position: createVector3(position?.x ?? 0, position?.y ?? 0, position?.z ?? 0),
18
39
  smoothTime: options?.smoothTime ?? 0,
19
40
  yaw,
20
- };
41
+ });
21
42
  }
22
43
  // Moves the goal look angles by the given radian deltas: `deltaYaw` turns horizontally (unbounded),
23
44
  // `deltaPitch` looks up/down (clamped to [minPitch, maxPitch]). The app maps a mouse-look delta to
@@ -38,6 +59,28 @@ export function moveFlyCameraController(controller, forward, right, up) {
38
59
  position.y += up;
39
60
  position.z += -cosYaw * forward + sinYaw * right;
40
61
  }
62
+ // Restores the controller to constructor defaults or the supplied seed. Current and goal angles are
63
+ // synchronized, which makes this suitable for scene changes without a one-frame eased transition.
64
+ export function resetFlyCameraController(controller, options) {
65
+ const yaw = options?.yaw ?? 0;
66
+ const pitch = options?.pitch ?? 0;
67
+ const position = options?.position;
68
+ controller.goalPitch = pitch;
69
+ controller.goalYaw = yaw;
70
+ controller.maxPitch = options?.maxPitch ?? DEFAULT_MAX_PITCH;
71
+ controller.minPitch = options?.minPitch ?? DEFAULT_MIN_PITCH;
72
+ controller.pitch = pitch;
73
+ controller.position.x = position?.x ?? 0;
74
+ controller.position.y = position?.y ?? 0;
75
+ controller.position.z = position?.z ?? 0;
76
+ controller.smoothTime = options?.smoothTime ?? 0;
77
+ controller.yaw = yaw;
78
+ }
79
+ // Snaps current look state to the clamped goal without writing a camera.
80
+ export function snapFlyCameraController(controller) {
81
+ controller.pitch = clamp(controller.goalPitch, controller.minPitch, controller.maxPitch);
82
+ controller.yaw = controller.goalYaw;
83
+ }
41
84
  // Advances the controller one step and writes the resulting view into `camera` (in place). Eases the
42
85
  // current yaw/pitch toward their clamped goals with `@flighthq/math` `damp` (frame-rate independent;
43
86
  // `smoothTime` <= 0 or `deltaTime` <= 0 snaps), builds the forward direction from the angles, and
@@ -46,7 +89,8 @@ export function updateFlyCameraController(controller, camera, deltaTime) {
46
89
  const goalPitch = clamp(controller.goalPitch, controller.minPitch, controller.maxPitch);
47
90
  if (controller.smoothTime > 0 && deltaTime > 0) {
48
91
  const lambda = 1 / controller.smoothTime;
49
- controller.yaw = damp(controller.yaw, controller.goalYaw, lambda, deltaTime);
92
+ const nearestGoalYaw = controller.yaw + deltaAngle(controller.yaw, controller.goalYaw);
93
+ controller.yaw = damp(controller.yaw, nearestGoalYaw, lambda, deltaTime);
50
94
  controller.pitch = damp(controller.pitch, goalPitch, lambda, deltaTime);
51
95
  }
52
96
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"flyCameraController.js","sourceRoot":"","sources":["../src/flyCameraController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flyCameraController.js","sourceRoot":"","sources":["../src/flyCameraController.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { BoundingSphereLike, OrbitCameraController, OrthographicProjection, PerspectiveProjection, Projection } from '@flighthq/types';
2
+ export declare function frameOrbitCameraControllerToSphere(controller: OrbitCameraController, projection: Projection, sphere: Readonly<BoundingSphereLike>, aspect: number, padding?: number): boolean;
3
+ export declare function getPerspectiveFrameDistanceToSphere(projection: Readonly<PerspectiveProjection>, radius: number, aspect: number, padding?: number): number;
4
+ export declare function setOrthographicProjectionFrameToSphere(projection: OrthographicProjection, radius: number, aspect: number, padding?: number): void;
5
+ //# sourceMappingURL=framing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framing.d.ts","sourceRoot":"","sources":["../src/framing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,EACX,MAAM,iBAAiB,CAAC;AAOzB,wBAAgB,kCAAkC,CAChD,UAAU,EAAE,qBAAqB,EACjC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,OAAO,SAAI,GACV,OAAO,CAeT;AAKD,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,QAAQ,CAAC,qBAAqB,CAAC,EAC3C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,SAAI,GACV,MAAM,CAKR;AAID,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,sBAAsB,EAClC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,SAAI,GACV,IAAI,CASN"}
@@ -0,0 +1,46 @@
1
+ import { clamp } from '@flighthq/math';
2
+ // Frames a non-empty sphere with an orbit controller and active viewport aspect. The target moves to
3
+ // the sphere center immediately. Perspective framing changes only `goalDistance`; orthographic
4
+ // framing changes only projection half-extents because orbit distance does not affect its visible
5
+ // size. Call `snapOrbitCameraController` separately when an immediate perspective dolly is desired.
6
+ // Near/far clip planes are never changed. Returns false without mutation for invalid inputs.
7
+ export function frameOrbitCameraControllerToSphere(controller, projection, sphere, aspect, padding = 1) {
8
+ if (!(sphere.radius >= 0) || !(aspect > 0) || !(padding > 0))
9
+ return false;
10
+ if (projection.kind === 'perspective') {
11
+ const distance = getPerspectiveFrameDistanceToSphere(projection, sphere.radius, aspect, padding);
12
+ if (!Number.isFinite(distance))
13
+ return false;
14
+ controller.goalDistance = clamp(distance, controller.minDistance, controller.maxDistance);
15
+ }
16
+ else {
17
+ setOrthographicProjectionFrameToSphere(projection, sphere.radius, aspect, padding);
18
+ }
19
+ controller.target.x = sphere.center.x;
20
+ controller.target.y = sphere.center.y;
21
+ controller.target.z = sphere.center.z;
22
+ return true;
23
+ }
24
+ // Computes the eye-to-center distance that contains a sphere in both dimensions of a perspective
25
+ // viewport. `padding` is a radius multiplier (1 is tangent). Clip-plane policy remains with the
26
+ // camera owner and is intentionally not hidden here.
27
+ export function getPerspectiveFrameDistanceToSphere(projection, radius, aspect, padding = 1) {
28
+ const paddedRadius = radius * padding;
29
+ const verticalHalfFov = projection.fovY * 0.5;
30
+ const horizontalHalfFov = Math.atan(Math.tan(verticalHalfFov) * aspect);
31
+ return paddedRadius / Math.sin(Math.min(verticalHalfFov, horizontalHalfFov));
32
+ }
33
+ // Sets orthographic half-extents that contain a sphere without distortion at `aspect`. The tighter
34
+ // viewport dimension touches the padded sphere; the other expands to preserve aspect.
35
+ export function setOrthographicProjectionFrameToSphere(projection, radius, aspect, padding = 1) {
36
+ const paddedRadius = radius * padding;
37
+ if (aspect >= 1) {
38
+ projection.halfHeight = paddedRadius;
39
+ projection.halfWidth = paddedRadius * aspect;
40
+ }
41
+ else {
42
+ projection.halfHeight = paddedRadius / aspect;
43
+ projection.halfWidth = paddedRadius;
44
+ }
45
+ }
46
+ //# sourceMappingURL=framing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framing.js","sourceRoot":"","sources":["../src/framing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AASvC,qGAAqG;AACrG,+FAA+F;AAC/F,kGAAkG;AAClG,oGAAoG;AACpG,6FAA6F;AAC7F,MAAM,UAAU,kCAAkC,CAChD,UAAiC,EACjC,UAAsB,EACtB,MAAoC,EACpC,MAAc,EACd,OAAO,GAAG,CAAC;IAEX,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAE3E,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,mCAAmC,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACjG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7C,UAAU,CAAC,YAAY,GAAG,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC5F,CAAC;SAAM,CAAC;QACN,sCAAsC,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACrF,CAAC;IAED,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iGAAiG;AACjG,gGAAgG;AAChG,qDAAqD;AACrD,MAAM,UAAU,mCAAmC,CACjD,UAA2C,EAC3C,MAAc,EACd,MAAc,EACd,OAAO,GAAG,CAAC;IAEX,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;IACtC,MAAM,eAAe,GAAG,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC;IAC9C,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,CAAC;IACxE,OAAO,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,mGAAmG;AACnG,sFAAsF;AACtF,MAAM,UAAU,sCAAsC,CACpD,UAAkC,EAClC,MAAc,EACd,MAAc,EACd,OAAO,GAAG,CAAC;IAEX,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;IACtC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAChB,UAAU,CAAC,UAAU,GAAG,YAAY,CAAC;QACrC,UAAU,CAAC,SAAS,GAAG,YAAY,GAAG,MAAM,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,UAAU,CAAC,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC;QAC9C,UAAU,CAAC,SAAS,GAAG,YAAY,CAAC;IACtC,CAAC;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './flyCameraController';
2
2
  export * from './follow';
3
+ export * from './framing';
3
4
  export * from './orbitCameraController';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,UAAU,CAAC;AACzB,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './flyCameraController';
2
2
  export * from './follow';
3
+ export * from './framing';
3
4
  export * from './orbitCameraController';
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,UAAU,CAAC;AACzB,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,yBAAyB,CAAC"}
@@ -1,7 +1,12 @@
1
1
  import type { Camera3D, OrbitCameraController, OrbitCameraControllerOptions } from '@flighthq/types';
2
+ export declare function cloneOrbitCameraController(source: Readonly<OrbitCameraController>): OrbitCameraController;
3
+ export declare function copyOrbitCameraController(out: OrbitCameraController, source: Readonly<OrbitCameraController>): void;
2
4
  export declare function createOrbitCameraController(options?: Readonly<OrbitCameraControllerOptions>): OrbitCameraController;
3
5
  export declare function dollyCameraController(controller: OrbitCameraController, deltaDistance: number): void;
4
6
  export declare function orbitCameraController(controller: OrbitCameraController, deltaAzimuth: number, deltaPolar: number): void;
5
7
  export declare function panCameraController(controller: OrbitCameraController, deltaRight: number, deltaUp: number): void;
8
+ export declare function panOrbitCameraControllerInViewPlane(controller: OrbitCameraController, deltaRight: number, deltaUp: number): void;
9
+ export declare function resetOrbitCameraController(controller: OrbitCameraController, options?: Readonly<OrbitCameraControllerOptions>): void;
10
+ export declare function snapOrbitCameraController(controller: OrbitCameraController): void;
6
11
  export declare function updateOrbitCameraController(controller: OrbitCameraController, camera: Camera3D, deltaTime: number): void;
7
12
  //# sourceMappingURL=orbitCameraController.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"orbitCameraController.d.ts","sourceRoot":"","sources":["../src/orbitCameraController.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAMrG,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,4BAA4B,CAAC,GAAG,qBAAqB,CAmBnH;AAID,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAMpG;AAKD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,qBAAqB,EACjC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,IAAI,CAGN;AAMD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAOhH;AAOD,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,QAAQ,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAuBN"}
1
+ {"version":3,"file":"orbitCameraController.d.ts","sourceRoot":"","sources":["../src/orbitCameraController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAGrG,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,CAIzG;AAID,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAenH;AAMD,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,4BAA4B,CAAC,GAAG,qBAAqB,CAmBnH;AAID,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAMpG;AAKD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,qBAAqB,EACjC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,IAAI,CAGN;AAMD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAOhH;AAID,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,qBAAqB,EACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CASN;AAID,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,qBAAqB,EACjC,OAAO,CAAC,EAAE,QAAQ,CAAC,4BAA4B,CAAC,GAC/C,IAAI,CAmBN;AAGD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAIjF;AAOD,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,QAAQ,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAwBN"}
@@ -1,6 +1,31 @@
1
1
  import { setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
+ import { createEntity } from '@flighthq/entity';
2
3
  import { createVector3 } from '@flighthq/geometry';
3
- import { clamp, damp } from '@flighthq/math';
4
+ import { clamp, damp, deltaAngle } from '@flighthq/math';
5
+ // Allocates an independent Entity containing the same controller value state.
6
+ export function cloneOrbitCameraController(source) {
7
+ const clone = createOrbitCameraController();
8
+ copyOrbitCameraController(clone, source);
9
+ return clone;
10
+ }
11
+ // Copies all controller state without sharing its mutable target. Entity runtime/binding state is
12
+ // deliberately left on `out`; this is a value-state operation, not an ownership transfer.
13
+ export function copyOrbitCameraController(out, source) {
14
+ out.azimuth = source.azimuth;
15
+ out.distance = source.distance;
16
+ out.goalAzimuth = source.goalAzimuth;
17
+ out.goalDistance = source.goalDistance;
18
+ out.goalPolar = source.goalPolar;
19
+ out.maxDistance = source.maxDistance;
20
+ out.maxPolar = source.maxPolar;
21
+ out.minDistance = source.minDistance;
22
+ out.minPolar = source.minPolar;
23
+ out.polar = source.polar;
24
+ out.smoothTime = source.smoothTime;
25
+ out.target.x = source.target.x;
26
+ out.target.y = source.target.y;
27
+ out.target.z = source.target.z;
28
+ }
4
29
  // Allocates an orbit controller. `target` defaults to the origin, `distance` to 10, angles to 0;
5
30
  // `polar` limits default to just inside ±90° so the look-at up vector never degenerates, `distance`
6
31
  // to (0.01, +∞), and `smoothTime` to 0 (no damping — the camera snaps to the goal each update). The
@@ -10,7 +35,7 @@ export function createOrbitCameraController(options) {
10
35
  const polar = options?.polar ?? 0;
11
36
  const distance = options?.distance ?? 10;
12
37
  const target = options?.target;
13
- return {
38
+ return createEntity({
14
39
  azimuth,
15
40
  distance,
16
41
  goalAzimuth: azimuth,
@@ -23,7 +48,7 @@ export function createOrbitCameraController(options) {
23
48
  polar,
24
49
  smoothTime: options?.smoothTime ?? 0,
25
50
  target: createVector3(target?.x ?? 0, target?.y ?? 0, target?.z ?? 0),
26
- };
51
+ });
27
52
  }
28
53
  // Moves the goal distance (dolly / zoom) by `deltaDistance`, clamped to [minDistance, maxDistance].
29
54
  // Negative moves the eye toward the target.
@@ -37,8 +62,8 @@ export function orbitCameraController(controller, deltaAzimuth, deltaPolar) {
37
62
  controller.goalAzimuth += deltaAzimuth;
38
63
  controller.goalPolar = clamp(controller.goalPolar + deltaPolar, controller.minPolar, controller.maxPolar);
39
64
  }
40
- // Slides the orbit `target` in the view plane: `deltaRight` along the camera's horizontal right axis
41
- // (at the current goal azimuth), `deltaUp` along world-up. Because the eye is derived from the target
65
+ // Slides the orbit `target` in a world-up plane: `deltaRight` along the camera's horizontal right axis
66
+ // (at the current goal azimuth), `deltaUp` along world Y. Because the eye is derived from the target
42
67
  // each update, panning the target pans the whole view. Reads the target into locals before writing so
43
68
  // aliasing is safe.
44
69
  export function panCameraController(controller, deltaRight, deltaUp) {
@@ -49,6 +74,46 @@ export function panCameraController(controller, deltaRight, deltaUp) {
49
74
  target.y += deltaUp;
50
75
  target.z += -sinAzimuth * deltaRight;
51
76
  }
77
+ // Slides the orbit target in the actual camera view plane. Unlike `panCameraController`, `deltaUp`
78
+ // follows screen-up at the goal polar angle, so it can change all three target coordinates.
79
+ export function panOrbitCameraControllerInViewPlane(controller, deltaRight, deltaUp) {
80
+ const cosAzimuth = Math.cos(controller.goalAzimuth);
81
+ const sinAzimuth = Math.sin(controller.goalAzimuth);
82
+ const cosPolar = Math.cos(controller.goalPolar);
83
+ const sinPolar = Math.sin(controller.goalPolar);
84
+ const target = controller.target;
85
+ target.x += cosAzimuth * deltaRight - sinAzimuth * sinPolar * deltaUp;
86
+ target.y += cosPolar * deltaUp;
87
+ target.z += -sinAzimuth * deltaRight - cosAzimuth * sinPolar * deltaUp;
88
+ }
89
+ // Restores the controller to constructor defaults or the supplied seed. Current and goal spherical
90
+ // coordinates are synchronized, which makes this suitable for scene changes without an eased tail.
91
+ export function resetOrbitCameraController(controller, options) {
92
+ const azimuth = options?.azimuth ?? 0;
93
+ const polar = options?.polar ?? 0;
94
+ const distance = options?.distance ?? 10;
95
+ const target = options?.target;
96
+ controller.azimuth = azimuth;
97
+ controller.distance = distance;
98
+ controller.goalAzimuth = azimuth;
99
+ controller.goalDistance = distance;
100
+ controller.goalPolar = polar;
101
+ controller.maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
102
+ controller.maxPolar = options?.maxPolar ?? DEFAULT_MAX_POLAR;
103
+ controller.minDistance = options?.minDistance ?? DEFAULT_MIN_DISTANCE;
104
+ controller.minPolar = options?.minPolar ?? DEFAULT_MIN_POLAR;
105
+ controller.polar = polar;
106
+ controller.smoothTime = options?.smoothTime ?? 0;
107
+ controller.target.x = target?.x ?? 0;
108
+ controller.target.y = target?.y ?? 0;
109
+ controller.target.z = target?.z ?? 0;
110
+ }
111
+ // Snaps current spherical state to the clamped goal without writing a camera.
112
+ export function snapOrbitCameraController(controller) {
113
+ controller.azimuth = controller.goalAzimuth;
114
+ controller.distance = clamp(controller.goalDistance, controller.minDistance, controller.maxDistance);
115
+ controller.polar = clamp(controller.goalPolar, controller.minPolar, controller.maxPolar);
116
+ }
52
117
  // Advances the controller one step and writes the resulting view into `camera` (in place). Eases the
53
118
  // current azimuth/polar/distance toward their clamped goals with `@flighthq/math` `damp`
54
119
  // (frame-rate independent; `smoothTime` <= 0 or `deltaTime` <= 0 snaps), places the eye on the sphere
@@ -59,7 +124,8 @@ export function updateOrbitCameraController(controller, camera, deltaTime) {
59
124
  const goalDistance = clamp(controller.goalDistance, controller.minDistance, controller.maxDistance);
60
125
  if (controller.smoothTime > 0 && deltaTime > 0) {
61
126
  const lambda = 1 / controller.smoothTime;
62
- controller.azimuth = damp(controller.azimuth, controller.goalAzimuth, lambda, deltaTime);
127
+ const nearestGoalAzimuth = controller.azimuth + deltaAngle(controller.azimuth, controller.goalAzimuth);
128
+ controller.azimuth = damp(controller.azimuth, nearestGoalAzimuth, lambda, deltaTime);
63
129
  controller.polar = damp(controller.polar, goalPolar, lambda, deltaTime);
64
130
  controller.distance = damp(controller.distance, goalDistance, lambda, deltaTime);
65
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"orbitCameraController.js","sourceRoot":"","sources":["../src/orbitCameraController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"orbitCameraController.js","sourceRoot":"","sources":["../src/orbitCameraController.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/camera-controls",
3
- "version": "0.2.1-next.451.d9a4a4e",
3
+ "version": "0.2.1-next.530.a223ca9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -32,14 +32,15 @@
32
32
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@flighthq/camera": "0.2.1-next.451.d9a4a4e",
36
- "@flighthq/geometry": "0.2.1-next.451.d9a4a4e",
37
- "@flighthq/math": "0.2.1-next.451.d9a4a4e",
38
- "@flighthq/types": "0.2.1-next.451.d9a4a4e"
35
+ "@flighthq/camera": "0.2.1-next.530.a223ca9",
36
+ "@flighthq/entity": "0.2.1-next.530.a223ca9",
37
+ "@flighthq/geometry": "0.2.1-next.530.a223ca9",
38
+ "@flighthq/math": "0.2.1-next.530.a223ca9",
39
+ "@flighthq/types": "0.2.1-next.530.a223ca9"
39
40
  },
40
41
  "devDependencies": {
41
42
  "typescript": "^5.3.0"
42
43
  },
43
- "description": "Subjective camera controllers: 2D follow and 3D orbit / fly over the pure camera",
44
+ "description": "Input-agnostic 2D follow and 3D orbit / fly camera-control primitives",
44
45
  "sideEffects": false
45
46
  }
@@ -1,11 +1,16 @@
1
1
  import { createCamera3D, createPerspectiveProjection, setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
2
  import { createVector3 } from '@flighthq/geometry';
3
+ import { EntityRuntimeKey } from '@flighthq/types';
3
4
  import { describe, expect, it } from 'vitest';
4
5
 
5
6
  import {
7
+ cloneFlyCameraController,
8
+ copyFlyCameraController,
6
9
  createFlyCameraController,
7
10
  lookFlyCameraController,
8
11
  moveFlyCameraController,
12
+ resetFlyCameraController,
13
+ snapFlyCameraController,
9
14
  updateFlyCameraController,
10
15
  } from './flyCameraController';
11
16
 
@@ -13,6 +18,32 @@ function testCamera() {
13
18
  return createCamera3D({ far: 100, near: 0.1, projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }) });
14
19
  }
15
20
 
21
+ describe('cloneFlyCameraController', () => {
22
+ it('creates an independent Entity with the same value state', () => {
23
+ const source = createFlyCameraController({ pitch: 0.2, position: createVector3(1, 2, 3), yaw: 0.4 });
24
+ const clone = cloneFlyCameraController(source);
25
+ expect(clone).toMatchObject({ pitch: 0.2, yaw: 0.4 });
26
+ expect(EntityRuntimeKey in clone).toBe(true);
27
+ expect(clone.position).not.toBe(source.position);
28
+ clone.position.x = 10;
29
+ expect(source.position.x).toBe(1);
30
+ });
31
+ });
32
+
33
+ describe('copyFlyCameraController', () => {
34
+ it('copies value state without sharing position or replacing runtime state', () => {
35
+ const source = createFlyCameraController({ pitch: 0.2, position: createVector3(1, 2, 3), yaw: 0.4 });
36
+ const out = createFlyCameraController();
37
+ const runtime = { binding: null };
38
+ out[EntityRuntimeKey] = runtime;
39
+ copyFlyCameraController(out, source);
40
+
41
+ expect(out).toMatchObject({ pitch: 0.2, yaw: 0.4 });
42
+ expect(out[EntityRuntimeKey]).toBe(runtime);
43
+ expect(out.position).not.toBe(source.position);
44
+ });
45
+ });
46
+
16
47
  describe('createFlyCameraController', () => {
17
48
  it('applies documented defaults with current equal to goal', () => {
18
49
  const c = createFlyCameraController();
@@ -28,6 +59,10 @@ describe('createFlyCameraController', () => {
28
59
  expect(c.yaw).toBe(0.5);
29
60
  expect(c.position.z).toBe(3);
30
61
  });
62
+
63
+ it('produces an Entity', () => {
64
+ expect(EntityRuntimeKey in createFlyCameraController()).toBe(true);
65
+ });
31
66
  });
32
67
 
33
68
  describe('lookFlyCameraController', () => {
@@ -49,6 +84,26 @@ describe('moveFlyCameraController', () => {
49
84
  });
50
85
  });
51
86
 
87
+ describe('resetFlyCameraController', () => {
88
+ it('resets all coupled state from a seed', () => {
89
+ const c = createFlyCameraController({ position: createVector3(1, 2, 3), yaw: 1 });
90
+ resetFlyCameraController(c, { maxPitch: 0.5, pitch: 0.25, position: createVector3(4, 5, 6), yaw: -1 });
91
+ expect(c).toMatchObject({ goalPitch: 0.25, goalYaw: -1, pitch: 0.25, yaw: -1 });
92
+ expect(c.position).toMatchObject({ x: 4, y: 5, z: 6 });
93
+ });
94
+ });
95
+
96
+ describe('snapFlyCameraController', () => {
97
+ it('snaps current angles to clamped goals', () => {
98
+ const c = createFlyCameraController({ maxPitch: 0.5 });
99
+ c.goalPitch = 2;
100
+ c.goalYaw = 3;
101
+ snapFlyCameraController(c);
102
+ expect(c.pitch).toBe(0.5);
103
+ expect(c.yaw).toBe(3);
104
+ });
105
+ });
106
+
52
107
  describe('updateFlyCameraController', () => {
53
108
  it('snaps to the goal when smoothTime is 0 and writes the look-at view', () => {
54
109
  const c = createFlyCameraController({ position: createVector3(0, 0, 5) }); // yaw/pitch 0 => looks toward -Z
@@ -68,4 +123,12 @@ describe('updateFlyCameraController', () => {
68
123
  expect(c.yaw).toBeGreaterThan(0);
69
124
  expect(c.yaw).toBeLessThan(1);
70
125
  });
126
+
127
+ it('takes the shortest arc across the wrapped yaw seam', () => {
128
+ const c = createFlyCameraController({ smoothTime: 0.5, yaw: Math.PI - 0.05 });
129
+ c.goalYaw = -Math.PI + 0.05;
130
+ updateFlyCameraController(c, testCamera(), 0.016);
131
+ expect(c.yaw).toBeGreaterThan(Math.PI - 0.05);
132
+ expect(c.yaw).toBeLessThan(Math.PI + 0.05);
133
+ });
71
134
  });
@@ -0,0 +1,65 @@
1
+ import { createOrthographicProjection, createPerspectiveProjection } from '@flighthq/camera';
2
+ import { createBoundingSphere } from '@flighthq/geometry';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import {
6
+ frameOrbitCameraControllerToSphere,
7
+ getPerspectiveFrameDistanceToSphere,
8
+ setOrthographicProjectionFrameToSphere,
9
+ } from './framing';
10
+ import { createOrbitCameraController } from './orbitCameraController';
11
+
12
+ describe('frameOrbitCameraControllerToSphere', () => {
13
+ it('targets the center and changes only goal distance for perspective', () => {
14
+ const controller = createOrbitCameraController({ distance: 10 });
15
+ const projection = createPerspectiveProjection({ aspect: 1, fovY: Math.PI / 2 });
16
+ const sphere = createBoundingSphere(1, 2, 3, 2);
17
+ expect(frameOrbitCameraControllerToSphere(controller, projection, sphere, 1)).toBe(true);
18
+ expect(controller.target).toMatchObject({ x: 1, y: 2, z: 3 });
19
+ expect(controller.distance).toBe(10);
20
+ expect(controller.goalDistance).toBeCloseTo(2 / Math.sin(Math.PI / 4));
21
+ });
22
+
23
+ it('targets the center and changes only projection extent for orthographic', () => {
24
+ const controller = createOrbitCameraController({ distance: 10 });
25
+ const projection = createOrthographicProjection({ halfHeight: 1, halfWidth: 1 });
26
+ const sphere = createBoundingSphere(1, 2, 3, 2);
27
+ expect(frameOrbitCameraControllerToSphere(controller, projection, sphere, 2)).toBe(true);
28
+ expect(controller.target).toMatchObject({ x: 1, y: 2, z: 3 });
29
+ expect(controller.goalDistance).toBe(10);
30
+ expect(projection).toMatchObject({ halfHeight: 2, halfWidth: 4 });
31
+ });
32
+
33
+ it('does not mutate for empty spheres or invalid viewport inputs', () => {
34
+ const controller = createOrbitCameraController({ target: { x: 1, y: 2, z: 3 } });
35
+ const projection = createPerspectiveProjection({ aspect: 1, fovY: Math.PI / 2 });
36
+ expect(frameOrbitCameraControllerToSphere(controller, projection, createBoundingSphere(), 1)).toBe(false);
37
+ expect(frameOrbitCameraControllerToSphere(controller, projection, createBoundingSphere(0, 0, 0, 2), 0)).toBe(false);
38
+ expect(controller.target).toMatchObject({ x: 1, y: 2, z: 3 });
39
+ });
40
+ });
41
+
42
+ describe('getPerspectiveFrameDistanceToSphere', () => {
43
+ it('uses the limiting vertical field for a wide viewport', () => {
44
+ const projection = createPerspectiveProjection({ aspect: 2, fovY: Math.PI / 2 });
45
+ expect(getPerspectiveFrameDistanceToSphere(projection, 2, 2)).toBeCloseTo(2 / Math.sin(Math.PI / 4));
46
+ });
47
+
48
+ it('uses the limiting horizontal field for a tall viewport', () => {
49
+ const projection = createPerspectiveProjection({ aspect: 0.5, fovY: Math.PI / 2 });
50
+ const horizontalHalfFov = Math.atan(0.5);
51
+ expect(getPerspectiveFrameDistanceToSphere(projection, 2, 0.5)).toBeCloseTo(2 / Math.sin(horizontalHalfFov));
52
+ });
53
+ });
54
+
55
+ describe('setOrthographicProjectionFrameToSphere', () => {
56
+ it('preserves wide and tall viewport aspect while containing the sphere', () => {
57
+ const wide = createOrthographicProjection({ halfHeight: 1, halfWidth: 1 });
58
+ setOrthographicProjectionFrameToSphere(wide, 2, 2, 1.5);
59
+ expect(wide).toMatchObject({ halfHeight: 3, halfWidth: 6 });
60
+
61
+ const tall = createOrthographicProjection({ halfHeight: 1, halfWidth: 1 });
62
+ setOrthographicProjectionFrameToSphere(tall, 2, 0.5, 1.5);
63
+ expect(tall).toMatchObject({ halfHeight: 6, halfWidth: 3 });
64
+ });
65
+ });
@@ -1,12 +1,18 @@
1
1
  import { createCamera3D, createPerspectiveProjection, setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
- import { createMatrix4, createVector3 } from '@flighthq/geometry';
2
+ import { createVector3 } from '@flighthq/geometry';
3
+ import { EntityRuntimeKey } from '@flighthq/types';
3
4
  import { describe, expect, it } from 'vitest';
4
5
 
5
6
  import {
7
+ cloneOrbitCameraController,
8
+ copyOrbitCameraController,
6
9
  createOrbitCameraController,
7
10
  dollyCameraController,
8
11
  orbitCameraController,
9
12
  panCameraController,
13
+ panOrbitCameraControllerInViewPlane,
14
+ resetOrbitCameraController,
15
+ snapOrbitCameraController,
10
16
  updateOrbitCameraController,
11
17
  } from './orbitCameraController';
12
18
 
@@ -14,6 +20,32 @@ function testCamera() {
14
20
  return createCamera3D({ far: 100, near: 0.1, projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }) });
15
21
  }
16
22
 
23
+ describe('cloneOrbitCameraController', () => {
24
+ it('creates an independent Entity with the same value state', () => {
25
+ const source = createOrbitCameraController({ azimuth: 0.2, distance: 4, target: createVector3(1, 2, 3) });
26
+ const clone = cloneOrbitCameraController(source);
27
+ expect(clone).toMatchObject({ azimuth: 0.2, distance: 4 });
28
+ expect(EntityRuntimeKey in clone).toBe(true);
29
+ expect(clone.target).not.toBe(source.target);
30
+ clone.target.x = 10;
31
+ expect(source.target.x).toBe(1);
32
+ });
33
+ });
34
+
35
+ describe('copyOrbitCameraController', () => {
36
+ it('copies value state without sharing target or replacing runtime state', () => {
37
+ const source = createOrbitCameraController({ azimuth: 0.2, distance: 4, target: createVector3(1, 2, 3) });
38
+ const out = createOrbitCameraController();
39
+ const runtime = { binding: null };
40
+ out[EntityRuntimeKey] = runtime;
41
+ copyOrbitCameraController(out, source);
42
+
43
+ expect(out).toMatchObject({ azimuth: 0.2, distance: 4 });
44
+ expect(out[EntityRuntimeKey]).toBe(runtime);
45
+ expect(out.target).not.toBe(source.target);
46
+ });
47
+ });
48
+
17
49
  describe('createOrbitCameraController', () => {
18
50
  it('applies documented defaults with current equal to goal', () => {
19
51
  const c = createOrbitCameraController();
@@ -32,6 +64,10 @@ describe('createOrbitCameraController', () => {
32
64
  expect(c.target.x).toBe(1);
33
65
  expect(c.target.z).toBe(3);
34
66
  });
67
+
68
+ it('produces an Entity', () => {
69
+ expect(EntityRuntimeKey in createOrbitCameraController()).toBe(true);
70
+ });
35
71
  });
36
72
 
37
73
  describe('dollyCameraController', () => {
@@ -65,6 +101,43 @@ describe('panCameraController', () => {
65
101
  });
66
102
  });
67
103
 
104
+ describe('panOrbitCameraControllerInViewPlane', () => {
105
+ it('follows screen-up at nonzero polar', () => {
106
+ const c = createOrbitCameraController({ polar: Math.PI / 4 });
107
+ panOrbitCameraControllerInViewPlane(c, 0, 2);
108
+ expect(c.target.x).toBeCloseTo(0);
109
+ expect(c.target.y).toBeCloseTo(Math.SQRT2);
110
+ expect(c.target.z).toBeCloseTo(-Math.SQRT2);
111
+ });
112
+ });
113
+
114
+ describe('resetOrbitCameraController', () => {
115
+ it('resets all coupled state from a seed', () => {
116
+ const c = createOrbitCameraController({ target: createVector3(1, 2, 3) });
117
+ resetOrbitCameraController(c, {
118
+ azimuth: 1,
119
+ distance: 4,
120
+ maxDistance: 6,
121
+ maxPolar: 0.5,
122
+ polar: 0.25,
123
+ target: createVector3(4, 5, 6),
124
+ });
125
+ expect(c).toMatchObject({ azimuth: 1, goalAzimuth: 1, distance: 4, goalDistance: 4, polar: 0.25 });
126
+ expect(c.target).toMatchObject({ x: 4, y: 5, z: 6 });
127
+ });
128
+ });
129
+
130
+ describe('snapOrbitCameraController', () => {
131
+ it('snaps current values to clamped goals', () => {
132
+ const c = createOrbitCameraController({ maxDistance: 6, maxPolar: 0.5 });
133
+ c.goalDistance = 20;
134
+ c.goalPolar = 2;
135
+ snapOrbitCameraController(c);
136
+ expect(c.distance).toBe(6);
137
+ expect(c.polar).toBe(0.5);
138
+ });
139
+ });
140
+
68
141
  describe('updateOrbitCameraController', () => {
69
142
  it('snaps to the goal when smoothTime is 0 and writes the look-at view', () => {
70
143
  const c = createOrbitCameraController({ distance: 10 }); // azimuth 0, polar 0 => eye (0,0,10)
@@ -84,4 +157,12 @@ describe('updateOrbitCameraController', () => {
84
157
  expect(c.azimuth).toBeGreaterThan(0);
85
158
  expect(c.azimuth).toBeLessThan(1); // has not reached the goal in one small step
86
159
  });
160
+
161
+ it('takes the shortest arc across the wrapped azimuth seam', () => {
162
+ const c = createOrbitCameraController({ azimuth: Math.PI - 0.05, smoothTime: 0.5 });
163
+ c.goalAzimuth = -Math.PI + 0.05;
164
+ updateOrbitCameraController(c, testCamera(), 0.016);
165
+ expect(c.azimuth).toBeGreaterThan(Math.PI - 0.05);
166
+ expect(c.azimuth).toBeLessThan(Math.PI + 0.05);
167
+ });
87
168
  });