@flighthq/camera 0.3.0-next.906.07cea63 → 0.3.1-next.1041.35b950c
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 -1
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +29 -19
- package/dist/camera.js.map +1 -1
- package/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -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 +28 -0
- package/dist/enableCameraGuards.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/projection.d.ts +1 -0
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +6 -0
- package/dist/projection.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 +7 -4
- package/src/camera.test.ts +35 -21
- package/src/enableCameraGuards.test.ts +63 -0
- package/src/projection.test.ts +10 -0
- package/src/visibleBounds.test.ts +35 -3
package/dist/camera.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export declare function setCamera3DAspect(camera: Camera3D, aspect: number): voi
|
|
|
6
6
|
export declare function setCamera3DJitter(camera: Camera3D, x: number, y: number): void;
|
|
7
7
|
export declare function setCamera3DViewMatrix4FromLookAt(camera: Camera3D, eye: Readonly<Vector3Like>, target: Readonly<Vector3Like>, up: Readonly<Vector3Like>): void;
|
|
8
8
|
export declare function setCamera3DViewMatrix4FromMatrix4(camera: Camera3D, view: Readonly<Matrix4Like>): void;
|
|
9
|
-
export declare function updateCamera3DInverseViewProjection(camera: Camera3D
|
|
9
|
+
export declare function updateCamera3DInverseViewProjection(camera: Readonly<Camera3D>, aspect: number): boolean;
|
|
10
10
|
//# sourceMappingURL=camera.d.ts.map
|
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;
|
|
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;AAID,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAErG;AAMD,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIvG"}
|
package/dist/camera.js
CHANGED
|
@@ -3,10 +3,10 @@ import { createMatrix4, createVector2, inverseMatrix4, multiplyMatrix4, setMatri
|
|
|
3
3
|
import { setProjectionMatrix4 } from './projection';
|
|
4
4
|
// Allocates a 3D camera. The camera stores its projection descriptor, a world->view Matrix4
|
|
5
5
|
// (`view`, initialized to identity), the clip-plane distances `near`/`far`, the per-frame
|
|
6
|
-
// sub-pixel NDC `jitter` (
|
|
7
|
-
// `inverseViewProjection`
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// sub-pixel NDC `jitter` (applied to every projection, initialized to zero), and the last
|
|
7
|
+
// `inverseViewProjection` computed for a reconstruction pass (initialized to identity). The view
|
|
8
|
+
// matrix is canonical: the camera has no separate Transform3D — a Matrix4 is the only world->view
|
|
9
|
+
// representation.
|
|
10
10
|
export function createCamera3D(opts) {
|
|
11
11
|
return createEntity({
|
|
12
12
|
far: opts.far,
|
|
@@ -19,12 +19,11 @@ export function createCamera3D(opts) {
|
|
|
19
19
|
}
|
|
20
20
|
// Writes the inverse of the camera's view-projection matrix into `out` and returns true, or
|
|
21
21
|
// returns false (writing NaN into `out`) when the view-projection is non-invertible. `aspect`
|
|
22
|
-
// is the viewport width / height.
|
|
23
|
-
//
|
|
22
|
+
// is the viewport width / height. Skybox and other world-position reconstruction passes use this
|
|
23
|
+
// calculation to refresh Camera3D.inverseViewProjection immediately before consuming it.
|
|
24
24
|
//
|
|
25
25
|
// Reads camera fields into a scratch matrix before writing `out`, so it is safe even if `out`
|
|
26
|
-
// aliases the camera's own `inverseViewProjection` or `view`.
|
|
27
|
-
// `updateCamera3DInverseViewProjection` instead to safely update the cached field.
|
|
26
|
+
// aliases the camera's own `inverseViewProjection` or `view`.
|
|
28
27
|
export function getCamera3DInverseViewProjectionMatrix4(out, camera, aspect) {
|
|
29
28
|
getCamera3DViewProjectionMatrix4(__scratchViewProjection, camera, aspect);
|
|
30
29
|
return inverseMatrix4(out, __scratchViewProjection);
|
|
@@ -37,6 +36,7 @@ export function getCamera3DInverseViewProjectionMatrix4(out, camera, aspect) {
|
|
|
37
36
|
// aliases the camera's own `view`.
|
|
38
37
|
export function getCamera3DViewProjectionMatrix4(out, camera, aspect) {
|
|
39
38
|
setProjectionMatrix4(__scratchProjection, camera.projection, aspect, camera.near, camera.far);
|
|
39
|
+
applyCamera3DProjectionJitter(__scratchProjection, camera.jitter.x, camera.jitter.y);
|
|
40
40
|
multiplyMatrix4(out, __scratchProjection, camera.view);
|
|
41
41
|
}
|
|
42
42
|
// Sets the viewport aspect ratio (width / height) on the camera's projection, in place. For a
|
|
@@ -53,8 +53,8 @@ export function setCamera3DAspect(camera, aspect) {
|
|
|
53
53
|
}
|
|
54
54
|
projection.halfWidth = projection.halfHeight * aspect;
|
|
55
55
|
}
|
|
56
|
-
// Sets the camera's per-frame sub-pixel jitter offset (in NDC), in place.
|
|
57
|
-
//
|
|
56
|
+
// Sets the camera's per-frame sub-pixel jitter offset (in NDC), in place. Every view-projection
|
|
57
|
+
// helper applies it to clip X/Y before the perspective divide.
|
|
58
58
|
export function setCamera3DJitter(camera, x, y) {
|
|
59
59
|
camera.jitter.x = x;
|
|
60
60
|
camera.jitter.y = y;
|
|
@@ -72,20 +72,30 @@ export function setCamera3DViewMatrix4FromLookAt(camera, eye, target, up) {
|
|
|
72
72
|
export function setCamera3DViewMatrix4FromMatrix4(camera, view) {
|
|
73
73
|
camera.view.m.set(view.m);
|
|
74
74
|
}
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// Call this once per frame after setting the view matrix and before any effects that read
|
|
80
|
-
// camera.inverseViewProjection (TAA, velocity, fog, depth-of-field).
|
|
75
|
+
// Refreshes the camera's required inverse-view-projection cache for the supplied viewport aspect.
|
|
76
|
+
// Returns true on success or false when projection×view is non-invertible, leaving the prior valid
|
|
77
|
+
// cache untouched. Reconstruction consumers call this immediately before reading the field so it
|
|
78
|
+
// cannot silently drift after mutable camera inputs change.
|
|
81
79
|
export function updateCamera3DInverseViewProjection(camera, aspect) {
|
|
82
|
-
// Write into a scratch first so the cache is never clobbered with NaN on failure.
|
|
83
80
|
const ok = getCamera3DInverseViewProjectionMatrix4(__scratchInverse, camera, aspect);
|
|
84
|
-
if (ok)
|
|
81
|
+
if (ok)
|
|
85
82
|
camera.inverseViewProjection.m.set(__scratchInverse.m);
|
|
86
|
-
}
|
|
87
83
|
return ok;
|
|
88
84
|
}
|
|
85
|
+
// Applies an NDC translation to a projection matrix: clip.xy += jitter * clip.w. Expressing it as
|
|
86
|
+
// row operations handles perspective (offset in m[8]/m[9]) and orthographic (m[12]/m[13]) matrices
|
|
87
|
+
// uniformly.
|
|
88
|
+
function applyCamera3DProjectionJitter(out, x, y) {
|
|
89
|
+
const m = out.m;
|
|
90
|
+
m[0] += x * m[3];
|
|
91
|
+
m[4] += x * m[7];
|
|
92
|
+
m[8] += x * m[11];
|
|
93
|
+
m[12] += x * m[15];
|
|
94
|
+
m[1] += y * m[3];
|
|
95
|
+
m[5] += y * m[7];
|
|
96
|
+
m[9] += y * m[11];
|
|
97
|
+
m[13] += y * m[15];
|
|
98
|
+
}
|
|
89
99
|
// Scratch matrices reused by the view-projection helpers. Single-threaded; not re-entrant.
|
|
90
100
|
const __scratchInverse = createMatrix4();
|
|
91
101
|
const __scratchProjection = createMatrix4();
|
package/dist/camera.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"camera.js","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,4FAA4F;AAC5F,0FAA0F;AAC1F,
|
|
1
|
+
{"version":3,"file":"camera.js","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,4FAA4F;AAC5F,0FAA0F;AAC1F,0FAA0F;AAC1F,iGAAiG;AACjG,kGAAkG;AAClG,kBAAkB;AAClB,MAAM,UAAU,cAAc,CAAC,IAA+B;IAC5D,OAAO,YAAY,CAAC;QAClB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,qBAAqB,EAAE,aAAa,EAAE;QACtC,MAAM,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,IAAI,EAAE,aAAa,EAAE;KACtB,CAAC,CAAC;AACL,CAAC;AAED,4FAA4F;AAC5F,8FAA8F;AAC9F,iGAAiG;AACjG,yFAAyF;AACzF,EAAE;AACF,8FAA8F;AAC9F,8DAA8D;AAC9D,MAAM,UAAU,uCAAuC,CACrD,GAAgB,EAChB,MAA0B,EAC1B,MAAc;IAEd,gCAAgC,CAAC,uBAAuB,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1E,OAAO,cAAc,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;AACtD,CAAC;AAED,6FAA6F;AAC7F,gGAAgG;AAChG,UAAU;AACV,EAAE;AACF,8FAA8F;AAC9F,mCAAmC;AACnC,MAAM,UAAU,gCAAgC,CAAC,GAAgB,EAAE,MAA0B,EAAE,MAAc;IAC3G,oBAAoB,CAAC,mBAAmB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9F,6BAA6B,CAAC,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACrF,eAAe,CAAC,GAAG,EAAE,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC;AAED,8FAA8F;AAC9F,iGAAiG;AACjG,iGAAiG;AACjG,+FAA+F;AAC/F,gGAAgG;AAChG,iDAAiD;AACjD,MAAM,UAAU,iBAAiB,CAAC,MAAgB,EAAE,MAAc;IAChE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACtC,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC,UAAU,GAAG,MAAM,CAAC;AACxD,CAAC;AAED,gGAAgG;AAChG,+DAA+D;AAC/D,MAAM,UAAU,iBAAiB,CAAC,MAAgB,EAAE,CAAS,EAAE,CAAS;IACtE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;IACpB,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED,iGAAiG;AACjG,gGAAgG;AAChG,4BAA4B;AAC5B,EAAE;AACF,4FAA4F;AAC5F,MAAM,UAAU,gCAAgC,CAC9C,MAAgB,EAChB,GAA0B,EAC1B,MAA6B,EAC7B,EAAyB;IAEzB,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,kGAAkG;AAClG,qFAAqF;AACrF,MAAM,UAAU,iCAAiC,CAAC,MAAgB,EAAE,IAA2B;IAC7F,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,kGAAkG;AAClG,mGAAmG;AACnG,iGAAiG;AACjG,4DAA4D;AAC5D,MAAM,UAAU,mCAAmC,CAAC,MAA0B,EAAE,MAAc;IAC5F,MAAM,EAAE,GAAG,uCAAuC,CAAC,gBAAgB,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACrF,IAAI,EAAE;QAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,kGAAkG;AAClG,mGAAmG;AACnG,aAAa;AACb,SAAS,6BAA6B,CAAC,GAAgB,EAAE,CAAS,EAAE,CAAS;IAC3E,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;AACrB,CAAC;AAED,2FAA2F;AAC3F,MAAM,gBAAgB,GAAG,aAAa,EAAE,CAAC;AACzC,MAAM,mBAAmB,GAAG,aAAa,EAAE,CAAC;AAC5C,MAAM,uBAAuB,GAAG,aAAa,EAAE,CAAC"}
|
package/dist/contract.d.ts
CHANGED
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,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
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,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":"AAMA,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAKD,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { logOnce } from '@flighthq/log/contract';
|
|
2
|
+
import { LogLevel } from '@flighthq/types/contract';
|
|
3
|
+
import { setCamera2DVisibleBoundsGuard } from './visibleBounds';
|
|
4
|
+
export function areCameraGuardsEnabled() {
|
|
5
|
+
return cameraGuardsEnabled;
|
|
6
|
+
}
|
|
7
|
+
export function disableCameraGuards() {
|
|
8
|
+
setCamera2DVisibleBoundsGuard(null);
|
|
9
|
+
cameraGuardsEnabled = false;
|
|
10
|
+
}
|
|
11
|
+
// Installs opt-in diagnostics for camera inputs that leave a computation with no answer. The core
|
|
12
|
+
// modules stay message-free, so an application that omits this one sheds both the text and
|
|
13
|
+
// @flighthq/log.
|
|
14
|
+
export function enableCameraGuards() {
|
|
15
|
+
setCamera2DVisibleBoundsGuard(warnOnDegenerateCamera2DVisibleBounds);
|
|
16
|
+
cameraGuardsEnabled = true;
|
|
17
|
+
}
|
|
18
|
+
// The unbounded rectangle is a deliberate fail-toward-drawing, not a silent success: nothing is culled,
|
|
19
|
+
// so the frame is correct but pays for every object. That is a performance cliff with no visible
|
|
20
|
+
// symptom, which is exactly the case a guard exists to name.
|
|
21
|
+
function warnOnDegenerateCamera2DVisibleBounds(camera) {
|
|
22
|
+
logOnce(`camera:degenerate-visible-bounds:${camera.zoom}`, LogLevel.Warn, {
|
|
23
|
+
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.',
|
|
24
|
+
zoom: camera.zoom,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
let cameraGuardsEnabled = false;
|
|
28
|
+
//# 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,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAEhE,MAAM,UAAU,sBAAsB;IACpC,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,6BAA6B,CAAC,IAAI,CAAC,CAAC;IACpC,mBAAmB,GAAG,KAAK,CAAC;AAC9B,CAAC;AAED,kGAAkG;AAClG,2FAA2F;AAC3F,iBAAiB;AACjB,MAAM,UAAU,kBAAkB;IAChC,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,IAAI,mBAAmB,GAAG,KAAK,CAAC"}
|
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, 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,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, 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,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/projection.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Matrix4Like } from '@flighthq/types/contract';
|
|
|
2
2
|
import type { OrthographicProjection, OrthographicProjectionOptions, PerspectiveProjection, PerspectiveProjectionOptions, Projection } from '@flighthq/types/contract';
|
|
3
3
|
export declare function createOrthographicProjection(opts: Readonly<OrthographicProjectionOptions>): OrthographicProjection;
|
|
4
4
|
export declare function createPerspectiveProjection(opts: Readonly<PerspectiveProjectionOptions>): PerspectiveProjection;
|
|
5
|
+
export declare function getOrthographicProjectionTexelSize(projection: Readonly<OrthographicProjection>, pixelWidth: number, pixelHeight: number): number;
|
|
5
6
|
export declare function isOrthographicProjection(projection: Readonly<Projection>): projection is OrthographicProjection;
|
|
6
7
|
export declare function isPerspectiveProjection(projection: Readonly<Projection>): projection is PerspectiveProjection;
|
|
7
8
|
export declare function setProjectionMatrix4(out: Matrix4Like, projection: Readonly<Projection>, aspect: number, near: number, far: number): void;
|
package/dist/projection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EACV,sBAAsB,EACtB,6BAA6B,EAC7B,qBAAqB,EACrB,4BAA4B,EAC5B,UAAU,EACX,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,QAAQ,CAAC,6BAA6B,CAAC,GAAG,sBAAsB,CAMlH;AAID,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,QAAQ,CAAC,4BAA4B,CAAC,GAAG,qBAAqB,CAM/G;AAGD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,IAAI,sBAAsB,CAE/G;AAGD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,IAAI,qBAAqB,CAE7G;AAUD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAWN"}
|
|
1
|
+
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EACV,sBAAsB,EACtB,6BAA6B,EAC7B,qBAAqB,EACrB,4BAA4B,EAC5B,UAAU,EACX,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,QAAQ,CAAC,6BAA6B,CAAC,GAAG,sBAAsB,CAMlH;AAID,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,QAAQ,CAAC,4BAA4B,CAAC,GAAG,qBAAqB,CAM/G;AAKD,wBAAgB,kCAAkC,CAChD,UAAU,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EAC5C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAGD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,IAAI,sBAAsB,CAE/G;AAGD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,IAAI,qBAAqB,CAE7G;AAUD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAWN"}
|
package/dist/projection.js
CHANGED
|
@@ -18,6 +18,12 @@ export function createPerspectiveProjection(opts) {
|
|
|
18
18
|
kind: 'perspective',
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
// Returns the larger world-space footprint of one texel across an orthographic projection. Using the
|
|
22
|
+
// larger axis gives receiver offsets one conservative, aspect-independent unit even when the shadow
|
|
23
|
+
// map or light projection is not square. `pixelWidth`/`pixelHeight` must be positive.
|
|
24
|
+
export function getOrthographicProjectionTexelSize(projection, pixelWidth, pixelHeight) {
|
|
25
|
+
return Math.max((projection.halfWidth * 2) / pixelWidth, (projection.halfHeight * 2) / pixelHeight);
|
|
26
|
+
}
|
|
21
27
|
// True when the projection is an orthographic descriptor. Narrows the discriminated union.
|
|
22
28
|
export function isOrthographicProjection(projection) {
|
|
23
29
|
return projection.kind === 'orthographic';
|
package/dist/projection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAU5F,0FAA0F;AAC1F,wFAAwF;AACxF,wEAAwE;AACxE,MAAM,UAAU,4BAA4B,CAAC,IAA6C;IACxF,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,IAAI,EAAE,cAAc;KACrB,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,gGAAgG;AAChG,MAAM,UAAU,2BAA2B,CAAC,IAA4C;IACtF,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,aAAa;KACpB,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,wBAAwB,CAAC,UAAgC;IACvE,OAAO,UAAU,CAAC,IAAI,KAAK,cAAc,CAAC;AAC5C,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,uBAAuB,CAAC,UAAgC;IACtE,OAAO,UAAU,CAAC,IAAI,KAAK,aAAa,CAAC;AAC3C,CAAC;AAED,iGAAiG;AACjG,uFAAuF;AACvF,iGAAiG;AACjG,iGAAiG;AACjG,6CAA6C;AAC7C,EAAE;AACF,4FAA4F;AAC5F,yCAAyC;AACzC,MAAM,UAAU,oBAAoB,CAClC,GAAgB,EAChB,UAAgC,EAChC,MAAc,EACd,IAAY,EACZ,GAAW;IAEX,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACtC,0FAA0F;QAC1F,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;QACpD,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;IACvC,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;IACzC,sBAAsB,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AACzF,CAAC"}
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAU5F,0FAA0F;AAC1F,wFAAwF;AACxF,wEAAwE;AACxE,MAAM,UAAU,4BAA4B,CAAC,IAA6C;IACxF,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,IAAI,EAAE,cAAc;KACrB,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,gGAAgG;AAChG,MAAM,UAAU,2BAA2B,CAAC,IAA4C;IACtF,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,aAAa;KACpB,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,oGAAoG;AACpG,sFAAsF;AACtF,MAAM,UAAU,kCAAkC,CAChD,UAA4C,EAC5C,UAAkB,EAClB,WAAmB;IAEnB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,UAAU,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC;AACtG,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,wBAAwB,CAAC,UAAgC;IACvE,OAAO,UAAU,CAAC,IAAI,KAAK,cAAc,CAAC;AAC5C,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,uBAAuB,CAAC,UAAgC;IACtE,OAAO,UAAU,CAAC,IAAI,KAAK,aAAa,CAAC;AAC3C,CAAC;AAED,iGAAiG;AACjG,uFAAuF;AACvF,iGAAiG;AACjG,iGAAiG;AACjG,6CAA6C;AAC7C,EAAE;AACF,4FAA4F;AAC5F,yCAAyC;AACzC,MAAM,UAAU,oBAAoB,CAClC,GAAgB,EAChB,UAAgC,EAChC,MAAc,EACd,IAAY,EACZ,GAAW;IAEX,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACtC,0FAA0F;QAC1F,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;QACpD,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;IACvC,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;IACzC,sBAAsB,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AACzF,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,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/camera",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1-next.1041.35b950c",
|
|
4
|
+
"author": "Joshua Granick and other contributors",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,9 +38,10 @@
|
|
|
36
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@flighthq/entity": "0.3.
|
|
40
|
-
"@flighthq/geometry": "0.3.
|
|
41
|
-
"@flighthq/
|
|
41
|
+
"@flighthq/entity": "0.3.1-next.1041.35b950c",
|
|
42
|
+
"@flighthq/geometry": "0.3.1-next.1041.35b950c",
|
|
43
|
+
"@flighthq/log": "0.3.1-next.1041.35b950c",
|
|
44
|
+
"@flighthq/types": "0.3.1-next.1041.35b950c"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"typescript": "^5.3.0"
|
package/src/camera.test.ts
CHANGED
|
@@ -89,6 +89,23 @@ describe('getCamera3DViewProjectionMatrix4', () => {
|
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
it('applies the camera jitter as an NDC projection offset', () => {
|
|
93
|
+
const camera = createCamera3D({
|
|
94
|
+
far: 100,
|
|
95
|
+
near: 0.1,
|
|
96
|
+
projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }),
|
|
97
|
+
});
|
|
98
|
+
const base = createMatrix4();
|
|
99
|
+
getCamera3DViewProjectionMatrix4(base, camera, 1);
|
|
100
|
+
|
|
101
|
+
setCamera3DJitter(camera, 0.25, -0.5);
|
|
102
|
+
const jittered = createMatrix4();
|
|
103
|
+
getCamera3DViewProjectionMatrix4(jittered, camera, 1);
|
|
104
|
+
|
|
105
|
+
expect(jittered.m[8]).toBeCloseTo(base.m[8] - 0.25);
|
|
106
|
+
expect(jittered.m[9]).toBeCloseTo(base.m[9] + 0.5);
|
|
107
|
+
});
|
|
108
|
+
|
|
92
109
|
it('is safe when out aliases the camera view', () => {
|
|
93
110
|
const projection = createPerspectiveProjection({ aspect: 1, fovY: 1 });
|
|
94
111
|
const camera = createCamera3D({ far: 100, near: 0.1, projection });
|
|
@@ -181,35 +198,32 @@ describe('setCamera3DViewMatrix4FromMatrix4', () => {
|
|
|
181
198
|
});
|
|
182
199
|
|
|
183
200
|
describe('updateCamera3DInverseViewProjection', () => {
|
|
184
|
-
it('
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
201
|
+
it('refreshes the required cache from the jittered projection', () => {
|
|
202
|
+
const camera = createCamera3D({
|
|
203
|
+
far: 100,
|
|
204
|
+
near: 0.1,
|
|
205
|
+
projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }),
|
|
206
|
+
});
|
|
207
|
+
setCamera3DJitter(camera, 0.25, -0.5);
|
|
208
|
+
expect(updateCamera3DInverseViewProjection(camera, 1.5)).toBe(true);
|
|
191
209
|
|
|
192
|
-
// Compute expected inverse independently.
|
|
193
210
|
const vp = createMatrix4();
|
|
194
211
|
getCamera3DViewProjectionMatrix4(vp, camera, 1.5);
|
|
195
212
|
const expected = createMatrix4();
|
|
196
213
|
inverseMatrix4(expected, vp);
|
|
197
|
-
|
|
198
|
-
for (let i = 0; i < 16; i++) {
|
|
199
|
-
expect(camera.inverseViewProjection.m[i]).toBeCloseTo(expected.m[i]);
|
|
200
|
-
}
|
|
214
|
+
for (let i = 0; i < 16; i++) expect(camera.inverseViewProjection.m[i]).toBeCloseTo(expected.m[i]);
|
|
201
215
|
});
|
|
202
216
|
|
|
203
|
-
it('
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
it('leaves the last valid cache untouched for a non-invertible projection', () => {
|
|
218
|
+
const camera = createCamera3D({
|
|
219
|
+
far: 100,
|
|
220
|
+
near: 0.1,
|
|
221
|
+
projection: createPerspectiveProjection({ aspect: 1, fovY: 1 }),
|
|
222
|
+
});
|
|
209
223
|
camera.inverseViewProjection.m[0] = 42;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
expect(
|
|
224
|
+
camera.near = 1;
|
|
225
|
+
camera.far = 1;
|
|
226
|
+
expect(updateCamera3DInverseViewProjection(camera, 1)).toBe(false);
|
|
213
227
|
expect(camera.inverseViewProjection.m[0]).toBe(42);
|
|
214
228
|
});
|
|
215
229
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { 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 { createCamera2D } from './camera2d';
|
|
7
|
+
import { areCameraGuardsEnabled, disableCameraGuards, enableCameraGuards } from './enableCameraGuards';
|
|
8
|
+
import { getCamera2DVisibleBounds } from './visibleBounds';
|
|
9
|
+
|
|
10
|
+
describe('areCameraGuardsEnabled', () => {
|
|
11
|
+
test('reports whether the guards are installed', () => {
|
|
12
|
+
expect(areCameraGuardsEnabled()).toBe(false);
|
|
13
|
+
enableCameraGuards();
|
|
14
|
+
expect(areCameraGuardsEnabled()).toBe(true);
|
|
15
|
+
disableCameraGuards();
|
|
16
|
+
expect(areCameraGuardsEnabled()).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('disableCameraGuards', () => {
|
|
21
|
+
test('leaves the core silent again once removed', () => {
|
|
22
|
+
enableCameraGuards();
|
|
23
|
+
disableCameraGuards();
|
|
24
|
+
expect(captureLog(() => getCamera2DVisibleBounds(degenerateCamera(), createRectangle()))).toHaveLength(0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('enableCameraGuards', () => {
|
|
29
|
+
// `logOnce` keys on the zoom and has no reset, so this is the ONE test allowed to trigger the
|
|
30
|
+
// degenerate key — a second would be deduplicated away and assert nothing. The seam itself is tested
|
|
31
|
+
// directly, and deterministically, in visibleBounds.test.ts.
|
|
32
|
+
test('warns, naming the zoom and the consequence, when the visible bounds cannot be computed', () => {
|
|
33
|
+
enableCameraGuards();
|
|
34
|
+
const entries = captureLog(() => getCamera2DVisibleBounds(degenerateCamera(), createRectangle()));
|
|
35
|
+
expect(entries).toHaveLength(1);
|
|
36
|
+
expect(entries[0].data).toMatchObject({ zoom: 0 });
|
|
37
|
+
expect(String((entries[0].data as { message: string }).message)).toContain('nothing is culled');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('stays silent for a camera whose view matrix inverts', () => {
|
|
41
|
+
enableCameraGuards();
|
|
42
|
+
expect(captureLog(() => getCamera2DVisibleBounds(createCamera2D(64, 64), createRectangle()))).toHaveLength(0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
disableCameraGuards();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function captureLog(run: () => void): LogEntry[] {
|
|
51
|
+
const entries: LogEntry[] = [];
|
|
52
|
+
setLogSink((entry) => entries.push(entry));
|
|
53
|
+
try {
|
|
54
|
+
run();
|
|
55
|
+
} finally {
|
|
56
|
+
setLogSink(null);
|
|
57
|
+
}
|
|
58
|
+
return entries;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function degenerateCamera(): Camera2D {
|
|
62
|
+
return createCamera2D(64, 64, { zoom: 0 });
|
|
63
|
+
}
|
package/src/projection.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { OrthographicProjection, PerspectiveProjection } from '@flighthq/ty
|
|
|
4
4
|
import {
|
|
5
5
|
createOrthographicProjection,
|
|
6
6
|
createPerspectiveProjection,
|
|
7
|
+
getOrthographicProjectionTexelSize,
|
|
7
8
|
isOrthographicProjection,
|
|
8
9
|
isPerspectiveProjection,
|
|
9
10
|
setProjectionMatrix4,
|
|
@@ -32,6 +33,15 @@ describe('createPerspectiveProjection', () => {
|
|
|
32
33
|
});
|
|
33
34
|
});
|
|
34
35
|
|
|
36
|
+
describe('getOrthographicProjectionTexelSize', () => {
|
|
37
|
+
it('uses the larger world-space texel footprint for a non-square projection and map', () => {
|
|
38
|
+
const projection = createOrthographicProjection({ halfHeight: 20, halfWidth: 10 });
|
|
39
|
+
|
|
40
|
+
expect(getOrthographicProjectionTexelSize(projection, 100, 100)).toBe(0.4);
|
|
41
|
+
expect(getOrthographicProjectionTexelSize(projection, 50, 200)).toBe(0.4);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
35
45
|
describe('isOrthographicProjection', () => {
|
|
36
46
|
it('is true for orthographic and false for perspective', () => {
|
|
37
47
|
const ortho = createOrthographicProjection({ halfHeight: 1, halfWidth: 1 });
|
|
@@ -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
|
+
});
|