@flighthq/camera 0.3.0 → 0.3.1-next.1050.9114695
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.d.ts +1 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +20 -0
- package/dist/camera.js.map +1 -1
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -1
- package/dist/enableCameraGuards.d.ts +4 -0
- package/dist/enableCameraGuards.d.ts.map +1 -0
- package/dist/enableCameraGuards.js +45 -0
- package/dist/enableCameraGuards.js.map +1 -0
- package/dist/explainCamera3DView.d.ts +3 -0
- package/dist/explainCamera3DView.d.ts.map +1 -0
- package/dist/explainCamera3DView.js +35 -0
- package/dist/explainCamera3DView.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/visibleBounds.d.ts +1 -0
- package/dist/visibleBounds.d.ts.map +1 -1
- package/dist/visibleBounds.js +30 -1
- package/dist/visibleBounds.js.map +1 -1
- package/package.json +5 -4
- package/src/camera.test.ts +20 -0
- package/src/enableCameraGuards.test.ts +94 -0
- package/src/explainCamera3DView.test.ts +49 -0
- package/src/visibleBounds.test.ts +35 -3
package/dist/camera.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare function getCamera3DInverseViewProjectionMatrix4(out: Matrix4Like
|
|
|
4
4
|
export declare function getCamera3DViewProjectionMatrix4(out: Matrix4Like, camera: Readonly<Camera3D>, aspect: number): void;
|
|
5
5
|
export declare function setCamera3DAspect(camera: Camera3D, aspect: number): void;
|
|
6
6
|
export declare function setCamera3DJitter(camera: Camera3D, x: number, y: number): void;
|
|
7
|
+
export declare function setCamera3DViewGuard(guard: ((camera: Readonly<Camera3D>) => void) | null): void;
|
|
7
8
|
export declare function setCamera3DViewMatrix4FromLookAt(camera: Camera3D, eye: Readonly<Vector3Like>, target: Readonly<Vector3Like>, up: Readonly<Vector3Like>): void;
|
|
8
9
|
export declare function setCamera3DViewMatrix4FromMatrix4(camera: Camera3D, view: Readonly<Matrix4Like>): void;
|
|
9
10
|
export declare function updateCamera3DInverseViewProjection(camera: Readonly<Camera3D>, aspect: number): boolean;
|
package/dist/camera.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAUpG,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,QAAQ,CASxE;AASD,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAC1B,MAAM,EAAE,MAAM,GACb,OAAO,CAGT;AAQD,wBAAgB,gCAAgC,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAInH;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOxE;AAID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAG9E;AAOD,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,QAAQ,EAChB,GAAG,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC1B,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC7B,EAAE,EAAE,QAAQ,CAAC,WAAW,CAAC,GACxB,IAAI,CAEN;
|
|
1
|
+
{"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAUpG,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,QAAQ,CASxE;AASD,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAC1B,MAAM,EAAE,MAAM,GACb,OAAO,CAGT;AAQD,wBAAgB,gCAAgC,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAInH;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOxE;AAID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAG9E;AAMD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE/F;AAOD,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,QAAQ,EAChB,GAAG,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC1B,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC7B,EAAE,EAAE,QAAQ,CAAC,WAAW,CAAC,GACxB,IAAI,CAEN;AAeD,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAGrG;AAMD,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIvG"}
|
package/dist/camera.js
CHANGED
|
@@ -59,6 +59,13 @@ export function setCamera3DJitter(camera, x, y) {
|
|
|
59
59
|
camera.jitter.x = x;
|
|
60
60
|
camera.jitter.y = y;
|
|
61
61
|
}
|
|
62
|
+
// The seam the guard layer installs into; `null` keeps this module free of message text and of any
|
|
63
|
+
// dependency on @flighthq/log. Installed here rather than at each consumer because the precondition is a
|
|
64
|
+
// fact about the CAMERA — reporting it once where the matrix enters beats one warning per consumer that
|
|
65
|
+
// happens to rely on it.
|
|
66
|
+
export function setCamera3DViewGuard(guard) {
|
|
67
|
+
camera3DViewGuard = guard;
|
|
68
|
+
}
|
|
62
69
|
// Builds the camera's world->view matrix in place from an eye position, a look-at target, and an
|
|
63
70
|
// up vector (right-handed look-at). This is the common path for positioning a camera without an
|
|
64
71
|
// explicit world transform.
|
|
@@ -69,8 +76,20 @@ export function setCamera3DViewMatrix4FromLookAt(camera, eye, target, up) {
|
|
|
69
76
|
}
|
|
70
77
|
// Copies a precomputed world->view matrix into the camera in place. Use this when the view matrix
|
|
71
78
|
// is derived elsewhere (for example, the inverse of a scene node's world transform).
|
|
79
|
+
//
|
|
80
|
+
// ★ PRECONDITION: `view` must be ORTHONORMAL — a rigid placement, position and orientation only. A view
|
|
81
|
+
// matrix is the inverse of a camera's world placement, and a placement does not scale; this is what the
|
|
82
|
+
// term means everywhere (Unity `worldToCameraMatrix`, Unreal's view matrix), not a Flight restriction.
|
|
83
|
+
// Reflections are FINE: an improper orthogonal matrix (det = -1, as a water or mirror camera produces)
|
|
84
|
+
// is still orthogonal, so Rᵀ is still R⁻¹ and every consumer that relies on the transpose stays exact.
|
|
85
|
+
// What is excluded is a SCALED view, which has no coherent meaning as a placement inverse.
|
|
86
|
+
//
|
|
87
|
+
// The precondition is not enforced here — checking it on every assignment would cost every correct
|
|
88
|
+
// caller for the benefit of an incorrect one. `explainCamera3DView` reports it on demand, and
|
|
89
|
+
// `enableCameraGuards` warns on it; consumers such as `getCamera3DPosition` simply rely on it.
|
|
72
90
|
export function setCamera3DViewMatrix4FromMatrix4(camera, view) {
|
|
73
91
|
camera.view.m.set(view.m);
|
|
92
|
+
camera3DViewGuard?.(camera);
|
|
74
93
|
}
|
|
75
94
|
// Refreshes the camera's required inverse-view-projection cache for the supplied viewport aspect.
|
|
76
95
|
// Returns true on success or false when projection×view is non-invertible, leaving the prior valid
|
|
@@ -100,4 +119,5 @@ function applyCamera3DProjectionJitter(out, x, y) {
|
|
|
100
119
|
const __scratchInverse = createMatrix4();
|
|
101
120
|
const __scratchProjection = createMatrix4();
|
|
102
121
|
const __scratchViewProjection = createMatrix4();
|
|
122
|
+
let camera3DViewGuard = null;
|
|
103
123
|
//# sourceMappingURL=camera.js.map
|
package/dist/camera.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"camera.js","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"camera.js","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './camera';
|
|
|
3
3
|
export * from './camera2d';
|
|
4
4
|
export * from './culling';
|
|
5
5
|
export * from './depth';
|
|
6
|
+
export * from './enableCameraGuards';
|
|
7
|
+
export * from './explainCamera3DView';
|
|
6
8
|
export * from './frustumCorners';
|
|
7
9
|
export * from './intersection';
|
|
8
10
|
export * from './parallax';
|
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC"}
|
package/dist/contract.js
CHANGED
|
@@ -3,6 +3,8 @@ export * from './camera';
|
|
|
3
3
|
export * from './camera2d';
|
|
4
4
|
export * from './culling';
|
|
5
5
|
export * from './depth';
|
|
6
|
+
export * from './enableCameraGuards';
|
|
7
|
+
export * from './explainCamera3DView';
|
|
6
8
|
export * from './frustumCorners';
|
|
7
9
|
export * from './intersection';
|
|
8
10
|
export * from './parallax';
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableCameraGuards.d.ts","sourceRoot":"","sources":["../src/enableCameraGuards.ts"],"names":[],"mappings":"AAQA,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAKD,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { logOnce } from '@flighthq/log/contract';
|
|
2
|
+
import { LogLevel } from '@flighthq/types/contract';
|
|
3
|
+
import { setCamera3DViewGuard } from './camera';
|
|
4
|
+
import { explainCamera3DView } from './explainCamera3DView';
|
|
5
|
+
import { setCamera2DVisibleBoundsGuard } from './visibleBounds';
|
|
6
|
+
export function areCameraGuardsEnabled() {
|
|
7
|
+
return cameraGuardsEnabled;
|
|
8
|
+
}
|
|
9
|
+
export function disableCameraGuards() {
|
|
10
|
+
setCamera3DViewGuard(null);
|
|
11
|
+
setCamera2DVisibleBoundsGuard(null);
|
|
12
|
+
cameraGuardsEnabled = false;
|
|
13
|
+
}
|
|
14
|
+
// Installs opt-in diagnostics for camera inputs that leave a computation with no answer. The core
|
|
15
|
+
// modules stay message-free, so an application that omits this one sheds both the text and
|
|
16
|
+
// @flighthq/log.
|
|
17
|
+
export function enableCameraGuards() {
|
|
18
|
+
setCamera3DViewGuard(warnOnNonOrthonormalCamera3DView);
|
|
19
|
+
setCamera2DVisibleBoundsGuard(warnOnDegenerateCamera2DVisibleBounds);
|
|
20
|
+
cameraGuardsEnabled = true;
|
|
21
|
+
}
|
|
22
|
+
// The unbounded rectangle is a deliberate fail-toward-drawing, not a silent success: nothing is culled,
|
|
23
|
+
// so the frame is correct but pays for every object. That is a performance cliff with no visible
|
|
24
|
+
// symptom, which is exactly the case a guard exists to name.
|
|
25
|
+
function warnOnDegenerateCamera2DVisibleBounds(camera) {
|
|
26
|
+
logOnce(`camera:degenerate-visible-bounds:${camera.zoom}`, LogLevel.Warn, {
|
|
27
|
+
message: 'getCamera2DVisibleBounds: the view matrix has no inverse, so the visible rectangle is unbounded and nothing is culled — a zoom of 0 is the usual cause; set a non-zero zoom.',
|
|
28
|
+
zoom: camera.zoom,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Names the CAUSE, not any one consequence: several consumers rely on the orthonormality precondition
|
|
32
|
+
// and warning once per consumer would be noise. A caller that wants to connect this to a specific wrong
|
|
33
|
+
// answer has `explainCamera3DView` for the numbers.
|
|
34
|
+
function warnOnNonOrthonormalCamera3DView(camera) {
|
|
35
|
+
const explanation = explainCamera3DView(camera);
|
|
36
|
+
if (explanation.isOrthonormal)
|
|
37
|
+
return;
|
|
38
|
+
logOnce('camera:non-orthonormal-view', LogLevel.Warn, {
|
|
39
|
+
message: 'setCamera3DViewMatrix4FromMatrix4: the view matrix is not orthonormal, which its consumers rely on — a scaled matrix is the usual cause. Reflections are fine; scale is not. Call explainCamera3DView(camera) for the measured deviations.',
|
|
40
|
+
scaleDeviation: explanation.scaleDeviation,
|
|
41
|
+
shearDeviation: explanation.shearDeviation,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
let cameraGuardsEnabled = false;
|
|
45
|
+
//# sourceMappingURL=enableCameraGuards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableCameraGuards.js","sourceRoot":"","sources":["../src/enableCameraGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAEhE,MAAM,UAAU,sBAAsB;IACpC,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC3B,6BAA6B,CAAC,IAAI,CAAC,CAAC;IACpC,mBAAmB,GAAG,KAAK,CAAC;AAC9B,CAAC;AAED,kGAAkG;AAClG,2FAA2F;AAC3F,iBAAiB;AACjB,MAAM,UAAU,kBAAkB;IAChC,oBAAoB,CAAC,gCAAgC,CAAC,CAAC;IACvD,6BAA6B,CAAC,qCAAqC,CAAC,CAAC;IACrE,mBAAmB,GAAG,IAAI,CAAC;AAC7B,CAAC;AAED,wGAAwG;AACxG,iGAAiG;AACjG,6DAA6D;AAC7D,SAAS,qCAAqC,CAAC,MAA0B;IACvE,OAAO,CAAC,oCAAoC,MAAM,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,IAAI,EAAE;QACxE,OAAO,EACL,8KAA8K;QAChL,IAAI,EAAE,MAAM,CAAC,IAAI;KAClB,CAAC,CAAC;AACL,CAAC;AAED,sGAAsG;AACtG,wGAAwG;AACxG,oDAAoD;AACpD,SAAS,gCAAgC,CAAC,MAA0B;IAClE,MAAM,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,WAAW,CAAC,aAAa;QAAE,OAAO;IACtC,OAAO,CAAC,6BAA6B,EAAE,QAAQ,CAAC,IAAI,EAAE;QACpD,OAAO,EACL,4OAA4O;QAC9O,cAAc,EAAE,WAAW,CAAC,cAAc;QAC1C,cAAc,EAAE,WAAW,CAAC,cAAc;KAC3C,CAAC,CAAC;AACL,CAAC;AAED,IAAI,mBAAmB,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explainCamera3DView.d.ts","sourceRoot":"","sources":["../src/explainCamera3DView.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAYlF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,uBAAuB,CA6BvF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Reports whether a camera's view matrix satisfies the orthonormality precondition its consumers rely
|
|
2
|
+
// on, as PLAIN DATA rather than a verdict. Callers get the measured deviations and decide for
|
|
3
|
+
// themselves what that means for the answer they got — `getCamera3DPosition`, for instance, is exact
|
|
4
|
+
// for any orthogonal basis and merely wrong for a scaled one, which is a judgement about their use, not
|
|
5
|
+
// about the matrix.
|
|
6
|
+
//
|
|
7
|
+
// Reflections pass: `determinant` is reported so a caller can tell a mirror camera (-1) from a rigid one
|
|
8
|
+
// (+1), but neither is a violation — an improper orthogonal matrix still satisfies Rᵀ = R⁻¹.
|
|
9
|
+
//
|
|
10
|
+
// Shakeable: nothing in the render path calls this, so an application that never asks sheds it entirely.
|
|
11
|
+
export function explainCamera3DView(camera) {
|
|
12
|
+
const m = camera.view.m;
|
|
13
|
+
const x = { x: m[0], y: m[1], z: m[2] };
|
|
14
|
+
const y = { x: m[4], y: m[5], z: m[6] };
|
|
15
|
+
const z = { x: m[8], y: m[9], z: m[10] };
|
|
16
|
+
const lengthX = Math.hypot(x.x, x.y, x.z);
|
|
17
|
+
const lengthY = Math.hypot(y.x, y.y, y.z);
|
|
18
|
+
const lengthZ = Math.hypot(z.x, z.y, z.z);
|
|
19
|
+
const determinant = m[0] * (m[5] * m[10] - m[6] * m[9]) - m[4] * (m[1] * m[10] - m[2] * m[9]) + m[8] * (m[1] * m[6] - m[2] * m[5]);
|
|
20
|
+
// Largest absolute deviation from unit length across the three basis vectors, and from zero across
|
|
21
|
+
// the three pairwise dots. One number each, so a caller can threshold without redoing the algebra.
|
|
22
|
+
const scaleDeviation = Math.max(Math.abs(lengthX - 1), Math.abs(lengthY - 1), Math.abs(lengthZ - 1));
|
|
23
|
+
const shearDeviation = Math.max(Math.abs(x.x * y.x + x.y * y.y + x.z * y.z), Math.abs(x.x * z.x + x.y * z.y + x.z * z.z), Math.abs(y.x * z.x + y.y * z.y + y.z * z.z));
|
|
24
|
+
return {
|
|
25
|
+
determinant,
|
|
26
|
+
isOrthonormal: scaleDeviation <= ORTHONORMAL_TOLERANCE && shearDeviation <= ORTHONORMAL_TOLERANCE,
|
|
27
|
+
isReflection: determinant < 0,
|
|
28
|
+
scaleDeviation,
|
|
29
|
+
shearDeviation,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Loose enough to pass a matrix that has been through a decompose/recompose round trip in float32,
|
|
33
|
+
// tight enough that a deliberate scale of even 1% is reported.
|
|
34
|
+
const ORTHONORMAL_TOLERANCE = 1e-3;
|
|
35
|
+
//# sourceMappingURL=explainCamera3DView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explainCamera3DView.js","sourceRoot":"","sources":["../src/explainCamera3DView.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { configureDirectionalShadowCamera3D, configureDirectionalShadowCamera3DTightFit, createCamera2D, createCamera3D, createOrthographicProjection, createPerspectiveProjection, getCamera2DParallaxPoint, getCamera2DViewMatrix, getCamera2DVisibleBounds, getCamera3DForward, getCamera3DFrustum, getCamera3DFrustumCorners, getCamera3DInverseViewProjectionMatrix4, getCamera3DLinearDepth, getCamera3DPosition, getCamera3DRayThroughBoundingSphere, getCamera3DRight, getCamera3DScreenToWorldRay, getCamera3DUp, getCamera3DViewProjectionMatrix4, getCamera3DViewSpaceZ, getCamera3DWorldToScreen, getOrthographicProjectionTexelSize, intersectCamera3DRayWithPlane, isBoxInCamera3DFrustum, isOrthographicProjection, isPerspectiveProjection, isPointInCamera3DFrustum, isSphereInCamera3DFrustum, projectCamera2DPoint, setCamera3DAspect, setCamera3DJitter, setCamera3DViewMatrix4FromLookAt, setCamera3DViewMatrix4FromMatrix4, setProjectionMatrix4, unprojectCamera2DPoint, updateCamera3DInverseViewProjection, zoomCamera2DAtScreenPoint, } from './contract';
|
|
1
|
+
export { areCameraGuardsEnabled, configureDirectionalShadowCamera3D, configureDirectionalShadowCamera3DTightFit, createCamera2D, createCamera3D, createOrthographicProjection, createPerspectiveProjection, disableCameraGuards, enableCameraGuards, getCamera2DParallaxPoint, getCamera2DViewMatrix, getCamera2DVisibleBounds, getCamera3DForward, getCamera3DFrustum, getCamera3DFrustumCorners, getCamera3DInverseViewProjectionMatrix4, getCamera3DLinearDepth, getCamera3DPosition, getCamera3DRayThroughBoundingSphere, getCamera3DRight, getCamera3DScreenToWorldRay, getCamera3DUp, getCamera3DViewProjectionMatrix4, getCamera3DViewSpaceZ, getCamera3DWorldToScreen, getOrthographicProjectionTexelSize, intersectCamera3DRayWithPlane, isBoxInCamera3DFrustum, isOrthographicProjection, isPerspectiveProjection, isPointInCamera3DFrustum, isSphereInCamera3DFrustum, projectCamera2DPoint, setCamera3DAspect, setCamera3DJitter, setCamera3DViewMatrix4FromLookAt, setCamera3DViewMatrix4FromMatrix4, setProjectionMatrix4, unprojectCamera2DPoint, updateCamera3DInverseViewProjection, zoomCamera2DAtScreenPoint, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,0CAA0C,EAC1C,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,uCAAuC,EACvC,sBAAsB,EACtB,mBAAmB,EACnB,mCAAmC,EACnC,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,gCAAgC,EAChC,qBAAqB,EACrB,wBAAwB,EACxB,kCAAkC,EAClC,6BAA6B,EAC7B,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,gCAAgC,EAChC,iCAAiC,EACjC,oBAAoB,EACpB,sBAAsB,EACtB,mCAAmC,EACnC,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,kCAAkC,EAClC,0CAA0C,EAC1C,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,2BAA2B,EAC3B,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,uCAAuC,EACvC,sBAAsB,EACtB,mBAAmB,EACnB,mCAAmC,EACnC,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,gCAAgC,EAChC,qBAAqB,EACrB,wBAAwB,EACxB,kCAAkC,EAClC,6BAA6B,EAC7B,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,gCAAgC,EAChC,iCAAiC,EACjC,oBAAoB,EACpB,sBAAsB,EACtB,mCAAmC,EACnC,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { configureDirectionalShadowCamera3D, configureDirectionalShadowCamera3DTightFit, createCamera2D, createCamera3D, createOrthographicProjection, createPerspectiveProjection, getCamera2DParallaxPoint, getCamera2DViewMatrix, getCamera2DVisibleBounds, getCamera3DForward, getCamera3DFrustum, getCamera3DFrustumCorners, getCamera3DInverseViewProjectionMatrix4, getCamera3DLinearDepth, getCamera3DPosition, getCamera3DRayThroughBoundingSphere, getCamera3DRight, getCamera3DScreenToWorldRay, getCamera3DUp, getCamera3DViewProjectionMatrix4, getCamera3DViewSpaceZ, getCamera3DWorldToScreen, getOrthographicProjectionTexelSize, intersectCamera3DRayWithPlane, isBoxInCamera3DFrustum, isOrthographicProjection, isPerspectiveProjection, isPointInCamera3DFrustum, isSphereInCamera3DFrustum, projectCamera2DPoint, setCamera3DAspect, setCamera3DJitter, setCamera3DViewMatrix4FromLookAt, setCamera3DViewMatrix4FromMatrix4, setProjectionMatrix4, unprojectCamera2DPoint, updateCamera3DInverseViewProjection, zoomCamera2DAtScreenPoint, } from './contract';
|
|
1
|
+
export { areCameraGuardsEnabled, configureDirectionalShadowCamera3D, configureDirectionalShadowCamera3DTightFit, createCamera2D, createCamera3D, createOrthographicProjection, createPerspectiveProjection, disableCameraGuards, enableCameraGuards, getCamera2DParallaxPoint, getCamera2DViewMatrix, getCamera2DVisibleBounds, getCamera3DForward, getCamera3DFrustum, getCamera3DFrustumCorners, getCamera3DInverseViewProjectionMatrix4, getCamera3DLinearDepth, getCamera3DPosition, getCamera3DRayThroughBoundingSphere, getCamera3DRight, getCamera3DScreenToWorldRay, getCamera3DUp, getCamera3DViewProjectionMatrix4, getCamera3DViewSpaceZ, getCamera3DWorldToScreen, getOrthographicProjectionTexelSize, intersectCamera3DRayWithPlane, isBoxInCamera3DFrustum, isOrthographicProjection, isPerspectiveProjection, isPointInCamera3DFrustum, isSphereInCamera3DFrustum, projectCamera2DPoint, setCamera3DAspect, setCamera3DJitter, setCamera3DViewMatrix4FromLookAt, setCamera3DViewMatrix4FromMatrix4, setProjectionMatrix4, unprojectCamera2DPoint, updateCamera3DInverseViewProjection, zoomCamera2DAtScreenPoint, } from './contract';
|
|
2
2
|
//# 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,OAAO,EACL,kCAAkC,EAClC,0CAA0C,EAC1C,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,uCAAuC,EACvC,sBAAsB,EACtB,mBAAmB,EACnB,mCAAmC,EACnC,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,gCAAgC,EAChC,qBAAqB,EACrB,wBAAwB,EACxB,kCAAkC,EAClC,6BAA6B,EAC7B,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,gCAAgC,EAChC,iCAAiC,EACjC,oBAAoB,EACpB,sBAAsB,EACtB,mCAAmC,EACnC,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,kCAAkC,EAClC,0CAA0C,EAC1C,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,2BAA2B,EAC3B,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,uCAAuC,EACvC,sBAAsB,EACtB,mBAAmB,EACnB,mCAAmC,EACnC,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,gCAAgC,EAChC,qBAAqB,EACrB,wBAAwB,EACxB,kCAAkC,EAClC,6BAA6B,EAC7B,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,gCAAgC,EAChC,iCAAiC,EACjC,oBAAoB,EACpB,sBAAsB,EACtB,mCAAmC,EACnC,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
package/dist/visibleBounds.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { Camera2D, RectangleLike } from '@flighthq/types/contract';
|
|
2
2
|
export declare function getCamera2DVisibleBounds(camera: Readonly<Camera2D>, out: RectangleLike): void;
|
|
3
|
+
export declare function setCamera2DVisibleBoundsGuard(guard: ((camera: Readonly<Camera2D>) => void) | null): void;
|
|
3
4
|
//# sourceMappingURL=visibleBounds.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visibleBounds.d.ts","sourceRoot":"","sources":["../src/visibleBounds.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"visibleBounds.d.ts","sourceRoot":"","sources":["../src/visibleBounds.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAkBxE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAW7F;AAID,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAExG"}
|
package/dist/visibleBounds.js
CHANGED
|
@@ -6,11 +6,40 @@ import { getCamera2DViewMatrix } from './viewMatrix';
|
|
|
6
6
|
// higher zoom it shrinks (a smaller world region fills the viewport); under rotation it is the
|
|
7
7
|
// enclosing AABB of the rotated view, so it over-covers rather than clipping visible content. The
|
|
8
8
|
// rectangle is always centered on the camera position `(x, y)`.
|
|
9
|
+
//
|
|
10
|
+
// A zero `zoom` makes the view matrix singular and it has no inverse, so there is no world rectangle
|
|
11
|
+
// to report. `zoom` is deliberately NOT clamped to rescue this: it is a caller-set value, and quietly
|
|
12
|
+
// substituting a different one is the kind of hidden correction the SDK does not do. The failure is
|
|
13
|
+
// handled HERE instead, and it fails toward drawing — an unbounded rectangle, so the cull excludes
|
|
14
|
+
// nothing. The asymmetry is the whole reason: over-drawing is slow and visible, under-drawing is
|
|
15
|
+
// silent and wrong, and a cull bound that silently collapses removes content with nothing to point at
|
|
16
|
+
// the cause.
|
|
9
17
|
export function getCamera2DVisibleBounds(camera, out) {
|
|
10
18
|
getCamera2DViewMatrix(camera, scratchMatrix);
|
|
11
|
-
inverseMatrix(scratchInverse, scratchMatrix)
|
|
19
|
+
if (!inverseMatrix(scratchInverse, scratchMatrix)) {
|
|
20
|
+
out.x = UNBOUNDED_ORIGIN;
|
|
21
|
+
out.y = UNBOUNDED_ORIGIN;
|
|
22
|
+
out.width = UNBOUNDED_EXTENT;
|
|
23
|
+
out.height = UNBOUNDED_EXTENT;
|
|
24
|
+
degenerateVisibleBoundsGuard?.(camera);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
12
27
|
matrixTransformBounds(out, scratchInverse, 0, 0, camera.viewportWidth, camera.viewportHeight);
|
|
13
28
|
}
|
|
29
|
+
// The seam the guard layer installs into; `null` keeps this module free of message text and of any
|
|
30
|
+
// dependency on @flighthq/log, so an application that never imports the guards sheds both.
|
|
31
|
+
export function setCamera2DVisibleBoundsGuard(guard) {
|
|
32
|
+
degenerateVisibleBoundsGuard = guard;
|
|
33
|
+
}
|
|
34
|
+
// Finite extremes rather than ±Infinity. With `-Infinity` origin and `Infinity` extent, a max edge is
|
|
35
|
+
// `-Infinity + Infinity` = NaN, and every NaN comparison is false — which `intersectsRectangle` happens
|
|
36
|
+
// to read as "overlaps", because it is written as the negation of four disjoint tests. That is the
|
|
37
|
+
// right answer reached by accident, and only for that one predicate: any cull that compares centers,
|
|
38
|
+
// areas, or sorts by extent gets NaN instead. Half of MAX_VALUE keeps `x + width` finite, so ordinary
|
|
39
|
+
// arithmetic holds everywhere and the rectangle still encloses any real content.
|
|
40
|
+
const UNBOUNDED_EXTENT = Number.MAX_VALUE;
|
|
41
|
+
const UNBOUNDED_ORIGIN = -Number.MAX_VALUE / 2;
|
|
42
|
+
let degenerateVisibleBoundsGuard = null;
|
|
14
43
|
const scratchInverse = createMatrix();
|
|
15
44
|
const scratchMatrix = createMatrix();
|
|
16
45
|
//# sourceMappingURL=visibleBounds.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visibleBounds.js","sourceRoot":"","sources":["../src/visibleBounds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAGjG,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,sGAAsG;AACtG,qGAAqG;AACrG,oGAAoG;AACpG,+FAA+F;AAC/F,kGAAkG;AAClG,gEAAgE;AAChE,MAAM,UAAU,wBAAwB,CAAC,MAA0B,EAAE,GAAkB;IACrF,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7C,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"visibleBounds.js","sourceRoot":"","sources":["../src/visibleBounds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAGjG,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,sGAAsG;AACtG,qGAAqG;AACrG,oGAAoG;AACpG,+FAA+F;AAC/F,kGAAkG;AAClG,gEAAgE;AAChE,EAAE;AACF,qGAAqG;AACrG,sGAAsG;AACtG,oGAAoG;AACpG,mGAAmG;AACnG,iGAAiG;AACjG,sGAAsG;AACtG,aAAa;AACb,MAAM,UAAU,wBAAwB,CAAC,MAA0B,EAAE,GAAkB;IACrF,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7C,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC;QACzB,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC;QACzB,GAAG,CAAC,KAAK,GAAG,gBAAgB,CAAC;QAC7B,GAAG,CAAC,MAAM,GAAG,gBAAgB,CAAC;QAC9B,4BAA4B,EAAE,CAAC,MAAM,CAAC,CAAC;QACvC,OAAO;IACT,CAAC;IACD,qBAAqB,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;AAChG,CAAC;AAED,mGAAmG;AACnG,2FAA2F;AAC3F,MAAM,UAAU,6BAA6B,CAAC,KAAoD;IAChG,4BAA4B,GAAG,KAAK,CAAC;AACvC,CAAC;AAED,sGAAsG;AACtG,wGAAwG;AACxG,mGAAmG;AACnG,qGAAqG;AACrG,sGAAsG;AACtG,iFAAiF;AACjF,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,CAAC;AAC1C,MAAM,gBAAgB,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;AAE/C,IAAI,4BAA4B,GAAkD,IAAI,CAAC;AACvF,MAAM,cAAc,GAAG,YAAY,EAAE,CAAC;AACtC,MAAM,aAAa,GAAG,YAAY,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/camera",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1-next.1050.9114695",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,9 +38,10 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/entity": "0.3.
|
|
42
|
-
"@flighthq/geometry": "0.3.
|
|
43
|
-
"@flighthq/
|
|
41
|
+
"@flighthq/entity": "0.3.1-next.1050.9114695",
|
|
42
|
+
"@flighthq/geometry": "0.3.1-next.1050.9114695",
|
|
43
|
+
"@flighthq/log": "0.3.1-next.1050.9114695",
|
|
44
|
+
"@flighthq/types": "0.3.1-next.1050.9114695"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"typescript": "^5.3.0"
|
package/src/camera.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getCamera3DViewProjectionMatrix4,
|
|
13
13
|
setCamera3DAspect,
|
|
14
14
|
setCamera3DJitter,
|
|
15
|
+
setCamera3DViewGuard,
|
|
15
16
|
setCamera3DViewMatrix4FromLookAt,
|
|
16
17
|
setCamera3DViewMatrix4FromMatrix4,
|
|
17
18
|
updateCamera3DInverseViewProjection,
|
|
@@ -157,6 +158,25 @@ describe('setCamera3DJitter', () => {
|
|
|
157
158
|
});
|
|
158
159
|
});
|
|
159
160
|
|
|
161
|
+
describe('setCamera3DViewGuard', () => {
|
|
162
|
+
it('is notified whenever a view matrix is assigned, and not otherwise', () => {
|
|
163
|
+
const seen: number[] = [];
|
|
164
|
+
setCamera3DViewGuard((camera) => seen.push(camera.view.m[0]));
|
|
165
|
+
try {
|
|
166
|
+
const camera = createCamera3D({
|
|
167
|
+
far: 100,
|
|
168
|
+
near: 0.1,
|
|
169
|
+
projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }),
|
|
170
|
+
});
|
|
171
|
+
expect(seen).toEqual([]);
|
|
172
|
+
setCamera3DViewMatrix4FromMatrix4(camera, createMatrix4(4, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1));
|
|
173
|
+
expect(seen).toEqual([4]);
|
|
174
|
+
} finally {
|
|
175
|
+
setCamera3DViewGuard(null);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
160
180
|
describe('setCamera3DViewMatrix4FromLookAt', () => {
|
|
161
181
|
it('builds the view matrix from eye-target-up', () => {
|
|
162
182
|
const camera = createCamera3D({
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createMatrix4, createRectangle } from '@flighthq/geometry/contract';
|
|
2
|
+
import { setLogSink } from '@flighthq/log/contract';
|
|
3
|
+
import type { Camera2D, LogEntry } from '@flighthq/types/contract';
|
|
4
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { createCamera3D, setCamera3DViewMatrix4FromMatrix4 } from './camera';
|
|
7
|
+
import { createCamera2D } from './camera2d';
|
|
8
|
+
import { areCameraGuardsEnabled, disableCameraGuards, enableCameraGuards } from './enableCameraGuards';
|
|
9
|
+
import { createPerspectiveProjection } from './projection';
|
|
10
|
+
import { getCamera2DVisibleBounds } from './visibleBounds';
|
|
11
|
+
|
|
12
|
+
describe('areCameraGuardsEnabled', () => {
|
|
13
|
+
test('reports whether the guards are installed', () => {
|
|
14
|
+
expect(areCameraGuardsEnabled()).toBe(false);
|
|
15
|
+
enableCameraGuards();
|
|
16
|
+
expect(areCameraGuardsEnabled()).toBe(true);
|
|
17
|
+
disableCameraGuards();
|
|
18
|
+
expect(areCameraGuardsEnabled()).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('disableCameraGuards', () => {
|
|
23
|
+
test('leaves the core silent again once removed', () => {
|
|
24
|
+
enableCameraGuards();
|
|
25
|
+
disableCameraGuards();
|
|
26
|
+
expect(captureLog(() => getCamera2DVisibleBounds(degenerateCamera(), createRectangle()))).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('enableCameraGuards', () => {
|
|
31
|
+
// `logOnce` keys on the zoom and has no reset, so this is the ONE test allowed to trigger the
|
|
32
|
+
// degenerate key — a second would be deduplicated away and assert nothing. The seam itself is tested
|
|
33
|
+
// directly, and deterministically, in visibleBounds.test.ts.
|
|
34
|
+
test('warns, naming the zoom and the consequence, when the visible bounds cannot be computed', () => {
|
|
35
|
+
enableCameraGuards();
|
|
36
|
+
const entries = captureLog(() => getCamera2DVisibleBounds(degenerateCamera(), createRectangle()));
|
|
37
|
+
expect(entries).toHaveLength(1);
|
|
38
|
+
expect(entries[0].data).toMatchObject({ zoom: 0 });
|
|
39
|
+
expect(String((entries[0].data as { message: string }).message)).toContain('nothing is culled');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('warns on a non-orthonormal 3D view, naming the cause rather than a consumer', () => {
|
|
43
|
+
enableCameraGuards();
|
|
44
|
+
const entries = captureLog(() => {
|
|
45
|
+
const camera = createCamera3D({
|
|
46
|
+
far: 100,
|
|
47
|
+
near: 0.1,
|
|
48
|
+
projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }),
|
|
49
|
+
});
|
|
50
|
+
setCamera3DViewMatrix4FromMatrix4(camera, createMatrix4(3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1));
|
|
51
|
+
});
|
|
52
|
+
expect(entries).toHaveLength(1);
|
|
53
|
+
expect(String((entries[0].data as { message: string }).message)).toContain('not orthonormal');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Reflections satisfy the precondition, so warning on them would be a false positive on a legitimate
|
|
57
|
+
// water/mirror camera.
|
|
58
|
+
test('stays silent for a reflection view', () => {
|
|
59
|
+
enableCameraGuards();
|
|
60
|
+
const entries = captureLog(() => {
|
|
61
|
+
const camera = createCamera3D({
|
|
62
|
+
far: 100,
|
|
63
|
+
near: 0.1,
|
|
64
|
+
projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }),
|
|
65
|
+
});
|
|
66
|
+
setCamera3DViewMatrix4FromMatrix4(camera, createMatrix4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2, 3, 4, 1));
|
|
67
|
+
});
|
|
68
|
+
expect(entries).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('stays silent for a camera whose view matrix inverts', () => {
|
|
72
|
+
enableCameraGuards();
|
|
73
|
+
expect(captureLog(() => getCamera2DVisibleBounds(createCamera2D(64, 64), createRectangle()))).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
disableCameraGuards();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function captureLog(run: () => void): LogEntry[] {
|
|
82
|
+
const entries: LogEntry[] = [];
|
|
83
|
+
setLogSink((entry) => entries.push(entry));
|
|
84
|
+
try {
|
|
85
|
+
run();
|
|
86
|
+
} finally {
|
|
87
|
+
setLogSink(null);
|
|
88
|
+
}
|
|
89
|
+
return entries;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function degenerateCamera(): Camera2D {
|
|
93
|
+
return createCamera2D(64, 64, { zoom: 0 });
|
|
94
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createMatrix4 } from '@flighthq/geometry/contract';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createCamera3D, setCamera3DViewMatrix4FromLookAt, setCamera3DViewMatrix4FromMatrix4 } from './camera';
|
|
5
|
+
import { explainCamera3DView } from './explainCamera3DView';
|
|
6
|
+
import { createPerspectiveProjection } from './projection';
|
|
7
|
+
|
|
8
|
+
describe('explainCamera3DView', () => {
|
|
9
|
+
test('accepts a rigid lookAt view', () => {
|
|
10
|
+
const camera = perspectiveCamera();
|
|
11
|
+
setCamera3DViewMatrix4FromLookAt(camera, { x: 0, y: 0, z: 10 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 });
|
|
12
|
+
const explanation = explainCamera3DView(camera);
|
|
13
|
+
expect(explanation.isOrthonormal).toBe(true);
|
|
14
|
+
expect(explanation.isReflection).toBe(false);
|
|
15
|
+
expect(explanation.determinant).toBeCloseTo(1, 6);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// The case manager asked to be costed before the contract was accepted: a mirror camera is improper
|
|
19
|
+
// orthogonal (det = -1) but STILL orthogonal, so Rᵀ = R⁻¹ holds and nothing that relies on the
|
|
20
|
+
// transpose is lost. It must not be reported as a violation.
|
|
21
|
+
test('accepts a reflection view and reports it as one', () => {
|
|
22
|
+
const camera = perspectiveCamera();
|
|
23
|
+
setCamera3DViewMatrix4FromMatrix4(camera, createMatrix4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 7, 9, 1));
|
|
24
|
+
const explanation = explainCamera3DView(camera);
|
|
25
|
+
expect(explanation.isOrthonormal).toBe(true);
|
|
26
|
+
expect(explanation.isReflection).toBe(true);
|
|
27
|
+
expect(explanation.determinant).toBeCloseTo(-1, 6);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('reports a scaled view as non-orthonormal, with the deviation', () => {
|
|
31
|
+
const camera = perspectiveCamera();
|
|
32
|
+
setCamera3DViewMatrix4FromMatrix4(camera, createMatrix4(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1));
|
|
33
|
+
const explanation = explainCamera3DView(camera);
|
|
34
|
+
expect(explanation.isOrthonormal).toBe(false);
|
|
35
|
+
expect(explanation.scaleDeviation).toBeCloseTo(1, 6);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('reports a sheared view as non-orthonormal', () => {
|
|
39
|
+
const camera = perspectiveCamera();
|
|
40
|
+
setCamera3DViewMatrix4FromMatrix4(camera, createMatrix4(1, 0, 0, 0, 0.5, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1));
|
|
41
|
+
const explanation = explainCamera3DView(camera);
|
|
42
|
+
expect(explanation.isOrthonormal).toBe(false);
|
|
43
|
+
expect(explanation.shearDeviation).toBeGreaterThan(0.1);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
function perspectiveCamera(): ReturnType<typeof createCamera3D> {
|
|
48
|
+
return createCamera3D({ far: 100, near: 0.1, projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }) });
|
|
49
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { createRectangle, createVector2 } from '@flighthq/geometry/contract';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { createRectangle, createVector2, intersectsRectangle } from '@flighthq/geometry/contract';
|
|
2
|
+
import { describe, expect, it, test } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import { createCamera2D } from './camera2d';
|
|
5
5
|
import { unprojectCamera2DPoint } from './projection2d';
|
|
6
|
-
import { getCamera2DVisibleBounds } from './visibleBounds';
|
|
6
|
+
import { getCamera2DVisibleBounds, setCamera2DVisibleBoundsGuard } from './visibleBounds';
|
|
7
7
|
|
|
8
8
|
describe('getCamera2DVisibleBounds', () => {
|
|
9
9
|
it('covers the full viewport in world units at zoom 1', () => {
|
|
@@ -52,3 +52,35 @@ describe('getCamera2DVisibleBounds', () => {
|
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
describe('setCamera2DVisibleBoundsGuard', () => {
|
|
57
|
+
test('fails toward drawing: an unbounded rectangle that still intersects distant content', () => {
|
|
58
|
+
const out = createRectangle();
|
|
59
|
+
getCamera2DVisibleBounds(createCamera2D(64, 64, { zoom: 0 }), out);
|
|
60
|
+
// Finite on purpose — the max edge must survive ordinary arithmetic, not rely on NaN comparisons.
|
|
61
|
+
expect(Number.isFinite(out.x + out.width)).toBe(true);
|
|
62
|
+
expect(Number.isFinite(out.y + out.height)).toBe(true);
|
|
63
|
+
// The property that matters is behavioural, not the constant: nothing gets culled.
|
|
64
|
+
expect(intersectsRectangle(out, createRectangle(1e12, -4e11, 10, 10))).toBe(true);
|
|
65
|
+
expect(intersectsRectangle(out, createRectangle(-9e15, 8e14, 1, 1))).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('notifies the installed guard exactly once per degenerate call, and never for an invertible camera', () => {
|
|
69
|
+
const seen: number[] = [];
|
|
70
|
+
setCamera2DVisibleBoundsGuard((camera) => seen.push(camera.zoom));
|
|
71
|
+
try {
|
|
72
|
+
getCamera2DVisibleBounds(createCamera2D(64, 64, { zoom: 0 }), createRectangle());
|
|
73
|
+
getCamera2DVisibleBounds(createCamera2D(64, 64), createRectangle());
|
|
74
|
+
expect(seen).toEqual([0]);
|
|
75
|
+
} finally {
|
|
76
|
+
setCamera2DVisibleBoundsGuard(null);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('leaves an invertible camera computing the same bounds as before', () => {
|
|
81
|
+
const out = createRectangle();
|
|
82
|
+
getCamera2DVisibleBounds(createCamera2D(64, 32), out);
|
|
83
|
+
expect(out.width).toBeCloseTo(64, 6);
|
|
84
|
+
expect(out.height).toBeCloseTo(32, 6);
|
|
85
|
+
});
|
|
86
|
+
});
|