@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/README.md +59 -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,889 @@
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 Tile from './tile';
7
+ import {TileID} from './tile_id';
8
+ import TilePyramid from './tile_pyramid';
9
+ import Geo from '../utils/geo';
10
+ import mainThreadLabelCollisionPass from '../labels/main_pass';
11
+ import {layoutProjectedAnnotations} from '../labels/projected-pass';
12
+ import {hasTerrainTileCoverage} from '../procedures/terrain-draping';
13
+ import type {DrapedTerrainQueryEntry} from '../selection/draped-terrain-surface';
14
+ import type HostFrame from '../scene/host_frame';
15
+ import log from '../utils/log';
16
+ import WorkerBroker from '../utils/worker_broker';
17
+ import Task from '../utils/task';
18
+ import {getGlobePreloadKey, getGlobeFallbackClipBounds, isGlobeFallbackStyle} from './globe_tile_preload';
19
+ import TangramTileset2D from './tangram_tileset_2d';
20
+ import type {ResourceTile} from './tile_resource_cache';
21
+ import type {TileCoordinate} from './tile_id';
22
+ import type {HostTileResourceOptions, TileResourceStatistics} from '../types';
23
+ import type DataSource from '../sources/data_source';
24
+ import type {TileBuildStyle, TileBuildProgress, TileStylePayload, TileDebug, WorkerBuildTile} from './tile-build-types';
25
+ import type {BrokerWorker} from '../utils/worker-types';
26
+ import type {TaskRecord} from '../utils/task';
27
+ import type {TerrainDrapingSnapshot} from '../procedures/terrain-draping';
28
+ import type VBOMesh from '../gl/vbo_mesh';
29
+ import type {TerrainResourceBudget, TerrainResourceLease} from '../map-logic/terrain-resource-budget';
30
+
31
+ /** Manager-facing camera operations; no classic camera implementation is required. */
32
+ interface TileManagerView {
33
+ center: {meters?: {x: number; y: number} | null; tile?: {z: number} | null} | null;
34
+ tile_zoom: number | undefined;
35
+ zoom: number | null;
36
+ zoom_direction: number;
37
+ camera_mode: string;
38
+ projection: {type: string};
39
+ findVisibleTileCoordinates(): TileCoordinate[];
40
+ findPreloadedTileCoordinates?(): TileCoordinate[];
41
+ pruneTilesForView(): void;
42
+ }
43
+ /** Scene services consumed by tile residency, upload, and collision. */
44
+ interface TileManagerScene {
45
+ id: number | string;
46
+ generation: number;
47
+ view: TileManagerView;
48
+ sources: Record<string, DataSource & {builds_geometry_tiles?: boolean}>;
49
+ workers: BrokerWorker[] | null | undefined;
50
+ styles: Record<string, TileBuildStyle & {base?: string}> | null;
51
+ style_manager: {updateActiveStyles(tiles: Tile[]): void; updateActiveBlendOrders(tiles: Tile[]): void};
52
+ terrain?: TerrainDrapingSnapshot | null;
53
+ terrainResourceBudget?: TerrainResourceBudget;
54
+ terrainTransaction?: unknown;
55
+ terrainExaggeration?: number;
56
+ terrainFrameTime?: number;
57
+ building?: {initial?: boolean} | null;
58
+ requestRedraw(): void;
59
+ withWebGLContext<Result>(callback: () => Result): Result;
60
+ tileManagerBuildError?(error: unknown): void;
61
+ tileManagerBuildDone(): void;
62
+ }
63
+ /** Serialized completion metadata never claims ownership of a live Tile's methods. */
64
+ type TileBuildReply = Pick<WorkerBuildTile, 'mesh_data' | 'debug'> & {
65
+ id: number; key: string; loading: boolean; loaded: boolean; generation: number | null; error: string | null;
66
+ };
67
+ /** Main-thread allocation failures and serialized worker failures share identity only. */
68
+ type TileBuildFailure = {id: number | string; key: string; generation: number | null; error: unknown; mesh_data?: Record<string, TileStylePayload>};
69
+
70
+ export default class TileManager {
71
+ /** Main-thread array and GPU estimates are separate domains in a borrowed application pool. */
72
+ private terrainRetainedLease?: TerrainResourceLease;
73
+ private terrainGPULease?: TerrainResourceLease;
74
+ /** At most two active/recent field clocks; late tiles never restart their admitted field's morph. */
75
+ private readonly terrainTransitionOrigins = new Map<number, number>();
76
+ /** Scene and proxy ownership are released on destruction. */
77
+ declare scene: TileManagerScene | null;
78
+ declare pyramid: TilePyramid<Tile> | null;
79
+ declare queued_coords: TileCoordinate[];
80
+ declare preloaded_coords: TileCoordinate[] | undefined;
81
+ declare preload_zoom: number | undefined;
82
+ declare building_tiles: Record<string, boolean> | null;
83
+ declare renderable_tiles: Tile[];
84
+ declare main_thread_target: string;
85
+ declare collision: {tile_keys: string | null; mesh_set: string | null; zoom: string | null; zoom_steps: number; task?: TaskRecord | null};
86
+ /** Current eye-union logical coordinates. */
87
+ declare visible_coords: Record<string, TileCoordinate>;
88
+ /** Coarse source/style tiles pinned by the current globe preload policy. */
89
+ declare preloaded_keys: Set<string>;
90
+ /** Resident metadata and shared resource policy; renderer work stays in this adapter. */
91
+ declare tileset: TangramTileset2D<Tile>;
92
+
93
+ /** Temporary keyed-table compatibility for existing renderer and test adapters. */
94
+ get tiles() { return this.tileset.tileRecords; }
95
+ set tiles(records) { this.tileset.tileRecords = records; }
96
+
97
+ /** Worker reply/cancellation bridge into the shared scheduler. */
98
+ get build_queue() { return this.tileset.buildQueue; }
99
+
100
+ constructor({ scene }: {scene: TileManagerScene}) {
101
+ this.scene = scene;
102
+ this.tileset = new TangramTileset2D();
103
+ this.pyramid = new TilePyramid();
104
+ this.visible_coords = {};
105
+ this.queued_coords = [];
106
+ this.preloaded_keys = new Set();
107
+ this.preload_zoom = undefined;
108
+ this.building_tiles = null;
109
+ this.renderable_tiles = [];
110
+ this.collision = {
111
+ tile_keys: null,
112
+ mesh_set: null,
113
+ zoom: null,
114
+ zoom_steps: 3 // divisions per zoom at which labels are re-collided (e.g. 0, 0.33, 0.66)
115
+ };
116
+
117
+ // Provide a hook for this object to be called from worker threads
118
+ this.main_thread_target = ['TileManager', this.activeScene.id].join('_');
119
+ WorkerBroker.addTarget(this.main_thread_target, this);
120
+ }
121
+
122
+ destroy() {
123
+ this.terrainTransitionOrigins.clear();
124
+ this.tileset.finalize(tile => tile.destroy());
125
+ this.terrainRetainedLease?.release(); this.terrainGPULease?.release();
126
+ this.pyramid = null;
127
+ this.visible_coords = {};
128
+ this.queued_coords = [];
129
+ this.preloaded_keys.clear();
130
+ this.scene = null;
131
+ WorkerBroker.removeTarget(this.main_thread_target);
132
+ }
133
+
134
+ get view () {
135
+ return this.activeScene.view;
136
+ }
137
+
138
+ get style_manager () {
139
+ return this.activeScene.style_manager;
140
+ }
141
+
142
+ /** Calls after teardown cannot use released scene services. */
143
+ private get activeScene(): TileManagerScene {
144
+ if (!this.scene) throw new Error('TileManager has been destroyed');
145
+ return this.scene;
146
+ }
147
+
148
+ keepTile(tile: Tile) {
149
+ const pyramid = this.pyramid;
150
+ if (!pyramid) throw new Error('TileManager has been destroyed');
151
+ this.tileset.setTile(tile);
152
+ pyramid.addTile(tile);
153
+ }
154
+
155
+ hasTile(key: string) {
156
+ return this.tileset.getTile(key) !== undefined;
157
+ }
158
+
159
+ forgetTile(key: string) {
160
+ this.tileset.forgetTile(key, tile => this.pyramid?.removeTile(tile));
161
+ this.tileBuildStop(key);
162
+ }
163
+
164
+ // Remove a single tile
165
+ removeTile(key: string) {
166
+ log('trace', `tile unload for ${key}`);
167
+
168
+ var tile = this.tiles[key];
169
+
170
+ if (tile != null) {
171
+ tile.destroy();
172
+ }
173
+
174
+ this.forgetTile(key);
175
+ this.syncTerrainResourceLeases();
176
+ this.activeScene.requestRedraw();
177
+ }
178
+
179
+ // Run a function on each tile
180
+ forEachTile(func: (tile: Tile) => void) {
181
+ for (let t in this.tiles) {
182
+ func(this.tiles[t]);
183
+ }
184
+ }
185
+
186
+ // Remove tiles that pass a filter condition
187
+ removeTiles(filter: (tile: Tile) => boolean) {
188
+ let remove_tiles = [];
189
+ for (let t in this.tiles) {
190
+ let tile = this.tiles[t];
191
+ if (filter(tile)) {
192
+ remove_tiles.push(t);
193
+ }
194
+ }
195
+ for (let r=0; r < remove_tiles.length; r++) {
196
+ let key = remove_tiles[r];
197
+ this.removeTile(key);
198
+ }
199
+ }
200
+
201
+ updateTilesForView() {
202
+ const styleZoom = this.view.tile_zoom;
203
+ if (styleZoom === undefined) return;
204
+ this.build_queue.suspend();
205
+ try {
206
+ // Find visible tiles and load new ones
207
+ this.visible_coords = {};
208
+ let tile_coords = this.view.findVisibleTileCoordinates().filter(coords => !this.activeScene.terrain || hasTerrainTileCoverage(this.activeScene.terrain.grids, coords));
209
+ for (let c=0; c < tile_coords.length; c++) {
210
+ const coords = tile_coords[c];
211
+ this.queueCoordinate(coords);
212
+ this.visible_coords[coords.key] = coords;
213
+ }
214
+
215
+ this.preloaded_keys.clear();
216
+ const preloaded = (this.view.findPreloadedTileCoordinates?.() || []).filter(coords => !this.activeScene.terrain || hasTerrainTileCoverage(this.activeScene.terrain.grids, coords));
217
+ this.preloaded_coords = preloaded;
218
+ this.preload_zoom = preloaded[0]?.z;
219
+ for (const coords of preloaded) {
220
+ for (const source of Object.values(this.activeScene.sources || {})) {
221
+ if (!source.builds_geometry_tiles || !source.includesTile(coords, styleZoom)) continue;
222
+ const key = getGlobePreloadKey(coords, source, styleZoom, coords.z);
223
+ if (key) this.preloaded_keys.add(key);
224
+ }
225
+ }
226
+
227
+ // Prioritize visible detail before the bounded background preload batch.
228
+ this.loadQueuedCoordinates();
229
+ this.updateTileStates();
230
+ } finally {
231
+ this.build_queue.resume();
232
+ }
233
+ }
234
+
235
+ /** Keep only the current source/style generation's global coarse tiles resident. */
236
+ isTilePreloaded(key: string) {
237
+ return this.preloaded_keys.has(key);
238
+ }
239
+
240
+ updateTileStates () {
241
+ this.build_queue.suspend();
242
+ try {
243
+ this.forEachTile(tile => {
244
+ this.updateVisibility(tile);
245
+ this.build_queue.setPriority(tile.key, this.getBuildPriority(tile));
246
+ });
247
+
248
+ this.loadQueuedCoordinates();
249
+ this.updateProxyTiles();
250
+ for (const coords of this.preloaded_coords || []) this.loadCoordinate(coords, true);
251
+ this.updateGlobeFallbackTiles();
252
+ this.view.pruneTilesForView();
253
+ this.enforceCacheLimits();
254
+ this.updateRenderableTiles();
255
+ this.style_manager.updateActiveStyles(this.renderable_tiles);
256
+ this.style_manager.updateActiveBlendOrders(this.renderable_tiles);
257
+ return this.updateLabels();
258
+ } finally {
259
+ this.build_queue.resume();
260
+ }
261
+ }
262
+
263
+ /** Configure shared limits without starting old queued work before the new eye union is installed. */
264
+ setResourceLimits(options: Readonly<HostTileResourceOptions> | undefined): void {
265
+ this.tileset.setOptions(options);
266
+ }
267
+
268
+ /** Visible detail precedes global fallback, which precedes retained off-screen builds. */
269
+ getBuildPriority(tile: Pick<Tile, 'visible' | 'key'>): number {
270
+ return tile.visible ? 0 : this.isTilePreloaded(tile.key) ? 1 : 2;
271
+ }
272
+
273
+ /** Evict only completed unneeded tiles; visible/proxy/preload residency is reported separately. */
274
+ enforceCacheLimits(): void {
275
+ for (const key of this.tileset.getEvictionKeys(key => this.isTilePreloaded(key))) this.removeTile(key);
276
+ }
277
+
278
+ /** Detached worker and mesh residency diagnostics, suitable for host UI or tests. */
279
+ getResourceStatistics(): TileResourceStatistics {
280
+ return this.tileset.getStatistics(key => this.isTilePreloaded(key));
281
+ }
282
+
283
+ /** Layout annotations before drawing and refresh proxy/style ownership after label swaps. */
284
+ updateProjectedLabels(frame: HostFrame, clockwiseRotation: boolean, terrainSurface?: import('../selection/terrain_surface').TerrainSurfaceCollection): void {
285
+ if (this.activeScene.terrain) {
286
+ const time = this.activeScene.terrainFrameTime ?? performance.now();
287
+ for (const tile of this.renderable_tiles) for (const meshes of [tile.meshes, tile.pending_label_meshes ?? {}]) for (const parts of Object.values(meshes)) for (const mesh of parts) {
288
+ if (mesh.variant?.terrain) this.getTerrainTransitionOrigin(mesh, time);
289
+ }
290
+ }
291
+ const tiles = this.renderable_tiles.filter(tile => tile.valid && tile.built && !tile.fallback_for);
292
+ const pending = tiles.some(tile => tile.pending_label_meshes !== null);
293
+ const layoutOptions = {clockwiseRotation, terrainSurface, terrainExaggeration: this.activeScene.terrainExaggeration ?? 1,
294
+ terrainTime: this.activeScene.terrainFrameTime,
295
+ terrainTransitionDuration: this.activeScene.terrain?.transitionDuration ?? 0};
296
+ if (layoutProjectedAnnotations(tiles, frame, layoutOptions)) {
297
+ if (pending) {
298
+ // Swapped child styles can retire proxies. Relayout before this same frame draws,
299
+ // otherwise an invisible older proxy keeps suppressing the child's annotations.
300
+ this.updateTileStates();
301
+ layoutProjectedAnnotations(this.renderable_tiles.filter(tile => tile.valid && tile.built && !tile.fallback_for),
302
+ frame, layoutOptions);
303
+ }
304
+ this.style_manager.updateActiveStyles(this.renderable_tiles);
305
+ this.style_manager.updateActiveBlendOrders(this.renderable_tiles);
306
+ this.activeScene.requestRedraw();
307
+ }
308
+ }
309
+
310
+ /** Summarize resident worker output, separate from source caches, GPU and transient allocations. */
311
+ getTerrainDrapingStatistics(terrain?: import('../procedures/terrain-draping').TerrainDrapingSnapshot): import('../procedures/terrain-draping').TerrainDrapingStatistics {
312
+ const result = {enabled: Boolean(terrain), gridBytes: terrain?.byteLength ?? 0, meshes: 0, vertices: 0, triangles: 0, suppressedTriangles: 0, meshBytes: 0, maximumPreparationMs: 0, detailLimitedMeshes: 0, queryBytes: 0, stagedMeshBytes: this.getStagedTerrainBytes(),
313
+ residentMeshBytes: this.getResidentTerrainMeshBytes(), residentMeshByteLimit: terrain?.maxResidentMeshBytes ?? 0, ribbonMeshes: 0};
314
+ for (const tile of Object.values(this.tiles)) for (const parts of Object.values(tile.meshes || {})) for (const mesh of parts) {
315
+ const stats = mesh.variant?.terrainStatistics;
316
+ if (!mesh.valid || !stats) continue;
317
+ result.meshes++; result.vertices += stats.vertices; result.triangles += stats.triangles;
318
+ if (stats.ribbonZoom !== undefined) result.ribbonMeshes++;
319
+ result.suppressedTriangles += stats.suppressedTriangles; result.meshBytes += stats.meshBytes;
320
+ result.maximumPreparationMs = Math.max(result.maximumPreparationMs, stats.preparationMs);
321
+ result.detailLimitedMeshes += Number(stats.detailLimited);
322
+ const query = mesh.terrainQueryMesh;
323
+ if (query) result.queryBytes += query.positions.byteLength + query.heights.byteLength + query.indices.byteLength + (query.globeBasis?.byteLength ?? 0);
324
+ }
325
+ return result;
326
+ }
327
+
328
+ /** Additional staged draw/query typed arrays; does not estimate textures or driver/GPU overhead. */
329
+ getStagedTerrainBytes(): number {
330
+ let bytes = 0;
331
+ for (const tile of Object.values(this.tiles)) if (tile.terrainStage) for (const meshes of [tile.terrainStage.meshes, tile.terrainStage.labels ?? {}]) for (const parts of Object.values(meshes)) for (const mesh of parts) {
332
+ const query = mesh.terrainQueryMesh;
333
+ bytes += mesh.buffer_size + (query ? query.positions.byteLength + query.heights.byteLength + query.indices.byteLength + (query.globeBasis?.byteLength ?? 0) : 0);
334
+ }
335
+ return bytes;
336
+ }
337
+
338
+ /** Count each live mesh owner once, including pending labels and replacement stages before retiring the incumbent. */
339
+ getResidentTerrainMeshBytes(): number {
340
+ const memory = this.getResidentTerrainMeshMemory();
341
+ return memory.retained + memory.gpu;
342
+ }
343
+
344
+ /** Estimate GPU buffer copies independently from actual retained CPU input arrays. */
345
+ private getResidentTerrainMeshMemory(): {retained: number; gpu: number} {
346
+ const visited = new Set<VBOMesh>();
347
+ let retained = 0, gpu = 0;
348
+ for (const tile of Object.values(this.tiles)) {
349
+ for (const meshes of [tile.meshes ?? {}, tile.pending_label_meshes ?? {}, tile.terrainStage?.meshes ?? {}, tile.terrainStage?.labels ?? {}]) {
350
+ for (const parts of Object.values(meshes)) for (const mesh of parts) {
351
+ if (!mesh.valid || !mesh.variant?.terrain || visited.has(mesh)) continue;
352
+ visited.add(mesh);
353
+ const query = mesh.terrainQueryMesh;
354
+ // Unretained meshes release their CPU inputs after upload;
355
+ // keep those distinct from the still-resident GPU estimate.
356
+ retained += (mesh.vertex_data?.byteLength ?? 0) + (mesh.element_data ? mesh.element_data.byteLength : 0) +
357
+ (query ? query.positions.byteLength + query.heights.byteLength + query.indices.byteLength + (query.globeBasis?.byteLength ?? 0) : 0);
358
+ gpu += mesh.buffer_size;
359
+ }
360
+ }
361
+ }
362
+ return {retained, gpu};
363
+ }
364
+
365
+ /** Reconcile retired mesh owners before reserving replacement storage. */
366
+ private syncTerrainResourceLeases(): void {
367
+ const budget = this.activeScene.terrainResourceBudget;
368
+ if (!budget) return;
369
+ this.terrainRetainedLease ??= budget.reserve('retained', 0) ?? undefined;
370
+ this.terrainGPULease ??= budget.reserve('gpu', 0) ?? undefined;
371
+ const memory = this.getResidentTerrainMeshMemory();
372
+ if (!this.terrainRetainedLease?.resize(memory.retained) || !this.terrainGPULease?.resize(memory.gpu)) throw new Error('Terrain shared mesh resource budget exceeded');
373
+ }
374
+
375
+ /** Refresh draw membership after a commit without starting another worker batch inside queue settlement. */
376
+ publishTerrainDisplay(): void {
377
+ this.syncTerrainResourceLeases();
378
+ this.updateProxyTiles(); this.updateGlobeFallbackTiles(); this.updateRenderableTiles();
379
+ this.style_manager.updateActiveStyles(this.renderable_tiles);
380
+ this.style_manager.updateActiveBlendOrders(this.renderable_tiles);
381
+ }
382
+
383
+ /** Publish one atomic field transition origin only after staged geometry succeeds. */
384
+ startTerrainTransition(generation: number, time: number): void {
385
+ if (this.terrainTransitionOrigins.has(generation)) return;
386
+ this.terrainTransitionOrigins.set(generation, time);
387
+ while (this.terrainTransitionOrigins.size > 2) this.terrainTransitionOrigins.delete(this.terrainTransitionOrigins.keys().next().value!);
388
+ }
389
+
390
+ /** Reuse the field clock for query layout, both eyes and late ordinary tile/detail arrivals. */
391
+ getTerrainTransitionOrigin(mesh: Pick<VBOMesh, 'variant' | 'terrainVisibleAt'>, time: number): number {
392
+ const generation = mesh.variant.terrainGeneration;
393
+ if (generation === undefined) return mesh.terrainVisibleAt ??= time;
394
+ let origin = this.terrainTransitionOrigins.get(generation);
395
+ if (origin === undefined) {
396
+ // Retained old meshes must not reinsert an evicted epoch and displace
397
+ // the current field clock used by late detail arrivals.
398
+ if (mesh.terrainVisibleAt !== undefined) return mesh.terrainVisibleAt;
399
+ origin = time;
400
+ this.startTerrainTransition(generation, origin);
401
+ }
402
+ return mesh.terrainVisibleAt = origin;
403
+ }
404
+
405
+ /** Ground query membership and fallback clipping follow the same admitted owners as the draw pass. */
406
+ getDrapedTerrainQueryEntries(time: number): DrapedTerrainQueryEntry[] {
407
+ const entries: DrapedTerrainQueryEntry[] = [];
408
+ for (const tile of this.renderable_tiles) for (const [style, parts] of Object.entries(tile.meshes)) {
409
+ if (!tile.shouldProxyForStyle(style)) continue;
410
+ for (const mesh of parts) {
411
+ if (!mesh.valid || !mesh.terrainQueryMesh) continue;
412
+ const duration = mesh.variant.terrainTransitionDuration ?? 0;
413
+ // Layout and draw must start newly visible ground on the same clock.
414
+ this.getTerrainTransitionOrigin(mesh, time);
415
+ const blend = mesh.terrainVisibleAt === undefined || !duration ? 1 : Math.max(0, Math.min(1, (time - mesh.terrainVisibleAt) / duration));
416
+ const scale = tile.span.x / 4096;
417
+ const clipBounds = tile.fallback_for ? Array.from(tile.fallback_for.values()) : mesh.variant.terrainGroundClip ? [[0, -4096, 4096, 0]] : undefined;
418
+ const clips = clipBounds?.map((bounds): [number, number, number, number] => [tile.min.x + bounds[0] * scale, tile.min.y + bounds[1] * scale, tile.min.x + bounds[2] * scale, tile.min.y + bounds[3] * scale]);
419
+ entries.push({id: `${tile.key}/${style}/${mesh.id}`, mesh: mesh.terrainQueryMesh, blend, clips, halfOpenClips: mesh.variant.terrainGroundClip === true});
420
+ }
421
+ }
422
+ return entries;
423
+ }
424
+
425
+ updateLabels () {
426
+ const viewZoom = this.view.zoom;
427
+ if (viewZoom === null) return Promise.resolve({});
428
+ // Projected candidates need actual screen cameras, not Mercator zoom/boxes.
429
+ // Renderer lays them out synchronously across the complete HostFrame before drawing.
430
+ if (this.view.projection.type === 'projected' || this.view.camera_mode === 'external') return Promise.resolve({});
431
+ if (this.activeScene.building && !this.activeScene.building.initial) {
432
+ // log('debug', `Skip label layout due to on-going scene rebuild`);
433
+ return Promise.resolve({});
434
+ }
435
+
436
+ // get current visible tiles and sort by key for consistency collision order
437
+ const tiles = this.renderable_tiles
438
+ .filter(t => !t.fallback_for)
439
+ .filter(t => t.valid)
440
+ .filter(t => t.built);
441
+
442
+ if (tiles.length === 0) {
443
+ return Promise.resolve({});
444
+ }
445
+
446
+ // Evaluate labels in order of tile build, to prevent previously visible labels
447
+ // from disappearing, e.g. due to a newly loaded repeat label nearby
448
+ tiles.sort((a, b) => a.build_id < b.build_id ? -1 : (a.build_id > b.build_id ? 1 : 0));
449
+
450
+ // check if tile set has changed (in ways that affect collision)
451
+ // if not, bail so that the existing collision task can carry on
452
+ // if so, carry on and start a new collision task
453
+ if (// 1st: check if same zoom level (rounded to a configurable precision)
454
+ this.collision.zoom === roundPrecision(viewZoom, this.collision.zoom_steps) &&
455
+ // 2nd: check if same set of tiles
456
+ this.collision.tile_keys === JSON.stringify(tiles.map(t => t.key)) &&
457
+ // 3rd: check if same set of meshes
458
+ this.collision.mesh_set === meshSetString(tiles)) {
459
+ // log('debug', `Skip label layout due to same tile/meshes (zoom ${this.view.zoom.toFixed(2)}, tiles ${this.collision.tile_keys})`);
460
+ return Promise.resolve({});
461
+ }
462
+
463
+ // update collision if not already updating
464
+ if (!this.collision.task) {
465
+ this.collision.zoom = roundPrecision(viewZoom, this.collision.zoom_steps);
466
+ this.collision.tile_keys = JSON.stringify(tiles.map(t => t.key));
467
+ this.collision.mesh_set = meshSetString(tiles);
468
+ // log('debug', `Update label collisions (zoom ${this.collision.zoom}, ${this.collision.tile_keys})`);
469
+
470
+ // make a new collision task
471
+ this.collision.task = {
472
+ type: 'tileManagerUpdateLabels',
473
+ run: async task => {
474
+ // Do collision pass, then update view
475
+ const results = await mainThreadLabelCollisionPass(tiles, Number(this.collision.zoom), this.isLoadingVisibleTiles());
476
+ this.activeScene.requestRedraw();
477
+
478
+ // Clear state to allow another collision pass to start
479
+ this.collision.task = null;
480
+ Task.finish(task, results);
481
+
482
+ // Check if tiles changed during previous collision pass - will start new pass if so
483
+ this.updateTileStates();
484
+ },
485
+ immediate: true
486
+ };
487
+ Task.add(this.collision.task);
488
+ }
489
+ // else {
490
+ // log('debug', `Skip label layout due to on-going layout (zoom ${this.view.zoom.toFixed(2)}, tiles ${this.collision.tile_keys})`);
491
+ // }
492
+ return this.collision.task.promise;
493
+ }
494
+
495
+ updateProxyTiles () {
496
+ if (this.preload_zoom !== undefined) {
497
+ this.forEachTile(tile => tile.setProxyFor(null));
498
+ return;
499
+ }
500
+ if (this.view.zoom_direction === 0) {
501
+ return;
502
+ }
503
+
504
+ // Clear previous proxies
505
+ this.forEachTile(tile => tile.setProxyFor(null));
506
+
507
+ let proxy = false;
508
+ this.forEachTile(tile => {
509
+ if (tile.visible && !tile.labeled) {
510
+ const parent = this.pyramid?.getAncestor(tile);
511
+ if (parent) {
512
+ parent.setProxyFor(tile);
513
+ proxy = true;
514
+ } else {
515
+ const descendants = this.pyramid?.getDescendants(tile) ?? [];
516
+ for (let i=0; i < descendants.length; i++) {
517
+ descendants[i].setProxyFor(tile);
518
+ proxy = true;
519
+ }
520
+ }
521
+ }
522
+ });
523
+
524
+ if (!proxy) {
525
+ this.view.zoom_direction = 0;
526
+ }
527
+ }
528
+
529
+ updateVisibility(tile: Tile) {
530
+ tile.visible = false;
531
+ if (tile.style_z === this.view.tile_zoom) {
532
+ const direct = this.visible_coords[tile.coords.key];
533
+ if (direct && TileID.normalizedCoord(direct, tile.source).key === tile.coords.key) {
534
+ tile.visible = true;
535
+ return;
536
+ }
537
+ // Match the actual source-normalized data level, not any ancestor.
538
+ // A data-only LOD transition retains style_z on both cached levels.
539
+ for (let key in this.visible_coords) {
540
+ if (TileID.normalizedCoord(this.visible_coords[key], tile.source).key === tile.coords.key) {
541
+ tile.visible = true;
542
+ break;
543
+ }
544
+ }
545
+ }
546
+ }
547
+
548
+ /** Fill missing visible detail, even when rotation does not change the zoom. */
549
+ updateGlobeFallbackTiles() {
550
+ this.forEachTile(tile => { tile.fallback_for = null; tile.fallback_pending = false; });
551
+ const styleZoom = this.view.tile_zoom;
552
+ if (this.preload_zoom === undefined || styleZoom === undefined) return;
553
+ for (const coords of Object.values(this.visible_coords)) {
554
+ for (const source of Object.values(this.activeScene.sources || {})) {
555
+ const key = TileID.normalizedKey(coords, source, styleZoom);
556
+ if (!key) continue;
557
+ const detail = this.tiles[key];
558
+ if (!detail || detail.built) continue;
559
+ const fallbackKey = getGlobePreloadKey(detail.coords, source, styleZoom, this.preload_zoom);
560
+ if (!fallbackKey) continue;
561
+ const fallback = this.tiles[fallbackKey];
562
+ if (!fallback || fallback === detail || !fallback.built) continue;
563
+ if (!Object.keys(fallback.meshes || {}).some(name => fallback.meshes[name]?.length &&
564
+ isGlobeFallbackStyle(name, this.activeScene.styles?.[name]?.base))) continue;
565
+ fallback.visible = true;
566
+ fallback.fallback_for = fallback.fallback_for || new Map();
567
+ fallback.fallback_for.set(detail.key, getGlobeFallbackClipBounds(fallback.coords, detail.coords));
568
+ detail.fallback_pending = true;
569
+ }
570
+ }
571
+ }
572
+
573
+ // Remove tiles that aren't visible, and flag remaining visible ones to be updated (for loading, proxy, etc.)
574
+ pruneToVisibleTiles () {
575
+ this.removeTiles(tile => !tile.visible);
576
+ }
577
+
578
+ getRenderableTiles () {
579
+ return this.renderable_tiles;
580
+ }
581
+
582
+ updateRenderableTiles() {
583
+ this.renderable_tiles = [];
584
+ for (let t in this.tiles) {
585
+ let tile = this.tiles[t];
586
+ if (tile.visible && tile.loaded && !tile.fallback_pending) {
587
+ this.renderable_tiles.push(tile);
588
+ }
589
+ }
590
+ return this.renderable_tiles;
591
+ }
592
+
593
+ isLoadingVisibleTiles () {
594
+ return Object.keys(this.tiles).some(k => this.tiles[k].visible && !this.tiles[k].built);
595
+ }
596
+
597
+ allVisibleTilesLabeled () {
598
+ return this.renderable_tiles.every(t => t.labeled);
599
+ }
600
+
601
+ // Queue a tile for load
602
+ queueCoordinate(coords: TileCoordinate) {
603
+ this.queued_coords[this.queued_coords.length] = coords;
604
+ }
605
+
606
+ // Load all queued tiles
607
+ loadQueuedCoordinates() {
608
+ if (this.queued_coords.length === 0) {
609
+ return;
610
+ }
611
+
612
+ // Sort queued tiles from center tile
613
+ this.queued_coords.sort((a, b) => {
614
+ let center = this.view.center?.meters;
615
+ if (!center) return 0;
616
+ let half_span = Geo.metersPerTile(a.z) / 2;
617
+
618
+ let ac = Geo.metersForTile(a);
619
+ ac.x += half_span;
620
+ ac.y -= half_span;
621
+
622
+ let bc = Geo.metersForTile(b);
623
+ bc.x += half_span;
624
+ bc.y -= half_span;
625
+
626
+ let ad = Math.abs(center.x - ac.x) + Math.abs(center.y - ac.y);
627
+ let bd = Math.abs(center.x - bc.x) + Math.abs(center.y - bc.y);
628
+
629
+ a.center_dist = ad;
630
+ b.center_dist = bd;
631
+
632
+ return (bd > ad ? -1 : (bd === ad ? 0 : 1));
633
+ });
634
+ this.queued_coords.forEach(coords => this.loadCoordinate(coords));
635
+ this.queued_coords = [];
636
+ }
637
+
638
+ // Load all tiles to cover a given logical tile coordinate
639
+ loadCoordinate(coords: TileCoordinate, preload = false) {
640
+ const styleZoom = this.view.tile_zoom;
641
+ if (styleZoom === undefined) return;
642
+ // A queued coordinate can outlive a field update; do not start uncovered work.
643
+ if (this.activeScene.terrain && !hasTerrainTileCoverage(this.activeScene.terrain.grids, coords)) return;
644
+ // Skip if not at current scene zoom
645
+ if (!preload && coords.z !== this.view.center?.tile?.z) {
646
+ return;
647
+ }
648
+
649
+ // Determine necessary tiles for each source
650
+ for (let s in this.activeScene.sources) {
651
+ let source = this.activeScene.sources[s];
652
+ // Check if data source should build this tile
653
+ if (!source.builds_geometry_tiles || !source.includesTile(coords, styleZoom)) {
654
+ continue;
655
+ }
656
+
657
+ let key = TileID.normalizedKey(coords, source, styleZoom);
658
+ if (preload && (!key || !this.preloaded_keys.has(key))) continue;
659
+ if (key && !this.hasTile(key)) {
660
+ const workers = this.activeScene.workers;
661
+ if (!workers) throw new Error('TileManager loading requires an initialized worker pool');
662
+ log('trace', `load tile ${key}, distance from view center: ${coords.center_dist}`);
663
+ let tile = new Tile({
664
+ source,
665
+ coords,
666
+ workers,
667
+ // Data LOD can be coarser than style zoom in a host frame.
668
+ style_z: styleZoom,
669
+ view: this.view
670
+ });
671
+
672
+ this.keepTile(tile);
673
+ this.buildTile(tile);
674
+ }
675
+ }
676
+ }
677
+
678
+ // Start tile build process
679
+ buildTile(tile: Tile, options?: Parameters<Tile['build']>[1]) {
680
+ if (this.activeScene.terrainTransaction && !tile.terrainStage) tile.beginTerrainStage();
681
+ this.tileBuildStart(tile.key);
682
+ this.updateVisibility(tile);
683
+ const generation = this.activeScene.generation;
684
+ const token = `${tile.id}/${generation}`;
685
+ this.build_queue.enqueue({key: tile.key, token, priority: this.getBuildPriority(tile),
686
+ start: () => {
687
+ Promise.resolve(tile.build(generation, options)).catch(error => {
688
+ if (this.tiles[tile.key] === tile && tile.generation === generation) {
689
+ this.buildTileError({...tile, error});
690
+ }
691
+ });
692
+ },
693
+ fail: error => this.buildTileError({...tile, error})
694
+ });
695
+ }
696
+
697
+ // Called on main thread when a web worker completes processing for a single tile (initial load, or rebuild)
698
+ buildTileStylesCompleted({tile: incomingTile, progress}: {tile: TileBuildReply; progress: TileBuildProgress}) {
699
+ const reply = incomingTile;
700
+ let tile: TileBuildReply | Tile = reply;
701
+ let sharedReservation: TerrainResourceLease | null | undefined;
702
+ // Validate ownership before accounting a reply: stale/removed work cannot fail a live generation.
703
+ if (this.activeScene.terrain && tile.generation === this.activeScene.generation && this.tiles[tile.key]?.id === tile.id) {
704
+ let incoming = 0;
705
+ for (const data of Object.values(tile.mesh_data ?? {})) for (const mesh of Object.values(data.meshes ?? {})) {
706
+ const query = mesh.terrain_surface;
707
+ incoming += 2 * ((mesh.vertex_data?.byteLength ?? 0) + (mesh.vertex_elements ? mesh.vertex_elements.byteLength : 0)) +
708
+ (query ? query.positions.byteLength + query.heights.byteLength + query.indices.byteLength + (query.globeBasis?.byteLength ?? 0) : 0);
709
+ }
710
+ if (incoming > this.activeScene.terrain.maxResidentMeshBytes - this.getResidentTerrainMeshBytes()) {
711
+ this.buildTileError({...tile, error: new Error('Terrain peak resident mesh-byte budget exceeded')}); return;
712
+ }
713
+ try {
714
+ this.syncTerrainResourceLeases();
715
+ sharedReservation = this.activeScene.terrainResourceBudget?.reserve('transient', incoming);
716
+ if (this.activeScene.terrainResourceBudget && !sharedReservation) throw new Error('Terrain shared replacement resource budget exceeded');
717
+ } catch (error) {this.buildTileError({...tile, error}); return;}
718
+ }
719
+ if (this.activeScene.terrainTransaction && tile.generation === this.activeScene.generation && this.tiles[tile.key]?.id === tile.id) {
720
+ let incoming = 0;
721
+ for (const data of Object.values(tile.mesh_data ?? {})) for (const mesh of Object.values(data.meshes ?? {})) {
722
+ const query = mesh.terrain_surface;
723
+ incoming += (mesh.vertex_data?.byteLength ?? 0) + (mesh.vertex_elements ? mesh.vertex_elements.byteLength : 0) + (query ? query.positions.byteLength + query.heights.byteLength + query.indices.byteLength + (query.globeBasis?.byteLength ?? 0) : 0);
724
+ }
725
+ if (this.activeScene.terrain && this.getStagedTerrainBytes() + incoming > this.activeScene.terrain.maxStagedMeshBytes) {
726
+ sharedReservation?.release();
727
+ this.buildTileError({...tile, error: new Error('Terrain staged mesh-byte budget exceeded')}); return;
728
+ }
729
+ }
730
+ // Removed this tile during load?
731
+ if (this.tiles[tile.key] == null) {
732
+ log('trace', `discarded tile ${tile.key} in TileManager.buildTileStylesCompleted because previously removed`);
733
+ Tile.abortBuild(tile);
734
+ this.updateTileStates();
735
+ }
736
+ // Built with an outdated scene configuration?
737
+ else if (tile.generation !== this.activeScene.generation) {
738
+ log('trace', `discarded tile ${tile.key} in TileManager.buildTileStylesCompleted because built with ` +
739
+ `scene config gen ${tile.generation}, current ${this.activeScene.generation}`);
740
+ Tile.abortBuild(tile);
741
+ this.updateTileStates();
742
+ }
743
+ else {
744
+ // Update tile with properties from worker
745
+ const retained = this.tiles[tile.key];
746
+ if (retained) {
747
+ // Ignore if from a previously discarded tile
748
+ if (tile.id < this.tiles[tile.key].id) {
749
+ log('trace', `discarded tile ${tile.key} for id ${tile.id} in TileManager.buildTileStylesCompleted because built for discarded tile id`);
750
+ Tile.abortBuild(tile);
751
+ return;
752
+ }
753
+
754
+ tile = retained.merge(reply);
755
+ }
756
+ const uploadedTile = this.tiles[reply.key];
757
+ if (!uploadedTile) return;
758
+
759
+ if (progress.done) {
760
+ if (uploadedTile.terrainStage) uploadedTile.terrainStage.done = true;
761
+ else uploadedTile.built = true;
762
+ }
763
+
764
+ const batch = tile.mesh_data;
765
+ try {
766
+ const styles = this.activeScene.styles;
767
+ if (!styles) throw new Error('TileManager uploads require loaded scene styles');
768
+ this.activeScene.withWebGLContext(() => uploadedTile.buildMeshes(styles, progress));
769
+ }
770
+ catch (error) {
771
+ sharedReservation?.release();
772
+ if (!this.activeScene.terrainTransaction) throw error;
773
+ delete tile.mesh_data;
774
+ this.buildTileError({...Tile.slice(uploadedTile), mesh_data: batch, error}); return;
775
+ }
776
+ sharedReservation?.release();
777
+ this.syncTerrainResourceLeases();
778
+ this.updateTileStates();
779
+ this.activeScene.requestRedraw();
780
+ }
781
+
782
+ if (progress.done) {
783
+ this.tileBuildStop(tile.key, `${tile.id}/${tile.generation}`);
784
+ // A completed off-screen tile becomes evictable only after releasing its build slot.
785
+ this.enforceCacheLimits();
786
+ }
787
+ }
788
+
789
+ // Called on main thread when web worker encounters an error building a tile
790
+ buildTileError(tile: TileBuildFailure) {
791
+ const current = this.tiles[tile.key];
792
+ if (!current || current.id !== tile.id || current.generation !== tile.generation ||
793
+ tile.generation !== this.activeScene.generation) {
794
+ Tile.abortBuild(tile);
795
+ this.tileBuildStop(tile.key, `${tile.id}/${tile.generation}`);
796
+ return;
797
+ }
798
+ log('error', `Error building tile ${tile.key}:`, tile.error);
799
+ // Record the failure before releasing work: the last release may settle
800
+ // the scene rebuild synchronously. Stale generations never reach here.
801
+ this.activeScene.tileManagerBuildError?.(tile.error);
802
+ if (this.activeScene.terrainTransaction) {
803
+ current.discardTerrainStage();
804
+ current.loading = false;
805
+ Tile.abortBuild(tile);
806
+ this.tileBuildStop(tile.key, `${tile.id}/${tile.generation}`);
807
+ return;
808
+ }
809
+ const ownedMeshData = current.mesh_data;
810
+ current.destroy();
811
+ this.forgetTile(tile.key);
812
+ // A rejected main-thread build can carry the same batch already owned by
813
+ // its meshes. Destruction released that batch's texture references;
814
+ // abort only independently transferred worker batches a second time.
815
+ Tile.abortBuild(tile.mesh_data === ownedMeshData ? {...tile, mesh_data: undefined} : tile);
816
+ }
817
+
818
+ // Track tile build state
819
+ tileBuildStart(key: string) {
820
+ this.building_tiles = this.building_tiles || {};
821
+ this.building_tiles[key] = true;
822
+ log('trace', `tileBuildStart for ${key}: ${Object.keys(this.building_tiles).length}`);
823
+ }
824
+
825
+ tileBuildStop(key: string, token?: string) {
826
+ if (token !== undefined) this.build_queue.finish(key, token);
827
+ // Done building?
828
+ if (this.building_tiles) {
829
+ log('trace', `tileBuildStop for ${key}: ${Object.keys(this.building_tiles).length}`);
830
+ if (!this.build_queue.has(key)) delete this.building_tiles[key];
831
+ this.checkBuildQueue();
832
+ }
833
+ }
834
+
835
+ // Check status of tile building queue and notify scene when we're done
836
+ checkBuildQueue() {
837
+ this.build_queue.pump();
838
+ if (!this.building_tiles || Object.keys(this.building_tiles).length === 0) {
839
+ this.building_tiles = null;
840
+ this.activeScene.tileManagerBuildDone();
841
+ }
842
+ }
843
+
844
+ // Get a debug property across tiles
845
+ getDebugProp(prop: keyof TileDebug, filter?: (tile: Tile) => boolean) {
846
+ var vals = [];
847
+ for (var t in this.tiles) {
848
+ if (this.tiles[t].debug[prop] != null && (typeof filter !== 'function' || filter(this.tiles[t]) === true)) {
849
+ vals.push(this.tiles[t].debug[prop]);
850
+ }
851
+ }
852
+ return vals;
853
+ }
854
+
855
+ // Sum of a debug property across tiles
856
+ getDebugSum(prop: 'building' | 'feature_count' | 'geometry_count' | 'buffer_size', filter?: (tile: Tile) => boolean) {
857
+ var sum = 0;
858
+ for (var t in this.tiles) {
859
+ if (this.tiles[t].debug[prop] != null && (typeof filter !== 'function' || filter(this.tiles[t]) === true)) {
860
+ sum += this.tiles[t].debug[prop] ?? 0;
861
+ }
862
+ }
863
+ return sum;
864
+ }
865
+
866
+ // Average of a debug property across tiles
867
+ getDebugAverage(prop: Parameters<TileManager['getDebugSum']>[0], filter?: (tile: Tile) => boolean) {
868
+ return this.getDebugSum(prop, filter) / Object.keys(this.tiles).length;
869
+ }
870
+
871
+ }
872
+
873
+ // Round a number to given number of decimal divisions
874
+ // e.g. roundPrecision(x, 4) rounds a number to increments of 0.25
875
+ function roundPrecision (x: number, d: number, places = 2) {
876
+ return (Math.floor(x * d) / d).toFixed(places);
877
+ }
878
+
879
+ // Create a string representing the current set of meshes for a given set of tiles,
880
+ // based on their created timestamp. Used to determine when tiles should be re-collided.
881
+ function meshSetString (tiles: Tile[]) {
882
+ return JSON.stringify(
883
+ Object.entries(tiles).map(([,t]) => {
884
+ return Object.entries(t.meshes).map(([,s]) => {
885
+ return s.map(m => m.created_at);
886
+ });
887
+ })
888
+ );
889
+ }