@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.1
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 +60 -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,298 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {Matrix4, Vector3} from '@math.gl/core';
|
|
6
|
+
import {TriangleQueryIndex} from '../map-logic/triangle-query-index';
|
|
7
|
+
import type HostFrame from '../scene/host_frame';
|
|
8
|
+
import {getGeographicProjectionProcedure, PROJECTION_CONSTANTS} from '../scene/projection_math';
|
|
9
|
+
import type {FeatureSelectionOptions} from '../types';
|
|
10
|
+
import {intersectRayBounds} from '@math.gl/culling/queries';
|
|
11
|
+
|
|
12
|
+
/** Detached conservative content-space bounds; null means the surface has no admitted triangles. */
|
|
13
|
+
export interface TerrainSurfaceBounds {
|
|
14
|
+
/** Closed lower corner in the same units as query rays. */
|
|
15
|
+
minimum: [number, number, number];
|
|
16
|
+
/** Closed upper corner in the same units as query rays. */
|
|
17
|
+
maximum: [number, number, number];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A ray in the same absolute content coordinates as the supplied terrain. */
|
|
21
|
+
export interface TerrainRay {
|
|
22
|
+
/** Absolute origin, before host view and room-placement transforms. */
|
|
23
|
+
origin: readonly [number, number, number];
|
|
24
|
+
/** Nonzero direction; the query normalizes its length. */
|
|
25
|
+
direction: readonly [number, number, number];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Nearest terrain triangle, not a worker feature ID or a GPU selection result. */
|
|
29
|
+
export interface TerrainSurfaceHit {
|
|
30
|
+
/** Absolute EPSG:3857 meters or radius-256 globe common coordinates. */
|
|
31
|
+
position: [number, number, number];
|
|
32
|
+
/** Longitude/latitude degrees and physical altitude meters. */
|
|
33
|
+
coordinate: [number, number, number];
|
|
34
|
+
/** Distance from the ray origin in the surface's coordinate units. */
|
|
35
|
+
distance: number;
|
|
36
|
+
/** Normal in the supplied mesh's coordinate system, following triangle winding. */
|
|
37
|
+
normal: [number, number, number];
|
|
38
|
+
/** Triangle offset in the supplied index list. */
|
|
39
|
+
triangleIndex: number;
|
|
40
|
+
/** Vertex weights in index order. */
|
|
41
|
+
barycentric: [number, number, number];
|
|
42
|
+
/** Eye used for a screen query; absent for a content-space ray. */
|
|
43
|
+
renderViewId?: string;
|
|
44
|
+
/** Caller collection identity; absent on an individual surface query. */
|
|
45
|
+
surfaceId?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Host-owned terrain query boundary; it does not load, render or displace a DEM. */
|
|
49
|
+
export interface TerrainSurface {
|
|
50
|
+
/** Coordinate convention of both the mesh and rays. */
|
|
51
|
+
readonly projection: 'web-mercator' | 'globe';
|
|
52
|
+
/** Return the nearest two-sided hit within the finite camera segment, or null. */
|
|
53
|
+
intersectRay(ray: TerrainRay, maxDistance?: number): TerrainSurfaceHit | null;
|
|
54
|
+
/** Optional cheap bounds for near-first collection queries; must contain every accepted triangle. */
|
|
55
|
+
getBounds?(): TerrainSurfaceBounds | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One stable caller-owned surface identity; collections snapshot membership, not geometry. */
|
|
59
|
+
export interface TerrainSurfaceEntry {
|
|
60
|
+
/** Unique nonempty identifier used to distinguish triangle offsets in different tiles. */
|
|
61
|
+
id: string;
|
|
62
|
+
/** Immutable terrain query owner, using the collection's projection. */
|
|
63
|
+
surface: TerrainSurface;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Nearest collection hit with the identity of its owning tile/surface. */
|
|
67
|
+
export interface TerrainCollectionHit extends TerrainSurfaceHit {
|
|
68
|
+
/** Original caller identity, unaffected by nearest-hit clipping. */
|
|
69
|
+
surfaceId: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Explicit geographic probe bracket; altitude values describe displayed geometry, not source elevation. */
|
|
73
|
+
export interface TerrainElevationProbe {
|
|
74
|
+
/** Lower geographic altitude in meters; must be strictly below maximumAltitude. */
|
|
75
|
+
minimumAltitude: number;
|
|
76
|
+
/** Upper geographic altitude in meters, above every surface that should be considered. */
|
|
77
|
+
maximumAltitude: number;
|
|
78
|
+
/** Optional Mercator longitude anchor for unwrapped tile coordinates. */
|
|
79
|
+
anchorLongitude?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Snapshot tile membership and query nearest geometry without owning sources, views, buffers or resources. */
|
|
83
|
+
export class TerrainSurfaceCollection implements TerrainSurface {
|
|
84
|
+
/** Shared absolute coordinate convention; mixed projections reject at construction. */
|
|
85
|
+
readonly projection: TerrainSurface['projection'];
|
|
86
|
+
/** Snapshotted membership and caller identities, retaining owners without copying their geometry. */
|
|
87
|
+
private readonly entries: readonly TerrainSurfaceEntry[];
|
|
88
|
+
|
|
89
|
+
/** Retain immutable surface references with unique IDs; caller membership changes do not affect this collection. */
|
|
90
|
+
constructor(projection: TerrainSurface['projection'], entries: readonly TerrainSurfaceEntry[]) {
|
|
91
|
+
if (!['web-mercator', 'globe'].includes(projection)) throw new Error('Terrain collection projection must be web-mercator or globe');
|
|
92
|
+
this.projection = projection;
|
|
93
|
+
this.entries = entries.map(({id, surface}) => {
|
|
94
|
+
if (typeof id !== 'string' || !id.length || surface.projection !== projection) throw new Error('Terrain collection requires nonempty IDs and matching projections');
|
|
95
|
+
return Object.freeze({id, surface});
|
|
96
|
+
});
|
|
97
|
+
if (new Set(this.entries.map(entry => entry.id)).size !== this.entries.length) throw new Error('Terrain collection IDs must be unique');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Clip farther tiles after an accepted hit; equal-distance ties retain original membership order. */
|
|
101
|
+
intersectRay(ray: TerrainRay, maxDistance = Infinity): TerrainCollectionHit | null {
|
|
102
|
+
if (maxDistance < 0 || Number.isNaN(maxDistance)) throw new Error('Terrain ray distance must be non-negative');
|
|
103
|
+
const length = Math.hypot(...ray.direction);
|
|
104
|
+
if (ray.origin.length !== 3 || ray.direction.length !== 3 || !ray.origin.every(Number.isFinite) || !ray.direction.every(Number.isFinite) || !Number.isFinite(length) || length === 0) return null;
|
|
105
|
+
const direction = ray.direction.map(component => component / length);
|
|
106
|
+
const candidates = this.entries.flatMap((entry, index) => {
|
|
107
|
+
if (!entry.surface.getBounds) return [{entry, index, distance: 0}];
|
|
108
|
+
const bounds = entry.surface.getBounds();
|
|
109
|
+
if (!bounds) return [];
|
|
110
|
+
const distance = intersectRayBounds(ray.origin, direction, bounds.minimum, bounds.maximum, maxDistance);
|
|
111
|
+
return distance === null ? [] : [{entry, index, distance}];
|
|
112
|
+
}).sort((first, second) => first.distance - second.distance || first.index - second.index);
|
|
113
|
+
let nearest: TerrainCollectionHit | null = null, nearestIndex = Infinity;
|
|
114
|
+
for (const {entry, index, distance} of candidates) {
|
|
115
|
+
if (distance > (nearest?.distance ?? maxDistance)) break;
|
|
116
|
+
const hit = entry.surface.intersectRay(ray, nearest?.distance ?? maxDistance);
|
|
117
|
+
if (hit && (!nearest || hit.distance < nearest.distance || hit.distance === nearest.distance && index < nearestIndex)) {
|
|
118
|
+
nearest = {...hit, surfaceId: entry.id}; nearestIndex = index;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return nearest;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Query a finite content-space sightline; misses mean no loaded triangle hit, not certified world visibility. */
|
|
125
|
+
intersectSegment(start: readonly [number, number, number], end: readonly [number, number, number], options: {
|
|
126
|
+
/** Trim origin self-intersections, in mesh coordinate units; defaults to zero. */
|
|
127
|
+
startInset?: number;
|
|
128
|
+
/** Trim target self-intersections, in mesh coordinate units; defaults to zero. */
|
|
129
|
+
endInset?: number;
|
|
130
|
+
} = {}): TerrainCollectionHit | null {
|
|
131
|
+
const startInset = options.startInset ?? 0, endInset = options.endInset ?? 0;
|
|
132
|
+
if (start.length !== 3 || end.length !== 3 || !start.every(Number.isFinite) || !end.every(Number.isFinite) || ![startInset, endInset].every(value => Number.isFinite(value) && value >= 0)) throw new Error('Terrain segment requires finite endpoints and non-negative finite insets');
|
|
133
|
+
const direction: [number, number, number] = [end[0] - start[0], end[1] - start[1], end[2] - start[2]], length = Math.hypot(...direction);
|
|
134
|
+
if (!Number.isFinite(length)) throw new Error('Terrain segment length must be finite');
|
|
135
|
+
if (length === 0 || startInset + endInset >= length) return null;
|
|
136
|
+
const origin: [number, number, number] = [start[0] + direction[0] * startInset / length, start[1] + direction[1] * startInset / length, start[2] + direction[2] * startInset / length];
|
|
137
|
+
const hit = this.intersectRay({origin, direction}, length - startInset - endInset);
|
|
138
|
+
return hit ? {...hit, distance: hit.distance + startInset} : null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Probe loaded top triangles along geographic up; returned coordinate altitude retains physical source meters. */
|
|
143
|
+
export function sampleTerrainElevation(surface: TerrainSurface, coordinate: readonly [number, number], options: TerrainElevationProbe): TerrainSurfaceHit | null {
|
|
144
|
+
if (coordinate.length !== 2 || !coordinate.every(Number.isFinite) || Math.abs(coordinate[1]) > 90 ||
|
|
145
|
+
![options.minimumAltitude, options.maximumAltitude].every(Number.isFinite) || options.minimumAltitude >= options.maximumAltitude ||
|
|
146
|
+
options.anchorLongitude !== undefined && !Number.isFinite(options.anchorLongitude)) throw new Error('Invalid terrain elevation probe');
|
|
147
|
+
const projection = getGeographicProjectionProcedure(surface.projection);
|
|
148
|
+
if (Math.abs(coordinate[1]) > projection.latitudeLimit) return null;
|
|
149
|
+
// A radial bracket must not cross the globe center and sample its far hemisphere.
|
|
150
|
+
if (surface.projection === 'globe' && options.minimumAltitude <= -PROJECTION_CONSTANTS.earthRadius) throw new Error('Globe elevation probes must remain above the center');
|
|
151
|
+
const origin = projection.project([coordinate[0], coordinate[1], options.maximumAltitude], options.anchorLongitude);
|
|
152
|
+
const end = projection.project([coordinate[0], coordinate[1], options.minimumAltitude], options.anchorLongitude);
|
|
153
|
+
const direction: [number, number, number] = [end[0] - origin[0], end[1] - origin[1], end[2] - origin[2]];
|
|
154
|
+
if (!origin.every(Number.isFinite) || !end.every(Number.isFinite) || !Number.isFinite(Math.hypot(...direction))) throw new Error('Terrain elevation probe exceeds finite projection range');
|
|
155
|
+
return surface.intersectRay({origin, direction}, Math.hypot(...direction));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Resolve a Mercator camera/anchor floor; this is a point probe, not swept-volume collision or locomotion. */
|
|
159
|
+
export function resolveTerrainClearance(surface: TerrainSurface, coordinate: readonly [number, number, number], clearance: number, options: TerrainElevationProbe): {
|
|
160
|
+
/** Geographic position raised only when it lies below displayed terrain plus clearance. */
|
|
161
|
+
coordinate: [number, number, number];
|
|
162
|
+
/** Source elevation meters, independent of any caller-provided display displacement. */
|
|
163
|
+
physicalElevation: number;
|
|
164
|
+
/** True when the supplied altitude was raised. */
|
|
165
|
+
adjusted: boolean;
|
|
166
|
+
} | null {
|
|
167
|
+
if (surface.projection !== 'web-mercator') throw new Error('Terrain clearance requires a physical-meter Mercator frame');
|
|
168
|
+
if (coordinate.length !== 3 || !coordinate.every(Number.isFinite) || !Number.isFinite(clearance) || clearance < 0) throw new Error('Invalid terrain clearance');
|
|
169
|
+
const hit = sampleTerrainElevation(surface, [coordinate[0], coordinate[1]], options);
|
|
170
|
+
if (!hit) return null;
|
|
171
|
+
const altitude = Math.max(coordinate[2], hit.position[2] + clearance);
|
|
172
|
+
if (!Number.isFinite(altitude)) throw new Error('Terrain clearance exceeds finite altitude range');
|
|
173
|
+
return {coordinate: [coordinate[0], coordinate[1], altitude], physicalElevation: hit.coordinate[2], adjusted: altitude > coordinate[2]};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Indexed content-space triangles; source buffers are copied at construction. */
|
|
177
|
+
export interface TerrainMeshOptions {
|
|
178
|
+
/** Coordinate convention, matching the HostFrame or XR placement. */
|
|
179
|
+
projection: TerrainSurface['projection'];
|
|
180
|
+
/** Interleaved absolute XYZ coordinates, not tile-local positions. */
|
|
181
|
+
positions: ArrayLike<number>;
|
|
182
|
+
/** Three zero-based vertex indices per triangle. */
|
|
183
|
+
indices: ArrayLike<number>;
|
|
184
|
+
/** Optional source meters per vertex, preserving physical queries on exaggerated display meshes. */
|
|
185
|
+
physicalHeights?: ArrayLike<number>;
|
|
186
|
+
/** Match an outward backface-culled draw pass; default false preserves two-sided queries. */
|
|
187
|
+
cullBackfaces?: boolean;
|
|
188
|
+
/** Lazy hierarchy typed-array limit; exact linear picking is used when acceleration does not fit. */
|
|
189
|
+
maxQueryIndexBytes?: number;
|
|
190
|
+
/** Keep queries exact and linear until an explicit cooperative warming pass completes. */
|
|
191
|
+
deferQueryIndexBuild?: boolean;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Detached retained-storage diagnostics; no GPU or JavaScript heap estimate is included. */
|
|
195
|
+
export interface TerrainMeshStatistics {
|
|
196
|
+
/** Owned immutable positions, topology and optional physical heights. */
|
|
197
|
+
geometryBytes: number;
|
|
198
|
+
/** Currently allocated packed hierarchy storage. */
|
|
199
|
+
queryIndexBytes: number;
|
|
200
|
+
/** Required acceleration bytes, even when linear picking is selected. */
|
|
201
|
+
requiredQueryIndexBytes: number;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Immutable physical terrain surface backed by the reusable CPU triangle hierarchy. */
|
|
205
|
+
export class TerrainMeshSurface implements TerrainSurface {
|
|
206
|
+
/** Mercator XY/physical Z meters, or already projected globe common coordinates. */
|
|
207
|
+
readonly projection: TerrainSurface['projection'];
|
|
208
|
+
/** Optional detached physical elevations; only indexed vertices must be finite. */
|
|
209
|
+
private readonly physicalHeights?: Float64Array;
|
|
210
|
+
/** Optional render-pass face convention; unrelated to geographic height units. */
|
|
211
|
+
private readonly cullBackfaces: boolean;
|
|
212
|
+
/** Projection-independent immutable triangle query index. */
|
|
213
|
+
private readonly queryIndex: TriangleQueryIndex;
|
|
214
|
+
|
|
215
|
+
/** Copy a finite indexed triangle mesh; mutation of caller buffers cannot change queries. */
|
|
216
|
+
constructor(options: TerrainMeshOptions) {
|
|
217
|
+
if (!['web-mercator', 'globe'].includes(options.projection)) throw new Error('Terrain projection must be web-mercator or globe');
|
|
218
|
+
if (options.positions.length % 3 || options.indices.length % 3) throw new Error('Terrain requires XYZ positions and triangle indices');
|
|
219
|
+
this.projection = options.projection;
|
|
220
|
+
try {
|
|
221
|
+
this.queryIndex = new TriangleQueryIndex(options.positions, options.indices, 8, {lazy: true, maxHierarchyBytes: options.maxQueryIndexBytes, deferBuild: options.deferQueryIndexBuild});
|
|
222
|
+
} catch (error) {throw new Error(`Terrain mesh: ${error instanceof Error ? error.message : String(error)}`);}
|
|
223
|
+
this.cullBackfaces = options.cullBackfaces ?? false;
|
|
224
|
+
const physicalHeights = options.physicalHeights;
|
|
225
|
+
if (physicalHeights) {
|
|
226
|
+
if (physicalHeights.length !== options.positions.length / 3) throw new Error('Terrain physical heights must match indexed vertices');
|
|
227
|
+
this.physicalHeights = Float64Array.from(physicalHeights);
|
|
228
|
+
for (let triangle = 0; triangle < this.queryIndex.triangleCount; triangle++) for (let vertex = 0; vertex < 3; vertex++) if (!Number.isFinite(this.physicalHeights[this.queryIndex.getVertexIndex(triangle, vertex)])) throw new Error('Terrain physical heights must match indexed vertices');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Exact retained typed arrays; snapshots cannot mutate query ownership. */
|
|
233
|
+
getStatistics(): TerrainMeshStatistics {
|
|
234
|
+
return {geometryBytes: this.queryIndex.geometryBytes + (this.physicalHeights?.byteLength ?? 0), queryIndexBytes: this.queryIndex.hierarchyBytes, requiredQueryIndexBytes: this.queryIndex.requiredHierarchyBytes};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Cooperatively warm picking acceleration; queries during preparation remain exact linear queries. */
|
|
238
|
+
*prepareQueryIndexSteps(): Generator<void, void, void> {yield* this.queryIndex.prepareHierarchySteps();}
|
|
239
|
+
|
|
240
|
+
/** Detached conservative mesh bounds; no mutable geometry or acceleration buffers escape. */
|
|
241
|
+
getBounds(): {minimum: [number, number, number]; maximum: [number, number, number]} | null {return this.queryIndex.getBounds();}
|
|
242
|
+
|
|
243
|
+
/** Indexed queries; an optional display clip rejects candidates before nearest-hit selection. */
|
|
244
|
+
intersectRay(ray: TerrainRay, maxDistance = Infinity, acceptPosition?: (position: readonly [number, number, number], triangleIndex: number, barycentric: readonly [number, number, number]) => boolean): TerrainSurfaceHit | null {
|
|
245
|
+
if (maxDistance < 0 || Number.isNaN(maxDistance)) throw new Error('Terrain ray distance must be non-negative');
|
|
246
|
+
const length = Math.hypot(...ray.direction);
|
|
247
|
+
if (ray.origin.length !== 3 || ray.direction.length !== 3 || !ray.origin.every(Number.isFinite) ||
|
|
248
|
+
!ray.direction.every(Number.isFinite) || !Number.isFinite(length) || length === 0 || !this.queryIndex.triangleCount) return null;
|
|
249
|
+
const direction: [number, number, number] = [ray.direction[0] / length, ray.direction[1] / length, ray.direction[2] / length];
|
|
250
|
+
const hit = this.queryIndex.queryRay(ray.origin, direction, maxDistance, candidate => {
|
|
251
|
+
if (acceptPosition && !acceptPosition([ray.origin[0] + direction[0] * candidate.parameter, ray.origin[1] + direction[1] * candidate.parameter, ray.origin[2] + direction[2] * candidate.parameter], candidate.triangleIndex, candidate.barycentric)) return false;
|
|
252
|
+
if (this.projection === 'globe' && Math.hypot(ray.origin[0] + direction[0] * candidate.parameter,
|
|
253
|
+
ray.origin[1] + direction[1] * candidate.parameter, ray.origin[2] + direction[2] * candidate.parameter) === 0) return false;
|
|
254
|
+
if (!this.cullBackfaces) return true;
|
|
255
|
+
const vertices = [0, 1, 2].map(vertex => this.queryIndex.getTriangleVertex(candidate.triangleIndex, vertex).position);
|
|
256
|
+
const first = new Vector3(vertices[0]);
|
|
257
|
+
const normal = new Vector3(vertices[1]).subtract(first).cross(new Vector3(vertices[2]).subtract(first));
|
|
258
|
+
return normal.dot(direction) < 0;
|
|
259
|
+
}).hit;
|
|
260
|
+
if (!hit) return null;
|
|
261
|
+
const index = hit.triangleIndex * 3;
|
|
262
|
+
const vertices = [0, 1, 2].map(offset => this.queryIndex.getTriangleVertex(hit.triangleIndex, offset).position);
|
|
263
|
+
const position: [number, number, number] = [ray.origin[0] + direction[0] * hit.parameter,
|
|
264
|
+
ray.origin[1] + direction[1] * hit.parameter, ray.origin[2] + direction[2] * hit.parameter];
|
|
265
|
+
const normal = new Vector3(vertices[1]).subtract(vertices[0]).cross(new Vector3(vertices[2]).subtract(vertices[0])).normalize();
|
|
266
|
+
const coordinate = getGeographicProjectionProcedure(this.projection).unproject(position);
|
|
267
|
+
const physicalHeights = this.physicalHeights;
|
|
268
|
+
if (physicalHeights) coordinate[2] = hit.barycentric.reduce((height, weight, vertex) => height + weight * physicalHeights[this.queryIndex.getVertexIndex(hit.triangleIndex, vertex)], 0);
|
|
269
|
+
return {position, coordinate,
|
|
270
|
+
distance: hit.parameter, normal: [normal[0], normal[1], normal[2]], triangleIndex: index / 3, barycentric: hit.barycentric};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Route top-origin CSS pixels through the actual HostFrame eye and its near/far segment. */
|
|
275
|
+
export function pickTerrainAt(frame: HostFrame, pixel: {x: number; y: number}, surface: TerrainSurface,
|
|
276
|
+
options: FeatureSelectionOptions = {}, activeViewId?: string): TerrainSurfaceHit | null {
|
|
277
|
+
if (surface.projection !== frame.projection.type) throw new Error('Terrain surface and HostFrame projection must match');
|
|
278
|
+
if (![pixel.x, pixel.y].every(Number.isFinite)) throw new Error('Terrain query pixels must be finite');
|
|
279
|
+
if (options.radius !== undefined && options.radius !== 0) throw new Error('Terrain queries select one ray, not a pixel radius');
|
|
280
|
+
const resolved = frame.resolveSelectionPoint(pixel, options, activeViewId);
|
|
281
|
+
if (!resolved) return null;
|
|
282
|
+
const {view, pixel: local} = resolved;
|
|
283
|
+
const matrix = new Matrix4(Array.from(view.camera.projection));
|
|
284
|
+
// Globe HostCamera.projection already contains the host view matrix.
|
|
285
|
+
if (surface.projection !== 'globe') matrix.multiplyRight(Array.from(view.camera.view));
|
|
286
|
+
const determinant = matrix.determinant();
|
|
287
|
+
if (!Number.isFinite(determinant) || determinant === 0) return null;
|
|
288
|
+
matrix.invert();
|
|
289
|
+
const x = 2 * local.x / view.viewport.width - 1;
|
|
290
|
+
const y = 1 - 2 * local.y / view.viewport.height;
|
|
291
|
+
const points = [-1, 1].map(z => matrix.transform([x, y, z, 1]));
|
|
292
|
+
if (points.some(point => !point.every(Number.isFinite) || point[3] === 0)) return null;
|
|
293
|
+
const origin: [number, number, number] = [points[0][0] / points[0][3], points[0][1] / points[0][3], points[0][2] / points[0][3]];
|
|
294
|
+
const direction: [number, number, number] = [points[1][0] / points[1][3] - origin[0],
|
|
295
|
+
points[1][1] / points[1][3] - origin[1], points[1][2] / points[1][3] - origin[2]];
|
|
296
|
+
const hit = surface.intersectRay({origin, direction}, Math.hypot(...direction));
|
|
297
|
+
return hit ? {...hit, renderViewId: view.id} : null;
|
|
298
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
6
|
+
|
|
7
|
+
/** Adapt Tangram's globe anchor and segment test to an explicit sphere radius. */
|
|
8
|
+
const glsl = `
|
|
9
|
+
vec3 globeHorizon_getAnchor(vec3 direction, float altitude, float radius, float earthRadius) {
|
|
10
|
+
return normalize(direction) * radius * (1. + altitude / earthRadius);
|
|
11
|
+
}
|
|
12
|
+
bool globeHorizon_isOccluded(vec3 position, vec3 eye, float radius) {
|
|
13
|
+
vec3 sphere_eye = eye / radius;
|
|
14
|
+
if (dot(sphere_eye, sphere_eye) <= 1.) { return false; }
|
|
15
|
+
vec3 sphere_position = position / radius;
|
|
16
|
+
vec3 segment = sphere_position - sphere_eye;
|
|
17
|
+
float segment_length_squared = dot(segment, segment);
|
|
18
|
+
if (segment_length_squared == 0.) { return false; }
|
|
19
|
+
float closest_amount = clamp(-dot(sphere_eye, segment) / segment_length_squared, 0., 1.);
|
|
20
|
+
vec3 closest = mix(sphere_eye, sphere_position, closest_amount);
|
|
21
|
+
return dot(closest, closest) < 1. - 0.000001;
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/** Same radius-normalized segment test and surface tolerance as Tangram's WGSL. */
|
|
26
|
+
const wgsl = `
|
|
27
|
+
fn globeHorizon_getAnchor(direction: vec3<f32>, altitude: f32, radius: f32, earthRadius: f32) -> vec3<f32> {
|
|
28
|
+
return normalize(direction) * radius * (1.0 + altitude / earthRadius);
|
|
29
|
+
}
|
|
30
|
+
fn globeHorizon_isOccluded(position: vec3<f32>, eye: vec3<f32>, radius: f32) -> bool {
|
|
31
|
+
let sphere_eye = eye / radius;
|
|
32
|
+
if (dot(sphere_eye, sphere_eye) <= 1.0) { return false; }
|
|
33
|
+
let sphere_position = position / radius;
|
|
34
|
+
let segment = sphere_position - sphere_eye;
|
|
35
|
+
let segment_length_squared = dot(segment, segment);
|
|
36
|
+
if (segment_length_squared == 0.0) { return false; }
|
|
37
|
+
let closest_amount = clamp(-dot(sphere_eye, segment) / segment_length_squared, 0.0, 1.0);
|
|
38
|
+
let closest = mix(sphere_eye, sphere_position, closest_amount);
|
|
39
|
+
return dot(closest, closest) < 1.0 - 0.000001;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Experimental binding-free sphere horizon occlusion adapted from Tangram label shaders.
|
|
45
|
+
* Position and eye are center-relative in the same orthonormal frame and linear units;
|
|
46
|
+
* radius must be positive. Missing/interior eyes have no exterior horizon (false).
|
|
47
|
+
* The finite eye-to-position segment, not an infinite ray, must enter the sphere by
|
|
48
|
+
* more than the squared-unit-radius 1e-6 tolerance. Elevated anchors are supported.
|
|
49
|
+
* getAnchor restores a nonzero direction's geographic radius using altitude and a
|
|
50
|
+
* positive earthRadius in identical units. Finite, f32-representable inputs required.
|
|
51
|
+
*/
|
|
52
|
+
export const globeHorizon = {name: 'globeHorizon', vs: glsl, fs: glsl, source: wgsl} satisfies ShaderModule;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
6
|
+
|
|
7
|
+
/** Decode linear normalized RGB samples; coefficients and offsets define output units. */
|
|
8
|
+
const glsl = `
|
|
9
|
+
float heightDecode_getHeight(vec3 rgb, vec3 coefficients, float offset) {
|
|
10
|
+
return dot(rgb * 255., coefficients) + offset;
|
|
11
|
+
}
|
|
12
|
+
float heightDecode_getTerrarium(vec3 rgb) {
|
|
13
|
+
// Subtract the red-channel offset before adding fractional blue elevation.
|
|
14
|
+
return (rgb.r * 255. - 128.) * 256. + rgb.g * 255. + rgb.b * (255. / 256.);
|
|
15
|
+
}
|
|
16
|
+
float heightDecode_getTerrainRGB(vec3 rgb) {
|
|
17
|
+
return heightDecode_getHeight(rgb, vec3(65536., 256., 1.), 0.) * 0.1 - 10000.;
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
/** WGSL counterpart; sampling, sRGB conversion and no-data policy remain host-owned. */
|
|
22
|
+
const wgsl = `
|
|
23
|
+
fn heightDecode_getHeight(rgb: vec3<f32>, coefficients: vec3<f32>, offset: f32) -> f32 {
|
|
24
|
+
return dot(rgb * 255.0, coefficients) + offset;
|
|
25
|
+
}
|
|
26
|
+
fn heightDecode_getTerrarium(rgb: vec3<f32>) -> f32 {
|
|
27
|
+
return (rgb.r * 255.0 - 128.0) * 256.0 + rgb.g * 255.0 + rgb.b * (255.0 / 256.0);
|
|
28
|
+
}
|
|
29
|
+
fn heightDecode_getTerrainRGB(rgb: vec3<f32>) -> f32 {
|
|
30
|
+
return heightDecode_getHeight(rgb, vec3<f32>(65536.0, 256.0, 1.0), 0.0) * 0.1 - 10000.0;
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Experimental binding-free RGB elevation decoding for normalized linear samples.
|
|
36
|
+
* Terrarium and Terrain-RGB return meters. Custom coefficients apply to byte-valued
|
|
37
|
+
* channels (normalized RGB multiplied by 255); custom output units are host-defined.
|
|
38
|
+
* No rounding, clamping, alpha/no-data interpretation or sampling is performed.
|
|
39
|
+
* Finite inputs are required; f32 precision limits apply, especially at large heights.
|
|
40
|
+
*/
|
|
41
|
+
export const heightDecode = {name: 'heightDecode', vs: glsl, fs: glsl, source: wgsl} satisfies ShaderModule;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
6
|
+
|
|
7
|
+
/** Projection-independent finite-difference normals and directional hillshading. */
|
|
8
|
+
const glsl = `
|
|
9
|
+
// Heights are west, east, south, north, in the same linear units as spacing.
|
|
10
|
+
// Positive local X is east, positive Y is north, and positive Z is up.
|
|
11
|
+
vec3 hillshade_getNormal(vec4 heights, vec2 spacing) {
|
|
12
|
+
vec2 safe_spacing = max(abs(spacing), vec2(0.000001));
|
|
13
|
+
vec2 gradient = vec2(heights.x - heights.y, heights.z - heights.w) / (2. * safe_spacing);
|
|
14
|
+
return normalize(vec3(gradient, 1.));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Rescaling avoids length underflow/overflow and keeps every nonzero direction scale invariant.
|
|
18
|
+
vec3 hillshade_normalizeDirection(vec3 direction) {
|
|
19
|
+
float scale = max(max(abs(direction.x), abs(direction.y)), abs(direction.z));
|
|
20
|
+
if (scale == 0.) { return vec3(0.); }
|
|
21
|
+
return normalize(direction / scale);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Light direction points from the surface towards the light in the same local frame.
|
|
25
|
+
float hillshade_getIntensity(vec3 normal, vec3 light_direction, float ambient) {
|
|
26
|
+
vec3 unit_normal = hillshade_normalizeDirection(normal);
|
|
27
|
+
vec3 unit_light = hillshade_normalizeDirection(light_direction);
|
|
28
|
+
float diffuse = clamp(dot(unit_normal, unit_light), 0., 1.);
|
|
29
|
+
float base = clamp(ambient, 0., 1.);
|
|
30
|
+
return base + (1. - base) * diffuse;
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
/** WGSL equivalent of the GLSL helpers, with no bindings or implicit coordinate conversion. */
|
|
35
|
+
const wgsl = `
|
|
36
|
+
// Heights: west, east, south, north. Local axes: east, north, up.
|
|
37
|
+
fn hillshade_getNormal(heights: vec4<f32>, spacing: vec2<f32>) -> vec3<f32> {
|
|
38
|
+
let safe_spacing = max(abs(spacing), vec2<f32>(0.000001));
|
|
39
|
+
let gradient = vec2<f32>(heights.x - heights.y, heights.z - heights.w) / (2.0 * safe_spacing);
|
|
40
|
+
return normalize(vec3<f32>(gradient, 1.0));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn hillshade_normalizeDirection(direction: vec3<f32>) -> vec3<f32> {
|
|
44
|
+
let scale = max(max(abs(direction.x), abs(direction.y)), abs(direction.z));
|
|
45
|
+
if (scale == 0.0) { return vec3<f32>(0.0); }
|
|
46
|
+
return normalize(direction / scale);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn hillshade_getIntensity(normal: vec3<f32>, light_direction: vec3<f32>, ambient: f32) -> f32 {
|
|
50
|
+
let unit_normal = hillshade_normalizeDirection(normal);
|
|
51
|
+
let unit_light = hillshade_normalizeDirection(light_direction);
|
|
52
|
+
let diffuse = clamp(dot(unit_normal, unit_light), 0.0, 1.0);
|
|
53
|
+
let base = clamp(ambient, 0.0, 1.0);
|
|
54
|
+
return base + (1.0 - base) * diffuse;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Experimental luma.gl ShaderModule with provider-independent hillshade functions.
|
|
60
|
+
* Hosts supply decoded, finite heights, positive finite neighbor spacing, and a
|
|
61
|
+
* local-frame light vector. Sampling, no-data handling, seam stitching, coordinate
|
|
62
|
+
* transforms, vertical exaggeration and color composition stay with the host.
|
|
63
|
+
* No uniforms, samplers, injection hooks, or runtime dependencies are introduced.
|
|
64
|
+
*/
|
|
65
|
+
export const hillshade = {
|
|
66
|
+
name: 'hillshade',
|
|
67
|
+
vs: glsl,
|
|
68
|
+
fs: glsl,
|
|
69
|
+
source: wgsl
|
|
70
|
+
} satisfies ShaderModule;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
export {hillshade} from './hillshade';
|
|
6
|
+
export {triplanar} from './triplanar';
|
|
7
|
+
export {planar} from './planar';
|
|
8
|
+
export {sphereMap} from './sphere-map';
|
|
9
|
+
export {heightDecode} from './height-decode';
|
|
10
|
+
export {globeHorizon} from './globe-horizon';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
7
|
+
|
|
8
|
+
/** Tangram uses one frequency for both world XY coordinates, not anisotropic XY scaling. */
|
|
9
|
+
const glsl = `
|
|
10
|
+
vec2 planar_getUV(vec3 position, float frequency) { return fract(position.xy * frequency); }
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
/** WGSL equivalent with explicit wrapping, including negative positions and frequencies. */
|
|
14
|
+
const wgsl = `
|
|
15
|
+
fn planar_getUV(position: vec3<f32>, frequency: f32) -> vec2<f32> { return fract(position.xy * frequency); }
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Experimental binding-free planar mapping extracted from Tangram material GLSL.
|
|
20
|
+
* Frequency is repeats per position unit; finite inputs must retain f32 UV precision.
|
|
21
|
+
* Tangram's legacy vec2 scale uses only scale.x: pass that component here.
|
|
22
|
+
* The host owns textures, sampling, filtering and coordinate transforms.
|
|
23
|
+
*/
|
|
24
|
+
export const planar = {name: 'planar', vs: glsl, fs: glsl, source: wgsl} satisfies ShaderModule;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
7
|
+
|
|
8
|
+
/** Reflection-map coordinates, not geographic sphere coordinates. */
|
|
9
|
+
const glsl = `
|
|
10
|
+
vec2 sphereMap_getUV(vec3 eye_to_point, vec3 normal, vec2 skew) {
|
|
11
|
+
vec3 eye = normalize(eye_to_point);
|
|
12
|
+
eye.xy -= skew;
|
|
13
|
+
eye = normalize(eye);
|
|
14
|
+
vec3 reflected = reflect(eye, normal);
|
|
15
|
+
reflected.z += 1.0;
|
|
16
|
+
return reflected.xy / (2.0 * length(reflected)) + 0.5;
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
/** WGSL equivalent retaining camera-skew and reflection order. */
|
|
21
|
+
const wgsl = `
|
|
22
|
+
fn sphereMap_getUV(eye_to_point: vec3<f32>, normal: vec3<f32>, skew: vec2<f32>) -> vec2<f32> {
|
|
23
|
+
var eye = normalize(eye_to_point);
|
|
24
|
+
eye = normalize(vec3<f32>(eye.xy - skew, eye.z));
|
|
25
|
+
var reflected = reflect(eye, normal);
|
|
26
|
+
reflected.z += 1.0;
|
|
27
|
+
return reflected.xy / (2.0 * length(reflected)) + 0.5;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Experimental binding-free sphere-map reflection UVs for luma assemblers.
|
|
33
|
+
* Supply a nonzero camera-to-surface vector and unit surface normal in one frame.
|
|
34
|
+
* Inputs, their intermediate lengths and UVs must be finite. The back-facing
|
|
35
|
+
* reflection pole (0, 0, -1) and a zero post-skew eye are undefined, as in Tangram.
|
|
36
|
+
* This is environment mapping, not equirectangular mapping or GlobeView projection.
|
|
37
|
+
*/
|
|
38
|
+
export const sphereMap = {name: 'sphereMap', vs: glsl, fs: glsl, source: wgsl} satisfies ShaderModule;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
7
|
+
|
|
8
|
+
/** Binding-free helpers extracted from lights/material.glsl; axis scales are UV frequencies. */
|
|
9
|
+
const glsl = `
|
|
10
|
+
vec3 triplanar_getWeights(vec3 normal) {
|
|
11
|
+
vec3 weights = normalize(max(abs(normal), vec3(0.00001)));
|
|
12
|
+
return weights / (weights.x + weights.y + weights.z);
|
|
13
|
+
}
|
|
14
|
+
vec2 triplanar_getUVX(vec3 position, vec3 scale) { return fract(position.yz * scale.x); }
|
|
15
|
+
vec2 triplanar_getUVY(vec3 position, vec3 scale) { return fract(position.xz * scale.y); }
|
|
16
|
+
vec2 triplanar_getUVZ(vec3 position, vec3 scale) { return fract(position.xy * scale.z); }
|
|
17
|
+
vec4 triplanar_blend(vec4 sample_x, vec4 sample_y, vec4 sample_z, vec3 weights) {
|
|
18
|
+
return sample_x * weights.x + sample_y * weights.y + sample_z * weights.z;
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
/** WGSL equivalent; callers sample their own textures before blending. */
|
|
23
|
+
const wgsl = `
|
|
24
|
+
fn triplanar_getWeights(normal: vec3<f32>) -> vec3<f32> {
|
|
25
|
+
let weights = normalize(max(abs(normal), vec3<f32>(0.00001)));
|
|
26
|
+
return weights / (weights.x + weights.y + weights.z);
|
|
27
|
+
}
|
|
28
|
+
fn triplanar_getUVX(position: vec3<f32>, scale: vec3<f32>) -> vec2<f32> { return fract(position.yz * scale.x); }
|
|
29
|
+
fn triplanar_getUVY(position: vec3<f32>, scale: vec3<f32>) -> vec2<f32> { return fract(position.xz * scale.y); }
|
|
30
|
+
fn triplanar_getUVZ(position: vec3<f32>, scale: vec3<f32>) -> vec2<f32> { return fract(position.xy * scale.z); }
|
|
31
|
+
fn triplanar_blend(sample_x: vec4<f32>, sample_y: vec4<f32>, sample_z: vec4<f32>, weights: vec3<f32>) -> vec4<f32> {
|
|
32
|
+
return sample_x * weights.x + sample_y * weights.y + sample_z * weights.z;
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Experimental Tangram-compatible triplanar UVs and RGBA blending for luma assemblers.
|
|
38
|
+
* Inputs must be finite and small enough for f32 normalization and UV precision.
|
|
39
|
+
* Normal components have a 1e-5 floor; a zero normal gives equal weights.
|
|
40
|
+
* Position and normal share a coordinate frame. Each scale component multiplies
|
|
41
|
+
* both UV coordinates of its projection plane, including negative frequencies.
|
|
42
|
+
* No sign-dependent UV mirroring or tangent-space normal reorientation is applied.
|
|
43
|
+
* Hosts own texture bindings, sampling, derivatives, color space and normal maps.
|
|
44
|
+
*/
|
|
45
|
+
export const triplanar = {name: 'triplanar', vs: glsl, fs: glsl, source: wgsl} satisfies ShaderModule;
|