@ifc-lite/renderer 1.43.0 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/camera-animation.d.ts +20 -32
- package/dist/camera-animation.d.ts.map +1 -1
- package/dist/camera-animation.js +71 -358
- package/dist/camera-animation.js.map +1 -1
- package/dist/camera-controls.d.ts +11 -51
- package/dist/camera-controls.d.ts.map +1 -1
- package/dist/camera-controls.js +140 -12
- package/dist/camera-controls.js.map +1 -1
- package/dist/camera-first-person.d.ts +52 -0
- package/dist/camera-first-person.d.ts.map +1 -0
- package/dist/camera-first-person.js +119 -0
- package/dist/camera-first-person.js.map +1 -0
- package/dist/camera-framing.d.ts +86 -0
- package/dist/camera-framing.d.ts.map +1 -0
- package/dist/camera-framing.js +230 -0
- package/dist/camera-framing.js.map +1 -0
- package/dist/camera-guards.d.ts +148 -0
- package/dist/camera-guards.d.ts.map +1 -0
- package/dist/camera-guards.js +150 -0
- package/dist/camera-guards.js.map +1 -0
- package/dist/camera-matrices.d.ts +14 -0
- package/dist/camera-matrices.d.ts.map +1 -0
- package/dist/camera-matrices.js +146 -0
- package/dist/camera-matrices.js.map +1 -0
- package/dist/camera-preset-view.d.ts +56 -0
- package/dist/camera-preset-view.d.ts.map +1 -0
- package/dist/camera-preset-view.js +180 -0
- package/dist/camera-preset-view.js.map +1 -0
- package/dist/camera-projection.d.ts +1 -1
- package/dist/camera-projection.d.ts.map +1 -1
- package/dist/camera-projection.js +83 -30
- package/dist/camera-projection.js.map +1 -1
- package/dist/camera-state.d.ts +62 -0
- package/dist/camera-state.d.ts.map +1 -0
- package/dist/camera-state.js +5 -0
- package/dist/camera-state.js.map +1 -0
- package/dist/camera.d.ts +22 -11
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +75 -120
- package/dist/camera.js.map +1 -1
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +23 -1
- package/dist/environment.js.map +1 -1
- package/dist/index.d.ts +154 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +294 -45
- package/dist/index.js.map +1 -1
- package/dist/math.d.ts +49 -1
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +122 -67
- package/dist/math.js.map +1 -1
- package/dist/renderer-symbolic-overlays.d.ts.map +1 -1
- package/dist/renderer-symbolic-overlays.js +20 -25
- package/dist/renderer-symbolic-overlays.js.map +1 -1
- package/dist/scene-geometry.d.ts +32 -0
- package/dist/scene-geometry.d.ts.map +1 -1
- package/dist/scene-geometry.js +66 -0
- package/dist/scene-geometry.js.map +1 -1
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +22 -97
- package/dist/scene.js.map +1 -1
- package/dist/section-2d-lift.d.ts +105 -0
- package/dist/section-2d-lift.d.ts.map +1 -0
- package/dist/section-2d-lift.js +162 -0
- package/dist/section-2d-lift.js.map +1 -0
- package/dist/section-2d-line-buffer.d.ts +58 -0
- package/dist/section-2d-line-buffer.d.ts.map +1 -0
- package/dist/section-2d-line-buffer.js +109 -0
- package/dist/section-2d-line-buffer.js.map +1 -0
- package/dist/section-2d-overlay.d.ts +28 -77
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +161 -593
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-plane-basis.d.ts +22 -3
- package/dist/section-plane-basis.d.ts.map +1 -1
- package/dist/section-plane-basis.js +51 -10
- package/dist/section-plane-basis.js.map +1 -1
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js +10 -1
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/section-2d-overlay.wgsl.d.ts +102 -0
- package/dist/shaders/section-2d-overlay.wgsl.d.ts.map +1 -0
- package/dist/shaders/section-2d-overlay.wgsl.js +281 -0
- package/dist/shaders/section-2d-overlay.wgsl.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { areFiniteNumbers, isUsableDistance } from './camera-guards.js';
|
|
5
|
+
export class FirstPersonNavigator {
|
|
6
|
+
state;
|
|
7
|
+
updateMatrices;
|
|
8
|
+
isFirstPersonMode = false;
|
|
9
|
+
walkVelocity = { x: 0, z: 0 };
|
|
10
|
+
constructor(state, updateMatrices) {
|
|
11
|
+
this.state = state;
|
|
12
|
+
this.updateMatrices = updateMatrices;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Set first-person mode.
|
|
16
|
+
*
|
|
17
|
+
* Load-bearing, not bookkeeping: {@link move} refuses to walk while this is
|
|
18
|
+
* off. The flag was write-only before the extraction — nothing ever read it,
|
|
19
|
+
* so `moveFirstPerson` moved the camera whether or not walk mode had been
|
|
20
|
+
* entered, and the only thing keeping that from being visible was the
|
|
21
|
+
* viewer's *second*, independent check on `activeTool === 'walk'`. Two
|
|
22
|
+
* unrelated gates for one decision means neither is load-bearing on its own,
|
|
23
|
+
* and the renderer is published: an embedder driving `moveFirstPerson` from
|
|
24
|
+
* its own key handler has only this one.
|
|
25
|
+
*
|
|
26
|
+
* Disabling also drops the accumulated walk velocity. It is smoothed in
|
|
27
|
+
* place (`+= (target - current) * 0.15`), so a leftover would be spent on
|
|
28
|
+
* the first frame after walk mode is re-entered — a visible lurch in
|
|
29
|
+
* whatever direction the user happened to be walking when they left.
|
|
30
|
+
*/
|
|
31
|
+
setEnabled(enabled) {
|
|
32
|
+
this.isFirstPersonMode = enabled;
|
|
33
|
+
if (!enabled)
|
|
34
|
+
this.stop();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Drop the accumulated walk velocity, keeping the mode as it is.
|
|
38
|
+
*
|
|
39
|
+
* Called on model reset. The mode itself is deliberately *not* cleared:
|
|
40
|
+
* it mirrors the viewer's active tool, which a model load does not change,
|
|
41
|
+
* and clearing it here would leave walk mode silently dead until the user
|
|
42
|
+
* toggled the tool off and back on again.
|
|
43
|
+
*/
|
|
44
|
+
stop() {
|
|
45
|
+
this.walkVelocity.x = 0;
|
|
46
|
+
this.walkVelocity.z = 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Walk on the horizontal XZ plane (Y-up coordinate system).
|
|
50
|
+
* Forward/backward moves in the camera's horizontal facing direction.
|
|
51
|
+
* Left/right strafes perpendicular. Y position stays fixed (walking on ground).
|
|
52
|
+
* Speed scales with scene size. Movement uses smooth acceleration to avoid
|
|
53
|
+
* abrupt jumps — velocity ramps up over successive frames.
|
|
54
|
+
*/
|
|
55
|
+
move(forward, right, _up) {
|
|
56
|
+
// Mode gate, ahead of every other guard: walking is only a thing that
|
|
57
|
+
// exists while walk mode is on. See {@link setEnabled}.
|
|
58
|
+
if (!this.isFirstPersonMode)
|
|
59
|
+
return;
|
|
60
|
+
// Argument-side guard (#2473). `walkVelocity` is accumulated in place, so
|
|
61
|
+
// a non-finite axis latches first-person movement dead for the session
|
|
62
|
+
// exactly the way a non-finite pose did (#2441) — and it does not need an
|
|
63
|
+
// exotic value: both `moveFirstPerson(NaN, 0, 0)` and
|
|
64
|
+
// `moveFirstPerson(Infinity, 0, 0)` came back with a non-finite position
|
|
65
|
+
// on a finite pose. `_up` is unused, so it is deliberately not tested.
|
|
66
|
+
if (!areFiniteNumbers(forward, right))
|
|
67
|
+
return;
|
|
68
|
+
// Camera forward direction projected onto XZ plane
|
|
69
|
+
const dir = {
|
|
70
|
+
x: this.state.camera.target.x - this.state.camera.position.x,
|
|
71
|
+
z: this.state.camera.target.z - this.state.camera.position.z,
|
|
72
|
+
};
|
|
73
|
+
const horizLen = Math.sqrt(dir.x * dir.x + dir.z * dir.z);
|
|
74
|
+
// Finiteness, not just magnitude: `NaN < 1e-10` is false, so a malformed
|
|
75
|
+
// pose fell through here and `dir.x / horizLen` came back NaN. That does
|
|
76
|
+
// not merely produce one bad step — `walkVelocity` is accumulated in place
|
|
77
|
+
// (`+= (target - current) * 0.15`), so a single NaN frame latches it and
|
|
78
|
+
// first-person movement stays dead for the rest of the session even after
|
|
79
|
+
// the pose is corrected (#2441).
|
|
80
|
+
if (!isUsableDistance(horizLen, 1e-10))
|
|
81
|
+
return;
|
|
82
|
+
// Normalized horizontal forward and right vectors
|
|
83
|
+
const fwdX = dir.x / horizLen;
|
|
84
|
+
const fwdZ = dir.z / horizLen;
|
|
85
|
+
const rightX = -fwdZ;
|
|
86
|
+
const rightZ = fwdX;
|
|
87
|
+
// Target velocity from input (forward/right can be -2..2 with sprint)
|
|
88
|
+
const targetVelX = fwdX * forward + rightX * right;
|
|
89
|
+
const targetVelZ = fwdZ * forward + rightZ * right;
|
|
90
|
+
// Smooth acceleration: lerp current walk velocity toward target
|
|
91
|
+
this.walkVelocity.x += (targetVelX - this.walkVelocity.x) * 0.15;
|
|
92
|
+
this.walkVelocity.z += (targetVelZ - this.walkVelocity.z) * 0.15;
|
|
93
|
+
// Speed proportional to scene size (use camera-target distance as proxy)
|
|
94
|
+
const camDir = {
|
|
95
|
+
x: this.state.camera.position.x - this.state.camera.target.x,
|
|
96
|
+
y: this.state.camera.position.y - this.state.camera.target.y,
|
|
97
|
+
z: this.state.camera.position.z - this.state.camera.target.z,
|
|
98
|
+
};
|
|
99
|
+
const distance = Math.sqrt(camDir.x * camDir.x + camDir.y * camDir.y + camDir.z * camDir.z);
|
|
100
|
+
// The horizontal guard above does not cover this one: `camDir` includes Y,
|
|
101
|
+
// so a pose whose *only* bad coordinate is vertical reaches here with a
|
|
102
|
+
// finite `horizLen` and a NaN `distance`. `Math.max` is NaN-transparent —
|
|
103
|
+
// `Math.max(0.02, NaN)` is `NaN`, not the floor — so the clamp would
|
|
104
|
+
// forward it into the offsets written back to position and target.
|
|
105
|
+
if (!isUsableDistance(distance, 0))
|
|
106
|
+
return;
|
|
107
|
+
const speed = Math.max(0.02, distance * 0.004);
|
|
108
|
+
// Apply smoothed velocity
|
|
109
|
+
const offsetX = this.walkVelocity.x * speed;
|
|
110
|
+
const offsetZ = this.walkVelocity.z * speed;
|
|
111
|
+
// Move both position and target by the same offset (preserves view direction)
|
|
112
|
+
this.state.camera.position.x += offsetX;
|
|
113
|
+
this.state.camera.position.z += offsetZ;
|
|
114
|
+
this.state.camera.target.x += offsetX;
|
|
115
|
+
this.state.camera.target.z += offsetZ;
|
|
116
|
+
this.updateMatrices();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=camera-first-person.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-first-person.js","sourceRoot":"","sources":["../src/camera-first-person.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAY/D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,OAAO,oBAAoB;IAKZ;IACA;IALX,iBAAiB,GAAG,KAAK,CAAC;IAC1B,YAAY,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAEtC,YACmB,KAA0B,EAC1B,cAA0B;QAD1B,UAAK,GAAL,KAAK,CAAqB;QAC1B,mBAAc,GAAd,cAAc,CAAY;IAC1C,CAAC;IAEJ;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,OAAgB;QACzB,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC;QACjC,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED;;;;;;;OAOG;IACH,IAAI;QACF,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED;;;;;;OAMG;IACH,IAAI,CAAC,OAAe,EAAE,KAAa,EAAE,GAAW;QAC9C,sEAAsE;QACtE,wDAAwD;QACxD,IAAI,CAAC,IAAI,CAAC,iBAAiB;YAAE,OAAO;QAEpC,0EAA0E;QAC1E,uEAAuE;QACvE,0EAA0E;QAC1E,sDAAsD;QACtD,yEAAyE;QACzE,uEAAuE;QACvE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC;YAAE,OAAO;QAE9C,mDAAmD;QACnD,MAAM,GAAG,GAAG;YACV,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC5D,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;SAC7D,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1D,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,0EAA0E;QAC1E,iCAAiC;QACjC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,OAAO;QAE/C,kDAAkD;QAClD,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QAC9B,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC;QAEpB,sEAAsE;QACtE,MAAM,UAAU,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;QACnD,MAAM,UAAU,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;QAEnD,gEAAgE;QAChE,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACjE,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QAEjE,yEAAyE;QACzE,MAAM,MAAM,GAAG;YACb,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5D,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5D,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;SAC7D,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC5F,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,qEAAqE;QACrE,mEAAmE;QACnE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;YAAE,OAAO;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,CAAC;QAE/C,0BAA0B;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC;QAE5C,8EAA8E;QAC9E,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC;QAEtC,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framing: "put the camera on this thing, keeping the direction it already
|
|
3
|
+
* has". Given the current camera state and a point or an AABB, work out the
|
|
4
|
+
* pose to end up in.
|
|
5
|
+
*
|
|
6
|
+
* The ViewCube's *preset* directions are the neighbouring subject and live in
|
|
7
|
+
* `camera-preset-view.ts`: there the direction is dictated by a named face and
|
|
8
|
+
* a rotation cycle rather than inherited from the pose, and the box has to pass
|
|
9
|
+
* its own admission check first. They share only the box arithmetic below,
|
|
10
|
+
* which is why `centerOf` / `maxExtentOf` are exported.
|
|
11
|
+
*
|
|
12
|
+
* Pure by construction — every function here reads `CameraInternalState` and
|
|
13
|
+
* returns a target, and none of them writes to it or touches the tween. That
|
|
14
|
+
* is what keeps this module free of a cycle back into `camera-animation.ts`,
|
|
15
|
+
* which is the one that applies the results. Same shape as
|
|
16
|
+
* `camera-fit-policy.ts`, which is already pure-picker + facade-applier.
|
|
17
|
+
*
|
|
18
|
+
* Its failure story is not the tween's. The tween latches a non-finite
|
|
19
|
+
* *gesture delta* into a velocity that never decays (#2441/#2473); framing
|
|
20
|
+
* takes a non-finite *box* — geometry-derived, and every AABB accumulator in
|
|
21
|
+
* this package hands out an inverted `+Inf/-Inf` sentinel for a mesh with no
|
|
22
|
+
* finite vertices — and writes it into position, target AND `orthoSize`, which
|
|
23
|
+
* `getOrthoSize()` then persists into a saved viewpoint (#2461).
|
|
24
|
+
*
|
|
25
|
+
* **Guard placement.** Each input is validated exactly once, here, and a
|
|
26
|
+
* rejection is `null`. Callers null-check and do nothing else: a second,
|
|
27
|
+
* defensive copy of the same guard in the caller would mean neither copy is
|
|
28
|
+
* load-bearing on its own, and the mutation tests that pin these guards would
|
|
29
|
+
* go quiet while still looking green.
|
|
30
|
+
*/
|
|
31
|
+
import type { Vec3 } from './types.js';
|
|
32
|
+
import type { CameraInternalState } from './camera-state.js';
|
|
33
|
+
/** A pose to animate to. `orthoSize` is undefined when the fit should not touch zoom. */
|
|
34
|
+
export interface FramingTarget {
|
|
35
|
+
position: Vec3;
|
|
36
|
+
target: Vec3;
|
|
37
|
+
orthoSize?: number;
|
|
38
|
+
}
|
|
39
|
+
/** As {@link FramingTarget}, plus the fit distance `zoomExtent` reports onward. */
|
|
40
|
+
export interface ZoomExtentTarget extends FramingTarget {
|
|
41
|
+
fitDistance: number;
|
|
42
|
+
}
|
|
43
|
+
/** An axis-aligned box. */
|
|
44
|
+
export interface FramingBounds {
|
|
45
|
+
min: Vec3;
|
|
46
|
+
max: Vec3;
|
|
47
|
+
}
|
|
48
|
+
/** Centre of a box. */
|
|
49
|
+
export declare function centerOf(min: Vec3, max: Vec3): Vec3;
|
|
50
|
+
/** Largest edge length of a box. */
|
|
51
|
+
export declare function maxExtentOf(min: Vec3, max: Vec3): number;
|
|
52
|
+
/**
|
|
53
|
+
* Distance at which a box of `maxSize` fits the perspective frustum, with
|
|
54
|
+
* `padding` slack. Shared with `camera-preset-view.ts`, which fits the same
|
|
55
|
+
* way from a dictated direction.
|
|
56
|
+
*
|
|
57
|
+
* Fits **both** screen axes. The vertical half-angle is `fov / 2`; the
|
|
58
|
+
* horizontal one is `atan(tan(fov / 2) * aspect)`, so on a portrait viewport
|
|
59
|
+
* (`aspect < 1`) the horizontal field is the *narrower* of the two and a
|
|
60
|
+
* distance derived from the vertical field alone leaves the box overflowing
|
|
61
|
+
* left and right — the fit silently clips the very thing it was asked to
|
|
62
|
+
* frame. Landscape is unaffected: for `aspect >= 1` the vertical field is
|
|
63
|
+
* already the binding one and this returns the vertical distance exactly,
|
|
64
|
+
* bit for bit.
|
|
65
|
+
*
|
|
66
|
+
* `orthoSizeFor` above has divided by `aspect` for the same reason since it
|
|
67
|
+
* was written; this is the perspective half of the same rule, which had been
|
|
68
|
+
* missing (the three fit-distance formulas in this package all predate it).
|
|
69
|
+
*/
|
|
70
|
+
export declare function fitDistanceFor(state: CameraInternalState, maxSize: number, padding: number): number;
|
|
71
|
+
/**
|
|
72
|
+
* Frame/center view on a point (keeps current distance and direction).
|
|
73
|
+
* Standard CAD "Frame Selection" behavior.
|
|
74
|
+
*/
|
|
75
|
+
export declare function framePointTarget(state: CameraInternalState, point: Vec3): FramingTarget | null;
|
|
76
|
+
/**
|
|
77
|
+
* Frame selection - zoom to fit bounds while keeping current view direction.
|
|
78
|
+
* This is what "Frame Selection" should do - zoom to fill screen.
|
|
79
|
+
*/
|
|
80
|
+
export declare function frameBoundsTarget(state: CameraInternalState, min: Vec3, max: Vec3): FramingTarget | null;
|
|
81
|
+
/**
|
|
82
|
+
* Zoom to extents: same fit, wider padding, and the current view direction is
|
|
83
|
+
* taken from the pose rather than the view matrix.
|
|
84
|
+
*/
|
|
85
|
+
export declare function zoomExtentTarget(state: CameraInternalState, min: Vec3, max: Vec3): ZoomExtentTarget | null;
|
|
86
|
+
//# sourceMappingURL=camera-framing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-framing.d.ts","sourceRoot":"","sources":["../src/camera-framing.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,IAAI,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2BAA2B;AAC3B,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,IAAI,CAAC;CACX;AAED,uBAAuB;AACvB,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI,CAMnD;AAED,oCAAoC;AACpC,wBAAgB,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAExD;AAYD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAQnG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,IAAI,GAAG,aAAa,GAAG,IAAI,CAuB9F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,GAAG,IAAI,CA4ExG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,gBAAgB,GAAG,IAAI,CAoE1G"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { areFiniteNumbers, isUsableBounds, isUsableDistance } from './camera-guards.js';
|
|
5
|
+
/** Centre of a box. */
|
|
6
|
+
export function centerOf(min, max) {
|
|
7
|
+
return {
|
|
8
|
+
x: (min.x + max.x) / 2,
|
|
9
|
+
y: (min.y + max.y) / 2,
|
|
10
|
+
z: (min.z + max.z) / 2,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Largest edge length of a box. */
|
|
14
|
+
export function maxExtentOf(min, max) {
|
|
15
|
+
return Math.max(max.x - min.x, max.y - min.y, max.z - min.z);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Orthographic half-height that shows a box of `maxSize` with `padding` slack,
|
|
19
|
+
* or `undefined` in perspective mode where `orthoSize` is not in play.
|
|
20
|
+
*/
|
|
21
|
+
function orthoSizeFor(state, maxSize, padding) {
|
|
22
|
+
if (state.projectionMode !== 'orthographic')
|
|
23
|
+
return undefined;
|
|
24
|
+
const aspect = state.camera.aspect || 1;
|
|
25
|
+
return Math.max(0.01, maxSize / 2, maxSize / 2 / aspect) * padding;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Distance at which a box of `maxSize` fits the perspective frustum, with
|
|
29
|
+
* `padding` slack. Shared with `camera-preset-view.ts`, which fits the same
|
|
30
|
+
* way from a dictated direction.
|
|
31
|
+
*
|
|
32
|
+
* Fits **both** screen axes. The vertical half-angle is `fov / 2`; the
|
|
33
|
+
* horizontal one is `atan(tan(fov / 2) * aspect)`, so on a portrait viewport
|
|
34
|
+
* (`aspect < 1`) the horizontal field is the *narrower* of the two and a
|
|
35
|
+
* distance derived from the vertical field alone leaves the box overflowing
|
|
36
|
+
* left and right — the fit silently clips the very thing it was asked to
|
|
37
|
+
* frame. Landscape is unaffected: for `aspect >= 1` the vertical field is
|
|
38
|
+
* already the binding one and this returns the vertical distance exactly,
|
|
39
|
+
* bit for bit.
|
|
40
|
+
*
|
|
41
|
+
* `orthoSizeFor` above has divided by `aspect` for the same reason since it
|
|
42
|
+
* was written; this is the perspective half of the same rule, which had been
|
|
43
|
+
* missing (the three fit-distance formulas in this package all predate it).
|
|
44
|
+
*/
|
|
45
|
+
export function fitDistanceFor(state, maxSize, padding) {
|
|
46
|
+
const fovFactor = Math.tan(state.camera.fov / 2);
|
|
47
|
+
const vertical = (maxSize / 2) / fovFactor * padding;
|
|
48
|
+
const aspect = state.camera.aspect;
|
|
49
|
+
// `setAspect` is the only writer and already rejects a non-positive or
|
|
50
|
+
// non-finite ratio, so this only ever narrows the *portrait* case.
|
|
51
|
+
if (!Number.isFinite(aspect) || aspect <= 0 || aspect >= 1)
|
|
52
|
+
return vertical;
|
|
53
|
+
return vertical / aspect;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Frame/center view on a point (keeps current distance and direction).
|
|
57
|
+
* Standard CAD "Frame Selection" behavior.
|
|
58
|
+
*/
|
|
59
|
+
export function framePointTarget(state, point) {
|
|
60
|
+
// The point is added to the current offset and animated into both
|
|
61
|
+
// `position` and `target`, so a non-finite one destroys the pose. It is
|
|
62
|
+
// externally derived: `zoomToTopic` frames a BCF marker position, which is
|
|
63
|
+
// computed from a file-supplied viewpoint direction (#2461/#2466).
|
|
64
|
+
if (!areFiniteNumbers(point.x, point.y, point.z))
|
|
65
|
+
return null;
|
|
66
|
+
// Keep current viewing direction and distance
|
|
67
|
+
const dir = {
|
|
68
|
+
x: state.camera.position.x - state.camera.target.x,
|
|
69
|
+
y: state.camera.position.y - state.camera.target.y,
|
|
70
|
+
z: state.camera.position.z - state.camera.target.z,
|
|
71
|
+
};
|
|
72
|
+
// New position: point + current offset
|
|
73
|
+
return {
|
|
74
|
+
position: {
|
|
75
|
+
x: point.x + dir.x,
|
|
76
|
+
y: point.y + dir.y,
|
|
77
|
+
z: point.z + dir.z,
|
|
78
|
+
},
|
|
79
|
+
target: point,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Frame selection - zoom to fit bounds while keeping current view direction.
|
|
84
|
+
* This is what "Frame Selection" should do - zoom to fill screen.
|
|
85
|
+
*/
|
|
86
|
+
export function frameBoundsTarget(state, min, max) {
|
|
87
|
+
// Bounds are the upstream input #2450 stopped short of (#2461). They are
|
|
88
|
+
// not caller-authored constants: they come from geometry, and every AABB
|
|
89
|
+
// accumulator in this package starts from `min = +Infinity, max =
|
|
90
|
+
// -Infinity` and only narrows on a comparison — which is false for a
|
|
91
|
+
// non-finite vertex — so a mesh with no finite vertices hands out that
|
|
92
|
+
// inverted sentinel as if it were a real box. `Math.max` picking the
|
|
93
|
+
// largest extent is NaN-transparent, so it reaches `position`, `target`
|
|
94
|
+
// AND `orthoSize`, and `getOrthoSize()` is what a saved viewpoint persists.
|
|
95
|
+
if (!isUsableBounds(min, max))
|
|
96
|
+
return null;
|
|
97
|
+
const center = centerOf(min, max);
|
|
98
|
+
const maxSize = maxExtentOf(min, max);
|
|
99
|
+
if (maxSize < 1e-6) {
|
|
100
|
+
// Very small or zero size - just center on it
|
|
101
|
+
return framePointTarget(state, center);
|
|
102
|
+
}
|
|
103
|
+
// Calculate required distance based on FOV to fit bounds
|
|
104
|
+
const distance = fitDistanceFor(state, maxSize, 1.2); // 1.2x padding for nice framing
|
|
105
|
+
// Get current viewing direction from view matrix (more reliable than position-target)
|
|
106
|
+
// View matrix forward is -Z axis in view space
|
|
107
|
+
const viewMatrix = state.viewMatrix.m;
|
|
108
|
+
// Extract forward direction from view matrix (negative Z column, normalized)
|
|
109
|
+
let dir = {
|
|
110
|
+
x: -viewMatrix[8], // -m[2][0] (forward X)
|
|
111
|
+
y: -viewMatrix[9], // -m[2][1] (forward Y)
|
|
112
|
+
z: -viewMatrix[10], // -m[2][2] (forward Z)
|
|
113
|
+
};
|
|
114
|
+
const dirLen = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
115
|
+
// Normalize direction
|
|
116
|
+
if (dirLen > 1e-6) {
|
|
117
|
+
dir.x /= dirLen;
|
|
118
|
+
dir.y /= dirLen;
|
|
119
|
+
dir.z /= dirLen;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Fallback: use position-target if view matrix is invalid
|
|
123
|
+
dir = {
|
|
124
|
+
x: state.camera.position.x - state.camera.target.x,
|
|
125
|
+
y: state.camera.position.y - state.camera.target.y,
|
|
126
|
+
z: state.camera.position.z - state.camera.target.z,
|
|
127
|
+
};
|
|
128
|
+
const fallbackLen = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
129
|
+
// Finiteness, not just magnitude — see `zoomExtentTarget`. `dirLen` above
|
|
130
|
+
// is safe (`MathUtils.lookAt` guarantees a finite view matrix), but this
|
|
131
|
+
// fallback reads the raw pose, where an overflowed coordinate is reachable.
|
|
132
|
+
if (isUsableDistance(fallbackLen, 1e-6)) {
|
|
133
|
+
dir.x /= fallbackLen;
|
|
134
|
+
dir.y /= fallbackLen;
|
|
135
|
+
dir.z /= fallbackLen;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Last resort: southeast isometric
|
|
139
|
+
dir.x = 0.6;
|
|
140
|
+
dir.y = 0.5;
|
|
141
|
+
dir.z = 0.6;
|
|
142
|
+
const len = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
143
|
+
dir.x /= len;
|
|
144
|
+
dir.y /= len;
|
|
145
|
+
dir.z /= len;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
// New position: center + direction * distance
|
|
150
|
+
position: {
|
|
151
|
+
x: center.x + dir.x * distance,
|
|
152
|
+
y: center.y + dir.y * distance,
|
|
153
|
+
z: center.z + dir.z * distance,
|
|
154
|
+
},
|
|
155
|
+
target: center,
|
|
156
|
+
// Calculate orthoSize for orthographic mode so zoom level resets properly
|
|
157
|
+
orthoSize: orthoSizeFor(state, maxSize, 1.2),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Zoom to extents: same fit, wider padding, and the current view direction is
|
|
162
|
+
* taken from the pose rather than the view matrix.
|
|
163
|
+
*/
|
|
164
|
+
export function zoomExtentTarget(state, min, max) {
|
|
165
|
+
// Same input class and same reasoning as `frameBoundsTarget` (#2461).
|
|
166
|
+
if (!isUsableBounds(min, max))
|
|
167
|
+
return null;
|
|
168
|
+
const center = centerOf(min, max);
|
|
169
|
+
const maxSize = maxExtentOf(min, max);
|
|
170
|
+
// Keep current viewing direction
|
|
171
|
+
const dir = {
|
|
172
|
+
x: state.camera.position.x - state.camera.target.x,
|
|
173
|
+
y: state.camera.position.y - state.camera.target.y,
|
|
174
|
+
z: state.camera.position.z - state.camera.target.z,
|
|
175
|
+
};
|
|
176
|
+
const currentDistance = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
177
|
+
// The degenerate box `frameBoundsTarget` has always special-cased and this
|
|
178
|
+
// one did not. `isUsableBounds` deliberately admits `max === min` (a flat
|
|
179
|
+
// wall, a single picked point, a one-element model), and for such a box the
|
|
180
|
+
// fit distance below is *zero* — so `position` is written equal to `target`,
|
|
181
|
+
// a pose that carries no view direction at all and that `MathUtils.lookAt`
|
|
182
|
+
// then has to substitute a whole basis for. Centre on the point at the
|
|
183
|
+
// distance the camera already has, exactly as `frameBounds` does, and report
|
|
184
|
+
// that distance rather than zero.
|
|
185
|
+
//
|
|
186
|
+
// Only when the current offset is usable. When it is not, the pose already
|
|
187
|
+
// has position === target (or is non-finite), so there is no offset to keep
|
|
188
|
+
// and nothing is gained by keeping it; fall through to the isometric
|
|
189
|
+
// fallback below, which is what this path did before.
|
|
190
|
+
if (maxSize < 1e-6 && isUsableDistance(currentDistance, 1e-10)) {
|
|
191
|
+
const framed = framePointTarget(state, center);
|
|
192
|
+
if (framed)
|
|
193
|
+
return { ...framed, fitDistance: currentDistance };
|
|
194
|
+
}
|
|
195
|
+
// Calculate required distance based on FOV
|
|
196
|
+
const distance = fitDistanceFor(state, maxSize, 1.5); // 1.5x for padding
|
|
197
|
+
// Normalize direction. Finiteness, not just magnitude: `len > 1e-10` is
|
|
198
|
+
// *true* for Infinity, and `Infinity / Infinity` is NaN, so a pose whose
|
|
199
|
+
// position has overflowed walks past a bare floor and writes a NaN position
|
|
200
|
+
// from a perfectly usable box (#2441).
|
|
201
|
+
if (isUsableDistance(currentDistance, 1e-10)) {
|
|
202
|
+
dir.x /= currentDistance;
|
|
203
|
+
dir.y /= currentDistance;
|
|
204
|
+
dir.z /= currentDistance;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// Fallback direction
|
|
208
|
+
dir.x = 0.6;
|
|
209
|
+
dir.y = 0.5;
|
|
210
|
+
dir.z = 0.6;
|
|
211
|
+
const len = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
212
|
+
dir.x /= len;
|
|
213
|
+
dir.y /= len;
|
|
214
|
+
dir.z /= len;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
// New position: center + direction * distance
|
|
218
|
+
position: {
|
|
219
|
+
x: center.x + dir.x * distance,
|
|
220
|
+
y: center.y + dir.y * distance,
|
|
221
|
+
z: center.z + dir.z * distance,
|
|
222
|
+
},
|
|
223
|
+
target: center,
|
|
224
|
+
// Calculate orthoSize for orthographic mode so zoom level resets properly
|
|
225
|
+
orthoSize: orthoSizeFor(state, maxSize, 1.5),
|
|
226
|
+
// The caller hands this to `CameraProjection.updateNearFarPlanes`.
|
|
227
|
+
fitDistance: distance,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=camera-framing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-framing.js","sourceRoot":"","sources":["../src/camera-framing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finiteness predicates shared by every camera sub-system.
|
|
3
|
+
*
|
|
4
|
+
* They live in one module rather than next to their first caller because the
|
|
5
|
+
* same three mistakes keep being made independently in `camera.ts`,
|
|
6
|
+
* `camera-controls.ts`, `camera-animation.ts` and `camera-projection.ts`:
|
|
7
|
+
*
|
|
8
|
+
* - **A magnitude comparison is not a finiteness check.** Every comparison
|
|
9
|
+
* involving NaN is false, so `dist < eps` does not reject NaN — it waves it
|
|
10
|
+
* through the guard that looks like it is rejecting it (#2441).
|
|
11
|
+
* - **A lower-bound floor rejects NaN but admits Infinity.** `len > 1e-10` is
|
|
12
|
+
* false for NaN and *true* for Infinity, and scaling infinite components by
|
|
13
|
+
* `1 / Infinity` is `Infinity * 0` — NaN. A guard written as
|
|
14
|
+
* `!Number.isNaN(...)` would pass every existing test and still be wrong.
|
|
15
|
+
* - **`Math.max`/`Math.min` are NaN-transparent.** `Math.max(0.01, NaN)` is
|
|
16
|
+
* `NaN`, not the floor, so a clamp forwards a malformed value rather than
|
|
17
|
+
* absorbing it.
|
|
18
|
+
*
|
|
19
|
+
* Every predicate here therefore keys on `Number.isFinite`, which is the only
|
|
20
|
+
* test that rejects NaN and both infinities together.
|
|
21
|
+
*/
|
|
22
|
+
import type { Vec3 } from './types.js';
|
|
23
|
+
/**
|
|
24
|
+
* Smallest orthographic half-height the projection matrix is ever built from.
|
|
25
|
+
* Matches the floor the orthographic zoom clamps to in `CameraControls`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MIN_ORTHO_SIZE = 0.01;
|
|
28
|
+
/** Default orthographic half-height, in world units, and the neutral fallback. */
|
|
29
|
+
export declare const DEFAULT_ORTHO_SIZE = 50;
|
|
30
|
+
/**
|
|
31
|
+
* Is `dist` a usable camera-to-target radius — finite, and at least `min` away
|
|
32
|
+
* from zero so dividing by it means something?
|
|
33
|
+
*
|
|
34
|
+
* The navigation gestures all opened with a bare `dist < eps` early return.
|
|
35
|
+
* That is a magnitude test, not a finiteness test, so a malformed pose walked
|
|
36
|
+
* straight past it (#2441). Past the guard, the gesture writes its result into
|
|
37
|
+
* `camera.position` *and* `camera.target`, so a single non-finite coordinate
|
|
38
|
+
* (a BCF viewpoint restored from a file reaches the public setters
|
|
39
|
+
* unvalidated) spreads across all six on the first drag.
|
|
40
|
+
*
|
|
41
|
+
* Callers keep their own fallback: there is no single substitute distance that
|
|
42
|
+
* is right for all of them, which is also why `Camera.getDistance()` reports
|
|
43
|
+
* the pose verbatim rather than sanitizing it centrally.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isUsableDistance(dist: number, min: number): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Is `up` a usable screen-up reference — every component finite?
|
|
48
|
+
*
|
|
49
|
+
* `up` is the third vector of the pose, and it reaches the camera exactly the
|
|
50
|
+
* way `position` and `target` do: BCF stores `CameraUpVector`, `bcfToViewerCoords`
|
|
51
|
+
* only swaps axes, and `applyViewpoint` writes the result straight through
|
|
52
|
+
* (`animateToWithUp` on the animated path — which bypasses `setUp` entirely —
|
|
53
|
+
* or `setUp` on the instant one).
|
|
54
|
+
*
|
|
55
|
+
* Finiteness only, deliberately no magnitude floor. `normalize` already absorbs
|
|
56
|
+
* a zero-length or degenerate `up` by returning `{0,0,0}`, and a *short* `up` is
|
|
57
|
+
* a valid input whose direction is perfectly well defined — `MathUtils.lookAt`
|
|
58
|
+
* documents the same thing and keys its own degeneracy test on the angle rather
|
|
59
|
+
* than a length for exactly this reason. A floor here would silently drop the
|
|
60
|
+
* cursor anchor for poses that navigate correctly today.
|
|
61
|
+
*
|
|
62
|
+
* The floor `normalize` used to have was a *lower* bound, which is what left
|
|
63
|
+
* the gap: `len > 1e-10` is false for NaN (so a NaN `up` degraded safely to
|
|
64
|
+
* the zero vector) but **true for Infinity**, and scaling infinite components
|
|
65
|
+
* by `1 / Infinity` is `Infinity * 0` — NaN. Measured, not reasoned: `up`
|
|
66
|
+
* `(Infinity, 1, 0)` on a finite pose came back from one cursor-anchored zoom
|
|
67
|
+
* with position AND target `(NaN, NaN, NaN)`, in both projection modes (#2441).
|
|
68
|
+
* Both copies of `normalize` now key that floor on `Number.isFinite` too
|
|
69
|
+
* (#2479), so an unusable `up` reaches the same zero vector by either route —
|
|
70
|
+
* this predicate says so at the top of the gesture rather than relying on a
|
|
71
|
+
* helper's degradation three lines in, and it is what stops the *pose* being
|
|
72
|
+
* rewritten from a basis that carries no orientation.
|
|
73
|
+
*
|
|
74
|
+
* As with {@link isUsableDistance}, the caller keeps its own fallback and the
|
|
75
|
+
* pose is left exactly as it was found — `getUp()` still reports the malformed
|
|
76
|
+
* vector, so a restored viewpoint is not silently rewritten on its way back out.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isUsableUp(up: Vec3): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Clamp an orthographic half-height, rejecting a non-finite one.
|
|
81
|
+
*
|
|
82
|
+
* `Math.max`/`Math.min` are NaN-transparent, so a clamp alone does not stop a
|
|
83
|
+
* malformed value reaching the projection matrix (#2441). Returns `null` when
|
|
84
|
+
* there is no usable size, so the caller can keep the value it already had
|
|
85
|
+
* rather than substitute an arbitrary one.
|
|
86
|
+
*
|
|
87
|
+
* The `Infinity` rejection is not theoretical: `orthoSize` is scaled in place
|
|
88
|
+
* by the orthographic zoom, so a legitimately huge but finite half-height
|
|
89
|
+
* overflows to `Infinity` after enough zoom-out notches, and
|
|
90
|
+
* `Math.max(MIN_ORTHO_SIZE, Infinity)` is `Infinity`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function usableOrthoSize(size: number): number | null;
|
|
93
|
+
/**
|
|
94
|
+
* Are all of `values` finite?
|
|
95
|
+
*
|
|
96
|
+
* The gesture *arguments* — wheel deltas, pointer deltas, walk axes — are a
|
|
97
|
+
* different input class from the pose the gestures already guard (#2473), and
|
|
98
|
+
* they reach the same arithmetic. Measured on the pre-guard code: `orbit(NaN,
|
|
99
|
+
* 0)`, `pan(NaN, 0)`, `zoom(NaN)` and `moveFirstPerson(NaN, 0, 0)` each turned
|
|
100
|
+
* a perfectly finite pose into a non-finite one in a single call, and
|
|
101
|
+
* `orbit(Infinity, 0)` / `pan(Infinity, 0)` / `moveFirstPerson(Infinity, 0, 0)`
|
|
102
|
+
* did the same — the two do **not** behave alike everywhere, so both must be
|
|
103
|
+
* tested. (`zoom` is the exception: its delta is clamped by
|
|
104
|
+
* `Math.min(|delta| * s, MAX_ZOOM_DELTA)`, which absorbs `Infinity` but not
|
|
105
|
+
* `NaN`.)
|
|
106
|
+
*/
|
|
107
|
+
export declare function areFiniteNumbers(...values: number[]): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Is this a usable cursor anchor — a finite cursor position inside a viewport
|
|
110
|
+
* with a real extent?
|
|
111
|
+
*
|
|
112
|
+
* The cursor-anchored zoom divides by the canvas dimensions, so they need
|
|
113
|
+
* `> 0` and not merely "truthy". Truthiness already rejects `0` and `NaN`
|
|
114
|
+
* (both falsy) which is why a collapsed canvas never reached the division, but
|
|
115
|
+
* it accepts `Infinity` and a negative width — the latter mirrors the anchor
|
|
116
|
+
* and drifts the target the wrong way. `Number.isFinite(w) && w > 0` is the
|
|
117
|
+
* check that means what the division needs.
|
|
118
|
+
*/
|
|
119
|
+
export declare function isUsableCursorAnchor(cursorX: number, cursorY: number, viewportWidth: number, viewportHeight: number): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Is this AABB something the camera can be fitted to?
|
|
122
|
+
*
|
|
123
|
+
* Two rejections, and they are different failures:
|
|
124
|
+
*
|
|
125
|
+
* - **Non-finite corners.** Fitting computes `center = (min + max) / 2` and
|
|
126
|
+
* `size = max - min`, and writes both into the pose. `Math.max` picking the
|
|
127
|
+
* largest extent is NaN-transparent, so one bad coordinate reaches
|
|
128
|
+
* `camera.position`, `camera.target` *and* (in orthographic mode)
|
|
129
|
+
* `orthoSize`, which `getOrthoSize()` then hands to a saved viewpoint —
|
|
130
|
+
* persisting the damage past the session (#2461).
|
|
131
|
+
* - **An inverted box** (`max < min` on any axis). That is the empty-AABB
|
|
132
|
+
* sentinel every accumulator in this package starts from (`min = +Infinity,
|
|
133
|
+
* max = -Infinity`) leaking out as if it were a real box, which is what a
|
|
134
|
+
* mesh with no finite vertices produces. Its centre is `NaN` and its size
|
|
135
|
+
* is negative; there is nothing to frame.
|
|
136
|
+
*
|
|
137
|
+
* A *degenerate* box (`max === min` on some or all axes — a flat wall, a
|
|
138
|
+
* single point) is explicitly **not** rejected: it is a legitimate thing to
|
|
139
|
+
* frame and `frameBounds` already centres on it.
|
|
140
|
+
*
|
|
141
|
+
* The answer to an unusable box is to reject the fit and leave the pose alone,
|
|
142
|
+
* not to clamp: there is no meaningful substitute for an infinite or inverted
|
|
143
|
+
* extent, and no previous bounds to fall back to at these entry points, which
|
|
144
|
+
* are stateless. Keeping the pose the user already had is the same policy
|
|
145
|
+
* `setAspect`, `setFOV` and `setOrthoSize` settled on (#2441/#2450).
|
|
146
|
+
*/
|
|
147
|
+
export declare function isUsableBounds(min: Vec3, max: Vec3): boolean;
|
|
148
|
+
//# sourceMappingURL=camera-guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-guards.d.ts","sourceRoot":"","sources":["../src/camera-guards.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAE5C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAK7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAMT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAO5D"}
|