@flighthq/camera 0.3.0 → 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/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/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/enableCameraGuards.test.ts +63 -0
- package/src/visibleBounds.test.ts +35 -3
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, 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.1041.35b950c",
|
|
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.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"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"typescript": "^5.3.0"
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
});
|