@loaders.gl/tiles 5.0.0-alpha.1 → 5.0.0-alpha.3

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.
Files changed (161) hide show
  1. package/LICENSE +14 -3
  2. package/dist/dist.dev.js +17482 -3685
  3. package/dist/dist.min.js +4 -1
  4. package/dist/index.cjs +4594 -422
  5. package/dist/index.cjs.map +3 -4
  6. package/dist/index.d.ts +16 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +7 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/point-cloud/point-cloud-tile.d.ts.map +1 -1
  11. package/dist/point-cloud/point-cloud-tile.js +3 -0
  12. package/dist/point-cloud/point-cloud-tile.js.map +1 -1
  13. package/dist/point-cloud/point-cloud-tileset.d.ts +4 -0
  14. package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -1
  15. package/dist/point-cloud/point-cloud-tileset.js +54 -12
  16. package/dist/point-cloud/point-cloud-tileset.js.map +1 -1
  17. package/dist/point-cloud/types.d.ts +17 -0
  18. package/dist/point-cloud/types.d.ts.map +1 -1
  19. package/dist/point-cloud/types.js +3 -0
  20. package/dist/point-cloud/types.js.map +1 -1
  21. package/dist/raster-set.d.ts +37 -29
  22. package/dist/raster-set.d.ts.map +1 -1
  23. package/dist/raster-set.js +25 -2
  24. package/dist/raster-set.js.map +1 -1
  25. package/dist/spatial/format-spatial-reference.d.ts +67 -0
  26. package/dist/spatial/format-spatial-reference.d.ts.map +1 -0
  27. package/dist/spatial/format-spatial-reference.js +233 -0
  28. package/dist/spatial/format-spatial-reference.js.map +1 -0
  29. package/dist/spatial/get-spatial-coordinate-frame.d.ts +9 -0
  30. package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -0
  31. package/dist/spatial/get-spatial-coordinate-frame.js +58 -0
  32. package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -0
  33. package/dist/spatial/i3s-elevation.d.ts +20 -0
  34. package/dist/spatial/i3s-elevation.d.ts.map +1 -0
  35. package/dist/spatial/i3s-elevation.js +90 -0
  36. package/dist/spatial/i3s-elevation.js.map +1 -0
  37. package/dist/spatial/i3s-spatial-transformer.d.ts +189 -0
  38. package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -0
  39. package/dist/spatial/i3s-spatial-transformer.js +686 -0
  40. package/dist/spatial/i3s-spatial-transformer.js.map +1 -0
  41. package/dist/spatial/spatial-coordinate-transformer.d.ts +48 -0
  42. package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -0
  43. package/dist/spatial/spatial-coordinate-transformer.js +211 -0
  44. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -0
  45. package/dist/spatial/spatial-resource-registry.d.ts +44 -0
  46. package/dist/spatial/spatial-resource-registry.d.ts.map +1 -0
  47. package/dist/spatial/spatial-resource-registry.js +67 -0
  48. package/dist/spatial/spatial-resource-registry.js.map +1 -0
  49. package/dist/spatial/spatial-types.d.ts +190 -0
  50. package/dist/spatial/spatial-types.d.ts.map +1 -0
  51. package/dist/spatial/spatial-types.js +192 -0
  52. package/dist/spatial/spatial-types.js.map +1 -0
  53. package/dist/tileset-3d/common/tile-3d.d.ts +147 -4
  54. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  55. package/dist/tileset-3d/common/tile-3d.js +358 -76
  56. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  57. package/dist/tileset-3d/common/tileset-3d.d.ts +168 -3
  58. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  59. package/dist/tileset-3d/common/tileset-3d.js +306 -23
  60. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  61. package/dist/tileset-3d/common/tileset-cache.d.ts +11 -1
  62. package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -1
  63. package/dist/tileset-3d/common/tileset-cache.js +11 -3
  64. package/dist/tileset-3d/common/tileset-cache.js.map +1 -1
  65. package/dist/tileset-3d/common/tileset-source.d.ts +32 -0
  66. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  67. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  68. package/dist/tileset-3d/common/tileset-traverser.d.ts +19 -1
  69. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
  70. package/dist/tileset-3d/common/tileset-traverser.js +39 -7
  71. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
  72. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +136 -0
  73. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
  74. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +384 -0
  75. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
  76. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +86 -3
  77. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  78. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +196 -21
  79. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  80. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +36 -0
  81. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
  82. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +66 -0
  83. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
  84. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -1
  85. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js +3 -0
  86. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -1
  87. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +14 -1
  88. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
  89. package/dist/tileset-3d/format-i3s/i3s-source.js +107 -17
  90. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
  91. package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -1
  92. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js +3 -0
  93. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js.map +1 -1
  94. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
  95. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +3 -0
  96. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
  97. package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -1
  98. package/dist/tileset-3d/helpers/3d-tiles-options.js +3 -0
  99. package/dist/tileset-3d/helpers/3d-tiles-options.js.map +1 -1
  100. package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
  101. package/dist/tileset-3d/helpers/bounding-volume.js +4 -33
  102. package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
  103. package/dist/tileset-3d/helpers/frame-state.d.ts +62 -2
  104. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
  105. package/dist/tileset-3d/helpers/frame-state.js +85 -9
  106. package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
  107. package/dist/tileset-3d/helpers/i3s-lod.d.ts.map +1 -1
  108. package/dist/tileset-3d/helpers/i3s-lod.js +20 -8
  109. package/dist/tileset-3d/helpers/i3s-lod.js.map +1 -1
  110. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts +61 -9
  111. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -1
  112. package/dist/tileset-3d/helpers/tiles-3d-lod.js +198 -93
  113. package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -1
  114. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
  115. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
  116. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
  117. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
  118. package/dist/types.d.ts +5 -0
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/types.js +3 -0
  121. package/dist/types.js.map +1 -1
  122. package/dist/utils/doubly-linked-list.d.ts.map +1 -1
  123. package/dist/utils/doubly-linked-list.js +1 -0
  124. package/dist/utils/doubly-linked-list.js.map +1 -1
  125. package/dist/utils/managed-array.d.ts.map +1 -1
  126. package/dist/utils/managed-array.js +1 -0
  127. package/dist/utils/managed-array.js.map +1 -1
  128. package/package.json +12 -9
  129. package/src/index.ts +61 -1
  130. package/src/point-cloud/point-cloud-tile.ts +4 -0
  131. package/src/point-cloud/point-cloud-tileset.ts +70 -12
  132. package/src/point-cloud/types.ts +21 -0
  133. package/src/raster-set.ts +118 -45
  134. package/src/spatial/format-spatial-reference.ts +311 -0
  135. package/src/spatial/get-spatial-coordinate-frame.ts +74 -0
  136. package/src/spatial/i3s-elevation.ts +96 -0
  137. package/src/spatial/i3s-spatial-transformer.ts +957 -0
  138. package/src/spatial/spatial-coordinate-transformer.ts +276 -0
  139. package/src/spatial/spatial-resource-registry.ts +79 -0
  140. package/src/spatial/spatial-types.ts +422 -0
  141. package/src/tileset-3d/common/tile-3d.ts +435 -84
  142. package/src/tileset-3d/common/tileset-3d.ts +445 -23
  143. package/src/tileset-3d/common/tileset-cache.ts +15 -7
  144. package/src/tileset-3d/common/tileset-source.ts +36 -0
  145. package/src/tileset-3d/common/tileset-traverser.ts +40 -7
  146. package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +612 -0
  147. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +254 -21
  148. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +77 -0
  149. package/src/tileset-3d/format-i3s/i3s-pending-tiles-register.ts +4 -0
  150. package/src/tileset-3d/format-i3s/i3s-source.ts +119 -18
  151. package/src/tileset-3d/format-i3s/i3s-tile-manager.ts +4 -0
  152. package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +4 -0
  153. package/src/tileset-3d/helpers/3d-tiles-options.ts +4 -0
  154. package/src/tileset-3d/helpers/bounding-volume.ts +5 -55
  155. package/src/tileset-3d/helpers/frame-state.ts +142 -11
  156. package/src/tileset-3d/helpers/i3s-lod.ts +22 -8
  157. package/src/tileset-3d/helpers/tiles-3d-lod.ts +291 -112
  158. package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
  159. package/src/types.ts +9 -0
  160. package/src/utils/doubly-linked-list.ts +2 -0
  161. package/src/utils/managed-array.ts +2 -0
