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,55 @@
|
|
|
1
|
+
import { Scene } from '@babylonjs/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pointer event handler that maps DOM pointer coordinates to Babylon.js scene picking.
|
|
5
|
+
* Used for both canvas-in-layer and canvas-overlay modes.
|
|
6
|
+
*/
|
|
7
|
+
export interface PointerEventHandler {
|
|
8
|
+
/** Attach pointer listeners to the given DOM element (usually the map canvas parent). */
|
|
9
|
+
connect(target: HTMLElement): void;
|
|
10
|
+
/** Detach listeners. */
|
|
11
|
+
disconnect(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a pointer event handler that bridges map DOM events to Babylon scene picking.
|
|
16
|
+
*/
|
|
17
|
+
export function createPointerHandler(scene: Scene, canvas: HTMLCanvasElement): PointerEventHandler {
|
|
18
|
+
let target: HTMLElement | null = null;
|
|
19
|
+
|
|
20
|
+
const onPointerDown = (evt: PointerEvent) => {
|
|
21
|
+
const pickResult = scene.pick(
|
|
22
|
+
evt.offsetX * (canvas.width / canvas.clientWidth),
|
|
23
|
+
evt.offsetY * (canvas.height / canvas.clientHeight),
|
|
24
|
+
);
|
|
25
|
+
if (pickResult?.hit && pickResult.pickedMesh) {
|
|
26
|
+
const mesh = pickResult.pickedMesh;
|
|
27
|
+
mesh.metadata?.onPointerDown?.(evt, pickResult);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const onPointerMove = (evt: PointerEvent) => {
|
|
32
|
+
const pickResult = scene.pick(
|
|
33
|
+
evt.offsetX * (canvas.width / canvas.clientWidth),
|
|
34
|
+
evt.offsetY * (canvas.height / canvas.clientHeight),
|
|
35
|
+
);
|
|
36
|
+
if (pickResult?.hit && pickResult.pickedMesh) {
|
|
37
|
+
const mesh = pickResult.pickedMesh;
|
|
38
|
+
mesh.metadata?.onPointerMove?.(evt, pickResult);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
connect(el: HTMLElement) {
|
|
44
|
+
target = el;
|
|
45
|
+
target.addEventListener('pointerdown', onPointerDown as EventListener);
|
|
46
|
+
target.addEventListener('pointermove', onPointerMove as EventListener);
|
|
47
|
+
},
|
|
48
|
+
disconnect() {
|
|
49
|
+
if (!target) return;
|
|
50
|
+
target.removeEventListener('pointerdown', onPointerDown as EventListener);
|
|
51
|
+
target.removeEventListener('pointermove', onPointerMove as EventListener);
|
|
52
|
+
target = null;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// mock of functions used by `react-babylon-map` from `Maplibre` or `Mapbox`
|
|
2
|
+
|
|
3
|
+
/** Generic interface of Mapbox/Maplibre `LayerProps` */
|
|
4
|
+
export interface LayerProps {
|
|
5
|
+
id: string;
|
|
6
|
+
type: 'custom';
|
|
7
|
+
renderingMode: '3d';
|
|
8
|
+
onRemove?(map: MapInstance, gl: WebGLRenderingContext): void;
|
|
9
|
+
onAdd?(map: MapInstance, gl: WebGLRenderingContext): void;
|
|
10
|
+
prerender?(gl: WebGLRenderingContext, matrix: number[]): void;
|
|
11
|
+
render(gl: WebGLRenderingContext, matrix: number[]): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Generic interface of Mapbox/Maplibre `LngLatLike` */
|
|
15
|
+
export type LngLatLike = {
|
|
16
|
+
lng: number;
|
|
17
|
+
lat: number;
|
|
18
|
+
} | {
|
|
19
|
+
lon: number;
|
|
20
|
+
lat: number;
|
|
21
|
+
} | [
|
|
22
|
+
number,
|
|
23
|
+
number
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** Generic interface of Mapbox/Maplibre `static MercatorCoordinate.fromLngLat` */
|
|
27
|
+
export type FromLngLat = (lngLatLike: LngLatLike, altitude?: number) => MercatorCoordinate;
|
|
28
|
+
|
|
29
|
+
/** Generic interface of Mapbox/Maplibre typeof `MercatorCoordinate` */
|
|
30
|
+
export interface MercatorCoordinate {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
z?: number;
|
|
34
|
+
meterInMercatorCoordinateUnits(): number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Generic interface of Mapbox/Maplibre `Map` */
|
|
38
|
+
export interface MapInstance {
|
|
39
|
+
getCanvas(): HTMLCanvasElement;
|
|
40
|
+
/** MapLibre only */
|
|
41
|
+
getPixelRatio?: ()=>number;
|
|
42
|
+
triggerRepaint(): void;
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
44
|
+
on<T extends keyof MapEventType>(type: T, listener: (ev: MapEventType[T] & Object) => void): void;
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
46
|
+
off<T extends keyof MapEventType>(type: T, listener: (ev: MapEventType[T] & Object) => void): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Generic interface of Mapbox/Maplibre `MapEventType` */
|
|
50
|
+
export type MapEventType = {
|
|
51
|
+
resize: MapEvent;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Generic interface of `MapLibreEvent` or `MapBoxEvent` */
|
|
55
|
+
export interface MapEvent<TOrig = unknown> {
|
|
56
|
+
type: string;
|
|
57
|
+
target: MapInstance;
|
|
58
|
+
originalEvent: TOrig;
|
|
59
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Color4, Engine, Scene } from '@babylonjs/core';
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface MapEngineProps {
|
|
5
|
+
/** External WebGL rendering context (canvas-in-layer mode). */
|
|
6
|
+
externalContext?: WebGLRenderingContext;
|
|
7
|
+
/** Render mode: manual = external code triggers scene.render(), auto = engine runRenderLoop. */
|
|
8
|
+
renderMode?: 'auto' | 'manual';
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wrapper that creates a Babylon Engine (optionally from an external GL context)
|
|
14
|
+
* and a Scene, then provides them to children.
|
|
15
|
+
*
|
|
16
|
+
* In *overlay* mode a new <canvas> is created.
|
|
17
|
+
* In *canvas-in-layer* mode the external MapLibre GL context is reused.
|
|
18
|
+
*/
|
|
19
|
+
export const MapEngine: React.FC<MapEngineProps> = ({
|
|
20
|
+
externalContext,
|
|
21
|
+
renderMode = 'manual',
|
|
22
|
+
children,
|
|
23
|
+
}) => {
|
|
24
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
25
|
+
const [engine, setEngine] = useState<Engine | null>(null);
|
|
26
|
+
const [scene, setScene] = useState<Scene | null>(null);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let eng: Engine;
|
|
30
|
+
|
|
31
|
+
if (externalContext) {
|
|
32
|
+
eng = new Engine(
|
|
33
|
+
externalContext as WebGL2RenderingContext,
|
|
34
|
+
false, // antialias
|
|
35
|
+
{ preserveDrawingBuffer: true, stencil: true },
|
|
36
|
+
false, // adaptToDeviceRatio
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
eng = new Engine(canvasRef.current!, true);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const scn = new Scene(eng);
|
|
43
|
+
scn.useRightHandedSystem = true;
|
|
44
|
+
scn.autoClear = false;
|
|
45
|
+
scn.clearColor = new Color4(0, 0, 0, 0);
|
|
46
|
+
|
|
47
|
+
if (renderMode === 'auto') {
|
|
48
|
+
eng.runRenderLoop(() => scn.render());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setEngine(eng);
|
|
52
|
+
setScene(scn);
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
scn.dispose();
|
|
56
|
+
eng.dispose();
|
|
57
|
+
};
|
|
58
|
+
}, [externalContext, renderMode]);
|
|
59
|
+
|
|
60
|
+
if (!engine || !scene) {
|
|
61
|
+
return externalContext ? null : <canvas ref={canvasRef} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} />;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
{!externalContext && <canvas ref={canvasRef} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} />}
|
|
67
|
+
{children}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Matrix } from '@babylonjs/core';
|
|
2
|
+
|
|
3
|
+
/** Column-major (MapLibre) → transposed flat array (Babylon row-major layout) */
|
|
4
|
+
export function transposeMatrix(m: number[]): number[] {
|
|
5
|
+
return [
|
|
6
|
+
m[0], m[4], m[8], m[12],
|
|
7
|
+
m[1], m[5], m[9], m[13],
|
|
8
|
+
m[2], m[6], m[10], m[14],
|
|
9
|
+
m[3], m[7], m[11], m[15],
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** MapLibre column-major matrix → Babylon.js Matrix */
|
|
14
|
+
export function columnMajorToBabylonMatrix(m: number[]): Matrix {
|
|
15
|
+
return Matrix.FromArray(transposeMatrix(m));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Extract a flat column-major array from a Babylon Matrix (for MapLibre interop) */
|
|
19
|
+
export function babylonMatrixToColumnMajor(m: Matrix): number[] {
|
|
20
|
+
const a = m.toArray();
|
|
21
|
+
return transposeMatrix(Array.from(a));
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FreeCamera, Matrix } from '@babylonjs/core';
|
|
2
|
+
|
|
3
|
+
const tmpCameraMx = Matrix.Identity();
|
|
4
|
+
const tmpWvpMx = Matrix.Identity();
|
|
5
|
+
|
|
6
|
+
/** Synchronise a Babylon camera with the map's view-projection matrix.
|
|
7
|
+
* @param camera The Babylon camera to update.
|
|
8
|
+
* @param origin Babylon world matrix for the Canvas origin.
|
|
9
|
+
* @param mapCamMx Flat array from MapLibre's view-projection (column-major).
|
|
10
|
+
*/
|
|
11
|
+
export function syncCamera(
|
|
12
|
+
camera: FreeCamera,
|
|
13
|
+
origin: Matrix,
|
|
14
|
+
mapCamMx: number[],
|
|
15
|
+
): void {
|
|
16
|
+
|
|
17
|
+
// Matrix.FromArray reads column-major data (same layout as MapLibre provides)
|
|
18
|
+
Matrix.FromArrayToRef(mapCamMx, 0, tmpCameraMx);
|
|
19
|
+
|
|
20
|
+
// world-view-projection = origin * camera (same as demo: worldMatrix.multiply(cameraMatrix))
|
|
21
|
+
origin.multiplyToRef(tmpCameraMx, tmpWvpMx);
|
|
22
|
+
|
|
23
|
+
// Freeze the projection to the composed matrix so Babylon's own
|
|
24
|
+
// projection math is bypassed.
|
|
25
|
+
camera.freezeProjectionMatrix(tmpWvpMx);
|
|
26
|
+
|
|
27
|
+
camera.minZ = 0;
|
|
28
|
+
camera.maxZ = 1e6;
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import { Engine, Scene } from '@babylonjs/core';
|
|
3
|
+
import { FromLngLat, MapInstance } from './generic-map';
|
|
4
|
+
|
|
5
|
+
/** Context object shared between both rendering modes. */
|
|
6
|
+
export interface BabylonMap<T extends MapInstance = MapInstance> {
|
|
7
|
+
/** Map provider instance */
|
|
8
|
+
map: T;
|
|
9
|
+
/** Column-major view-projection matrix from the map provider */
|
|
10
|
+
viewProjMx: number[];
|
|
11
|
+
/** Mercator coordinate factory */
|
|
12
|
+
fromLngLat: FromLngLat;
|
|
13
|
+
/** Babylon Engine (may be null during init) */
|
|
14
|
+
engine?: Engine;
|
|
15
|
+
/** Babylon Scene (may be null during init) */
|
|
16
|
+
scene?: Scene;
|
|
17
|
+
/** Current origin coordinates (set by Canvas) */
|
|
18
|
+
coords?: { longitude: number; latitude: number; altitude?: number };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── React context ──────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export const BabylonMapContext = createContext<BabylonMap | null>(null);
|
|
24
|
+
|
|
25
|
+
/** Access the current BabylonMap context (map, scene, engine, viewProjMx). */
|
|
26
|
+
export function useBabylonMap<T extends MapInstance = MapInstance>(): BabylonMap<T> {
|
|
27
|
+
const ctx = useContext(BabylonMapContext);
|
|
28
|
+
if (!ctx) throw new Error('useBabylonMap must be used inside a <Canvas>');
|
|
29
|
+
return ctx as BabylonMap<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Initialise / update the BabylonMap context value. */
|
|
33
|
+
export function createBabylonMap<T extends MapInstance>(opts: {
|
|
34
|
+
map: T;
|
|
35
|
+
fromLngLat: FromLngLat;
|
|
36
|
+
engine?: Engine;
|
|
37
|
+
scene?: Scene;
|
|
38
|
+
}): BabylonMap<T> {
|
|
39
|
+
return {
|
|
40
|
+
map: opts.map,
|
|
41
|
+
viewProjMx: new Array(16).fill(0) as number[],
|
|
42
|
+
fromLngLat: opts.fromLngLat,
|
|
43
|
+
engine: opts.engine,
|
|
44
|
+
scene: opts.scene,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { Matrix } from '@babylonjs/core';
|
|
3
|
+
import { coordsToMatrix } from './coords-to-matrix';
|
|
4
|
+
|
|
5
|
+
type Props = Parameters<typeof coordsToMatrix>[0];
|
|
6
|
+
|
|
7
|
+
/** Calculate a Babylon Matrix from coordinates (memoised). */
|
|
8
|
+
export function useCoordsToMatrix({ latitude, longitude, altitude, fromLngLat }: Props): Matrix {
|
|
9
|
+
return useMemo(
|
|
10
|
+
() => coordsToMatrix({ latitude, longitude, altitude, fromLngLat }),
|
|
11
|
+
[latitude, longitude, altitude, fromLngLat],
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react';
|
|
2
|
+
import { Coords } from '../api/coords';
|
|
3
|
+
import { BabylonMapContext, BabylonMap } from './use-babylon-map';
|
|
4
|
+
|
|
5
|
+
/** Read the current coordinates from context. */
|
|
6
|
+
export function useCoords(): Coords {
|
|
7
|
+
const ctx = useContext(BabylonMapContext);
|
|
8
|
+
if (!ctx) throw new Error('useCoords must be used inside a <Canvas>');
|
|
9
|
+
return (ctx as any).coords as Coords; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Update the coordinates stored on the context. */
|
|
13
|
+
export function useSetCoords({ longitude, latitude, altitude }: Coords) {
|
|
14
|
+
// Coordinates are now stored directly on the BabylonMap context object
|
|
15
|
+
// so downstream hooks can read them.
|
|
16
|
+
useMemo(() => {
|
|
17
|
+
const ctx = (BabylonMapContext as any)._currentValue as BabylonMap | null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
18
|
+
if (ctx) {
|
|
19
|
+
(ctx as any).coords = { longitude, latitude, altitude }; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
20
|
+
}
|
|
21
|
+
}, [longitude, latitude, altitude]);
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
+
export const useFunction = <T extends (...args: any[]) => any>(callback: T): T => {
|
|
5
|
+
const callbackRef = useRef(callback);
|
|
6
|
+
callbackRef.current = callback;
|
|
7
|
+
return useCallback((...args: any[]) => {
|
|
8
|
+
return callbackRef.current(...args);
|
|
9
|
+
}, []) as T;
|
|
10
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
2
|
+
import { MercatorCoordinate } from 'mapbox-gl';
|
|
3
|
+
import { memo, useState } from 'react';
|
|
4
|
+
import { Layer, useMap } from 'react-map-gl/mapbox';
|
|
5
|
+
import { CanvasProps } from '../api/canvas-props';
|
|
6
|
+
import { useCanvasInLayer } from '../core/canvas-in-layer/use-canvas-in-layer';
|
|
7
|
+
import { InitCanvasFC } from '../core/canvas-overlay/init-canvas-fc';
|
|
8
|
+
import { Render } from '../core/canvas-overlay/render';
|
|
9
|
+
import { MapInstance } from '../core/generic-map';
|
|
10
|
+
import { useFunction } from '../core/use-function';
|
|
11
|
+
|
|
12
|
+
const fromLngLat = MercatorCoordinate.fromLngLat;
|
|
13
|
+
|
|
14
|
+
/** `react-babylon-map` canvas inside `Mapbox` */
|
|
15
|
+
export const Canvas = memo<CanvasProps>(({ overlay, ...props }) => {
|
|
16
|
+
const map = useMap().current!.getMap(); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{overlay && <CanvasOverlay map={map} {...props} />}
|
|
21
|
+
{!overlay && <CanvasInLayer map={map} {...props} />}
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
Canvas.displayName = 'Canvas';
|
|
26
|
+
|
|
27
|
+
interface CanvasPropsAndMap extends CanvasProps {
|
|
28
|
+
map: MapInstance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CanvasInLayer = memo<CanvasPropsAndMap>(({ map, ...props }) => {
|
|
32
|
+
const layerProps = useCanvasInLayer(props, fromLngLat, map);
|
|
33
|
+
/* @ts-ignore */
|
|
34
|
+
return <Layer {...layerProps} />;
|
|
35
|
+
});
|
|
36
|
+
CanvasInLayer.displayName = 'CanvasInLayer';
|
|
37
|
+
|
|
38
|
+
const CanvasOverlay = memo<CanvasPropsAndMap>(({ map, id, beforeId, ...props }) => {
|
|
39
|
+
const [onRender, setOnRender] = useState<(mx: number[]) => void>();
|
|
40
|
+
|
|
41
|
+
const render = useFunction<Render>((_gl, mx) => {
|
|
42
|
+
if (!onRender) return;
|
|
43
|
+
onRender(mx as number[]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
{/* @ts-ignore */}
|
|
49
|
+
<Layer id={id} beforeId={beforeId} type="custom" render={render} />
|
|
50
|
+
<InitCanvasFC
|
|
51
|
+
{...props}
|
|
52
|
+
setOnRender={setOnRender}
|
|
53
|
+
map={map}
|
|
54
|
+
fromLngLat={fromLngLat}
|
|
55
|
+
/>
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
CanvasOverlay.displayName = 'CanvasOverlay';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
2
|
+
import { MercatorCoordinate } from 'maplibre-gl';
|
|
3
|
+
import { memo, useState } from 'react';
|
|
4
|
+
import { Layer, useMap } from 'react-map-gl/maplibre';
|
|
5
|
+
import { CanvasProps } from '../api/canvas-props';
|
|
6
|
+
import { useCanvasInLayer } from '../core/canvas-in-layer/use-canvas-in-layer';
|
|
7
|
+
import { InitCanvasFC } from '../core/canvas-overlay/init-canvas-fc';
|
|
8
|
+
import { Render } from '../core/canvas-overlay/render';
|
|
9
|
+
import { MapInstance } from '../core/generic-map';
|
|
10
|
+
import { useFunction } from '../core/use-function';
|
|
11
|
+
|
|
12
|
+
const fromLngLat = MercatorCoordinate.fromLngLat;
|
|
13
|
+
|
|
14
|
+
/** `react-babylon-map` canvas inside `MapLibre` */
|
|
15
|
+
export const Canvas = memo<CanvasProps>(({ overlay, ...props }) => {
|
|
16
|
+
const map = useMap().current!.getMap(); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{overlay && <CanvasOverlay map={map} {...props} />}
|
|
21
|
+
{!overlay && <CanvasInLayer map={map} {...props} />}
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
Canvas.displayName = 'Canvas';
|
|
26
|
+
|
|
27
|
+
interface CanvasPropsAndMap extends CanvasProps {
|
|
28
|
+
map: MapInstance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CanvasInLayer = memo<CanvasPropsAndMap>(({ map, ...props }) => {
|
|
32
|
+
const layerProps = useCanvasInLayer(props, fromLngLat, map);
|
|
33
|
+
/* @ts-ignore */
|
|
34
|
+
return <Layer {...layerProps} />;
|
|
35
|
+
});
|
|
36
|
+
CanvasInLayer.displayName = 'CanvasInLayer';
|
|
37
|
+
|
|
38
|
+
const CanvasOverlay = memo<CanvasPropsAndMap>(({ map, id, beforeId, ...props }) => {
|
|
39
|
+
const [onRender, setOnRender] = useState<(mx: number[]) => void>();
|
|
40
|
+
|
|
41
|
+
const render = useFunction<Render>((_gl, mx) => {
|
|
42
|
+
if (!onRender) return;
|
|
43
|
+
onRender(mx as number[]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
{/* @ts-ignore */}
|
|
49
|
+
<Layer id={id} beforeId={beforeId} type="custom" render={render} />
|
|
50
|
+
<InitCanvasFC
|
|
51
|
+
{...props}
|
|
52
|
+
setOnRender={setOnRender}
|
|
53
|
+
map={map}
|
|
54
|
+
fromLngLat={fromLngLat}
|
|
55
|
+
/>
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
CanvasOverlay.displayName = 'CanvasOverlay';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { GlobalProvider } from "@ladle/react";
|
|
2
|
+
import './style.css';
|
|
3
|
+
import React, { memo, useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
|
|
6
|
+
export const Provider: GlobalProvider = ({ children }) => {
|
|
7
|
+
const [header] = useState(() => document.createElement('div'));
|
|
8
|
+
|
|
9
|
+
const toggle = useCallback(() => {
|
|
10
|
+
if (!header.parentElement) return;
|
|
11
|
+
header.parentElement.classList.toggle('hide')
|
|
12
|
+
}, [])
|
|
13
|
+
|
|
14
|
+
const hide = useCallback((e: any)=>{ // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
15
|
+
if (!header.parentElement) return;
|
|
16
|
+
if(e.target.nodeName !== 'A') return;
|
|
17
|
+
header.parentElement.classList.add('hide')
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const container = document.querySelector('.ladle-aside');
|
|
22
|
+
if (!container) return;
|
|
23
|
+
container.prepend(header);
|
|
24
|
+
container.classList.add('hide');
|
|
25
|
+
container.addEventListener('click', hide)
|
|
26
|
+
return () => {
|
|
27
|
+
header.remove();
|
|
28
|
+
container.removeEventListener('click', hide)
|
|
29
|
+
}
|
|
30
|
+
}, [])
|
|
31
|
+
return <>
|
|
32
|
+
{createPortal(<Header toggle={toggle} />, header)}
|
|
33
|
+
{children}
|
|
34
|
+
</>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface HeaderProps {
|
|
38
|
+
toggle: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const Header = memo<HeaderProps>(({ toggle }) => {
|
|
42
|
+
return <>
|
|
43
|
+
<button className="story-menu-btn" onClick={toggle}>☰</button>
|
|
44
|
+
<h1>react-babylon-map</h1>
|
|
45
|
+
<div style={{ paddingBottom: 15 }} className="story-header">
|
|
46
|
+
<p>Babylon.js inside Mapbox & Maplibre</p>
|
|
47
|
+
</div>
|
|
48
|
+
</>
|
|
49
|
+
})
|
|
50
|
+
Header.displayName = 'Header';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.ladle-main {
|
|
2
|
+
padding: 0;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ladle-aside {
|
|
6
|
+
order: -1;
|
|
7
|
+
padding: 1em 1.5em;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
header.ladle-addons {
|
|
11
|
+
max-width: 18em
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
header.ladle-addons ul {
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
header.ladle-addons li {
|
|
19
|
+
margin: 0.35em;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
header.ladle-addons li:nth-of-type(4) {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.story-menu-btn {
|
|
27
|
+
border: none;
|
|
28
|
+
background: none;
|
|
29
|
+
position: fixed;
|
|
30
|
+
right: 1rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.story-menu-btn img {
|
|
34
|
+
width: 2rem;
|
|
35
|
+
height: 2rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (max-width: 767px) {
|
|
39
|
+
.ladle-aside h1 {
|
|
40
|
+
margin: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ladle-aside {
|
|
44
|
+
position: fixed;
|
|
45
|
+
left: 0;
|
|
46
|
+
right: 0;
|
|
47
|
+
top: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.hide.ladle-aside>*:not(:first-child) {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.hide .story-header {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (min-width: 768px) {
|
|
60
|
+
.story-menu-btn {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stories",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "ladle serve"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@babylonjs/core": "^9.0.0",
|
|
10
|
+
"@babylonjs/materials": "^9.0.0",
|
|
11
|
+
"@ifc-lite/geometry": "^1.16.5",
|
|
12
|
+
"@ladle/react": "^4.0.2",
|
|
13
|
+
"leva": "^0.9.35",
|
|
14
|
+
"luxon": "^3.7.2",
|
|
15
|
+
"mapbox-gl": "^3.9.4",
|
|
16
|
+
"maplibre-gl": "^5.4.0",
|
|
17
|
+
"react": "^18.2.0",
|
|
18
|
+
"react-dom": "^18.2.0",
|
|
19
|
+
"react-map-gl": "^8.0.1",
|
|
20
|
+
"suncalc": "^1.9.0",
|
|
21
|
+
"suspend-react": "^0.1.3",
|
|
22
|
+
"tz-lookup": "^6.1.25"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^18.2.15",
|
|
26
|
+
"@types/react-dom": "^18.2.7",
|
|
27
|
+
"@vitejs/plugin-react": "^4.0.3",
|
|
28
|
+
"typescript": "^5.1.6",
|
|
29
|
+
"vite": "^4.4.4"
|
|
30
|
+
}
|
|
31
|
+
}
|