@loaders.gl/tiles 4.5.3 → 5.0.0-alpha.10

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 (295) hide show
  1. package/LICENSE +14 -3
  2. package/README.md +136 -1
  3. package/dist/dist.dev.js +22570 -4947
  4. package/dist/dist.min.js +3 -1
  5. package/dist/image-set.d.ts +94 -0
  6. package/dist/image-set.d.ts.map +1 -0
  7. package/dist/image-set.js +243 -0
  8. package/dist/image-set.js.map +1 -0
  9. package/dist/index.cjs +10171 -2117
  10. package/dist/index.cjs.map +3 -4
  11. package/dist/index.d.ts +50 -9
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +28 -8
  14. package/dist/index.js.map +1 -1
  15. package/dist/point-cloud/point-cloud-tile.d.ts +47 -0
  16. package/dist/point-cloud/point-cloud-tile.d.ts.map +1 -0
  17. package/dist/point-cloud/point-cloud-tile.js +70 -0
  18. package/dist/point-cloud/point-cloud-tile.js.map +1 -0
  19. package/dist/point-cloud/point-cloud-tileset.d.ts +135 -0
  20. package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -0
  21. package/dist/point-cloud/point-cloud-tileset.js +549 -0
  22. package/dist/point-cloud/point-cloud-tileset.js.map +1 -0
  23. package/dist/point-cloud/types.d.ts +81 -0
  24. package/dist/point-cloud/types.d.ts.map +1 -0
  25. package/dist/point-cloud/types.js +5 -0
  26. package/dist/point-cloud/types.js.map +1 -0
  27. package/dist/raster-set.d.ts +124 -0
  28. package/dist/raster-set.d.ts.map +1 -0
  29. package/dist/raster-set.js +348 -0
  30. package/dist/raster-set.js.map +1 -0
  31. package/dist/spatial/format-spatial-reference.d.ts +68 -0
  32. package/dist/spatial/format-spatial-reference.d.ts.map +1 -0
  33. package/dist/spatial/format-spatial-reference.js +291 -0
  34. package/dist/spatial/format-spatial-reference.js.map +1 -0
  35. package/dist/spatial/get-spatial-coordinate-frame.d.ts +20 -0
  36. package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -0
  37. package/dist/spatial/get-spatial-coordinate-frame.js +139 -0
  38. package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -0
  39. package/dist/spatial/i3s-elevation.d.ts +20 -0
  40. package/dist/spatial/i3s-elevation.d.ts.map +1 -0
  41. package/dist/spatial/i3s-elevation.js +90 -0
  42. package/dist/spatial/i3s-elevation.js.map +1 -0
  43. package/dist/spatial/i3s-spatial-transformer.d.ts +189 -0
  44. package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -0
  45. package/dist/spatial/i3s-spatial-transformer.js +686 -0
  46. package/dist/spatial/i3s-spatial-transformer.js.map +1 -0
  47. package/dist/spatial/normalize-crs-identifier.d.ts +15 -0
  48. package/dist/spatial/normalize-crs-identifier.d.ts.map +1 -0
  49. package/dist/spatial/normalize-crs-identifier.js +22 -0
  50. package/dist/spatial/normalize-crs-identifier.js.map +1 -0
  51. package/dist/spatial/spatial-coordinate-transformer.d.ts +52 -0
  52. package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -0
  53. package/dist/spatial/spatial-coordinate-transformer.js +295 -0
  54. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -0
  55. package/dist/spatial/spatial-resource-registry.d.ts +44 -0
  56. package/dist/spatial/spatial-resource-registry.d.ts.map +1 -0
  57. package/dist/spatial/spatial-resource-registry.js +67 -0
  58. package/dist/spatial/spatial-resource-registry.js.map +1 -0
  59. package/dist/spatial/spatial-types.d.ts +190 -0
  60. package/dist/spatial/spatial-types.d.ts.map +1 -0
  61. package/dist/spatial/spatial-types.js +192 -0
  62. package/dist/spatial/spatial-types.js.map +1 -0
  63. package/dist/spatial/tiles-3d-spatial-transformer.d.ts +32 -0
  64. package/dist/spatial/tiles-3d-spatial-transformer.d.ts.map +1 -0
  65. package/dist/spatial/tiles-3d-spatial-transformer.js +175 -0
  66. package/dist/spatial/tiles-3d-spatial-transformer.js.map +1 -0
  67. package/dist/tileset-2d/adapter.d.ts +37 -0
  68. package/dist/tileset-2d/adapter.d.ts.map +1 -0
  69. package/dist/tileset-2d/adapter.js +5 -0
  70. package/dist/tileset-2d/adapter.js.map +1 -0
  71. package/dist/tileset-2d/index.d.ts +7 -0
  72. package/dist/tileset-2d/index.d.ts.map +1 -0
  73. package/dist/tileset-2d/index.js +6 -0
  74. package/dist/tileset-2d/index.js.map +1 -0
  75. package/dist/tileset-2d/tile-2d-header.d.ts +68 -0
  76. package/dist/tileset-2d/tile-2d-header.d.ts.map +1 -0
  77. package/dist/tileset-2d/tile-2d-header.js +181 -0
  78. package/dist/tileset-2d/tile-2d-header.js.map +1 -0
  79. package/dist/tileset-2d/tileset-2d.d.ts +200 -0
  80. package/dist/tileset-2d/tileset-2d.d.ts.map +1 -0
  81. package/dist/tileset-2d/tileset-2d.js +532 -0
  82. package/dist/tileset-2d/tileset-2d.js.map +1 -0
  83. package/dist/tileset-2d/types.d.ts +44 -0
  84. package/dist/tileset-2d/types.d.ts.map +1 -0
  85. package/dist/tileset-2d/types.js +5 -0
  86. package/dist/tileset-2d/types.js.map +1 -0
  87. package/dist/tileset-2d.d.ts +2 -0
  88. package/dist/tileset-2d.d.ts.map +1 -0
  89. package/dist/tileset-2d.js +5 -0
  90. package/dist/tileset-2d.js.map +1 -0
  91. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts +60 -0
  92. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts.map +1 -0
  93. package/dist/tileset-3d/common/indexed-archive-tileset-source.js +130 -0
  94. package/dist/tileset-3d/common/indexed-archive-tileset-source.js.map +1 -0
  95. package/dist/tileset-3d/common/tile-3d-contracts.d.ts +63 -0
  96. package/dist/tileset-3d/common/tile-3d-contracts.d.ts.map +1 -0
  97. package/dist/tileset-3d/common/tile-3d-contracts.js +51 -0
  98. package/dist/tileset-3d/common/tile-3d-contracts.js.map +1 -0
  99. package/dist/tileset-3d/common/tile-3d.d.ts +410 -0
  100. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -0
  101. package/dist/tileset-3d/common/tile-3d.js +1056 -0
  102. package/dist/tileset-3d/common/tile-3d.js.map +1 -0
  103. package/dist/tileset-3d/common/tileset-3d.d.ts +360 -0
  104. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -0
  105. package/dist/tileset-3d/common/tileset-3d.js +846 -0
  106. package/dist/tileset-3d/common/tileset-3d.js.map +1 -0
  107. package/dist/tileset-3d/common/tileset-cache.d.ts +29 -0
  108. package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -0
  109. package/dist/{tileset → tileset-3d/common}/tileset-cache.js +12 -4
  110. package/dist/tileset-3d/common/tileset-cache.js.map +1 -0
  111. package/dist/tileset-3d/common/tileset-content-traversal.d.ts +37 -0
  112. package/dist/tileset-3d/common/tileset-content-traversal.d.ts.map +1 -0
  113. package/dist/tileset-3d/common/tileset-content-traversal.js +80 -0
  114. package/dist/tileset-3d/common/tileset-content-traversal.js.map +1 -0
  115. package/dist/tileset-3d/common/tileset-observability.d.ts +60 -0
  116. package/dist/tileset-3d/common/tileset-observability.d.ts.map +1 -0
  117. package/dist/tileset-3d/common/tileset-observability.js +39 -0
  118. package/dist/tileset-3d/common/tileset-observability.js.map +1 -0
  119. package/dist/tileset-3d/common/tileset-source.d.ts +228 -0
  120. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -0
  121. package/dist/tileset-3d/common/tileset-source.js +17 -0
  122. package/dist/tileset-3d/common/tileset-source.js.map +1 -0
  123. package/dist/{tileset → tileset-3d/common}/tileset-traverser.d.ts +35 -3
  124. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -0
  125. package/dist/{tileset → tileset-3d/common}/tileset-traverser.js +61 -13
  126. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -0
  127. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +182 -0
  128. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
  129. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +681 -0
  130. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
  131. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +221 -0
  132. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -0
  133. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +770 -0
  134. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -0
  135. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +110 -0
  136. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -0
  137. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +331 -0
  138. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -0
  139. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -0
  140. package/dist/{tileset → tileset-3d}/format-i3s/i3s-pending-tiles-register.js +3 -0
  141. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -0
  142. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +116 -0
  143. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -0
  144. package/dist/tileset-3d/format-i3s/i3s-source.js +450 -0
  145. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -0
  146. package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -0
  147. package/dist/{tileset → tileset-3d}/format-i3s/i3s-tile-manager.js +5 -2
  148. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js.map +1 -0
  149. package/dist/{tileset → tileset-3d}/format-i3s/i3s-tileset-traverser.d.ts +2 -2
  150. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -0
  151. package/dist/{tileset → tileset-3d}/format-i3s/i3s-tileset-traverser.js +17 -23
  152. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -0
  153. package/dist/{tileset → tileset-3d}/helpers/3d-tiles-options.d.ts +1 -1
  154. package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -0
  155. package/dist/{tileset → tileset-3d}/helpers/3d-tiles-options.js +3 -0
  156. package/dist/tileset-3d/helpers/3d-tiles-options.js.map +1 -0
  157. package/dist/{tileset → tileset-3d}/helpers/bounding-volume.d.ts +7 -3
  158. package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -0
  159. package/dist/{tileset → tileset-3d}/helpers/bounding-volume.js +20 -91
  160. package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -0
  161. package/dist/tileset-3d/helpers/frame-state.d.ts +93 -0
  162. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -0
  163. package/dist/{tileset → tileset-3d}/helpers/frame-state.js +92 -10
  164. package/dist/tileset-3d/helpers/frame-state.js.map +1 -0
  165. package/dist/{tileset → tileset-3d}/helpers/i3s-lod.d.ts +1 -1
  166. package/dist/tileset-3d/helpers/i3s-lod.d.ts.map +1 -0
  167. package/dist/{tileset → tileset-3d}/helpers/i3s-lod.js +21 -9
  168. package/dist/tileset-3d/helpers/i3s-lod.js.map +1 -0
  169. package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts +17 -0
  170. package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts.map +1 -0
  171. package/dist/tileset-3d/helpers/s2-bounding-volume.js +20 -0
  172. package/dist/tileset-3d/helpers/s2-bounding-volume.js.map +1 -0
  173. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts +72 -0
  174. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -0
  175. package/dist/tileset-3d/helpers/tiles-3d-lod.js +257 -0
  176. package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -0
  177. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
  178. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
  179. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
  180. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
  181. package/dist/{tileset → tileset-3d}/helpers/transform-utils.d.ts +1 -1
  182. package/dist/tileset-3d/helpers/transform-utils.d.ts.map +1 -0
  183. package/dist/tileset-3d/helpers/transform-utils.js.map +1 -0
  184. package/dist/tileset-3d/helpers/zoom.d.ts.map +1 -0
  185. package/dist/tileset-3d/helpers/zoom.js.map +1 -0
  186. package/dist/types.d.ts +14 -0
  187. package/dist/types.d.ts.map +1 -1
  188. package/dist/types.js +3 -0
  189. package/dist/types.js.map +1 -1
  190. package/dist/utils/doubly-linked-list.d.ts.map +1 -1
  191. package/dist/utils/doubly-linked-list.js +1 -0
  192. package/dist/utils/doubly-linked-list.js.map +1 -1
  193. package/dist/utils/managed-array.d.ts.map +1 -1
  194. package/dist/utils/managed-array.js +1 -0
  195. package/dist/utils/managed-array.js.map +1 -1
  196. package/package.json +12 -9
  197. package/src/image-set.ts +328 -0
  198. package/src/index.ts +157 -9
  199. package/src/point-cloud/point-cloud-tile.ts +80 -0
  200. package/src/point-cloud/point-cloud-tileset.ts +767 -0
  201. package/src/point-cloud/types.ts +99 -0
  202. package/src/raster-set.ts +504 -0
  203. package/src/spatial/format-spatial-reference.ts +385 -0
  204. package/src/spatial/get-spatial-coordinate-frame.ts +169 -0
  205. package/src/spatial/i3s-elevation.ts +96 -0
  206. package/src/spatial/i3s-spatial-transformer.ts +957 -0
  207. package/src/spatial/normalize-crs-identifier.ts +26 -0
  208. package/src/spatial/spatial-coordinate-transformer.ts +376 -0
  209. package/src/spatial/spatial-resource-registry.ts +79 -0
  210. package/src/spatial/spatial-types.ts +422 -0
  211. package/src/spatial/tiles-3d-spatial-transformer.ts +206 -0
  212. package/src/tileset-2d/adapter.ts +46 -0
  213. package/src/tileset-2d/index.ts +33 -0
  214. package/src/tileset-2d/tile-2d-header.ts +224 -0
  215. package/src/tileset-2d/tileset-2d.ts +715 -0
  216. package/src/tileset-2d/types.ts +39 -0
  217. package/src/tileset-2d.ts +5 -0
  218. package/src/tileset-3d/common/indexed-archive-tileset-source.ts +205 -0
  219. package/src/tileset-3d/common/tile-3d-contracts.ts +115 -0
  220. package/src/tileset-3d/common/tile-3d.ts +1310 -0
  221. package/src/tileset-3d/common/tileset-3d.ts +1166 -0
  222. package/src/{tileset → tileset-3d/common}/tileset-cache.ts +16 -8
  223. package/src/tileset-3d/common/tileset-content-traversal.ts +114 -0
  224. package/src/tileset-3d/common/tileset-observability.ts +100 -0
  225. package/src/tileset-3d/common/tileset-source.ts +284 -0
  226. package/src/{tileset → tileset-3d/common}/tileset-traverser.ts +74 -14
  227. package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +1059 -0
  228. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +1025 -0
  229. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +379 -0
  230. package/src/{tileset → tileset-3d}/format-i3s/i3s-pending-tiles-register.ts +4 -0
  231. package/src/tileset-3d/format-i3s/i3s-source.ts +557 -0
  232. package/src/{tileset → tileset-3d}/format-i3s/i3s-tile-manager.ts +6 -2
  233. package/src/{tileset → tileset-3d}/format-i3s/i3s-tileset-traverser.ts +20 -24
  234. package/src/{tileset → tileset-3d}/helpers/3d-tiles-options.ts +5 -1
  235. package/src/{tileset → tileset-3d}/helpers/bounding-volume.ts +31 -114
  236. package/src/tileset-3d/helpers/frame-state.ts +322 -0
  237. package/src/{tileset → tileset-3d}/helpers/i3s-lod.ts +24 -10
  238. package/src/tileset-3d/helpers/s2-bounding-volume.ts +35 -0
  239. package/src/tileset-3d/helpers/tiles-3d-lod.ts +370 -0
  240. package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
  241. package/src/{tileset → tileset-3d}/helpers/transform-utils.ts +1 -1
  242. package/src/types.ts +18 -0
  243. package/src/utils/doubly-linked-list.ts +2 -0
  244. package/src/utils/managed-array.ts +2 -0
  245. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.d.ts +0 -7
  246. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.d.ts.map +0 -1
  247. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.js +0 -53
  248. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.js.map +0 -1
  249. package/dist/tileset/format-i3s/i3s-pending-tiles-register.d.ts.map +0 -1
  250. package/dist/tileset/format-i3s/i3s-pending-tiles-register.js.map +0 -1
  251. package/dist/tileset/format-i3s/i3s-tile-manager.d.ts.map +0 -1
  252. package/dist/tileset/format-i3s/i3s-tile-manager.js.map +0 -1
  253. package/dist/tileset/format-i3s/i3s-tileset-traverser.d.ts.map +0 -1
  254. package/dist/tileset/format-i3s/i3s-tileset-traverser.js.map +0 -1
  255. package/dist/tileset/helpers/3d-tiles-options.d.ts.map +0 -1
  256. package/dist/tileset/helpers/3d-tiles-options.js.map +0 -1
  257. package/dist/tileset/helpers/bounding-volume.d.ts.map +0 -1
  258. package/dist/tileset/helpers/bounding-volume.js.map +0 -1
  259. package/dist/tileset/helpers/frame-state.d.ts +0 -28
  260. package/dist/tileset/helpers/frame-state.d.ts.map +0 -1
  261. package/dist/tileset/helpers/frame-state.js.map +0 -1
  262. package/dist/tileset/helpers/i3s-lod.d.ts.map +0 -1
  263. package/dist/tileset/helpers/i3s-lod.js.map +0 -1
  264. package/dist/tileset/helpers/tiles-3d-lod.d.ts +0 -8
  265. package/dist/tileset/helpers/tiles-3d-lod.d.ts.map +0 -1
  266. package/dist/tileset/helpers/tiles-3d-lod.js +0 -113
  267. package/dist/tileset/helpers/tiles-3d-lod.js.map +0 -1
  268. package/dist/tileset/helpers/transform-utils.d.ts.map +0 -1
  269. package/dist/tileset/helpers/transform-utils.js.map +0 -1
  270. package/dist/tileset/helpers/zoom.d.ts.map +0 -1
  271. package/dist/tileset/helpers/zoom.js.map +0 -1
  272. package/dist/tileset/tile-3d.d.ts +0 -230
  273. package/dist/tileset/tile-3d.d.ts.map +0 -1
  274. package/dist/tileset/tile-3d.js +0 -631
  275. package/dist/tileset/tile-3d.js.map +0 -1
  276. package/dist/tileset/tileset-3d.d.ts +0 -284
  277. package/dist/tileset/tileset-3d.d.ts.map +0 -1
  278. package/dist/tileset/tileset-3d.js +0 -814
  279. package/dist/tileset/tileset-3d.js.map +0 -1
  280. package/dist/tileset/tileset-cache.d.ts +0 -19
  281. package/dist/tileset/tileset-cache.d.ts.map +0 -1
  282. package/dist/tileset/tileset-cache.js.map +0 -1
  283. package/dist/tileset/tileset-traverser.d.ts.map +0 -1
  284. package/dist/tileset/tileset-traverser.js.map +0 -1
  285. package/src/tileset/format-3d-tiles/tileset-3d-traverser.ts +0 -63
  286. package/src/tileset/helpers/frame-state.ts +0 -183
  287. package/src/tileset/helpers/tiles-3d-lod.ts +0 -149
  288. package/src/tileset/tile-3d.ts +0 -788
  289. package/src/tileset/tileset-3d.ts +0 -1037
  290. /package/dist/{tileset → tileset-3d}/format-i3s/i3s-pending-tiles-register.d.ts +0 -0
  291. /package/dist/{tileset → tileset-3d}/format-i3s/i3s-tile-manager.d.ts +0 -0
  292. /package/dist/{tileset → tileset-3d}/helpers/transform-utils.js +0 -0
  293. /package/dist/{tileset → tileset-3d}/helpers/zoom.d.ts +0 -0
  294. /package/dist/{tileset → tileset-3d}/helpers/zoom.js +0 -0
  295. /package/src/{tileset → tileset-3d}/helpers/zoom.ts +0 -0