@@ -5,162 +5,214 @@
5
5
  // This file is derived from the Cesium code base under Apache 2 license
6
6
  // See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md
7
7
 
8
- // TODO - Dynamic screen space error provides an optimization when looking at
9
- // tilesets from above
8
+ import {Matrix4, Quaternion, Vector3, clamp} from '@math.gl/core';
9
+ import {Ellipsoid} from '@math.gl/geospatial';
10
+ import type {Tile3D} from '../common/tile-3d';
11
+ import type {FrameState} from './frame-state';
10
12
 
11
- /* eslint-disable */
12
- // @ts-nocheck
13
- import {Matrix4, Vector3, clamp} from '@math.gl/core';
13
+ /** Options that control the perspective dynamic screen-space error optimization. */
14
+ export type DynamicScreenSpaceErrorOptions = {
15
+ /** Base fog density used to reduce distant, horizon-facing screen-space error. */
16
+ dynamicScreenSpaceErrorDensity: number;
17
+ /** Maximum number of logical pixels subtracted from perspective screen-space error. */
18
+ dynamicScreenSpaceErrorFactor: number;
19
+ /** Fraction of the tileset height at which the optimization begins to fade. */
20
+ dynamicScreenSpaceErrorHeightFalloff: number;
21
+ };
22
+
23
+ type TileBoundingVolumeHeader = {
24
+ box?: number[];
25
+ region?: number[];
26
+ sphere?: number[];
27
+ };
28
+
29
+ type TilesetHeightRange = {
30
+ cameraHeight: number;
31
+ minimumHeight: number;
32
+ maximumHeight: number;
33
+ cameraDirection: Vector3;
34
+ upDirection: Vector3;
35
+ };
14
36
 
