react-babylon-map 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.claude/settings.local.json +78 -0
  2. package/demo.html +161 -0
  3. package/dist/cjs/main.js +520 -0
  4. package/dist/es/main.mjs +20 -0
  5. package/dist/es/main.mjs.map +1 -0
  6. package/dist/es/main10.mjs +33 -0
  7. package/dist/es/main10.mjs.map +1 -0
  8. package/dist/es/main11.mjs +12 -0
  9. package/dist/es/main11.mjs.map +1 -0
  10. package/dist/es/main12.mjs +14 -0
  11. package/dist/es/main12.mjs.map +1 -0
  12. package/dist/es/main13.mjs +12 -0
  13. package/dist/es/main13.mjs.map +1 -0
  14. package/dist/es/main14.mjs +5 -0
  15. package/dist/es/main14.mjs.map +1 -0
  16. package/dist/es/main15.mjs +12 -0
  17. package/dist/es/main15.mjs.map +1 -0
  18. package/dist/es/main16.mjs +25 -0
  19. package/dist/es/main16.mjs.map +1 -0
  20. package/dist/es/main17.mjs +54 -0
  21. package/dist/es/main17.mjs.map +1 -0
  22. package/dist/es/main18.mjs +88 -0
  23. package/dist/es/main18.mjs.map +1 -0
  24. package/dist/es/main19.mjs +18 -0
  25. package/dist/es/main19.mjs.map +1 -0
  26. package/dist/es/main2.mjs +9 -0
  27. package/dist/es/main2.mjs.map +1 -0
  28. package/dist/es/main20.mjs +21 -0
  29. package/dist/es/main20.mjs.map +1 -0
  30. package/dist/es/main21.mjs +61 -0
  31. package/dist/es/main21.mjs.map +1 -0
  32. package/dist/es/main3.mjs +46 -0
  33. package/dist/es/main3.mjs.map +1 -0
  34. package/dist/es/main4.mjs +23 -0
  35. package/dist/es/main4.mjs.map +1 -0
  36. package/dist/es/main5.mjs +69 -0
  37. package/dist/es/main5.mjs.map +1 -0
  38. package/dist/es/main6.mjs +35 -0
  39. package/dist/es/main6.mjs.map +1 -0
  40. package/dist/es/main7.mjs +65 -0
  41. package/dist/es/main7.mjs.map +1 -0
  42. package/dist/es/main8.mjs +14 -0
  43. package/dist/es/main8.mjs.map +1 -0
  44. package/dist/es/main9.mjs +26 -0
  45. package/dist/es/main9.mjs.map +1 -0
  46. package/dist/maplibre/cjs/main.js +520 -0
  47. package/dist/maplibre/es/main.mjs +20 -0
  48. package/dist/maplibre/es/main.mjs.map +1 -0
  49. package/dist/maplibre/es/main10.mjs +33 -0
  50. package/dist/maplibre/es/main10.mjs.map +1 -0
  51. package/dist/maplibre/es/main11.mjs +12 -0
  52. package/dist/maplibre/es/main11.mjs.map +1 -0
  53. package/dist/maplibre/es/main12.mjs +14 -0
  54. package/dist/maplibre/es/main12.mjs.map +1 -0
  55. package/dist/maplibre/es/main13.mjs +12 -0
  56. package/dist/maplibre/es/main13.mjs.map +1 -0
  57. package/dist/maplibre/es/main14.mjs +5 -0
  58. package/dist/maplibre/es/main14.mjs.map +1 -0
  59. package/dist/maplibre/es/main15.mjs +12 -0
  60. package/dist/maplibre/es/main15.mjs.map +1 -0
  61. package/dist/maplibre/es/main16.mjs +25 -0
  62. package/dist/maplibre/es/main16.mjs.map +1 -0
  63. package/dist/maplibre/es/main17.mjs +54 -0
  64. package/dist/maplibre/es/main17.mjs.map +1 -0
  65. package/dist/maplibre/es/main18.mjs +88 -0
  66. package/dist/maplibre/es/main18.mjs.map +1 -0
  67. package/dist/maplibre/es/main19.mjs +18 -0
  68. package/dist/maplibre/es/main19.mjs.map +1 -0
  69. package/dist/maplibre/es/main2.mjs +9 -0
  70. package/dist/maplibre/es/main2.mjs.map +1 -0
  71. package/dist/maplibre/es/main20.mjs +61 -0
  72. package/dist/maplibre/es/main20.mjs.map +1 -0
  73. package/dist/maplibre/es/main21.mjs +21 -0
  74. package/dist/maplibre/es/main21.mjs.map +1 -0
  75. package/dist/maplibre/es/main3.mjs +46 -0
  76. package/dist/maplibre/es/main3.mjs.map +1 -0
  77. package/dist/maplibre/es/main4.mjs +23 -0
  78. package/dist/maplibre/es/main4.mjs.map +1 -0
  79. package/dist/maplibre/es/main5.mjs +69 -0
  80. package/dist/maplibre/es/main5.mjs.map +1 -0
  81. package/dist/maplibre/es/main6.mjs +35 -0
  82. package/dist/maplibre/es/main6.mjs.map +1 -0
  83. package/dist/maplibre/es/main7.mjs +65 -0
  84. package/dist/maplibre/es/main7.mjs.map +1 -0
  85. package/dist/maplibre/es/main8.mjs +14 -0
  86. package/dist/maplibre/es/main8.mjs.map +1 -0
  87. package/dist/maplibre/es/main9.mjs +26 -0
  88. package/dist/maplibre/es/main9.mjs.map +1 -0
  89. package/dist/maplibre/types/api/canvas-props.d.ts +9 -0
  90. package/dist/maplibre/types/api/coordinates.d.ts +13 -0
  91. package/dist/maplibre/types/api/coords-to-vector-3.d.ts +3 -0
  92. package/dist/maplibre/types/api/coords.d.ts +5 -0
  93. package/dist/maplibre/types/api/index.d.ts +7 -0
  94. package/dist/maplibre/types/api/near-coordinates.d.ts +13 -0
  95. package/dist/maplibre/types/api/use-map.d.ts +3 -0
  96. package/dist/maplibre/types/api/vector-3-to-coords.d.ts +2 -0
  97. package/dist/maplibre/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
  98. package/dist/maplibre/types/core/canvas-in-layer/use-render.d.ts +15 -0
  99. package/dist/maplibre/types/core/canvas-in-layer/use-root.d.ts +11 -0
  100. package/dist/maplibre/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
  101. package/dist/maplibre/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
  102. package/dist/maplibre/types/core/canvas-overlay/render.d.ts +1 -0
  103. package/dist/maplibre/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
  104. package/dist/maplibre/types/core/coords-to-matrix.d.ts +9 -0
  105. package/dist/maplibre/types/core/earth-radius.d.ts +1 -0
  106. package/dist/maplibre/types/core/generic-map.d.ts +49 -0
  107. package/dist/maplibre/types/core/matrix-utils.d.ts +7 -0
  108. package/dist/maplibre/types/core/sync-camera.d.ts +7 -0
  109. package/dist/maplibre/types/core/use-babylon-map.d.ts +32 -0
  110. package/dist/maplibre/types/core/use-coords-to-matrix.d.ts +6 -0
  111. package/dist/maplibre/types/core/use-coords.d.ts +5 -0
  112. package/dist/maplibre/types/core/use-function.d.ts +1 -0
  113. package/dist/maplibre/types/maplibre/canvas.d.ts +4 -0
  114. package/dist/maplibre/types/maplibre.index.d.ts +4 -0
  115. package/dist/types/api/canvas-props.d.ts +9 -0
  116. package/dist/types/api/coordinates.d.ts +13 -0
  117. package/dist/types/api/coords-to-vector-3.d.ts +3 -0
  118. package/dist/types/api/coords.d.ts +5 -0
  119. package/dist/types/api/index.d.ts +7 -0
  120. package/dist/types/api/near-coordinates.d.ts +13 -0
  121. package/dist/types/api/use-map.d.ts +3 -0
  122. package/dist/types/api/vector-3-to-coords.d.ts +2 -0
  123. package/dist/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
  124. package/dist/types/core/canvas-in-layer/use-render.d.ts +15 -0
  125. package/dist/types/core/canvas-in-layer/use-root.d.ts +11 -0
  126. package/dist/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
  127. package/dist/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
  128. package/dist/types/core/canvas-overlay/render.d.ts +1 -0
  129. package/dist/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
  130. package/dist/types/core/coords-to-matrix.d.ts +9 -0
  131. package/dist/types/core/earth-radius.d.ts +1 -0
  132. package/dist/types/core/generic-map.d.ts +49 -0
  133. package/dist/types/core/matrix-utils.d.ts +7 -0
  134. package/dist/types/core/sync-camera.d.ts +7 -0
  135. package/dist/types/core/use-babylon-map.d.ts +32 -0
  136. package/dist/types/core/use-coords-to-matrix.d.ts +6 -0
  137. package/dist/types/core/use-coords.d.ts +5 -0
  138. package/dist/types/core/use-function.d.ts +1 -0
  139. package/dist/types/mapbox/canvas.d.ts +4 -0
  140. package/dist/types/mapbox.index.d.ts +4 -0
  141. package/package.json +58 -0
  142. package/plan.md +719 -0
  143. package/src/api/canvas-props.ts +10 -0
  144. package/src/api/coordinates.tsx +83 -0
  145. package/src/api/coords-to-vector-3.ts +39 -0
  146. package/src/api/coords.tsx +6 -0
  147. package/src/api/index.ts +7 -0
  148. package/src/api/near-coordinates.tsx +87 -0
  149. package/src/api/use-map.ts +8 -0
  150. package/src/api/vector-3-to-coords.ts +13 -0
  151. package/src/core/canvas-in-layer/use-canvas-in-layer.tsx +27 -0
  152. package/src/core/canvas-in-layer/use-render.ts +43 -0
  153. package/src/core/canvas-in-layer/use-root.tsx +82 -0
  154. package/src/core/canvas-overlay/canvas-portal.tsx +98 -0
  155. package/src/core/canvas-overlay/init-canvas-fc.tsx +45 -0
  156. package/src/core/canvas-overlay/render.tsx +1 -0
  157. package/src/core/canvas-overlay/sync-camera-fc.tsx +83 -0
  158. package/src/core/coords-to-matrix.ts +21 -0
  159. package/src/core/earth-radius.ts +1 -0
  160. package/src/core/events.ts +55 -0
  161. package/src/core/generic-map.ts +59 -0
  162. package/src/core/map-engine.tsx +70 -0
  163. package/src/core/matrix-utils.ts +22 -0
  164. package/src/core/sync-camera.ts +29 -0
  165. package/src/core/use-babylon-map.ts +46 -0
  166. package/src/core/use-coords-to-matrix.ts +13 -0
  167. package/src/core/use-coords.tsx +22 -0
  168. package/src/core/use-function.ts +10 -0
  169. package/src/mapbox/canvas.tsx +59 -0
  170. package/src/mapbox.index.ts +7 -0
  171. package/src/maplibre/canvas.tsx +59 -0
  172. package/src/maplibre.index.ts +7 -0
  173. package/src/vite-env.d.ts +1 -0
  174. package/stories/.ladle/components.tsx +50 -0
  175. package/stories/.ladle/style.css +63 -0
  176. package/stories/package.json +31 -0
  177. package/stories/pnpm-lock.yaml +5450 -0
  178. package/stories/sandbox.config.json +3 -0
  179. package/stories/src/adaptive-dpr.tsx +34 -0
  180. package/stories/src/billboard.stories.tsx +111 -0
  181. package/stories/src/buildings-3d.stories.tsx +280 -0
  182. package/stories/src/canvas/mapbox.stories.tsx +113 -0
  183. package/stories/src/canvas/maplibre.stories.tsx +93 -0
  184. package/stories/src/comparison.stories.tsx +161 -0
  185. package/stories/src/extrude/chaillot.ts +8 -0
  186. package/stories/src/exude-coordinates.stories.tsx +139 -0
  187. package/stories/src/free-3d-buildings/get-buildings-data.ts +49 -0
  188. package/stories/src/html-on-top.stories.tsx +156 -0
  189. package/stories/src/ifc/ifc-to-babylon.ts +97 -0
  190. package/stories/src/ifc/ifc.main.ts +904 -0
  191. package/stories/src/ifc/ifc2bb.ts +343 -0
  192. package/stories/src/ifc/model.ifc +14155 -0
  193. package/stories/src/ifc.stories.tsx +276 -0
  194. package/stories/src/mapbox/story-mapbox.tsx +97 -0
  195. package/stories/src/maplibre/story-maplibre.tsx +36 -0
  196. package/stories/src/multi-coordinates.stories.tsx +115 -0
  197. package/stories/src/pivot-controls.stories.tsx +148 -0
  198. package/stories/src/postprocessing.stories.tsx +125 -0
  199. package/stories/src/render-on-demand.stories.tsx +76 -0
  200. package/stories/src/story-map.tsx +44 -0
  201. package/stories/src/sunlight.stories.tsx +215 -0
  202. package/stories/src/vite-env.d.ts +1 -0
  203. package/stories/tsconfig.json +32 -0
  204. package/stories/tsconfig.node.json +10 -0
  205. package/stories/vite.config.ts +27 -0
  206. package/tsconfig.json +31 -0
  207. package/tsconfig.mapbox.json +7 -0
  208. package/tsconfig.maplibre.json +7 -0
  209. package/tsconfig.node.json +10 -0
  210. package/tsconfig.types.json +25 -0
  211. package/vite.config.ts +65 -0
