leaflet-city-mapper 0.0.0-stage → 0.0.2

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 (412) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +78 -2
  3. package/cucumber.mjs +9 -0
  4. package/dist/leaflet-city-mapper.js +2 -0
  5. package/dist/leaflet-city-mapper.js.map +7 -0
  6. package/dist/utils/CELL.js +2 -0
  7. package/dist/utils/CELL.js.map +7 -0
  8. package/dist/utils/addBridges.js +2 -0
  9. package/dist/utils/addBridges.js.map +7 -0
  10. package/dist/utils/addRasterTile.js +2 -0
  11. package/dist/utils/addRasterTile.js.map +7 -0
  12. package/dist/utils/along.js +2 -0
  13. package/dist/utils/along.js.map +7 -0
  14. package/dist/utils/atmosphere.js +2 -0
  15. package/dist/utils/atmosphere.js.map +7 -0
  16. package/dist/utils/attach.js +2 -0
  17. package/dist/utils/attach.js.map +7 -0
  18. package/dist/utils/bindSunlight.js +2 -0
  19. package/dist/utils/bindSunlight.js.map +7 -0
  20. package/dist/utils/box.js +2 -0
  21. package/dist/utils/box.js.map +7 -0
  22. package/dist/utils/bridgeElevation.js +2 -0
  23. package/dist/utils/bridgeElevation.js.map +7 -0
  24. package/dist/utils/build.js +2 -0
  25. package/dist/utils/build.js.map +7 -0
  26. package/dist/utils/cameraControls.js +2 -0
  27. package/dist/utils/cameraControls.js.map +7 -0
  28. package/dist/utils/channel.js +2 -0
  29. package/dist/utils/channel.js.map +7 -0
  30. package/dist/utils/collider.js +2 -0
  31. package/dist/utils/collider.js.map +7 -0
  32. package/dist/utils/colliders.js +2 -0
  33. package/dist/utils/colliders.js.map +7 -0
  34. package/dist/utils/computeBounds.js +2 -0
  35. package/dist/utils/computeBounds.js.map +7 -0
  36. package/dist/utils/conformModelToTerrain.js +2 -0
  37. package/dist/utils/conformModelToTerrain.js.map +7 -0
  38. package/dist/utils/createMesh.js +2 -0
  39. package/dist/utils/createMesh.js.map +7 -0
  40. package/dist/utils/createProgram.js +3 -0
  41. package/dist/utils/createProgram.js.map +7 -0
  42. package/dist/utils/decodeDirectory.js +2 -0
  43. package/dist/utils/decodeDirectory.js.map +7 -0
  44. package/dist/utils/decodeHeader.js +2 -0
  45. package/dist/utils/decodeHeader.js.map +7 -0
  46. package/dist/utils/decodeModel.js +2 -0
  47. package/dist/utils/decodeModel.js.map +7 -0
  48. package/dist/utils/decompress.js +2 -0
  49. package/dist/utils/decompress.js.map +7 -0
  50. package/dist/utils/destroyTexture.js +2 -0
  51. package/dist/utils/destroyTexture.js.map +7 -0
  52. package/dist/utils/disposeMesh.js +2 -0
  53. package/dist/utils/disposeMesh.js.map +7 -0
  54. package/dist/utils/drawGeometry.js +2 -0
  55. package/dist/utils/drawGeometry.js.map +7 -0
  56. package/dist/utils/elevationWGSL.js +24 -0
  57. package/dist/utils/elevationWGSL.js.map +7 -0
  58. package/dist/utils/emptyNode.js +2 -0
  59. package/dist/utils/emptyNode.js.map +7 -0
  60. package/dist/utils/endZoom.js +2 -0
  61. package/dist/utils/endZoom.js.map +7 -0
  62. package/dist/utils/findFeature.js +2 -0
  63. package/dist/utils/findFeature.js.map +7 -0
  64. package/dist/utils/geometry.js +1 -0
  65. package/dist/utils/geometry.js.map +7 -0
  66. package/dist/utils/getElevation.js +2 -0
  67. package/dist/utils/getElevation.js.map +7 -0
  68. package/dist/utils/getHeader.js +2 -0
  69. package/dist/utils/getHeader.js.map +7 -0
  70. package/dist/utils/getZxy.js +2 -0
  71. package/dist/utils/getZxy.js.map +7 -0
  72. package/dist/utils/glass.js +2 -0
  73. package/dist/utils/glass.js.map +7 -0
  74. package/dist/utils/headlight.js +2 -0
  75. package/dist/utils/headlight.js.map +7 -0
  76. package/dist/utils/hit.js +2 -0
  77. package/dist/utils/hit.js.map +7 -0
  78. package/dist/utils/horizontalPosition.js +2 -0
  79. package/dist/utils/horizontalPosition.js.map +7 -0
  80. package/dist/utils/horizontalRay.js +2 -0
  81. package/dist/utils/horizontalRay.js.map +7 -0
  82. package/dist/utils/hull.js +2 -0
  83. package/dist/utils/hull.js.map +7 -0
  84. package/dist/utils/initSunlight.js +2 -0
  85. package/dist/utils/initSunlight.js.map +7 -0
  86. package/dist/utils/initTerrain.js +2 -0
  87. package/dist/utils/initTerrain.js.map +7 -0
  88. package/dist/utils/initTraffic.js +40 -0
  89. package/dist/utils/initTraffic.js.map +7 -0
  90. package/dist/utils/initWalk.js +2 -0
  91. package/dist/utils/initWalk.js.map +7 -0
  92. package/dist/utils/intersectMeshes.js +2 -0
  93. package/dist/utils/intersectMeshes.js.map +7 -0
  94. package/dist/utils/isClockWise.js +2 -0
  95. package/dist/utils/isClockWise.js.map +7 -0
  96. package/dist/utils/lampPostGeometry.js +2 -0
  97. package/dist/utils/lampPostGeometry.js.map +7 -0
  98. package/dist/utils/lightGeometry.js +2 -0
  99. package/dist/utils/lightGeometry.js.map +7 -0
  100. package/dist/utils/lightGrid.js +2 -0
  101. package/dist/utils/lightGrid.js.map +7 -0
  102. package/dist/utils/limbs.js +2 -0
  103. package/dist/utils/limbs.js.map +7 -0
  104. package/dist/utils/loadModel.js +2 -0
  105. package/dist/utils/loadModel.js.map +7 -0
  106. package/dist/utils/loadSource.js +2 -0
  107. package/dist/utils/loadSource.js.map +7 -0
  108. package/dist/utils/loadTerrain.js +2 -0
  109. package/dist/utils/loadTerrain.js.map +7 -0
  110. package/dist/utils/mapCamera.js +2 -0
  111. package/dist/utils/mapCamera.js.map +7 -0
  112. package/dist/utils/materialShaders.js +211 -0
  113. package/dist/utils/materialShaders.js.map +7 -0
  114. package/dist/utils/mirror.js +2 -0
  115. package/dist/utils/mirror.js.map +7 -0
  116. package/dist/utils/moonPosition.js +2 -0
  117. package/dist/utils/moonPosition.js.map +7 -0
  118. package/dist/utils/moves.js +2 -0
  119. package/dist/utils/moves.js.map +7 -0
  120. package/dist/utils/nativeEvents.js +2 -0
  121. package/dist/utils/nativeEvents.js.map +7 -0
  122. package/dist/utils/nativeGeometry.js +2 -0
  123. package/dist/utils/nativeGeometry.js.map +7 -0
  124. package/dist/utils/nativeLayer.js +2 -0
  125. package/dist/utils/nativeLayer.js.map +7 -0
  126. package/dist/utils/nodeMatrix.js +2 -0
  127. package/dist/utils/nodeMatrix.js.map +7 -0
  128. package/dist/utils/onAdd3DLayer.js +2 -0
  129. package/dist/utils/onAdd3DLayer.js.map +7 -0
  130. package/dist/utils/onAddSun.js +2 -0
  131. package/dist/utils/onAddSun.js.map +7 -0
  132. package/dist/utils/onClick.js +2 -0
  133. package/dist/utils/onClick.js.map +7 -0
  134. package/dist/utils/onMapChange.js +2 -0
  135. package/dist/utils/onMapChange.js.map +7 -0
  136. package/dist/utils/onPMTileAdd.js +2 -0
  137. package/dist/utils/onPMTileAdd.js.map +7 -0
  138. package/dist/utils/onPMTileRemove.js +2 -0
  139. package/dist/utils/onPMTileRemove.js.map +7 -0
  140. package/dist/utils/onPointerLeave.js +2 -0
  141. package/dist/utils/onPointerLeave.js.map +7 -0
  142. package/dist/utils/onPointerMove.js +2 -0
  143. package/dist/utils/onPointerMove.js.map +7 -0
  144. package/dist/utils/onRemove3DLayer.js +2 -0
  145. package/dist/utils/onRemove3DLayer.js.map +7 -0
  146. package/dist/utils/onRemoveSun.js +2 -0
  147. package/dist/utils/onRemoveSun.js.map +7 -0
  148. package/dist/utils/onTerrainAdd.js +2 -0
  149. package/dist/utils/onTerrainAdd.js.map +7 -0
  150. package/dist/utils/onTerrainChange.js +2 -0
  151. package/dist/utils/onTerrainChange.js.map +7 -0
  152. package/dist/utils/onTerrainRemove.js +2 -0
  153. package/dist/utils/onTerrainRemove.js.map +7 -0
  154. package/dist/utils/onZoomAnim.js +2 -0
  155. package/dist/utils/onZoomAnim.js.map +7 -0
  156. package/dist/utils/paints.js +2 -0
  157. package/dist/utils/paints.js.map +7 -0
  158. package/dist/utils/parseColor.js +2 -0
  159. package/dist/utils/parseColor.js.map +7 -0
  160. package/dist/utils/parts.js +2 -0
  161. package/dist/utils/parts.js.map +7 -0
  162. package/dist/utils/projectCelestialBody.js +2 -0
  163. package/dist/utils/projectCelestialBody.js.map +7 -0
  164. package/dist/utils/pump.js +2 -0
  165. package/dist/utils/pump.js.map +7 -0
  166. package/dist/utils/rasterTiles.js +2 -0
  167. package/dist/utils/rasterTiles.js.map +7 -0
  168. package/dist/utils/raycast.js +2 -0
  169. package/dist/utils/raycast.js.map +7 -0
  170. package/dist/utils/read.js +2 -0
  171. package/dist/utils/read.js.map +7 -0
  172. package/dist/utils/readAccessor.js +2 -0
  173. package/dist/utils/readAccessor.js.map +7 -0
  174. package/dist/utils/releaseArea.js +2 -0
  175. package/dist/utils/releaseArea.js.map +7 -0
  176. package/dist/utils/removeRasterTile.js +2 -0
  177. package/dist/utils/removeRasterTile.js.map +7 -0
  178. package/dist/utils/render.js +15 -0
  179. package/dist/utils/render.js.map +7 -0
  180. package/dist/utils/renderSunlight.js +13 -0
  181. package/dist/utils/renderSunlight.js.map +7 -0
  182. package/dist/utils/renderers.js +2 -0
  183. package/dist/utils/renderers.js.map +7 -0
  184. package/dist/utils/resizeSunlight.js +2 -0
  185. package/dist/utils/resizeSunlight.js.map +7 -0
  186. package/dist/utils/roofCoverage.js +2 -0
  187. package/dist/utils/roofCoverage.js.map +7 -0
  188. package/dist/utils/routeGeometry.js +2 -0
  189. package/dist/utils/routeGeometry.js.map +7 -0
  190. package/dist/utils/scheduledTrips.js +2 -0
  191. package/dist/utils/scheduledTrips.js.map +7 -0
  192. package/dist/utils/setGeoJSONData.js +2 -0
  193. package/dist/utils/setGeoJSONData.js.map +7 -0
  194. package/dist/utils/setHighlight.js +2 -0
  195. package/dist/utils/setHighlight.js.map +7 -0
  196. package/dist/utils/setPMTileOpacity.js +2 -0
  197. package/dist/utils/setPMTileOpacity.js.map +7 -0
  198. package/dist/utils/setPMTileZIndex.js +2 -0
  199. package/dist/utils/setPMTileZIndex.js.map +7 -0
  200. package/dist/utils/setParent.js +2 -0
  201. package/dist/utils/setParent.js.map +7 -0
  202. package/dist/utils/setTerrainHeight.js +2 -0
  203. package/dist/utils/setTerrainHeight.js.map +7 -0
  204. package/dist/utils/setVertexColor.js +2 -0
  205. package/dist/utils/setVertexColor.js.map +7 -0
  206. package/dist/utils/showFeature.js +2 -0
  207. package/dist/utils/showFeature.js.map +7 -0
  208. package/dist/utils/sizes.js +2 -0
  209. package/dist/utils/sizes.js.map +7 -0
  210. package/dist/utils/skin.js +2 -0
  211. package/dist/utils/skin.js.map +7 -0
  212. package/dist/utils/solarPosition.js +2 -0
  213. package/dist/utils/solarPosition.js.map +7 -0
  214. package/dist/utils/sortMeshes.js +2 -0
  215. package/dist/utils/sortMeshes.js.map +7 -0
  216. package/dist/utils/speed.js +2 -0
  217. package/dist/utils/speed.js.map +7 -0
  218. package/dist/utils/startZoom.js +2 -0
  219. package/dist/utils/startZoom.js.map +7 -0
  220. package/dist/utils/taillight.js +2 -0
  221. package/dist/utils/taillight.js.map +7 -0
  222. package/dist/utils/terrainAtlas.js +2 -0
  223. package/dist/utils/terrainAtlas.js.map +7 -0
  224. package/dist/utils/terrainElevation.js +2 -0
  225. package/dist/utils/terrainElevation.js.map +7 -0
  226. package/dist/utils/terrainGrid.js +2 -0
  227. package/dist/utils/terrainGrid.js.map +7 -0
  228. package/dist/utils/textureSampler.js +2 -0
  229. package/dist/utils/textureSampler.js.map +7 -0
  230. package/dist/utils/tileId.js +2 -0
  231. package/dist/utils/tileId.js.map +7 -0
  232. package/dist/utils/toLanes.js +2 -0
  233. package/dist/utils/toLanes.js.map +7 -0
  234. package/dist/utils/toSRGB.js +2 -0
  235. package/dist/utils/toSRGB.js.map +7 -0
  236. package/dist/utils/tooltip.js +3 -0
  237. package/dist/utils/tooltip.js.map +7 -0
  238. package/dist/utils/traffic.js +2 -0
  239. package/dist/utils/traffic.js.map +7 -0
  240. package/dist/utils/traverse.js +2 -0
  241. package/dist/utils/traverse.js.map +7 -0
  242. package/dist/utils/treeGeometry.js +2 -0
  243. package/dist/utils/treeGeometry.js.map +7 -0
  244. package/dist/utils/triangulate.js +2 -0
  245. package/dist/utils/triangulate.js.map +7 -0
  246. package/dist/utils/tripsAt.js +2 -0
  247. package/dist/utils/tripsAt.js.map +7 -0
  248. package/dist/utils/types.js +2 -0
  249. package/dist/utils/types.js.map +7 -0
  250. package/dist/utils/underEntry.js +2 -0
  251. package/dist/utils/underEntry.js.map +7 -0
  252. package/dist/utils/update.js +2 -0
  253. package/dist/utils/update.js.map +7 -0
  254. package/dist/utils/updateArea.js +2 -0
  255. package/dist/utils/updateArea.js.map +7 -0
  256. package/dist/utils/updateCamera.js +2 -0
  257. package/dist/utils/updateCamera.js.map +7 -0
  258. package/dist/utils/updatePointLights.js +2 -0
  259. package/dist/utils/updatePointLights.js.map +7 -0
  260. package/dist/utils/updateWorldMatrix.js +2 -0
  261. package/dist/utils/updateWorldMatrix.js.map +7 -0
  262. package/dist/utils/vehicleOffsets.js +2 -0
  263. package/dist/utils/vehicleOffsets.js.map +7 -0
  264. package/dist/utils/vehicles.js +2 -0
  265. package/dist/utils/vehicles.js.map +7 -0
  266. package/dist/utils/vertexNormals.js +2 -0
  267. package/dist/utils/vertexNormals.js.map +7 -0
  268. package/dist/utils/walk.js +2 -0
  269. package/dist/utils/walk.js.map +7 -0
  270. package/dist/utils/white.js +2 -0
  271. package/dist/utils/white.js.map +7 -0
  272. package/dist/utils/worldLights.js +2 -0
  273. package/dist/utils/worldLights.js.map +7 -0
  274. package/dist/utils/wrap.js +2 -0
  275. package/dist/utils/wrap.js.map +7 -0
  276. package/package.json +43 -4
  277. package/src/index.js +397 -0
  278. package/src/utils/CELL.js +4 -0
  279. package/src/utils/addBridges.js +66 -0
  280. package/src/utils/addRasterTile.js +112 -0
  281. package/src/utils/along.js +9 -0
  282. package/src/utils/atmosphere.js +20 -0
  283. package/src/utils/attach.js +255 -0
  284. package/src/utils/bindSunlight.js +12 -0
  285. package/src/utils/box.js +1 -0
  286. package/src/utils/bridgeElevation.js +18 -0
  287. package/src/utils/build.js +20 -0
  288. package/src/utils/cameraControls.js +287 -0
  289. package/src/utils/channel.js +4 -0
  290. package/src/utils/collider.js +62 -0
  291. package/src/utils/colliders.js +2 -0
  292. package/src/utils/computeBounds.js +17 -0
  293. package/src/utils/conformModelToTerrain.js +69 -0
  294. package/src/utils/createMesh.js +31 -0
  295. package/src/utils/createProgram.js +92 -0
  296. package/src/utils/decodeDirectory.js +40 -0
  297. package/src/utils/decodeHeader.js +15 -0
  298. package/src/utils/decodeModel.js +43 -0
  299. package/src/utils/decompress.js +5 -0
  300. package/src/utils/destroyTexture.js +7 -0
  301. package/src/utils/disposeMesh.js +13 -0
  302. package/src/utils/drawGeometry.js +122 -0
  303. package/src/utils/elevationWGSL.js +27 -0
  304. package/src/utils/emptyNode.js +9 -0
  305. package/src/utils/endZoom.js +15 -0
  306. package/src/utils/findFeature.js +49 -0
  307. package/src/utils/geometry.js +2 -0
  308. package/src/utils/getElevation.js +10 -0
  309. package/src/utils/getHeader.js +9 -0
  310. package/src/utils/getZxy.js +76 -0
  311. package/src/utils/glass.js +1 -0
  312. package/src/utils/headlight.js +1 -0
  313. package/src/utils/hit.js +41 -0
  314. package/src/utils/horizontalPosition.js +16 -0
  315. package/src/utils/horizontalRay.js +12 -0
  316. package/src/utils/hull.js +1 -0
  317. package/src/utils/initSunlight.js +9 -0
  318. package/src/utils/initTerrain.js +19 -0
  319. package/src/utils/initTraffic.js +93 -0
  320. package/src/utils/initWalk.js +72 -0
  321. package/src/utils/intersectMeshes.js +121 -0
  322. package/src/utils/isClockWise.js +12 -0
  323. package/src/utils/lampPostGeometry.js +26 -0
  324. package/src/utils/lightGeometry.js +22 -0
  325. package/src/utils/lightGrid.js +50 -0
  326. package/src/utils/limbs.js +9 -0
  327. package/src/utils/loadModel.js +217 -0
  328. package/src/utils/loadSource.js +17 -0
  329. package/src/utils/loadTerrain.js +65 -0
  330. package/src/utils/mapCamera.js +432 -0
  331. package/src/utils/materialShaders.js +243 -0
  332. package/src/utils/mirror.js +6 -0
  333. package/src/utils/moonPosition.js +17 -0
  334. package/src/utils/moves.js +11 -0
  335. package/src/utils/nativeEvents.js +77 -0
  336. package/src/utils/nativeGeometry.js +131 -0
  337. package/src/utils/nativeLayer.js +317 -0
  338. package/src/utils/nodeMatrix.js +22 -0
  339. package/src/utils/onAdd3DLayer.js +31 -0
  340. package/src/utils/onAddSun.js +147 -0
  341. package/src/utils/onClick.js +13 -0
  342. package/src/utils/onMapChange.js +16 -0
  343. package/src/utils/onPMTileAdd.js +15 -0
  344. package/src/utils/onPMTileRemove.js +8 -0
  345. package/src/utils/onPointerLeave.js +11 -0
  346. package/src/utils/onPointerMove.js +15 -0
  347. package/src/utils/onRemove3DLayer.js +78 -0
  348. package/src/utils/onRemoveSun.js +3 -0
  349. package/src/utils/onTerrainAdd.js +18 -0
  350. package/src/utils/onTerrainChange.js +13 -0
  351. package/src/utils/onTerrainRemove.js +40 -0
  352. package/src/utils/onZoomAnim.js +14 -0
  353. package/src/utils/paints.js +10 -0
  354. package/src/utils/parseColor.js +42 -0
  355. package/src/utils/parts.js +16 -0
  356. package/src/utils/projectCelestialBody.js +13 -0
  357. package/src/utils/pump.js +226 -0
  358. package/src/utils/rasterTiles.js +285 -0
  359. package/src/utils/raycast.js +60 -0
  360. package/src/utils/read.js +29 -0
  361. package/src/utils/readAccessor.js +68 -0
  362. package/src/utils/releaseArea.js +34 -0
  363. package/src/utils/removeRasterTile.js +13 -0
  364. package/src/utils/render.js +406 -0
  365. package/src/utils/renderSunlight.js +170 -0
  366. package/src/utils/renderers.js +2 -0
  367. package/src/utils/resizeSunlight.js +15 -0
  368. package/src/utils/roofCoverage.js +47 -0
  369. package/src/utils/routeGeometry.js +22 -0
  370. package/src/utils/scheduledTrips.js +29 -0
  371. package/src/utils/setGeoJSONData.js +11 -0
  372. package/src/utils/setHighlight.js +13 -0
  373. package/src/utils/setPMTileOpacity.js +7 -0
  374. package/src/utils/setPMTileZIndex.js +7 -0
  375. package/src/utils/setParent.js +7 -0
  376. package/src/utils/setTerrainHeight.js +10 -0
  377. package/src/utils/setVertexColor.js +14 -0
  378. package/src/utils/showFeature.js +27 -0
  379. package/src/utils/sizes.js +1 -0
  380. package/src/utils/skin.js +2 -0
  381. package/src/utils/solarPosition.js +17 -0
  382. package/src/utils/sortMeshes.js +12 -0
  383. package/src/utils/speed.js +3 -0
  384. package/src/utils/startZoom.js +5 -0
  385. package/src/utils/taillight.js +1 -0
  386. package/src/utils/terrainAtlas.js +21 -0
  387. package/src/utils/terrainElevation.js +20 -0
  388. package/src/utils/terrainGrid.js +23 -0
  389. package/src/utils/textureSampler.js +10 -0
  390. package/src/utils/tileId.js +18 -0
  391. package/src/utils/toLanes.js +7 -0
  392. package/src/utils/toSRGB.js +1 -0
  393. package/src/utils/tooltip.js +6 -0
  394. package/src/utils/traffic.js +315 -0
  395. package/src/utils/traverse.js +6 -0
  396. package/src/utils/treeGeometry.js +45 -0
  397. package/src/utils/triangulate.js +100 -0
  398. package/src/utils/tripsAt.js +16 -0
  399. package/src/utils/types.js +8 -0
  400. package/src/utils/underEntry.js +17 -0
  401. package/src/utils/update.js +18 -0
  402. package/src/utils/updateArea.js +40 -0
  403. package/src/utils/updateCamera.js +32 -0
  404. package/src/utils/updatePointLights.js +38 -0
  405. package/src/utils/updateWorldMatrix.js +47 -0
  406. package/src/utils/vehicleOffsets.js +8 -0
  407. package/src/utils/vehicles.js +65 -0
  408. package/src/utils/vertexNormals.js +30 -0
  409. package/src/utils/walk.js +187 -0
  410. package/src/utils/white.js +1 -0
  411. package/src/utils/worldLights.js +25 -0
  412. package/src/utils/wrap.js +1 -0
