@ifc-lite/viewer 1.17.3 → 1.17.6

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 (530) hide show
  1. package/.turbo/turbo-build.log +39 -30
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +132 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -0
  5. package/dist/assets/arrow-CZ5kQ26f.js +20 -0
  6. package/dist/assets/basketViewActivator-86rgogji.js +1 -0
  7. package/dist/assets/{bcf-D5-QWGO9.js → bcf-DOG9_WPX.js} +1 -1
  8. package/dist/assets/{browser-CKs-FY1P.js → browser-C5TFR7sH.js} +1 -1
  9. package/dist/assets/cesium-ADbP7waU.css +1 -0
  10. package/dist/assets/cesium-DUOzBlqv.js +17817 -0
  11. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  12. package/dist/assets/{exporters-C_6J153K.js → exporters-CcPS9MK5.js} +2898 -2380
  13. package/dist/assets/geometry.worker-BFUYA08u.js +1 -0
  14. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  15. package/dist/assets/ifc-lite_bg-BINvzoCP.wasm +0 -0
  16. package/dist/assets/{index-jhBr1wbn.js → index-Bfms9I4A.js} +41036 -34613
  17. package/dist/assets/index-_bfZsDCC.css +1 -0
  18. package/dist/assets/{maplibre-gl-BpvwNKKy.js → maplibre-gl-CGLcoNXc.js} +1 -1
  19. package/dist/assets/native-bridge-DUyLCMZS.js +429 -0
  20. package/dist/assets/{sandbox-B79eavQ3.js → sandbox-C8575tul.js} +4342 -4324
  21. package/dist/assets/{server-client-D3bUPJJc.js → server-client-BuZK7OST.js} +4 -4
  22. package/dist/assets/tauri-core-stub-D8Fa-u43.js +1 -0
  23. package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +1 -0
  24. package/dist/assets/tauri-fs-stub-BdeRC7aK.js +1 -0
  25. package/dist/assets/wasm-bridge-JsqEGDV8.js +1 -0
  26. package/dist/assets/{zip-B-jFFAGa.js → zip-DBEtpeu6.js} +3 -3
  27. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
  28. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
  29. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
  30. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
  31. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
  32. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
  33. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
  34. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
  35. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
  36. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
  37. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
  38. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
  39. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
  40. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
  41. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
  42. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
  43. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
  44. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
  45. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
  46. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
  47. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
  48. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
  49. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
  50. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
  51. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
  52. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
  53. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
  54. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
  55. package/dist/cesium/Assets/Images/bing_maps_credit.png +0 -0
  56. package/dist/cesium/Assets/Images/cesium_credit.png +0 -0
  57. package/dist/cesium/Assets/Images/google_earth_credit.png +0 -0
  58. package/dist/cesium/Assets/Images/ion-credit.png +0 -0
  59. package/dist/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
  60. package/dist/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
  61. package/dist/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
  62. package/dist/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
  63. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
  64. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
  65. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
  66. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
  67. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
  68. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
  69. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
  70. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
  71. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
  72. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
  73. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
  74. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
  75. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
  76. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
  77. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
  78. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
  79. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
  80. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
  81. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
  82. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
  83. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
  84. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
  85. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
  86. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
  87. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
  88. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
  89. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
  90. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
  91. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
  92. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
  93. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
  94. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
  95. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
  96. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
  97. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
  98. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
  99. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
  100. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
  101. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
  102. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
  103. package/dist/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
  104. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
  105. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
  106. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
  107. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
  108. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
  109. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
  110. package/dist/cesium/Assets/Textures/maki/airfield.png +0 -0
  111. package/dist/cesium/Assets/Textures/maki/airport.png +0 -0
  112. package/dist/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
  113. package/dist/cesium/Assets/Textures/maki/america-football.png +0 -0
  114. package/dist/cesium/Assets/Textures/maki/art-gallery.png +0 -0
  115. package/dist/cesium/Assets/Textures/maki/bakery.png +0 -0
  116. package/dist/cesium/Assets/Textures/maki/bank.png +0 -0
  117. package/dist/cesium/Assets/Textures/maki/bar.png +0 -0
  118. package/dist/cesium/Assets/Textures/maki/baseball.png +0 -0
  119. package/dist/cesium/Assets/Textures/maki/basketball.png +0 -0
  120. package/dist/cesium/Assets/Textures/maki/beer.png +0 -0
  121. package/dist/cesium/Assets/Textures/maki/bicycle.png +0 -0
  122. package/dist/cesium/Assets/Textures/maki/building.png +0 -0
  123. package/dist/cesium/Assets/Textures/maki/bus.png +0 -0
  124. package/dist/cesium/Assets/Textures/maki/cafe.png +0 -0
  125. package/dist/cesium/Assets/Textures/maki/camera.png +0 -0
  126. package/dist/cesium/Assets/Textures/maki/campsite.png +0 -0
  127. package/dist/cesium/Assets/Textures/maki/car.png +0 -0
  128. package/dist/cesium/Assets/Textures/maki/cemetery.png +0 -0
  129. package/dist/cesium/Assets/Textures/maki/cesium.png +0 -0
  130. package/dist/cesium/Assets/Textures/maki/chemist.png +0 -0
  131. package/dist/cesium/Assets/Textures/maki/cinema.png +0 -0
  132. package/dist/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
  133. package/dist/cesium/Assets/Textures/maki/circle.png +0 -0
  134. package/dist/cesium/Assets/Textures/maki/city.png +0 -0
  135. package/dist/cesium/Assets/Textures/maki/clothing-store.png +0 -0
  136. package/dist/cesium/Assets/Textures/maki/college.png +0 -0
  137. package/dist/cesium/Assets/Textures/maki/commercial.png +0 -0
  138. package/dist/cesium/Assets/Textures/maki/cricket.png +0 -0
  139. package/dist/cesium/Assets/Textures/maki/cross.png +0 -0
  140. package/dist/cesium/Assets/Textures/maki/dam.png +0 -0
  141. package/dist/cesium/Assets/Textures/maki/danger.png +0 -0
  142. package/dist/cesium/Assets/Textures/maki/disability.png +0 -0
  143. package/dist/cesium/Assets/Textures/maki/dog-park.png +0 -0
  144. package/dist/cesium/Assets/Textures/maki/embassy.png +0 -0
  145. package/dist/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
  146. package/dist/cesium/Assets/Textures/maki/entrance.png +0 -0
  147. package/dist/cesium/Assets/Textures/maki/farm.png +0 -0
  148. package/dist/cesium/Assets/Textures/maki/fast-food.png +0 -0
  149. package/dist/cesium/Assets/Textures/maki/ferry.png +0 -0
  150. package/dist/cesium/Assets/Textures/maki/fire-station.png +0 -0
  151. package/dist/cesium/Assets/Textures/maki/fuel.png +0 -0
  152. package/dist/cesium/Assets/Textures/maki/garden.png +0 -0
  153. package/dist/cesium/Assets/Textures/maki/gift.png +0 -0
  154. package/dist/cesium/Assets/Textures/maki/golf.png +0 -0
  155. package/dist/cesium/Assets/Textures/maki/grocery.png +0 -0
  156. package/dist/cesium/Assets/Textures/maki/hairdresser.png +0 -0
  157. package/dist/cesium/Assets/Textures/maki/harbor.png +0 -0
  158. package/dist/cesium/Assets/Textures/maki/heart.png +0 -0
  159. package/dist/cesium/Assets/Textures/maki/heliport.png +0 -0
  160. package/dist/cesium/Assets/Textures/maki/hospital.png +0 -0
  161. package/dist/cesium/Assets/Textures/maki/ice-cream.png +0 -0
  162. package/dist/cesium/Assets/Textures/maki/industrial.png +0 -0
  163. package/dist/cesium/Assets/Textures/maki/land-use.png +0 -0
  164. package/dist/cesium/Assets/Textures/maki/laundry.png +0 -0
  165. package/dist/cesium/Assets/Textures/maki/library.png +0 -0
  166. package/dist/cesium/Assets/Textures/maki/lighthouse.png +0 -0
  167. package/dist/cesium/Assets/Textures/maki/lodging.png +0 -0
  168. package/dist/cesium/Assets/Textures/maki/logging.png +0 -0
  169. package/dist/cesium/Assets/Textures/maki/london-underground.png +0 -0
  170. package/dist/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
  171. package/dist/cesium/Assets/Textures/maki/marker.png +0 -0
  172. package/dist/cesium/Assets/Textures/maki/minefield.png +0 -0
  173. package/dist/cesium/Assets/Textures/maki/mobilephone.png +0 -0
  174. package/dist/cesium/Assets/Textures/maki/monument.png +0 -0
  175. package/dist/cesium/Assets/Textures/maki/museum.png +0 -0
  176. package/dist/cesium/Assets/Textures/maki/music.png +0 -0
  177. package/dist/cesium/Assets/Textures/maki/oil-well.png +0 -0
  178. package/dist/cesium/Assets/Textures/maki/park.png +0 -0
  179. package/dist/cesium/Assets/Textures/maki/park2.png +0 -0
  180. package/dist/cesium/Assets/Textures/maki/parking-garage.png +0 -0
  181. package/dist/cesium/Assets/Textures/maki/parking.png +0 -0
  182. package/dist/cesium/Assets/Textures/maki/pharmacy.png +0 -0
  183. package/dist/cesium/Assets/Textures/maki/pitch.png +0 -0
  184. package/dist/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
  185. package/dist/cesium/Assets/Textures/maki/playground.png +0 -0
  186. package/dist/cesium/Assets/Textures/maki/police.png +0 -0
  187. package/dist/cesium/Assets/Textures/maki/polling-place.png +0 -0
  188. package/dist/cesium/Assets/Textures/maki/post.png +0 -0
  189. package/dist/cesium/Assets/Textures/maki/prison.png +0 -0
  190. package/dist/cesium/Assets/Textures/maki/rail-above.png +0 -0
  191. package/dist/cesium/Assets/Textures/maki/rail-light.png +0 -0
  192. package/dist/cesium/Assets/Textures/maki/rail-metro.png +0 -0
  193. package/dist/cesium/Assets/Textures/maki/rail-underground.png +0 -0
  194. package/dist/cesium/Assets/Textures/maki/rail.png +0 -0
  195. package/dist/cesium/Assets/Textures/maki/religious-christian.png +0 -0
  196. package/dist/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
  197. package/dist/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
  198. package/dist/cesium/Assets/Textures/maki/restaurant.png +0 -0
  199. package/dist/cesium/Assets/Textures/maki/roadblock.png +0 -0
  200. package/dist/cesium/Assets/Textures/maki/rocket.png +0 -0
  201. package/dist/cesium/Assets/Textures/maki/school.png +0 -0
  202. package/dist/cesium/Assets/Textures/maki/scooter.png +0 -0
  203. package/dist/cesium/Assets/Textures/maki/shop.png +0 -0
  204. package/dist/cesium/Assets/Textures/maki/skiing.png +0 -0
  205. package/dist/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
  206. package/dist/cesium/Assets/Textures/maki/soccer.png +0 -0
  207. package/dist/cesium/Assets/Textures/maki/square-stroked.png +0 -0
  208. package/dist/cesium/Assets/Textures/maki/square.png +0 -0
  209. package/dist/cesium/Assets/Textures/maki/star-stroked.png +0 -0
  210. package/dist/cesium/Assets/Textures/maki/star.png +0 -0
  211. package/dist/cesium/Assets/Textures/maki/suitcase.png +0 -0
  212. package/dist/cesium/Assets/Textures/maki/swimming.png +0 -0
  213. package/dist/cesium/Assets/Textures/maki/telephone.png +0 -0
  214. package/dist/cesium/Assets/Textures/maki/tennis.png +0 -0
  215. package/dist/cesium/Assets/Textures/maki/theatre.png +0 -0
  216. package/dist/cesium/Assets/Textures/maki/toilets.png +0 -0
  217. package/dist/cesium/Assets/Textures/maki/town-hall.png +0 -0
  218. package/dist/cesium/Assets/Textures/maki/town.png +0 -0
  219. package/dist/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
  220. package/dist/cesium/Assets/Textures/maki/triangle.png +0 -0
  221. package/dist/cesium/Assets/Textures/maki/village.png +0 -0
  222. package/dist/cesium/Assets/Textures/maki/warehouse.png +0 -0
  223. package/dist/cesium/Assets/Textures/maki/waste-basket.png +0 -0
  224. package/dist/cesium/Assets/Textures/maki/water.png +0 -0
  225. package/dist/cesium/Assets/Textures/maki/wetland.png +0 -0
  226. package/dist/cesium/Assets/Textures/maki/zoo.png +0 -0
  227. package/dist/cesium/Assets/Textures/moonSmall.jpg +0 -0
  228. package/dist/cesium/Assets/Textures/pin.svg +1 -0
  229. package/dist/cesium/Assets/Textures/waterNormals.jpg +0 -0
  230. package/dist/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
  231. package/dist/cesium/Assets/approximateTerrainHeights.json +1 -0
  232. package/dist/cesium/ThirdParty/Workers/package.json +1 -0
  233. package/dist/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
  234. package/dist/cesium/ThirdParty/basis_transcoder.wasm +0 -0
  235. package/dist/cesium/ThirdParty/draco_decoder.wasm +0 -0
  236. package/dist/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
  237. package/dist/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
  238. package/dist/cesium/ThirdParty/zip-module.wasm +0 -0
  239. package/dist/cesium/Widgets/Animation/Animation.css +127 -0
  240. package/dist/cesium/Widgets/Animation/lighter.css +70 -0
  241. package/dist/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
  242. package/dist/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
  243. package/dist/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
  244. package/dist/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
  245. package/dist/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
  246. package/dist/cesium/Widgets/CesiumWidget/lighter.css +14 -0
  247. package/dist/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
  248. package/dist/cesium/Widgets/Geocoder/Geocoder.css +70 -0
  249. package/dist/cesium/Widgets/Geocoder/lighter.css +17 -0
  250. package/dist/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
  251. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
  252. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
  253. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
  254. package/dist/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
  255. package/dist/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
  256. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
  257. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
  258. package/dist/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
  259. package/dist/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
  260. package/dist/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
  261. package/dist/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
  262. package/dist/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
  263. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
  264. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
  265. package/dist/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
  266. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
  267. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
  268. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
  269. package/dist/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
  270. package/dist/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
  271. package/dist/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
  272. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
  273. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
  274. package/dist/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
  275. package/dist/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
  276. package/dist/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
  277. package/dist/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
  278. package/dist/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
  279. package/dist/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
  280. package/dist/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
  281. package/dist/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
  282. package/dist/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
  283. package/dist/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
  284. package/dist/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
  285. package/dist/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
  286. package/dist/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
  287. package/dist/cesium/Widgets/Images/TimelineIcons.png +0 -0
  288. package/dist/cesium/Widgets/Images/info-loading.gif +0 -0
  289. package/dist/cesium/Widgets/InfoBox/InfoBox.css +92 -0
  290. package/dist/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
  291. package/dist/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
  292. package/dist/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
  293. package/dist/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
  294. package/dist/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
  295. package/dist/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
  296. package/dist/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
  297. package/dist/cesium/Widgets/Timeline/Timeline.css +103 -0
  298. package/dist/cesium/Widgets/Timeline/lighter.css +23 -0
  299. package/dist/cesium/Widgets/VRButton/VRButton.css +8 -0
  300. package/dist/cesium/Widgets/Viewer/Viewer.css +107 -0
  301. package/dist/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
  302. package/dist/cesium/Widgets/lighter.css +237 -0
  303. package/dist/cesium/Widgets/lighterShared.css +46 -0
  304. package/dist/cesium/Widgets/shared.css +103 -0
  305. package/dist/cesium/Widgets/widgets.css +1342 -0
  306. package/dist/cesium/Workers/chunk-23ZQ2IVV.js +29 -0
  307. package/dist/cesium/Workers/chunk-2EQO3Q56.js +26 -0
  308. package/dist/cesium/Workers/chunk-2MJIIVP4.js +26 -0
  309. package/dist/cesium/Workers/chunk-2TE5NTVD.js +26 -0
  310. package/dist/cesium/Workers/chunk-2ZBHLJST.js +26 -0
  311. package/dist/cesium/Workers/chunk-5TJMAQVL.js +26 -0
  312. package/dist/cesium/Workers/chunk-6BD4U3VO.js +26 -0
  313. package/dist/cesium/Workers/chunk-7TVGLKQF.js +26 -0
  314. package/dist/cesium/Workers/chunk-BTSYJ5XU.js +26 -0
  315. package/dist/cesium/Workers/chunk-BXMEEOCS.js +63 -0
  316. package/dist/cesium/Workers/chunk-BYLCY7GP.js +29 -0
  317. package/dist/cesium/Workers/chunk-CTHM3W6I.js +26 -0
  318. package/dist/cesium/Workers/chunk-CUUSNIVQ.js +26 -0
  319. package/dist/cesium/Workers/chunk-E3JOOS3S.js +26 -0
  320. package/dist/cesium/Workers/chunk-E7KYDCM5.js +26 -0
  321. package/dist/cesium/Workers/chunk-EDVBB7SS.js +27 -0
  322. package/dist/cesium/Workers/chunk-EFBN7QNX.js +26 -0
  323. package/dist/cesium/Workers/chunk-EQ4YRVWL.js +26 -0
  324. package/dist/cesium/Workers/chunk-F6PRE7D6.js +26 -0
  325. package/dist/cesium/Workers/chunk-FC4ZZ65J.js +26 -0
  326. package/dist/cesium/Workers/chunk-FFBVWF2L.js +26 -0
  327. package/dist/cesium/Workers/chunk-GBAA6GVX.js +26 -0
  328. package/dist/cesium/Workers/chunk-ICALLYLG.js +26 -0
  329. package/dist/cesium/Workers/chunk-ILRYTWTP.js +26 -0
  330. package/dist/cesium/Workers/chunk-IRNLBSEJ.js +26 -0
  331. package/dist/cesium/Workers/chunk-IX4VMHEV.js +26 -0
  332. package/dist/cesium/Workers/chunk-L6QHHACZ.js +26 -0
  333. package/dist/cesium/Workers/chunk-LI2ZSORM.js +26 -0
  334. package/dist/cesium/Workers/chunk-LSLE2RL4.js +26 -0
  335. package/dist/cesium/Workers/chunk-M4HLDBCG.js +26 -0
  336. package/dist/cesium/Workers/chunk-MJHHSGEH.js +26 -0
  337. package/dist/cesium/Workers/chunk-NMVKML6W.js +26 -0
  338. package/dist/cesium/Workers/chunk-OCWJRAXS.js +26 -0
  339. package/dist/cesium/Workers/chunk-OIRKANTH.js +26 -0
  340. package/dist/cesium/Workers/chunk-OIT7J4IC.js +26 -0
  341. package/dist/cesium/Workers/chunk-OLZ3FYUM.js +26 -0
  342. package/dist/cesium/Workers/chunk-Q5BPHJQF.js +26 -0
  343. package/dist/cesium/Workers/chunk-QFM5DCMQ.js +26 -0
  344. package/dist/cesium/Workers/chunk-QKUIYMGC.js +28 -0
  345. package/dist/cesium/Workers/chunk-S44JILQT.js +26 -0
  346. package/dist/cesium/Workers/chunk-SLT4J352.js +26 -0
  347. package/dist/cesium/Workers/chunk-SQMIIXB7.js +26 -0
  348. package/dist/cesium/Workers/chunk-TJ4XLGBQ.js +26 -0
  349. package/dist/cesium/Workers/chunk-TNSUQXWK.js +27 -0
  350. package/dist/cesium/Workers/chunk-UBOGZS7F.js +26 -0
  351. package/dist/cesium/Workers/chunk-V3OSTMM6.js +26 -0
  352. package/dist/cesium/Workers/chunk-V7QEYVP3.js +26 -0
  353. package/dist/cesium/Workers/chunk-VUKYSU4H.js +26 -0
  354. package/dist/cesium/Workers/chunk-W37FE5GR.js +26 -0
  355. package/dist/cesium/Workers/chunk-WBOV35NL.js +26 -0
  356. package/dist/cesium/Workers/chunk-WPMZLB3Y.js +26 -0
  357. package/dist/cesium/Workers/chunk-WWWZVEEH.js +26 -0
  358. package/dist/cesium/Workers/chunk-XFIQ5DEQ.js +28 -0
  359. package/dist/cesium/Workers/chunk-XQHLGIO7.js +26 -0
  360. package/dist/cesium/Workers/chunk-XUSCFAVF.js +26 -0
  361. package/dist/cesium/Workers/chunk-YP7I5QBZ.js +26 -0
  362. package/dist/cesium/Workers/chunk-Z3QF2EHT.js +26 -0
  363. package/dist/cesium/Workers/combineGeometry.js +26 -0
  364. package/dist/cesium/Workers/createBoxGeometry.js +26 -0
  365. package/dist/cesium/Workers/createBoxOutlineGeometry.js +26 -0
  366. package/dist/cesium/Workers/createCircleGeometry.js +26 -0
  367. package/dist/cesium/Workers/createCircleOutlineGeometry.js +26 -0
  368. package/dist/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
  369. package/dist/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
  370. package/dist/cesium/Workers/createCorridorGeometry.js +26 -0
  371. package/dist/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
  372. package/dist/cesium/Workers/createCylinderGeometry.js +26 -0
  373. package/dist/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
  374. package/dist/cesium/Workers/createEllipseGeometry.js +26 -0
  375. package/dist/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
  376. package/dist/cesium/Workers/createEllipsoidGeometry.js +26 -0
  377. package/dist/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
  378. package/dist/cesium/Workers/createFrustumGeometry.js +26 -0
  379. package/dist/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
  380. package/dist/cesium/Workers/createGeometry.js +26 -0
  381. package/dist/cesium/Workers/createGroundPolylineGeometry.js +26 -0
  382. package/dist/cesium/Workers/createPlaneGeometry.js +26 -0
  383. package/dist/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
  384. package/dist/cesium/Workers/createPolygonGeometry.js +26 -0
  385. package/dist/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
  386. package/dist/cesium/Workers/createPolylineGeometry.js +26 -0
  387. package/dist/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
  388. package/dist/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
  389. package/dist/cesium/Workers/createRectangleGeometry.js +26 -0
  390. package/dist/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
  391. package/dist/cesium/Workers/createSimplePolylineGeometry.js +26 -0
  392. package/dist/cesium/Workers/createSphereGeometry.js +26 -0
  393. package/dist/cesium/Workers/createSphereOutlineGeometry.js +26 -0
  394. package/dist/cesium/Workers/createTaskProcessorWorker.js +26 -0
  395. package/dist/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
  396. package/dist/cesium/Workers/createVectorTileGeometries.js +26 -0
  397. package/dist/cesium/Workers/createVectorTilePoints.js +26 -0
  398. package/dist/cesium/Workers/createVectorTilePolygons.js +26 -0
  399. package/dist/cesium/Workers/createVectorTilePolylines.js +26 -0
  400. package/dist/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
  401. package/dist/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
  402. package/dist/cesium/Workers/createVerticesFromHeightmap.js +26 -0
  403. package/dist/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
  404. package/dist/cesium/Workers/createWallGeometry.js +26 -0
  405. package/dist/cesium/Workers/createWallOutlineGeometry.js +26 -0
  406. package/dist/cesium/Workers/decodeDraco.js +26 -0
  407. package/dist/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
  408. package/dist/cesium/Workers/decodeI3S.js +26 -0
  409. package/dist/cesium/Workers/gaussianSplatSorter.js +26 -0
  410. package/dist/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
  411. package/dist/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
  412. package/dist/cesium/Workers/transcodeKTX2.js +56 -0
  413. package/dist/cesium/Workers/transferTypedArrayTest.js +26 -0
  414. package/dist/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
  415. package/dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
  416. package/dist/index.html +13 -10
  417. package/index.html +1 -0
  418. package/package.json +18 -15
  419. package/src/App.tsx +7 -9
  420. package/src/components/viewer/BCFPanel.tsx +46 -4
  421. package/src/components/viewer/CesiumOverlay.tsx +715 -0
  422. package/src/components/viewer/CesiumSettingsDialog.tsx +100 -0
  423. package/src/components/viewer/ChatPanel.tsx +232 -90
  424. package/src/components/viewer/CommandPalette.tsx +6 -1
  425. package/src/components/viewer/DesktopEntitlementBanner.tsx +74 -0
  426. package/src/components/viewer/ExportChangesButton.tsx +6 -1
  427. package/src/components/viewer/ExportDialog.tsx +22 -6
  428. package/src/components/viewer/HierarchyPanel.tsx +196 -0
  429. package/src/components/viewer/IDSPanel.tsx +52 -3
  430. package/src/components/viewer/KeyboardShortcutsDialog.tsx +1 -1
  431. package/src/components/viewer/MainToolbar.tsx +355 -28
  432. package/src/components/viewer/PropertiesPanel.tsx +234 -81
  433. package/src/components/viewer/ScriptPanel.tsx +34 -8
  434. package/src/components/viewer/SettingsPage.tsx +581 -0
  435. package/src/components/viewer/StatusBar.tsx +17 -1
  436. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  437. package/src/components/viewer/ViewerLayout.tsx +48 -6
  438. package/src/components/viewer/Viewport.tsx +61 -8
  439. package/src/components/viewer/ViewportContainer.tsx +265 -28
  440. package/src/components/viewer/ViewportOverlays.tsx +132 -27
  441. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  442. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  443. package/src/components/viewer/properties/GeoreferencingPanel.tsx +229 -55
  444. package/src/components/viewer/properties/LocationMap.tsx +462 -19
  445. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  446. package/src/components/viewer/selectionHandlers.ts +4 -3
  447. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  448. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  449. package/src/components/viewer/useAnimationLoop.ts +13 -1
  450. package/src/components/viewer/useGeometryStreaming.ts +127 -40
  451. package/src/components/viewer/useMouseControls.ts +4 -1
  452. package/src/components/viewer/useRenderUpdates.ts +1 -1
  453. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  454. package/src/hooks/ingest/viewerModelIngest.ts +280 -0
  455. package/src/hooks/useIDS.ts +1 -1
  456. package/src/hooks/useIfc.ts +7 -1
  457. package/src/hooks/useIfcCache.ts +28 -15
  458. package/src/hooks/useIfcFederation.ts +378 -291
  459. package/src/hooks/useIfcLoader.ts +1657 -130
  460. package/src/hooks/useIfcServer.ts +0 -69
  461. package/src/hooks/useViewControls.ts +13 -5
  462. package/src/index.css +484 -10
  463. package/src/lib/desktop/desktopEntitlementEvents.ts +39 -0
  464. package/src/lib/desktop-entitlement.ts +43 -0
  465. package/src/lib/desktop-product.ts +124 -0
  466. package/src/lib/geo/cesium-bridge.ts +318 -0
  467. package/src/lib/geo/effective-georef.test.ts +73 -0
  468. package/src/lib/geo/effective-georef.ts +111 -0
  469. package/src/lib/geo/reproject.ts +249 -37
  470. package/src/lib/llm/byok-guard.test.ts +77 -0
  471. package/src/lib/llm/byok-guard.ts +39 -0
  472. package/src/lib/llm/free-models.test.ts +0 -6
  473. package/src/lib/llm/models.ts +104 -42
  474. package/src/lib/llm/stream-client.ts +74 -110
  475. package/src/lib/llm/stream-direct.test.ts +130 -0
  476. package/src/lib/llm/stream-direct.ts +316 -0
  477. package/src/lib/llm/types.ts +14 -2
  478. package/src/lib/recent-files.ts +2 -1
  479. package/src/main.tsx +1 -10
  480. package/src/services/analysis-extensions.ts +125 -0
  481. package/src/services/api-keys.ts +73 -0
  482. package/src/services/app-navigation.ts +13 -0
  483. package/src/services/bsdd.ts +53 -4
  484. package/src/services/cacheService.ts +1 -1
  485. package/src/services/desktop-cache.ts +43 -0
  486. package/src/services/desktop-export.ts +77 -0
  487. package/src/services/desktop-harness.ts +196 -0
  488. package/src/services/desktop-logger.ts +20 -0
  489. package/src/services/desktop-native-metadata.ts +207 -0
  490. package/src/services/desktop-panel-actions.ts +43 -0
  491. package/src/services/desktop-preferences.ts +44 -0
  492. package/src/services/file-dialog.ts +147 -0
  493. package/src/services/tauri-core-stub.ts +7 -0
  494. package/src/services/tauri-dialog-stub.ts +7 -0
  495. package/src/services/tauri-fs-stub.ts +7 -0
  496. package/src/store/constants.ts +20 -2
  497. package/src/store/index.ts +52 -7
  498. package/src/store/slices/cesiumSlice.ts +127 -0
  499. package/src/store/slices/chatSlice.test.ts +6 -76
  500. package/src/store/slices/chatSlice.ts +21 -58
  501. package/src/store/slices/dataSlice.ts +139 -28
  502. package/src/store/slices/desktopEntitlementSlice.ts +86 -0
  503. package/src/store/slices/loadingSlice.ts +14 -2
  504. package/src/store/slices/modelSlice.ts +58 -3
  505. package/src/store/slices/sectionSlice.test.ts +87 -7
  506. package/src/store/slices/sectionSlice.ts +151 -5
  507. package/src/store/slices/uiSlice.ts +28 -5
  508. package/src/store/types.ts +122 -2
  509. package/src/store.ts +1 -1
  510. package/src/utils/desktopModelSnapshot.ts +358 -0
  511. package/src/utils/ifcConfig.ts +6 -1
  512. package/src/utils/nativeSpatialDataStore.ts +253 -0
  513. package/src/utils/serverDataModel.ts +4 -0
  514. package/src/utils/spatialHierarchy.ts +10 -11
  515. package/src/utils/viewportUtils.ts +7 -2
  516. package/src/vite-env.d.ts +0 -4
  517. package/vite.config.ts +24 -0
  518. package/dist/assets/arrow-DJf2ErbF.js +0 -20
  519. package/dist/assets/basketViewActivator-aojwdomq.js +0 -1
  520. package/dist/assets/desktop-cache-oPzaWXYE.js +0 -1
  521. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  522. package/dist/assets/geometry.worker-Nz9_YIqh.js +0 -1
  523. package/dist/assets/ids-B4jTqB1O.js +0 -1
  524. package/dist/assets/ifc-lite_bg-eSkBTizQ.wasm +0 -0
  525. package/dist/assets/index-pbE7itQS.css +0 -1
  526. package/dist/assets/native-bridge-DSIyEYXG.js +0 -113
  527. package/dist/assets/wasm-bridge-B0J07fZZ.js +0 -1
  528. package/src/components/viewer/UpgradePage.tsx +0 -69
  529. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  530. package/src/lib/llm/clerk-auth.ts +0 -62