@@ -0,0 +1,161 @@
1
+ import { FC, memo, useEffect, useRef, useState } from "react";
2
+ import {
3
+ MeshBuilder, StandardMaterial, Vector3, Color3, Color4,
4
+ HemisphericLight, DirectionalLight, PointLight, ShadowGenerator,
5
+ GroundMesh,
6
+ } from '@babylonjs/core';
7
+ import { useControls } from "leva";
8
+ import { StoryMap } from "./story-map";
9
+ import { useBabylonMap } from 'react-babylon-map';
10
+
11
+ // ── Lights ──────────────────────────────────────────────────────
12
+
13
+ function Lights({ showCamHelper }: { showCamHelper?: boolean }) {
14
+ const { scene } = useBabylonMap();
15
+
16
+ useEffect(() => {
17
+ if (!scene) return;
18
+
19
+ // Ambient
20
+ const hemi = new HemisphericLight("ambient", new Vector3(0, 1, 0), scene);
21
+ hemi.intensity = 0.5;
22
+
23
+ // Directional (sun) with shadows
24
+ const dir = new DirectionalLight("dir", new Vector3(-2.5, -50, -5).normalize(), scene);
25
+ dir.position = new Vector3(2.5, 50, 5);
26
+ dir.intensity = 1.5;
27
+
28
+ const shadowGen = new ShadowGenerator(1024, dir);
29
+ shadowGen.useBlurExponentialShadowMap = true;
30
+ shadowGen.blurKernel = 32;
31
+
32
+ // Point lights
33
+ const p1 = new PointLight("p1", new Vector3(50, 5, 10), scene);
34
+ p1.intensity = 1;
35
+ const p2 = new PointLight("p2", new Vector3(-50, 5, 10), scene);
36
+ p2.intensity = 1;
37
+ const p3 = new PointLight("p3", new Vector3(0, 5, 0), scene);
38
+ p3.intensity = 1;
39
+
40
+ // Store shadow generator on scene metadata so boxes can add themselves
41
+ (scene as any)._shadowGenerator = shadowGen;
42
+
43
+ return () => {
44
+ shadowGen.dispose();
45
+ hemi.dispose(); dir.dispose();
46
+ p1.dispose(); p2.dispose(); p3.dispose();
47
+ };
48
+ }, [scene]);
49
+
50
+ return null;
51
+ }
52
+ Lights.displayName = 'Lights';
53
+
54
+ // ── Floor ───────────────────────────────────────────────────────
55
+
56
+ function Floor() {
57
+ const { scene } = useBabylonMap();
58
+
59
+ useEffect(() => {
60
+ if (!scene) return;
61
+ const ground = MeshBuilder.CreateGround("floor", { width: 200, height: 200 }, scene);
62
+ const mat = new StandardMaterial("floorMat", scene);
63
+ mat.diffuseColor = new Color3(0.3, 0.3, 0.3);
64
+ mat.alpha = 0.5;
65
+ ground.material = mat;
66
+ ground.receiveShadows = true;
67
+
68
+ return () => { ground.dispose(); mat.dispose(); };
69
+ }, [scene]);
70
+
71
+ return null;
72
+ }
73
+ Floor.displayName = 'Floor';
74
+
75
+ // ── Animated Box ────────────────────────────────────────────────
76
+
77
+ function MyBox({ position, animate }: { position: [number, number, number]; animate?: boolean }) {
78
+ const { scene } = useBabylonMap();
79
+ const [hovered, setHovered] = useState(false);
80
+ const boxRef = useRef<any>(null);
81
+ const matRef = useRef<any>(null);
82
+
83
+ useEffect(() => {
84
+ if (!scene) return;
85
+
86
+ const box = MeshBuilder.CreateBox("box", { width: 16, height: 16, depth: 16 }, scene);
87
+ box.position = new Vector3(position[0], position[1], position[2]);
88
+ boxRef.current = box;
89
+
90
+ const mat = new StandardMaterial("boxMat", scene);
91
+ mat.diffuseColor = Color3.FromHexString("#ffa500");
92
+ mat.specularColor = new Color3(0.3, 0.3, 0.3);
93
+ matRef.current = mat;
94
+ box.material = mat;
95
+
96
+ // Add to shadow caster list
97
+ const shadowGen = (scene as any)._shadowGenerator as ShadowGenerator | undefined;
98
+ if (shadowGen) shadowGen.addShadowCaster(box);
99
+
100
+ box.metadata = {
101
+ onPointerOver: () => setHovered(true),
102
+ onPointerOut: () => setHovered(false),
103
+ };
104
+
105
+ return () => {
106
+ box.dispose(); mat.dispose();
107
+ };
108
+ }, [scene]);
109
+
110
+ // Rotation animation
111
+ useEffect(() => {
112
+ if (!scene) return;
113
+ if (!animate) return;
114
+
115
+ let lastTime = performance.now();
116
+ const observer = scene.onBeforeRenderObservable.add(() => {
117
+ if (!boxRef.current) return;
118
+ const now = performance.now();
119
+ const dt = (now - lastTime) / 1000;
120
+ lastTime = now;
121
+ boxRef.current.rotation.y += dt;
122
+ });
123
+ return () => scene.onBeforeRenderObservable.remove(observer);
124
+ }, [scene, animate]);
125
+
126
+ // Hover color
127
+ useEffect(() => {
128
+ if (!matRef.current) return;
129
+ matRef.current.diffuseColor = hovered
130
+ ? Color3.FromHexString("#ff0000")
131
+ : Color3.FromHexString("#ffa500");
132
+ }, [hovered]);
133
+
134
+ return null;
135
+ }
136
+ MyBox.displayName = 'MyBox';
137
+
138
+ // ── Stories ─────────────────────────────────────────────────────
139
+
140
+ export default { title: 'Comparison' };
141
+
142
+ export function WithMap() {
143
+ const { showCamHelper } = useControls({ showCamHelper: { value: false, label: 'show camera helper' } });
144
+ const { animate } = useControls({ animate: true });
145
+
146
+ return (
147
+ <div style={{ height: '100vh', position: 'relative' }}>
148
+ <StoryMap
149
+ latitude={51.5073218}
150
+ longitude={-0.1276473}
151
+ zoom={18}
152
+ pitch={60}
153
+ >
154
+ <Lights showCamHelper={showCamHelper} />
155
+ <Floor />
156
+ <MyBox animate={animate} position={[-8 * 3, 8 * 1.5, 0]} />
157
+ <MyBox animate={animate} position={[8 * 3, 8 * 1.5, 0]} />
158
+ </StoryMap>
159
+ </div>
160
+ );
161
+ }
@@ -0,0 +1,8 @@
1
+ type Poly2D = [number, number][];
2
+
3
+ export const Chaillot: Poly2D[] = [
4
+ [[2.2876091301441193,48.86232738746588],[2.287643998861313,48.86230709517818],[2.2876587510108948,48.86231680018636],[2.2876694798469543,48.862309741998786],[2.2876547276973724,48.86230003698921],[2.287694960832596,48.86227356877174],[2.2877097129821777,48.86228415606041],[2.2877217829227448,48.86227709786823],[2.287707030773163,48.86226651057805],[2.287745922803879,48.862241806892314],[2.2877606749534607,48.86225151191317],[2.287774085998535,48.862243571441724],[2.2877593338489532,48.86223298414444],[2.287798225879669,48.86220828044213],[2.287812978029251,48.8622179854695],[2.287825047969818,48.86221004499271],[2.287810295820236,48.86219945768838],[2.2878505289554596,48.862174753969526],[2.2878652811050415,48.86218445900337],[2.2878773510456085,48.86217651852127],[2.2878625988960266,48.86216593120986],[2.28790283203125,48.86214034519796],[2.2879189252853394,48.862150932514794],[2.2879309952259064,48.86214299202737],[2.2879162430763245,48.86213328698548],[2.287956476211548,48.86210681867982],[2.2879712283611298,48.862116523726854],[2.2879832983016968,48.862108583233976],[2.287968546152115,48.86209887818541],[2.288007438182831,48.86207329213923],[2.28802353143692,48.862083879470276],[2.288035601377487,48.86207682124984],[2.2880208492279053,48.86206623391732],[2.288047671318054,48.862047706080006],[2.2879792749881744,48.86200094531725],[2.288035601377487,48.86196388958743],[2.2880007326602936,48.86194095031237],[2.2880208492279053,48.86192683383018],[2.287999391555786,48.86191271734401],[2.287968546152115,48.8619135996245],[2.2879403829574585,48.861910070502404],[2.2879256308078766,48.86190654138005],[2.287900149822235,48.86189595401149],[2.287878692150116,48.861883602078706],[2.287861257791519,48.86186683873646],[2.287849187850952,48.86184831082531],[2.28784516453743,48.86182890062537],[2.2878478467464447,48.8618094904179],[2.2878263890743256,48.861795373898616],[2.2878049314022064,48.86180860813556],[2.287770062685013,48.86178478650655],[2.2877123951911926,48.861820960086845],[2.287643998861313,48.861775081395194],[2.2876332700252533,48.861780375092536],[2.2875353693962097,48.86170538099469],[2.2873100638389587,48.861511278102],[2.2872644662857056,48.861463634549665],[2.2871947288513184,48.861383346238455],[2.28715717792511,48.8613312911107],[2.2871048748493195,48.861241297372345],[2.2870901226997375,48.86121306400929],[2.2870485484600067,48.86112483464717],[2.28703111410141,48.86107189695525],[2.2866368293762207,48.86107189695525],[2.286640852689743,48.86109130744879],[2.2866716980934143,48.86118483063035],[2.28671595454216,48.86128011822049],[2.286752164363861,48.86134981921322],[2.2867950797080994,48.86141687324121],[2.2868473827838898,48.86149451463507],[2.286883592605591,48.861535982148354],[2.2870364785194397,48.86169302901487],[2.2871196269989014,48.86176802313122],[2.2872430086135864,48.861871250142855],[2.287355661392212,48.861957713629806],[2.2872912883758545,48.86199829848033],[2.287275195121765,48.86199565164324],[2.2872671484947205,48.86199565164324],[2.287251055240631,48.86199829848033],[2.287236303091049,48.86200270987513],[2.2872282564640045,48.862007121269556],[2.28722020983696,48.862014179499795],[2.2872135043144226,48.86202388456479],[2.287212163209915,48.862029178235844],[2.2872108221054077,48.86204064785446],[2.2872135043144226,48.862047706080006],[2.2872067987918854,48.862052117470455],[2.287275195121765,48.86209799590816],[2.2872188687324524,48.8621350515387],[2.2872550785541534,48.86215975527713],[2.287241667509079,48.86216857803791],[2.2873395681381226,48.86223563096897],[2.287433445453644,48.86230003698921],[2.2874468564987183,48.86229121425163],[2.2874844074249268,48.86231591791298],[2.287542074918747,48.862280626964434],[2.2876091301441193,48.86232738746588]],
5
+ [[2.290222942829132,48.86337287028812],[2.290288656949997,48.86336934126891],[2.290354371070862,48.86336228322975],[2.290399968624115,48.86335610744467],[2.2904670238494873,48.86334375587222],[2.2905313968658447,48.863328757530184],[2.2905756533145905,48.863316405951],[2.290634661912918,48.863297878576475],[2.2906923294067383,48.86327582216933],[2.2906936705112457,48.863274939912856],[2.2906936705112457,48.8629361522716],[2.290666848421097,48.862956444304245],[2.290622591972351,48.86298644120703],[2.290574312210083,48.86301379130862],[2.290540784597397,48.86303143652748],[2.2904710471630096,48.86306231564558],[2.2904106974601746,48.86308437214683],[2.290370464324951,48.863094959263975],[2.2903288900852203,48.86310378185988],[2.290285974740982,48.8631099576761],[2.2902430593967438,48.86311436897293],[2.2901558876037598,48.86311613349156],[2.2901062667369843,48.86311525123227],[2.290057986974716,48.8631108399355],[2.2900083661079407,48.86310378185988],[2.289942651987076,48.86309407700432],[2.289879620075226,48.86308172536721],[2.289816588163376,48.863065844686446],[2.2897562384605408,48.863047317219156],[2.2896382212638855,48.863010262264055],[2.2895389795303345,48.86297497180513],[2.28946253657341,48.8629449748955],[2.2893592715263367,48.86289909723419],[2.2892600297927856,48.862849690475],[2.2891902923583984,48.8628117531089],[2.2890588641166687,48.86273764282248],[2.2889676690101624,48.86268206003561],[2.2889770567417145,48.86267676643365],[2.2889140248298645,48.86263353532996],[2.2889703512191772,48.86259824460541],[2.288934141397476,48.86257265881454],[2.288954257965088,48.86255942477965],[2.2889328002929688,48.86254530847191],[2.288903295993805,48.86254619074123],[2.288873791694641,48.862542661663724],[2.288846969604492,48.86253472123846],[2.288822829723358,48.862522369463306],[2.288804054260254,48.86250737087511],[2.288789302110672,48.86248972547142],[2.2887812554836273,48.86247119779091],[2.2887812554836273,48.862451787832526],[2.2887825965881348,48.86244296512166],[2.2887611389160156,48.86242796650964],[2.2887396812438965,48.8624412005793],[2.288704812526703,48.86241737925141],[2.2886458039283752,48.86245355237449],[2.2885867953300476,48.86241208562143],[2.288578748703003,48.86240679199091],[2.288549244403839,48.86242620196674],[2.2885331511497498,48.86241561470814],[2.28852242231369,48.862422672880825],[2.288537174463272,48.86243326013792],[2.2884996235370636,48.862457963729184],[2.288482189178467,48.86244649420618],[2.2884687781333923,48.862455316916396],[2.2884848713874817,48.86246678643738],[2.2884459793567657,48.862493254552675],[2.2884298861026764,48.86248266730823],[2.288416475057602,48.86249060774176],[2.2884325683116913,48.86250119498453],[2.288392335176468,48.86252766308158],[2.2883762419223785,48.86251707584444],[2.288362830877304,48.8625241340028],[2.288380265235901,48.86253560350801],[2.2883400321006775,48.86256118931783],[2.2883225977420807,48.86255060208774],[2.2883105278015137,48.86255766024138],[2.288326621055603,48.862570012007836],[2.2882890701293945,48.86259383326308],[2.2882716357707977,48.86258324603992],[2.288256883621216,48.86259206872606],[2.2882743179798126,48.862603538215666],[2.288236767053604,48.86262735945499],[2.2882193326950073,48.86261588997081],[2.2882072627544403,48.86262471265118],[2.2882233560085297,48.86263529986556],[2.28814959526062,48.86268206003561],[2.2881415486335754,48.86268735363706],[2.288209944963455,48.86273411375848],[2.2881536185741425,48.86277028665262],[2.288157641887665,48.86277293344875],[2.288191169500351,48.862795872342474],[2.288176417350769,48.86280469499113],[2.288272976875305,48.862870864806325],[2.2883695363998413,48.8629361522716],[2.2883829474449158,48.8629264473835],[2.28840172290802,48.862938799058924],[2.288423180580139,48.86295379751786],[2.2884781658649445,48.862914977967876],[2.288546562194824,48.862961737876645],[2.2885559499263763,48.862955562042146],[2.2885707020759583,48.86295909109052],[2.2885867953300476,48.86295909109052],[2.2885948419570923,48.862957326566345],[2.288609594106674,48.86295379751786],[2.2886230051517487,48.8629458571578],[2.2886310517787933,48.86293791679651],[2.2886377573013306,48.86292291833283],[2.2886377573013306,48.862917624756335],[2.2886337339878082,48.86290615533866],[2.2886981070041656,48.86286380669688],[2.2888724505901337,48.86296703144848],[2.289125919342041,48.86309760604291],[2.2892452776432037,48.86315495288551],[2.2893472015857697,48.86319730128096],[2.289453148841858,48.86323612061193],[2.289561778306961,48.86327052863015],[2.289597988128662,48.863281115707935],[2.28970929980278,48.86331023016024],[2.2898246347904205,48.86333493331861],[2.2899332642555237,48.863354342934514],[2.290021777153015,48.86336404773965],[2.290111631155014,48.863371105778555],[2.290201485157013,48.86337287028812],[2.290222942829132,48.86337287028812]],
6
+ [[2.2870512306690216,48.86113012841329],[2.2870485484600067,48.86112483464717],[2.28703111410141,48.86107189695525],[2.2870177030563354,48.86101719461476],[2.287016361951828,48.86098102045355],[2.2870177030563354,48.860944846266165],[2.2870230674743652,48.860915730437824],[2.28703111410141,48.860889261488296],[2.287043184041977,48.860863674823776],[2.2870592772960663,48.86083808814615],[2.2870874404907227,48.86079926695513],[2.2871115803718567,48.86077279794398],[2.2871585190296173,48.86073221209972],[2.2872456908226013,48.86066956866679],[2.287319451570511,48.86062457122293],[2.2873127460479736,48.860620159706656],[2.2873328626155853,48.86060780745899],[2.287263125181198,48.86055928074225],[2.287307381629944,48.860530164689635],[2.287265807390213,48.860500166314665],[2.2872738540172577,48.860494872481894],[2.2871142625808716,48.86038458417244],[2.2871048748493195,48.86038987801686],[2.2870592772960663,48.86035987955779],[2.287013679742813,48.86038987801686],[2.2870056331157684,48.860383701864976],[2.28684201836586,48.86048957864864],[2.2868500649929047,48.86049575478742],[2.286805808544159,48.86052487086005],[2.2868259251117706,48.86053810543294],[2.2867481410503387,48.8605866321702],[2.286796420812607,48.860620159706656],[2.2867441177368164,48.86067574478338],[2.286701202392578,48.860735741304865],[2.2866716980934143,48.860791326253235],[2.2866448760032654,48.86085838102949],[2.2866354882717133,48.86089720217464],[2.286631464958191,48.860927200311636],[2.2866274416446686,48.860975726671654],[2.286628782749176,48.86099778409249],[2.2866328060626984,48.8610498395671],[2.286640852689743,48.86109130744879],[2.2866542637348175,48.86113012841329],[2.2870512306690216,48.86113012841329]],
7
+ [[2.2906051576137543,48.86330670113662],[2.290634661912918,48.863297878576475],[2.2906923294067383,48.86327582216933],[2.2907473146915436,48.863251118981765],[2.290790230035782,48.86322994481131],[2.290847897529602,48.863269646373595],[2.290892153978348,48.86324141415477],[2.2909002006053925,48.863246707697016],[2.2910651564598083,48.86313995448688],[2.2910557687282562,48.86313377867435],[2.2910986840724945,48.86310642863836],[2.2911013662815094,48.86310466411942],[2.291058450937271,48.86307554954749],[2.2910678386688232,48.86306937372706],[2.2910302877426147,48.86304378817701],[2.290991395711899,48.863017320352895],[2.290916293859482,48.86296614918652],[2.2909069061279297,48.8629608556146],[2.290898859500885,48.86296614918652],[2.290853261947632,48.8629361522716],[2.2908076643943787,48.86296526692456],[2.2907379269599915,48.862916742493525],[2.290715128183365,48.8629308586965],[2.2907084226608276,48.86292468285819],[2.290666848421097,48.862956444304245],[2.290622591972351,48.86298644120703],[2.2906051576137543,48.8629961460835],[2.2906051576137543,48.86330670113662]]
8
+ ];
@@ -0,0 +1,139 @@
1
+ import { FC, memo, useEffect, useMemo } from "react";
2
+ import {
3
+ Mesh, MeshBuilder, StandardMaterial, Vector3, Color3,
4
+ HemisphericLight, VertexData, VertexBuffer,
5
+ } from '@babylonjs/core';
6
+ import { useBabylonMap, coordsToVector3 } from 'react-babylon-map';
7
+ import { StoryMap } from "./story-map";
8
+ import { Chaillot } from "./extrude/chaillot";
9
+
10
+ const origin = {
11
+ longitude: 2.289449241104535,
12
+ latitude: 48.861422672242895,
13
+ };
14
+
15
+ function Lights() {
16
+ const { scene } = useBabylonMap();
17
+ useEffect(() => {
18
+ if (!scene) return;
19
+ const light = new HemisphericLight("hemiLight", new Vector3(1, 4.5, 3), scene);
20
+ light.intensity = Math.PI;
21
+ light.groundColor = Color3.FromHexString("#60666C");
22
+ return () => { light.dispose(); };
23
+ }, [scene]);
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * Extrude a polygon footprint into a 3D building shape.
29
+ * Creates side walls + top cap using MeshBuilder.CreateRibbon.
30
+ */
31
+ const ExtrudeShape: FC<{
32
+ points: [number, number][];
33
+ originCoords: typeof origin;
34
+ height?: number;
35
+ color?: string;
36
+ }> = memo(({ points, originCoords, height = 30, color = '#e0e4cc' }) => {
37
+ const { scene } = useBabylonMap();
38
+
39
+ const positions3D = useMemo(
40
+ () => points.map(p => coordsToVector3({ longitude: p[0], latitude: p[1] }, originCoords)),
41
+ [points, originCoords]
42
+ );
43
+
44
+ useEffect(() => {
45
+ if (!scene || positions3D.length < 3) return;
46
+
47
+ const n = positions3D.length;
48
+ const walls: Vector3[][] = [];
49
+
50
+ // Build ribbon paths: bottom edge + top edge for each wall segment
51
+ const bottom: Vector3[] = [];
52
+ const top: Vector3[] = [];
53
+ for (let i = 0; i < n; i++) {
54
+ bottom.push(new Vector3(positions3D[i][0], 0, positions3D[i][2]));
55
+ top.push(new Vector3(positions3D[i][0], height, positions3D[i][2]));
56
+ }
57
+ // Close the loop
58
+ bottom.push(bottom[0].clone());
59
+ top.push(top[0].clone());
60
+
61
+ const ribbon = MeshBuilder.CreateRibbon('buildingWall', {
62
+ pathArray: [bottom, top],
63
+ closePath: true,
64
+ closeArray: false,
65
+ }, scene);
66
+
67
+ // Top cap (polygon triangulated as a fan)
68
+ const topCap: Vector3[] = [];
69
+ for (let i = 0; i < n; i++) {
70
+ topCap.push(new Vector3(positions3D[i][0], height, positions3D[i][2]));
71
+ }
72
+ const cap = MeshBuilder.CreateDisc('buildingCap', {
73
+ radius: 1,
74
+ tessellation: n,
75
+ }, scene);
76
+ // Replace disc geometry with our polygon
77
+ const capPositions: number[] = [];
78
+ const capIndices: number[] = [];
79
+ // Fan triangulation
80
+ for (let i = 0; i < n; i++) {
81
+ capPositions.push(positions3D[i][0], height, positions3D[i][2]);
82
+ }
83
+ // Center of polygon
84
+ let cx = 0, cz = 0;
85
+ for (const p of positions3D) { cx += p[0]; cz += p[2]; }
86
+ cx /= n; cz /= n;
87
+ capPositions.push(cx, height, cz);
88
+ const centerIdx = n;
89
+ for (let i = 0; i < n; i++) {
90
+ const next = (i + 1) % n;
91
+ capIndices.push(centerIdx, i, next);
92
+ }
93
+
94
+ const capVD = new VertexData();
95
+ capVD.positions = capPositions;
96
+ capVD.indices = capIndices;
97
+ // Compute normals
98
+ const normals = new Float32Array(capPositions.length);
99
+ VertexData.ComputeNormals(capPositions, capIndices, normals);
100
+ capVD.normals = normals;
101
+ capVD.applyToMesh(cap);
102
+ cap.geometry?.setVerticesBuffer(new VertexBuffer(cap.getEngine(), normals, 'normal', false));
103
+
104
+ const mat = new StandardMaterial('buildingMat', scene);
105
+ mat.diffuseColor = Color3.FromHexString(color);
106
+ mat.specularColor = new Color3(0.5, 0.5, 0.5);
107
+ mat.specularPower = 64;
108
+ ribbon.material = mat;
109
+ cap.material = mat;
110
+
111
+ return () => {
112
+ ribbon.dispose();
113
+ cap.dispose();
114
+ mat.dispose();
115
+ };
116
+ }, [scene, positions3D, height, color]);
117
+
118
+ return null;
119
+ });
120
+ ExtrudeShape.displayName = 'ExtrudeShape';
121
+
122
+ export default { title: 'Extrude Coordinates' };
123
+
124
+ export function ExtrudeCoordinates() {
125
+ return (
126
+ <StoryMap
127
+ zoom={16.5}
128
+ longitude={origin.longitude}
129
+ latitude={origin.latitude}
130
+ bearing={-48}
131
+ pitch={59}
132
+ >
133
+ <Lights />
134
+ {Chaillot.map((points, i) => (
135
+ <ExtrudeShape key={i} points={points} originCoords={origin} />
136
+ ))}
137
+ </StoryMap>
138
+ );
139
+ }
@@ -0,0 +1,49 @@
1
+ import { Coords } from "react-babylon-map";
2
+
3
+ export interface OverpassElement {
4
+ type: "node" | "way" | "relation";
5
+ id: number;
6
+ lat?: number;
7
+ lon?: number;
8
+ tags?: {
9
+ height?: string;
10
+ min_height?: string;
11
+ ['building:levels']?: string;
12
+ };
13
+ nodes?: number[];
14
+ geometry?: Array<{
15
+ lat: number;
16
+ lon: number;
17
+ }>;
18
+ }
19
+
20
+ interface OverpassApiResponse {
21
+ version: number;
22
+ generator: string;
23
+ elements: OverpassElement[];
24
+ }
25
+
26
+ export async function getBuildingsData({ start, end }: { start: Coords, end: Coords }) {
27
+ const overpassApiUrl = "https://overpass-api.de/api/interpreter";
28
+ const bbox = [start.latitude, start.longitude, end.latitude, end.longitude].join(',');
29
+ const query = `
30
+ [out:json];
31
+ (
32
+ way["building"](${bbox});
33
+ way["building:part"](${bbox});
34
+ relation["building"](${bbox});
35
+ relation["building:part"](${bbox});
36
+ );
37
+ out body;
38
+ out geom;
39
+ `;
40
+
41
+ const response: OverpassApiResponse = await (await fetch(overpassApiUrl, {
42
+ method: 'POST',
43
+ body: query
44
+ })).json();
45
+
46
+ const buildings = response.elements.filter(e => e.geometry);
47
+
48
+ return buildings;
49
+ }
@@ -0,0 +1,156 @@
1
+ import { FC, memo, useCallback, useEffect, useRef, useState } from "react";
2
+ import { createPortal } from 'react-dom';
3
+ import {
4
+ MeshBuilder, StandardMaterial, Vector3, Color3, Matrix,
5
+ HemisphericLight,
6
+ } from '@babylonjs/core';
7
+ import { useBabylonMap } from 'react-babylon-map';
8
+ import { StoryMap } from "./story-map";
9
+
10
+ function Lights() {
11
+ const { scene } = useBabylonMap();
12
+ useEffect(() => {
13
+ if (!scene) return;
14
+ const light = new HemisphericLight("hemiLight", new Vector3(0, 1, 0), scene);
15
+ light.intensity = 1;
16
+ return () => { light.dispose(); };
17
+ }, [scene]);
18
+ return null;
19
+ }
20
+
21
+ /**
22
+ * Projects a 3D world position to screen coordinates and renders
23
+ * HTML content at that position via a React portal.
24
+ * Equivalent to drei's <Html>.
25
+ */
26
+ const HtmlOverlay: FC<{
27
+ position: [number, number, number];
28
+ children: React.ReactNode;
29
+ style?: React.CSSProperties;
30
+ }> = memo(({ position, children, style }) => {
31
+ const { scene, engine } = useBabylonMap();
32
+ const divRef = useRef<HTMLDivElement>(null);
33
+ const [container, setContainer] = useState<HTMLElement | null>(null);
34
+
35
+ // Find the map container (parent of canvas) to portal into
36
+ useEffect(() => {
37
+ if (!engine) return;
38
+ const canvas = engine.getRenderingCanvas();
39
+ if (!canvas) return;
40
+ // Walk up to find the positioned container div
41
+ const parent = canvas.parentElement?.parentElement;
42
+ if (parent) setContainer(parent);
43
+ }, [engine]);
44
+
45
+ // Update position each frame
46
+ useEffect(() => {
47
+ if (!scene || !engine || !divRef.current) return;
48
+ const canvas = engine.getRenderingCanvas();
49
+ if (!canvas) return;
50
+
51
+ const observer = scene.onAfterRenderObservable.add(() => {
52
+ if (!divRef.current || !scene.activeCamera) return;
53
+ const cam = scene.activeCamera;
54
+ const w = canvas.clientWidth;
55
+ const h = canvas.clientHeight;
56
+
57
+ // The frozen projection matrix IS the full WVP (world * view * proj).
58
+ // Transform position through it to get NDC, then map to screen.
59
+ const wvp = cam.getProjectionMatrix();
60
+ const pos = new Vector3(position[0], position[1], position[2]);
61
+
62
+ // Manual: multiply by WVP, perspective divide, map to screen
63
+ const x = wvp.m[0] * pos.x + wvp.m[4] * pos.y + wvp.m[8] * pos.z + wvp.m[12];
64
+ const y = wvp.m[1] * pos.x + wvp.m[5] * pos.y + wvp.m[9] * pos.z + wvp.m[13];
65
+ const z = wvp.m[2] * pos.x + wvp.m[6] * pos.y + wvp.m[10] * pos.z + wvp.m[14];
66
+ const w4 = wvp.m[3] * pos.x + wvp.m[7] * pos.y + wvp.m[11] * pos.z + wvp.m[15];
67
+
68
+ if (w4 === 0) return;
69
+ const ndcX = x / w4;
70
+ const ndcY = y / w4;
71
+ const ndcZ = z / w4;
72
+
73
+ // NDC [-1,1] -> screen [0,w], [0,h]
74
+ const screenX = (ndcX + 1) / 2 * w;
75
+ const screenY = (1 - ndcY) / 2 * h; // flip Y
76
+
77
+ divRef.current.style.left = `${screenX}px`;
78
+ divRef.current.style.top = `${screenY}px`;
79
+ divRef.current.style.display = ndcZ > 1 || ndcZ < -1 ? 'none' : 'block';
80
+ });
81
+
82
+ return () => { scene.onAfterRenderObservable.remove(observer); };
83
+ }, [scene, engine, position, container]);
84
+
85
+ if (!container) return null;
86
+
87
+ return createPortal(
88
+ <div
89
+ ref={divRef}
90
+ style={{
91
+ position: 'absolute',
92
+ pointerEvents: 'none',
93
+ transform: 'translate(-50%, -100%)',
94
+ whiteSpace: 'nowrap',
95
+ zIndex: 10,
96
+ ...style,
97
+ }}
98
+ >
99
+ {children}
100
+ </div>,
101
+ container
102
+ );
103
+ });
104
+ HtmlOverlay.displayName = 'HtmlOverlay';
105
+
106
+ /** Interactive box that changes color on hover */
107
+ const MyBox: FC<{ position: [number, number, number]; rotation: number }> = memo(({ position, rotation }) => {
108
+ const { scene } = useBabylonMap();
109
+ const [hovered, setHovered] = useState(false);
110
+ const matRef = useRef<StandardMaterial | null>(null);
111
+
112
+ useEffect(() => {
113
+ if (!scene) return;
114
+ const box = MeshBuilder.CreateBox('box', { width: 500, height: 500, depth: 500 }, scene);
115
+ box.position.set(position[0], position[1], position[2]);
116
+ box.rotation.y = rotation;
117
+
118
+ const mat = new StandardMaterial('boxMat', scene);
119
+ mat.diffuseColor = Color3.FromHexString("#FFA500"); // orange
120
+ mat.emissiveColor = Color3.FromHexString("#FFA500").scale(0.2);
121
+ matRef.current = mat;
122
+ box.material = mat;
123
+
124
+ box.metadata = {
125
+ onPointerOver: () => setHovered(true),
126
+ onPointerOut: () => setHovered(false),
127
+ };
128
+
129
+ return () => { box.dispose(); mat.dispose(); };
130
+ }, [scene, position, rotation]);
131
+
132
+ // Update color on hover
133
+ useEffect(() => {
134
+ if (!matRef.current) return;
135
+ const hex = hovered ? '#800080' : '#FFA500'; // purple : orange
136
+ matRef.current.diffuseColor = Color3.FromHexString(hex);
137
+ matRef.current.emissiveColor = Color3.FromHexString(hex).scale(0.2);
138
+ }, [hovered]);
139
+
140
+ return null;
141
+ });
142
+ MyBox.displayName = 'MyBox';
143
+
144
+ export default { title: 'HTML on Top' };
145
+
146
+ export function Default() {
147
+ return (
148
+ <StoryMap latitude={51} longitude={0} zoom={13} pitch={60}>
149
+ <Lights />
150
+ <MyBox position={[0, 250, 0]} rotation={45 * Math.PI / 180} />
151
+ <HtmlOverlay position={[0, 500, 0]} style={{ textAlign: 'center', fontSize: '2em', width: '10em', lineHeight: '1.5em' }}>
152
+ <i>Some</i> <b>HTML</b><br />content!
153
+ </HtmlOverlay>
154
+ </StoryMap>
155
+ );
156
+ }
@@ -0,0 +1,97 @@
1
+ import { Mesh, Scene, VertexData } from "@babylonjs/core";
2
+ import type { MeshData } from "@ifc-lite/geometry";
3
+
4
+ /**
5
+ * Convert a single IFC MeshData to a Babylon.js Mesh.
6
+ * Swaps Y↔Z to convert IFC Z-up to Babylon Y-up (matching web-ifc-three behavior).
7
+ */
8
+ export function meshDataToBabylon(data: MeshData, scene: Scene): Mesh {
9
+ const positions = new Float32Array(data.positions.length);
10
+ for (let i = 0; i < positions.length; i += 3) {
11
+ positions[i] = data.positions[i];
12
+ positions[i + 1] = data.positions[i + 2]; // IFC Z → Babylon Y
13
+ positions[i + 2] = data.positions[i + 1]; // IFC Y → Babylon Z
14
+ }
15
+
16
+ const normals = new Float32Array(data.normals.length);
17
+ for (let i = 0; i < normals.length; i += 3) {
18
+ normals[i] = data.normals[i];
19
+ normals[i + 1] = data.normals[i + 2];
20
+ normals[i + 2] = data.normals[i + 1];
21
+ }
22
+
23
+ const mesh = new Mesh(`ifc-${data.expressId}`, scene);
24
+ const vd = new VertexData();
25
+ vd.positions = positions;
26
+ vd.normals = normals;
27
+ vd.indices = data.indices;
28
+ vd.applyToMesh(mesh);
29
+ return mesh;
30
+ }
31
+
32
+ /**
33
+ * Merge all IFC meshes into a single Babylon.js Mesh for one draw call.
34
+ */
35
+ export function batchWithVertexColors(allMeshes: MeshData[], scene: Scene): Mesh {
36
+ let totalVerts = 0;
37
+ let totalIdx = 0;
38
+ for (const m of allMeshes) {
39
+ totalVerts += m.positions.length / 3;
40
+ totalIdx += m.indices.length;
41
+ }
42
+
43
+ const positions = new Float32Array(totalVerts * 3);
44
+ const normals = new Float32Array(totalVerts * 3);
45
+ const indices = new Uint32Array(totalIdx);
46
+ const colors = new Float32Array(totalVerts * 4);
47
+
48
+ let vOff = 0;
49
+ let iOff = 0;
50
+ let cOff = 0;
51
+
52
+ for (const m of allMeshes) {
53
+ const vc = m.positions.length / 3;
54
+
55
+ positions.set(m.positions, vOff * 3);
56
+ normals.set(m.normals, vOff * 3);
57
+
58
+ for (let i = 0; i < vc; i++) {
59
+ colors[cOff++] = m.color[0];
60
+ colors[cOff++] = m.color[1];
61
+ colors[cOff++] = m.color[2];
62
+ colors[cOff++] = m.color[3];
63
+ }
64
+
65
+ for (let j = 0; j < m.indices.length; j++) {
66
+ indices[iOff++] = m.indices[j] + vOff;
67
+ }
68
+
69
+ vOff += vc;
70
+ }
71
+
72
+ const mesh = new Mesh("ifc-batch", scene);
73
+ const vd = new VertexData();
74
+ vd.positions = positions;
75
+ vd.normals = normals;
76
+ vd.indices = indices;
77
+ vd.colors = colors;
78
+ vd.applyToMesh(mesh);
79
+
80
+ return mesh;
81
+ }
82
+
83
+ /**
84
+ * Load an IFC file via @ifc-lite/geometry and return parsed MeshData array.
85
+ */
86
+ export async function loadIFC(url: string): Promise<MeshData[]> {
87
+ const response = await fetch(url);
88
+ const buffer = new Uint8Array(await response.arrayBuffer());
89
+
90
+ const { GeometryProcessor } = await import("@ifc-lite/geometry");
91
+ const processor = new GeometryProcessor();
92
+ await processor.init();
93
+ const result = await processor.process(buffer);
94
+ processor.dispose();
95
+
96
+ return result.meshes;
97
+ }