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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/README.md +60 -2
  2. package/dist/core.js +36923 -0
  3. package/dist/core.js.map +6 -0
  4. package/dist/index.js +32 -0
  5. package/dist/loaders-gl-worker.js +82 -0
  6. package/dist/loaders-gl-worker.js.map +6 -0
  7. package/dist/map-logic.js +2 -0
  8. package/dist/map-logic.js.map +6 -0
  9. package/dist/projected-basemaps-worker.js +3 -0
  10. package/dist/projected-basemaps-worker.js.map +6 -0
  11. package/dist/shader-modules.js +141 -0
  12. package/dist/shader-modules.js.map +6 -0
  13. package/dist/style-schema.js +126 -0
  14. package/dist/style-schema.js.map +6 -0
  15. package/dist/tangram-style.schema.json +882 -0
  16. package/dist/tangram.debug.mjs +37139 -0
  17. package/dist/tangram.debug.mjs.map +6 -0
  18. package/dist/tangram.min.mjs +4950 -0
  19. package/dist/types/builders/earcut.d.ts +15 -0
  20. package/dist/types/core.d.ts +29 -0
  21. package/dist/types/gl/constants.d.ts +2 -0
  22. package/dist/types/gl/globe_mesh.d.ts +48 -0
  23. package/dist/types/gl/glsl.d.ts +17 -0
  24. package/dist/types/gl/vertex-constants.d.ts +4 -0
  25. package/dist/types/gl/vertex-types.d.ts +34 -0
  26. package/dist/types/gl/vertex_data.d.ts +32 -0
  27. package/dist/types/gl/vertex_elements.d.ts +8 -0
  28. package/dist/types/gl/vertex_layout.d.ts +32 -0
  29. package/dist/types/glsl.d.ts +14 -0
  30. package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
  31. package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
  32. package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
  33. package/dist/types/index.d.ts +155 -0
  34. package/dist/types/lights/light-definitions.d.ts +121 -0
  35. package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
  36. package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
  37. package/dist/types/map-logic/globe-terrain.d.ts +54 -0
  38. package/dist/types/map-logic/index.d.ts +33 -0
  39. package/dist/types/map-logic/label-identity.d.ts +22 -0
  40. package/dist/types/map-logic/label-placement.d.ts +32 -0
  41. package/dist/types/map-logic/screen-bounds.d.ts +15 -0
  42. package/dist/types/map-logic/terrain-data.d.ts +55 -0
  43. package/dist/types/map-logic/terrain-error.d.ts +51 -0
  44. package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
  45. package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
  46. package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
  47. package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
  48. package/dist/types/map-logic/terrain-query.d.ts +33 -0
  49. package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
  50. package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
  51. package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
  52. package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
  53. package/dist/types/map-logic/tile-residency.d.ts +17 -0
  54. package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
  55. package/dist/types/procedures/js-yaml.d.ts +15 -0
  56. package/dist/types/procedures/mesh-projector.d.ts +107 -0
  57. package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
  58. package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
  59. package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
  60. package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
  61. package/dist/types/procedures/terrain-draping.d.ts +165 -0
  62. package/dist/types/procedures/terrain-globe.d.ts +6 -0
  63. package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
  64. package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
  65. package/dist/types/procedures/web-mercator-math.d.ts +4 -0
  66. package/dist/types/scene/globals.d.ts +11 -0
  67. package/dist/types/scene/ground_footprint.d.ts +16 -0
  68. package/dist/types/scene/host_frame.d.ts +54 -0
  69. package/dist/types/scene/projection_math.d.ts +52 -0
  70. package/dist/types/scene/scene-resource-types.d.ts +177 -0
  71. package/dist/types/scene/scene_bundle.d.ts +69 -0
  72. package/dist/types/scene/visibility_adapter.d.ts +106 -0
  73. package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
  74. package/dist/types/selection/terrain_surface.d.ts +149 -0
  75. package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
  76. package/dist/types/shader-modules/height-decode.d.ts +13 -0
  77. package/dist/types/shader-modules/hillshade.d.ts +13 -0
  78. package/dist/types/shader-modules/index.d.ts +6 -0
  79. package/dist/types/shader-modules/planar.d.ts +12 -0
  80. package/dist/types/shader-modules/sphere-map.d.ts +13 -0
  81. package/dist/types/shader-modules/triplanar.d.ts +15 -0
  82. package/dist/types/sources/decoded_tile_store.d.ts +95 -0
  83. package/dist/types/sources/tile_source_metadata.d.ts +32 -0
  84. package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
  85. package/dist/types/tile/tile_id.d.ts +37 -0
  86. package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
  87. package/dist/types/types.d.ts +293 -0
  88. package/dist/types/utils/geo.d.ts +57 -0
  89. package/dist/types/utils/hash.d.ts +1 -0
  90. package/dist/types/utils/log.d.ts +27 -0
  91. package/dist/types/utils/props.d.ts +6 -0
  92. package/dist/types/utils/subscribe.d.ts +10 -0
  93. package/dist/types/utils/thread.d.ts +6 -0
  94. package/dist/types/utils/urls.d.ts +26 -0
  95. package/dist/types/utils/utils.d.ts +26 -0
  96. package/dist/types/utils/version.d.ts +2 -0
  97. package/dist/types/utils/worker-types.d.ts +68 -0
  98. package/dist/types/utils/worker_broker.d.ts +3 -0
  99. package/package.json +103 -3
  100. package/src/builders/common.ts +41 -0
  101. package/src/builders/earcut.d.ts +15 -0
  102. package/src/builders/geometry-types.ts +60 -0
  103. package/src/builders/points.ts +115 -0
  104. package/src/builders/polygons.ts +225 -0
  105. package/src/builders/polylines.ts +682 -0
  106. package/src/builders/wireframe.ts +31 -0
  107. package/src/core.d.ts +29 -0
  108. package/src/core.ts +33 -0
  109. package/src/experimental/loaders-gl-worker.ts +19 -0
  110. package/src/experimental/projected-basemaps-worker.ts +13 -0
  111. package/src/experimental/projected-mesh-cache.ts +137 -0
  112. package/src/experimental/projected-mesh-domain.ts +110 -0
  113. package/src/experimental/projected-mesh-refinement.ts +193 -0
  114. package/src/experimental/projected-mesh.ts +399 -0
  115. package/src/experimental/projected-normal.ts +26 -0
  116. package/src/experimental/projected-symbol-mesh.ts +74 -0
  117. package/src/gl/constants.ts +18 -0
  118. package/src/gl/context.ts +103 -0
  119. package/src/gl/extensions.ts +30 -0
  120. package/src/gl/globe_mesh.ts +285 -0
  121. package/src/gl/glsl.ts +257 -0
  122. package/src/gl/mesh-types.ts +56 -0
  123. package/src/gl/render_state.ts +111 -0
  124. package/src/gl/shader_program.ts +1310 -0
  125. package/src/gl/texture.ts +814 -0
  126. package/src/gl/uniform_buffer.ts +340 -0
  127. package/src/gl/vao.ts +154 -0
  128. package/src/gl/vbo_mesh.ts +349 -0
  129. package/src/gl/vertex-constants.ts +19 -0
  130. package/src/gl/vertex-types.ts +35 -0
  131. package/src/gl/vertex_data.ts +126 -0
  132. package/src/gl/vertex_elements.ts +54 -0
  133. package/src/gl/vertex_layout.ts +241 -0
  134. package/src/glsl.d.ts +14 -0
  135. package/src/gpu/luma_device_renderer.ts +579 -0
  136. package/src/gpu/pixel_readback.ts +51 -0
  137. package/src/gpu/resource_lifecycle.ts +32 -0
  138. package/src/gpu/tangram_gpu_backend.ts +195 -0
  139. package/src/index.d.ts +155 -0
  140. package/src/index.ts +105 -0
  141. package/src/labels/collision-types.ts +94 -0
  142. package/src/labels/collision.ts +273 -0
  143. package/src/labels/collision_grid.ts +61 -0
  144. package/src/labels/intersect.ts +30 -0
  145. package/src/labels/label-types.ts +91 -0
  146. package/src/labels/label.ts +207 -0
  147. package/src/labels/label_line.ts +801 -0
  148. package/src/labels/label_point.ts +131 -0
  149. package/src/labels/main-pass-types.ts +73 -0
  150. package/src/labels/main_pass.ts +221 -0
  151. package/src/labels/point_anchor.ts +100 -0
  152. package/src/labels/point_placement.ts +150 -0
  153. package/src/labels/projected-pass.ts +259 -0
  154. package/src/labels/repeat_group.ts +72 -0
  155. package/src/lights/ambient_light.glsl +18 -0
  156. package/src/lights/directional_light.glsl +39 -0
  157. package/src/lights/geographic-lights.ts +21 -0
  158. package/src/lights/light-definitions.ts +285 -0
  159. package/src/lights/light.ts +590 -0
  160. package/src/lights/lighting-uniforms.ts +80 -0
  161. package/src/lights/lighting-wgsl.ts +116 -0
  162. package/src/lights/material.glsl +237 -0
  163. package/src/lights/material.ts +145 -0
  164. package/src/lights/native-falloff.ts +37 -0
  165. package/src/lights/point_light.glsl +96 -0
  166. package/src/lights/spot_light.glsl +111 -0
  167. package/src/map-logic/draped-terrain-bounds.ts +71 -0
  168. package/src/map-logic/globe-terrain-preparation.ts +217 -0
  169. package/src/map-logic/globe-terrain.ts +296 -0
  170. package/src/map-logic/index.ts +37 -0
  171. package/src/map-logic/label-identity.ts +36 -0
  172. package/src/map-logic/label-placement.ts +117 -0
  173. package/src/map-logic/screen-bounds.ts +34 -0
  174. package/src/map-logic/terrain-data.ts +107 -0
  175. package/src/map-logic/terrain-error.ts +134 -0
  176. package/src/map-logic/terrain-ground-topology.ts +164 -0
  177. package/src/map-logic/terrain-height-field.ts +254 -0
  178. package/src/map-logic/terrain-navigation.ts +50 -0
  179. package/src/map-logic/terrain-neighborhood.ts +43 -0
  180. package/src/map-logic/terrain-query.ts +51 -0
  181. package/src/map-logic/terrain-resource-budget.ts +124 -0
  182. package/src/map-logic/terrain-transition-topology.ts +164 -0
  183. package/src/map-logic/tile-build-queue.ts +110 -0
  184. package/src/map-logic/tile-cache-policy.ts +89 -0
  185. package/src/map-logic/tile-residency.ts +50 -0
  186. package/src/map-logic/triangle-query-index.ts +265 -0
  187. package/src/procedures/js-yaml.d.ts +15 -0
  188. package/src/procedures/mesh-projector.ts +191 -0
  189. package/src/procedures/mlt-loaders.ts +55 -0
  190. package/src/procedures/mvt-dependencies.d.ts +20 -0
  191. package/src/procedures/mvt-legacy.ts +150 -0
  192. package/src/procedures/mvt-loaders.ts +53 -0
  193. package/src/procedures/mvt-parser.ts +48 -0
  194. package/src/procedures/mvt-properties.ts +36 -0
  195. package/src/procedures/mvt-tile-provider.ts +72 -0
  196. package/src/procedures/pmtiles-loader.ts +75 -0
  197. package/src/procedures/projected-coordinate-transform.ts +79 -0
  198. package/src/procedures/projection-batch-executor.ts +140 -0
  199. package/src/procedures/scene-yaml-legacy.ts +16 -0
  200. package/src/procedures/scene-yaml-loaders.ts +10 -0
  201. package/src/procedures/terrain-draping.ts +713 -0
  202. package/src/procedures/terrain-globe.ts +32 -0
  203. package/src/procedures/terrain-styled-topology.ts +184 -0
  204. package/src/procedures/web-mercator-legacy.ts +24 -0
  205. package/src/procedures/web-mercator-math.ts +50 -0
  206. package/src/scene/camera.ts +298 -0
  207. package/src/scene/camera_base.ts +116 -0
  208. package/src/scene/classic_scene.ts +20 -0
  209. package/src/scene/external_camera.ts +101 -0
  210. package/src/scene/globals.ts +120 -0
  211. package/src/scene/ground_footprint.ts +139 -0
  212. package/src/scene/host_frame.ts +326 -0
  213. package/src/scene/projected_tile_lod.ts +125 -0
  214. package/src/scene/projection_math.ts +142 -0
  215. package/src/scene/projection_shaders.ts +183 -0
  216. package/src/scene/renderer.ts +516 -0
  217. package/src/scene/scene-resource-types.ts +174 -0
  218. package/src/scene/scene.ts +2378 -0
  219. package/src/scene/scene_bundle.ts +304 -0
  220. package/src/scene/scene_debug.ts +129 -0
  221. package/src/scene/scene_loader.ts +402 -0
  222. package/src/scene/scene_worker.ts +528 -0
  223. package/src/scene/view.ts +683 -0
  224. package/src/scene/visibility_adapter.ts +319 -0
  225. package/src/scene/worker_url.ts +15 -0
  226. package/src/selection/draped-terrain-surface.ts +377 -0
  227. package/src/selection/selection-types.ts +79 -0
  228. package/src/selection/selection.ts +542 -0
  229. package/src/selection/selection_fragment.glsl +18 -0
  230. package/src/selection/selection_globals.glsl +9 -0
  231. package/src/selection/selection_pixels.ts +59 -0
  232. package/src/selection/selection_vertex.glsl +15 -0
  233. package/src/selection/terrain_surface.ts +298 -0
  234. package/src/shader-modules/globe-horizon.ts +52 -0
  235. package/src/shader-modules/height-decode.ts +41 -0
  236. package/src/shader-modules/hillshade.ts +70 -0
  237. package/src/shader-modules/index.ts +10 -0
  238. package/src/shader-modules/planar.ts +24 -0
  239. package/src/shader-modules/sphere-map.ts +38 -0
  240. package/src/shader-modules/triplanar.ts +45 -0
  241. package/src/sources/aligned_tile_source.ts +122 -0
  242. package/src/sources/data_source.ts +655 -0
  243. package/src/sources/decoded_tile_store.ts +245 -0
  244. package/src/sources/geojson.ts +300 -0
  245. package/src/sources/mvt.ts +247 -0
  246. package/src/sources/raster.ts +341 -0
  247. package/src/sources/shared_tile_source_adapter.ts +117 -0
  248. package/src/sources/sources.ts +10 -0
  249. package/src/sources/tile_source_adapter.ts +84 -0
  250. package/src/sources/tile_source_metadata.ts +65 -0
  251. package/src/sources/tile_source_state.ts +112 -0
  252. package/src/sources/topojson.ts +90 -0
  253. package/src/styles/feature_annotations.ts +16 -0
  254. package/src/styles/filter.ts +190 -0
  255. package/src/styles/geometry-style-types.ts +209 -0
  256. package/src/styles/globe_visibility_wgsl.ts +22 -0
  257. package/src/styles/layer-types.ts +45 -0
  258. package/src/styles/layer.ts +586 -0
  259. package/src/styles/lines/dasharray.ts +49 -0
  260. package/src/styles/lines/lines.ts +707 -0
  261. package/src/styles/lines/lines_wgsl.ts +224 -0
  262. package/src/styles/points/point-runtime-types.ts +125 -0
  263. package/src/styles/points/points.ts +1079 -0
  264. package/src/styles/points/points_fragment.glsl +126 -0
  265. package/src/styles/points/points_vertex.glsl +230 -0
  266. package/src/styles/points/points_wgsl.ts +189 -0
  267. package/src/styles/polygons/polygons.ts +268 -0
  268. package/src/styles/polygons/polygons_fragment.glsl +173 -0
  269. package/src/styles/polygons/polygons_vertex.glsl +213 -0
  270. package/src/styles/polygons/polygons_wgsl.ts +127 -0
  271. package/src/styles/property-types.ts +125 -0
  272. package/src/styles/raster/raster.ts +33 -0
  273. package/src/styles/raster/raster_globals.glsl +46 -0
  274. package/src/styles/style-mixing-types.ts +101 -0
  275. package/src/styles/style-runtime-types.ts +220 -0
  276. package/src/styles/style-schema.ts +133 -0
  277. package/src/styles/style.ts +1033 -0
  278. package/src/styles/style_globals.glsl +69 -0
  279. package/src/styles/style_manager.ts +464 -0
  280. package/src/styles/style_parser.ts +620 -0
  281. package/src/styles/text/font_manager.ts +180 -0
  282. package/src/styles/text/fontfaceobserver.d.ts +10 -0
  283. package/src/styles/text/text-types.ts +220 -0
  284. package/src/styles/text/text.ts +378 -0
  285. package/src/styles/text/text_canvas.ts +773 -0
  286. package/src/styles/text/text_labels.ts +436 -0
  287. package/src/styles/text/text_segments.ts +142 -0
  288. package/src/styles/text/text_settings.ts +228 -0
  289. package/src/styles/text/text_wgsl.ts +159 -0
  290. package/src/styles/text/text_wrap.ts +194 -0
  291. package/src/tile/globe_tile_preload.ts +40 -0
  292. package/src/tile/tangram_tileset_2d.ts +108 -0
  293. package/src/tile/tile-build-types.ts +155 -0
  294. package/src/tile/tile.ts +807 -0
  295. package/src/tile/tile_id.ts +172 -0
  296. package/src/tile/tile_manager.ts +889 -0
  297. package/src/tile/tile_pyramid.ts +119 -0
  298. package/src/tile/tile_resource_cache.ts +91 -0
  299. package/src/tile/tile_traversal_adapter.ts +132 -0
  300. package/src/types.ts +332 -0
  301. package/src/utils/debounce.ts +17 -0
  302. package/src/utils/debug_settings.ts +43 -0
  303. package/src/utils/errors.ts +12 -0
  304. package/src/utils/functions.ts +95 -0
  305. package/src/utils/geo.ts +364 -0
  306. package/src/utils/hash.ts +14 -0
  307. package/src/utils/log.ts +108 -0
  308. package/src/utils/media_capture.ts +193 -0
  309. package/src/utils/merge.ts +44 -0
  310. package/src/utils/obb.ts +134 -0
  311. package/src/utils/props.ts +43 -0
  312. package/src/utils/slice.ts +14 -0
  313. package/src/utils/subscribe.ts +58 -0
  314. package/src/utils/task.ts +154 -0
  315. package/src/utils/thread.ts +26 -0
  316. package/src/utils/urls.ts +223 -0
  317. package/src/utils/utils.ts +310 -0
  318. package/src/utils/vector.ts +197 -0
  319. package/src/utils/version.ts +8 -0
  320. package/src/utils/worker-types.ts +79 -0
  321. package/src/utils/worker_broker.ts +523 -0
