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.
- package/.claude/settings.local.json +78 -0
- package/demo.html +161 -0
- package/dist/cjs/main.js +520 -0
- package/dist/es/main.mjs +20 -0
- package/dist/es/main.mjs.map +1 -0
- package/dist/es/main10.mjs +33 -0
- package/dist/es/main10.mjs.map +1 -0
- package/dist/es/main11.mjs +12 -0
- package/dist/es/main11.mjs.map +1 -0
- package/dist/es/main12.mjs +14 -0
- package/dist/es/main12.mjs.map +1 -0
- package/dist/es/main13.mjs +12 -0
- package/dist/es/main13.mjs.map +1 -0
- package/dist/es/main14.mjs +5 -0
- package/dist/es/main14.mjs.map +1 -0
- package/dist/es/main15.mjs +12 -0
- package/dist/es/main15.mjs.map +1 -0
- package/dist/es/main16.mjs +25 -0
- package/dist/es/main16.mjs.map +1 -0
- package/dist/es/main17.mjs +54 -0
- package/dist/es/main17.mjs.map +1 -0
- package/dist/es/main18.mjs +88 -0
- package/dist/es/main18.mjs.map +1 -0
- package/dist/es/main19.mjs +18 -0
- package/dist/es/main19.mjs.map +1 -0
- package/dist/es/main2.mjs +9 -0
- package/dist/es/main2.mjs.map +1 -0
- package/dist/es/main20.mjs +21 -0
- package/dist/es/main20.mjs.map +1 -0
- package/dist/es/main21.mjs +61 -0
- package/dist/es/main21.mjs.map +1 -0
- package/dist/es/main3.mjs +46 -0
- package/dist/es/main3.mjs.map +1 -0
- package/dist/es/main4.mjs +23 -0
- package/dist/es/main4.mjs.map +1 -0
- package/dist/es/main5.mjs +69 -0
- package/dist/es/main5.mjs.map +1 -0
- package/dist/es/main6.mjs +35 -0
- package/dist/es/main6.mjs.map +1 -0
- package/dist/es/main7.mjs +65 -0
- package/dist/es/main7.mjs.map +1 -0
- package/dist/es/main8.mjs +14 -0
- package/dist/es/main8.mjs.map +1 -0
- package/dist/es/main9.mjs +26 -0
- package/dist/es/main9.mjs.map +1 -0
- package/dist/maplibre/cjs/main.js +520 -0
- package/dist/maplibre/es/main.mjs +20 -0
- package/dist/maplibre/es/main.mjs.map +1 -0
- package/dist/maplibre/es/main10.mjs +33 -0
- package/dist/maplibre/es/main10.mjs.map +1 -0
- package/dist/maplibre/es/main11.mjs +12 -0
- package/dist/maplibre/es/main11.mjs.map +1 -0
- package/dist/maplibre/es/main12.mjs +14 -0
- package/dist/maplibre/es/main12.mjs.map +1 -0
- package/dist/maplibre/es/main13.mjs +12 -0
- package/dist/maplibre/es/main13.mjs.map +1 -0
- package/dist/maplibre/es/main14.mjs +5 -0
- package/dist/maplibre/es/main14.mjs.map +1 -0
- package/dist/maplibre/es/main15.mjs +12 -0
- package/dist/maplibre/es/main15.mjs.map +1 -0
- package/dist/maplibre/es/main16.mjs +25 -0
- package/dist/maplibre/es/main16.mjs.map +1 -0
- package/dist/maplibre/es/main17.mjs +54 -0
- package/dist/maplibre/es/main17.mjs.map +1 -0
- package/dist/maplibre/es/main18.mjs +88 -0
- package/dist/maplibre/es/main18.mjs.map +1 -0
- package/dist/maplibre/es/main19.mjs +18 -0
- package/dist/maplibre/es/main19.mjs.map +1 -0
- package/dist/maplibre/es/main2.mjs +9 -0
- package/dist/maplibre/es/main2.mjs.map +1 -0
- package/dist/maplibre/es/main20.mjs +61 -0
- package/dist/maplibre/es/main20.mjs.map +1 -0
- package/dist/maplibre/es/main21.mjs +21 -0
- package/dist/maplibre/es/main21.mjs.map +1 -0
- package/dist/maplibre/es/main3.mjs +46 -0
- package/dist/maplibre/es/main3.mjs.map +1 -0
- package/dist/maplibre/es/main4.mjs +23 -0
- package/dist/maplibre/es/main4.mjs.map +1 -0
- package/dist/maplibre/es/main5.mjs +69 -0
- package/dist/maplibre/es/main5.mjs.map +1 -0
- package/dist/maplibre/es/main6.mjs +35 -0
- package/dist/maplibre/es/main6.mjs.map +1 -0
- package/dist/maplibre/es/main7.mjs +65 -0
- package/dist/maplibre/es/main7.mjs.map +1 -0
- package/dist/maplibre/es/main8.mjs +14 -0
- package/dist/maplibre/es/main8.mjs.map +1 -0
- package/dist/maplibre/es/main9.mjs +26 -0
- package/dist/maplibre/es/main9.mjs.map +1 -0
- package/dist/maplibre/types/api/canvas-props.d.ts +9 -0
- package/dist/maplibre/types/api/coordinates.d.ts +13 -0
- package/dist/maplibre/types/api/coords-to-vector-3.d.ts +3 -0
- package/dist/maplibre/types/api/coords.d.ts +5 -0
- package/dist/maplibre/types/api/index.d.ts +7 -0
- package/dist/maplibre/types/api/near-coordinates.d.ts +13 -0
- package/dist/maplibre/types/api/use-map.d.ts +3 -0
- package/dist/maplibre/types/api/vector-3-to-coords.d.ts +2 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-render.d.ts +15 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-root.d.ts +11 -0
- package/dist/maplibre/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
- package/dist/maplibre/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
- package/dist/maplibre/types/core/canvas-overlay/render.d.ts +1 -0
- package/dist/maplibre/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
- package/dist/maplibre/types/core/coords-to-matrix.d.ts +9 -0
- package/dist/maplibre/types/core/earth-radius.d.ts +1 -0
- package/dist/maplibre/types/core/generic-map.d.ts +49 -0
- package/dist/maplibre/types/core/matrix-utils.d.ts +7 -0
- package/dist/maplibre/types/core/sync-camera.d.ts +7 -0
- package/dist/maplibre/types/core/use-babylon-map.d.ts +32 -0
- package/dist/maplibre/types/core/use-coords-to-matrix.d.ts +6 -0
- package/dist/maplibre/types/core/use-coords.d.ts +5 -0
- package/dist/maplibre/types/core/use-function.d.ts +1 -0
- package/dist/maplibre/types/maplibre/canvas.d.ts +4 -0
- package/dist/maplibre/types/maplibre.index.d.ts +4 -0
- package/dist/types/api/canvas-props.d.ts +9 -0
- package/dist/types/api/coordinates.d.ts +13 -0
- package/dist/types/api/coords-to-vector-3.d.ts +3 -0
- package/dist/types/api/coords.d.ts +5 -0
- package/dist/types/api/index.d.ts +7 -0
- package/dist/types/api/near-coordinates.d.ts +13 -0
- package/dist/types/api/use-map.d.ts +3 -0
- package/dist/types/api/vector-3-to-coords.d.ts +2 -0
- package/dist/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
- package/dist/types/core/canvas-in-layer/use-render.d.ts +15 -0
- package/dist/types/core/canvas-in-layer/use-root.d.ts +11 -0
- package/dist/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
- package/dist/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
- package/dist/types/core/canvas-overlay/render.d.ts +1 -0
- package/dist/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
- package/dist/types/core/coords-to-matrix.d.ts +9 -0
- package/dist/types/core/earth-radius.d.ts +1 -0
- package/dist/types/core/generic-map.d.ts +49 -0
- package/dist/types/core/matrix-utils.d.ts +7 -0
- package/dist/types/core/sync-camera.d.ts +7 -0
- package/dist/types/core/use-babylon-map.d.ts +32 -0
- package/dist/types/core/use-coords-to-matrix.d.ts +6 -0
- package/dist/types/core/use-coords.d.ts +5 -0
- package/dist/types/core/use-function.d.ts +1 -0
- package/dist/types/mapbox/canvas.d.ts +4 -0
- package/dist/types/mapbox.index.d.ts +4 -0
- package/package.json +58 -0
- package/plan.md +719 -0
- package/src/api/canvas-props.ts +10 -0
- package/src/api/coordinates.tsx +83 -0
- package/src/api/coords-to-vector-3.ts +39 -0
- package/src/api/coords.tsx +6 -0
- package/src/api/index.ts +7 -0
- package/src/api/near-coordinates.tsx +87 -0
- package/src/api/use-map.ts +8 -0
- package/src/api/vector-3-to-coords.ts +13 -0
- package/src/core/canvas-in-layer/use-canvas-in-layer.tsx +27 -0
- package/src/core/canvas-in-layer/use-render.ts +43 -0
- package/src/core/canvas-in-layer/use-root.tsx +82 -0
- package/src/core/canvas-overlay/canvas-portal.tsx +98 -0
- package/src/core/canvas-overlay/init-canvas-fc.tsx +45 -0
- package/src/core/canvas-overlay/render.tsx +1 -0
- package/src/core/canvas-overlay/sync-camera-fc.tsx +83 -0
- package/src/core/coords-to-matrix.ts +21 -0
- package/src/core/earth-radius.ts +1 -0
- package/src/core/events.ts +55 -0
- package/src/core/generic-map.ts +59 -0
- package/src/core/map-engine.tsx +70 -0
- package/src/core/matrix-utils.ts +22 -0
- package/src/core/sync-camera.ts +29 -0
- package/src/core/use-babylon-map.ts +46 -0
- package/src/core/use-coords-to-matrix.ts +13 -0
- package/src/core/use-coords.tsx +22 -0
- package/src/core/use-function.ts +10 -0
- package/src/mapbox/canvas.tsx +59 -0
- package/src/mapbox.index.ts +7 -0
- package/src/maplibre/canvas.tsx +59 -0
- package/src/maplibre.index.ts +7 -0
- package/src/vite-env.d.ts +1 -0
- package/stories/.ladle/components.tsx +50 -0
- package/stories/.ladle/style.css +63 -0
- package/stories/package.json +31 -0
- package/stories/pnpm-lock.yaml +5450 -0
- package/stories/sandbox.config.json +3 -0
- package/stories/src/adaptive-dpr.tsx +34 -0
- package/stories/src/billboard.stories.tsx +111 -0
- package/stories/src/buildings-3d.stories.tsx +280 -0
- package/stories/src/canvas/mapbox.stories.tsx +113 -0
- package/stories/src/canvas/maplibre.stories.tsx +93 -0
- package/stories/src/comparison.stories.tsx +161 -0
- package/stories/src/extrude/chaillot.ts +8 -0
- package/stories/src/exude-coordinates.stories.tsx +139 -0
- package/stories/src/free-3d-buildings/get-buildings-data.ts +49 -0
- package/stories/src/html-on-top.stories.tsx +156 -0
- package/stories/src/ifc/ifc-to-babylon.ts +97 -0
- package/stories/src/ifc/ifc.main.ts +904 -0
- package/stories/src/ifc/ifc2bb.ts +343 -0
- package/stories/src/ifc/model.ifc +14155 -0
- package/stories/src/ifc.stories.tsx +276 -0
- package/stories/src/mapbox/story-mapbox.tsx +97 -0
- package/stories/src/maplibre/story-maplibre.tsx +36 -0
- package/stories/src/multi-coordinates.stories.tsx +115 -0
- package/stories/src/pivot-controls.stories.tsx +148 -0
- package/stories/src/postprocessing.stories.tsx +125 -0
- package/stories/src/render-on-demand.stories.tsx +76 -0
- package/stories/src/story-map.tsx +44 -0
- package/stories/src/sunlight.stories.tsx +215 -0
- package/stories/src/vite-env.d.ts +1 -0
- package/stories/tsconfig.json +32 -0
- package/stories/tsconfig.node.json +10 -0
- package/stories/vite.config.ts +27 -0
- package/tsconfig.json +31 -0
- package/tsconfig.mapbox.json +7 -0
- package/tsconfig.maplibre.json +7 -0
- package/tsconfig.node.json +10 -0
- package/tsconfig.types.json +25 -0
- 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
|
+
}
|