@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,542 @@
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 log from '../utils/log';
7
+ import Texture from '../gl/texture';
8
+ import WorkerBroker from '../utils/worker_broker';
9
+ import type {Device, Framebuffer} from '@luma.gl/core';
10
+ import {Texture as DeviceTexture} from '@luma.gl/core';
11
+ import {findSelectionKey, readSelectionPixels} from './selection_pixels';
12
+
13
+ import type {BrokerWorker} from '../utils/worker-types';
14
+ import type {SelectionContext, SelectionEntry, SelectionPoint, SelectionReply, SelectionRequest,
15
+ SelectionResult, SelectionSourceFeature, SelectionTile, SelectionTileEntries} from './selection-types';
16
+
17
+ /** Selection readback and worker lookup lifecycle, independent of the scene's render backend. */
18
+ export default class FeatureSelection {
19
+ /** Invalidates asynchronous GPU pixels when worker feature tables move to another generation. */
20
+ private readbackRevision = 0;
21
+ /** Legacy GL handle, absent for device-only selection. */
22
+ declare gl: WebGLRenderingContext | null;
23
+ /** Worker lookup endpoints indexed by the alpha-byte worker identifier. */
24
+ declare workers: BrokerWorker[] | Record<number, BrokerWorker>;
25
+ /** luma device used for asynchronous texture readback. */
26
+ declare device: Device | null;
27
+ /** Optional external selection-buffer lock. */
28
+ declare _lock_fn: false | (() => boolean);
29
+ /** Whether teardown has invalidated all pending work. */
30
+ declare destroyed: boolean;
31
+ /** Requests keyed by their monotonically increasing selection identifier. */
32
+ declare requests: Record<string | number, SelectionRequest>;
33
+ /** Last resolved payload; custom workers may supply arbitrary cloneable data. */
34
+ declare feature: unknown;
35
+ /** Delay before reading the selection buffer. */
36
+ declare read_delay: number;
37
+ /** Scheduled readback timer, or null after cancellation/execution. */
38
+ declare read_delay_timer: ReturnType<typeof setTimeout> | null;
39
+ /** Lazily allocated legacy readback storage. */
40
+ declare pixels: Uint8Array | null;
41
+ /** Selection attachment dimensions. */
42
+ declare fbo_size: {width: number; height: number};
43
+ /** Owned luma framebuffer, installed only on the device path. */
44
+ declare framebuffer: Framebuffer | null | undefined;
45
+ /** Explicit copyable attachment; the framebuffer does not own supplied textures. */
46
+ private selectionTexture: DeviceTexture | null = null;
47
+ /** Legacy color attachment owned by this target, never shared through a fixed texture name. */
48
+ private legacySelectionTexture: {destroy(): void} | null = null;
49
+ /** Legacy depth attachment released with its framebuffer. */
50
+ private depthRenderbuffer: WebGLRenderbuffer | null = null;
51
+ /** Unique names also isolate eyes belonging to different scenes/contexts. */
52
+ private static nextAttachmentId = 0;
53
+ /** Owned legacy GL framebuffer, installed only on the GL path. */
54
+ declare fbo: WebGLFramebuffer | null | undefined;
55
+ /** Deliberately uninitialized until the first request retains legacy ID allocation. */
56
+ declare selection_request_id: number | undefined;
57
+ /** Worker-local selection entries. */
58
+ declare static map: Record<number, SelectionEntry>;
59
+ /** Worker-local selection ownership by build key. */
60
+ declare static tiles: Record<string, SelectionTileEntries>;
61
+ /** Number of live worker-local selection entries. */
62
+ declare static map_size: number;
63
+ /** Next worker-local color counter. */
64
+ declare static map_entry: number;
65
+ /** Worker identifier encoded in the alpha channel. */
66
+ declare static map_prefix: number;
67
+ /** Clear color for empty selection pixels. */
68
+ declare static defaultColor: [number, number, number, number];
69
+
70
+
71
+ /** Create the existing device or legacy GL selection attachments. */
72
+ constructor(gl: WebGLRenderingContext, workers: BrokerWorker[] | Record<number, BrokerWorker>,
73
+ lock_fn?: (() => boolean) | null, device?: null);
74
+ constructor(gl: WebGLRenderingContext | null, workers: BrokerWorker[] | Record<number, BrokerWorker>,
75
+ lock_fn: (() => boolean) | null | undefined, device: Device);
76
+ constructor(gl: WebGLRenderingContext | null, workers: BrokerWorker[] | Record<number, BrokerWorker>,
77
+ lock_fn?: (() => boolean) | null, device: Device | null = null) {
78
+ this.gl = gl;
79
+ this.workers = workers; // pool of workers to request feature look-ups from, keyed by id
80
+ this.device = device;
81
+ this._lock_fn = (typeof lock_fn === 'function') && lock_fn; // indicates if safe to read/write selection buffer this frame
82
+ this.init();
83
+ }
84
+
85
+ /** Initialize empty selection state and allocate its framebuffer. */
86
+ init(): void {
87
+ this.destroyed = false;
88
+ // Selection state tracking
89
+ this.requests = {}; // pending selection requests
90
+ this.feature = null; // currently selected feature
91
+ this.read_delay = 0; // delay time from selection render to framebuffer sample, to avoid CPU/GPU sync lock
92
+ this.read_delay_timer = null; // current timer (setTimeout) for delayed selection reads
93
+ this.pixels = null; // allocated lazily on request
94
+
95
+ this.fbo_size = { width: 256, height: 256 }; // TODO: make configurable / adaptive based on canvas size
96
+ if (this.device) {
97
+ // Device-backed textures cannot be attached using the legacy raw
98
+ // WebGL path. Let luma own the attachments and selection render pass.
99
+ const texture = this.device.createTexture({
100
+ id: 'tangram-selection-color', format: 'rgba8unorm',
101
+ width: this.fbo_size.width, height: this.fbo_size.height,
102
+ usage: DeviceTexture.RENDER_ATTACHMENT | DeviceTexture.COPY_SRC
103
+ });
104
+ try {
105
+ this.framebuffer = this.device.createFramebuffer({
106
+ id: 'tangram-selection',
107
+ width: this.fbo_size.width,
108
+ height: this.fbo_size.height,
109
+ colorAttachments: [texture],
110
+ depthStencilAttachment: 'depth16unorm'
111
+ });
112
+ this.selectionTexture = texture;
113
+ } catch (error) {
114
+ texture.destroy();
115
+ throw error;
116
+ }
117
+ return;
118
+ }
119
+
120
+ // Frame buffer for selection
121
+ // TODO: initiate lazily in case we don't need to do any selection
122
+ this.fbo = this.gl!.createFramebuffer();
123
+ this.gl!.bindFramebuffer(this.gl!.FRAMEBUFFER, this.fbo!);
124
+
125
+ // Texture for the FBO color attachment
126
+ var fbo_texture = (Texture as unknown as {
127
+ create(gl: WebGLRenderingContext, name: string, options: {filtering: string}): {
128
+ texture: WebGLTexture; setData(width: number, height: number, data: null, options: {filtering: string}): void;
129
+ destroy(): void;
130
+ };
131
+ }).create( this.gl!, `__selection_fbo_${FeatureSelection.nextAttachmentId++}`, { filtering: 'nearest' });
132
+ this.legacySelectionTexture = fbo_texture;
133
+ fbo_texture.setData(this.fbo_size.width, this.fbo_size.height, null, { filtering: 'nearest' });
134
+ this.gl!.framebufferTexture2D(this.gl!.FRAMEBUFFER, this.gl!.COLOR_ATTACHMENT0, this.gl!.TEXTURE_2D, fbo_texture.texture, 0);
135
+
136
+ // Renderbuffer for the FBO depth attachment
137
+ var fbo_depth_rb = this.gl!.createRenderbuffer();
138
+ this.depthRenderbuffer = fbo_depth_rb;
139
+ this.gl!.bindRenderbuffer(this.gl!.RENDERBUFFER, fbo_depth_rb);
140
+ this.gl!.renderbufferStorage(this.gl!.RENDERBUFFER, this.gl!.DEPTH_COMPONENT16, this.fbo_size.width, this.fbo_size.height);
141
+ this.gl!.framebufferRenderbuffer(this.gl!.FRAMEBUFFER, this.gl!.DEPTH_ATTACHMENT, this.gl!.RENDERBUFFER, fbo_depth_rb);
142
+
143
+ this.gl!.bindFramebuffer(this.gl!.FRAMEBUFFER, null);
144
+ }
145
+
146
+ /** Reject pending requests, cancel delayed reads, and release the framebuffer once. */
147
+ destroy(): void {
148
+ if (this.destroyed) return;
149
+ this.destroyed = true;
150
+ clearTimeout(this.read_delay_timer!);
151
+ this.read_delay_timer = null;
152
+ for (const request of Object.values(this.requests)) request.reject(new Error('Feature selection destroyed'));
153
+ this.requests = {};
154
+ if (this.framebuffer) {
155
+ this.framebuffer.destroy();
156
+ this.selectionTexture?.destroy();
157
+ this.selectionTexture = null;
158
+ this.framebuffer = null;
159
+ this.fbo = null;
160
+ return;
161
+ }
162
+ if (this.gl && this.fbo) {
163
+ this.gl!.deleteFramebuffer(this.fbo);
164
+ this.fbo = null;
165
+ this.gl!.bindFramebuffer(this.gl!.FRAMEBUFFER, null);
166
+ }
167
+
168
+ this.legacySelectionTexture?.destroy();
169
+ this.legacySelectionTexture = null;
170
+ if (this.gl && this.depthRenderbuffer) this.gl.deleteRenderbuffer(this.depthRenderbuffer);
171
+ this.depthRenderbuffer = null;
172
+ }
173
+
174
+ // external lock function determines when it's safe to read/write from selection buffer
175
+ /** Whether the host currently prevents selection readback. */
176
+ get locked (): boolean {
177
+ return (this._lock_fn && this._lock_fn()) || false;
178
+ }
179
+
180
+ /** Bind the legacy framebuffer; the device path requires a host-owned render pass. */
181
+ bind(): void {
182
+ if (this.device) throw new Error('Device selection must use a luma render pass');
183
+ // Switch to FBO
184
+ this.gl!.bindFramebuffer(this.gl!.FRAMEBUFFER, this.fbo!);
185
+ this.gl!.viewport(0, 0, this.fbo_size.width, this.fbo_size.height);
186
+ this.gl!.clearColor(...FeatureSelection.defaultColor);
187
+ }
188
+
189
+ // Request feature selection
190
+ // Runs asynchronously, schedules selection buffer to be updated
191
+ /** Queue a normalized canvas point and optional radius for the next selection frame. */
192
+ getFeatureAt(point: SelectionPoint | null | undefined, { radius }: {radius?: SelectionPoint | null}): Promise<SelectionResult> {
193
+ if (this.destroyed) return Promise.reject(new Error('Feature selection destroyed'));
194
+ // ensure requested point is in canvas bounds
195
+ if (!point || ![point.x, point.y].every(Number.isFinite) || point.x < 0 || point.y < 0 || point.x > 1 || point.y > 1) {
196
+ return Promise.resolve({ feature: null, changed: false });
197
+ }
198
+
199
+ return new Promise<SelectionResult>((resolve, reject) => {
200
+ // Queue requests for feature selection, and they will be picked up by the render loop
201
+ this.selection_request_id = (this.selection_request_id! + 1) || 0;
202
+ this.requests[this.selection_request_id] = {
203
+ id: this.selection_request_id,
204
+ point,
205
+ radius,
206
+ resolve,
207
+ reject
208
+ };
209
+ });
210
+ }
211
+
212
+ // Any pending selection requests
213
+ /** Return pending request records, or null when the queue is empty. */
214
+ pendingRequests(): Record<string | number, SelectionRequest> | null {
215
+ return Object.keys(this.requests).length ? this.requests : null;
216
+ }
217
+
218
+ /** Whether any selection read or worker reply is outstanding. */
219
+ hasPendingRequests(): boolean {
220
+ return this.pendingRequests() != null;
221
+ }
222
+
223
+ /** Reject unsent requests without canceling worker replies already in flight. */
224
+ clearPendingRequests(): void {
225
+ for (var r in this.requests) {
226
+ var request = this.requests[r];
227
+
228
+ // This request was already sent to the worker, we're just awaiting its reply
229
+ if (request.sent) {
230
+ continue;
231
+ }
232
+
233
+ // Reject request since it will never be fulfilled
234
+ // TODO: pass a reason for rejection?
235
+ request.reject({ request });
236
+ delete this.requests[r];
237
+ }
238
+ }
239
+
240
+ /** Keep unsent promises queued, but prevent old GPU pixels from looking up newly assigned feature keys. */
241
+ invalidateReadback(): void {
242
+ this.readbackRevision++;
243
+ if (this.read_delay_timer !== null) clearTimeout(this.read_delay_timer);
244
+ this.read_delay_timer = null;
245
+ for (const request of Object.values(this.requests)) if (!request.sent) request.reading = false;
246
+ }
247
+
248
+ // Read pending results from the selection buffer. Called after rendering to selection buffer.
249
+ /** Schedule readback after rendering, respecting the external lock. */
250
+ read(): void {
251
+ // Delay reading the pixel result from the selection buffer to avoid CPU/GPU sync lock.
252
+ // Calling readPixels synchronously caused a massive performance hit, presumably since it
253
+ // forced this function to wait for the GPU to finish rendering and retrieve the texture contents.
254
+ if (this.read_delay_timer != null) {
255
+ clearTimeout(this.read_delay_timer!);
256
+ }
257
+ this.read_delay_timer = setTimeout(() => {
258
+ this.read_delay_timer = null;
259
+ if (this.locked || this.destroyed) {
260
+ return;
261
+ }
262
+ if (this.device) {
263
+ void this.readDeviceRequests();
264
+ return;
265
+ }
266
+
267
+ var gl = this.gl!;
268
+
269
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo!);
270
+
271
+ for (var r in this.requests) {
272
+ var request = this.requests[r];
273
+
274
+ // This request was already sent to the worker, we're just awaiting its reply
275
+ if (request.sent) {
276
+ continue;
277
+ }
278
+
279
+ // Check selection map against FBO
280
+ let feature_key, worker_id = 255;
281
+ let {point, radius} = request;
282
+ let diam_px;
283
+
284
+ if (!radius) {
285
+ radius = { x: 0, y: 0 };
286
+ diam_px = { x: 1, y: 1 };
287
+ }
288
+ else {
289
+ // diameter in selection buffer pixels
290
+ let max_radius = Math.min(this.fbo_size.width, this.fbo_size.height);
291
+ diam_px = {
292
+ x: Math.min(Math.ceil(radius.x * 2 * this.fbo_size.width), max_radius),
293
+ y: Math.min(Math.ceil(radius.y * 2 * this.fbo_size.height), max_radius)
294
+ };
295
+ }
296
+
297
+ // allocate or resize
298
+ if (this.pixels == null || this.pixels.byteLength < diam_px.x * diam_px.y * 4) {
299
+ this.pixels = new Uint8Array(diam_px.x * diam_px.y * 4);
300
+ }
301
+
302
+ // clear pixels
303
+ if (this.pixels.fill instanceof Function) {
304
+ this.pixels.fill(0); // native typed array fill
305
+ }
306
+ else {
307
+ for (let p=0; p < this.pixels.length; p++) {
308
+ this.pixels[p] = 0;
309
+ }
310
+ }
311
+
312
+ // capture pixels
313
+ gl.readPixels(
314
+ Math.round(((point.x - radius.x) * this.fbo_size.width)),
315
+ Math.round((1 - point.y - radius.y) * this.fbo_size.height),
316
+ diam_px.x, diam_px.y, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
317
+
318
+ // first check center pixel (avoid scanning all pixels if cursor is directly on a feature)
319
+ let p = (Math.round(diam_px.y / 2) * diam_px.x + Math.round(diam_px.x / 2)) * 4;
320
+ let v = this.pixels[p] + (this.pixels[p+1] << 8) + (this.pixels[p+2] << 16); // feature id in RGB channels
321
+ if (v > 0) {
322
+ feature_key = (v + (this.pixels[p+3] << 24)) >>> 0; // worker id in alpha channel
323
+ worker_id = this.pixels[p+3];
324
+ }
325
+ else {
326
+ // scan all pixels for feature closest to cursor
327
+ let min_dist = -1 >>> 0;
328
+ p = 0;
329
+ for (let y=0; y < diam_px.y; y++) {
330
+ for (let x=0; x < diam_px.x; x++, p += 4) {
331
+ v = this.pixels[p] + (this.pixels[p+1] << 8) + (this.pixels[p+2] << 16); // feature id in RGB channels
332
+ if (v > 0) { // non-zero value indicates a feature
333
+ // check to see if closer than last found feature
334
+ let dist = (x - diam_px.x/2) * (x - diam_px.x/2) + (y - diam_px.y/2) * (y - diam_px.y/2);
335
+ if (dist <= min_dist) {
336
+ // get worker id from alpha channel
337
+ feature_key = (v + (this.pixels[p+3] << 24)) >>> 0;
338
+ worker_id = this.pixels[p+3];
339
+ min_dist = dist;
340
+ }
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ // If feature found, ask appropriate web worker to lookup feature
347
+ if (worker_id !== 255) { // 255 indicates an empty selection buffer pixel
348
+ if (this.workers[worker_id] != null) {
349
+ WorkerBroker.postMessage<SelectionReply>(
350
+ this.workers[worker_id],
351
+ 'self.getFeatureSelection',
352
+ { id: request.id, key: feature_key })
353
+ .then(message => {
354
+ this.finishRead(message);
355
+ });
356
+ }
357
+ }
358
+ // No feature found, but still need to resolve promise
359
+ else {
360
+ this.finishRead({ id: request.id });
361
+ }
362
+
363
+ request.sent = true;
364
+ }
365
+
366
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
367
+
368
+ }, this.read_delay);
369
+ }
370
+
371
+ /** Read each request without raw GL handles; late/canceled reads cannot update selection state. */
372
+ async readDeviceRequests(): Promise<void> {
373
+ for (const request of Object.values(this.requests)) {
374
+ // A subsequent frame may invalidate the target while a previous
375
+ // request awaits its worker. Leave the remaining requests queued.
376
+ if (this.locked) return;
377
+ if (this.destroyed || this.requests[request.id] !== request || request.sent || request.reading) continue;
378
+ request.reading = true;
379
+ const revision = this.readbackRevision;
380
+ try {
381
+ const texture = this.framebuffer!.colorAttachments[0].texture;
382
+ const {pixels, width, height} = await readSelectionPixels(this.device!, texture, request.point, request.radius);
383
+ if (revision !== this.readbackRevision) return;
384
+ if (this.destroyed || this.requests[request.id] !== request) continue;
385
+ if (this.locked) {request.reading = false; return;}
386
+ const hit = findSelectionKey(pixels, width, height);
387
+ if (hit && hit.workerId !== 255 && this.workers[hit.workerId]) {
388
+ request.sent = true;
389
+ const message = await WorkerBroker.postMessage<SelectionReply>(this.workers[hit.workerId], 'self.getFeatureSelection',
390
+ {id: request.id, key: hit.key});
391
+ if (!this.destroyed && this.requests[request.id] === request) this.finishRead(message);
392
+ } else {
393
+ this.finishRead({id: request.id});
394
+ }
395
+ } catch (error) {
396
+ if (revision !== this.readbackRevision) return;
397
+ if (this.requests[request.id] === request) {
398
+ request.reject(error);
399
+ delete this.requests[request.id];
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ // Called on main thread when a web worker finds a feature in the selection buffer
406
+ /** Resolve a worker reply and preserve the existing JSON-based change comparison. */
407
+ finishRead (message: SelectionReply): void {
408
+ var request = this.requests[message.id];
409
+ if (!request) {
410
+ log('error', 'FeatureSelection.finishRead(): could not find message', message);
411
+ return; // request was cleared before it returned
412
+ }
413
+
414
+ var feature = message.feature;
415
+ var changed = false;
416
+ if ((feature != null && this.feature == null) ||
417
+ (feature == null && this.feature != null) ||
418
+ (feature != null && this.feature != null &&
419
+ JSON.stringify(feature) !== JSON.stringify(this.feature))) {
420
+ changed = true;
421
+ }
422
+
423
+ this.feature = feature; // store the most recently selected feature
424
+
425
+ // Resolve the request
426
+ request.resolve({ feature, changed, request });
427
+ delete this.requests[message.id]; // done processing this request
428
+ }
429
+
430
+
431
+ // Selection map generation
432
+ // Each worker will create its own independent, 'local' selection map
433
+
434
+ // Create a unique 32-bit color to identify a feature
435
+ // Workers independently create/modify selection colors in their own threads, but we also
436
+ // need the main thread to know where each feature color originated. To accomplish this,
437
+ // we partition the map by setting the 4th component (alpha channel) to the worker's id.
438
+ /** Allocate a worker-prefixed selection color owned by the given tile build. */
439
+ static makeEntry(tile: SelectionTile): SelectionEntry {
440
+ // 32-bit color key
441
+ this.map_entry++;
442
+ var ir = this.map_entry & 255;
443
+ var ig = (this.map_entry >> 8) & 255;
444
+ var ib = (this.map_entry >> 16) & 255;
445
+ var ia = this.map_prefix;
446
+ var r = ir / 255;
447
+ var g = ig / 255;
448
+ var b = ib / 255;
449
+ var a = ia / 255;
450
+ var key = (ir + (ig << 8) + (ib << 16) + (ia << 24)) >>> 0; // need unsigned right shift to convert to positive #
451
+
452
+ this.map[key] = {
453
+ color: [r, g, b, a],
454
+ };
455
+ this.map_size++;
456
+
457
+ // Initialize tile-specific tracking info
458
+ if (!this.tiles[tile.key]) {
459
+ this.tiles[tile.key] = {
460
+ entries: [], // set of feature entries in this thread
461
+ tile: { // subset of tile properties to pass back with feature
462
+ key: tile.key,
463
+ coords: tile.coords,
464
+ style_z: tile.style_z,
465
+ source: tile.source,
466
+ generation: tile.generation
467
+ }
468
+ };
469
+ }
470
+
471
+ this.tiles[tile.key].entries.push(key);
472
+
473
+ return this.map[key];
474
+ }
475
+
476
+ /** Attach feature/source metadata to a newly allocated selection entry. */
477
+ static makeColor(feature: SelectionSourceFeature, tile: SelectionTile, context: SelectionContext): SelectionEntry['color'] {
478
+ var selector = this.makeEntry(tile);
479
+ selector.feature = {
480
+ id: feature.id,
481
+ properties: feature.properties,
482
+ source_name: context.source,
483
+ source_layer: context.layer,
484
+ layers: context.layers,
485
+ tile: this.tiles[tile.key].tile
486
+ };
487
+
488
+ return selector.color;
489
+ }
490
+
491
+ /** Clear named sources, or all entries when no source list is supplied. */
492
+ static reset(sources?: string[] | null): void {
493
+ // Clear specific sources
494
+ if (Array.isArray(sources)) {
495
+ sources.forEach(source => this.clearSource(source));
496
+ }
497
+ // Clear all sources
498
+ else {
499
+ this.tiles = {};
500
+ this.map = {};
501
+ this.map_size = 0;
502
+ this.map_entry = 0;
503
+ }
504
+ }
505
+
506
+ /** Release every selection entry owned by the named source. */
507
+ static clearSource(source: string): void {
508
+ for (let key in this.tiles) {
509
+ if (this.tiles[key].tile.source === source) {
510
+ this.clearTile(key);
511
+ }
512
+ }
513
+ }
514
+
515
+ /** Release every selection key owned by one tile build. */
516
+ static clearTile(key: string): void {
517
+ if (this.tiles[key]) {
518
+ this.tiles[key].entries.forEach(k => delete this.map[k]);
519
+ this.map_size -= this.tiles[key].entries.length;
520
+ delete this.tiles[key];
521
+ }
522
+ }
523
+
524
+ /** Return the number of live worker-local feature entries. */
525
+ static getMapSize(): number {
526
+ return this.map_size;
527
+ }
528
+
529
+ /** Set the worker identifier used in future selection colors. */
530
+ static setPrefix(prefix: number): void {
531
+ this.map_prefix = prefix;
532
+ }
533
+
534
+ }
535
+
536
+ // Static properties
537
+ FeatureSelection.map = {}; // this will be unique per module instance (so unique per worker)
538
+ FeatureSelection.tiles = {}; // selection keys, by tile
539
+ FeatureSelection.map_size = 0;
540
+ FeatureSelection.map_entry = 0;
541
+ FeatureSelection.map_prefix = 0; // set by worker to worker id #
542
+ FeatureSelection.defaultColor = [0, 0, 0, 1];
@@ -0,0 +1,18 @@
1
+ // Tangram
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) 2013-2016 Brett Camper and Mapzen
4
+
5
+ // Fragment shader for feature selection passes
6
+ // Renders in silhouette according to selection (picking) color, or black if none defined
7
+
8
+ #ifdef TANGRAM_FEATURE_SELECTION
9
+ varying vec4 v_selection_color;
10
+ #endif
11
+
12
+ void main (void) {
13
+ #ifdef TANGRAM_FEATURE_SELECTION
14
+ gl_FragColor = v_selection_color;
15
+ #else
16
+ gl_FragColor = vec4(0., 0., 0., 1.);
17
+ #endif
18
+ }
@@ -0,0 +1,9 @@
1
+ // Tangram
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) 2013-2016 Brett Camper and Mapzen
4
+
5
+ // Vertex attribute + varying for feature selection
6
+ #if defined(TANGRAM_FEATURE_SELECTION) && defined(TANGRAM_VERTEX_SHADER)
7
+ attribute vec4 a_selection_color;
8
+ varying vec4 v_selection_color;
9
+ #endif
@@ -0,0 +1,59 @@
1
+ // Tangram
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) 2013-2016 Brett Camper and Mapzen
4
+ // Copyright (c) 2026 vis.gl contributors
5
+
6
+ import type {Device, Texture} from '@luma.gl/core';
7
+ import {readDevicePixels} from '../gpu/pixel_readback';
8
+
9
+ /** Read a selection rectangle with transparent padding where it crosses texture edges. */
10
+ export async function readSelectionPixels(device: Device, texture: Texture,
11
+ point: {x: number; y: number}, radius?: {x: number; y: number} | null): Promise<{pixels: Uint8Array; width: number; height: number}> {
12
+ if (![point.x, point.y, radius?.x ?? 0, radius?.y ?? 0].every(Number.isFinite) ||
13
+ (radius && (radius.x < 0 || radius.y < 0))) {
14
+ throw new Error('Selection point and radius must be finite, with a non-negative radius');
15
+ }
16
+ const maximum = Math.min(texture.width, texture.height);
17
+ const width = radius ? Math.min(Math.ceil(radius.x * 2 * texture.width), maximum) : 1;
18
+ const height = radius ? Math.min(Math.ceil(radius.y * 2 * texture.height), maximum) : 1;
19
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width < 0 || height < 0) {
20
+ throw new Error('Selection radius must produce finite non-negative pixel dimensions');
21
+ }
22
+ const pixels = new Uint8Array(width * height * 4);
23
+ // CSS points are top-origin; native readback retains each backend's row order.
24
+ const x = Math.round((point.x - (radius?.x || 0)) * texture.width);
25
+ const y = Math.round(((device.type === 'webgpu' ? point.y : 1 - point.y) - (radius?.y || 0)) * texture.height);
26
+ const sourceX = Math.max(0, x), sourceY = Math.max(0, y);
27
+ const clippedWidth = Math.min(texture.width, x + width) - sourceX;
28
+ const clippedHeight = Math.min(texture.height, y + height) - sourceY;
29
+ if (clippedWidth > 0 && clippedHeight > 0) {
30
+ const data = await readDevicePixels(device, texture, {x: sourceX, y: sourceY, width: clippedWidth, height: clippedHeight});
31
+ for (let row = 0; row < clippedHeight; row++) {
32
+ pixels.set(data.subarray(row * clippedWidth * 4, (row + 1) * clippedWidth * 4),
33
+ ((sourceY - y + row) * width + sourceX - x) * 4);
34
+ }
35
+ }
36
+ return {pixels, width, height};
37
+ }
38
+
39
+ /** Decode the existing RGB feature/alpha worker key, preferring the center then the nearest hit. */
40
+ export function findSelectionKey(pixels: Uint8Array, width: number, height: number): {key: number; workerId: number} | null {
41
+ if (width <= 0 || height <= 0) return null;
42
+ const center = (Math.min(height - 1, Math.round(height / 2)) * width + Math.min(width - 1, Math.round(width / 2))) * 4;
43
+ const decode = (offset: number) => {
44
+ const feature = pixels[offset] + (pixels[offset + 1] << 8) + (pixels[offset + 2] << 16);
45
+ return feature > 0 ? {key: (feature + (pixels[offset + 3] << 24)) >>> 0, workerId: pixels[offset + 3]} : null;
46
+ };
47
+ const centerHit = decode(center);
48
+ if (centerHit) return centerHit;
49
+ let nearest = null;
50
+ let minimumDistance = Infinity;
51
+ for (let y = 0; y < height; y++) {
52
+ for (let x = 0; x < width; x++) {
53
+ const hit = decode((y * width + x) * 4);
54
+ const distance = (x - width / 2) ** 2 + (y - height / 2) ** 2;
55
+ if (hit && distance <= minimumDistance) { nearest = hit; minimumDistance = distance; }
56
+ }
57
+ }
58
+ return nearest;
59
+ }
@@ -0,0 +1,15 @@
1
+ // Tangram
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) 2013-2016 Brett Camper and Mapzen
4
+
5
+ // Selection pass-specific rendering
6
+ #if defined(TANGRAM_FEATURE_SELECTION) && defined(TANGRAM_VERTEX_SHADER)
7
+ if (a_selection_color.rgb == vec3(0.)) {
8
+ // Discard by forcing invalid triangle if we're in the feature
9
+ // selection pass but have no selection info
10
+ // TODO: in some cases we may actually want non-selectable features to occlude selectable ones?
11
+ gl_Position = vec4(0., 0., 0., 1.);
12
+ return;
13
+ }
14
+ v_selection_color = a_selection_color;
15
+ #endif