@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,32 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {getProjectionSurface, PROJECTION_CONSTANTS} from '../scene/projection_math';
|
|
6
|
+
|
|
7
|
+
/** Zero-relief globe point followed by the common-space displacement per unit exaggeration. */
|
|
8
|
+
export function createTerrainGlobePoint(coordinate: readonly [number, number], height: number, normalized?: readonly [number, number]): Float64Array {
|
|
9
|
+
const circumference = 2 * Math.PI * PROJECTION_CONSTANTS.mercatorRadius;
|
|
10
|
+
const x = normalized ? (normalized[0] - Math.floor(normalized[0]) - 0.5) * circumference : coordinate[0];
|
|
11
|
+
const y = normalized ? (0.5 - normalized[1]) * circumference : coordinate[1];
|
|
12
|
+
const base = getProjectionSurface(x, y, 'globe').position;
|
|
13
|
+
return Float64Array.from([...base, ...base.map(component => component * height / PROJECTION_CONSTANTS.earthRadius)]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Preserve a canonical face's affine globe plane rather than projecting a new cut back onto the sphere. */
|
|
17
|
+
export function interpolateTerrainGlobePoints(points: readonly Float64Array[], weights: ArrayLike<number>): Float64Array {
|
|
18
|
+
return Float64Array.from({length: points[0].length}, (_value, channel) =>
|
|
19
|
+
points.reduce((sum, point, corner) => sum + point[channel] * weights[corner], 0));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Evaluate the same current/previous affine surface as the shader, with independent authored physical altitude. */
|
|
23
|
+
export function projectTerrainGlobeVertex(channels: ArrayLike<number>, vertex: number, blend: number, exaggeration: number,
|
|
24
|
+
coordinate: readonly [number, number, number]): [number, number, number] {
|
|
25
|
+
const offset = vertex * 12;
|
|
26
|
+
const direction = coordinate[2] ? getProjectionSurface(coordinate[0], coordinate[1], 'globe').position : [0, 0, 0];
|
|
27
|
+
const component = (axis: number) =>
|
|
28
|
+
channels[offset + 6 + axis] * (1 - blend) + channels[offset + axis] * blend +
|
|
29
|
+
(channels[offset + 9 + axis] * (1 - blend) + channels[offset + 3 + axis] * blend) * exaggeration +
|
|
30
|
+
direction[axis] * coordinate[2] / PROJECTION_CONSTANTS.earthRadius;
|
|
31
|
+
return [component(0), component(1), component(2)];
|
|
32
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type VertexLayout from '../gl/vertex_layout';
|
|
6
|
+
import {interpolatePackedVertex, selectTrianglePlane} from '../gl/globe_mesh';
|
|
7
|
+
|
|
8
|
+
/** A clipped authored vertex, retaining its unrounded geometric clipping position. */
|
|
9
|
+
interface ClipVertex {
|
|
10
|
+
/** Authored packed channels, never shared across feature triangles. */
|
|
11
|
+
record: Uint8Array;
|
|
12
|
+
/** Expanded nominal tile-space XY used for clipping. */
|
|
13
|
+
position: [number, number];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Closed tile-space triangle envelope and original canonical face offset. */
|
|
17
|
+
interface FaceBounds {
|
|
18
|
+
/** Canonical index-buffer offset, not a reordered face identity. */
|
|
19
|
+
offset: number;
|
|
20
|
+
/** Closed XY envelope. */
|
|
21
|
+
bounds: [number, number, number, number];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Temporary balanced bounds tree; released after one worker mesh is prepared. */
|
|
25
|
+
interface FaceNode {
|
|
26
|
+
/** Union of descendant envelopes. */
|
|
27
|
+
bounds: [number, number, number, number];
|
|
28
|
+
/** Original canonical triangles at a small leaf. */
|
|
29
|
+
faces?: FaceBounds[];
|
|
30
|
+
/** Median-partitioned children. */
|
|
31
|
+
children?: [FaceNode, FaceNode];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Styled geometry and its barycentric references to the shared physical surface. */
|
|
35
|
+
export interface TerrainStyledTopology {
|
|
36
|
+
/** Packed source channels interpolated only within each original feature face. */
|
|
37
|
+
vertices: Uint8Array;
|
|
38
|
+
/** Source winding/order retained; disconnected features never weld their attributes. */
|
|
39
|
+
indices: Uint32Array;
|
|
40
|
+
/** Three canonical vertex identities per output vertex. */
|
|
41
|
+
surfaceIndices: Uint32Array;
|
|
42
|
+
/** Three surface weights at the actual packed nominal XY per output vertex. */
|
|
43
|
+
surfaceWeights: Float64Array;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Signed twice-area in tile-space XY; canonical faces are counterclockwise. */
|
|
47
|
+
function cross(first: readonly number[], second: readonly number[], third: readonly number[]): number {
|
|
48
|
+
return (second[0] - first[0]) * (third[1] - first[1]) - (second[1] - first[1]) * (third[0] - first[0]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Build a mesh-local broad phase instead of scanning every terrain cell for every source face. */
|
|
52
|
+
function buildNode(faces: FaceBounds[]): FaceNode {
|
|
53
|
+
const bounds: FaceNode['bounds'] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
54
|
+
for (const face of faces) for (let axis = 0; axis < 2; axis++) {
|
|
55
|
+
bounds[axis] = Math.min(bounds[axis], face.bounds[axis]);
|
|
56
|
+
bounds[axis + 2] = Math.max(bounds[axis + 2], face.bounds[axis + 2]);
|
|
57
|
+
}
|
|
58
|
+
if (faces.length <= 8) return {bounds, faces};
|
|
59
|
+
const axis = bounds[2] - bounds[0] >= bounds[3] - bounds[1] ? 0 : 1;
|
|
60
|
+
faces.sort((first, second) => first.bounds[axis] + first.bounds[axis + 2] - second.bounds[axis] - second.bounds[axis + 2] || first.offset - second.offset);
|
|
61
|
+
const middle = Math.floor(faces.length / 2);
|
|
62
|
+
return {bounds, children: [buildNode(faces.slice(0, middle)), buildNode(faces.slice(middle))]};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Intersect source faces with complete DEM triangles, preserving holes, walls and feature identity.
|
|
66
|
+
* Heights are interpolated from the terrain face, not resampled from a different bilinear surface.
|
|
67
|
+
* The caller owns DEM validation, physical sampling and final draw-byte admission.
|
|
68
|
+
*/
|
|
69
|
+
export function conformTerrainStyledMesh(options: {
|
|
70
|
+
/** Floating-position authored records before terrain channels are appended. */
|
|
71
|
+
vertices: Uint8Array;
|
|
72
|
+
/** Valid source triangles in authored draw order. */
|
|
73
|
+
indices: Uint16Array | Uint32Array;
|
|
74
|
+
/** Original packed attribute interpolation semantics. */
|
|
75
|
+
layout: VertexLayout;
|
|
76
|
+
/** Canonical terrain nodes in the same tile-local coordinate system. */
|
|
77
|
+
surfacePositions: Float64Array;
|
|
78
|
+
/** Counterclockwise canonical terrain triangles. */
|
|
79
|
+
surfaceIndices: Uint32Array;
|
|
80
|
+
/** Nominal expanded corner, including road width and offset. */
|
|
81
|
+
getPosition: (record: DataView) => [number, number];
|
|
82
|
+
/** Authored elevation, used to retain vertical walls rather than discarding XY degenerates. */
|
|
83
|
+
getElevation: (record: DataView) => number;
|
|
84
|
+
/** Maximum completed output vertices; a failed admission never returns partial geometry. */
|
|
85
|
+
maxVertices: number;
|
|
86
|
+
}): TerrainStyledTopology {
|
|
87
|
+
const {layout, surfacePositions, surfaceIndices} = options;
|
|
88
|
+
const point = (index: number): [number, number] => [surfacePositions[index * 2], surfacePositions[index * 2 + 1]];
|
|
89
|
+
const edgeCounts = new Map<string, number>();
|
|
90
|
+
const edgeKey = (first: number, second: number) => `${Math.min(first, second)}:${Math.max(first, second)}`;
|
|
91
|
+
const faces: FaceBounds[] = [];
|
|
92
|
+
for (let offset = 0; offset < surfaceIndices.length; offset += 3) {
|
|
93
|
+
const corners = [0, 1, 2].map(corner => point(surfaceIndices[offset + corner]));
|
|
94
|
+
faces.push({offset, bounds: [Math.min(...corners.map(vertex => vertex[0])), Math.min(...corners.map(vertex => vertex[1])),
|
|
95
|
+
Math.max(...corners.map(vertex => vertex[0])), Math.max(...corners.map(vertex => vertex[1]))]});
|
|
96
|
+
for (let edge = 0; edge < 3; edge++) {
|
|
97
|
+
const key = edgeKey(surfaceIndices[offset + edge], surfaceIndices[offset + (edge + 1) % 3]);
|
|
98
|
+
edgeCounts.set(key, (edgeCounts.get(key) ?? 0) + 1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const root = faces.length ? buildNode(faces) : null;
|
|
102
|
+
const records: Uint8Array[] = [], indices: number[] = [], references: number[] = [], weights: number[] = [];
|
|
103
|
+
const view = (record: Uint8Array) => new DataView(record.buffer, record.byteOffset, layout.stride);
|
|
104
|
+
const intersects = (first: readonly number[], second: readonly number[]) => first[0] <= second[2] && first[2] >= second[0] && first[1] <= second[3] && first[3] >= second[1];
|
|
105
|
+
for (let offset = 0; offset < options.indices.length; offset += 3) {
|
|
106
|
+
const source = [0, 1, 2].map(corner => {
|
|
107
|
+
const index = options.indices[offset + corner];
|
|
108
|
+
const record = options.vertices.subarray(index * layout.stride, (index + 1) * layout.stride);
|
|
109
|
+
return {record, position: options.getPosition(view(record))};
|
|
110
|
+
});
|
|
111
|
+
// Validate flat/custom channels even when a face happens not to need clipping.
|
|
112
|
+
interpolatePackedVertex(source[0].record, source[1].record, layout, 0);
|
|
113
|
+
interpolatePackedVertex(source[0].record, source[2].record, layout, 0);
|
|
114
|
+
if (layout.offset.a_extrude !== undefined) {
|
|
115
|
+
// Width/offset scale products must remain affine within an authored face.
|
|
116
|
+
for (const corner of source.slice(1)) {
|
|
117
|
+
if (view(corner.record).getFloat32(layout.offset.a_position + 8, true) !== view(source[0].record).getFloat32(layout.offset.a_position + 8, true) ||
|
|
118
|
+
layout.offset.a_z_and_offset_scale !== undefined && view(corner.record).getInt16(layout.offset.a_z_and_offset_scale + 2, true) !== view(source[0].record).getInt16(layout.offset.a_z_and_offset_scale + 2, true)) {
|
|
119
|
+
throw new Error('Canonical terrain roads require uniform per-face width and offset scales');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const bounds = [Math.min(...source.map(vertex => vertex.position[0])), Math.min(...source.map(vertex => vertex.position[1])),
|
|
124
|
+
Math.max(...source.map(vertex => vertex.position[0])), Math.max(...source.map(vertex => vertex.position[1]))];
|
|
125
|
+
const plane = selectTrianglePlane(source.map(vertex => [...vertex.position, options.getElevation(view(vertex.record))] as const));
|
|
126
|
+
const candidates: FaceBounds[] = [], pending = root ? [root] : [];
|
|
127
|
+
while (pending.length) {
|
|
128
|
+
const node = pending.pop()!;
|
|
129
|
+
if (!intersects(bounds, node.bounds)) continue;
|
|
130
|
+
if (node.faces) for (const face of node.faces) {if (intersects(bounds, face.bounds)) candidates.push(face);}
|
|
131
|
+
else pending.push(...node.children!);
|
|
132
|
+
}
|
|
133
|
+
candidates.sort((first, second) => first.offset - second.offset);
|
|
134
|
+
for (const face of candidates) {
|
|
135
|
+
const corners = [0, 1, 2].map(corner => point(surfaceIndices[face.offset + corner]));
|
|
136
|
+
let polygon: ClipVertex[] = source;
|
|
137
|
+
for (let edge = 0; edge < 3 && polygon.length; edge++) {
|
|
138
|
+
const first = corners[edge], second = corners[(edge + 1) % 3];
|
|
139
|
+
// A wall on a shared terrain edge belongs to just one incident face.
|
|
140
|
+
if (polygon.every(vertex => cross(first, second, vertex.position) === 0) &&
|
|
141
|
+
edgeCounts.get(edgeKey(surfaceIndices[face.offset + edge], surfaceIndices[face.offset + (edge + 1) % 3])) === 2 &&
|
|
142
|
+
!(second[1] > first[1] || second[1] === first[1] && second[0] < first[0])) {polygon = []; break;}
|
|
143
|
+
const clipped: ClipVertex[] = [];
|
|
144
|
+
for (let index = 0; index < polygon.length; index++) {
|
|
145
|
+
let start = polygon[index], end = polygon[(index + 1) % polygon.length];
|
|
146
|
+
let startDistance = cross(first, second, start.position), endDistance = cross(first, second, end.position);
|
|
147
|
+
if (startDistance >= 0) clipped.push(start);
|
|
148
|
+
if (startDistance < 0 && endDistance > 0 || startDistance > 0 && endDistance < 0) {
|
|
149
|
+
// Stable geometric orientation gives reversed/shared edges identical intersections.
|
|
150
|
+
if (start.position[0] > end.position[0] || start.position[0] === end.position[0] && start.position[1] > end.position[1]) {
|
|
151
|
+
[start, end] = [end, start]; [startDistance, endDistance] = [endDistance, startDistance];
|
|
152
|
+
}
|
|
153
|
+
const fraction = startDistance / (startDistance - endDistance);
|
|
154
|
+
clipped.push({record: interpolatePackedVertex(start.record, end.record, layout, fraction), position: [
|
|
155
|
+
start.position[0] + (end.position[0] - start.position[0]) * fraction,
|
|
156
|
+
start.position[1] + (end.position[1] - start.position[1]) * fraction]});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
polygon = clipped;
|
|
160
|
+
}
|
|
161
|
+
if (polygon.length < 3) continue;
|
|
162
|
+
const positions = polygon.map(vertex => [...vertex.position, options.getElevation(view(vertex.record))] as const);
|
|
163
|
+
const area = (first: number, second: number, third: number) => cross(
|
|
164
|
+
[positions[first][plane[0]], positions[first][plane[1]]], [positions[second][plane[0]], positions[second][plane[1]]], [positions[third][plane[0]], positions[third][plane[1]]]);
|
|
165
|
+
if (!polygon.slice(2).some((_vertex, index) => area(0, index + 1, index + 2) !== 0)) continue;
|
|
166
|
+
if (records.length + polygon.length > options.maxVertices) throw new Error('Canonical styled terrain vertex/mesh budget exceeded');
|
|
167
|
+
const base = records.length, denominator = cross(corners[0], corners[1], corners[2]);
|
|
168
|
+
for (const vertex of polygon) {
|
|
169
|
+
const record = vertex.record.slice(), packed = view(record), actual = options.getPosition(packed);
|
|
170
|
+
// Preserve the road's dynamic extrusion; only correct roundoff in its centerline.
|
|
171
|
+
packed.setFloat32(layout.offset.a_position, packed.getFloat32(layout.offset.a_position, true) + vertex.position[0] - actual[0], true);
|
|
172
|
+
packed.setFloat32(layout.offset.a_position + 4, packed.getFloat32(layout.offset.a_position + 4, true) + vertex.position[1] - actual[1], true);
|
|
173
|
+
const position = options.getPosition(packed);
|
|
174
|
+
const first = cross(corners[1], corners[2], position) / denominator;
|
|
175
|
+
const second = cross(corners[2], corners[0], position) / denominator;
|
|
176
|
+
records.push(record); references.push(...surfaceIndices.subarray(face.offset, face.offset + 3)); weights.push(first, second, 1 - first - second);
|
|
177
|
+
}
|
|
178
|
+
for (let index = 1; index + 1 < polygon.length; index++) if (area(0, index, index + 1) !== 0) indices.push(base, base + index, base + index + 1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const vertices = new Uint8Array(records.length * layout.stride);
|
|
182
|
+
records.forEach((record, index) => vertices.set(record, index * layout.stride));
|
|
183
|
+
return {vertices, indices: Uint32Array.from(indices), surfaceIndices: Uint32Array.from(references), surfaceWeights: Float64Array.from(weights)};
|
|
184
|
+
}
|
|
@@ -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
|
+
const HALF_CIRCUMFERENCE_METERS = 20037508.342789244;
|
|
7
|
+
|
|
8
|
+
/** Project longitude/latitude degrees to Web Mercator meters using Tangram's established formula. */
|
|
9
|
+
export function projectLngLatToMetersLegacy(coordinates: readonly [number, number]): [number, number] {
|
|
10
|
+
const longitude = coordinates[0] * (HALF_CIRCUMFERENCE_METERS / 180);
|
|
11
|
+
const latitude =
|
|
12
|
+
(Math.log(Math.tan((coordinates[1] * Math.PI) / 360 + Math.PI / 4)) / Math.PI) *
|
|
13
|
+
HALF_CIRCUMFERENCE_METERS;
|
|
14
|
+
return [longitude, latitude];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Unproject Web Mercator meters to longitude/latitude degrees using Tangram's established formula. */
|
|
18
|
+
export function unprojectMetersToLngLatLegacy(coordinates: readonly [number, number]): [number, number] {
|
|
19
|
+
const longitude = (coordinates[0] / HALF_CIRCUMFERENCE_METERS) * 180;
|
|
20
|
+
const normalizedLatitude = coordinates[1] / HALF_CIRCUMFERENCE_METERS;
|
|
21
|
+
const latitude =
|
|
22
|
+
((2 * Math.atan(Math.exp(normalizedLatitude * Math.PI)) - Math.PI / 2) / Math.PI) * 180;
|
|
23
|
+
return [longitude, latitude];
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {lngLatToWorld, worldToLngLat} from '@math.gl/web-mercator';
|
|
6
|
+
import {projectLngLatToMetersLegacy} from './web-mercator-legacy';
|
|
7
|
+
|
|
8
|
+
const WORLD_SIZE = 512;
|
|
9
|
+
const HALF_CIRCUMFERENCE_METERS = 20037508.342789244;
|
|
10
|
+
const CIRCUMFERENCE_METERS = HALF_CIRCUMFERENCE_METERS * 2;
|
|
11
|
+
|
|
12
|
+
/** Return whether a normalized world coordinate lies on a slippy-map tile edge. */
|
|
13
|
+
function isTileBoundary(worldCoordinate: number): boolean {
|
|
14
|
+
// Every boundary through zoom 30 is an integer at zoom 30, so one check
|
|
15
|
+
// avoids a zoom loop on the per-coordinate projection hot path.
|
|
16
|
+
const tileCoordinate = worldCoordinate * 2 ** 30;
|
|
17
|
+
const nearestBoundary = Math.round(tileCoordinate);
|
|
18
|
+
const floatingPointTolerance = Number.EPSILON * Math.max(1, Math.abs(tileCoordinate)) * 4;
|
|
19
|
+
return Math.abs(tileCoordinate - nearestBoundary) <= floatingPointTolerance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Project longitude/latitude degrees to Tangram Web Mercator meters using math.gl. */
|
|
23
|
+
export function projectLngLatToMetersWithMath(
|
|
24
|
+
coordinates: readonly [number, number]
|
|
25
|
+
): [number, number] {
|
|
26
|
+
const [worldX, worldY] = lngLatToWorld([coordinates[0], coordinates[1]]);
|
|
27
|
+
const normalizedWorldX = worldX / WORLD_SIZE;
|
|
28
|
+
const normalizedWorldY = worldY / WORLD_SIZE;
|
|
29
|
+
const legacyMeters =
|
|
30
|
+
isTileBoundary(normalizedWorldX) || isTileBoundary(normalizedWorldY)
|
|
31
|
+
? projectLngLatToMetersLegacy(coordinates)
|
|
32
|
+
: undefined;
|
|
33
|
+
return [
|
|
34
|
+
legacyMeters?.[0] ?? normalizedWorldX * CIRCUMFERENCE_METERS - HALF_CIRCUMFERENCE_METERS,
|
|
35
|
+
// math.gl world Y is north-positive: latitude +40 maps above WORLD_SIZE / 2.
|
|
36
|
+
legacyMeters?.[1] ?? normalizedWorldY * CIRCUMFERENCE_METERS - HALF_CIRCUMFERENCE_METERS
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Unproject Tangram Web Mercator meters to longitude/latitude degrees using math.gl. */
|
|
41
|
+
export function unprojectMetersToLngLatWithMath(
|
|
42
|
+
coordinates: readonly [number, number]
|
|
43
|
+
): [number, number] {
|
|
44
|
+
const worldX =
|
|
45
|
+
((coordinates[0] + HALF_CIRCUMFERENCE_METERS) / CIRCUMFERENCE_METERS) * WORLD_SIZE;
|
|
46
|
+
// Preserve math.gl's north-positive world-Y convention for the inverse transform.
|
|
47
|
+
const worldY =
|
|
48
|
+
((coordinates[1] + HALF_CIRCUMFERENCE_METERS) / CIRCUMFERENCE_METERS) * WORLD_SIZE;
|
|
49
|
+
return worldToLngLat([worldX, worldY]) as [number, number];
|
|
50
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
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 Utils from '../utils/utils';
|
|
7
|
+
import ShaderProgram from '../gl/shader_program';
|
|
8
|
+
import CameraBase, {type CameraView, type CameraConfiguration, type Program, type UniformBuffer} from './camera_base';
|
|
9
|
+
import ExternalCamera from './external_camera';
|
|
10
|
+
|
|
11
|
+
/** Classic Tangram camera factory, intentionally absent from the core entry. */
|
|
12
|
+
export default class Camera extends CameraBase {
|
|
13
|
+
// Create a camera by type name, factory-style
|
|
14
|
+
static create(name: string, view: CameraView, config: CameraConfiguration): Camera {
|
|
15
|
+
switch (config.type) {
|
|
16
|
+
case 'external':
|
|
17
|
+
return new ExternalCamera(name, view, config);
|
|
18
|
+
case 'isometric':
|
|
19
|
+
return new IsometricCamera(name, view, config);
|
|
20
|
+
case 'flat':
|
|
21
|
+
return new FlatCamera(name, view, config);
|
|
22
|
+
case 'perspective':
|
|
23
|
+
/* falls through */
|
|
24
|
+
default:
|
|
25
|
+
return new PerspectiveCamera(name, view, config);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
Perspective matrix projection
|
|
33
|
+
|
|
34
|
+
This is a specialized perspective camera that, given a desired camera focal length (which can also vary by zoom level),
|
|
35
|
+
constrains the camera height above the ground plane such that the displayed ground area of the map matches that of
|
|
36
|
+
a traditional web mercator map. This means you can set the camera location by [lat, lng, zoom] as you would a typical
|
|
37
|
+
web mercator map, then adjust the focal length as needed.
|
|
38
|
+
|
|
39
|
+
Vanishing point can also be adjusted to achieve different 'viewing angles', e.g. instead of looking straight down into
|
|
40
|
+
the center of the viewport, the camera appears to be tilted at an angle. For example:
|
|
41
|
+
|
|
42
|
+
[0, 0] = looking towards center of viewport
|
|
43
|
+
[-250, -250] = looking 250 pixels from the viewport center to the lower-left corner
|
|
44
|
+
[400, 0] = looking 400 pixels to the right of the viewport center
|
|
45
|
+
*/
|
|
46
|
+
export class PerspectiveCamera extends Camera {
|
|
47
|
+
focal_length: number | number[][] | undefined;
|
|
48
|
+
fov: number | number[][] | undefined;
|
|
49
|
+
vanishing_point: number[];
|
|
50
|
+
vanishing_point_skew: number[];
|
|
51
|
+
|
|
52
|
+
constructor(name: string, view: CameraView, options: CameraConfiguration = {}) {
|
|
53
|
+
super(name, view, options);
|
|
54
|
+
this.type = 'perspective';
|
|
55
|
+
|
|
56
|
+
// a single scalar, or pairs of stops mapping zoom levels, e.g. [zoom, focal length]
|
|
57
|
+
this.focal_length = options.focal_length;
|
|
58
|
+
this.fov = options.fov;
|
|
59
|
+
if (!this.focal_length && !this.fov) {
|
|
60
|
+
// Default focal length ranges by zoom
|
|
61
|
+
this.focal_length = [[16, 2], [17, 2.5], [18, 3], [19, 4], [20, 6]];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.vanishing_point = options.vanishing_point || [0, 0]; // [x, y]
|
|
65
|
+
this.vanishing_point = this.vanishing_point.map(value => parseFloat(String(value))); // we implicitly only support px units here
|
|
66
|
+
this.vanishing_point_skew = [];
|
|
67
|
+
|
|
68
|
+
this.position_meters = [];
|
|
69
|
+
this.view_matrix = new Float64Array(16);
|
|
70
|
+
this.projection_matrix = new Float32Array(16);
|
|
71
|
+
|
|
72
|
+
// 'camera' is the name of the shader block, e.g. determines where in the shader this code is injected
|
|
73
|
+
ShaderProgram.replaceBlock('camera', `
|
|
74
|
+
uniform mat4 u_projection;
|
|
75
|
+
uniform vec3 u_eye;
|
|
76
|
+
uniform vec2 u_vanishing_point;
|
|
77
|
+
|
|
78
|
+
void cameraProjection (inout vec4 position) {
|
|
79
|
+
position = u_projection * position;
|
|
80
|
+
}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Constrains the camera so that the viewable area matches given the viewport height
|
|
85
|
+
// (in world space, e.g. meters), given either a camera focal length or field-of-view
|
|
86
|
+
// (focal length is used if both are passed).
|
|
87
|
+
constrainCamera({view_height, height, focal_length, fov}: {
|
|
88
|
+
view_height: number;
|
|
89
|
+
height?: number;
|
|
90
|
+
focal_length?: number;
|
|
91
|
+
fov?: number;
|
|
92
|
+
}): {view_height: number; height: number; focal_length: number; fov: number} {
|
|
93
|
+
// Solve for camera height
|
|
94
|
+
if (!height) {
|
|
95
|
+
// We have focal length, calculate FOV
|
|
96
|
+
if (focal_length) {
|
|
97
|
+
fov = Math.atan(1 / focal_length) * 2;
|
|
98
|
+
}
|
|
99
|
+
// We have FOV, calculate focal length
|
|
100
|
+
else if (fov) {
|
|
101
|
+
fov = fov * Math.PI / 180; // convert FOV degrees to radians
|
|
102
|
+
focal_length = 1 / Math.tan(fov / 2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Distance that camera should be from ground such that it fits the field of view expected
|
|
106
|
+
// for a conventional web mercator map at the current zoom level and camera focal length
|
|
107
|
+
height = view_height / 2 * (focal_length ?? 0);
|
|
108
|
+
}
|
|
109
|
+
// Solve for camera focal length / field-of-view
|
|
110
|
+
else {
|
|
111
|
+
focal_length = 2 * height / view_height;
|
|
112
|
+
fov = Math.atan(1 / focal_length) * 2;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
view_height,
|
|
117
|
+
height: height as number,
|
|
118
|
+
focal_length: focal_length as number,
|
|
119
|
+
fov: fov as number
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
updateMatrices(): void {
|
|
124
|
+
// TODO: only re-calculate these vars when necessary
|
|
125
|
+
|
|
126
|
+
// Height of the viewport in meters at current zoom
|
|
127
|
+
var viewport_height = this.view.size.css.height * this.view.meters_per_pixel;
|
|
128
|
+
|
|
129
|
+
// Compute camera properties to fit desired view
|
|
130
|
+
var { height, fov } = this.constrainCamera({
|
|
131
|
+
view_height: viewport_height,
|
|
132
|
+
focal_length: Utils.interpolate(this.view.zoom, this.focal_length) as number | undefined,
|
|
133
|
+
fov: Utils.interpolate(this.view.zoom, this.fov) as number | undefined
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// View matrix
|
|
137
|
+
var position = [this.view.center.meters.x, this.view.center.meters.y, height];
|
|
138
|
+
this.position_meters = position;
|
|
139
|
+
|
|
140
|
+
// Exclude camera height from view matrix
|
|
141
|
+
this.matrix4.identity().lookAt({
|
|
142
|
+
eye: [position[0], position[1], 0],
|
|
143
|
+
center: [position[0], position[1], -1],
|
|
144
|
+
up: [0, 1, 0]
|
|
145
|
+
}).toArray(this.view_matrix);
|
|
146
|
+
|
|
147
|
+
// Projection matrix
|
|
148
|
+
this.matrix4.identity().perspective({fovy: fov, aspect: this.view.aspect, near: 1, far: height * 2})
|
|
149
|
+
.toArray(this.projection_matrix);
|
|
150
|
+
|
|
151
|
+
// Convert vanishing point from pixels to viewport space
|
|
152
|
+
this.vanishing_point_skew[0] = this.vanishing_point[0] / this.view.size.css.width;
|
|
153
|
+
this.vanishing_point_skew[1] = this.vanishing_point[1] / this.view.size.css.height;
|
|
154
|
+
|
|
155
|
+
// Adjust projection matrix to include vanishing point skew
|
|
156
|
+
this.projection_matrix[8] = -this.vanishing_point_skew[0] * 2; // z column of x row, e.g. amount z skews x
|
|
157
|
+
this.projection_matrix[9] = -this.vanishing_point_skew[1] * 2; // z column of y row, e.g. amount z skews y
|
|
158
|
+
|
|
159
|
+
// Translate geometry into the distance so that camera is appropriate height above ground
|
|
160
|
+
// Additionally, adjust xy to compensate for any vanishing point skew, e.g. move geometry so that the displayed g
|
|
161
|
+
// plane of the map matches that expected by a traditional web mercator map at this [lat, lng, zoom].
|
|
162
|
+
this.matrix4.copy(this.projection_matrix).translate(
|
|
163
|
+
[
|
|
164
|
+
viewport_height/2 * this.view.aspect * (-this.vanishing_point_skew[0] * 2),
|
|
165
|
+
viewport_height/2 * (-this.vanishing_point_skew[1] * 2),
|
|
166
|
+
0
|
|
167
|
+
]
|
|
168
|
+
).toArray(this.projection_matrix);
|
|
169
|
+
|
|
170
|
+
// Include camera height in projection matrix
|
|
171
|
+
this.matrix4.copy(this.projection_matrix).translate([0, 0, -height]).toArray(this.projection_matrix);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
update(): void {
|
|
175
|
+
super.update();
|
|
176
|
+
this.updateMatrices();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setupProgram(program: Program, uniform_buffer?: UniformBuffer): void {
|
|
180
|
+
if (uniform_buffer) {
|
|
181
|
+
uniform_buffer.setUniforms({
|
|
182
|
+
u_projection: this.projection_matrix,
|
|
183
|
+
u_eye: [0, 0, this.position_meters[2]],
|
|
184
|
+
u_vanishing_point: this.vanishing_point_skew
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
program.uniform('Matrix4fv', 'u_projection', this.projection_matrix);
|
|
189
|
+
program.uniform('3f', 'u_eye', [0, 0, this.position_meters[2]]);
|
|
190
|
+
program.uniform('2fv', 'u_vanishing_point', this.vanishing_point_skew);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Isometric-style projection
|
|
197
|
+
// Note: this is actually an 'axonometric' projection, but I'm using the colloquial term isometric because it is more recognizable.
|
|
198
|
+
// An isometric projection is a specific subset of axonometric projections.
|
|
199
|
+
// 'axis' determines the xy skew applied to a vertex based on its z coordinate, e.g. [0, 1] axis causes buildings to be drawn
|
|
200
|
+
// straight upwards on screen at their true height, [0, .5] would draw them up at half-height, [1, 0] would be sideways, etc.
|
|
201
|
+
export class IsometricCamera extends Camera {
|
|
202
|
+
axis: {x: number; y: number};
|
|
203
|
+
viewport_height: number | null;
|
|
204
|
+
|
|
205
|
+
constructor(name: string, view: CameraView, options: CameraConfiguration = {}) {
|
|
206
|
+
super(name, view, options);
|
|
207
|
+
this.type = 'isometric';
|
|
208
|
+
const axis = options.axis;
|
|
209
|
+
this.axis = Array.isArray(axis)
|
|
210
|
+
? {x: axis[0], y: axis[1]}
|
|
211
|
+
: axis || {x: 0, y: 1};
|
|
212
|
+
|
|
213
|
+
this.position_meters = [];
|
|
214
|
+
this.viewport_height = null;
|
|
215
|
+
|
|
216
|
+
this.view_matrix = new Float64Array(16);
|
|
217
|
+
this.projection_matrix = new Float32Array(16);
|
|
218
|
+
|
|
219
|
+
// 'camera' is the name of the shader block, e.g. determines where in the shader this code is injected
|
|
220
|
+
ShaderProgram.replaceBlock('camera', `
|
|
221
|
+
uniform mat4 u_projection;
|
|
222
|
+
uniform vec3 u_eye;
|
|
223
|
+
uniform vec2 u_vanishing_point;
|
|
224
|
+
|
|
225
|
+
void cameraProjection (inout vec4 position) {
|
|
226
|
+
position = u_projection * position;
|
|
227
|
+
// position.xy += position.z * u_isometric_axis;
|
|
228
|
+
|
|
229
|
+
// Reverse z for depth buffer so up is negative,
|
|
230
|
+
// and scale down values so objects higher than one screen height will not get clipped
|
|
231
|
+
// pull forward slightly to avoid going past far clipping plane
|
|
232
|
+
position.z = -position.z / 100. + 1. - 0.001;
|
|
233
|
+
}`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
update(): void {
|
|
238
|
+
super.update();
|
|
239
|
+
|
|
240
|
+
this.viewport_height = this.view.size.css.height * this.view.meters_per_pixel;
|
|
241
|
+
var position = [this.view.center.meters.x, this.view.center.meters.y, this.viewport_height];
|
|
242
|
+
this.position_meters = position;
|
|
243
|
+
|
|
244
|
+
// View
|
|
245
|
+
this.matrix4.identity().translate([-position[0], -position[1], 0]).toArray(this.view_matrix);
|
|
246
|
+
|
|
247
|
+
// Projection
|
|
248
|
+
this.matrix4.identity().toArray(this.projection_matrix);
|
|
249
|
+
|
|
250
|
+
// apply isometric skew
|
|
251
|
+
this.projection_matrix[8] = this.axis.x / this.view.aspect; // z column of x row, e.g. amount z skews x
|
|
252
|
+
this.projection_matrix[9] = this.axis.y; // z column of x row, e.g. amount z skews y
|
|
253
|
+
|
|
254
|
+
// convert meters to viewport
|
|
255
|
+
this.matrix4.copy(this.projection_matrix).scale(
|
|
256
|
+
[
|
|
257
|
+
2 / this.view.size.meters.x,
|
|
258
|
+
2 / this.view.size.meters.y,
|
|
259
|
+
2 / this.view.size.meters.y
|
|
260
|
+
]
|
|
261
|
+
).toArray(this.projection_matrix);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
setupProgram(program: Program, uniform_buffer?: UniformBuffer): void {
|
|
265
|
+
if (uniform_buffer) {
|
|
266
|
+
uniform_buffer.setUniforms({
|
|
267
|
+
u_projection: this.projection_matrix,
|
|
268
|
+
u_eye: [0, 0, this.viewport_height ?? 0],
|
|
269
|
+
u_vanishing_point: [0, 0]
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
program.uniform('Matrix4fv', 'u_projection', this.projection_matrix);
|
|
274
|
+
program.uniform('3fv', 'u_eye', [0, 0, this.viewport_height ?? 0]);
|
|
275
|
+
// program.uniform('3f', 'u_eye', this.viewport_height * this.axis.x, this.viewport_height * this.axis.y, this.viewport_height);
|
|
276
|
+
program.uniform('2fv', 'u_vanishing_point', [0, 0]);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Flat projection (e.g. just top-down, no perspective) - a degenerate isometric camera
|
|
283
|
+
export class FlatCamera extends IsometricCamera {
|
|
284
|
+
|
|
285
|
+
constructor(name: string, view: CameraView, options: CameraConfiguration = {}) {
|
|
286
|
+
super(name, view, options);
|
|
287
|
+
this.type = 'flat';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
update(): void {
|
|
291
|
+
// Axis is fixed to (0, 0) for flat camera
|
|
292
|
+
this.axis.x = 0;
|
|
293
|
+
this.axis.y = 0;
|
|
294
|
+
|
|
295
|
+
super.update();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
}
|