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,10 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { Coords } from './coords';
|
|
3
|
+
|
|
4
|
+
export interface CanvasProps extends Coords, PropsWithChildren {
|
|
5
|
+
id?: string;
|
|
6
|
+
beforeId?: string;
|
|
7
|
+
frameloop?: 'always' | 'demand';
|
|
8
|
+
/** render on a separated `<canvas>` that sits on top of the map provider */
|
|
9
|
+
overlay?: boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Color4, FreeCamera, HemisphericLight, Scene, Vector3 } from '@babylonjs/core';
|
|
2
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { syncCamera } from '../core/sync-camera';
|
|
4
|
+
import { useCoordsToMatrix } from '../core/use-coords-to-matrix';
|
|
5
|
+
import { useBabylonMap, BabylonMapContext } from '../core/use-babylon-map';
|
|
6
|
+
import type { BabylonMap } from '../core/use-babylon-map';
|
|
7
|
+
|
|
8
|
+
export interface CoordinatesProps {
|
|
9
|
+
longitude: number;
|
|
10
|
+
latitude: number;
|
|
11
|
+
altitude?: number;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Place 3D content at specific geographic coordinates inside a `<Canvas>`.
|
|
17
|
+
* Creates a sub-scene with its own camera synced to the given geo position,
|
|
18
|
+
* mirroring react-three-map's createPortal approach.
|
|
19
|
+
*/
|
|
20
|
+
export const Coordinates = memo<CoordinatesProps>(({
|
|
21
|
+
latitude, longitude, altitude = 0, children,
|
|
22
|
+
}) => {
|
|
23
|
+
const babylonMap = useBabylonMap();
|
|
24
|
+
const origin = useCoordsToMatrix({
|
|
25
|
+
latitude, longitude, altitude, fromLngLat: babylonMap.fromLngLat,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Create a sub-scene for this coordinate portal
|
|
29
|
+
const [subScene] = useState(() => {
|
|
30
|
+
if (!babylonMap.engine) return null;
|
|
31
|
+
const sub = new Scene(babylonMap.engine);
|
|
32
|
+
sub.autoClear = false;
|
|
33
|
+
sub.clearColor = new Color4(0, 0, 0, 0);
|
|
34
|
+
return sub;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const cameraRef = useRef<FreeCamera | null>(null);
|
|
38
|
+
|
|
39
|
+
// Create camera + default light in sub-scene
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!subScene) return;
|
|
42
|
+
const cam = new FreeCamera('coordsCam', Vector3.Zero(), subScene);
|
|
43
|
+
cam.inputs.clear();
|
|
44
|
+
cam.minZ = 0;
|
|
45
|
+
cameraRef.current = cam;
|
|
46
|
+
|
|
47
|
+
// Add a default light so StandardMaterial isn't black
|
|
48
|
+
const light = new HemisphericLight('coordsLight', new Vector3(0, 1, 0), subScene);
|
|
49
|
+
light.intensity = 1;
|
|
50
|
+
|
|
51
|
+
return () => { cam.dispose(); light.dispose(); };
|
|
52
|
+
}, [subScene]);
|
|
53
|
+
|
|
54
|
+
// Render the sub-scene each frame, synced to its geographic position
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!subScene || !babylonMap.scene) return;
|
|
57
|
+
const mainScene = babylonMap.scene;
|
|
58
|
+
|
|
59
|
+
const observer = mainScene.onBeforeRenderObservable.add(() => {
|
|
60
|
+
if (!cameraRef.current) return;
|
|
61
|
+
syncCamera(cameraRef.current, origin, babylonMap.viewProjMx);
|
|
62
|
+
babylonMap.engine?.wipeCaches(true);
|
|
63
|
+
subScene.render(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
mainScene.onBeforeRenderObservable.remove(observer);
|
|
68
|
+
};
|
|
69
|
+
}, [subScene, babylonMap.scene, origin, babylonMap]);
|
|
70
|
+
|
|
71
|
+
// Provide child context pointing at the sub-scene
|
|
72
|
+
const childMap: BabylonMap = {
|
|
73
|
+
...babylonMap,
|
|
74
|
+
scene: subScene || undefined,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<BabylonMapContext.Provider value={childMap}>
|
|
79
|
+
{children}
|
|
80
|
+
</BabylonMapContext.Provider>
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
Coordinates.displayName = 'Coordinates';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { earthRadius } from '../core/earth-radius';
|
|
2
|
+
import { Coords } from './coords';
|
|
3
|
+
|
|
4
|
+
const DEG2RAD = Math.PI / 180;
|
|
5
|
+
|
|
6
|
+
const mercatorScaleLookup: { [key: number]: number } = {};
|
|
7
|
+
|
|
8
|
+
function getMercatorScale(lat: number): number {
|
|
9
|
+
const index = Math.round(lat * 1000);
|
|
10
|
+
if (mercatorScaleLookup[index] === undefined) {
|
|
11
|
+
mercatorScaleLookup[index] = 1 / Math.cos(lat * DEG2RAD);
|
|
12
|
+
}
|
|
13
|
+
return mercatorScaleLookup[index];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function averageMercatorScale(originLat: number, pointLat: number, steps = 10): number {
|
|
17
|
+
let totalScale = 0;
|
|
18
|
+
const latStep = (pointLat - originLat) / steps;
|
|
19
|
+
for (let i = 0; i <= steps; i++) {
|
|
20
|
+
const lat = originLat + latStep * i;
|
|
21
|
+
totalScale += getMercatorScale(lat);
|
|
22
|
+
}
|
|
23
|
+
return totalScale / (steps + 1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function coordsToVector3(point: Coords, origin: Coords): [number, number, number] {
|
|
27
|
+
const latitudeDiff = (point.latitude - origin.latitude) * DEG2RAD;
|
|
28
|
+
const longitudeDiff = (point.longitude - origin.longitude) * DEG2RAD;
|
|
29
|
+
const altitudeDiff = (point.altitude || 0) - (origin.altitude || 0);
|
|
30
|
+
|
|
31
|
+
const x = longitudeDiff * earthRadius * Math.cos(origin.latitude * DEG2RAD);
|
|
32
|
+
const y = altitudeDiff;
|
|
33
|
+
|
|
34
|
+
const steps = Math.ceil(Math.abs(point.latitude - origin.latitude)) * 100 + 1;
|
|
35
|
+
const avgScale = averageMercatorScale(origin.latitude, point.latitude, steps);
|
|
36
|
+
|
|
37
|
+
const z = ((-latitudeDiff * earthRadius) / getMercatorScale(origin.latitude)) * avgScale;
|
|
38
|
+
return [x, y, z];
|
|
39
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './canvas-props';
|
|
2
|
+
export * from './coordinates';
|
|
3
|
+
export * from './coords';
|
|
4
|
+
export * from './coords-to-vector-3';
|
|
5
|
+
export * from './near-coordinates';
|
|
6
|
+
export * from './vector-3-to-coords';
|
|
7
|
+
export { useBabylonMap, BabylonMapContext } from '../core/use-babylon-map';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Color4, FreeCamera, HemisphericLight, Scene, Vector3 } from '@babylonjs/core';
|
|
2
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useBabylonMap, BabylonMapContext } from '../core/use-babylon-map';
|
|
4
|
+
import type { BabylonMap } from '../core/use-babylon-map';
|
|
5
|
+
import { useCoords } from '../core/use-coords';
|
|
6
|
+
import { coordsToVector3 } from './coords-to-vector-3';
|
|
7
|
+
|
|
8
|
+
export interface NearCoordinatesProps {
|
|
9
|
+
longitude: number;
|
|
10
|
+
latitude: number;
|
|
11
|
+
altitude?: number;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Place 3D content at a nearby coordinate (relative to the Canvas origin).
|
|
17
|
+
* Uses the sub-scene approach like Coordinates but computes a relative offset
|
|
18
|
+
* for the camera's world matrix.
|
|
19
|
+
*/
|
|
20
|
+
export const NearCoordinates = memo<NearCoordinatesProps>(({ children, ...coords }) => {
|
|
21
|
+
const { latitude, longitude, altitude } = useCoords();
|
|
22
|
+
const babylonMap = useBabylonMap();
|
|
23
|
+
|
|
24
|
+
const pos = coordsToVector3(coords, { latitude, longitude, altitude });
|
|
25
|
+
|
|
26
|
+
// Create a sub-scene for this coordinate portal
|
|
27
|
+
const [subScene] = useState(() => {
|
|
28
|
+
if (!babylonMap.engine) return null;
|
|
29
|
+
const sub = new Scene(babylonMap.engine);
|
|
30
|
+
sub.autoClear = false;
|
|
31
|
+
sub.clearColor = new Color4(0, 0, 0, 0);
|
|
32
|
+
return sub;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const cameraRef = useRef<FreeCamera | null>(null);
|
|
36
|
+
|
|
37
|
+
// Create camera + default light in sub-scene
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!subScene) return;
|
|
40
|
+
const cam = new FreeCamera('nearCoordsCam', new Vector3(pos[0], pos[1], pos[2]), subScene);
|
|
41
|
+
cam.inputs.clear();
|
|
42
|
+
cam.minZ = 0;
|
|
43
|
+
cameraRef.current = cam;
|
|
44
|
+
|
|
45
|
+
const light = new HemisphericLight('nearCoordsLight', new Vector3(0, 1, 0), subScene);
|
|
46
|
+
light.intensity = 1;
|
|
47
|
+
|
|
48
|
+
return () => { cam.dispose(); light.dispose(); };
|
|
49
|
+
}, [subScene]);
|
|
50
|
+
|
|
51
|
+
// Update camera position when offset changes
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!cameraRef.current) return;
|
|
54
|
+
cameraRef.current.position.set(pos[0], pos[1], pos[2]);
|
|
55
|
+
}, [pos]);
|
|
56
|
+
|
|
57
|
+
// Render the sub-scene each frame
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!subScene || !babylonMap.scene) return;
|
|
60
|
+
const mainScene = babylonMap.scene;
|
|
61
|
+
|
|
62
|
+
const observer = mainScene.onBeforeRenderObservable.add(() => {
|
|
63
|
+
if (!cameraRef.current) return;
|
|
64
|
+
// Use the same projection as the main scene but offset by local position
|
|
65
|
+
cameraRef.current.freezeProjectionMatrix(babylonMap.scene!.activeCamera!.getProjectionMatrix());
|
|
66
|
+
babylonMap.engine?.wipeCaches(true);
|
|
67
|
+
subScene.render(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
mainScene.onBeforeRenderObservable.remove(observer);
|
|
72
|
+
};
|
|
73
|
+
}, [subScene, babylonMap.scene, babylonMap]);
|
|
74
|
+
|
|
75
|
+
// Provide child context pointing at the sub-scene
|
|
76
|
+
const childMap: BabylonMap = {
|
|
77
|
+
...babylonMap,
|
|
78
|
+
scene: subScene || undefined,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<BabylonMapContext.Provider value={childMap}>
|
|
83
|
+
{children}
|
|
84
|
+
</BabylonMapContext.Provider>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
NearCoordinates.displayName = 'NearCoordinates';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MapInstance } from '../core/generic-map';
|
|
2
|
+
import { useBabylonMap } from '../core/use-babylon-map';
|
|
3
|
+
|
|
4
|
+
/** Access the underlying map instance from inside a `<Canvas>`. */
|
|
5
|
+
export const useMap = <T extends MapInstance = MapInstance>(): T => {
|
|
6
|
+
const ctx = useBabylonMap();
|
|
7
|
+
return ctx.map as T;
|
|
8
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { earthRadius } from '../core/earth-radius';
|
|
2
|
+
import { Coords } from './coords';
|
|
3
|
+
|
|
4
|
+
const DEG2RAD = Math.PI / 180;
|
|
5
|
+
const RAD2DEG = 180 / Math.PI;
|
|
6
|
+
|
|
7
|
+
export function vector3ToCoords(position: [number, number, number], origin: Coords): Coords {
|
|
8
|
+
const [x, y, z] = position;
|
|
9
|
+
const latitude = origin.latitude + (-z / earthRadius) * RAD2DEG;
|
|
10
|
+
const longitude = origin.longitude + (x / earthRadius) * RAD2DEG / Math.cos(origin.latitude * DEG2RAD);
|
|
11
|
+
const altitude = (origin.altitude || 0) + y;
|
|
12
|
+
return { latitude, longitude, altitude };
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CanvasProps } from '../../api/canvas-props';
|
|
2
|
+
import { FromLngLat, MapInstance } from '../generic-map';
|
|
3
|
+
import { useCoordsToMatrix } from '../use-coords-to-matrix';
|
|
4
|
+
import { useRender } from './use-render';
|
|
5
|
+
import { useRoot } from './use-root';
|
|
6
|
+
|
|
7
|
+
/** Get all the properties needed to render as a map `<Layer>` */
|
|
8
|
+
export function useCanvasInLayer(props: CanvasProps, fromLngLat: FromLngLat, map: MapInstance) {
|
|
9
|
+
const { latitude, longitude, altitude, frameloop } = props;
|
|
10
|
+
|
|
11
|
+
const origin = useCoordsToMatrix({
|
|
12
|
+
latitude, longitude, altitude, fromLngLat,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const { onRemove, scene, camera, babylonMap } = useRoot(fromLngLat, map, props);
|
|
16
|
+
|
|
17
|
+
const render = useRender({ origin, frameloop, scene, camera, map, babylonMap });
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
id: props.id,
|
|
21
|
+
beforeId: props.beforeId,
|
|
22
|
+
onRemove,
|
|
23
|
+
render,
|
|
24
|
+
type: 'custom' as const,
|
|
25
|
+
renderingMode: '3d' as const,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FreeCamera, Matrix, Scene } from '@babylonjs/core';
|
|
2
|
+
import { MapInstance } from '../generic-map';
|
|
3
|
+
import { syncCamera } from '../sync-camera';
|
|
4
|
+
import { useFunction } from '../use-function';
|
|
5
|
+
import { BabylonMap } from '../use-babylon-map';
|
|
6
|
+
|
|
7
|
+
export function useRender({
|
|
8
|
+
map, origin, scene, camera, frameloop, babylonMap,
|
|
9
|
+
}: {
|
|
10
|
+
map: MapInstance;
|
|
11
|
+
origin: Matrix;
|
|
12
|
+
scene: Scene;
|
|
13
|
+
camera: FreeCamera;
|
|
14
|
+
frameloop?: 'always' | 'demand';
|
|
15
|
+
babylonMap: BabylonMap;
|
|
16
|
+
}) {
|
|
17
|
+
const render = useFunction((
|
|
18
|
+
_gl: WebGL2RenderingContext,
|
|
19
|
+
projViewMx: number[] | { defaultProjectionData: { mainMatrix: Record<string, number> } },
|
|
20
|
+
) => {
|
|
21
|
+
const pVMx = 'defaultProjectionData' in projViewMx
|
|
22
|
+
? Object.values(projViewMx.defaultProjectionData.mainMatrix)
|
|
23
|
+
: projViewMx;
|
|
24
|
+
|
|
25
|
+
// Update shared context
|
|
26
|
+
babylonMap.viewProjMx = pVMx as number[];
|
|
27
|
+
|
|
28
|
+
// Sync camera
|
|
29
|
+
syncCamera(camera, origin, pVMx as number[]);
|
|
30
|
+
|
|
31
|
+
// Wipe Babylon's internal GL state caches so the next render
|
|
32
|
+
// re-applies everything from scratch. This is critical when
|
|
33
|
+
// sharing a GL context with MapLibre.
|
|
34
|
+
scene.getEngine().wipeCaches(true);
|
|
35
|
+
|
|
36
|
+
// Render the Babylon scene into the shared GL context.
|
|
37
|
+
scene.render(false);
|
|
38
|
+
|
|
39
|
+
if (!frameloop || frameloop === 'always') map.triggerRepaint();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return render;
|
|
43
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Color4, Engine, FreeCamera, Scene, Vector3 } from '@babylonjs/core';
|
|
2
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { CanvasProps } from '../../api/canvas-props';
|
|
5
|
+
import { BabylonMap, BabylonMapContext, createBabylonMap } from '../use-babylon-map';
|
|
6
|
+
import { FromLngLat, MapInstance } from '../generic-map';
|
|
7
|
+
import { useFunction } from '../use-function';
|
|
8
|
+
|
|
9
|
+
interface RootState {
|
|
10
|
+
engine: Engine;
|
|
11
|
+
scene: Scene;
|
|
12
|
+
camera: FreeCamera;
|
|
13
|
+
reactRoot: Root;
|
|
14
|
+
container: HTMLDivElement;
|
|
15
|
+
babylonMap: BabylonMap;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useRoot(
|
|
19
|
+
fromLngLat: FromLngLat,
|
|
20
|
+
map: MapInstance,
|
|
21
|
+
{ longitude, latitude, altitude, frameloop, ...props }: CanvasProps,
|
|
22
|
+
) {
|
|
23
|
+
const [{ engine, scene, camera, reactRoot, babylonMap }] = useState<RootState>(() => {
|
|
24
|
+
const canvas = map.getCanvas();
|
|
25
|
+
const gl = (canvas.getContext('webgl2') || canvas.getContext('webgl')) as WebGLRenderingContext;
|
|
26
|
+
|
|
27
|
+
// Create Babylon Engine from MapLibre's GL context (same pattern as demo.html)
|
|
28
|
+
const engine = new Engine(
|
|
29
|
+
gl as WebGL2RenderingContext,
|
|
30
|
+
true,
|
|
31
|
+
{ useHighPrecisionMatrix: true },
|
|
32
|
+
true,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const scene = new Scene(engine);
|
|
36
|
+
scene.autoClear = false;
|
|
37
|
+
scene.clearColor = new Color4(0, 0, 0, 0);
|
|
38
|
+
scene.detachControl(); // let MapLibre handle pointer events
|
|
39
|
+
|
|
40
|
+
const camera = new FreeCamera('cam', Vector3.Zero(), scene);
|
|
41
|
+
camera.inputs.clear();
|
|
42
|
+
camera.minZ = 0;
|
|
43
|
+
|
|
44
|
+
const babylonMap = createBabylonMap({ map, fromLngLat, engine, scene });
|
|
45
|
+
|
|
46
|
+
// Separate React root for children
|
|
47
|
+
const container = document.createElement('div');
|
|
48
|
+
const reactRoot = createRoot(container);
|
|
49
|
+
|
|
50
|
+
return { engine, scene, camera, reactRoot, container, babylonMap };
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const onResize = useFunction(() => {
|
|
54
|
+
engine.resize();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const onRemove = useFunction(() => {
|
|
58
|
+
reactRoot.unmount();
|
|
59
|
+
scene.dispose();
|
|
60
|
+
engine.dispose();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Update coords on babylonMap
|
|
64
|
+
babylonMap.coords = { longitude, latitude, altitude };
|
|
65
|
+
|
|
66
|
+
// Resize listener
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
map.on('resize', onResize);
|
|
69
|
+
return () => { map.off('resize', onResize); };
|
|
70
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
|
+
|
|
72
|
+
// Render children into the separate React root
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
reactRoot.render(
|
|
75
|
+
<BabylonMapContext.Provider value={babylonMap}>
|
|
76
|
+
{props.children}
|
|
77
|
+
</BabylonMapContext.Provider>,
|
|
78
|
+
);
|
|
79
|
+
}, [props.children]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
80
|
+
|
|
81
|
+
return { onRemove, engine, scene, camera, babylonMap };
|
|
82
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Color4, Engine, FreeCamera, Scene, Vector3 } from '@babylonjs/core';
|
|
2
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { CanvasProps } from '../../api/canvas-props';
|
|
4
|
+
import { BabylonMap, BabylonMapContext, createBabylonMap } from '../use-babylon-map';
|
|
5
|
+
import { FromLngLat, MapInstance } from '../generic-map';
|
|
6
|
+
import { useFunction } from '../use-function';
|
|
7
|
+
import { SyncCameraFC } from './sync-camera-fc';
|
|
8
|
+
|
|
9
|
+
interface CanvasPortalProps extends CanvasProps {
|
|
10
|
+
setOnRender: (callback: () => (mx: number[]) => void) => void;
|
|
11
|
+
map: MapInstance;
|
|
12
|
+
fromLngLat: FromLngLat;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const canvasStyle: React.CSSProperties = {
|
|
16
|
+
position: 'absolute',
|
|
17
|
+
top: 0,
|
|
18
|
+
left: 0,
|
|
19
|
+
width: '100%',
|
|
20
|
+
height: '100%',
|
|
21
|
+
pointerEvents: 'none',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const CanvasPortal = memo<CanvasPortalProps>(({
|
|
25
|
+
children, latitude, longitude, altitude,
|
|
26
|
+
setOnRender, map, fromLngLat,
|
|
27
|
+
}) => {
|
|
28
|
+
|
|
29
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
30
|
+
const mapCanvas = map.getCanvas();
|
|
31
|
+
|
|
32
|
+
const [babylonMap, setBabylonMap] = useState<BabylonMap | null>(null);
|
|
33
|
+
const [ready, setReady] = useState(false);
|
|
34
|
+
|
|
35
|
+
// Create Engine + Scene on mount
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const canvas = canvasRef.current!;
|
|
38
|
+
const engine = new Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true, useHighPrecisionMatrix: true });
|
|
39
|
+
const scene = new Scene(engine);
|
|
40
|
+
scene.clearColor = new Color4(0, 0, 0, 0);
|
|
41
|
+
|
|
42
|
+
const camera = new FreeCamera('cam', Vector3.Zero(), scene);
|
|
43
|
+
camera.inputs.clear();
|
|
44
|
+
camera.minZ = 0;
|
|
45
|
+
|
|
46
|
+
setBabylonMap(createBabylonMap({ map, fromLngLat, engine, scene }));
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
scene.dispose();
|
|
50
|
+
engine.dispose();
|
|
51
|
+
};
|
|
52
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
53
|
+
|
|
54
|
+
// Resize handling
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!babylonMap?.engine) return;
|
|
57
|
+
const engine = babylonMap.engine;
|
|
58
|
+
|
|
59
|
+
const onResize = () => {
|
|
60
|
+
const c = canvasRef.current;
|
|
61
|
+
if (!c) return;
|
|
62
|
+
c.width = mapCanvas.clientWidth * window.devicePixelRatio;
|
|
63
|
+
c.height = mapCanvas.clientHeight * window.devicePixelRatio;
|
|
64
|
+
c.style.width = `${mapCanvas.clientWidth}px`;
|
|
65
|
+
c.style.height = `${mapCanvas.clientHeight}px`;
|
|
66
|
+
engine.resize();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
onResize();
|
|
70
|
+
map.on('resize', onResize);
|
|
71
|
+
return () => { map.off('resize', onResize); };
|
|
72
|
+
}, [babylonMap, map, mapCanvas]);
|
|
73
|
+
|
|
74
|
+
const onReady = useFunction(() => {
|
|
75
|
+
setReady(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<canvas ref={canvasRef} style={canvasStyle} />
|
|
81
|
+
{babylonMap && (
|
|
82
|
+
<BabylonMapContext.Provider value={babylonMap}>
|
|
83
|
+
<SyncCameraFC
|
|
84
|
+
latitude={latitude}
|
|
85
|
+
longitude={longitude}
|
|
86
|
+
altitude={altitude}
|
|
87
|
+
setOnRender={setOnRender}
|
|
88
|
+
onReady={onReady}
|
|
89
|
+
map={map}
|
|
90
|
+
canvasRef={canvasRef}
|
|
91
|
+
/>
|
|
92
|
+
{ready && children}
|
|
93
|
+
</BabylonMapContext.Provider>
|
|
94
|
+
)}
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
CanvasPortal.displayName = 'CanvasPortal';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { memo, useEffect, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { CanvasProps } from '../../api/canvas-props';
|
|
4
|
+
import { FromLngLat, MapInstance } from '../generic-map';
|
|
5
|
+
import { CanvasPortal } from './canvas-portal';
|
|
6
|
+
|
|
7
|
+
interface InitCanvasFCProps extends CanvasProps {
|
|
8
|
+
map: MapInstance;
|
|
9
|
+
setOnRender: (callback: () => (mx: number[]) => void) => void;
|
|
10
|
+
frameloop?: 'always' | 'demand';
|
|
11
|
+
fromLngLat: FromLngLat;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const InitCanvasFC = memo<InitCanvasFCProps>((props) => {
|
|
15
|
+
const canvas = props.map.getCanvas();
|
|
16
|
+
|
|
17
|
+
const [el] = useState(() => {
|
|
18
|
+
const el = document.createElement('div');
|
|
19
|
+
el.style.position = 'absolute';
|
|
20
|
+
el.style.top = '0';
|
|
21
|
+
el.style.bottom = '0';
|
|
22
|
+
el.style.left = '0';
|
|
23
|
+
el.style.right = '0';
|
|
24
|
+
el.style.pointerEvents = 'none';
|
|
25
|
+
return el;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const parent = canvas.parentElement!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
30
|
+
parent.appendChild(el);
|
|
31
|
+
return () => {
|
|
32
|
+
parent.removeChild(el);
|
|
33
|
+
};
|
|
34
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
{createPortal(
|
|
39
|
+
<CanvasPortal {...props} />,
|
|
40
|
+
el,
|
|
41
|
+
)}
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
InitCanvasFC.displayName = 'InitCanvasFC';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Render = (gl: WebGLRenderingContext, matrix: number[]) => void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { FreeCamera } from '@babylonjs/core';
|
|
2
|
+
import { memo, RefObject, useEffect, useRef, useMemo } from 'react';
|
|
3
|
+
import { Coords } from '../../api/coords';
|
|
4
|
+
import { MapInstance } from '../generic-map';
|
|
5
|
+
import { syncCamera } from '../sync-camera';
|
|
6
|
+
import { useCoordsToMatrix } from '../use-coords-to-matrix';
|
|
7
|
+
import { useFunction } from '../use-function';
|
|
8
|
+
import { useBabylonMap } from '../use-babylon-map';
|
|
9
|
+
|
|
10
|
+
interface SyncCameraFCProps extends Coords {
|
|
11
|
+
setOnRender?: (callback: () => (mx: number[]) => void) => void;
|
|
12
|
+
onReady?: () => void;
|
|
13
|
+
map: MapInstance;
|
|
14
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** React component to sync the Babylon camera with the map provider on each render. */
|
|
18
|
+
export const SyncCameraFC = memo<SyncCameraFCProps>(({
|
|
19
|
+
latitude, longitude, altitude = 0, setOnRender, onReady, map, canvasRef,
|
|
20
|
+
}) => {
|
|
21
|
+
|
|
22
|
+
const mapCanvas = map.getCanvas();
|
|
23
|
+
const babylonMap = useBabylonMap();
|
|
24
|
+
const scene = babylonMap.scene!;
|
|
25
|
+
const engine = babylonMap.engine!;
|
|
26
|
+
|
|
27
|
+
const origin = useCoordsToMatrix({ latitude, longitude, altitude, fromLngLat: babylonMap.fromLngLat });
|
|
28
|
+
|
|
29
|
+
const ready = useRef(false);
|
|
30
|
+
|
|
31
|
+
const triggerRepaint = useMemo(() => map.triggerRepaint, [map]);
|
|
32
|
+
const mapPaintRequests = useRef(0);
|
|
33
|
+
const triggerRepaintOff = useFunction(() => {
|
|
34
|
+
mapPaintRequests.current++;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Render callback — called by MapLibre on each paint frame
|
|
38
|
+
const onRender = useFunction((viewProjMx: number[] | { defaultProjectionData: { mainMatrix: Record<string, number> } }) => {
|
|
39
|
+
map.triggerRepaint = triggerRepaintOff;
|
|
40
|
+
|
|
41
|
+
// Resize if needed
|
|
42
|
+
if (canvasRef.current && engine) {
|
|
43
|
+
if (canvasRef.current.width !== mapCanvas.width || canvasRef.current.height !== mapCanvas.height) {
|
|
44
|
+
canvasRef.current.width = mapCanvas.clientWidth * window.devicePixelRatio;
|
|
45
|
+
canvasRef.current.height = mapCanvas.clientHeight * window.devicePixelRatio;
|
|
46
|
+
canvasRef.current.style.width = `${mapCanvas.clientWidth}px`;
|
|
47
|
+
canvasRef.current.style.height = `${mapCanvas.clientHeight}px`;
|
|
48
|
+
engine.resize();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pVMx = 'defaultProjectionData' in viewProjMx
|
|
53
|
+
? Object.values(viewProjMx.defaultProjectionData.mainMatrix)
|
|
54
|
+
: viewProjMx;
|
|
55
|
+
babylonMap.viewProjMx = pVMx as number[];
|
|
56
|
+
|
|
57
|
+
// Sync camera
|
|
58
|
+
syncCamera(scene.activeCamera as FreeCamera, origin, babylonMap.viewProjMx);
|
|
59
|
+
|
|
60
|
+
// Wipe caches and render one frame
|
|
61
|
+
engine.wipeCaches(true);
|
|
62
|
+
scene.render(false);
|
|
63
|
+
|
|
64
|
+
if (!ready.current && onReady) {
|
|
65
|
+
ready.current = true;
|
|
66
|
+
onReady();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Restore triggerRepaint for demand-based rendering
|
|
70
|
+
map.triggerRepaint = triggerRepaint;
|
|
71
|
+
if (mapPaintRequests.current > 0) {
|
|
72
|
+
mapPaintRequests.current = 0;
|
|
73
|
+
map.triggerRepaint();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setOnRender && setOnRender(() => onRender);
|
|
79
|
+
}, [setOnRender, onRender]);
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
});
|
|
83
|
+
SyncCameraFC.displayName = 'SyncCameraFC';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Matrix, Quaternion, Vector3 } from '@babylonjs/core';
|
|
2
|
+
import { FromLngLat } from './generic-map';
|
|
3
|
+
import { Coords } from '../api/coords';
|
|
4
|
+
|
|
5
|
+
/** Calculate Babylon.js Matrix from coordinates.
|
|
6
|
+
* Returns a Babylon Matrix (world transform at the given geo coord).
|
|
7
|
+
* Matches the pattern from MapLibre + Babylon.js integration demo. */
|
|
8
|
+
export function coordsToMatrix({
|
|
9
|
+
longitude, latitude, altitude, fromLngLat
|
|
10
|
+
}: Coords & { fromLngLat: FromLngLat }): Matrix {
|
|
11
|
+
const center = fromLngLat([longitude, latitude], altitude);
|
|
12
|
+
const scaleUnit = center.meterInMercatorCoordinateUnits();
|
|
13
|
+
|
|
14
|
+
const position = new Vector3(center.x, center.y, center.z || 0);
|
|
15
|
+
const scaling = new Vector3(scaleUnit, scaleUnit, scaleUnit);
|
|
16
|
+
// Babylon default: +x east, +y up, +z north
|
|
17
|
+
// MapLibre default: +x east, -y north, +z up
|
|
18
|
+
const rotation = Quaternion.FromEulerAngles(Math.PI / 2, 0, 0);
|
|
19
|
+
|
|
20
|
+
return Matrix.Compose(scaling, rotation, position);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const earthRadius = 6371008.8;
|