@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.
@@ -3,6 +3,7 @@ export * from './camera';
3
3
  export * from './camera2d';
4
4
  export * from './culling';
5
5
  export * from './depth';
6
+ export * from './enableCameraGuards';
6
7
  export * from './frustumCorners';
7
8
  export * from './intersection';
8
9
  export * from './parallax';
@@ -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
@@ -3,6 +3,7 @@ export * from './camera';
3
3
  export * from './camera2d';
4
4
  export * from './culling';
5
5
  export * from './depth';
6
+ export * from './enableCameraGuards';
6
7
  export * from './frustumCorners';
7
8
  export * from './intersection';
8
9
  export * from './parallax';
@@ -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,4 @@
1
+ export declare function areCameraGuardsEnabled(): boolean;
2
+ export declare function disableCameraGuards(): void;
3
+ export declare function enableCameraGuards(): void;
4
+ //# sourceMappingURL=enableCameraGuards.d.ts.map
@@ -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
@@ -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"}
@@ -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;AAUxE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAI7F"}
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"}
@@ -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;IAC7C,qBAAqB,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;AAChG,CAAC;AAED,MAAM,cAAc,GAAG,YAAY,EAAE,CAAC;AACtC,MAAM,aAAa,GAAG,YAAY,EAAE,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.0",
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.0",
42
- "@flighthq/geometry": "0.3.0",
43
- "@flighthq/types": "0.3.0"
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
+ });