@@ -0,0 +1,715 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * CesiumOverlay — renders a CesiumJS globe behind the WebGPU canvas,
7
+ * providing real-world 3D context (terrain, buildings, imagery) for
8
+ * georeferenced IFC models.
9
+ *
10
+ * Architecture:
11
+ * - A separate <div> behind the WebGPU <canvas> (z-index layering)
12
+ * - WebGPU canvas uses transparent clear color so Cesium shows through
13
+ * - Camera is synchronized every frame from the IFC viewer camera
14
+ * - CesiumJS is lazy-loaded on first activation to avoid bundle bloat
15
+ * - User controls remain on the WebGPU canvas; Cesium's are disabled
16
+ *
17
+ * Live edit support:
18
+ * - When georef props change (e.g. user edits EPSG, eastings, rotation),
19
+ * the coordinate bridge is rebuilt and the globe flies to the new location
20
+ * - The Cesium viewer itself is NOT recreated — only the bridge is updated
21
+ */
22
+
23
+ import { useEffect, useRef, useState } from 'react';
24
+ import { useViewerStore } from '@/store';
25
+ import type { MapConversion, ProjectedCRS } from '@ifc-lite/parser';
26
+ import type { CoordinateInfo, GeometryResult } from '@ifc-lite/geometry';
27
+ import { getGlobalRenderer } from '@/hooks/useBCF';
28
+ import { createCesiumBridge, type CesiumBridge } from '@/lib/geo/cesium-bridge';
29
+
30
+ // Lazy-loaded Cesium module and CSS
31
+ let cesiumPromise: Promise<typeof import('cesium')> | null = null;
32
+ let cesiumModule: typeof import('cesium') | null = null;
33
+ function loadCesium() {
34
+ if (!cesiumPromise) {
35
+ cesiumPromise = Promise.all([
36
+ import('cesium'),
37
+ import('cesium/Build/Cesium/Widgets/widgets.css'),
38
+ ]).then(([cesium]) => {
39
+ cesiumModule = cesium;
40
+ return cesium;
41
+ });
42
+ }
43
+ return cesiumPromise;
44
+ }
45
+
46
+ /**
47
+ * Build a minimal GLB with all geometry merged into a SINGLE mesh.
48
+ * This is MUCH faster than GLTFExporter (which creates one glTF node per IFC mesh).
49
+ * For a 42K mesh model: GLTFExporter takes seconds, this takes ~100ms.
50
+ */
51
+ function buildMergedGLB(meshes: import('@ifc-lite/geometry').MeshData[]): Uint8Array {
52
+ // Pass 1: calculate total sizes
53
+ let totalVerts = 0;
54
+ let totalIdxs = 0;
55
+ for (const m of meshes) {
56
+ if (!m.positions?.length || !m.indices?.length) continue;
57
+ totalVerts += m.positions.length / 3;
58
+ totalIdxs += m.indices.length;
59
+ }
60
+
61
+ // Allocate merged buffers
62
+ const positions = new Float32Array(totalVerts * 3);
63
+ const colors = new Uint8Array(totalVerts * 4);
64
+ const indices = new Uint32Array(totalIdxs);
65
+
66
+ // Pass 2: merge
67
+ let vertOff = 0;
68
+ let idxOff = 0;
69
+ for (const m of meshes) {
70
+ if (!m.positions?.length || !m.indices?.length) continue;
71
+ const nv = m.positions.length / 3;
72
+ positions.set(m.positions, vertOff * 3);
73
+ // Vertex colors from mesh color
74
+ const r = Math.round((m.color?.[0] ?? 0.7) * 255);
75
+ const g = Math.round((m.color?.[1] ?? 0.7) * 255);
76
+ const b = Math.round((m.color?.[2] ?? 0.7) * 255);
77
+ const a = Math.round((m.color?.[3] ?? 1.0) * 255);
78
+ for (let i = 0; i < nv; i++) {
79
+ const ci = (vertOff + i) * 4;
80
+ colors[ci] = r; colors[ci + 1] = g; colors[ci + 2] = b; colors[ci + 3] = a;
81
+ }
82
+ for (let i = 0; i < m.indices.length; i++) {
83
+ indices[idxOff + i] = m.indices[i] + vertOff;
84
+ }
85
+ vertOff += nv;
86
+ idxOff += m.indices.length;
87
+ }
88
+
89
+ // Compute bounds
90
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
91
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
92
+ for (let i = 0; i < positions.length; i += 3) {
93
+ const x = positions[i], y = positions[i + 1], z = positions[i + 2];
94
+ if (x < minX) minX = x; if (x > maxX) maxX = x;
95
+ if (y < minY) minY = y; if (y > maxY) maxY = y;
96
+ if (z < minZ) minZ = z; if (z > maxZ) maxZ = z;
97
+ }
98
+
99
+ // Build minimal glTF JSON
100
+ const posByteLen = positions.byteLength;
101
+ const colByteLen = colors.byteLength;
102
+ const idxByteLen = indices.byteLength;
103
+ const totalBinLen = posByteLen + colByteLen + idxByteLen;
104
+
105
+ const gltf = {
106
+ asset: { version: '2.0', generator: 'IFC-Lite-Cesium' },
107
+ scene: 0,
108
+ scenes: [{ nodes: [0] }],
109
+ nodes: [{ mesh: 0 }],
110
+ meshes: [{ primitives: [{ attributes: { POSITION: 0, COLOR_0: 1 }, indices: 2 }] }],
111
+ accessors: [
112
+ { bufferView: 0, componentType: 5126, count: totalVerts, type: 'VEC3', min: [minX, minY, minZ], max: [maxX, maxY, maxZ] },
113
+ { bufferView: 1, componentType: 5121, count: totalVerts, type: 'VEC4', normalized: true },
114
+ { bufferView: 2, componentType: 5125, count: totalIdxs, type: 'SCALAR' },
115
+ ],
116
+ bufferViews: [
117
+ { buffer: 0, byteOffset: 0, byteLength: posByteLen, target: 34962 },
118
+ { buffer: 0, byteOffset: posByteLen, byteLength: colByteLen, target: 34962 },
119
+ { buffer: 0, byteOffset: posByteLen + colByteLen, byteLength: idxByteLen, target: 34963 },
120
+ ],
121
+ buffers: [{ byteLength: totalBinLen }],
122
+ extensionsUsed: ['KHR_materials_unlit'],
123
+ };
124
+
125
+ const jsonStr = JSON.stringify(gltf);
126
+ const jsonBuf = new TextEncoder().encode(jsonStr);
127
+ // Pad JSON to 4-byte alignment
128
+ const jsonPad = (4 - (jsonBuf.length % 4)) % 4;
129
+ const jsonChunkLen = jsonBuf.length + jsonPad;
130
+ // Pad binary to 4-byte alignment
131
+ const binPad = (4 - (totalBinLen % 4)) % 4;
132
+ const binChunkLen = totalBinLen + binPad;
133
+
134
+ // GLB: 12-byte header + 8-byte JSON chunk header + JSON + 8-byte BIN chunk header + BIN
135
+ const glbLen = 12 + 8 + jsonChunkLen + 8 + binChunkLen;
136
+ const glb = new ArrayBuffer(glbLen);
137
+ const view = new DataView(glb);
138
+ let off = 0;
139
+
140
+ // GLB header
141
+ view.setUint32(off, 0x46546C67, true); off += 4; // magic "glTF"
142
+ view.setUint32(off, 2, true); off += 4; // version
143
+ view.setUint32(off, glbLen, true); off += 4; // total length
144
+
145
+ // JSON chunk
146
+ view.setUint32(off, jsonChunkLen, true); off += 4;
147
+ view.setUint32(off, 0x4E4F534A, true); off += 4; // "JSON"
148
+ new Uint8Array(glb, off, jsonBuf.length).set(jsonBuf); off += jsonBuf.length;
149
+ for (let i = 0; i < jsonPad; i++) view.setUint8(off++, 0x20); // space padding
150
+
151
+ // BIN chunk
152
+ view.setUint32(off, binChunkLen, true); off += 4;
153
+ view.setUint32(off, 0x004E4942, true); off += 4; // "BIN\0"
154
+ new Uint8Array(glb, off, posByteLen).set(new Uint8Array(positions.buffer)); off += posByteLen;
155
+ new Uint8Array(glb, off, colByteLen).set(colors); off += colByteLen;
156
+ new Uint8Array(glb, off, idxByteLen).set(new Uint8Array(indices.buffer)); off += idxByteLen;
157
+
158
+ return new Uint8Array(glb);
159
+ }
160
+
161
+ /**
162
+ * Build a Cesium model matrix for placing the IFC model in ECEF.
163
+ * Extracted as a pure function so it can be called from both
164
+ * the GLB load effect (initial) and the matrix update effect (instant).
165
+ */
166
+ function buildModelMatrix(
167
+ Cesium: typeof import('cesium'),
168
+ bridge: CesiumBridge,
169
+ mapConversion: MapConversion | undefined,
170
+ coordinateInfo: CoordinateInfo | undefined,
171
+ clamp: boolean,
172
+ terrainH: number | null,
173
+ ) {
174
+ const hScale = mapConversion?.scale ?? 1.0;
175
+ const absc = mapConversion?.xAxisAbscissa ?? 1.0;
176
+ const ordi = mapConversion?.xAxisOrdinate ?? 0.0;
177
+ const bounds = coordinateInfo?.originalBounds;
178
+ // Viewer bounds are already in metres (geometry engine converts from IFC native unit)
179
+ const mvx = bounds ? (bounds.min.x + bounds.max.x) / 2 : 0;
180
+ const mvy = bounds ? (bounds.min.y + bounds.max.y) / 2 : 0;
181
+ const mvz = bounds ? (bounds.min.z + bounds.max.z) / 2 : 0;
182
+
183
+ let placementHeight = bridge.modelOrigin.height;
184
+ if (clamp && terrainH !== null) {
185
+ const minY = bounds?.min.y ?? 0;
186
+ const bottomOffset = mvy - minY; // already in metres
187
+ placementHeight = terrainH + bottomOffset;
188
+ }
189
+
190
+ const origin = Cesium.Cartesian3.fromDegrees(
191
+ bridge.modelOrigin.longitude, bridge.modelOrigin.latitude, placementHeight,
192
+ );
193
+ const enuToEcef = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
194
+ // No lengthUnitScale here — viewer-space GLB vertices are already in metres.
195
+ const sa = hScale * absc, so = hScale * ordi;
196
+ const tx = -(sa * mvx + so * mvz);
197
+ const ty = -(so * mvx - sa * mvz);
198
+ const tz = -mvy;
199
+ const ifcToEnu = new Cesium.Matrix4(
200
+ sa, 0, so, tx,
201
+ so, 0, -sa, ty,
202
+ 0, 1, 0, tz,
203
+ 0, 0, 0, 1,
204
+ );
205
+ return Cesium.Matrix4.multiply(enuToEcef, ifcToEnu, new Cesium.Matrix4());
206
+ }
207
+
208
+ export interface CesiumOverlayProps {
209
+ mapConversion?: MapConversion;
210
+ projectedCRS?: ProjectedCRS;
211
+ coordinateInfo?: CoordinateInfo;
212
+ geometryResult?: GeometryResult | null;
213
+ /** IFC project length unit → metres (e.g. 0.001 for mm models). Default 1. */
214
+ lengthUnitScale?: number;
215
+ }
216
+
217
+ export function CesiumOverlay({
218
+ mapConversion,
219
+ projectedCRS,
220
+ coordinateInfo,
221
+ geometryResult,
222
+ lengthUnitScale = 1,
223
+ }: CesiumOverlayProps) {
224
+ const containerRef = useRef<HTMLDivElement>(null);
225
+ const viewerRef = useRef<InstanceType<typeof import('cesium').Viewer> | null>(null);
226
+ const bridgeRef = useRef<CesiumBridge | null>(null);
227
+ const rafRef = useRef<number | null>(null);
228
+ const [status, setStatus] = useState<'idle' | 'loading' | 'ready' | 'error'>('idle');
229
+ const [error, setError] = useState<string | null>(null);
230
+ // Tracks bridge readiness as state (not just a ref) so terrain query effect re-runs
231
+ const [bridgeVersion, setBridgeVersion] = useState(0);
232
+
233
+ const cesiumEnabled = useViewerStore((s) => s.cesiumEnabled);
234
+ const dataSource = useViewerStore((s) => s.cesiumDataSource);
235
+ const ionToken = useViewerStore((s) => s.cesiumIonToken);
236
+ const terrainEnabled = useViewerStore((s) => s.cesiumTerrainEnabled);
237
+ const terrainClamp = useViewerStore((s) => s.cesiumTerrainClamp);
238
+ const setCesiumTerrainClamp = useViewerStore((s) => s.setCesiumTerrainClamp);
239
+ const terrainHeight = useViewerStore((s) => s.cesiumTerrainHeight);
240
+ const setCesiumTerrainHeight = useViewerStore((s) => s.setCesiumTerrainHeight);
241
+ const setCesiumTerrainClipY = useViewerStore((s) => s.setCesiumTerrainClipY);
242
+ const setCesiumGlbLoaded = useViewerStore((s) => s.setCesiumGlbLoaded);
243
+
244
+ // Use refs so the camera sync loop always reads the latest values
245
+ const terrainClampRef = useRef(terrainClamp);
246
+ const terrainHeightRef = useRef(terrainHeight);
247
+ terrainClampRef.current = terrainClamp;
248
+ terrainHeightRef.current = terrainHeight;
249
+
250
+ // Track whether we've auto-clamped to terrain (only once, so user can still uncheck)
251
+ const autoClampedRef = useRef(false);
252
+
253
+ // Track the Cesium model (IFC geometry loaded as glTF for correct world positioning)
254
+ const cesiumModelRef = useRef<{ modelMatrix: any; destroy?: () => void } | null>(null);
255
+ const glbCacheRef = useRef<{ meshCount: number; glb: Uint8Array } | null>(null);
256
+
257
+ // ─── Effect 1: Create/destroy the Cesium viewer (heavy, rare) ───────────
258
+ // Only depends on cesiumEnabled, ionToken, terrainEnabled, dataSource.
259
+ // NOT on mapConversion/projectedCRS — those are handled by Effect 2.
260
+ useEffect(() => {
261
+ if (!cesiumEnabled || !containerRef.current) return;
262
+
263
+ let cancelled = false;
264
+ setStatus('loading');
265
+ setError(null);
266
+
267
+ (async () => {
268
+ try {
269
+ const Cesium = await loadCesium();
270
+ if (cancelled || !containerRef.current) return;
271
+
272
+ // Configure Cesium ion token if provided
273
+ if (ionToken) {
274
+ Cesium.Ion.defaultAccessToken = ionToken;
275
+ }
276
+
277
+ const viewer = new Cesium.Viewer(containerRef.current, {
278
+ animation: false,
279
+ baseLayerPicker: false,
280
+ fullscreenButton: false,
281
+ geocoder: false,
282
+ homeButton: false,
283
+ infoBox: false,
284
+ sceneModePicker: false,
285
+ selectionIndicator: false,
286
+ timeline: false,
287
+ navigationHelpButton: false,
288
+ navigationInstructionsInitiallyVisible: false,
289
+ // Cesium ion ToS requires visible attribution — use a small container
290
+ // at bottom of the overlay rather than hiding credits entirely.
291
+ msaaSamples: 1,
292
+ requestRenderMode: true,
293
+ maximumRenderTimeChange: Infinity,
294
+ baseLayer: false,
295
+ });
296
+
297
+ if (cancelled) { viewer.destroy(); return; }
298
+
299
+ // Disable Cesium's user input — the IFC viewer controls the camera.
300
+ // Keep collision detection off since we set the camera programmatically.
301
+ const scene = viewer.scene;
302
+ const sscc = scene.screenSpaceCameraController;
303
+ sscc.enableRotate = false;
304
+ sscc.enableTranslate = false;
305
+ sscc.enableZoom = false;
306
+ sscc.enableTilt = false;
307
+ sscc.enableLook = false;
308
+ sscc.enableCollisionDetection = false;
309
+ sscc.minimumZoomDistance = 0;
310
+ sscc.maximumZoomDistance = Infinity;
311
+ // Enable depth testing so the model (and other objects) get clipped
312
+ // by terrain — prevents seeing underground portions.
313
+ scene.globe.depthTestAgainstTerrain = true;
314
+
315
+ // Move credit/logo from bottom-left to top-left to avoid overlap
316
+ // with other UI elements.
317
+ const bottomContainer = viewer.bottomContainer as HTMLElement;
318
+ if (bottomContainer) {
319
+ bottomContainer.style.top = '0';
320
+ bottomContainer.style.bottom = 'auto';
321
+ bottomContainer.style.left = '0';
322
+ bottomContainer.style.right = 'auto';
323
+ }
324
+
325
+ // Disable skybox/atmosphere/fog for transparent compositing
326
+ if (scene.skyBox) (scene.skyBox as any).show = false;
327
+ if (scene.sun) scene.sun.show = false;
328
+ if (scene.moon) scene.moon.show = false;
329
+ if (scene.skyAtmosphere) scene.skyAtmosphere.show = false;
330
+ scene.fog.enabled = false;
331
+ scene.globe.showGroundAtmosphere = false;
332
+ scene.backgroundColor = Cesium.Color.TRANSPARENT;
333
+ scene.globe.baseColor = Cesium.Color.TRANSPARENT;
334
+
335
+ // Add imagery
336
+ try {
337
+ const imageryProvider = await Cesium.IonImageryProvider.fromAssetId(2);
338
+ viewer.imageryLayers.addImageryProvider(imageryProvider);
339
+ } catch {
340
+ viewer.imageryLayers.addImageryProvider(
341
+ new Cesium.OpenStreetMapImageryProvider({
342
+ url: 'https://a.tile.openstreetmap.org/',
343
+ })
344
+ );
345
+ }
346
+
347
+ // Add terrain
348
+ if (terrainEnabled && ionToken) {
349
+ try {
350
+ const terrainProvider = await Cesium.CesiumTerrainProvider.fromIonAssetId(1);
351
+ viewer.terrainProvider = terrainProvider;
352
+ } catch { /* terrain unavailable */ }
353
+ }
354
+
355
+ // Add data source layer
356
+ await addDataSourceLayer(Cesium, viewer, dataSource, ionToken);
357
+
358
+ if (cancelled) { viewer.destroy(); return; }
359
+
360
+ viewerRef.current = viewer;
361
+ setStatus('ready');
362
+ } catch (err) {
363
+ if (!cancelled) {
364
+ console.error('[CesiumOverlay] Init failed:', err);
365
+ setError(err instanceof Error ? err.message : 'Cesium initialization failed');
366
+ setStatus('error');
367
+ }
368
+ }
369
+ })();
370
+
371
+ return () => {
372
+ cancelled = true;
373
+ if (rafRef.current !== null) {
374
+ cancelAnimationFrame(rafRef.current);
375
+ rafRef.current = null;
376
+ }
377
+ if (viewerRef.current) {
378
+ viewerRef.current.destroy();
379
+ viewerRef.current = null;
380
+ }
381
+ // Invalidate model ref — the destroyed viewer took the primitive with it,
382
+ // so Effect 2c must re-load the GLB into the next viewer instance.
383
+ cesiumModelRef.current = null;
384
+ bridgeRef.current = null;
385
+ setStatus('idle');
386
+ };
387
+ }, [cesiumEnabled, ionToken, terrainEnabled, dataSource]);
388
+
389
+ // ─── Effect 2: Rebuild coordinate bridge when georef changes (fast) ─────
390
+ // This is the live-edit handler. When the user changes EPSG, eastings,
391
+ // northings, rotation, etc., we rebuild the bridge and fly to the new spot.
392
+ useEffect(() => {
393
+ if (status !== 'ready' || !mapConversion || !projectedCRS) {
394
+ bridgeRef.current = null;
395
+ return;
396
+ }
397
+
398
+ let cancelled = false;
399
+
400
+ (async () => {
401
+ const bridge = await createCesiumBridge(mapConversion, projectedCRS, coordinateInfo, lengthUnitScale);
402
+ if (cancelled) return;
403
+
404
+ if (!bridge) {
405
+ bridgeRef.current = null;
406
+ return;
407
+ }
408
+
409
+ const prevBridge = bridgeRef.current;
410
+ bridgeRef.current = bridge;
411
+ autoClampedRef.current = false; // reset for new bridge
412
+ // Bump version so terrain query effect re-runs now that bridge is ready
413
+ setBridgeVersion((v) => v + 1);
414
+
415
+ // Fly to the new model location (smooth animation)
416
+ const viewer = viewerRef.current;
417
+ const Cesium = cesiumModule;
418
+ if (viewer && Cesium) {
419
+ const { modelOrigin } = bridge;
420
+
421
+ const isFirstPosition = !prevBridge;
422
+ const target = Cesium.Cartesian3.fromDegrees(
423
+ modelOrigin.longitude, modelOrigin.latitude, modelOrigin.height,
424
+ );
425
+
426
+ if (isFirstPosition) {
427
+ // First time: fly to the model location.
428
+ // On first load terrain tiles may not be ready, so globe.getHeight
429
+ // can return undefined. Use flyTo which handles terrain automatically,
430
+ // targeting a safe altitude above the model origin.
431
+ const safeHeight = Math.max(modelOrigin.height, 100);
432
+ viewer.camera.flyTo({
433
+ destination: Cesium.Cartesian3.fromDegrees(
434
+ modelOrigin.longitude, modelOrigin.latitude, safeHeight + 500,
435
+ ),
436
+ orientation: {
437
+ heading: 0,
438
+ pitch: Cesium.Math.toRadians(-45),
439
+ roll: 0,
440
+ },
441
+ duration: 0, // instant
442
+ });
443
+ } else if (prevBridge) {
444
+ // Georef edit: just re-render, the camera sync loop will pick
445
+ // up the new bridge on the next frame. No dramatic fly animation.
446
+ viewer.scene.requestRender();
447
+ }
448
+ }
449
+ })();
450
+
451
+ return () => { cancelled = true; };
452
+ }, [status, mapConversion, projectedCRS, coordinateInfo, lengthUnitScale]);
453
+
454
+ // ─── Effect 2b: Query terrain height when bridge is ready ───────────────
455
+ // Also re-queries when terrainClamp is toggled on (in case first query failed)
456
+ useEffect(() => {
457
+ if (status !== 'ready') return;
458
+ const bridge = bridgeRef.current;
459
+ const viewer = viewerRef.current;
460
+ const Cesium = cesiumModule;
461
+ if (!bridge || !viewer || !Cesium) return;
462
+
463
+ let cancelled = false;
464
+
465
+ // Query immediately, then retry after a delay if terrain tiles weren't loaded yet
466
+ // Compute model center Y in viewer space for terrain clip offset
467
+ const bounds = coordinateInfo?.originalBounds;
468
+ const modelVY = bounds ? (bounds.min.y + bounds.max.y) / 2 : 0;
469
+ const modelMinY = bounds ? bounds.min.y : 0;
470
+
471
+ const doQuery = () => {
472
+ bridge.queryTerrainHeight(Cesium, viewer).then((h) => {
473
+ if (!cancelled && h !== null) {
474
+ setCesiumTerrainHeight(h);
475
+ // Compute terrain clip Y in viewer space (both h and modelOrigin.height are metres,
476
+ // bounds are also in metres since the geometry engine converts during extraction)
477
+ const terrainClipY = modelMinY + (h - bridge.modelOrigin.height);
478
+ setCesiumTerrainClipY(terrainClipY);
479
+
480
+ // Auto-enable terrain clamping if the model is significantly below terrain
481
+ // (only once — don't override if the user has manually toggled)
482
+ if (!autoClampedRef.current && h > bridge.modelOrigin.height + 5) {
483
+ autoClampedRef.current = true;
484
+ setCesiumTerrainClamp(true);
485
+ // Fly camera to the clamped position so the model is visible
486
+ viewer.camera.flyTo({
487
+ destination: Cesium.Cartesian3.fromDegrees(
488
+ bridge.modelOrigin.longitude, bridge.modelOrigin.latitude, h + 500,
489
+ ),
490
+ orientation: { heading: 0, pitch: Cesium.Math.toRadians(-45), roll: 0 },
491
+ duration: 0,
492
+ });
493
+ }
494
+ }
495
+ });
496
+ };
497
+
498
+ // First attempt
499
+ doQuery();
500
+ // Retry after 5s in case terrain tiles were still loading
501
+ const retryTimer = setTimeout(doQuery, 5000);
502
+
503
+ return () => { cancelled = true; clearTimeout(retryTimer); };
504
+ }, [status, terrainEnabled, terrainClamp, bridgeVersion]);
505
+
506
+ // ─── Effect 2c: Load GLB into Cesium (only when geometry changes) ───────
507
+ // This is the heavy operation — only re-runs when geometry actually changes.
508
+ useEffect(() => {
509
+ if (status !== 'ready' || !geometryResult?.meshes?.length) return;
510
+ const viewer = viewerRef.current;
511
+ const bridge = bridgeRef.current;
512
+ const Cesium = cesiumModule;
513
+ if (!viewer || !bridge || !Cesium) return;
514
+
515
+ let cancelled = false;
516
+
517
+ const startExport = async () => {
518
+ if (cancelled) return;
519
+ try {
520
+ // Export GLB (cached by mesh count — skip if already loaded)
521
+ const meshCount = geometryResult.meshes.length;
522
+ if (cesiumModelRef.current && glbCacheRef.current?.meshCount === meshCount) {
523
+ // Model already loaded with same geometry — just update matrix
524
+ return;
525
+ }
526
+
527
+ // Remove previous model
528
+ if (cesiumModelRef.current) {
529
+ viewer.scene.primitives.remove(cesiumModelRef.current);
530
+ cesiumModelRef.current = null;
531
+ }
532
+
533
+ let glbBytes: Uint8Array;
534
+ if (glbCacheRef.current?.meshCount === meshCount) {
535
+ glbBytes = glbCacheRef.current.glb;
536
+ } else {
537
+ await new Promise(r => setTimeout(r, 50));
538
+ if (cancelled) return;
539
+ glbBytes = buildMergedGLB(geometryResult.meshes);
540
+ glbCacheRef.current = { meshCount, glb: glbBytes };
541
+ }
542
+ if (cancelled) return;
543
+
544
+ await new Promise(r => setTimeout(r, 0));
545
+ if (cancelled) return;
546
+
547
+ // Build initial model matrix
548
+ const modelMatrix = buildModelMatrix(Cesium, bridge, mapConversion, coordinateInfo, terrainClampRef.current, terrainHeightRef.current);
549
+
550
+ const blob = new Blob([glbBytes as BlobPart], { type: 'model/gltf-binary' });
551
+ const glbUrl = URL.createObjectURL(blob);
552
+ let model: { modelMatrix: any; destroy?: () => void } | null = null;
553
+ try {
554
+ model = await Cesium.Model.fromGltfAsync({
555
+ url: glbUrl,
556
+ modelMatrix,
557
+ shadows: Cesium.ShadowMode.DISABLED,
558
+ // The generated GLB stores viewer-space vertices and buildModelMatrix
559
+ // already maps viewer axes into ENU. Avoid Cesium's default glTF
560
+ // Y-up/Z-forward correction or the model is rotated onto its side.
561
+ upAxis: Cesium.Axis.Z,
562
+ forwardAxis: Cesium.Axis.X,
563
+ });
564
+ } finally {
565
+ URL.revokeObjectURL(glbUrl);
566
+ }
567
+ if (cancelled) {
568
+ model?.destroy?.();
569
+ return;
570
+ }
571
+
572
+ viewer.scene.primitives.add(model);
573
+ cesiumModelRef.current = model;
574
+ setCesiumGlbLoaded(true);
575
+ viewer.scene.requestRender();
576
+ } catch (err) {
577
+ console.warn('[CesiumOverlay] Failed to load IFC model into Cesium:', err);
578
+ }
579
+ };
580
+
581
+ const deferTimer = setTimeout(startExport, 1000);
582
+
583
+ return () => {
584
+ cancelled = true;
585
+ clearTimeout(deferTimer);
586
+ if (cesiumModelRef.current && viewerRef.current) {
587
+ viewerRef.current.scene.primitives.remove(cesiumModelRef.current);
588
+ cesiumModelRef.current = null;
589
+ }
590
+ setCesiumGlbLoaded(false);
591
+ };
592
+ }, [status, bridgeVersion, geometryResult]);
593
+
594
+ // ─── Effect 2d: Update model matrix (instant, no reload) ────────────────
595
+ // When terrain clamp, terrain height, or georef changes, just update the
596
+ // existing model's matrix — no GLB re-export, no flicker.
597
+ useEffect(() => {
598
+ const model = cesiumModelRef.current;
599
+ const bridge = bridgeRef.current;
600
+ const viewer = viewerRef.current;
601
+ const Cesium = cesiumModule;
602
+ if (!model || !bridge || !viewer || !Cesium) return;
603
+
604
+ const newMatrix = buildModelMatrix(Cesium, bridge, mapConversion, coordinateInfo, terrainClamp, terrainHeight);
605
+ model.modelMatrix = newMatrix;
606
+ viewer.scene.requestRender();
607
+ }, [terrainClamp, terrainHeight, mapConversion, coordinateInfo, lengthUnitScale]);
608
+
609
+ // ─── Effect 3: Camera sync loop ─────────────────────────────────────────
610
+ useEffect(() => {
611
+ if (status !== 'ready') return;
612
+
613
+ const viewer = viewerRef.current;
614
+ if (!viewer) return;
615
+
616
+ let cancelled = false;
617
+
618
+ function syncCamera() {
619
+ if (cancelled) return;
620
+
621
+ const bridge = bridgeRef.current;
622
+ const renderer = getGlobalRenderer();
623
+ const Cesium = cesiumModule;
624
+ if (!viewer || !bridge || !renderer || !Cesium) {
625
+ rafRef.current = requestAnimationFrame(syncCamera);
626
+ return;
627
+ }
628
+
629
+ const camera = renderer.getCamera();
630
+ const camPos = camera.getPosition();
631
+ const camTarget = camera.getTarget();
632
+ const camUp = camera.getUp();
633
+ const fov = camera.getFOV();
634
+
635
+ // Sync Cesium camera (no terrain offset — model matrix handles clamping)
636
+ bridge.syncCamera(Cesium, viewer, camPos, camTarget, camUp, fov);
637
+
638
+ rafRef.current = requestAnimationFrame(syncCamera);
639
+ }
640
+
641
+ rafRef.current = requestAnimationFrame(syncCamera);
642
+
643
+ return () => {
644
+ cancelled = true;
645
+ if (rafRef.current !== null) {
646
+ cancelAnimationFrame(rafRef.current);
647
+ rafRef.current = null;
648
+ }
649
+ };
650
+ }, [status]);
651
+
652
+ if (!cesiumEnabled || !mapConversion || !projectedCRS) {
653
+ return null;
654
+ }
655
+
656
+ return (
657
+ <>
658
+ <div
659
+ ref={containerRef}
660
+ className="absolute inset-0 z-0"
661
+ style={{ pointerEvents: 'none' }}
662
+ />
663
+ {status === 'loading' && (
664
+ <div className="absolute top-2 left-1/2 -translate-x-1/2 z-10 flex items-center gap-2 px-3 py-1.5 bg-black/60 backdrop-blur-sm rounded text-xs text-white font-mono">
665
+ <div className="h-3 w-3 border-2 border-white/30 border-t-white rounded-full animate-spin" />
666
+ Loading 3D context...
667
+ </div>
668
+ )}
669
+ {status === 'error' && error && (
670
+ <div className="absolute top-2 left-1/2 -translate-x-1/2 z-10 px-3 py-1.5 bg-red-900/80 backdrop-blur-sm rounded text-xs text-red-200 font-mono">
671
+ {error}
672
+ </div>
673
+ )}
674
+ </>
675
+ );
676
+ }
677
+
678
+ /**
679
+ * Add the selected 3D data source layer to the Cesium viewer.
680
+ */
681
+ async function addDataSourceLayer(
682
+ Cesium: typeof import('cesium'),
683
+ viewer: InstanceType<typeof import('cesium').Viewer>,
684
+ dataSource: string,
685
+ ionToken: string,
686
+ ) {
687
+ try {
688
+ switch (dataSource) {
689
+ case 'osm-buildings': {
690
+ if (!ionToken) return;
691
+ const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(96188);
692
+ viewer.scene.primitives.add(tileset);
693
+ break;
694
+ }
695
+ case 'google-photorealistic': {
696
+ try {
697
+ const tileset = await Cesium.createGooglePhotorealistic3DTileset();
698
+ viewer.scene.primitives.add(tileset);
699
+ } catch {
700
+ if (ionToken) {
701
+ const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(2275207);
702
+ viewer.scene.primitives.add(tileset);
703
+ }
704
+ }
705
+ break;
706
+ }
707
+ case 'bing-aerial':
708
+ default:
709
+ // No 3D tileset for Bing — imagery is added separately via imageryLayers
710
+ break;
711
+ }
712
+ } catch (err) {
713
+ console.warn('[CesiumOverlay] Failed to add data source:', dataSource, err);
714
+ }
715
+ }