15
37
  const scratchPositionNormal = new Vector3();
16
38
  const scratchCartographic = new Vector3();
17
- const scratchMatrix = new Matrix4();
18
39
  const scratchCenter = new Vector3();
19
40
  const scratchPosition = new Vector3();
20
41
  const scratchDirection = new Vector3();
42
+ const scratchBoxXAxis = new Vector3();
43
+ const scratchBoxYAxis = new Vector3();
44
+ const scratchBoxZAxis = new Vector3();
45
+ const scratchBoxQuaternion = new Quaternion();
46
+ const unitZDirection = new Vector3(0, 0, 1);
21
47
 
22
- // eslint-disable-next-line max-statements, complexity
23
- export function calculateDynamicScreenSpaceError(root, {camera, mapProjection}, options = {}) {
24
- const {dynamicScreenSpaceErrorHeightFalloff = 0.25, dynamicScreenSpaceErrorDensity = 0.00278} =
25
- options;
26
-
27
- let up;
28
- let direction;
29
- let height;
30
- let minimumHeight;
31
- let maximumHeight;
32
-
33
- const tileBoundingVolume = root.contentBoundingVolume;
34
-
35
- if (tileBoundingVolume instanceof TileBoundingRegion) {
36
- up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
37
- direction = camera.directionWC;
38
- height = camera.positionCartographic.height;
39
- minimumHeight = tileBoundingVolume.minimumHeight;
40
- maximumHeight = tileBoundingVolume.maximumHeight;
41
- } else {
42
- // Transform camera position and direction into the local coordinate system of the tileset
43
- const transformLocal = Matrix4.inverseTransformation(root.computedTransform, scratchMatrix);
44
- const ellipsoid = mapProjection.ellipsoid;
45
- const boundingVolume = tileBoundingVolume.boundingVolume;
46
- const centerLocal = Matrix4.multiplyByPoint(
47
- transformLocal,
48
- boundingVolume.center,
49
- scratchCenter
50
- );
51
- if (Cartesian3.magnitude(centerLocal) > ellipsoid.minimumRadius) {
52
- // The tileset is defined in WGS84. Approximate the minimum and maximum height.
53
- const centerCartographic = Cartographic.fromCartesian(
54
- centerLocal,
55
- ellipsoid,
56
- scratchCartographic
57
- );
58
- up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
59
- direction = camera.directionWC;
60
- height = camera.positionCartographic.height;
61
- minimumHeight = 0.0;
62
- maximumHeight = centerCartographic.height * 2.0;
63
- } else {
64
- // The tileset is defined in local coordinates (z-up)
65
- const positionLocal = Matrix4.multiplyByPoint(
66
- transformLocal,
67
- camera.positionWC,
68
- scratchPosition
69
- );
70
- up = Cartesian3.UNIT_Z;
71
- direction = Matrix4.multiplyByPointAsVector(
72
- transformLocal,
73
- camera.directionWC,
74
- scratchDirection
75
- );
76
- direction = Cartesian3.normalize(direction, direction);
77
- height = positionLocal.z;
78
- if (tileBoundingVolume instanceof TileOrientedBoundingBox) {
79
- // Assuming z-up, the last component stores the half-height of the box
80
- const boxHeight = root._header.boundingVolume.box[11];
81
- minimumHeight = centerLocal.z - boxHeight;
82
- maximumHeight = centerLocal.z + boxHeight;
83
- } else if (tileBoundingVolume instanceof TileBoundingSphere) {
84
- const radius = boundingVolume.radius;
85
- minimumHeight = centerLocal.z - radius;
86
- maximumHeight = centerLocal.z + radius;
87
- }
88
- }
89
- }
48
+ /**
49
+ * Refreshes the root transform before calculating transform-dependent dynamic SSE density.
50
+ *
51
+ * Traversal normally updates transforms while visiting tile visibility, but density is calculated
52
+ * immediately before traversal. Performing the root update at that earlier boundary keeps local
53
+ * box and sphere height falloff synchronized with an animated tileset model matrix in the same
54
+ * frame. {@link Tile3D._updateTransform} also recalculates world-space geometric error from its raw
55
+ * source value, so repeated calls do not compound scale.
56
+ *
57
+ * @param root - Root tile whose computed transform feeds dynamic SSE height calculations.
58
+ * @param modelMatrix - Current tileset-to-world transform for this frame.
59
+ */
60
+ export function updateRootTransformForDynamicScreenSpaceError(
61
+ root: Tile3D,
62
+ modelMatrix: Matrix4
63
+ ): void {
64
+ root._updateTransform(modelMatrix);
65
+ }
90
66
 
91
- // The range where the density starts to lessen. Start at the quarter height of the tileset.
92
- const heightFalloff = dynamicScreenSpaceErrorHeightFalloff;
93
- const heightClose = minimumHeight + (maximumHeight - minimumHeight) * heightFalloff;
94
- const heightFar = maximumHeight;
67
+ /**
68
+ * Calculates the effective dynamic screen-space error density for one viewport and frame.
69
+ *
70
+ * Dynamic SSE is a perspective-only optimization for street-level views. It reduces refinement
71
+ * for distant tiles near the horizon, where loading every geometric detail is expensive and has
72
+ * limited visual benefit. Density fades to zero as the camera rises above the tileset or points
73
+ * vertically. The returned value belongs to the supplied frame state; callers must not reuse it
74
+ * for another viewport because camera height and direction are view dependent.
75
+ *
76
+ * @param root - Root tile whose transform and source bounding volume define the tileset space.
77
+ * @param frameState - Current camera measurements in WGS84 and logical viewport space.
78
+ * @param options - Dynamic SSE density and height-falloff controls.
79
+ * @returns Effective fog density for this viewport, or zero when it cannot be calculated.
80
+ */
81
+ export function calculateDynamicScreenSpaceErrorDensity(
82
+ root: Tile3D,
83
+ frameState: FrameState,
84
+ options: DynamicScreenSpaceErrorOptions
85
+ ): number {
86
+ if (
87
+ !Number.isFinite(options.dynamicScreenSpaceErrorDensity) ||
88
+ options.dynamicScreenSpaceErrorDensity <= 0
89
+ ) {
90
+ return 0;
91
+ }
95
92
 
96
- const t = clamp((height - heightClose) / (heightFar - heightClose), 0.0, 1.0);
93
+ const boundingVolumeHeader = getContentBoundingVolumeHeader(root);
94
+ const heightRange = boundingVolumeHeader.region
95
+ ? getRegionHeightRange(boundingVolumeHeader.region, frameState)
96
+ : getCartesianHeightRange(root, boundingVolumeHeader, frameState);
97
97
 
98
- // Increase density as the camera tilts towards the horizon
99
- const dot = Math.abs(Cartesian3.dot(direction, up));
98
+ if (!heightRange) {
99
+ return 0;
100
+ }
100
101
 
101
- let horizonFactor = 1.0 - dot;
102
+ const heightFalloff = clamp(options.dynamicScreenSpaceErrorHeightFalloff, 0, 1);
103
+ const heightClose =
104
+ heightRange.minimumHeight +
105
+ (heightRange.maximumHeight - heightRange.minimumHeight) * heightFalloff;
106
+ const heightSpan = heightRange.maximumHeight - heightClose;
107
+ const heightPercentage =
108
+ heightSpan > 0
109
+ ? clamp((heightRange.cameraHeight - heightClose) / heightSpan, 0, 1)
110
+ : heightRange.cameraHeight >= heightRange.maximumHeight
111
+ ? 1
112
+ : 0;
102
113
 
103
- // Weaken the horizon factor as the camera height increases, implying the camera is further away from the tileset.
104
- // The goal is to increase density for the "street view", not when viewing the tileset from a distance.
105
- horizonFactor = horizonFactor * (1.0 - t);
114
+ // A horizontal view has a dot product near zero and receives the strongest optimization.
115
+ // A vertical view has a dot product near one and keeps the unmodified perspective SSE.
116
+ const verticalAlignment = clamp(
117
+ Math.abs(heightRange.cameraDirection.dot(heightRange.upDirection)),
118
+ 0,
119
+ 1
120
+ );
121
+ const horizonFactor = (1 - verticalAlignment) * (1 - heightPercentage);
106
122
 
107
- return dynamicScreenSpaceErrorDensity * horizonFactor;
123
+ return options.dynamicScreenSpaceErrorDensity * horizonFactor;
108
124
  }
109
125
 
110
- export function fog(distanceToCamera, density) {
126
+ /**
127
+ * Calculates exponential fog strength for a camera distance and density.
128
+ * @param distanceToCamera - Distance from the camera to the tile bounding volume in meters.
129
+ * @param density - Effective dynamic SSE density in inverse meters.
130
+ * @returns Unitless strength in the inclusive range from zero to one.
131
+ */
132
+ export function getDynamicScreenSpaceErrorFog(distanceToCamera: number, density: number): number {
111
133
  const scalar = distanceToCamera * density;
112
- return 1.0 - Math.exp(-(scalar * scalar));
134
+ return 1 - Math.exp(-(scalar * scalar));
113
135
  }
114
136
 
115
- export function getDynamicScreenSpaceError(tileset, distanceToCamera) {
116
- if (tileset.dynamicScreenSpaceError && tileset.dynamicScreenSpaceErrorComputedDensity) {
117
- const density = tileset.dynamicScreenSpaceErrorComputedDensity;
118
- const factor = tileset.dynamicScreenSpaceErrorFactor;
119
- // TODO: Refined screen space error that minimizes tiles in non-first-person
120
- const dynamicError = fog(distanceToCamera, density) * factor;
121
- return dynamicError;
137
+ /**
138
+ * Calculates the perspective SSE reduction for a tile.
139
+ * @param distanceToCamera - Distance from the camera to the tile bounding volume in meters.
140
+ * @param density - View-dependent fog density calculated for the current frame.
141
+ * @param factor - Maximum reduction in logical/CSS pixels.
142
+ * @returns Number of logical pixels to subtract from perspective SSE.
143
+ */
144
+ export function getDynamicScreenSpaceError(
145
+ distanceToCamera: number,
146
+ density: number,
147
+ factor: number
148
+ ): number {
149
+ if (density <= 0 || factor <= 0 || !Number.isFinite(density) || !Number.isFinite(factor)) {
150
+ return 0;
122
151
  }
123
152
 
124
- return 0;
153
+ return getDynamicScreenSpaceErrorFog(distanceToCamera, density) * factor;
125
154
  }
126
155
 
127
156
  /**
128
157
  * Calculates 3D Tiles screen-space error (SSE) for the current projection.
129
158
  *
130
159
  * Perspective SSE projects the tile's world-space geometric error through the existing viewport
131
- * height and frustum denominator. Orthographic projection has no perspective distance falloff, so
132
- * its error is divided directly by the viewport's world-space meters per logical pixel.
160
+ * height and frustum denominator. Dynamic SSE may then reduce distant horizon refinement using
161
+ * the density calculated specifically for this frame and viewport. Orthographic projection has
162
+ * no perspective distance falloff, so its error is divided directly by the viewport's world-space
163
+ * meters per logical pixel and is never adjusted by perspective dynamic SSE.
133
164
  *
134
165
  * @param tile - Tile containing the transform-scaled geometric error and camera distance.
135
166
  * @param frameState - Current camera and viewport measurements.
136
167
  * @param useParentLodMetric - Whether request prioritization should use the parent's error.
168
+ * @param viewportHeightFraction - Fraction of viewport height represented by the priority pass.
137
169
  * @returns Estimated error in logical/CSS pixels.
138
170
  */
139
- export function getTiles3DScreenSpaceError(tile, frameState, useParentLodMetric) {
171
+ export function getTiles3DScreenSpaceError(
172
+ tile: Tile3D,
173
+ frameState: FrameState,
174
+ useParentLodMetric: boolean,
175
+ viewportHeightFraction = 1
176
+ ): number {
140
177
  const tileset = tile.tileset;
141
178
  const parentLodMetricValue = (tile.parent && tile.parent.lodMetricValue) || tile.lodMetricValue;
142
179
  const lodMetricValue = useParentLodMetric ? parentLodMetricValue : tile.lodMetricValue;
143
180
 
144
- // Leaf tiles do not have any error so save the computation
145
- if (lodMetricValue === 0.0) {
146
- return 0.0;
181
+ // Leaf tiles do not have any error so save the computation.
182
+ if (lodMetricValue === 0) {
183
+ return 0;
147
184
  }
148
185
 
149
186
  const {viewDistanceScale} = tileset.options;
150
- const lodScale = viewDistanceScale || 1.0;
151
- const orthographicError = getOrthographicScreenSpaceError(lodMetricValue, frameState, lodScale);
187
+ const lodScale = viewDistanceScale || 1;
188
+ const heightFraction = Number.isFinite(viewportHeightFraction)
189
+ ? Math.max(viewportHeightFraction, 0)
190
+ : 1;
191
+ const orthographicError = getOrthographicScreenSpaceError(
192
+ lodMetricValue,
193
+ frameState,
194
+ lodScale,
195
+ heightFraction
196
+ );
152
197
  if (orthographicError !== null) {
153
198
  return orthographicError;
154
199
  }
155
200
 
156
- // Avoid divide by zero when viewer is inside the tile
157
- const distance = Math.max(tile._distanceToCamera, 1e-7);
201
+ // Avoid divide by zero when viewer is inside the tile.
202
+ const distanceToCamera = Math.max(tile._distanceToCamera, 1e-7);
158
203
  const {height, sseDenominator} = frameState;
159
- let error = (lodMetricValue * height * lodScale) / (distance * sseDenominator);
204
+ let screenSpaceError =
205
+ (lodMetricValue * height * heightFraction * lodScale) / (distanceToCamera * sseDenominator);
160
206
 
161
- error -= getDynamicScreenSpaceError(tileset, distance);
207
+ if (tileset.options.dynamicScreenSpaceError && !frameState.viewport.orthographic) {
208
+ screenSpaceError -= getDynamicScreenSpaceError(
209
+ distanceToCamera,
210
+ frameState.dynamicScreenSpaceErrorDensity,
211
+ tileset.options.dynamicScreenSpaceErrorFactor
212
+ );
213
+ }
162
214
 
163
- return error;
215
+ return screenSpaceError;
164
216
  }
165
217
 
166
218
  /**
@@ -174,18 +226,145 @@ export function getTiles3DScreenSpaceError(tile, frameState, useParentLodMetric)
174
226
  * @param lodMetricValue - Transform-scaled geometric error in world-space meters.
175
227
  * @param frameState - Current frame state containing the viewport.
176
228
  * @param lodScale - Application refinement scale from `viewDistanceScale`.
229
+ * @param viewportHeightFraction - Fraction of logical viewport height represented by the pass.
177
230
  * @returns SSE in logical pixels, or `null` when orthographic SSE cannot be calculated.
178
231
  */
179
- function getOrthographicScreenSpaceError(lodMetricValue, frameState, lodScale) {
232
+ function getOrthographicScreenSpaceError(
233
+ lodMetricValue: number,
234
+ frameState: FrameState,
235
+ lodScale: number,
236
+ viewportHeightFraction: number
237
+ ): number | null {
180
238
  const viewport = frameState.viewport;
181
239
  if (!viewport?.orthographic) {
182
240
  return null;
183
241
  }
184
242
 
185
243
  const metersPerPixel = viewport.metersPerPixel;
186
- if (!Number.isFinite(metersPerPixel) || metersPerPixel <= 0) {
244
+ if (!Number.isFinite(metersPerPixel) || !metersPerPixel || metersPerPixel <= 0) {
187
245
  return null;
188
246
  }
189
247
 
190
- return (lodMetricValue * lodScale) / metersPerPixel;
248
+ return (lodMetricValue * lodScale * viewportHeightFraction) / metersPerPixel;
249
+ }
250
+
251
+ /** Returns the content bounding volume header, falling back to the traversal volume. */
252
+ function getContentBoundingVolumeHeader(root: Tile3D): TileBoundingVolumeHeader {
253
+ return root.header.content?.boundingVolume || root.header.boundingVolume;
254
+ }
255
+
256
+ /** Calculates camera and tileset heights for a geodetic region bounding volume. */
257
+ function getRegionHeightRange(region: number[], frameState: FrameState): TilesetHeightRange {
258
+ const upDirection = scratchPositionNormal.copy(frameState.camera.position).normalize();
259
+ const cameraDirection = scratchDirection.copy(frameState.camera.direction).normalize();
260
+
261
+ return {
262
+ cameraHeight: frameState.camera.cartographicPosition[2],
263
+ minimumHeight: region[4],
264
+ maximumHeight: region[5],
265
+ cameraDirection,
266
+ upDirection
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Calculates camera and tileset heights for box and sphere volumes.
272
+ *
273
+ * The root transform is inverted so that both camera height and source volume dimensions use the
274
+ * same coordinate system. Small coordinates are treated as local z-up. Earth-scale coordinates
275
+ * are treated as WGS84 and use cartographic height, matching Cesium's conservative approximation.
276
+ */
277
+ function getCartesianHeightRange(
278
+ root: Tile3D,
279
+ boundingVolumeHeader: TileBoundingVolumeHeader,
280
+ frameState: FrameState
281
+ ): TilesetHeightRange | null {
282
+ const halfHeight = getBoundingVolumeCenterAndHalfHeight(boundingVolumeHeader, scratchCenter);
283
+ if (halfHeight === null) {
284
+ return null;
285
+ }
286
+
287
+ const localTransform = new Matrix4(root.computedTransform).invert();
288
+ const ellipsoid = root.tileset.ellipsoid as Ellipsoid;
289
+ const centerMagnitude = Math.hypot(scratchCenter[0], scratchCenter[1], scratchCenter[2]);
290
+
291
+ if (centerMagnitude > ellipsoid.minimumRadius) {
292
+ const centerCartographic = ellipsoid.cartesianToCartographic(
293
+ scratchCenter,
294
+ scratchCartographic
295
+ );
296
+ const upDirection = scratchPositionNormal.copy(frameState.camera.position).normalize();
297
+ const cameraDirection = scratchDirection.copy(frameState.camera.direction).normalize();
298
+
299
+ return {
300
+ cameraHeight: frameState.camera.cartographicPosition[2],
301
+ minimumHeight: 0,
302
+ maximumHeight: centerCartographic[2] * 2,
303
+ cameraDirection,
304
+ upDirection
305
+ };
306
+ }
307
+
308
+ localTransform.transformAsPoint(frameState.camera.position, scratchPosition);
309
+ localTransform.transformAsVector(frameState.camera.direction, scratchDirection);
310
+ scratchDirection.normalize();
311
+
312
+ return {
313
+ cameraHeight: scratchPosition[2],
314
+ minimumHeight: scratchCenter[2] - halfHeight,
315
+ maximumHeight: scratchCenter[2] + halfHeight,
316
+ cameraDirection: scratchDirection,
317
+ upDirection: unitZDirection
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Copies the source volume center and returns its conservative z-up half-height.
323
+ * @param boundingVolumeHeader - Root content or traversal volume from tileset JSON.
324
+ * @param result - Scratch vector that receives the source-space center.
325
+ * @returns Half-height in source-space meters, or `null` for an unsupported volume.
326
+ */
327
+ function getBoundingVolumeCenterAndHalfHeight(
328
+ boundingVolumeHeader: TileBoundingVolumeHeader,
329
+ result: Vector3
330
+ ): number | null {
331
+ if (boundingVolumeHeader.box) {
332
+ const box = boundingVolumeHeader.box;
333
+ result.set(box[0], box[1], box[2]);
334
+ return getBoxHalfHeight(box);
335
+ }
336
+
337
+ if (boundingVolumeHeader.sphere) {
338
+ const sphere = boundingVolumeHeader.sphere;
339
+ result.set(sphere[0], sphere[1], sphere[2]);
340
+ return sphere[3];
341
+ }
342
+
343
+ return null;
344
+ }
345
+
346
+ /**
347
+ * Returns the conservative source-z extent of an oriented box.
348
+ *
349
+ * A 12-component 3D Tiles box stores three half-axis vectors, so every vector can contribute to
350
+ * vertical height after rotation. The alternate 10-component representation stores half sizes and
351
+ * a quaternion; rotating its three scaled basis vectors produces the same sum of absolute z
352
+ * projections. Using only the nominal z axis would underestimate tilted volumes and fade dynamic
353
+ * SSE too early while the camera is still within the tileset's height range.
354
+ *
355
+ * @param box - Source-space oriented box in half-axis or half-size/quaternion form.
356
+ * @returns Conservative source-z half-height in the box coordinate system's distance units.
357
+ */
358
+ function getBoxHalfHeight(box: number[]): number {
359
+ if (box.length === 10) {
360
+ scratchBoxQuaternion.fromArray(box, 6);
361
+ scratchBoxXAxis.set(box[3], 0, 0).transformByQuaternion(scratchBoxQuaternion);
362
+ scratchBoxYAxis.set(0, box[4], 0).transformByQuaternion(scratchBoxQuaternion);
363
+ scratchBoxZAxis.set(0, 0, box[5]).transformByQuaternion(scratchBoxQuaternion);
364
+ return (
365
+ Math.abs(scratchBoxXAxis[2]) + Math.abs(scratchBoxYAxis[2]) + Math.abs(scratchBoxZAxis[2])
366
+ );
367
+ }
368
+
369
+ return Math.abs(box[5]) + Math.abs(box[8]) + Math.abs(box[11]);
191
370
  }