@@ -0,0 +1,315 @@
1
+ import * as createMeshApi from './createMesh.js';
2
+ import * as disposeMesh from './disposeMesh.js';
3
+ import * as setHighlight from './setHighlight.js';
4
+ import * as showFeature from './showFeature.js';
5
+ import { update as updateScene } from './update.js';
6
+ import { along } from './along.js';
7
+ import { build } from './build.js';
8
+ import { computeBounds } from './computeBounds.js';
9
+ import { routeGeometry } from './routeGeometry.js';
10
+ import { toLanes } from './toLanes.js';
11
+ import { tripsAt } from './tripsAt.js';
12
+ import { underEntry } from './underEntry.js';
13
+ import { getElevation } from './getElevation.js';
14
+
15
+ export function trafficNow(state) {
16
+ let control = { live: true };
17
+ state.layer._map?.eachLayer(other => { if (other.control?.time) control = other.control; });
18
+ return { live: control.live, time: control.live ? new Date() : control.time };
19
+ }
20
+
21
+ export function createTrafficMesh(state, data, featureFaces, material, usage = 0) {
22
+ const result = createMeshApi.createMesh(state.layer, data, material, usage);
23
+ result.extras.featureFaces = featureFaces;
24
+ return result;
25
+ }
26
+
27
+ export function disposeTrafficObject(state, object) {
28
+ if (!object) return;
29
+ if (state.layer.highlightedFeature?.mesh === object) {
30
+ setHighlight.setHighlight(state.layer, null);
31
+ state.layer.hoveredFeature = null;
32
+ showFeature.showFeature(state.layer, null);
33
+ };
34
+ disposeMesh.disposeMesh(state.layer, object);
35
+ }
36
+
37
+ export function failTraffic(state, error) {
38
+ state.status.textContent = `Error: ${error.message}`;
39
+ state.status.setAttribute('role', 'alert');
40
+ console.error('Traffic layer', error);
41
+ }
42
+
43
+ export function groundTraffic(state) {
44
+ const { layer, lanes } = state;
45
+ const terrain = layer._map._terrain3D?._terrainReady && layer._map._terrain3D, base = layer.root.position[1];
46
+ for (const lane of lanes) lane.points.forEach((point, i) => {
47
+ const elevation = terrain && (lane.elevation ?? getElevation(terrain, lane.latlngs[i]));
48
+ point[2] = Number.isFinite(elevation) && Number.isFinite(base) ? elevation - base : 0;
49
+ });
50
+ state.lanesDirty = true;
51
+ state.nextCheck = -Infinity;
52
+ }
53
+
54
+ export function prepareTraffic(state) {
55
+ const { layer, lanes } = state;
56
+ const crs = layer._map.options.crs, origin = crs.project(layer.location);
57
+ const metres = Math.cos(layer.location.lat * Math.PI / 180);
58
+ for (const lane of lanes) {
59
+ lane.points = lane.latlngs.map(([lat, lng]) => {
60
+ const point = crs.project(new L.LatLng(lat, lng));
61
+ return [(point.x - origin.x) * metres, -(point.y - origin.y) * metres, 0];
62
+ });
63
+ lane.lengths = [0];
64
+ for (let i = 1; i < lane.points.length; i++) {
65
+ lane.lengths.push(lane.lengths[i - 1] + Math.hypot(lane.points[i][0] - lane.points[i - 1][0], lane.points[i][1] - lane.points[i - 1][1]));
66
+ }
67
+ lane.length = lane.lengths.at(-1);
68
+ }
69
+ groundTraffic(state);
70
+ disposeTrafficObject(state, state.route);
71
+ state.route = null;
72
+ state.tripsKey = null;
73
+ }
74
+
75
+ export function drawTrafficRoute(state) {
76
+ state.route = createTrafficMesh(state, routeGeometry(state.lanes), [], { emissive: [0.03, 0.25, 0.3] });
77
+ }
78
+
79
+ export function rebuildTrafficMesh(state) {
80
+ const { trips, model, vertices } = state;
81
+ disposeTrafficObject(state, state.mesh);
82
+ state.mesh = null;
83
+ if (!trips.length) return;
84
+ const color = trips.flatMap((trip, i) => build(model(i)).color), count = color.length / 3;
85
+ state.mesh = createTrafficMesh(state, { position: color.map(() => 0), normal: color.map(() => 0), color },
86
+ trips.map((trip, i) => ({
87
+ id: trip.id, properties: trip.properties, faceStart: i * vertices / 3, faceEnd: (i + 1) * vertices / 3,
88
+ vertexStart: i * vertices, vertexEnd: (i + 1) * vertices
89
+ })), {}, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC);
90
+ state.mesh.geometry.drawRange.count = count;
91
+ trafficBounds(state);
92
+ }
93
+
94
+ export function trafficBounds(state) {
95
+ const extent = { attributes: { position: { data: state.lanes.flatMap(lane => lane.points.flatMap(([x, z, y]) => [x - 20, y, z - 20, x + 20, y + 5, z + 20])) } } };
96
+ computeBounds(extent);
97
+ state.mesh.geometry.bounds = extent.bounds;
98
+ }
99
+
100
+ export function destroyTrafficGpu(state) {
101
+ for (const buffer of [state.gpu?.params, state.gpu?.template, state.gpu?.points, state.gpu?.vehicles]) buffer?.destroy();
102
+ state.gpu = null;
103
+ }
104
+
105
+ export function trafficStorage(device, data) {
106
+ const buffer = device.createBuffer({ size: Math.max(16, data.byteLength), usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
107
+ device.queue.writeBuffer(buffer, 0, data);
108
+ return buffer;
109
+ }
110
+
111
+ export function rebaseTraffic(state) {
112
+ const { device } = state.layer.renderer;
113
+ if (state.gpu?.device !== device) {
114
+ destroyTrafficGpu(state);
115
+ state.gpu = {
116
+ device,
117
+ pipeline: device.createComputePipeline({ layout: 'auto', compute: { module: device.createShaderModule({ code: state.shader }), entryPoint: 'main' } }),
118
+ params: device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }),
119
+ template: trafficStorage(device, new Float32Array([...state.template.position, ...state.template.normal])),
120
+ };
121
+ state.lanesDirty = true;
122
+ }
123
+ if (state.lanesDirty) {
124
+ const data = new Float32Array(state.lanes.reduce((count, lane) => count + lane.points.length, 0) * 4);
125
+ let offset = 0;
126
+ for (const lane of state.lanes) {
127
+ lane.start = offset / 4;
128
+ lane.points.forEach((point, i) => { data.set([point[0], point[1], point[2], lane.lengths[i]], offset); offset += 4; });
129
+ }
130
+ state.gpu.points?.destroy();
131
+ state.gpu.points = trafficStorage(device, data);
132
+ state.lanesDirty = false;
133
+ }
134
+ const data = new Float32Array(state.trips.length * 4), index = new Uint32Array(data.buffer);
135
+ state.trips.forEach((trip, i) => {
136
+ index[i * 4] = trip.lane.start;
137
+ index[i * 4 + 1] = trip.lane.points.length;
138
+ data[i * 4 + 2] = trip.distance;
139
+ data[i * 4 + 3] = trip.speed;
140
+ state.mesh.extras.featureFaces[i].properties = trip.properties;
141
+ });
142
+ if (state.gpu.vehicles?.size >= data.byteLength) device.queue.writeBuffer(state.gpu.vehicles, 0, data);
143
+ else {
144
+ state.gpu.vehicles?.destroy();
145
+ state.gpu.vehicles = trafficStorage(device, data);
146
+ }
147
+ const { position, normal } = state.mesh.geometry.attributes;
148
+ state.gpu.bindGroup = device.createBindGroup({
149
+ layout: state.gpu.pipeline.getBindGroupLayout(0),
150
+ entries: [state.gpu.params, state.gpu.template, state.gpu.points, state.gpu.vehicles, position.buffer, normal.buffer]
151
+ .map((buffer, binding) => ({ binding, resource: { buffer } })),
152
+ });
153
+ }
154
+
155
+ export function moveTraffic(state, seconds) {
156
+ const { device } = state.gpu;
157
+ state.paramFloats[0] = state.elapsed = seconds;
158
+ state.paramFloats[1] = state.side;
159
+ state.paramInts[2] = state.vertices;
160
+ state.paramInts[3] = state.trips.length * state.vertices;
161
+ device.queue.writeBuffer(state.gpu.params, 0, state.params);
162
+ const encoder = device.createCommandEncoder(), pass = encoder.beginComputePass();
163
+ pass.setPipeline(state.gpu.pipeline);
164
+ pass.setBindGroup(0, state.gpu.bindGroup);
165
+ pass.dispatchWorkgroups(Math.ceil(state.paramInts[3] / 64));
166
+ pass.end();
167
+ device.queue.submit([encoder.finish()]);
168
+ state.stale = true;
169
+ }
170
+
171
+ export function locateTraffic(state, trip) {
172
+ const { points, lengths, length } = trip.lane;
173
+ let distance = trip.distance + state.elapsed * trip.speed;
174
+ distance -= length * Math.floor(distance / length);
175
+ const { x, y, z, dx, dz } = along(points, lengths, distance);
176
+ return { x: x - dz * state.side, y, z: z + dx * state.side, dx, dz };
177
+ }
178
+
179
+ export function placeTraffic(state) {
180
+ if (!state.mesh || !state.stale) return;
181
+ state.stale = false;
182
+ const { position, normal } = state.mesh.geometry.attributes;
183
+ state.trips.forEach((trip, i) => {
184
+ const { x: cx, y, z: cz, dx, dz } = locateTraffic(state, trip);
185
+ for (let k = 0, o = i * state.stride; k < state.stride; k += 3, o += 3) {
186
+ const lx = state.template.position[k], lz = state.template.position[k + 2], nx = state.template.normal[k], nz = state.template.normal[k + 2];
187
+ position.data[o] = cx - lx * dz - lz * dx;
188
+ position.data[o + 1] = y + state.template.position[k + 1];
189
+ position.data[o + 2] = cz + lx * dx - lz * dz;
190
+ normal.data[o] = -nx * dz - nz * dx;
191
+ normal.data[o + 1] = state.template.normal[k + 1];
192
+ normal.data[o + 2] = nx * dx - nz * dz;
193
+ }
194
+ });
195
+ }
196
+
197
+ export function tickTraffic(state) {
198
+ state.frame = undefined;
199
+ if (!state.ready || !state.layer._map) return;
200
+ if (!state.layer.renderer) { state.frame = requestAnimationFrame(state.tick); return; }
201
+ if (state.mesh && !(state.mesh.geometry.attributes.position.buffer.usage & GPUBufferUsage.STORAGE)) {
202
+ state.tripsKey = null;
203
+ state.nextCheck = -Infinity;
204
+ }
205
+ const { live, time } = trafficNow(state), ms = time?.getTime(), valid = Number.isFinite(ms);
206
+ let changed = false;
207
+ if (!live || !valid || !(ms >= state.rebasedAt && ms < state.nextCheck)) {
208
+ let text;
209
+ try {
210
+ state.trips = valid ? tripsAt(time, state.lanes, state.velocity) : [];
211
+ text = !valid ? 'Select a valid simulation date' : !state.loaded ? 'Loading…' :
212
+ `${state.trips.length} vehicle${state.trips.length === 1 ? '' : 's'}${state.loaded.note ? ` · ${state.loaded.note}` : ''}`;
213
+ } catch (error) { return failTraffic(state, error); }
214
+ state.rebasedAt = ms;
215
+ state.nextCheck = ms + 1000;
216
+ const key = state.trips.map(trip => trip.id).join();
217
+ changed = key !== state.tripsKey || (state.outline && !state.route && state.lanes.length);
218
+ if (state.outline && !state.route && state.lanes.length) drawTrafficRoute(state);
219
+ if (key !== state.tripsKey) { rebuildTrafficMesh(state); state.tripsKey = key; }
220
+ if (state.mesh) rebaseTraffic(state);
221
+ if (state.status.textContent !== text) state.status.textContent = text;
222
+ state.status.removeAttribute('role');
223
+ }
224
+ if (state.mesh) moveTraffic(state, (ms - state.rebasedAt) / 1000);
225
+ if (state.mesh && state.lights && (!live || !(performance.now() - state.litAt < 100))) {
226
+ state.litAt = performance.now();
227
+ state.mesh.extras.lights = state.trips.flatMap(trip => {
228
+ const { x, y, z, dx, dz } = locateTraffic(state, trip);
229
+ return state.lights.map(({ position: [lx, ly, lz], ...light }) => ({ ...light, position: [x - lx * dz - lz * dx, y + ly, z + lx * dx - lz * dz] }));
230
+ });
231
+ }
232
+ if (state.mesh || changed) updateScene(state.layer);
233
+ if (live && valid) state.frame = requestAnimationFrame(state.tick);
234
+ }
235
+
236
+ export async function fetchTrafficLanes(state, time, signal) {
237
+ const href = new URL(state.url, document.baseURI).href;
238
+ if (/\.m?js(?:[?#]|$)/i.test(state.url)) return (await import(href)).default(time, state.layer, signal);
239
+ if (!state.file) {
240
+ const response = await fetch(href, { signal });
241
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
242
+ state.file = await response.json();
243
+ }
244
+ return state.file;
245
+ }
246
+
247
+ export function loadedTrafficLanes(state, signal, data) {
248
+ if (signal.aborted || !state.layer._map || data === state.loaded) return;
249
+ state.loaded = data;
250
+ state.lanes = toLanes(data);
251
+ prepareTraffic(state);
252
+ if (!state.frame) state.tick();
253
+ }
254
+
255
+ export function failedTrafficLoad(state, error) {
256
+ if (error.name !== 'AbortError') failTraffic(state, error);
257
+ }
258
+
259
+ export function loadTraffic(state) {
260
+ const { time } = trafficNow(state);
261
+ if (!Number.isFinite(time?.getTime())) return;
262
+ state.request?.abort();
263
+ const { signal } = state.request = new AbortController();
264
+ fetchTrafficLanes(state, time, signal).then(
265
+ loadedTrafficLanes.bind(null, state, signal),
266
+ failedTrafficLoad.bind(null, state),
267
+ );
268
+ }
269
+
270
+ export function updateTraffic(state, event) {
271
+ if (event?.layer === state.layer) return;
272
+ cancelAnimationFrame(state.frame);
273
+ loadTraffic(state);
274
+ state.tick();
275
+ }
276
+
277
+ export function updateTrafficTerrain(state) {
278
+ if (!state.lanes.length) return;
279
+ groundTraffic(state);
280
+ disposeTrafficObject(state, state.route);
281
+ state.route = null;
282
+ if (state.mesh) trafficBounds(state);
283
+ if (!state.frame) state.tick();
284
+ }
285
+
286
+ export function startTraffic(state) {
287
+ state.layer._map.off('simulationtimechange layerremove', state.update).on('simulationtimechange layerremove', state.update);
288
+ state.layer._map.off('terrainchange', state.terrain).on('terrainchange', state.terrain);
289
+ underEntry(state.layer, state.status);
290
+ state.update();
291
+ }
292
+
293
+ export function readyTraffic(state) {
294
+ state.ready = true;
295
+ if (state.layer._map) startTraffic(state);
296
+ }
297
+
298
+ export function addTraffic(state) {
299
+ if (state.ready) startTraffic(state);
300
+ }
301
+
302
+ export function removeTraffic(state) {
303
+ state.request?.abort();
304
+ cancelAnimationFrame(state.frame);
305
+ state.frame = undefined;
306
+ state.layer._map.off('simulationtimechange layerremove', state.update).off('terrainchange', state.terrain);
307
+ disposeTrafficObject(state, state.mesh);
308
+ disposeTrafficObject(state, state.route);
309
+ state.mesh = state.route = null;
310
+ delete state.layer._beforeRaycast;
311
+ destroyTrafficGpu(state);
312
+ state.lanes = [];
313
+ state.trips = [];
314
+ state.tripsKey = state.loaded = null;
315
+ }
@@ -0,0 +1,6 @@
1
+ /** Depth-first walk; a truthy callback result skips that node's descendants. */
2
+
3
+ export function traverse(node, callback) {
4
+ if (callback(node)) return;
5
+ for (const child of node.children) traverse(child, callback);
6
+ }
@@ -0,0 +1,45 @@
1
+ import { parseColor } from "./parseColor.js";
2
+
3
+ /** Low-poly tree, sharing the source block's buffers, picking and terrain offset. */
4
+
5
+ export function treeGeometry(point, style, addTriangle) {
6
+ const { top, bottom = 0, trunkRadius, crownRadius, crownBottom, crownShape = 'round' } = style;
7
+ if (![trunkRadius, crownRadius, crownBottom].every(Number.isFinite) ||
8
+ trunkRadius <= 0 || crownRadius < trunkRadius || crownBottom <= bottom || crownBottom >= top ||
9
+ !['round', 'cone'].includes(crownShape)) {
10
+ throw new Error('Invalid tree dimensions or crown shape');
11
+ }
12
+ const trunk = parseColor(style.trunkColor ?? 0x72513a);
13
+ const crown = parseColor(style.color ?? 0x4f823b);
14
+ const ring = radius => Array.from({ length: 8 }, (_, i) => ({
15
+ x: point.x + Math.cos(i * Math.PI / 4) * radius,
16
+ y: point.y + Math.sin(i * Math.PI / 4) * radius,
17
+ ground: point.ground,
18
+ }));
19
+ const surface = (lowRadius, low, highRadius, high, color) => {
20
+ const lower = ring(lowRadius), upper = ring(highRadius);
21
+ for (let i = 0; i < 8; i++) {
22
+ const next = (i + 1) % 8;
23
+ const angle = (i + 0.5) * Math.PI / 4;
24
+ const slope = (lowRadius - highRadius) / (high - low);
25
+ const length = Math.hypot(1, slope);
26
+ const nx = Math.cos(angle) / length, ny = slope / length, nz = -Math.sin(angle) / length;
27
+ if (lowRadius) addTriangle(lower[i], low, lower[next], low, upper[next], high, nx, ny, nz, color);
28
+ if (highRadius) addTriangle(lower[i], low, upper[next], high, upper[i], high, nx, ny, nz, color);
29
+ }
30
+ };
31
+ surface(trunkRadius, bottom, trunkRadius, crownBottom + (top - crownBottom) * 0.25, trunk);
32
+ if (crownShape === 'cone') {
33
+ const base = ring(crownRadius);
34
+ for (let i = 0; i < 8; i++) addTriangle(point, crownBottom, base[i], crownBottom,
35
+ base[(i + 1) % 8], crownBottom, 0, -1, 0, crown);
36
+ surface(crownRadius, crownBottom, 0, top, crown);
37
+ } else {
38
+ const profile = [[0, 0], [0.35, 0.8], [0.6, 1], [0.85, 0.7], [1, 0]];
39
+ for (let i = 1; i < profile.length; i++) {
40
+ const [low, lr] = profile[i - 1], [high, hr] = profile[i];
41
+ surface(lr * crownRadius, crownBottom + low * (top - crownBottom),
42
+ hr * crownRadius, crownBottom + high * (top - crownBottom), crown);
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,100 @@
1
+ import { isClockWise } from "./isClockWise.js";
2
+
3
+ /**
4
+ * Triangulates a polygon given as `{x, y}` points.
5
+ * Rings omit the repeated closing point; winding is normalized internally.
6
+ * Vertex indices remain relative to the original inputs after duplicate/collinear
7
+ * cleanup. Hole bridges duplicate endpoints, not source vertices.
8
+ * @param {Object[]} contour Outer boundary.
9
+ * @param {Object[][]} [holes] Interior boundaries.
10
+ *
11
+ * @returns {number[][]} Triangles as indices in `[...contour, ...holes.flat()]`.
12
+ */
13
+
14
+ export function triangulate(contour, holes = []) {
15
+ const points = [contour, ...holes].flat();
16
+ if (points.some(p => !Number.isFinite(p.x) || !Number.isFinite(p.y))) throw new Error('Invalid polygon coordinates');
17
+ // Translate near the origin to avoid cancellation with geographic coordinates.
18
+ const vertices = points.map((p, index) => ({ x: p.x - contour[0].x, y: p.y - contour[0].y, index }));
19
+ const cross = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
20
+ const equal = (a, b) => a.x === b.x && a.y === b.y;
21
+ // Remove zero-area corners before connecting holes; keep original vertex indices.
22
+ const clean = ring => {
23
+ let changed = true;
24
+ while (changed && ring.length > 3) {
25
+ changed = false;
26
+ for (let i = 0; i < ring.length; i++) {
27
+ const a = ring[(i + ring.length - 1) % ring.length], b = ring[i], c = ring[(i + 1) % ring.length];
28
+ if (equal(a, b) || Math.abs(cross(a, b, c)) < 1e-10) {
29
+ ring.splice(i, 1); changed = true; break;
30
+ }
31
+ }
32
+ }
33
+ if (ring.length < 3) throw new Error('Degenerate polygon ring');
34
+ return ring;
35
+ };
36
+ let offset = 0;
37
+ const rings = [contour, ...holes].map((path, index) => {
38
+ const ring = clean(vertices.slice(offset, offset += path.length));
39
+ if (isClockWise(ring) !== Boolean(index)) ring.reverse();
40
+ return ring;
41
+ });
42
+ const inside = (point, ring) => {
43
+ let result = false;
44
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45
+ const a = ring[i], b = ring[j];
46
+ if ((a.y > point.y) !== (b.y > point.y) &&
47
+ point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x) result = !result;
48
+ }
49
+ return result;
50
+ };
51
+ const intersects = (a, b, c, d) => {
52
+ if (equal(a, c) || equal(a, d) || equal(b, c) || equal(b, d)) return false;
53
+ const abC = cross(a, b, c), abD = cross(a, b, d), cdA = cross(c, d, a), cdB = cross(c, d, b);
54
+ const on = (p, q, r) => Math.min(p.x, q.x) <= r.x && r.x <= Math.max(p.x, q.x) &&
55
+ Math.min(p.y, q.y) <= r.y && r.y <= Math.max(p.y, q.y);
56
+ return abC * abD < 0 && cdA * cdB < 0 ||
57
+ abC === 0 && on(a, b, c) || abD === 0 && on(a, b, d) ||
58
+ cdA === 0 && on(c, d, a) || cdB === 0 && on(c, d, b);
59
+ };
60
+ let polygon = rings[0].slice();
61
+ // A bridge must stay inside the outer boundary, outside every hole, and cross no edge.
62
+ // ponytail: quadratic ear clipping for building footprints; use spatial indexing for large rings.
63
+ for (const hole of rings.slice(1).sort((a, b) => Math.min(...a.map(p => p.x)) - Math.min(...b.map(p => p.x)))) {
64
+ let bridge, distance = Infinity;
65
+ for (let h = 0; h < hole.length; h++) for (let p = 0; p < polygon.length; p++) {
66
+ const a = hole[h], b = polygon[p], d = (a.x - b.x) ** 2 + (a.y - b.y) ** 2;
67
+ if (d >= distance) continue;
68
+ const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
69
+ if (!inside(mid, rings[0]) || rings.slice(1).some(r => inside(mid, r))) continue;
70
+ if ([polygon, ...rings.slice(1)].some(r => r.some((c, i) => intersects(a, b, c, r[(i + 1) % r.length])))) continue;
71
+ bridge = [h, p]; distance = d;
72
+ }
73
+ if (!bridge) throw new Error('Unable to connect polygon hole');
74
+ const [h, p] = bridge;
75
+ polygon = [...polygon.slice(0, p + 1), ...hole.slice(h), ...hole.slice(0, h + 1),
76
+ polygon[p], ...polygon.slice(p + 1)];
77
+ }
78
+ // Clip a convex corner only when no other vertex is inside/on its triangle.
79
+ const result = [];
80
+ while (polygon.length > 3) {
81
+ let clipped = false;
82
+ for (let i = 0; i < polygon.length; i++) {
83
+ const a = polygon[(i + polygon.length - 1) % polygon.length], b = polygon[i], c = polygon[(i + 1) % polygon.length];
84
+ if (cross(a, b, c) <= 1e-10) continue;
85
+ if (polygon.some(p => !equal(p, a) && !equal(p, b) && !equal(p, c) &&
86
+ cross(a, b, p) >= 0 && cross(b, c, p) >= 0 && cross(c, a, p) >= 0)) continue;
87
+ result.push([a.index, b.index, c.index]); polygon.splice(i, 1); clipped = true; break;
88
+ }
89
+ if (!clipped) throw new Error('Unable to triangulate polygon: invalid or self-intersecting rings');
90
+ }
91
+ if (cross(...polygon) > 1e-10) result.push(polygon.map(p => p.index));
92
+ const area = ring => Math.abs(ring.reduce((sum, p, i) => {
93
+ const q = ring[(i + 1) % ring.length]; return sum + p.x * q.y - q.x * p.y;
94
+ }, 0)) / 2;
95
+ // Reject incomplete coverage instead of silently emitting a partial roof.
96
+ const expected = area(rings[0]) - rings.slice(1).reduce((sum, ring) => sum + area(ring), 0);
97
+ const actual = result.reduce((sum, triangle) => sum + cross(...triangle.map(i => vertices[i])) / 2, 0);
98
+ if (expected <= 0 || Math.abs(actual - expected) > Math.max(1e-7, expected * 1e-8)) throw new Error('Polygon triangulation area mismatch');
99
+ return result;
100
+ }
@@ -0,0 +1,16 @@
1
+ import { vehicleOffsets } from "./vehicleOffsets.js";
2
+ import { scheduledTrips } from "./scheduledTrips.js";
3
+
4
+ // A lane with a `timetable` runs scheduled trips end to end; one with a `flow` (vehicles/h) loops evenly spaced vehicles.
5
+
6
+ export const tripsAt = (time, lanes, velocity) => lanes.flatMap((lane, l) => {
7
+ const { timetable, flow = 0 } = lane.properties;
8
+ if (!lane.length) return [];
9
+ if (timetable) return scheduledTrips(time, timetable).map(trip => ({
10
+ id: `${l}-${trip.id}`, lane, distance: trip.progress * lane.length, speed: lane.length / (timetable.duration * 60),
11
+ properties: { ...lane.properties, departure: trip.departure },
12
+ }));
13
+ const travelled = time / 1000 * velocity;
14
+ return (lane.offsets ||= vehicleOffsets(lane.length, flow, velocity))
15
+ .map((offset, k) => ({ id: `${l}-${k}`, lane, distance: (offset + travelled) % lane.length, speed: velocity, properties: lane.properties }));
16
+ });
@@ -0,0 +1,8 @@
1
+ export const types = {
2
+ 5120: Int8Array,
3
+ 5121: Uint8Array,
4
+ 5122: Int16Array,
5
+ 5123: Uint16Array,
6
+ 5125: Uint32Array,
7
+ 5126: Float32Array,
8
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Keeps `element` right under `layer`'s entry in the map's layers control while the layer is on the map.
3
+ * Returns false when no layers control lists the layer.
4
+ */
5
+ export function underEntry(layer, element) {
6
+ const map = layer._map;
7
+ const place = () => {
8
+ const input = [...map.getContainer().querySelectorAll('.leaflet-control-layers-selector')].find(input => input.layerId === L.Util.stamp(layer));
9
+ input?.closest('label').after(element);
10
+ return Boolean(input);
11
+ };
12
+ // Leaflet rebuilds the list after 'layeradd'/'layerremove' (on the layer's own 'add'/'remove'): re-place afterwards.
13
+ const replace = () => queueMicrotask(() => map.hasLayer(layer) && place());
14
+ map.on('layeradd layerremove', replace);
15
+ layer.once('remove', () => { map.off('layeradd layerremove', replace); element.remove(); });
16
+ return place();
17
+ }
@@ -0,0 +1,18 @@
1
+ import { render } from "./render.js";
2
+
3
+ /** Scene graph, shared camera and frame rendering. */
4
+ /** Content changes: render once on the next animation frame. */
5
+
6
+ export function update(backend) {
7
+ const shared = backend._shared;
8
+ if (!shared) return backend;
9
+ if (shared !== backend) {
10
+ update(shared);
11
+ return backend;
12
+ }
13
+ backend._frame ||= requestAnimationFrame(() => {
14
+ backend._frame = null;
15
+ if (backend._shared === backend && backend.renderer) render(backend);
16
+ });
17
+ return backend;
18
+ }
@@ -0,0 +1,40 @@
1
+ import { pump } from "./pump.js";
2
+ import { releaseArea } from "./releaseArea.js";
3
+
4
+ /** Cover the visible ground bounds and one bordering block, clipped to the archive. */
5
+
6
+ export function updateArea(backend) {
7
+ if (!backend._map || !backend._shared) return;
8
+ if (backend._geojson) {
9
+ if (!backend._areas.has('geojson')) {
10
+ backend._areas.set('geojson', { request: new AbortController() });
11
+ }
12
+ pump(backend);
13
+ return;
14
+ }
15
+ if (!backend.header) return;
16
+ const z = backend.header.minZoom;
17
+ const center = backend._map.project(backend._map.getCenter(), z).divideBy(256).floor();
18
+ const bounds = backend._map.getBounds();
19
+ const nw = backend._map.project(bounds.getNorthWest(), z).divideBy(256).floor();
20
+ const se = backend._map.project(bounds.getSouthEast(), z).divideBy(256).floor();
21
+ const archiveNW = backend._map.project([backend.header.maxLat, backend.header.minLon], z).divideBy(256).floor();
22
+ const archiveSE = backend._map.project([backend.header.minLat, backend.header.maxLon], z).divideBy(256).floor();
23
+ const wanted = new Map();
24
+ for (let x = Math.max(0, nw.x - 1, archiveNW.x); x <= Math.min(2 ** z - 1, se.x + 1, archiveSE.x); x++) {
25
+ for (let y = Math.max(0, nw.y - 1, archiveNW.y); y <= Math.min(2 ** z - 1, se.y + 1, archiveSE.y); y++) {
26
+ wanted.set(`${x}/${y}`, { z, x, y });
27
+ }
28
+ }
29
+ for (const [key, area] of backend._areas) {
30
+ if (!wanted.has(key)) {
31
+ releaseArea(backend, area);
32
+ backend._areas.delete(key);
33
+ }
34
+ }
35
+ // Load the nearest new blocks first without rebuilding retained geometry.
36
+ for (const [key, coords] of [...wanted].sort(([, a], [, b]) => Math.hypot(a.x - center.x, a.y - center.y) - Math.hypot(b.x - center.x, b.y - center.y))) {
37
+ if (!backend._areas.has(key)) backend._areas.set(key, { coords, request: new AbortController() });
38
+ }
39
+ pump(backend);
40
+ }
@@ -0,0 +1,32 @@
1
+ /** Orthonormal view basis plus perspective matrices, refreshed before projecting/drawing. */
2
+
3
+ const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
4
+
5
+ export function updateCamera(camera) {
6
+ const z = camera.position.map((v, i) => v - camera.target[i]);
7
+ const zLength = Math.hypot(...z) || 1;
8
+ for (let i = 0; i < 3; i++) {
9
+ z[i] /= zLength;
10
+ }
11
+ const x = cross(camera.up, z), xLength = Math.hypot(...x) || 1;
12
+ for (let i = 0; i < 3; i++) {
13
+ x[i] /= xLength;
14
+ }
15
+ const y = cross(z, x);
16
+ const view = new DOMMatrix([x[0], x[1], x[2], 0, y[0], y[1], y[2], 0, z[0], z[1], z[2], 0, ...camera.position, 1]).inverse().toFloat64Array();
17
+ if (![...view].every(Number.isFinite)) {
18
+ throw new Error('Singular 3D transform');
19
+ }
20
+ for (let i = 0; i < 16; i++) {
21
+ camera.viewMatrix[i] = view[i];
22
+ }
23
+ const f = 1 / Math.tan(camera.fov * Math.PI / 360), n = camera.near, r = camera.far;
24
+ const projection = [f / camera.aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, r / (n - r), -1, 0, 0, r * n / (n - r), 0];
25
+ for (let i = 0; i < 16; i++) {
26
+ camera.projectionMatrix[i] = projection[i];
27
+ }
28
+ const projectionView = new DOMMatrix(camera.projectionMatrix).multiply(new DOMMatrix(camera.viewMatrix)).toFloat64Array();
29
+ for (let i = 0; i < 16; i++) {
30
+ camera.projectionViewMatrix[i] = projectionView[i];
31
+ }
32
+ }
@@ -0,0 +1,38 @@
1
+ import { lightGrid } from './lightGrid.js';
2
+ import { traverse } from './traverse.js';
3
+ import { worldLights } from './worldLights.js';
4
+ import { destroyTexture } from './destroyTexture.js';
5
+ import { bindSunlight } from './bindSunlight.js';
6
+
7
+ /** A cached spatial grid limits fragment lighting to nearby lamps, without dropping dense cells. */
8
+
9
+ export function updatePointLights(backend) {
10
+ const { device, sunlight: sun } = backend.renderer;
11
+ const meshes = [];
12
+ for (const layer of backend._layers) {
13
+ if (!layer.options.nightLight || !layer.root.visible) continue;
14
+ traverse(layer.root, mesh => {
15
+ if (!mesh.visible) return true;
16
+ if (mesh.extras?.lights) meshes.push(mesh);
17
+ });
18
+ }
19
+ if (sun.lightMeshes?.length === meshes.length && meshes.every((mesh, i) => sun.lightMeshes[i].mesh === mesh &&
20
+ sun.lightMeshes[i].lights === mesh.extras.lights && mesh.worldMatrix.every((v, j) => v === sun.lightMeshes[i].matrix[j]))) return;
21
+ // Per mesh, world-space [x, y, z, radius, r, g, b, intensity] per light: re-packed only when its lights or matrix change.
22
+ const packed = meshes.map(mesh => {
23
+ const cache = mesh._worldLights, m = mesh.worldMatrix, lights = mesh.extras.lights;
24
+ if (cache?.lights === lights && m.every((v, j) => v === cache.matrix[j])) return cache.data;
25
+ const values = worldLights(lights, m);
26
+ mesh._worldLights = { lights, matrix: [...m], data: values };
27
+ return values;
28
+ });
29
+ const { data, count } = lightGrid(packed, device.limits.maxStorageBufferBindingSize);
30
+ if (sun.lights.size < data.byteLength) {
31
+ destroyTexture(backend, sun.lights);
32
+ sun.lights = device.createBuffer({ size: data.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
33
+ bindSunlight(backend);
34
+ }
35
+ device.queue.writeBuffer(sun.lights, 0, data);
36
+ sun.lightCount = count;
37
+ sun.lightMeshes = meshes.map(mesh => ({ mesh, lights: mesh.extras.lights, matrix: [...mesh.worldMatrix] }));
38
+ }