@@ -0,0 +1,767 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Vector3} from '@math.gl/core';
6
+ import {BoundingSphere, CullingVolume, Plane} from '@math.gl/culling';
7
+ import type {Viewport} from '../types';
8
+ import {PointCloudTile} from './point-cloud-tile';
9
+ import type {
10
+ PointCloudBoundingVolume,
11
+ PointCloudTileHeader,
12
+ PointCloudTilesetSource
13
+ } from './types';
14
+
15
+ export type PointCloudTilesetOptions = {
16
+ debounceTime?: number;
17
+ minimumNodePixelSize?: number;
18
+ maximumScreenSpaceError?: number;
19
+ /** Override source LOD metric for point-cloud traversal. */
20
+ lodSelectionMetricType?: 'maxScreenThresholdSQ' | 'density-threshold';
21
+ /** Minimum projected point density at which a node is refined. */
22
+ densityThreshold?: number;
23
+ pointBudget?: number;
24
+ maxDepth?: number;
25
+ onTileLoad?: (tile: PointCloudTile) => void;
26
+ onTileError?: (tile: PointCloudTile, error: Error) => void;
27
+ onTraversalComplete?: (selectedTiles: PointCloudTile[]) => PointCloudTile[];
28
+ onUpdate?: () => void;
29
+ };
30
+
31
+ type PointCloudTilesetProps = Required<PointCloudTilesetOptions>;
32
+
33
+ const DEFAULT_PROPS: PointCloudTilesetProps = {
34
+ debounceTime: 0,
35
+ minimumNodePixelSize: 150,
36
+ maximumScreenSpaceError: 1,
37
+ lodSelectionMetricType: 'maxScreenThresholdSQ',
38
+ densityThreshold: 1,
39
+ pointBudget: 2_000_000,
40
+ maxDepth: Number.POSITIVE_INFINITY,
41
+ onTileLoad: () => {},
42
+ onTileError: () => {},
43
+ onTraversalComplete: (selectedTiles: PointCloudTile[]) => selectedTiles,
44
+ onUpdate: () => {}
45
+ };
46
+
47
+ const VISIBILITY_MARGIN_PIXELS = 64;
48
+
49
+ type TraversalCandidate = {
50
+ tile: PointCloudTile;
51
+ weight: number;
52
+ };
53
+
54
+ type ProjectedBounds = {
55
+ minX: number;
56
+ minY: number;
57
+ maxX: number;
58
+ maxY: number;
59
+ };
60
+
61
+ type ProjectedFootprint = {
62
+ x: number;
63
+ y: number;
64
+ radius: number;
65
+ };
66
+
67
+ type FrustumPlane = {
68
+ distance: number;
69
+ normal: number[] | Vector3;
70
+ };
71
+
72
+ type PointCloudViewport = Viewport & {
73
+ cameraPosition?: number[] | Vector3;
74
+ getFrustumPlanes?: () => Record<string, FrustumPlane>;
75
+ projectPosition?: (coordinates: number[] | Vector3) => number[];
76
+ projectionMatrix?: number[];
77
+ };
78
+
79
+ type TraversalContext = {
80
+ viewport: Viewport;
81
+ cullingVolume: CullingVolume | null;
82
+ };
83
+
84
+ /**
85
+ * A minimal point-cloud-oriented tileset manager backed by a DataSource.
86
+ */
87
+ export class PointCloudTileset {
88
+ readonly dataSource: PointCloudTilesetSource;
89
+ options: PointCloudTilesetProps;
90
+ root: PointCloudTile | null = null;
91
+ cartographicCenter: Vector3 | null = null;
92
+ zoom = 1;
93
+ boundingVolume: PointCloudBoundingVolume | null = null;
94
+ visibleTilesCount = 0;
95
+
96
+ private frameNumberValue = 0;
97
+ private pendingCount = 0;
98
+ private lastUpdatedViewports: Viewport[] | Viewport | null = null;
99
+ private updatePromise: Promise<number> | null = null;
100
+ private readonly tilesMap: Map<string, PointCloudTile> = new Map();
101
+
102
+ constructor(
103
+ dataSource: PointCloudTilesetSource,
104
+ options: Partial<PointCloudTilesetOptions> = {}
105
+ ) {
106
+ this.dataSource = dataSource;
107
+ this.options = {...DEFAULT_PROPS, ...options};
108
+ this.tilesetInitializationPromise = this.initialize();
109
+ }
110
+
111
+ readonly tilesetInitializationPromise: Promise<void>;
112
+
113
+ /**
114
+ * Whether the tileset finished initialization.
115
+ */
116
+ get isReady(): boolean {
117
+ return this.dataSource.isReady;
118
+ }
119
+
120
+ /**
121
+ * Runtime frame number.
122
+ */
123
+ get frameNumber(): number {
124
+ return this.frameNumberValue;
125
+ }
126
+
127
+ /**
128
+ * All discovered tiles.
129
+ */
130
+ get tiles(): PointCloudTile[] {
131
+ return Array.from(this.tilesMap.values());
132
+ }
133
+
134
+ /**
135
+ * Whether all currently selected tiles have finished loading.
136
+ */
137
+ isLoaded(): boolean {
138
+ return (
139
+ this.frameNumberValue > 0 &&
140
+ this.pendingCount === 0 &&
141
+ this.selectedTiles.every(tile => tile.contentAvailable || tile.contentFailed)
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Currently selected tiles.
147
+ */
148
+ selectedTiles: PointCloudTile[] = [];
149
+
150
+ /**
151
+ * Fire-and-forget update helper.
152
+ */
153
+ update(viewports: Viewport[] | Viewport | null = null): void {
154
+ this.selectTiles(viewports).catch(() => undefined);
155
+ }
156
+
157
+ /**
158
+ * Releases traversal state held by this tileset.
159
+ */
160
+ destroy(): void {
161
+ this.selectedTiles = [];
162
+ this.tilesMap.clear();
163
+ this.root = null;
164
+ this.cartographicCenter = null;
165
+ this.boundingVolume = null;
166
+ this.visibleTilesCount = 0;
167
+ this.pendingCount = 0;
168
+ this.lastUpdatedViewports = null;
169
+ this.updatePromise = null;
170
+ }
171
+
172
+ /**
173
+ * Initialize the underlying source and the root tile.
174
+ */
175
+ async initialize(): Promise<void> {
176
+ await this.dataSource.initialize();
177
+ const rootHeader = await this.dataSource.getRootTile();
178
+ this.root = this.getOrCreateTile(rootHeader, null);
179
+
180
+ const viewState = await this.dataSource.getViewState?.();
181
+ this.boundingVolume = viewState?.boundingVolume || this.root.boundingVolume;
182
+
183
+ const center = viewState?.cartographicCenter || this.root.boundingVolume.center;
184
+ this.cartographicCenter = new Vector3(center);
185
+ this.zoom = viewState?.zoom || this.estimateZoom(this.boundingVolume);
186
+ }
187
+
188
+ /**
189
+ * Debounced async tile selection.
190
+ */
191
+ async selectTiles(viewports: Viewport[] | Viewport | null = null): Promise<number> {
192
+ await this.tilesetInitializationPromise;
193
+ if (viewports) {
194
+ this.lastUpdatedViewports = viewports;
195
+ }
196
+
197
+ if (!this.updatePromise) {
198
+ this.updatePromise = new Promise<number>((resolve, reject) => {
199
+ setTimeout(() => {
200
+ this.doSelectTiles()
201
+ .then(() => resolve(this.frameNumberValue))
202
+ .catch(reject)
203
+ .finally(() => {
204
+ this.updatePromise = null;
205
+ });
206
+ }, this.options.debounceTime);
207
+ });
208
+ }
209
+
210
+ return this.updatePromise;
211
+ }
212
+
213
+ private async doSelectTiles(): Promise<void> {
214
+ if (!this.root || !this.lastUpdatedViewports) {
215
+ return;
216
+ }
217
+
218
+ const previousSelectedIds = new Set(this.selectedTiles.map(tile => tile.id));
219
+ for (const tile of this.tilesMap.values()) {
220
+ tile.clearSelection();
221
+ }
222
+
223
+ this.frameNumberValue++;
224
+
225
+ const preparedViewports = Array.isArray(this.lastUpdatedViewports)
226
+ ? this.lastUpdatedViewports
227
+ : [this.lastUpdatedViewports];
228
+
229
+ const selectedTilesById = new Map<string, PointCloudTile>();
230
+ const visibleTileIds = new Set<string>();
231
+ let selectedPointCount = 0;
232
+ for (const viewport of preparedViewports) {
233
+ const traversalContext = this.getTraversalContext(viewport);
234
+ selectedPointCount = await this.traverseTiles(
235
+ traversalContext,
236
+ selectedTilesById,
237
+ selectedPointCount,
238
+ visibleTileIds
239
+ );
240
+ }
241
+ this.visibleTilesCount = visibleTileIds.size;
242
+
243
+ this.selectedTiles = this.options.onTraversalComplete(Array.from(selectedTilesById.values()));
244
+
245
+ for (const tile of this.selectedTiles) {
246
+ this.loadTile(tile).catch(() => undefined);
247
+ }
248
+
249
+ const nextSelectedIds = new Set(this.selectedTiles.map(tile => tile.id));
250
+ if (!this.haveSameIds(previousSelectedIds, nextSelectedIds)) {
251
+ this.options.onUpdate();
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Traverses visible tiles in descending projected-size order, similar to Potree's viewer update loop.
257
+ */
258
+ private async traverseTiles(
259
+ traversalContext: TraversalContext,
260
+ selectedTilesById: Map<string, PointCloudTile>,
261
+ initialSelectedPointCount: number,
262
+ visibleTileIds: Set<string>
263
+ ): Promise<number> {
264
+ if (!this.root) {
265
+ return initialSelectedPointCount;
266
+ }
267
+
268
+ let selectedPointCount = initialSelectedPointCount;
269
+ const traversalQueue: TraversalCandidate[] = [
270
+ {tile: this.root, weight: this.estimateScreenRadius(this.root, traversalContext.viewport)}
271
+ ];
272
+
273
+ while (traversalQueue.length > 0) {
274
+ traversalQueue.sort((candidateA, candidateB) => candidateB.weight - candidateA.weight);
275
+ const candidate = traversalQueue.shift();
276
+ if (!candidate) {
277
+ break;
278
+ }
279
+
280
+ const {tile, weight} = candidate;
281
+ if (!this.isVisible(tile, traversalContext)) {
282
+ continue;
283
+ }
284
+ visibleTileIds.add(tile.id);
285
+
286
+ if (!selectedTilesById.has(tile.id) && tile.pointCount > 0) {
287
+ const exceedsPointBudget =
288
+ selectedPointCount > 0 && selectedPointCount + tile.pointCount > this.options.pointBudget;
289
+ if (exceedsPointBudget) {
290
+ break;
291
+ }
292
+
293
+ selectedTilesById.set(tile.id, tile);
294
+ selectedPointCount += tile.pointCount;
295
+ }
296
+
297
+ if (selectedTilesById.has(tile.id)) {
298
+ tile.setSelected(traversalContext.viewport.id);
299
+ }
300
+
301
+ if (!this.shouldRefine(tile, weight)) {
302
+ continue;
303
+ }
304
+
305
+ const visibleChildren = await this.getVisibleChildren(tile, traversalContext);
306
+ if (visibleChildren.length > 0) {
307
+ traversalQueue.push(...visibleChildren);
308
+ }
309
+ }
310
+
311
+ return selectedPointCount;
312
+ }
313
+
314
+ /** Whether a tile should refine to children based on its projected screen radius. */
315
+ private shouldRefine(tile: PointCloudTile, traversalWeight: number): boolean {
316
+ if (
317
+ tile.level < this.options.maxDepth &&
318
+ Number.isFinite(traversalWeight) &&
319
+ (tile.header.lodSelectionMetricType || this.options.lodSelectionMetricType) ===
320
+ 'density-threshold'
321
+ ) {
322
+ const projectedArea = Math.max(1, Math.PI * traversalWeight * traversalWeight);
323
+ const density = tile.pointCount / projectedArea;
324
+ const threshold = tile.header.lodThreshold || this.options.densityThreshold;
325
+ return density < threshold;
326
+ }
327
+ return (
328
+ tile.level < this.options.maxDepth &&
329
+ Number.isFinite(traversalWeight) &&
330
+ traversalWeight >= this.options.minimumNodePixelSize
331
+ );
332
+ }
333
+
334
+ /**
335
+ * Build per-viewport traversal state once per traversal pass.
336
+ */
337
+ private getTraversalContext(viewport: Viewport): TraversalContext {
338
+ const cullingVolume = this.getCullingVolume(viewport as PointCloudViewport);
339
+ return {
340
+ viewport,
341
+ cullingVolume: this.shouldUseCullingVolume(viewport, cullingVolume) ? cullingVolume : null
342
+ };
343
+ }
344
+
345
+ /**
346
+ * Check tile visibility against the viewport frustum when possible.
347
+ */
348
+ private isVisible(tile: PointCloudTile, traversalContext: TraversalContext): boolean {
349
+ if (traversalContext.cullingVolume) {
350
+ const boundingSphere = this.getCommonSpaceBoundingSphere(
351
+ tile.boundingVolume,
352
+ traversalContext.viewport as PointCloudViewport
353
+ );
354
+
355
+ if (boundingSphere) {
356
+ return this.isBoundingSphereInsideCullingVolume(
357
+ boundingSphere,
358
+ traversalContext.cullingVolume
359
+ );
360
+ }
361
+ }
362
+
363
+ const footprint = this.getProjectedFootprint(tile.boundingVolume, traversalContext.viewport);
364
+ if (!footprint) {
365
+ return false;
366
+ }
367
+
368
+ return this.isProjectedFootprintVisible(footprint, traversalContext.viewport);
369
+ }
370
+
371
+ /**
372
+ * Returns true when the viewport frustum appears usable for this tileset's coordinate space.
373
+ */
374
+ private shouldUseCullingVolume(
375
+ viewport: Viewport,
376
+ cullingVolume: CullingVolume | null
377
+ ): cullingVolume is CullingVolume {
378
+ if (!this.root || !cullingVolume) {
379
+ return false;
380
+ }
381
+
382
+ const rootBoundingSphere = this.getCommonSpaceBoundingSphere(
383
+ this.root.boundingVolume,
384
+ viewport as PointCloudViewport
385
+ );
386
+ if (!rootBoundingSphere) {
387
+ return false;
388
+ }
389
+
390
+ if (this.isBoundingSphereInsideCullingVolume(rootBoundingSphere, cullingVolume)) {
391
+ return true;
392
+ }
393
+
394
+ const projectedFootprint = this.getProjectedFootprint(this.root.boundingVolume, viewport);
395
+ return !projectedFootprint || !this.isProjectedFootprintVisible(projectedFootprint, viewport);
396
+ }
397
+
398
+ /**
399
+ * Checks whether a common-space bounding sphere intersects a culling volume.
400
+ */
401
+ private isBoundingSphereInsideCullingVolume(
402
+ boundingSphere: BoundingSphere,
403
+ cullingVolume: CullingVolume
404
+ ): boolean {
405
+ return (
406
+ cullingVolume.computeVisibilityWithPlaneMask(
407
+ boundingSphere,
408
+ CullingVolume.MASK_INDETERMINATE
409
+ ) !== CullingVolume.MASK_OUTSIDE
410
+ );
411
+ }
412
+
413
+ /**
414
+ * Checks a projected screen-space footprint against the viewport rectangle.
415
+ */
416
+ private isProjectedFootprintVisible(footprint: ProjectedFootprint, viewport: Viewport): boolean {
417
+ return !(
418
+ footprint.x + footprint.radius < -VISIBILITY_MARGIN_PIXELS ||
419
+ footprint.x - footprint.radius > viewport.width + VISIBILITY_MARGIN_PIXELS ||
420
+ footprint.y + footprint.radius < -VISIBILITY_MARGIN_PIXELS ||
421
+ footprint.y - footprint.radius > viewport.height + VISIBILITY_MARGIN_PIXELS
422
+ );
423
+ }
424
+
425
+ /** Estimate traversal priority from projected node radius in screen pixels. */
426
+ private estimateScreenRadius(tile: PointCloudTile, viewport: Viewport): number {
427
+ const footprint = this.getProjectedFootprint(tile.boundingVolume, viewport);
428
+ return footprint ? footprint.radius : 0;
429
+ }
430
+
431
+ /**
432
+ * Project a tile AABB into screen space and return its 2D footprint.
433
+ */
434
+ private projectBounds(
435
+ boundingVolume: PointCloudBoundingVolume,
436
+ viewport: Viewport
437
+ ): ProjectedBounds | null {
438
+ const corners = this.getBoundingVolumeCorners(boundingVolume);
439
+
440
+ let minX = Number.POSITIVE_INFINITY;
441
+ let minY = Number.POSITIVE_INFINITY;
442
+ let maxX = Number.NEGATIVE_INFINITY;
443
+ let maxY = Number.NEGATIVE_INFINITY;
444
+
445
+ try {
446
+ for (const corner of corners) {
447
+ const [x, y] = viewport.project(corner);
448
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
449
+ continue;
450
+ }
451
+ minX = Math.min(minX, x);
452
+ minY = Math.min(minY, y);
453
+ maxX = Math.max(maxX, x);
454
+ maxY = Math.max(maxY, y);
455
+ }
456
+ } catch {
457
+ return null;
458
+ }
459
+
460
+ if (
461
+ !Number.isFinite(minX) ||
462
+ !Number.isFinite(minY) ||
463
+ !Number.isFinite(maxX) ||
464
+ !Number.isFinite(maxY)
465
+ ) {
466
+ return null;
467
+ }
468
+
469
+ return {minX, minY, maxX, maxY};
470
+ }
471
+
472
+ /**
473
+ * Project a tile to a screen-space center/radius footprint.
474
+ */
475
+ private getProjectedFootprint(
476
+ boundingVolume: PointCloudBoundingVolume,
477
+ viewport: Viewport
478
+ ): ProjectedFootprint | null {
479
+ let centerX = Number.NaN;
480
+ let centerY = Number.NaN;
481
+
482
+ try {
483
+ [centerX, centerY] = viewport.project(boundingVolume.center);
484
+ } catch {
485
+ return null;
486
+ }
487
+
488
+ if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) {
489
+ return null;
490
+ }
491
+
492
+ const corners = this.getBoundingVolumeCorners(boundingVolume);
493
+ let projectedRadius = 0;
494
+
495
+ try {
496
+ for (const corner of corners) {
497
+ const [x, y] = viewport.project(corner);
498
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
499
+ continue;
500
+ }
501
+
502
+ projectedRadius = Math.max(projectedRadius, Math.hypot(x - centerX, y - centerY));
503
+ }
504
+ } catch {
505
+ return null;
506
+ }
507
+
508
+ if (projectedRadius === 0) {
509
+ const fallbackBounds = this.projectBounds(boundingVolume, viewport);
510
+ if (fallbackBounds) {
511
+ projectedRadius =
512
+ Math.max(
513
+ fallbackBounds.maxX - fallbackBounds.minX,
514
+ fallbackBounds.maxY - fallbackBounds.minY
515
+ ) / 2;
516
+ }
517
+ }
518
+
519
+ return {x: centerX, y: centerY, radius: projectedRadius};
520
+ }
521
+
522
+ /**
523
+ * Returns visible children ordered by their projected importance.
524
+ */
525
+ private async getVisibleChildren(
526
+ tile: PointCloudTile,
527
+ traversalContext: TraversalContext
528
+ ): Promise<TraversalCandidate[]> {
529
+ if (tile.level >= this.options.maxDepth) {
530
+ return [];
531
+ }
532
+
533
+ const children = await this.getChildren(tile);
534
+
535
+ return children
536
+ .map(child => ({
537
+ tile: child,
538
+ weight: this.estimateScreenRadius(child, traversalContext.viewport)
539
+ }))
540
+ .filter(candidate => this.isVisible(candidate.tile, traversalContext));
541
+ }
542
+
543
+ private async getChildren(tile: PointCloudTile): Promise<PointCloudTile[]> {
544
+ if (tile.childrenLoaded) {
545
+ return tile.children;
546
+ }
547
+
548
+ if (!tile.childrenPromise) {
549
+ const childrenPromise = this.dataSource
550
+ .getChildren(tile.header)
551
+ .then(headers => headers.map(header => this.getOrCreateTile(header, tile)))
552
+ .then(children => {
553
+ tile.children = children;
554
+ tile.childrenLoaded = true;
555
+ return children;
556
+ });
557
+
558
+ tile.childrenPromise = childrenPromise.finally(() => {
559
+ if (tile.childrenPromise === childrenPromise) {
560
+ tile.childrenPromise = null;
561
+ }
562
+ });
563
+ }
564
+
565
+ return tile.childrenPromise;
566
+ }
567
+
568
+ private getOrCreateTile(
569
+ header: PointCloudTileHeader,
570
+ parent: PointCloudTile | null
571
+ ): PointCloudTile {
572
+ const existingTile = this.tilesMap.get(header.id);
573
+ if (existingTile) {
574
+ existingTile.header = header;
575
+ if (parent && existingTile.parent !== parent) {
576
+ existingTile.parent = parent;
577
+ }
578
+ return existingTile;
579
+ }
580
+
581
+ const tile = new PointCloudTile(header, parent);
582
+ this.tilesMap.set(header.id, tile);
583
+ if (parent && !parent.children.find(child => child.id === tile.id)) {
584
+ parent.children.push(tile);
585
+ }
586
+ return tile;
587
+ }
588
+
589
+ private async loadTile(tile: PointCloudTile): Promise<void> {
590
+ if (tile.contentAvailable || tile.isContentLoading || tile.contentFailed) {
591
+ return;
592
+ }
593
+
594
+ tile.isContentLoading = true;
595
+ this.pendingCount++;
596
+
597
+ try {
598
+ const content = await this.dataSource.loadTileContent(tile.header);
599
+ tile.content = content;
600
+ tile.contentAvailable = true;
601
+ if (content) {
602
+ this.options.onTileLoad(tile);
603
+ }
604
+ } catch (error) {
605
+ tile.contentFailed = true;
606
+ this.options.onTileError(tile, error as Error);
607
+ } finally {
608
+ tile.isContentLoading = false;
609
+ this.pendingCount--;
610
+ }
611
+ }
612
+
613
+ private estimateZoom(boundingVolume: PointCloudBoundingVolume | null): number {
614
+ if (!boundingVolume) {
615
+ return 1;
616
+ }
617
+ if (boundingVolume.coordinateFrame === 'cartesian') {
618
+ return 1;
619
+ }
620
+
621
+ const [minBounds, maxBounds] = boundingVolume.cartographicBounds;
622
+ const longitudeSpan = boundingVolume.coversFullLongitude
623
+ ? 360
624
+ : boundingVolume.wrapsDateline
625
+ ? 360 - Math.abs(maxBounds[0] - minBounds[0])
626
+ : Math.abs(maxBounds[0] - minBounds[0]);
627
+ const normalizedLongitudeSpan = Math.max(longitudeSpan, 0.000001);
628
+ return Math.max(1, Math.round(Math.log2(360 / normalizedLongitudeSpan)));
629
+ }
630
+
631
+ private haveSameIds(idsA: Set<string>, idsB: Set<string>): boolean {
632
+ if (idsA.size !== idsB.size) {
633
+ return false;
634
+ }
635
+
636
+ for (const id of idsA) {
637
+ if (!idsB.has(id)) {
638
+ return false;
639
+ }
640
+ }
641
+
642
+ return true;
643
+ }
644
+
645
+ /**
646
+ * Convert a tile bounding box into its eight corners.
647
+ */
648
+ private getBoundingVolumeCorners(boundingVolume: PointCloudBoundingVolume): number[][] {
649
+ const [minBounds, maxBounds] = boundingVolume.cartographicBounds;
650
+ const longitudeIntervals = boundingVolume.coversFullLongitude
651
+ ? [[-180, 180]]
652
+ : boundingVolume.wrapsDateline
653
+ ? [
654
+ [minBounds[0], 180],
655
+ [-180, maxBounds[0]]
656
+ ]
657
+ : [[minBounds[0], maxBounds[0]]];
658
+ const corners: number[][] = [];
659
+ const wrappedLongitudeSpan = 360 - Math.abs(maxBounds[0] - minBounds[0]);
660
+ const longitudeReference = minBounds[0] + wrappedLongitudeSpan / 2;
661
+ for (const [minimumLongitude, maximumLongitude] of longitudeIntervals) {
662
+ const normalizeLongitude = (longitude: number) => {
663
+ if (!boundingVolume.wrapsDateline) {
664
+ return longitude;
665
+ }
666
+ let normalizedLongitude = longitude;
667
+ while (normalizedLongitude - longitudeReference > 180) {
668
+ normalizedLongitude -= 360;
669
+ }
670
+ while (normalizedLongitude - longitudeReference < -180) {
671
+ normalizedLongitude += 360;
672
+ }
673
+ return normalizedLongitude;
674
+ };
675
+ const unwrappedMinimumLongitude = normalizeLongitude(minimumLongitude);
676
+ const unwrappedMaximumLongitude = normalizeLongitude(maximumLongitude);
677
+ corners.push(
678
+ [unwrappedMinimumLongitude, minBounds[1], minBounds[2] || 0],
679
+ [unwrappedMinimumLongitude, minBounds[1], maxBounds[2] || 0],
680
+ [unwrappedMinimumLongitude, maxBounds[1], minBounds[2] || 0],
681
+ [unwrappedMinimumLongitude, maxBounds[1], maxBounds[2] || 0],
682
+ [unwrappedMaximumLongitude, minBounds[1], minBounds[2] || 0],
683
+ [unwrappedMaximumLongitude, minBounds[1], maxBounds[2] || 0],
684
+ [unwrappedMaximumLongitude, maxBounds[1], minBounds[2] || 0],
685
+ [unwrappedMaximumLongitude, maxBounds[1], maxBounds[2] || 0]
686
+ );
687
+ }
688
+ return corners;
689
+ }
690
+
691
+ /**
692
+ * Build a culling volume directly from the viewport frustum in common space.
693
+ */
694
+ private getCullingVolume(viewport: PointCloudViewport): CullingVolume | null {
695
+ const getFrustumPlanes = viewport.getFrustumPlanes?.bind(viewport);
696
+ if (!getFrustumPlanes) {
697
+ return null;
698
+ }
699
+
700
+ const frustumPlanes = getFrustumPlanes();
701
+ const directions = ['left', 'right', 'bottom', 'top', 'near', 'far'];
702
+ const planes: Plane[] = [];
703
+
704
+ for (const direction of directions) {
705
+ const plane = frustumPlanes[direction];
706
+ if (!plane) {
707
+ return null;
708
+ }
709
+
710
+ planes.push(new Plane(plane.normal, -plane.distance));
711
+ }
712
+
713
+ return new CullingVolume(planes);
714
+ }
715
+
716
+ /**
717
+ * Approximate a tile AABB by a common-space bounding sphere for frustum culling.
718
+ */
719
+ private getCommonSpaceBoundingSphere(
720
+ boundingVolume: PointCloudBoundingVolume,
721
+ viewport: PointCloudViewport
722
+ ): BoundingSphere | null {
723
+ const projectPosition = viewport.projectPosition?.bind(viewport);
724
+ if (!projectPosition) {
725
+ return null;
726
+ }
727
+
728
+ const corners = this.getBoundingVolumeCorners(boundingVolume);
729
+ let minX = Number.POSITIVE_INFINITY;
730
+ let minY = Number.POSITIVE_INFINITY;
731
+ let minZ = Number.POSITIVE_INFINITY;
732
+ let maxX = Number.NEGATIVE_INFINITY;
733
+ let maxY = Number.NEGATIVE_INFINITY;
734
+ let maxZ = Number.NEGATIVE_INFINITY;
735
+
736
+ try {
737
+ for (const corner of corners) {
738
+ const [x, y, z = 0] = projectPosition(corner);
739
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
740
+ continue;
741
+ }
742
+
743
+ minX = Math.min(minX, x);
744
+ minY = Math.min(minY, y);
745
+ minZ = Math.min(minZ, z);
746
+ maxX = Math.max(maxX, x);
747
+ maxY = Math.max(maxY, y);
748
+ maxZ = Math.max(maxZ, z);
749
+ }
750
+ } catch {
751
+ return null;
752
+ }
753
+
754
+ if (
755
+ !Number.isFinite(minX) ||
756
+ !Number.isFinite(minY) ||
757
+ !Number.isFinite(minZ) ||
758
+ !Number.isFinite(maxX) ||
759
+ !Number.isFinite(maxY) ||
760
+ !Number.isFinite(maxZ)
761
+ ) {
762
+ return null;
763
+ }
764
+
765
+ return new BoundingSphere().fromCornerPoints([minX, minY, minZ], [maxX, maxY, maxZ]);
766
+ }
767
+ }