@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.0
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/README.md +59 -2
- package/dist/core.js +36923 -0
- package/dist/core.js.map +6 -0
- package/dist/index.js +32 -0
- package/dist/loaders-gl-worker.js +82 -0
- package/dist/loaders-gl-worker.js.map +6 -0
- package/dist/map-logic.js +2 -0
- package/dist/map-logic.js.map +6 -0
- package/dist/projected-basemaps-worker.js +3 -0
- package/dist/projected-basemaps-worker.js.map +6 -0
- package/dist/shader-modules.js +141 -0
- package/dist/shader-modules.js.map +6 -0
- package/dist/style-schema.js +126 -0
- package/dist/style-schema.js.map +6 -0
- package/dist/tangram-style.schema.json +882 -0
- package/dist/tangram.debug.mjs +37139 -0
- package/dist/tangram.debug.mjs.map +6 -0
- package/dist/tangram.min.mjs +4950 -0
- package/dist/types/builders/earcut.d.ts +15 -0
- package/dist/types/core.d.ts +29 -0
- package/dist/types/gl/constants.d.ts +2 -0
- package/dist/types/gl/globe_mesh.d.ts +48 -0
- package/dist/types/gl/glsl.d.ts +17 -0
- package/dist/types/gl/vertex-constants.d.ts +4 -0
- package/dist/types/gl/vertex-types.d.ts +34 -0
- package/dist/types/gl/vertex_data.d.ts +32 -0
- package/dist/types/gl/vertex_elements.d.ts +8 -0
- package/dist/types/gl/vertex_layout.d.ts +32 -0
- package/dist/types/glsl.d.ts +14 -0
- package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
- package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
- package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/lights/light-definitions.d.ts +121 -0
- package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
- package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
- package/dist/types/map-logic/globe-terrain.d.ts +54 -0
- package/dist/types/map-logic/index.d.ts +33 -0
- package/dist/types/map-logic/label-identity.d.ts +22 -0
- package/dist/types/map-logic/label-placement.d.ts +32 -0
- package/dist/types/map-logic/screen-bounds.d.ts +15 -0
- package/dist/types/map-logic/terrain-data.d.ts +55 -0
- package/dist/types/map-logic/terrain-error.d.ts +51 -0
- package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
- package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
- package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
- package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
- package/dist/types/map-logic/terrain-query.d.ts +33 -0
- package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
- package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
- package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
- package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
- package/dist/types/map-logic/tile-residency.d.ts +17 -0
- package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
- package/dist/types/procedures/js-yaml.d.ts +15 -0
- package/dist/types/procedures/mesh-projector.d.ts +107 -0
- package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
- package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
- package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
- package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
- package/dist/types/procedures/terrain-draping.d.ts +165 -0
- package/dist/types/procedures/terrain-globe.d.ts +6 -0
- package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
- package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
- package/dist/types/procedures/web-mercator-math.d.ts +4 -0
- package/dist/types/scene/globals.d.ts +11 -0
- package/dist/types/scene/ground_footprint.d.ts +16 -0
- package/dist/types/scene/host_frame.d.ts +54 -0
- package/dist/types/scene/projection_math.d.ts +52 -0
- package/dist/types/scene/scene-resource-types.d.ts +177 -0
- package/dist/types/scene/scene_bundle.d.ts +69 -0
- package/dist/types/scene/visibility_adapter.d.ts +106 -0
- package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
- package/dist/types/selection/terrain_surface.d.ts +149 -0
- package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
- package/dist/types/shader-modules/height-decode.d.ts +13 -0
- package/dist/types/shader-modules/hillshade.d.ts +13 -0
- package/dist/types/shader-modules/index.d.ts +6 -0
- package/dist/types/shader-modules/planar.d.ts +12 -0
- package/dist/types/shader-modules/sphere-map.d.ts +13 -0
- package/dist/types/shader-modules/triplanar.d.ts +15 -0
- package/dist/types/sources/decoded_tile_store.d.ts +95 -0
- package/dist/types/sources/tile_source_metadata.d.ts +32 -0
- package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
- package/dist/types/tile/tile_id.d.ts +37 -0
- package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
- package/dist/types/types.d.ts +293 -0
- package/dist/types/utils/geo.d.ts +57 -0
- package/dist/types/utils/hash.d.ts +1 -0
- package/dist/types/utils/log.d.ts +27 -0
- package/dist/types/utils/props.d.ts +6 -0
- package/dist/types/utils/subscribe.d.ts +10 -0
- package/dist/types/utils/thread.d.ts +6 -0
- package/dist/types/utils/urls.d.ts +26 -0
- package/dist/types/utils/utils.d.ts +26 -0
- package/dist/types/utils/version.d.ts +2 -0
- package/dist/types/utils/worker-types.d.ts +68 -0
- package/dist/types/utils/worker_broker.d.ts +3 -0
- package/package.json +103 -3
- package/src/builders/common.ts +41 -0
- package/src/builders/earcut.d.ts +15 -0
- package/src/builders/geometry-types.ts +60 -0
- package/src/builders/points.ts +115 -0
- package/src/builders/polygons.ts +225 -0
- package/src/builders/polylines.ts +682 -0
- package/src/builders/wireframe.ts +31 -0
- package/src/core.d.ts +29 -0
- package/src/core.ts +33 -0
- package/src/experimental/loaders-gl-worker.ts +19 -0
- package/src/experimental/projected-basemaps-worker.ts +13 -0
- package/src/experimental/projected-mesh-cache.ts +137 -0
- package/src/experimental/projected-mesh-domain.ts +110 -0
- package/src/experimental/projected-mesh-refinement.ts +193 -0
- package/src/experimental/projected-mesh.ts +399 -0
- package/src/experimental/projected-normal.ts +26 -0
- package/src/experimental/projected-symbol-mesh.ts +74 -0
- package/src/gl/constants.ts +18 -0
- package/src/gl/context.ts +103 -0
- package/src/gl/extensions.ts +30 -0
- package/src/gl/globe_mesh.ts +285 -0
- package/src/gl/glsl.ts +257 -0
- package/src/gl/mesh-types.ts +56 -0
- package/src/gl/render_state.ts +111 -0
- package/src/gl/shader_program.ts +1310 -0
- package/src/gl/texture.ts +814 -0
- package/src/gl/uniform_buffer.ts +340 -0
- package/src/gl/vao.ts +154 -0
- package/src/gl/vbo_mesh.ts +349 -0
- package/src/gl/vertex-constants.ts +19 -0
- package/src/gl/vertex-types.ts +35 -0
- package/src/gl/vertex_data.ts +126 -0
- package/src/gl/vertex_elements.ts +54 -0
- package/src/gl/vertex_layout.ts +241 -0
- package/src/glsl.d.ts +14 -0
- package/src/gpu/luma_device_renderer.ts +579 -0
- package/src/gpu/pixel_readback.ts +51 -0
- package/src/gpu/resource_lifecycle.ts +32 -0
- package/src/gpu/tangram_gpu_backend.ts +195 -0
- package/src/index.d.ts +155 -0
- package/src/index.ts +105 -0
- package/src/labels/collision-types.ts +94 -0
- package/src/labels/collision.ts +273 -0
- package/src/labels/collision_grid.ts +61 -0
- package/src/labels/intersect.ts +30 -0
- package/src/labels/label-types.ts +91 -0
- package/src/labels/label.ts +207 -0
- package/src/labels/label_line.ts +801 -0
- package/src/labels/label_point.ts +131 -0
- package/src/labels/main-pass-types.ts +73 -0
- package/src/labels/main_pass.ts +221 -0
- package/src/labels/point_anchor.ts +100 -0
- package/src/labels/point_placement.ts +150 -0
- package/src/labels/projected-pass.ts +259 -0
- package/src/labels/repeat_group.ts +72 -0
- package/src/lights/ambient_light.glsl +18 -0
- package/src/lights/directional_light.glsl +39 -0
- package/src/lights/geographic-lights.ts +21 -0
- package/src/lights/light-definitions.ts +285 -0
- package/src/lights/light.ts +590 -0
- package/src/lights/lighting-uniforms.ts +80 -0
- package/src/lights/lighting-wgsl.ts +116 -0
- package/src/lights/material.glsl +237 -0
- package/src/lights/material.ts +145 -0
- package/src/lights/native-falloff.ts +37 -0
- package/src/lights/point_light.glsl +96 -0
- package/src/lights/spot_light.glsl +111 -0
- package/src/map-logic/draped-terrain-bounds.ts +71 -0
- package/src/map-logic/globe-terrain-preparation.ts +217 -0
- package/src/map-logic/globe-terrain.ts +296 -0
- package/src/map-logic/index.ts +37 -0
- package/src/map-logic/label-identity.ts +36 -0
- package/src/map-logic/label-placement.ts +117 -0
- package/src/map-logic/screen-bounds.ts +34 -0
- package/src/map-logic/terrain-data.ts +107 -0
- package/src/map-logic/terrain-error.ts +134 -0
- package/src/map-logic/terrain-ground-topology.ts +164 -0
- package/src/map-logic/terrain-height-field.ts +254 -0
- package/src/map-logic/terrain-navigation.ts +50 -0
- package/src/map-logic/terrain-neighborhood.ts +43 -0
- package/src/map-logic/terrain-query.ts +51 -0
- package/src/map-logic/terrain-resource-budget.ts +124 -0
- package/src/map-logic/terrain-transition-topology.ts +164 -0
- package/src/map-logic/tile-build-queue.ts +110 -0
- package/src/map-logic/tile-cache-policy.ts +89 -0
- package/src/map-logic/tile-residency.ts +50 -0
- package/src/map-logic/triangle-query-index.ts +265 -0
- package/src/procedures/js-yaml.d.ts +15 -0
- package/src/procedures/mesh-projector.ts +191 -0
- package/src/procedures/mlt-loaders.ts +55 -0
- package/src/procedures/mvt-dependencies.d.ts +20 -0
- package/src/procedures/mvt-legacy.ts +150 -0
- package/src/procedures/mvt-loaders.ts +53 -0
- package/src/procedures/mvt-parser.ts +48 -0
- package/src/procedures/mvt-properties.ts +36 -0
- package/src/procedures/mvt-tile-provider.ts +72 -0
- package/src/procedures/pmtiles-loader.ts +75 -0
- package/src/procedures/projected-coordinate-transform.ts +79 -0
- package/src/procedures/projection-batch-executor.ts +140 -0
- package/src/procedures/scene-yaml-legacy.ts +16 -0
- package/src/procedures/scene-yaml-loaders.ts +10 -0
- package/src/procedures/terrain-draping.ts +713 -0
- package/src/procedures/terrain-globe.ts +32 -0
- package/src/procedures/terrain-styled-topology.ts +184 -0
- package/src/procedures/web-mercator-legacy.ts +24 -0
- package/src/procedures/web-mercator-math.ts +50 -0
- package/src/scene/camera.ts +298 -0
- package/src/scene/camera_base.ts +116 -0
- package/src/scene/classic_scene.ts +20 -0
- package/src/scene/external_camera.ts +101 -0
- package/src/scene/globals.ts +120 -0
- package/src/scene/ground_footprint.ts +139 -0
- package/src/scene/host_frame.ts +326 -0
- package/src/scene/projected_tile_lod.ts +125 -0
- package/src/scene/projection_math.ts +142 -0
- package/src/scene/projection_shaders.ts +183 -0
- package/src/scene/renderer.ts +516 -0
- package/src/scene/scene-resource-types.ts +174 -0
- package/src/scene/scene.ts +2378 -0
- package/src/scene/scene_bundle.ts +304 -0
- package/src/scene/scene_debug.ts +129 -0
- package/src/scene/scene_loader.ts +402 -0
- package/src/scene/scene_worker.ts +528 -0
- package/src/scene/view.ts +683 -0
- package/src/scene/visibility_adapter.ts +319 -0
- package/src/scene/worker_url.ts +15 -0
- package/src/selection/draped-terrain-surface.ts +377 -0
- package/src/selection/selection-types.ts +79 -0
- package/src/selection/selection.ts +542 -0
- package/src/selection/selection_fragment.glsl +18 -0
- package/src/selection/selection_globals.glsl +9 -0
- package/src/selection/selection_pixels.ts +59 -0
- package/src/selection/selection_vertex.glsl +15 -0
- package/src/selection/terrain_surface.ts +298 -0
- package/src/shader-modules/globe-horizon.ts +52 -0
- package/src/shader-modules/height-decode.ts +41 -0
- package/src/shader-modules/hillshade.ts +70 -0
- package/src/shader-modules/index.ts +10 -0
- package/src/shader-modules/planar.ts +24 -0
- package/src/shader-modules/sphere-map.ts +38 -0
- package/src/shader-modules/triplanar.ts +45 -0
- package/src/sources/aligned_tile_source.ts +122 -0
- package/src/sources/data_source.ts +655 -0
- package/src/sources/decoded_tile_store.ts +245 -0
- package/src/sources/geojson.ts +300 -0
- package/src/sources/mvt.ts +247 -0
- package/src/sources/raster.ts +341 -0
- package/src/sources/shared_tile_source_adapter.ts +117 -0
- package/src/sources/sources.ts +10 -0
- package/src/sources/tile_source_adapter.ts +84 -0
- package/src/sources/tile_source_metadata.ts +65 -0
- package/src/sources/tile_source_state.ts +112 -0
- package/src/sources/topojson.ts +90 -0
- package/src/styles/feature_annotations.ts +16 -0
- package/src/styles/filter.ts +190 -0
- package/src/styles/geometry-style-types.ts +209 -0
- package/src/styles/globe_visibility_wgsl.ts +22 -0
- package/src/styles/layer-types.ts +45 -0
- package/src/styles/layer.ts +586 -0
- package/src/styles/lines/dasharray.ts +49 -0
- package/src/styles/lines/lines.ts +707 -0
- package/src/styles/lines/lines_wgsl.ts +224 -0
- package/src/styles/points/point-runtime-types.ts +125 -0
- package/src/styles/points/points.ts +1079 -0
- package/src/styles/points/points_fragment.glsl +126 -0
- package/src/styles/points/points_vertex.glsl +230 -0
- package/src/styles/points/points_wgsl.ts +189 -0
- package/src/styles/polygons/polygons.ts +268 -0
- package/src/styles/polygons/polygons_fragment.glsl +173 -0
- package/src/styles/polygons/polygons_vertex.glsl +213 -0
- package/src/styles/polygons/polygons_wgsl.ts +127 -0
- package/src/styles/property-types.ts +125 -0
- package/src/styles/raster/raster.ts +33 -0
- package/src/styles/raster/raster_globals.glsl +46 -0
- package/src/styles/style-mixing-types.ts +101 -0
- package/src/styles/style-runtime-types.ts +220 -0
- package/src/styles/style-schema.ts +133 -0
- package/src/styles/style.ts +1033 -0
- package/src/styles/style_globals.glsl +69 -0
- package/src/styles/style_manager.ts +464 -0
- package/src/styles/style_parser.ts +620 -0
- package/src/styles/text/font_manager.ts +180 -0
- package/src/styles/text/fontfaceobserver.d.ts +10 -0
- package/src/styles/text/text-types.ts +220 -0
- package/src/styles/text/text.ts +378 -0
- package/src/styles/text/text_canvas.ts +773 -0
- package/src/styles/text/text_labels.ts +436 -0
- package/src/styles/text/text_segments.ts +142 -0
- package/src/styles/text/text_settings.ts +228 -0
- package/src/styles/text/text_wgsl.ts +159 -0
- package/src/styles/text/text_wrap.ts +194 -0
- package/src/tile/globe_tile_preload.ts +40 -0
- package/src/tile/tangram_tileset_2d.ts +108 -0
- package/src/tile/tile-build-types.ts +155 -0
- package/src/tile/tile.ts +807 -0
- package/src/tile/tile_id.ts +172 -0
- package/src/tile/tile_manager.ts +889 -0
- package/src/tile/tile_pyramid.ts +119 -0
- package/src/tile/tile_resource_cache.ts +91 -0
- package/src/tile/tile_traversal_adapter.ts +132 -0
- package/src/types.ts +332 -0
- package/src/utils/debounce.ts +17 -0
- package/src/utils/debug_settings.ts +43 -0
- package/src/utils/errors.ts +12 -0
- package/src/utils/functions.ts +95 -0
- package/src/utils/geo.ts +364 -0
- package/src/utils/hash.ts +14 -0
- package/src/utils/log.ts +108 -0
- package/src/utils/media_capture.ts +193 -0
- package/src/utils/merge.ts +44 -0
- package/src/utils/obb.ts +134 -0
- package/src/utils/props.ts +43 -0
- package/src/utils/slice.ts +14 -0
- package/src/utils/subscribe.ts +58 -0
- package/src/utils/task.ts +154 -0
- package/src/utils/thread.ts +26 -0
- package/src/utils/urls.ts +223 -0
- package/src/utils/utils.ts +310 -0
- package/src/utils/vector.ts +197 -0
- package/src/utils/version.ts +8 -0
- package/src/utils/worker-types.ts +79 -0
- package/src/utils/worker_broker.ts +523 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {TerrainMeshSurface, TerrainSurfaceCollection, type TerrainSurface, type TerrainSurfaceEntry, type TerrainSurfaceBounds} from './terrain_surface';
|
|
6
|
+
import {getGeographicProjectionProcedure, PROJECTION_CONSTANTS} from '../scene/projection_math';
|
|
7
|
+
import {TriangleQueryIndex} from '../map-logic/triangle-query-index';
|
|
8
|
+
import log from '../utils/log';
|
|
9
|
+
import {summarizeDrapedTerrainBounds, type DrapedTerrainBoundsSummary} from '../map-logic/draped-terrain-bounds';
|
|
10
|
+
import {intersectRayTriangle} from '@math.gl/culling/queries';
|
|
11
|
+
import {Vector3} from '@math.gl/core';
|
|
12
|
+
import type {TerrainRay, TerrainSurfaceHit} from './terrain_surface';
|
|
13
|
+
import {projectTerrainGlobeVertex} from '../procedures/terrain-globe';
|
|
14
|
+
|
|
15
|
+
/** Measured CPU costs for one immutable terrain display, excluding other retained snapshots. */
|
|
16
|
+
export interface DrapedTerrainQueryStatistics {
|
|
17
|
+
/** Unique retained physical input bytes. */
|
|
18
|
+
inputBytes: number;
|
|
19
|
+
/** Lazily projected geometry bytes. */
|
|
20
|
+
projectedBytes: number;
|
|
21
|
+
/** Completed acceleration hierarchy bytes. */
|
|
22
|
+
queryIndexBytes: number;
|
|
23
|
+
/** Aggregate permitted hierarchy bytes for this snapshot. */
|
|
24
|
+
reservedQueryIndexBytes: number;
|
|
25
|
+
/** Number of queried draw entries, including repeated clip leases. */
|
|
26
|
+
preparedMeshes: number;
|
|
27
|
+
/** Projected owners allocated by this snapshot. */
|
|
28
|
+
projectionBuilds: number;
|
|
29
|
+
/** Projection and cooperative hierarchy CPU time, excluding suspended time. */
|
|
30
|
+
preparationMs: number;
|
|
31
|
+
/** Ray-query CPU time. */
|
|
32
|
+
queryMs: number;
|
|
33
|
+
/** Owners whose acceleration failed; their exact linear queries remain available. */
|
|
34
|
+
preparationFailures: number;
|
|
35
|
+
/** Demanded entries using bounded-memory exact triangle projection rather than retained copies. */
|
|
36
|
+
streamingMeshes: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Do not retry failed acceleration until a new projected owner is admitted. */
|
|
40
|
+
const failedPreparation = new WeakSet<ProjectedQuerySurface>();
|
|
41
|
+
/** Immutable worker inputs are checked once, not rescanned for every eye and animation frame. */
|
|
42
|
+
const validatedGlobeBasis = new WeakSet<DrapedTerrainQueryMesh>();
|
|
43
|
+
|
|
44
|
+
/** Cached or bounded-memory picking owner, with the same exact clipping boundary. */
|
|
45
|
+
type ProjectedQuerySurface = TerrainMeshSurface | StreamingTerrainSurface;
|
|
46
|
+
/** Candidate clip evaluated in original triangle/barycentric coordinates. */
|
|
47
|
+
type AcceptTerrainPosition = (position: readonly [number, number, number], triangleIndex: number, weights: readonly [number, number, number]) => boolean;
|
|
48
|
+
|
|
49
|
+
/** Project only the current triangle when retaining another full mesh would exceed the snapshot budget. */
|
|
50
|
+
class StreamingTerrainSurface implements TerrainSurface {
|
|
51
|
+
/** Captured content projection, independent of a deck viewport. */
|
|
52
|
+
readonly projection: TerrainSurface['projection'];
|
|
53
|
+
/** Source arrays remain immutable under the same worker-payload contract as cached queries. */
|
|
54
|
+
private readonly mesh: DrapedTerrainQueryMesh;
|
|
55
|
+
/** Display blend used by the matching color pass. */
|
|
56
|
+
private readonly blend: number;
|
|
57
|
+
/** Display-only height multiplier; physical query altitude remains unchanged. */
|
|
58
|
+
private readonly exaggeration: number;
|
|
59
|
+
|
|
60
|
+
/** Capture one immutable display without allocating per-vertex projected arrays. */
|
|
61
|
+
constructor(mesh: DrapedTerrainQueryMesh, projection: TerrainSurface['projection'], blend: number, exaggeration: number) {
|
|
62
|
+
this.mesh = mesh; this.projection = projection; this.blend = blend; this.exaggeration = exaggeration;
|
|
63
|
+
if (mesh.indices.length % 3 || !mesh.positions.every(Number.isFinite) ||
|
|
64
|
+
mesh.indices.some(index => index >= mesh.positions.length / 3 || !Number.isFinite(mesh.heights[index * 2]) || !Number.isFinite(mesh.heights[index * 2 + 1]))) throw new Error('Terrain mesh: triangle positions, indices and physical heights must be finite and in range');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Streaming owners retain no additional full-mesh typed arrays or acceleration. */
|
|
68
|
+
getStatistics(): {geometryBytes: number; queryIndexBytes: number} {return {geometryBytes: 0, queryIndexBytes: 0};}
|
|
69
|
+
|
|
70
|
+
/** Match cached exact nearest-hit, face, physical-height, and source-clip semantics. */
|
|
71
|
+
intersectRay(ray: TerrainRay, maximum = Infinity, accept?: AcceptTerrainPosition): TerrainSurfaceHit | null {
|
|
72
|
+
if (maximum < 0 || Number.isNaN(maximum)) throw new Error('Terrain ray distance must be non-negative');
|
|
73
|
+
const length = Math.hypot(...ray.direction);
|
|
74
|
+
if (ray.origin.length !== 3 || ray.direction.length !== 3 || !ray.origin.every(Number.isFinite) ||
|
|
75
|
+
!ray.direction.every(Number.isFinite) || !Number.isFinite(length) || length === 0) return null;
|
|
76
|
+
const direction: [number, number, number] = [ray.direction[0] / length, ray.direction[1] / length, ray.direction[2] / length];
|
|
77
|
+
const mercator = getGeographicProjectionProcedure('web-mercator'), projection = getGeographicProjectionProcedure(this.projection);
|
|
78
|
+
let nearest: TerrainSurfaceHit | null = null;
|
|
79
|
+
for (let triangle = 0; triangle < this.mesh.indices.length / 3; triangle++) {
|
|
80
|
+
const physicalHeights: number[] = [];
|
|
81
|
+
const vertices = [0, 1, 2].map(vertex => {
|
|
82
|
+
const index = this.mesh.indices[triangle * 3 + vertex], offset = index * 3;
|
|
83
|
+
const current = this.mesh.heights[index * 2], previous = this.mesh.heights[index * 2 + 1];
|
|
84
|
+
const height = previous + (current - previous) * this.blend;
|
|
85
|
+
physicalHeights.push(this.mesh.positions[offset + 2] + height);
|
|
86
|
+
const position: [number, number, number] = [this.mesh.positions[offset], this.mesh.positions[offset + 1], this.mesh.positions[offset + 2] + height * this.exaggeration];
|
|
87
|
+
return this.projection === 'globe' ? this.mesh.globeBasis ? projectTerrainGlobeVertex(this.mesh.globeBasis, index, this.blend, this.exaggeration,
|
|
88
|
+
[position[0], position[1], this.mesh.positions[offset + 2]]) : projection.project(mercator.unproject(position)) : position;
|
|
89
|
+
});
|
|
90
|
+
const candidate = intersectRayTriangle(ray.origin, direction, vertices[0], vertices[1], vertices[2], {maxT: nearest?.distance ?? maximum});
|
|
91
|
+
if (!candidate || nearest && candidate.t >= nearest.distance) continue;
|
|
92
|
+
const position: [number, number, number] = [ray.origin[0] + direction[0] * candidate.t, ray.origin[1] + direction[1] * candidate.t, ray.origin[2] + direction[2] * candidate.t];
|
|
93
|
+
if (accept && !accept(position, triangle, candidate.barycentric)) continue;
|
|
94
|
+
const normal = new Vector3(vertices[1]).subtract(vertices[0]).cross(new Vector3(vertices[2]).subtract(vertices[0]));
|
|
95
|
+
if (this.projection === 'globe' && (Math.hypot(...position) === 0 || normal.dot(direction) >= 0)) continue;
|
|
96
|
+
normal.normalize();
|
|
97
|
+
const coordinate = projection.unproject(position);
|
|
98
|
+
coordinate[2] = candidate.barycentric.reduce((height, weight, vertex) => height + weight * physicalHeights[vertex], 0);
|
|
99
|
+
nearest = {position, coordinate, distance: candidate.t, normal: [normal[0], normal[1], normal[2]], triangleIndex: triangle, barycentric: candidate.barycentric};
|
|
100
|
+
}
|
|
101
|
+
return nearest;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Worker-owned query channels extracted from the final ground draw topology. */
|
|
106
|
+
export interface DrapedTerrainQueryMesh {
|
|
107
|
+
/** Absolute Mercator XYZ before terrain displacement; includes authored ground offsets. */
|
|
108
|
+
positions: Float64Array;
|
|
109
|
+
/** Current and previous physical height per vertex, interleaved in that order. */
|
|
110
|
+
heights: Float32Array;
|
|
111
|
+
/** Optional affine canonical globe channels: current base/relief, previous base/relief, three components each. */
|
|
112
|
+
globeBasis?: Float32Array;
|
|
113
|
+
/** Exact admitted draw triangles, excluding holes. */
|
|
114
|
+
indices: Uint16Array | Uint32Array;
|
|
115
|
+
/** Indexed XY footprint used to avoid allocating query geometry for unrelated vertical probes. */
|
|
116
|
+
bounds: readonly [number, number, number, number];
|
|
117
|
+
/** Worker-computed immutable extrema, avoiding vertex scans and projected allocations during the first host probe. */
|
|
118
|
+
boundsSummary?: DrapedTerrainBoundsSummary;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Conservative display envelope without allocating any per-vertex projected query buffers. */
|
|
122
|
+
function getDisplayBounds(mesh: DrapedTerrainQueryMesh, projection: TerrainSurface['projection'], exaggeration: number,
|
|
123
|
+
clips?: readonly (readonly number[])[]): TerrainSurfaceBounds | null {
|
|
124
|
+
if (!mesh.indices.length || clips?.length === 0) return null;
|
|
125
|
+
const summary = summarizeDrapedTerrainBounds(mesh);
|
|
126
|
+
const low = summary.offsets[0] + summary.heights[0] * exaggeration;
|
|
127
|
+
const high = summary.offsets[1] + summary.heights[1] * exaggeration;
|
|
128
|
+
let minimum: [number, number, number], maximum: [number, number, number];
|
|
129
|
+
if (projection === 'web-mercator') {
|
|
130
|
+
minimum = [mesh.bounds[0], mesh.bounds[1], low]; maximum = [mesh.bounds[2], mesh.bounds[3], high];
|
|
131
|
+
if (clips) {
|
|
132
|
+
minimum[0] = Math.max(minimum[0], Math.min(...clips.map(clip => clip[0])));
|
|
133
|
+
minimum[1] = Math.max(minimum[1], Math.min(...clips.map(clip => clip[1])));
|
|
134
|
+
maximum[0] = Math.min(maximum[0], Math.max(...clips.map(clip => clip[2])));
|
|
135
|
+
maximum[1] = Math.min(maximum[1], Math.max(...clips.map(clip => clip[3])));
|
|
136
|
+
if (minimum[0] > maximum[0] || minimum[1] > maximum[1]) return null;
|
|
137
|
+
}
|
|
138
|
+
} else if (summary.globe) {
|
|
139
|
+
minimum = [0, 0, 0]; maximum = [0, 0, 0];
|
|
140
|
+
for (const axis of [0, 1, 2]) {
|
|
141
|
+
const offsets = summary.offsets.flatMap(offset => [summary.directions.minimum[axis] * offset, summary.directions.maximum[axis] * offset])
|
|
142
|
+
.map(value => value * PROJECTION_CONSTANTS.globeRadius / PROJECTION_CONSTANTS.earthRadius);
|
|
143
|
+
minimum[axis] = summary.globe.base.minimum[axis] + summary.globe.relief.minimum[axis] * exaggeration + Math.min(...offsets);
|
|
144
|
+
maximum[axis] = summary.globe.base.maximum[axis] + summary.globe.relief.maximum[axis] * exaggeration + Math.max(...offsets);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const radii = [low, high].map(height => PROJECTION_CONSTANTS.globeRadius * (1 + height / PROJECTION_CONSTANTS.earthRadius));
|
|
148
|
+
minimum = [0, 0, 0]; maximum = [0, 0, 0];
|
|
149
|
+
for (const axis of [0, 1, 2]) {
|
|
150
|
+
const products = radii.flatMap(radius => [summary.directions.minimum[axis] * radius, summary.directions.maximum[axis] * radius]);
|
|
151
|
+
minimum[axis] = Math.min(...products); maximum[axis] = Math.max(...products);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Round outward: multiplication/division order differs from exact projected vertices.
|
|
155
|
+
for (const axis of [0, 1, 2]) {
|
|
156
|
+
const margin = Math.max(1, Math.abs(minimum[axis]), Math.abs(maximum[axis])) * 1e-12;
|
|
157
|
+
minimum[axis] -= margin; maximum[axis] += margin;
|
|
158
|
+
}
|
|
159
|
+
return {minimum, maximum};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** One admitted draw owner; callers never include pending worker generations. */
|
|
163
|
+
export interface DrapedTerrainQueryEntry {
|
|
164
|
+
/** Stable tile/style/variant identity. */
|
|
165
|
+
id: string;
|
|
166
|
+
/** Final worker geometry retained independently of GPU buffers. */
|
|
167
|
+
mesh: DrapedTerrainQueryMesh;
|
|
168
|
+
/** Blend actually submitted for this draw generation. */
|
|
169
|
+
blend: number;
|
|
170
|
+
/** Optional absolute Mercator rectangles matching coarse globe fallback clipping. */
|
|
171
|
+
clips?: readonly (readonly [number, number, number, number])[];
|
|
172
|
+
/** Half-open interior ownership; terminal EPSG:3857 world edges stay closed for physical probes. Default false. */
|
|
173
|
+
halfOpenClips?: boolean;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const projectedQueries = new WeakMap<DrapedTerrainQueryMesh, {projection: TerrainSurface['projection']; blend: number; exaggeration: number; indexLimit: number; surface: TerrainMeshSurface}>();
|
|
177
|
+
/** Geometry identity, not a reusable tile/style name, invalidates cached visibility after ordinary tile rebuilds. */
|
|
178
|
+
const meshIdentities = new WeakMap<DrapedTerrainQueryMesh, number>();
|
|
179
|
+
let nextMeshIdentity = 0;
|
|
180
|
+
/** Stable identity for one immutable worker payload. */
|
|
181
|
+
function getMeshIdentity(mesh: DrapedTerrainQueryMesh): number {
|
|
182
|
+
let identity = meshIdentities.get(mesh);
|
|
183
|
+
if (identity === undefined) {identity = nextMeshIdentity++; meshIdentities.set(mesh, identity);}
|
|
184
|
+
return identity;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Reuse one immutable projected query owner while an admitted mesh's displacement remains unchanged. */
|
|
188
|
+
function getProjectedSurface(mesh: DrapedTerrainQueryMesh, projection: TerrainSurface['projection'], blend: number, exaggeration: number, indexLimit: number): TerrainMeshSurface {
|
|
189
|
+
const cached = projectedQueries.get(mesh);
|
|
190
|
+
if (cached?.projection === projection && cached.blend === blend && cached.exaggeration === exaggeration && cached.indexLimit === indexLimit) return cached.surface;
|
|
191
|
+
const positions = new Float64Array(mesh.positions.length), physicalHeights = new Float64Array(mesh.positions.length / 3);
|
|
192
|
+
const mercator = getGeographicProjectionProcedure('web-mercator'), globe = getGeographicProjectionProcedure('globe');
|
|
193
|
+
for (let vertex = 0; vertex < physicalHeights.length; vertex++) {
|
|
194
|
+
const offset = vertex * 3, current = mesh.heights[vertex * 2], previous = mesh.heights[vertex * 2 + 1];
|
|
195
|
+
const height = previous + (current - previous) * blend;
|
|
196
|
+
physicalHeights[vertex] = mesh.positions[offset + 2] + height;
|
|
197
|
+
const position: [number, number, number] = [mesh.positions[offset], mesh.positions[offset + 1], mesh.positions[offset + 2] + height * exaggeration];
|
|
198
|
+
positions.set(projection === 'globe' ? mesh.globeBasis ? projectTerrainGlobeVertex(mesh.globeBasis, vertex, blend, exaggeration,
|
|
199
|
+
[position[0], position[1], mesh.positions[offset + 2]]) : globe.project(mercator.unproject(position)) : position, offset);
|
|
200
|
+
}
|
|
201
|
+
// No per-frame hierarchy allocations during morphing; settled meshes reuse this owner.
|
|
202
|
+
const surface = new TerrainMeshSurface({projection, positions, indices: mesh.indices, physicalHeights, cullBackfaces: projection === 'globe', maxQueryIndexBytes: indexLimit, deferQueryIndexBuild: true});
|
|
203
|
+
projectedQueries.set(mesh, {projection, blend, exaggeration, indexLimit, surface});
|
|
204
|
+
return surface;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Conservative finite-ray XY admission; direction parameters are not assumed to be normalized. */
|
|
208
|
+
function intersectsFootprint(ray: import('./terrain_surface').TerrainRay, maximum: number | undefined, bounds: DrapedTerrainQueryMesh['bounds']): boolean {
|
|
209
|
+
let near = 0, far = (maximum ?? Infinity) / Math.hypot(...ray.direction);
|
|
210
|
+
for (const axis of [0, 1]) {
|
|
211
|
+
if (ray.direction[axis] === 0) {
|
|
212
|
+
if (ray.origin[axis] < bounds[axis] || ray.origin[axis] > bounds[axis + 2]) return false;
|
|
213
|
+
} else {
|
|
214
|
+
const first = (bounds[axis] - ray.origin[axis]) / ray.direction[axis], second = (bounds[axis + 2] - ray.origin[axis]) / ray.direction[axis];
|
|
215
|
+
near = Math.max(near, Math.min(first, second)); far = Math.min(far, Math.max(first, second));
|
|
216
|
+
if (near > far) return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Immutable frame-specific surface, matching ground draw topology and temporal displacement. */
|
|
223
|
+
export class DrapedTerrainSurfaceSnapshot extends TerrainSurfaceCollection {
|
|
224
|
+
/** Lazy query owners and measured work for this snapshot, never global renderer/GPU ownership. */
|
|
225
|
+
private readonly queryOwners: {surface?: ProjectedQuerySurface; prepare(): ProjectedQuerySurface; blend: number; projectionBuilds: number; preparationMs: number; queryMs: number}[];
|
|
226
|
+
/** Scene mesh generation, not source-cache or provider revision. */
|
|
227
|
+
readonly generation: number;
|
|
228
|
+
/** Shared monotonic clock used for both eyes and the corresponding draw. */
|
|
229
|
+
readonly frameTime: number;
|
|
230
|
+
/** GPU relief multiplier captured with the snapshot. */
|
|
231
|
+
readonly exaggeration: number;
|
|
232
|
+
/** Retained physical query input arrays, including indices; excludes projected copies and GPU memory. */
|
|
233
|
+
readonly inputBytes: number;
|
|
234
|
+
/** Admitted membership, blend and exaggeration key for skipping repeated settled host probes. */
|
|
235
|
+
readonly displayKey: string;
|
|
236
|
+
/** Demand-admitted index bytes; unqueried display entries never reserve acceleration. */
|
|
237
|
+
get reservedQueryIndexBytes(): number {return this.indexBudget.reserved;}
|
|
238
|
+
/** Snapshot-local reservation state shared by repeated clip leases. */
|
|
239
|
+
private readonly indexBudget: {reserved: number};
|
|
240
|
+
|
|
241
|
+
/** Capture displacement without copying/projecting every mesh until it is queried. */
|
|
242
|
+
constructor(projection: TerrainSurface['projection'], entries: readonly DrapedTerrainQueryEntry[], generation: number, frameTime: number, exaggeration: number, maxQueryIndexBytes = 16 * 1024 * 1024, maxProjectedQueryBytes = 32 * 1024 * 1024) {
|
|
243
|
+
if (!Number.isSafeInteger(generation) || generation < 0 || !Number.isFinite(frameTime) || !Number.isFinite(exaggeration) || exaggeration < 0 || exaggeration > 1000) throw new Error('Invalid terrain surface frame');
|
|
244
|
+
if (!Number.isSafeInteger(maxQueryIndexBytes) || maxQueryIndexBytes < 0) throw new Error('Invalid terrain query index budget');
|
|
245
|
+
if (!Number.isSafeInteger(maxProjectedQueryBytes) || maxProjectedQueryBytes < 0) throw new Error('Invalid terrain projected query budget');
|
|
246
|
+
const queryOwners: DrapedTerrainSurfaceSnapshot['queryOwners'] = [];
|
|
247
|
+
const indexBudget = {reserved: 0};
|
|
248
|
+
const indexLimits = new Map<DrapedTerrainQueryMesh, number>();
|
|
249
|
+
const admittedSurfaces = new Map<DrapedTerrainQueryMesh, Map<number, ProjectedQuerySurface>>();
|
|
250
|
+
let projectedBytes = 0;
|
|
251
|
+
const surfaces: TerrainSurfaceEntry[] = entries.map(entry => {
|
|
252
|
+
if (!Number.isFinite(entry.blend) || entry.blend < 0 || entry.blend > 1) throw new Error('Invalid terrain surface blend');
|
|
253
|
+
const mesh = entry.mesh;
|
|
254
|
+
if (mesh.positions.length % 3 || mesh.heights.length !== mesh.positions.length / 3 * 2) throw new Error('Invalid terrain query channels');
|
|
255
|
+
if (mesh.globeBasis && !validatedGlobeBasis.has(mesh)) {
|
|
256
|
+
if (mesh.globeBasis.length !== mesh.positions.length * 4 || !mesh.globeBasis.every(Number.isFinite)) throw new Error('Invalid terrain globe basis channels');
|
|
257
|
+
validatedGlobeBasis.add(mesh);
|
|
258
|
+
}
|
|
259
|
+
const blend = entry.blend, clips = entry.clips?.map(bounds => [...bounds]), halfOpenClips = entry.halfOpenClips ?? false;
|
|
260
|
+
if (typeof halfOpenClips !== 'boolean') throw new Error('Invalid terrain query clip ownership');
|
|
261
|
+
const required = TriangleQueryIndex.getRequiredHierarchyBytes(mesh.indices.length / 3);
|
|
262
|
+
if (clips?.some(bounds => bounds.length !== 4 || !bounds.every(Number.isFinite) || bounds[0] > bounds[2] || bounds[1] > bounds[3])) throw new Error('Invalid terrain query clip');
|
|
263
|
+
const worldHalf = Math.PI * PROJECTION_CONSTANTS.mercatorRadius;
|
|
264
|
+
// A finite world has no owner beyond its east/north edge. Normalize
|
|
265
|
+
// source-bound reconstruction ulps, but never close interior seams.
|
|
266
|
+
if (halfOpenClips) for (const bounds of clips ?? []) for (const axis of [2, 3]) {
|
|
267
|
+
if (Math.abs(bounds[axis] - worldHalf) <= worldHalf * Number.EPSILON * 8) bounds[axis] = worldHalf;
|
|
268
|
+
}
|
|
269
|
+
let displayBounds: TerrainSurfaceBounds | null | undefined;
|
|
270
|
+
const owner: DrapedTerrainSurfaceSnapshot['queryOwners'][number] = {blend, projectionBuilds: 0, preparationMs: 0, queryMs: 0, prepare() {
|
|
271
|
+
if (!owner.surface) {
|
|
272
|
+
const existing = admittedSurfaces.get(mesh)?.get(blend);
|
|
273
|
+
if (existing) {owner.surface = existing; return existing;}
|
|
274
|
+
const started = performance.now();
|
|
275
|
+
// TerrainMeshSurface owns Float64 XYZ, Float64 physical heights, and Uint32 topology.
|
|
276
|
+
const geometryBytes = mesh.positions.length * 8 + mesh.heights.length / 2 * 8 + mesh.indices.length * 4;
|
|
277
|
+
if (geometryBytes > maxProjectedQueryBytes - projectedBytes) {
|
|
278
|
+
// A live mesh key must not pin an earlier snapshot's
|
|
279
|
+
// projected arrays after this demand is denied residency.
|
|
280
|
+
// Older snapshots keep their own admitted surface owner.
|
|
281
|
+
projectedQueries.delete(mesh);
|
|
282
|
+
owner.surface = new StreamingTerrainSurface(mesh, projection, blend, exaggeration);
|
|
283
|
+
} else {
|
|
284
|
+
projectedBytes += geometryBytes;
|
|
285
|
+
// Near-first collection admission chooses the first demanded owner, not
|
|
286
|
+
// constructor membership order. Clip leases of the same settled mesh share it.
|
|
287
|
+
const indexLimit = blend !== 1 ? 0 : indexLimits.get(mesh) ?? (required <= maxQueryIndexBytes - indexBudget.reserved ? required : 0);
|
|
288
|
+
if (blend === 1 && !indexLimits.has(mesh)) {indexLimits.set(mesh, indexLimit); indexBudget.reserved += indexLimit;}
|
|
289
|
+
const cached = projectedQueries.get(mesh);
|
|
290
|
+
owner.projectionBuilds = Number(cached?.projection !== projection || cached.blend !== blend || cached.exaggeration !== exaggeration || cached.indexLimit !== indexLimit);
|
|
291
|
+
owner.surface = getProjectedSurface(mesh, projection, blend, exaggeration, indexLimit);
|
|
292
|
+
}
|
|
293
|
+
owner.preparationMs += performance.now() - started;
|
|
294
|
+
const blends = admittedSurfaces.get(mesh) ?? new Map<number, ProjectedQuerySurface>();
|
|
295
|
+
blends.set(blend, owner.surface); admittedSurfaces.set(mesh, blends);
|
|
296
|
+
}
|
|
297
|
+
return owner.surface;
|
|
298
|
+
}};
|
|
299
|
+
queryOwners.push(owner);
|
|
300
|
+
return {id: entry.id, surface: {projection, getBounds() {
|
|
301
|
+
if (displayBounds === undefined) displayBounds = getDisplayBounds(mesh, projection, exaggeration, clips);
|
|
302
|
+
return displayBounds ? {minimum: [...displayBounds.minimum], maximum: [...displayBounds.maximum]} : null;
|
|
303
|
+
}, intersectRay(ray, maximum) {
|
|
304
|
+
if (clips?.length === 0 || !mesh.indices.length) return null;
|
|
305
|
+
if (projection === 'web-mercator' && !intersectsFootprint(ray, maximum, mesh.bounds)) return null;
|
|
306
|
+
const surface = owner.prepare();
|
|
307
|
+
const started = performance.now();
|
|
308
|
+
const hit = surface.intersectRay(ray, maximum, clips ? (point, triangleIndex, weights) => {
|
|
309
|
+
// The draw clips its interpolated tile-position varying, not the inverse
|
|
310
|
+
// geographic projection of the curved triangle's common-space hit.
|
|
311
|
+
const position = projection === 'web-mercator' ? point : [0, 1].map(axis => weights.reduce((value, weight, vertex) => value + weight * mesh.positions[mesh.indices[triangleIndex * 3 + vertex] * 3 + axis], 0));
|
|
312
|
+
return clips.some(bounds => position[0] >= bounds[0] && (halfOpenClips && bounds[2] !== worldHalf ? position[0] < bounds[2] : position[0] <= bounds[2]) &&
|
|
313
|
+
position[1] >= bounds[1] && (halfOpenClips && bounds[3] !== worldHalf ? position[1] < bounds[3] : position[1] <= bounds[3]));
|
|
314
|
+
} : undefined);
|
|
315
|
+
owner.queryMs += performance.now() - started;
|
|
316
|
+
return hit;
|
|
317
|
+
}}};
|
|
318
|
+
});
|
|
319
|
+
super(projection, surfaces);
|
|
320
|
+
this.generation = generation; this.frameTime = frameTime; this.exaggeration = exaggeration;
|
|
321
|
+
this.queryOwners = queryOwners;
|
|
322
|
+
this.indexBudget = indexBudget;
|
|
323
|
+
this.inputBytes = [...new Set(entries.map(entry => entry.mesh))].reduce((bytes, mesh) => bytes + mesh.positions.byteLength + mesh.heights.byteLength + mesh.indices.byteLength + (mesh.globeBasis?.byteLength ?? 0), 0);
|
|
324
|
+
this.displayKey = `${projection}/${generation}/${exaggeration}/${maxQueryIndexBytes}/${maxProjectedQueryBytes}/${entries.map(entry => `${entry.id}:${getMeshIdentity(entry.mesh)}:${entry.blend}:${JSON.stringify(entry.clips)}:${Boolean(entry.halfOpenClips)}`).join(',')}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Fairly warm demanded settled owners; closing releases every partial hierarchy. */
|
|
328
|
+
*prepareQueryIndexSteps(): Generator<void, void, void> {
|
|
329
|
+
const warmed = new Set<ProjectedQuerySurface>();
|
|
330
|
+
const pending: {owner: DrapedTerrainSurfaceSnapshot['queryOwners'][number]; steps: Generator<void, void, void>}[] = [];
|
|
331
|
+
for (const owner of this.queryOwners) {
|
|
332
|
+
const surface = owner.surface;
|
|
333
|
+
if (!surface || surface instanceof StreamingTerrainSurface || owner.blend !== 1 || warmed.has(surface) || failedPreparation.has(surface)) continue;
|
|
334
|
+
warmed.add(surface);
|
|
335
|
+
pending.push({owner, steps: surface.prepareQueryIndexSteps()});
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
while (pending.length) {
|
|
339
|
+
for (let index = 0; index < pending.length;) {
|
|
340
|
+
const {owner, steps} = pending[index];
|
|
341
|
+
const started = performance.now();
|
|
342
|
+
let done: boolean | undefined;
|
|
343
|
+
try {done = steps.next().done;}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (owner.surface) failedPreparation.add(owner.surface);
|
|
346
|
+
log('warn', 'Terrain query acceleration failed; retaining exact linear queries', error);
|
|
347
|
+
done = true;
|
|
348
|
+
}
|
|
349
|
+
finally {owner.preparationMs += performance.now() - started;}
|
|
350
|
+
if (done) {steps.return(); pending.splice(index, 1);}
|
|
351
|
+
else {index++; yield;}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} finally {
|
|
355
|
+
for (const {steps} of pending) steps.return();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Detached snapshot-local CPU query costs; excludes other retained host snapshots and GPU/transient storage. */
|
|
360
|
+
getStatistics(): DrapedTerrainQueryStatistics {
|
|
361
|
+
const statistics = {inputBytes: this.inputBytes, projectedBytes: 0, queryIndexBytes: 0, reservedQueryIndexBytes: this.reservedQueryIndexBytes, preparedMeshes: 0, projectionBuilds: 0, preparationMs: 0, queryMs: 0, preparationFailures: 0, streamingMeshes: 0};
|
|
362
|
+
const counted = new Set<ProjectedQuerySurface>();
|
|
363
|
+
for (const owner of this.queryOwners) {
|
|
364
|
+
if (owner.surface && !counted.has(owner.surface)) {
|
|
365
|
+
const sizes = owner.surface.getStatistics();
|
|
366
|
+
statistics.projectedBytes += sizes.geometryBytes; statistics.queryIndexBytes += sizes.queryIndexBytes;
|
|
367
|
+
statistics.preparationFailures += Number(failedPreparation.has(owner.surface));
|
|
368
|
+
counted.add(owner.surface);
|
|
369
|
+
}
|
|
370
|
+
statistics.preparedMeshes += Number(owner.surface !== undefined);
|
|
371
|
+
statistics.streamingMeshes += Number(owner.surface instanceof StreamingTerrainSurface);
|
|
372
|
+
statistics.projectionBuilds += owner.projectionBuilds;
|
|
373
|
+
statistics.preparationMs += owner.preparationMs; statistics.queryMs += owner.queryMs;
|
|
374
|
+
}
|
|
375
|
+
return statistics;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {TileCoordinates} from '../tile/tile_id';
|
|
6
|
+
|
|
7
|
+
/** Canvas-relative selection position or radius. */
|
|
8
|
+
export interface SelectionPoint {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Build-owned tile metadata returned with a selected feature. */
|
|
14
|
+
export interface SelectionTile {
|
|
15
|
+
key: string;
|
|
16
|
+
coords: TileCoordinates;
|
|
17
|
+
style_z: number;
|
|
18
|
+
source: string;
|
|
19
|
+
generation: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Source feature members copied into the worker's selection map. */
|
|
23
|
+
export interface SelectionSourceFeature {
|
|
24
|
+
id?: string | number;
|
|
25
|
+
properties: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Source and matched scene layers attached to a selection entry. */
|
|
29
|
+
export interface SelectionContext {
|
|
30
|
+
source: string;
|
|
31
|
+
/** Unnamed GeoJSON/default collections do not supply a source-layer name. */
|
|
32
|
+
layer?: string;
|
|
33
|
+
layers: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Public feature metadata produced by the built-in worker selection map. */
|
|
37
|
+
export interface SelectedFeature extends SelectionSourceFeature {
|
|
38
|
+
source_name: string;
|
|
39
|
+
/** Absent for an unnamed default source collection. */
|
|
40
|
+
source_layer?: string;
|
|
41
|
+
layers: string[];
|
|
42
|
+
tile: SelectionTile;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Normalized RGBA selection color with optional feature metadata. */
|
|
46
|
+
export interface SelectionEntry {
|
|
47
|
+
color: [number, number, number, number];
|
|
48
|
+
feature?: SelectedFeature;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Per-build ownership of selection keys. */
|
|
52
|
+
export interface SelectionTileEntries {
|
|
53
|
+
entries: number[];
|
|
54
|
+
tile: SelectionTile;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Worker reply; custom registered workers can return arbitrary feature payloads. */
|
|
58
|
+
export interface SelectionReply {
|
|
59
|
+
id: number;
|
|
60
|
+
feature?: unknown;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolved selection state; out-of-bounds requests have no queued request record. */
|
|
64
|
+
export interface SelectionResult {
|
|
65
|
+
feature: unknown;
|
|
66
|
+
changed: boolean;
|
|
67
|
+
request?: SelectionRequest;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Pending readback/lookup, retained by identity to reject stale asynchronous replies. */
|
|
71
|
+
export interface SelectionRequest {
|
|
72
|
+
id: number;
|
|
73
|
+
point: SelectionPoint;
|
|
74
|
+
radius?: SelectionPoint | null;
|
|
75
|
+
resolve(result: SelectionResult): void;
|
|
76
|
+
reject(error: unknown): void;
|
|
77
|
+
sent?: boolean;
|
|
78
|
+
reading?: boolean;
|
|
79
|
+
}
|