@@ -0,0 +1,131 @@
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 Label from './label';
7
+ import type {CollisionBounds, CollisionLabel} from './collision-types';
8
+ import type {LabelLayout, LabelParent} from './label-types';
9
+ import PointAnchor from './point_anchor';
10
+ import OBB from '../utils/obb';
11
+ import StyleParser from '../styles/style_parser';
12
+
13
+ type LabelPointCoordinate = [number, number];
14
+ type LabelPointSize = [number, number];
15
+ type LabelPointLayout = LabelLayout;
16
+ const typedStyleParser = StyleParser as unknown as {zeroPair: readonly number[]};
17
+
18
+ export default class LabelPoint extends Label {
19
+ /** Discriminator used by the checked point/text quad builders. */
20
+ declare type: 'point';
21
+
22
+ position: LabelPointCoordinate;
23
+ angle: number;
24
+ parent: LabelParent | undefined;
25
+ start_anchor_index: number;
26
+ degenerate: boolean;
27
+ throw_away: boolean;
28
+ static PLACEMENT: Record<string, number>;
29
+
30
+ constructor (position: LabelPointCoordinate, size: LabelPointSize, layout: LabelPointLayout, angle = 0) {
31
+ super(size, layout);
32
+ this.type = 'point';
33
+ this.position = [position[0], position[1]];
34
+ this.angle = angle;
35
+ this.parent = this.layout.parent;
36
+ this.update();
37
+
38
+ this.start_anchor_index = 1;
39
+ this.degenerate = !this.size[0] && !this.size[1] && !this.layout.buffer[0] && !this.layout.buffer[1];
40
+ this.throw_away = false;
41
+ }
42
+
43
+ update() {
44
+ super.update();
45
+ this.computeOffset();
46
+ this.updateBBoxes();
47
+ }
48
+
49
+ computeOffset () {
50
+ this.offset = [this.layout.offset[0], this.layout.offset[1]];
51
+
52
+ // Additional anchor/offset for point:
53
+ if (this.parent) {
54
+ let parent = this.parent;
55
+ // point's own anchor, text anchor applied to point, additional point offset
56
+ this.offset = PointAnchor.computeOffset(this.offset, parent.size, parent.anchor, PointAnchor.zero_buffer);
57
+ this.offset = PointAnchor.computeOffset(this.offset, parent.size, this.anchor, PointAnchor.zero_buffer);
58
+ if (parent.offset !== typedStyleParser.zeroPair) { // point has an offset
59
+ if (this.offset === typedStyleParser.zeroPair) { // no text offset, use point's
60
+ this.offset = parent.offset;
61
+ }
62
+ else { // text has offset, add point's
63
+ this.offset[0] += parent.offset[0];
64
+ this.offset[1] += parent.offset[1];
65
+ }
66
+ }
67
+ }
68
+
69
+ this.offset = PointAnchor.computeOffset(this.offset, this.size, this.anchor);
70
+ }
71
+
72
+ updateBBoxes (this: Pick<LabelPoint, 'size' | 'unit_scale' | 'position' | 'offset' | 'angle' | 'obb' | 'aabb' | 'breach' | 'may_repeat_across_tiles'> & {layout: Pick<LabelLayout, 'buffer' | 'italic'>} & Partial<Pick<LabelPoint, 'inTileBounds' | 'mayRepeatAcrossTiles'>>) {
73
+ let width = (this.size[0] + this.layout.buffer[0] * 2) * this.unit_scale * Label.epsilon;
74
+ let height = (this.size[1] + this.layout.buffer[1] * 2) * this.unit_scale * Label.epsilon;
75
+
76
+ // fudge width value as text may overflow bounding box if it has italic, bold, etc style
77
+ if (this.layout.italic) {
78
+ width += 5 * this.unit_scale;
79
+ }
80
+
81
+ // make bounding boxes
82
+ this.obb = new OBB(
83
+ this.position[0] + (this.offset[0] * this.unit_scale),
84
+ this.position[1] - (this.offset[1] * this.unit_scale),
85
+ -this.angle, // angle is negative because tile system y axis is pointing down
86
+ width,
87
+ height
88
+ );
89
+ this.aabb = this.obb.getExtent();
90
+
91
+ if (this.inTileBounds) {
92
+ this.breach = !this.inTileBounds();
93
+ }
94
+
95
+ if (this.mayRepeatAcrossTiles) {
96
+ this.may_repeat_across_tiles = this.mayRepeatAcrossTiles();
97
+ }
98
+ }
99
+
100
+ discard (bboxes: CollisionBounds, exclude: CollisionLabel | null | 0 | '' = null) {
101
+ if (this.degenerate) {
102
+ return false;
103
+ }
104
+
105
+ if (super.discard(bboxes, exclude)) {
106
+ // If more than one anchor specified, try them in order
107
+ if (Array.isArray(this.layout.anchor)) {
108
+ // Start on second anchor (first anchor was set on creation)
109
+ for (let i=this.start_anchor_index; i < this.layout.anchor.length; i++) {
110
+ this.anchor = this.layout.anchor[i];
111
+ this.update();
112
+
113
+ if (!super.discard(bboxes, exclude)) {
114
+ return false;
115
+ }
116
+ }
117
+ }
118
+ return true;
119
+ }
120
+ return false;
121
+ }
122
+
123
+ }
124
+
125
+ // Placement strategies
126
+ LabelPoint.PLACEMENT = {
127
+ VERTEX: 0, // place labels at endpoints of line segments
128
+ MIDPOINT: 1, // place labels at midpoints of line segments
129
+ SPACED: 2, // place labels equally spaced along line
130
+ CENTROID: 3 // place labels at center of polygons
131
+ };
@@ -0,0 +1,73 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {CollisionLabel, CollisionObject} from './collision-types';
6
+ import type {LabelPointCoordinate, SerializedLabel} from './label-types';
7
+ import type OBB from '../utils/obb';
8
+
9
+ /** Serialized label container attached to a worker-built mesh. */
10
+ export interface MeshLabel {
11
+ container: {label: SerializedLabel; linked?: number | string | null};
12
+ /** Byte offset and vertex count for each label geometry range. */
13
+ ranges: [number, number][];
14
+ }
15
+
16
+ /** Mutable GPU-mesh surface consumed by the visibility pass. */
17
+ export interface LabelMesh {
18
+ /** First terrain draw timestamp, shared with color/selection morphing. */
19
+ terrainVisibleAt?: number;
20
+ /** Last submitted height blend, for non-terrain callers and diagnostics. */
21
+ terrainBlend?: number;
22
+ /** Captured worker generation's duration; a pending field must not retime admitted labels. */
23
+ variant?: {terrainTransitionDuration?: number};
24
+ valid: boolean;
25
+ labels?: Record<string, MeshLabel>;
26
+ vertex_layout: {offset?: Record<string, number>; stride: number};
27
+ /** CPU bytes are absent after an unretained mesh upload or destruction. */
28
+ vertex_data?: import('../gl/mesh-types').MeshVertexData;
29
+ upload(): void;
30
+ }
31
+
32
+ /** Current/proxy tile metadata needed for cross-tile collisions. */
33
+ export interface LabelTile {
34
+ coords: {z: number};
35
+ style_z: number;
36
+ build_id: number;
37
+ min: {x: number; y: number};
38
+ span: {x: number};
39
+ meshes: Record<string, LabelMesh[]>;
40
+ pending_label_meshes: Record<string, LabelMesh[]> | null;
41
+ isProxy(): boolean;
42
+ /** Whether a proxy style still draws while a child finishes its label meshes. */
43
+ shouldProxyForStyle?(style: string): boolean;
44
+ swapPendingLabels(): void;
45
+ }
46
+
47
+ /** Main-pass reconstruction: screen-scaled boxes without alternate-anchor fitting. */
48
+ export interface MainPassLabel extends CollisionLabel {
49
+ id: number;
50
+ type: string;
51
+ layout: SerializedLabel['layout'] & {repeat_scale: number};
52
+ build_id: number;
53
+ unit_scale: number;
54
+ size: LabelPointCoordinate;
55
+ offset: LabelPointCoordinate;
56
+ angle: number;
57
+ obb: OBB | null;
58
+ aabb: number[] | null;
59
+ obbs: OBB[];
60
+ aabbs: number[][];
61
+ }
62
+
63
+ /** Resolved cross-tile candidate, including its mesh update ranges. */
64
+ export interface MainPassContainer extends CollisionObject {
65
+ label: MainPassLabel;
66
+ ranges: [number, number][];
67
+ mesh: LabelMesh;
68
+ }
69
+
70
+ /** Temporary worker ID link, resolved before collision submission. */
71
+ export type UnresolvedMainPassContainer = Omit<MainPassContainer, 'linked'> & {
72
+ linked?: number | string | CollisionObject | null;
73
+ };
@@ -0,0 +1,221 @@
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 {CollisionBounds, CollisionLabel} from './collision-types';
7
+ import type {LabelTile, LabelMesh, MainPassLabel, MainPassContainer, UnresolvedMainPassContainer} from './main-pass-types';
8
+ import Label from './label';
9
+ import LabelPoint from './label_point';
10
+ import {LabelLineStraight} from './label_line';
11
+ import Collision from './collision';
12
+ import OBB from '../utils/obb';
13
+ import Geo from '../utils/geo';
14
+
15
+ let visible: Record<string, boolean> = {}; // currently visible labels
16
+ let prev_visible: Record<string, boolean> = {}; // previously visible labels (in last collision run)
17
+
18
+ export default async function mainThreadLabelCollisionPass (tiles: LabelTile[], view_zoom: number, hide_breach = false): Promise<{labels: MainPassContainer[]; containers: MainPassContainer[]}> {
19
+ // Swap/reset visible label set
20
+ prev_visible = visible; // save last visible label set
21
+ visible = {}; // initialize new visible label set
22
+
23
+ // Build label containers from tiles
24
+ let containers = buildLabels(tiles, view_zoom);
25
+
26
+ // Collide all labels in a single group
27
+ // TODO: maybe rename tile and style to group/subgroup?
28
+ Collision.startTile('main', { apply_repeat_groups: true, return_hidden: true });
29
+ Collision.addStyle('main', 'main');
30
+
31
+ // Adaptive collision grid, using a heuristic based on the tile with the most labels
32
+ const max_tile_label_count =
33
+ Math.max(0, ...Object.values(tiles)
34
+ .map(t => Object.values(t.meshes))
35
+ .flat()
36
+ .map(meshes => Math.max(0, ...meshes.map(mesh => mesh.labels ? Object.keys(mesh.labels).length : 0)))
37
+ );
38
+
39
+ const grid_divs = Math.floor(max_tile_label_count / Geo.tile_size); // heuristic of label density to tile size
40
+ if (grid_divs > 0) {
41
+ Collision.initGrid({
42
+ anchor: { x: Math.min(...tiles.map(t => t.min.x)), y: Math.min(...tiles.map(t => t.min.y)) },
43
+ span: tiles[0].span.x / grid_divs
44
+ });
45
+ }
46
+ else {
47
+ Collision.initGrid();
48
+ }
49
+
50
+ const labels = await Collision.collide(containers, 'main', 'main');
51
+
52
+ // Update label visiblity
53
+ let meshes: LabelMesh[] = [];
54
+ labels.forEach(container => {
55
+ // Hide breach labels (those that cross tile boundaries) while tiles are loading, unless they
56
+ // were previously visible (otherwise fully loaded/collided breach labels will flicker in and out
57
+ // when new tiles load, even if they aren't adjacent)
58
+ let show = 0;
59
+ if (container.show === true &&
60
+ (!hide_breach || !container.label.breach || prev_visible[container.label.id])) {
61
+ show = 1;
62
+ }
63
+
64
+ if (show) {
65
+ visible[container.label.id] = true; // track visible labels
66
+ }
67
+
68
+ let changed = true; // check if label visibility changed on this collision pass
69
+
70
+ container.ranges.forEach(r => {
71
+ if (!changed) {
72
+ return; // skip rest of label if state hasn't changed
73
+ }
74
+
75
+ let mesh = container.mesh;
76
+ if (!mesh.valid || !mesh.vertex_data || !mesh.vertex_layout.offset) {
77
+ return;
78
+ }
79
+
80
+ let off = mesh.vertex_layout.offset.a_shape; // byte offset (within each vertex) of attribute
81
+ let stride = mesh.vertex_layout.stride; // byte stride per vertex
82
+
83
+ for (let i=0; i < r[1]; i++) {
84
+ // NB: +6 is because attribute is a short int (2 bytes each), and we're skipping to 3rd element, 6=3*2
85
+ if (mesh.vertex_data[r[0] + i * stride + off + 6] === show) {
86
+ changed = false;
87
+ return; // label hasn't changed states, skip further updates
88
+ }
89
+ mesh.vertex_data[r[0] + i * stride + off + 6] = show;
90
+ }
91
+
92
+ if (meshes.indexOf(mesh) === -1) {
93
+ meshes.push(mesh);
94
+ }
95
+ });
96
+ });
97
+
98
+ // Upload updated meshes and make them visible
99
+ meshes.forEach(mesh => mesh.upload());
100
+ tiles.forEach(t => t.swapPendingLabels());
101
+
102
+ return { labels, containers }; // currently returned for debugging
103
+ }
104
+
105
+ function buildLabels (tiles: LabelTile[], view_zoom: number): MainPassContainer[] {
106
+ const labels: Record<string, MainPassLabel> = {};
107
+ let containers: Record<string, UnresolvedMainPassContainer> | UnresolvedMainPassContainer[] = {};
108
+
109
+ // Collect labels from each tile and turn into new label instances
110
+ tiles.forEach(tile => {
111
+ const units_per_meter = Geo.unitsPerMeter(tile.coords.z); // scale from tile units to mercator meters
112
+ const zoom_scale = Math.pow(2, view_zoom - tile.style_z); // adjust label size by view zoom
113
+ const size_scale = units_per_meter * zoom_scale; // scale from tile units to zoom-adjusted meters
114
+ const meters_per_pixel = Geo.metersPerPixel(view_zoom);
115
+
116
+ // First pass: create label instances and centralize collision containers
117
+ // Combine existing (previously collided) and pending (waiting to be collided for first time) meshes
118
+ const tile_meshes = Object.assign({}, tile.meshes, tile.pending_label_meshes);
119
+ for (let style in tile_meshes) {
120
+ const meshes = tile_meshes[style];
121
+ meshes.forEach(mesh => {
122
+ if (mesh.labels) {
123
+ for (let label_id in mesh.labels) {
124
+ // For proxy tiles, only allow visible labels to be *hidden* by further collisions,
125
+ // don't allow new ones to appear. Promotes label stability and prevents thrash
126
+ // from different labels (often not thematically relevant given the different zoom level of
127
+ // the proxy tile content, e.g. random POIs popping in/out when zooming out to city-wide view).
128
+ if (tile.isProxy() && !prev_visible[label_id]) {
129
+ continue;
130
+ }
131
+
132
+ const params = mesh.labels[label_id].container.label;
133
+ const linked = mesh.labels[label_id].container.linked;
134
+ const ranges = mesh.labels[label_id].ranges;
135
+ // const debug = Object.assign({}, mesh.labels[label_id].debug, { tile, params, label_id });
136
+
137
+ // The worker snapshot and bounds are installed below before collision reads this record.
138
+ let label = labels[label_id] = {} as MainPassLabel;
139
+ label.discard = discard.bind(label);
140
+ label.build_id = tile.build_id; // original order in which tiles were built
141
+
142
+ Object.assign(label, params);
143
+ label.layout = Object.assign({}, params.layout) as MainPassLabel['layout']; // repeat_scale is installed below
144
+ label.layout.repeat_scale = 0.75; // looser second pass on repeat groups, to weed out repeats near tile edges
145
+ label.layout.repeat_distance = label.layout.repeat_distance || 0;
146
+ label.layout.repeat_distance /= size_scale; // TODO: where should this be scaled?
147
+ label.position = [ // don't overwrite referenced values
148
+ label.position[0] / units_per_meter + tile.min.x,
149
+ label.position[1] / units_per_meter + tile.min.y
150
+ ];
151
+ label.unit_scale = meters_per_pixel;
152
+
153
+ if (label.type === 'point') { // TODO: move to integer constants to avoid excess string copies
154
+ LabelPoint.prototype.updateBBoxes.call(label);
155
+ }
156
+ else if (label.type === 'straight') {
157
+ LabelLineStraight.prototype.updateBBoxes.call(label, label.position, label.size, label.angle, label.angle, label.offset);
158
+ }
159
+ else if (params.obbs) {
160
+ // NB: this is a very rough approximation of curved label collision at intermediate zooms,
161
+ // because the position/scale of each collision box isn't correctly updated; however,
162
+ // it's good enough to provide some additional label coverage, with less overhead
163
+ const obbs = params.obbs.map(o => {
164
+ let { x, y, a, w, h } = o;
165
+ x = x / units_per_meter + tile.min.x;
166
+ y = y / units_per_meter + tile.min.y;
167
+ w /= size_scale;
168
+ h /= size_scale;
169
+ return new OBB(x, y, a, w, h);
170
+ });
171
+ label.obbs = obbs;
172
+ label.aabbs = obbs.map(o => o.getExtent());
173
+ }
174
+
175
+ (containers as Record<string, UnresolvedMainPassContainer>)[label_id] = {
176
+ label,
177
+ linked,
178
+ ranges,
179
+ mesh,
180
+ // debug
181
+ };
182
+ }
183
+ }
184
+ });
185
+ }
186
+ });
187
+
188
+ // Resolve links between label containers
189
+ for (let c in containers) {
190
+ const container = containers[c];
191
+ if (container.linked) {
192
+ container.linked = containers[container.linked as string] as MainPassContainer;
193
+ }
194
+ // NB: if linked label not found, it was discarded in initial tile collision pass
195
+ }
196
+
197
+ // Convert container map to array
198
+ containers = Object.keys(containers).map(k => (containers as Record<string, UnresolvedMainPassContainer>)[k]);
199
+ return containers as MainPassContainer[];
200
+ }
201
+
202
+ // Generic discard function for labels, does simple occlusion with one or more bounding boxes
203
+ // (no additional logic to try alternate anchors or other layout options, etc.)
204
+ function discard (this: MainPassLabel, bboxes: CollisionBounds, exclude: CollisionLabel | null | 0 | '' = null): boolean {
205
+ if (this.obb) { // single collision box
206
+ return Label.prototype.occluded.call(this, bboxes, exclude);
207
+ }
208
+ else if (this.obbs) { // mutliple collision boxes
209
+ for (let i = 0; i < this.obbs.length; i++){
210
+ let aabb = this.aabbs[i];
211
+ let obb = this.obbs[i];
212
+ let obj = { aabb, obb };
213
+
214
+ let should_discard = Label.prototype.occluded.call(obj, bboxes, exclude);
215
+ if (should_discard) {
216
+ return true;
217
+ }
218
+ }
219
+ }
220
+ return false;
221
+ }
@@ -0,0 +1,100 @@
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
+ // Sets of values to match for directional and corner anchors
7
+ const lefts = ['left', 'top-left', 'bottom-left'];
8
+ const rights = ['right', 'top-right', 'bottom-right'];
9
+ const tops = ['top', 'top-left', 'top-right'];
10
+ const bottoms = ['bottom', 'bottom-left', 'bottom-right'];
11
+
12
+ type AnchorName = string | string[] | undefined;
13
+ type Point = [number, number];
14
+ type Buffer = [number, number, number, number];
15
+ interface PointAnchorNamespace {
16
+ computeOffset(offset: Point, size: Point, anchor: AnchorName, buffer?: Buffer | null): Point;
17
+ alignForAnchor(anchor: AnchorName): string;
18
+ isLeftAnchor(anchor: AnchorName): boolean;
19
+ isRightAnchor(anchor: AnchorName): boolean;
20
+ isTopAnchor(anchor: AnchorName): boolean;
21
+ isBottomAnchor(anchor: AnchorName): boolean;
22
+ default_buffer: Buffer;
23
+ zero_buffer: Buffer;
24
+ }
25
+ const PointAnchor = {
26
+
27
+ computeOffset (offset: Point, size: Point, anchor: AnchorName, buffer: Buffer | null = null): Point {
28
+ if (!anchor || anchor === 'center') {
29
+ return offset;
30
+ }
31
+
32
+ const offset2: Point = [offset[0], offset[1]];
33
+ buffer = buffer || this.default_buffer;
34
+
35
+ // An optional left/right offset
36
+ if (this.isLeftAnchor(anchor)) {
37
+ offset2[0] -= size[0] / 2;
38
+ if (anchor === 'left') {
39
+ offset2[0] -= buffer[0];
40
+ }
41
+ }
42
+ else if (this.isRightAnchor(anchor)) {
43
+ offset2[0] += size[0] / 2;
44
+ if (anchor === 'right') {
45
+ offset2[0] += buffer[1];
46
+ }
47
+ }
48
+
49
+ // An optional top/bottom offset
50
+ if (this.isTopAnchor(anchor)) {
51
+ offset2[1] -= size[1] / 2;
52
+ if (anchor === 'top') {
53
+ offset2[1] -= buffer[2];
54
+ }
55
+ }
56
+ else if (this.isBottomAnchor(anchor)) {
57
+ offset2[1] += size[1] / 2;
58
+ if (anchor === 'bottom') {
59
+ offset2[1] += buffer[3];
60
+ }
61
+ }
62
+
63
+ return offset2;
64
+ },
65
+
66
+ alignForAnchor (anchor: AnchorName): string {
67
+ if (anchor && anchor !== 'center') {
68
+ if (this.isLeftAnchor(anchor)) {
69
+ return 'right';
70
+ }
71
+ else if (this.isRightAnchor(anchor)) {
72
+ return 'left';
73
+ }
74
+ }
75
+ return 'center';
76
+ },
77
+
78
+ isLeftAnchor (anchor: AnchorName): boolean {
79
+ return (lefts.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
80
+ },
81
+
82
+ isRightAnchor (anchor: AnchorName): boolean {
83
+ return (rights.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
84
+ },
85
+
86
+ isTopAnchor (anchor: AnchorName): boolean {
87
+ return (tops.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
88
+ },
89
+
90
+ isBottomAnchor (anchor: AnchorName): boolean {
91
+ return (bottoms.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
92
+ },
93
+
94
+ // Buffers: [left, right, top, bottom]
95
+ default_buffer: [2.5, 2.5, 1.5, 0.75],
96
+ zero_buffer: [0, 0, 0, 0]
97
+
98
+ } as PointAnchorNamespace;
99
+
100
+ export default PointAnchor;
@@ -0,0 +1,150 @@
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
+ // Logic for placing point labels along a line geometry
7
+
8
+ import LabelPoint from './label_point';
9
+ import type {LabelLayout} from './label-types';
10
+ import {isCoordOutsideTile} from '../builders/common';
11
+
12
+ const PLACEMENT = LabelPoint.PLACEMENT;
13
+ const default_spacing = 80; // spacing of points along line in pixels
14
+ type Point = [number, number];
15
+ type PlacementLayout = LabelLayout;
16
+ type PlacementResult = {positions: Point[]; angles: number[]};
17
+
18
+ export default function placePointsOnLine (line: Point[], size: Point, layout: PlacementLayout): LabelPoint[] {
19
+ const labels: LabelPoint[] = [];
20
+ const strategy = layout.placement;
21
+ const min_length = Math.max(size[0], size[1]) * Number(layout.placement_min_length_ratio) * layout.units_per_pixel;
22
+
23
+ if (strategy === PLACEMENT.SPACED) {
24
+ let result = getPositionsAndAngles(line, min_length, layout);
25
+ // false will be returned if line have no length
26
+ if (!result) {
27
+ return [];
28
+ }
29
+
30
+ let positions = result.positions;
31
+ let angles = result.angles;
32
+ for (let i = 0; i < positions.length; i++) {
33
+ let position = positions[i];
34
+ let angle = angles[i];
35
+ if (layout.tile_edges === true || !isCoordOutsideTile(position)) {
36
+ labels.push(new LabelPoint(position, size, layout, angle));
37
+ }
38
+ }
39
+ }
40
+ else if (strategy === PLACEMENT.VERTEX) {
41
+ let p, q;
42
+ for (let i = 0; i < line.length - 1; i++) {
43
+ p = line[i];
44
+ q = line[i + 1];
45
+ if (layout.tile_edges === true || !isCoordOutsideTile(p)) {
46
+ const angle = getAngle(p as Point, q as Point, layout.angle);
47
+ labels.push(new LabelPoint(p as Point, size, layout, angle));
48
+ }
49
+ }
50
+
51
+ // add last endpoint
52
+ const angle = getAngle(p as Point, q as Point, layout.angle);
53
+ labels.push(new LabelPoint(q as Point, size, layout, angle));
54
+ }
55
+ else if (strategy === PLACEMENT.MIDPOINT) {
56
+ for (let i = 0; i < line.length - 1; i++) {
57
+ let p = line[i];
58
+ let q = line[i + 1];
59
+ let position = [
60
+ 0.5 * (p[0] + q[0]),
61
+ 0.5 * (p[1] + q[1])
62
+ ];
63
+ if (layout.tile_edges === true || !isCoordOutsideTile(position)) {
64
+ if (!min_length || norm(p, q) > min_length) {
65
+ const angle = getAngle(p, q, layout.angle);
66
+ labels.push(new LabelPoint(position as Point, size, layout, angle));
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return labels;
72
+ }
73
+
74
+ function getPositionsAndAngles(line: Point[], min_length: number, layout: PlacementLayout): PlacementResult | false {
75
+ let upp = layout.units_per_pixel;
76
+ let spacing = (layout.placement_spacing || default_spacing) * upp;
77
+
78
+ let length = getLineLength(line);
79
+ if (length <= min_length) {
80
+ return false;
81
+ }
82
+
83
+ let num_labels = Math.max(Math.floor(length / spacing), 1);
84
+ let remainder = length - (num_labels - 1) * spacing;
85
+ let positions: Point[] = [];
86
+ let angles: number[] = [];
87
+
88
+ let distance = 0.5 * remainder;
89
+ for (let i = 0; i < num_labels; i++) {
90
+ let {position, angle} = interpolateLine(line, distance, min_length, layout);
91
+ if (position != null && angle != null) {
92
+ positions.push(position);
93
+ angles.push(angle);
94
+ }
95
+ distance += spacing;
96
+ }
97
+
98
+ return {positions, angles};
99
+ }
100
+
101
+ function getAngle(p: Point, q: Point, angle: number | 'auto' = 0): number {
102
+ return (angle === 'auto') ? Math.atan2(q[0] - p[0], q[1] - p[1]) : angle;
103
+ }
104
+
105
+ function getLineLength(line: Point[]): number {
106
+ let distance = 0;
107
+ for (let i = 0; i < line.length - 1; i++) {
108
+ distance += norm(line[i], line[i+1]);
109
+ }
110
+ return distance;
111
+ }
112
+
113
+ function norm(p: Point, q: Point): number {
114
+ return Math.sqrt(Math.pow(p[0] - q[0], 2) + Math.pow(p[1] - q[1], 2));
115
+ }
116
+
117
+ // TODO: can be optimized.
118
+ // you don't have to start from the first index every time for placement
119
+ function interpolateLine(line: Point[], distance: number, min_length: number, layout: PlacementLayout): {position?: Point; angle?: number} {
120
+ let sum = 0;
121
+ let position: Point | undefined;
122
+ let angle: number | undefined;
123
+ for (let i = 0; i < line.length-1; i++) {
124
+ let p = line[i];
125
+ let q = line[i+1];
126
+
127
+ const length = norm(p, q);
128
+ if (length <= min_length) {
129
+ continue;
130
+ }
131
+
132
+ sum += length;
133
+
134
+ if (sum > distance) {
135
+ position = interpolateSegment(p, q, sum - distance);
136
+ angle = getAngle(p, q, layout.angle);
137
+ break;
138
+ }
139
+ }
140
+ return {position, angle};
141
+ }
142
+
143
+ function interpolateSegment(p: Point, q: Point, distance: number): Point {
144
+ let length = norm(p, q);
145
+ let ratio = distance / length;
146
+ return [
147
+ ratio * p[0] + (1 - ratio) * q[0],
148
+ ratio * p[1] + (1 - ratio) * q[1]
149
+ ];
150
+ }