@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.
Files changed (321) hide show
  1. package/README.md +60 -2
  2. package/dist/core.js +36923 -0
  3. package/dist/core.js.map +6 -0
  4. package/dist/index.js +32 -0
  5. package/dist/loaders-gl-worker.js +82 -0
  6. package/dist/loaders-gl-worker.js.map +6 -0
  7. package/dist/map-logic.js +2 -0
  8. package/dist/map-logic.js.map +6 -0
  9. package/dist/projected-basemaps-worker.js +3 -0
  10. package/dist/projected-basemaps-worker.js.map +6 -0
  11. package/dist/shader-modules.js +141 -0
  12. package/dist/shader-modules.js.map +6 -0
  13. package/dist/style-schema.js +126 -0
  14. package/dist/style-schema.js.map +6 -0
  15. package/dist/tangram-style.schema.json +882 -0
  16. package/dist/tangram.debug.mjs +37139 -0
  17. package/dist/tangram.debug.mjs.map +6 -0
  18. package/dist/tangram.min.mjs +4950 -0
  19. package/dist/types/builders/earcut.d.ts +15 -0
  20. package/dist/types/core.d.ts +29 -0
  21. package/dist/types/gl/constants.d.ts +2 -0
  22. package/dist/types/gl/globe_mesh.d.ts +48 -0
  23. package/dist/types/gl/glsl.d.ts +17 -0
  24. package/dist/types/gl/vertex-constants.d.ts +4 -0
  25. package/dist/types/gl/vertex-types.d.ts +34 -0
  26. package/dist/types/gl/vertex_data.d.ts +32 -0
  27. package/dist/types/gl/vertex_elements.d.ts +8 -0
  28. package/dist/types/gl/vertex_layout.d.ts +32 -0
  29. package/dist/types/glsl.d.ts +14 -0
  30. package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
  31. package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
  32. package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
  33. package/dist/types/index.d.ts +155 -0
  34. package/dist/types/lights/light-definitions.d.ts +121 -0
  35. package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
  36. package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
  37. package/dist/types/map-logic/globe-terrain.d.ts +54 -0
  38. package/dist/types/map-logic/index.d.ts +33 -0
  39. package/dist/types/map-logic/label-identity.d.ts +22 -0
  40. package/dist/types/map-logic/label-placement.d.ts +32 -0
  41. package/dist/types/map-logic/screen-bounds.d.ts +15 -0
  42. package/dist/types/map-logic/terrain-data.d.ts +55 -0
  43. package/dist/types/map-logic/terrain-error.d.ts +51 -0
  44. package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
  45. package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
  46. package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
  47. package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
  48. package/dist/types/map-logic/terrain-query.d.ts +33 -0
  49. package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
  50. package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
  51. package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
  52. package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
  53. package/dist/types/map-logic/tile-residency.d.ts +17 -0
  54. package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
  55. package/dist/types/procedures/js-yaml.d.ts +15 -0
  56. package/dist/types/procedures/mesh-projector.d.ts +107 -0
  57. package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
  58. package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
  59. package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
  60. package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
  61. package/dist/types/procedures/terrain-draping.d.ts +165 -0
  62. package/dist/types/procedures/terrain-globe.d.ts +6 -0
  63. package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
  64. package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
  65. package/dist/types/procedures/web-mercator-math.d.ts +4 -0
  66. package/dist/types/scene/globals.d.ts +11 -0
  67. package/dist/types/scene/ground_footprint.d.ts +16 -0
  68. package/dist/types/scene/host_frame.d.ts +54 -0
  69. package/dist/types/scene/projection_math.d.ts +52 -0
  70. package/dist/types/scene/scene-resource-types.d.ts +177 -0
  71. package/dist/types/scene/scene_bundle.d.ts +69 -0
  72. package/dist/types/scene/visibility_adapter.d.ts +106 -0
  73. package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
  74. package/dist/types/selection/terrain_surface.d.ts +149 -0
  75. package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
  76. package/dist/types/shader-modules/height-decode.d.ts +13 -0
  77. package/dist/types/shader-modules/hillshade.d.ts +13 -0
  78. package/dist/types/shader-modules/index.d.ts +6 -0
  79. package/dist/types/shader-modules/planar.d.ts +12 -0
  80. package/dist/types/shader-modules/sphere-map.d.ts +13 -0
  81. package/dist/types/shader-modules/triplanar.d.ts +15 -0
  82. package/dist/types/sources/decoded_tile_store.d.ts +95 -0
  83. package/dist/types/sources/tile_source_metadata.d.ts +32 -0
  84. package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
  85. package/dist/types/tile/tile_id.d.ts +37 -0
  86. package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
  87. package/dist/types/types.d.ts +293 -0
  88. package/dist/types/utils/geo.d.ts +57 -0
  89. package/dist/types/utils/hash.d.ts +1 -0
  90. package/dist/types/utils/log.d.ts +27 -0
  91. package/dist/types/utils/props.d.ts +6 -0
  92. package/dist/types/utils/subscribe.d.ts +10 -0
  93. package/dist/types/utils/thread.d.ts +6 -0
  94. package/dist/types/utils/urls.d.ts +26 -0
  95. package/dist/types/utils/utils.d.ts +26 -0
  96. package/dist/types/utils/version.d.ts +2 -0
  97. package/dist/types/utils/worker-types.d.ts +68 -0
  98. package/dist/types/utils/worker_broker.d.ts +3 -0
  99. package/package.json +103 -3
  100. package/src/builders/common.ts +41 -0
  101. package/src/builders/earcut.d.ts +15 -0
  102. package/src/builders/geometry-types.ts +60 -0
  103. package/src/builders/points.ts +115 -0
  104. package/src/builders/polygons.ts +225 -0
  105. package/src/builders/polylines.ts +682 -0
  106. package/src/builders/wireframe.ts +31 -0
  107. package/src/core.d.ts +29 -0
  108. package/src/core.ts +33 -0
  109. package/src/experimental/loaders-gl-worker.ts +19 -0
  110. package/src/experimental/projected-basemaps-worker.ts +13 -0
  111. package/src/experimental/projected-mesh-cache.ts +137 -0
  112. package/src/experimental/projected-mesh-domain.ts +110 -0
  113. package/src/experimental/projected-mesh-refinement.ts +193 -0
  114. package/src/experimental/projected-mesh.ts +399 -0
  115. package/src/experimental/projected-normal.ts +26 -0
  116. package/src/experimental/projected-symbol-mesh.ts +74 -0
  117. package/src/gl/constants.ts +18 -0
  118. package/src/gl/context.ts +103 -0
  119. package/src/gl/extensions.ts +30 -0
  120. package/src/gl/globe_mesh.ts +285 -0
  121. package/src/gl/glsl.ts +257 -0
  122. package/src/gl/mesh-types.ts +56 -0
  123. package/src/gl/render_state.ts +111 -0
  124. package/src/gl/shader_program.ts +1310 -0
  125. package/src/gl/texture.ts +814 -0
  126. package/src/gl/uniform_buffer.ts +340 -0
  127. package/src/gl/vao.ts +154 -0
  128. package/src/gl/vbo_mesh.ts +349 -0
  129. package/src/gl/vertex-constants.ts +19 -0
  130. package/src/gl/vertex-types.ts +35 -0
  131. package/src/gl/vertex_data.ts +126 -0
  132. package/src/gl/vertex_elements.ts +54 -0
  133. package/src/gl/vertex_layout.ts +241 -0
  134. package/src/glsl.d.ts +14 -0
  135. package/src/gpu/luma_device_renderer.ts +579 -0
  136. package/src/gpu/pixel_readback.ts +51 -0
  137. package/src/gpu/resource_lifecycle.ts +32 -0
  138. package/src/gpu/tangram_gpu_backend.ts +195 -0
  139. package/src/index.d.ts +155 -0
  140. package/src/index.ts +105 -0
  141. package/src/labels/collision-types.ts +94 -0
  142. package/src/labels/collision.ts +273 -0
  143. package/src/labels/collision_grid.ts +61 -0
  144. package/src/labels/intersect.ts +30 -0
  145. package/src/labels/label-types.ts +91 -0
  146. package/src/labels/label.ts +207 -0
  147. package/src/labels/label_line.ts +801 -0
  148. package/src/labels/label_point.ts +131 -0
  149. package/src/labels/main-pass-types.ts +73 -0
  150. package/src/labels/main_pass.ts +221 -0
  151. package/src/labels/point_anchor.ts +100 -0
  152. package/src/labels/point_placement.ts +150 -0
  153. package/src/labels/projected-pass.ts +259 -0
  154. package/src/labels/repeat_group.ts +72 -0
  155. package/src/lights/ambient_light.glsl +18 -0
  156. package/src/lights/directional_light.glsl +39 -0
  157. package/src/lights/geographic-lights.ts +21 -0
  158. package/src/lights/light-definitions.ts +285 -0
  159. package/src/lights/light.ts +590 -0
  160. package/src/lights/lighting-uniforms.ts +80 -0
  161. package/src/lights/lighting-wgsl.ts +116 -0
  162. package/src/lights/material.glsl +237 -0
  163. package/src/lights/material.ts +145 -0
  164. package/src/lights/native-falloff.ts +37 -0
  165. package/src/lights/point_light.glsl +96 -0
  166. package/src/lights/spot_light.glsl +111 -0
  167. package/src/map-logic/draped-terrain-bounds.ts +71 -0
  168. package/src/map-logic/globe-terrain-preparation.ts +217 -0
  169. package/src/map-logic/globe-terrain.ts +296 -0
  170. package/src/map-logic/index.ts +37 -0
  171. package/src/map-logic/label-identity.ts +36 -0
  172. package/src/map-logic/label-placement.ts +117 -0
  173. package/src/map-logic/screen-bounds.ts +34 -0
  174. package/src/map-logic/terrain-data.ts +107 -0
  175. package/src/map-logic/terrain-error.ts +134 -0
  176. package/src/map-logic/terrain-ground-topology.ts +164 -0
  177. package/src/map-logic/terrain-height-field.ts +254 -0
  178. package/src/map-logic/terrain-navigation.ts +50 -0
  179. package/src/map-logic/terrain-neighborhood.ts +43 -0
  180. package/src/map-logic/terrain-query.ts +51 -0
  181. package/src/map-logic/terrain-resource-budget.ts +124 -0
  182. package/src/map-logic/terrain-transition-topology.ts +164 -0
  183. package/src/map-logic/tile-build-queue.ts +110 -0
  184. package/src/map-logic/tile-cache-policy.ts +89 -0
  185. package/src/map-logic/tile-residency.ts +50 -0
  186. package/src/map-logic/triangle-query-index.ts +265 -0
  187. package/src/procedures/js-yaml.d.ts +15 -0
  188. package/src/procedures/mesh-projector.ts +191 -0
  189. package/src/procedures/mlt-loaders.ts +55 -0
  190. package/src/procedures/mvt-dependencies.d.ts +20 -0
  191. package/src/procedures/mvt-legacy.ts +150 -0
  192. package/src/procedures/mvt-loaders.ts +53 -0
  193. package/src/procedures/mvt-parser.ts +48 -0
  194. package/src/procedures/mvt-properties.ts +36 -0
  195. package/src/procedures/mvt-tile-provider.ts +72 -0
  196. package/src/procedures/pmtiles-loader.ts +75 -0
  197. package/src/procedures/projected-coordinate-transform.ts +79 -0
  198. package/src/procedures/projection-batch-executor.ts +140 -0
  199. package/src/procedures/scene-yaml-legacy.ts +16 -0
  200. package/src/procedures/scene-yaml-loaders.ts +10 -0
  201. package/src/procedures/terrain-draping.ts +713 -0
  202. package/src/procedures/terrain-globe.ts +32 -0
  203. package/src/procedures/terrain-styled-topology.ts +184 -0
  204. package/src/procedures/web-mercator-legacy.ts +24 -0
  205. package/src/procedures/web-mercator-math.ts +50 -0
  206. package/src/scene/camera.ts +298 -0
  207. package/src/scene/camera_base.ts +116 -0
  208. package/src/scene/classic_scene.ts +20 -0
  209. package/src/scene/external_camera.ts +101 -0
  210. package/src/scene/globals.ts +120 -0
  211. package/src/scene/ground_footprint.ts +139 -0
  212. package/src/scene/host_frame.ts +326 -0
  213. package/src/scene/projected_tile_lod.ts +125 -0
  214. package/src/scene/projection_math.ts +142 -0
  215. package/src/scene/projection_shaders.ts +183 -0
  216. package/src/scene/renderer.ts +516 -0
  217. package/src/scene/scene-resource-types.ts +174 -0
  218. package/src/scene/scene.ts +2378 -0
  219. package/src/scene/scene_bundle.ts +304 -0
  220. package/src/scene/scene_debug.ts +129 -0
  221. package/src/scene/scene_loader.ts +402 -0
  222. package/src/scene/scene_worker.ts +528 -0
  223. package/src/scene/view.ts +683 -0
  224. package/src/scene/visibility_adapter.ts +319 -0
  225. package/src/scene/worker_url.ts +15 -0
  226. package/src/selection/draped-terrain-surface.ts +377 -0
  227. package/src/selection/selection-types.ts +79 -0
  228. package/src/selection/selection.ts +542 -0
  229. package/src/selection/selection_fragment.glsl +18 -0
  230. package/src/selection/selection_globals.glsl +9 -0
  231. package/src/selection/selection_pixels.ts +59 -0
  232. package/src/selection/selection_vertex.glsl +15 -0
  233. package/src/selection/terrain_surface.ts +298 -0
  234. package/src/shader-modules/globe-horizon.ts +52 -0
  235. package/src/shader-modules/height-decode.ts +41 -0
  236. package/src/shader-modules/hillshade.ts +70 -0
  237. package/src/shader-modules/index.ts +10 -0
  238. package/src/shader-modules/planar.ts +24 -0
  239. package/src/shader-modules/sphere-map.ts +38 -0
  240. package/src/shader-modules/triplanar.ts +45 -0
  241. package/src/sources/aligned_tile_source.ts +122 -0
  242. package/src/sources/data_source.ts +655 -0
  243. package/src/sources/decoded_tile_store.ts +245 -0
  244. package/src/sources/geojson.ts +300 -0
  245. package/src/sources/mvt.ts +247 -0
  246. package/src/sources/raster.ts +341 -0
  247. package/src/sources/shared_tile_source_adapter.ts +117 -0
  248. package/src/sources/sources.ts +10 -0
  249. package/src/sources/tile_source_adapter.ts +84 -0
  250. package/src/sources/tile_source_metadata.ts +65 -0
  251. package/src/sources/tile_source_state.ts +112 -0
  252. package/src/sources/topojson.ts +90 -0
  253. package/src/styles/feature_annotations.ts +16 -0
  254. package/src/styles/filter.ts +190 -0
  255. package/src/styles/geometry-style-types.ts +209 -0
  256. package/src/styles/globe_visibility_wgsl.ts +22 -0
  257. package/src/styles/layer-types.ts +45 -0
  258. package/src/styles/layer.ts +586 -0
  259. package/src/styles/lines/dasharray.ts +49 -0
  260. package/src/styles/lines/lines.ts +707 -0
  261. package/src/styles/lines/lines_wgsl.ts +224 -0
  262. package/src/styles/points/point-runtime-types.ts +125 -0
  263. package/src/styles/points/points.ts +1079 -0
  264. package/src/styles/points/points_fragment.glsl +126 -0
  265. package/src/styles/points/points_vertex.glsl +230 -0
  266. package/src/styles/points/points_wgsl.ts +189 -0
  267. package/src/styles/polygons/polygons.ts +268 -0
  268. package/src/styles/polygons/polygons_fragment.glsl +173 -0
  269. package/src/styles/polygons/polygons_vertex.glsl +213 -0
  270. package/src/styles/polygons/polygons_wgsl.ts +127 -0
  271. package/src/styles/property-types.ts +125 -0
  272. package/src/styles/raster/raster.ts +33 -0
  273. package/src/styles/raster/raster_globals.glsl +46 -0
  274. package/src/styles/style-mixing-types.ts +101 -0
  275. package/src/styles/style-runtime-types.ts +220 -0
  276. package/src/styles/style-schema.ts +133 -0
  277. package/src/styles/style.ts +1033 -0
  278. package/src/styles/style_globals.glsl +69 -0
  279. package/src/styles/style_manager.ts +464 -0
  280. package/src/styles/style_parser.ts +620 -0
  281. package/src/styles/text/font_manager.ts +180 -0
  282. package/src/styles/text/fontfaceobserver.d.ts +10 -0
  283. package/src/styles/text/text-types.ts +220 -0
  284. package/src/styles/text/text.ts +378 -0
  285. package/src/styles/text/text_canvas.ts +773 -0
  286. package/src/styles/text/text_labels.ts +436 -0
  287. package/src/styles/text/text_segments.ts +142 -0
  288. package/src/styles/text/text_settings.ts +228 -0
  289. package/src/styles/text/text_wgsl.ts +159 -0
  290. package/src/styles/text/text_wrap.ts +194 -0
  291. package/src/tile/globe_tile_preload.ts +40 -0
  292. package/src/tile/tangram_tileset_2d.ts +108 -0
  293. package/src/tile/tile-build-types.ts +155 -0
  294. package/src/tile/tile.ts +807 -0
  295. package/src/tile/tile_id.ts +172 -0
  296. package/src/tile/tile_manager.ts +889 -0
  297. package/src/tile/tile_pyramid.ts +119 -0
  298. package/src/tile/tile_resource_cache.ts +91 -0
  299. package/src/tile/tile_traversal_adapter.ts +132 -0
  300. package/src/types.ts +332 -0
  301. package/src/utils/debounce.ts +17 -0
  302. package/src/utils/debug_settings.ts +43 -0
  303. package/src/utils/errors.ts +12 -0
  304. package/src/utils/functions.ts +95 -0
  305. package/src/utils/geo.ts +364 -0
  306. package/src/utils/hash.ts +14 -0
  307. package/src/utils/log.ts +108 -0
  308. package/src/utils/media_capture.ts +193 -0
  309. package/src/utils/merge.ts +44 -0
  310. package/src/utils/obb.ts +134 -0
  311. package/src/utils/props.ts +43 -0
  312. package/src/utils/slice.ts +14 -0
  313. package/src/utils/subscribe.ts +58 -0
  314. package/src/utils/task.ts +154 -0
  315. package/src/utils/thread.ts +26 -0
  316. package/src/utils/urls.ts +223 -0
  317. package/src/utils/utils.ts +310 -0
  318. package/src/utils/vector.ts +197 -0
  319. package/src/utils/version.ts +8 -0
  320. package/src/utils/worker-types.ts +79 -0
  321. 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
+ }