react-three-map 0.1.5 → 0.2.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/CHANGELOG.md +21 -0
- package/README.md +105 -48
- package/dist/cjs/main.js.js +1318 -1
- package/dist/es/main.js10.mjs +56 -0
- package/dist/es/main.js10.mjs.map +1 -0
- package/dist/es/main.js11.mjs +9 -0
- package/dist/es/main.js11.mjs.map +1 -0
- package/dist/es/main.js2.mjs +23 -28
- package/dist/es/main.js2.mjs.map +1 -1
- package/dist/es/main.js3.mjs +6 -7
- package/dist/es/main.js3.mjs.map +1 -1
- package/dist/es/main.js4.mjs +28 -56
- package/dist/es/main.js4.mjs.map +1 -1
- package/dist/es/main.js5.mjs +23533 -12
- package/dist/es/main.js5.mjs.map +1 -1
- package/dist/es/main.js6.mjs +2 -53
- package/dist/es/main.js6.mjs.map +1 -1
- package/dist/es/main.js7.mjs +2 -6
- package/dist/es/main.js7.mjs.map +1 -1
- package/dist/es/main.js8.mjs +60 -0
- package/dist/es/main.js8.mjs.map +1 -0
- package/dist/es/main.js9.mjs +16 -0
- package/dist/es/main.js9.mjs.map +1 -0
- package/dist/maplibre/cjs/main.js.js +1 -0
- package/dist/maplibre/es/main.js.mjs +5 -0
- package/dist/maplibre/es/main.js.mjs.map +1 -0
- package/dist/maplibre/es/main.js2.mjs +34 -0
- package/dist/maplibre/es/main.js2.mjs.map +1 -0
- package/dist/maplibre/es/main.js3.mjs +10 -0
- package/dist/maplibre/es/main.js3.mjs.map +1 -0
- package/dist/maplibre/es/main.js4.mjs +31 -0
- package/dist/maplibre/es/main.js4.mjs.map +1 -0
- package/dist/maplibre/es/main.js5.mjs +60 -0
- package/dist/maplibre/es/main.js5.mjs.map +1 -0
- package/dist/maplibre/es/main.js6.mjs +16 -0
- package/dist/maplibre/es/main.js6.mjs.map +1 -0
- package/dist/maplibre/es/main.js7.mjs +56 -0
- package/dist/maplibre/es/main.js7.mjs.map +1 -0
- package/dist/maplibre/es/main.js8.mjs +9 -0
- package/dist/maplibre/es/main.js8.mjs.map +1 -0
- package/docs/basic-app.gif +0 -0
- package/maplibre/package.json +7 -0
- package/package.json +12 -5
- package/src/canvas/canvas-mapbox.tsx +32 -0
- package/src/canvas/canvas-maplibre.tsx +32 -0
- package/src/canvas/core/coords-to-matrix.ts +20 -0
- package/src/canvas/{create-events.ts → core/create-events.ts} +2 -2
- package/src/canvas/core/generic-map.test.ts +2 -0
- package/src/canvas/core/generic-map.ts +44 -0
- package/src/canvas/core/internal-canvas.tsx +43 -0
- package/src/canvas/{state-ref.ts → core/state-ref.ts} +2 -2
- package/src/canvas/{use-on-add.ts → core/use-on-add.ts} +30 -18
- package/src/canvas/core/use-render.ts +25 -0
- package/src/main-mapbox.ts +1 -0
- package/src/main-maplibre.ts +1 -0
- package/src/stories/canvas.basic.stories.tsx +63 -0
- package/src/stories/{canvas.stories.tsx → comparison.stories.tsx} +6 -4
- package/src/stories/html-on-top.stories.tsx +26 -0
- package/src/stories/my-scene.tsx +18 -11
- package/src/stories/render-on-demand.stories.tsx +9 -7
- package/src/stories/story-map.tsx +40 -0
- package/dist/cjs/vite.svg +0 -1
- package/dist/es/vite.svg +0 -1
- package/index.html +0 -13
- package/src/canvas/canvas.tsx +0 -49
- package/src/canvas/coords-to-matrix.ts +0 -19
- package/src/canvas/use-render.ts +0 -29
- package/src/main.ts +0 -1
- /package/src/canvas/{use-function.ts → core/use-function.ts} +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Euler, Matrix4, Quaternion, Vector3 } from "three";
|
|
2
|
+
import { FromLngLat } from "./generic-map";
|
|
3
|
+
|
|
4
|
+
const quat = new Quaternion();
|
|
5
|
+
const euler = new Euler();
|
|
6
|
+
const pos = new Vector3();
|
|
7
|
+
const scale = new Vector3();
|
|
8
|
+
const m4 = new Matrix4();
|
|
9
|
+
|
|
10
|
+
/** calculate Matrix4 from coordinates */
|
|
11
|
+
export function coordsToMatrix({ longitude, latitude, altitude, fromLngLat }: {
|
|
12
|
+
longitude: number, latitude: number, altitude: number, fromLngLat: FromLngLat
|
|
13
|
+
}) {
|
|
14
|
+
const center = fromLngLat([longitude, latitude], altitude);
|
|
15
|
+
const scaleUnit = center.meterInMercatorCoordinateUnits();
|
|
16
|
+
pos.set(center.x, center.y, center.z || 0);
|
|
17
|
+
scale.set(scaleUnit, -scaleUnit, scaleUnit);
|
|
18
|
+
quat.setFromEuler(euler.set(-Math.PI * .5, 0, 0));
|
|
19
|
+
return m4.compose(pos, quat, scale).toArray();
|
|
20
|
+
}
|
|
@@ -17,7 +17,7 @@ const DOM_EVENTS = {
|
|
|
17
17
|
} as const;
|
|
18
18
|
|
|
19
19
|
/** ThreeLayer event manager for MapLibre and Mapbox */
|
|
20
|
-
export function createEvents()
|
|
20
|
+
export function createEvents(): RenderProps<HTMLCanvasElement>["events"] {
|
|
21
21
|
return (store: UseBoundStore<RootState>) => {
|
|
22
22
|
const { handlePointer } = createFiberEvents(store);
|
|
23
23
|
return {
|
|
@@ -50,7 +50,7 @@ export function createEvents() : RenderProps<HTMLCanvasElement>["events"] {
|
|
|
50
50
|
connect: (target: HTMLElement) => {
|
|
51
51
|
const { set, events } = store.getState();
|
|
52
52
|
events.disconnect?.();
|
|
53
|
-
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
53
|
+
set((state) => ({ events: { ...state.events, connected: target.parentNode } }));
|
|
54
54
|
Object.entries(events.handlers ?? []).forEach(([name, event]) => {
|
|
55
55
|
const [eventName, passive] = DOM_EVENTS[name as keyof typeof DOM_EVENTS];
|
|
56
56
|
target.addEventListener(eventName, event, { passive });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// mock of functions used by `react-three-map` from `Maplibre` or `Mapbox`
|
|
2
|
+
|
|
3
|
+
/** Generic interface of Mapbox/Maplibre `LngLatLike` */
|
|
4
|
+
export type LngLatLike = {
|
|
5
|
+
lng: number;
|
|
6
|
+
lat: number;
|
|
7
|
+
} | {
|
|
8
|
+
lon: number;
|
|
9
|
+
lat: number;
|
|
10
|
+
} | [
|
|
11
|
+
number,
|
|
12
|
+
number
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/** Generic interface of Mapbox/Maplibre `static MercatorCoordinate.fromLngLat` */
|
|
16
|
+
export type FromLngLat = (lngLatLike: LngLatLike, altitude?: number) => MercatorCoordinate;
|
|
17
|
+
|
|
18
|
+
/** Generic interface of Mapbox/Maplibre typeof `MercatorCoordinate` */
|
|
19
|
+
export interface MercatorCoordinate {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
z?: number;
|
|
23
|
+
meterInMercatorCoordinateUnits(): number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Generic interface of Mapbox/Maplibre `Map` */
|
|
27
|
+
export interface Map {
|
|
28
|
+
getCanvas(): HTMLCanvasElement;
|
|
29
|
+
triggerRepaint(): void;
|
|
30
|
+
on<T extends keyof MapEventType>(type: T, listener: (ev: MapEventType[T] & Object) => void): this;
|
|
31
|
+
off<T extends keyof MapEventType>(type: T, listener: (ev: MapEventType[T] & Object) => void): this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Generic interface of Mapbox/Maplibre `MapEventType` */
|
|
35
|
+
export type MapEventType = {
|
|
36
|
+
resize: MapEvent;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Generic interface of `MapLibreEvent` or `MapBoxEvent` */
|
|
40
|
+
export interface MapEvent<TOrig = unknown> {
|
|
41
|
+
type: string;
|
|
42
|
+
target: Map;
|
|
43
|
+
originalEvent: TOrig;
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RenderProps } from "@react-three/fiber";
|
|
2
|
+
import { PropsWithChildren, memo, useEffect, useId, useRef } from "react";
|
|
3
|
+
import { Layer } from "react-map-gl";
|
|
4
|
+
import { Matrix4Tuple } from "three";
|
|
5
|
+
import { StateRef } from "./state-ref";
|
|
6
|
+
import { useOnAdd } from "./use-on-add";
|
|
7
|
+
import { useRender } from "./use-render";
|
|
8
|
+
|
|
9
|
+
export interface InternalCanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'frameloop'>, PropsWithChildren {
|
|
10
|
+
frameloop: 'always' | 'demand',
|
|
11
|
+
m4: Matrix4Tuple;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const InternalCanvas = memo<InternalCanvasProps>(({
|
|
15
|
+
m4, children, frameloop, ...renderProps
|
|
16
|
+
}) => {
|
|
17
|
+
const id = useId();
|
|
18
|
+
|
|
19
|
+
const stateRef: StateRef = useRef();
|
|
20
|
+
|
|
21
|
+
const { onAdd, onRemove, mounted } = useOnAdd(stateRef, { frameloop, ...renderProps });
|
|
22
|
+
|
|
23
|
+
const render = useRender(m4, stateRef, frameloop);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!mounted) return;
|
|
27
|
+
if (!stateRef.current) return;
|
|
28
|
+
stateRef.current.root.render(<>{children}</>);
|
|
29
|
+
}, [stateRef, mounted, children])
|
|
30
|
+
|
|
31
|
+
return <Layer
|
|
32
|
+
id={id}
|
|
33
|
+
type="custom"
|
|
34
|
+
renderingMode="3d"
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
onAdd={onAdd as any}
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
onRemove={onRemove as any}
|
|
39
|
+
render={render}
|
|
40
|
+
/>
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
InternalCanvas.displayName = 'InternalCanvas';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ReconcilerRoot, RootState } from "@react-three/fiber";
|
|
2
2
|
import { MutableRefObject } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { Map } from "./generic-map";
|
|
4
4
|
|
|
5
5
|
export type StateRef = MutableRefObject<{
|
|
6
|
-
map:
|
|
6
|
+
map: Map;
|
|
7
7
|
root: ReconcilerRoot<HTMLCanvasElement>;
|
|
8
8
|
state?: RootState;
|
|
9
9
|
} | undefined>
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { RenderProps, createRoot } from "@react-three/fiber";
|
|
2
|
+
import { Map } from "./generic-map";
|
|
2
3
|
import { useState } from "react";
|
|
3
|
-
import { MapInstance } from "react-map-gl";
|
|
4
4
|
import { createEvents } from "./create-events";
|
|
5
5
|
import { StateRef } from "./state-ref";
|
|
6
6
|
import { useFunction } from "./use-function";
|
|
7
7
|
|
|
8
|
-
export function useOnAdd
|
|
8
|
+
export function useOnAdd(ref: StateRef, { frameloop, ...renderProps }: RenderProps<HTMLCanvasElement>) {
|
|
9
9
|
|
|
10
10
|
const [mounted, setMounted] = useState(false);
|
|
11
11
|
|
|
12
|
-
const onAdd = useFunction((map:
|
|
13
|
-
|
|
12
|
+
const onAdd = useFunction((map: Map, gl: WebGLRenderingContext) => {
|
|
13
|
+
|
|
14
14
|
const canvas = map.getCanvas();
|
|
15
15
|
const root = createRoot(canvas);
|
|
16
16
|
root.configure({
|
|
17
|
-
frameloop: "demand",
|
|
18
17
|
dpr: window.devicePixelRatio,
|
|
19
|
-
shadows: true,
|
|
20
18
|
events: createEvents(),
|
|
21
19
|
...renderProps,
|
|
20
|
+
frameloop: 'never',
|
|
22
21
|
gl: {
|
|
23
22
|
context: gl,
|
|
24
23
|
depth: true,
|
|
@@ -27,16 +26,29 @@ export function useOnAdd (ref: StateRef, renderProps: RenderProps<HTMLCanvasElem
|
|
|
27
26
|
...renderProps?.gl,
|
|
28
27
|
},
|
|
29
28
|
onCreated: (state) => {
|
|
29
|
+
|
|
30
|
+
if (frameloop === 'demand') {
|
|
31
|
+
state.set({
|
|
32
|
+
frameloop,
|
|
33
|
+
invalidate: () => {
|
|
34
|
+
map.triggerRepaint();
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
ref.current = {
|
|
31
40
|
state,
|
|
32
41
|
map,
|
|
33
42
|
root,
|
|
34
43
|
}
|
|
44
|
+
|
|
35
45
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
36
46
|
state.gl.forceContextLoss = () => { };
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
|
|
48
|
+
// we update the camera on create rather than on config
|
|
49
|
+
// because otherwise some components may use it too early
|
|
50
|
+
// and get NaN values they can't recover from (drei HTML)
|
|
51
|
+
state.camera.matrixAutoUpdate = false;
|
|
40
52
|
},
|
|
41
53
|
size: {
|
|
42
54
|
width: canvas.clientWidth,
|
|
@@ -54,25 +66,25 @@ export function useOnAdd (ref: StateRef, renderProps: RenderProps<HTMLCanvasElem
|
|
|
54
66
|
|
|
55
67
|
map.on('resize', onResize)
|
|
56
68
|
|
|
57
|
-
setTimeout(()=>setMounted(true));
|
|
58
|
-
|
|
69
|
+
setTimeout(() => setMounted(true));
|
|
70
|
+
|
|
59
71
|
})
|
|
60
72
|
|
|
61
|
-
const onResize = useFunction(()=>{
|
|
62
|
-
if(!ref.current?.state) return;
|
|
73
|
+
const onResize = useFunction(() => {
|
|
74
|
+
if (!ref.current?.state) return;
|
|
63
75
|
const state = ref.current.state;
|
|
64
76
|
const map = ref.current.map;
|
|
65
77
|
const canvas = map.getCanvas();
|
|
66
78
|
state.setSize(canvas.width, canvas.height);
|
|
67
79
|
})
|
|
68
80
|
|
|
69
|
-
const onRemove = useFunction((map:
|
|
70
|
-
setTimeout(()=>{
|
|
71
|
-
if(!ref.current) return;
|
|
81
|
+
const onRemove = useFunction((map: Map) => {
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
if (!ref.current) return;
|
|
72
84
|
ref.current.root.unmount();
|
|
73
|
-
map.off(
|
|
85
|
+
map.off('resize', onResize)
|
|
74
86
|
})
|
|
75
87
|
})
|
|
76
88
|
|
|
77
|
-
return {onAdd, onRemove, mounted};
|
|
89
|
+
return { onAdd, onRemove, mounted };
|
|
78
90
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Matrix4, Matrix4Tuple } from "three";
|
|
2
|
+
import { StateRef } from "./state-ref";
|
|
3
|
+
import { useFunction } from "./use-function";
|
|
4
|
+
|
|
5
|
+
const mx = new Matrix4();
|
|
6
|
+
|
|
7
|
+
export function useRender(
|
|
8
|
+
m4: Matrix4Tuple, stateRef: StateRef, frameloop: 'always' | 'demand'
|
|
9
|
+
) {
|
|
10
|
+
|
|
11
|
+
const render = useFunction((_gl: WebGL2RenderingContext, matrix: number[]) => {
|
|
12
|
+
if (!stateRef.current?.state) return;
|
|
13
|
+
const camera = stateRef.current.state.camera;
|
|
14
|
+
const gl = stateRef.current.state.gl;
|
|
15
|
+
const advance = stateRef.current.state.advance;
|
|
16
|
+
camera.projectionMatrix.fromArray(matrix).multiply(mx.fromArray(m4));
|
|
17
|
+
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
18
|
+
gl.resetState();
|
|
19
|
+
advance(Date.now() * 0.001, true);
|
|
20
|
+
if (frameloop === 'always') stateRef.current.map.triggerRepaint();
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return render;
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './canvas/canvas-mapbox';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './canvas/canvas-maplibre';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useFrame, Vector3 } from "@react-three/fiber";
|
|
2
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
3
|
+
import { FC, useRef, useState } from "react";
|
|
4
|
+
import Map from 'react-map-gl';
|
|
5
|
+
import { Mesh } from "three";
|
|
6
|
+
import { Canvas } from "../canvas/canvas-maplibre";
|
|
7
|
+
import MapLibre from "maplibre-gl";
|
|
8
|
+
|
|
9
|
+
export default {title: 'Canvas'}
|
|
10
|
+
|
|
11
|
+
const Box : FC<{position: Vector3}> = (props) => {
|
|
12
|
+
// This reference gives us direct access to the THREE.Mesh object
|
|
13
|
+
const ref = useRef<Mesh>(null)
|
|
14
|
+
// Hold state for hovered and clicked events
|
|
15
|
+
const [hovered, hover] = useState(false)
|
|
16
|
+
const [clicked, click] = useState(false)
|
|
17
|
+
// Subscribe this component to the render-loop, rotate the mesh every frame
|
|
18
|
+
useFrame((_state, delta) => {
|
|
19
|
+
if(!ref.current) return;
|
|
20
|
+
ref.current.rotation.x += delta;
|
|
21
|
+
ref.current.rotation.z -= delta;
|
|
22
|
+
})
|
|
23
|
+
// Return the view, these are regular Threejs elements expressed in JSX
|
|
24
|
+
return (
|
|
25
|
+
<mesh
|
|
26
|
+
{...props}
|
|
27
|
+
ref={ref}
|
|
28
|
+
scale={clicked ? 1.5 : 1}
|
|
29
|
+
onClick={() => click(!clicked)}
|
|
30
|
+
onPointerOver={() => hover(true)}
|
|
31
|
+
onPointerOut={() => hover(false)}>
|
|
32
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
33
|
+
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
|
|
34
|
+
</mesh>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function BasicExample() {
|
|
39
|
+
return <div style={{ height: '100vh' }}>
|
|
40
|
+
<Map
|
|
41
|
+
mapLib={MapLibre}
|
|
42
|
+
antialias
|
|
43
|
+
initialViewState={{
|
|
44
|
+
latitude: 51,
|
|
45
|
+
longitude: 0,
|
|
46
|
+
zoom: 13,
|
|
47
|
+
pitch: 60,
|
|
48
|
+
}}
|
|
49
|
+
mapStyle="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
|
50
|
+
>
|
|
51
|
+
<Canvas latitude={51} longitude={0}>
|
|
52
|
+
<hemisphereLight
|
|
53
|
+
args={["#ffffff", "#60666C"]}
|
|
54
|
+
position={[1, 4.5, 3]}
|
|
55
|
+
/>
|
|
56
|
+
<object3D scale={500}>
|
|
57
|
+
<Box position={[-1.2, 1, 0]} />
|
|
58
|
+
<Box position={[1.2, 1, 0]} />
|
|
59
|
+
</object3D>
|
|
60
|
+
</Canvas>
|
|
61
|
+
</Map>
|
|
62
|
+
</div>
|
|
63
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ThemeState, useLadleContext } from "@ladle/react";
|
|
2
2
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
3
|
-
import Map from 'react-map-gl
|
|
4
|
-
import { Canvas } from "../canvas/canvas";
|
|
3
|
+
import Map from 'react-map-gl';
|
|
4
|
+
import { Canvas } from "../canvas/canvas-maplibre";
|
|
5
5
|
import { Canvas as FiberCanvas } from "@react-three/fiber"
|
|
6
6
|
import { MyScene } from "./my-scene";
|
|
7
7
|
import { MapControls } from "@react-three/drei"
|
|
8
|
+
import MapLibre from "maplibre-gl";
|
|
8
9
|
|
|
9
10
|
export function WithMap() {
|
|
10
11
|
const theme = useLadleContext().globalState.theme;
|
|
@@ -13,6 +14,7 @@ export function WithMap() {
|
|
|
13
14
|
: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
|
14
15
|
return <div style={{ height: '100vh' }}>
|
|
15
16
|
<Map
|
|
17
|
+
mapLib={MapLibre}
|
|
16
18
|
antialias
|
|
17
19
|
initialViewState={{
|
|
18
20
|
latitude: 51.5073218,
|
|
@@ -22,7 +24,7 @@ export function WithMap() {
|
|
|
22
24
|
}}
|
|
23
25
|
mapStyle={mapStyle}
|
|
24
26
|
>
|
|
25
|
-
<Canvas latitude={51.5073218} longitude={-0.1276473}>
|
|
27
|
+
<Canvas latitude={51.5073218} longitude={-0.1276473} shadows="variance">
|
|
26
28
|
<MyScene />
|
|
27
29
|
</Canvas>
|
|
28
30
|
</Map>
|
|
@@ -31,7 +33,7 @@ export function WithMap() {
|
|
|
31
33
|
|
|
32
34
|
export const WithoutMap = () => {
|
|
33
35
|
return <div style={{ height: '100vh' }}>
|
|
34
|
-
<FiberCanvas camera={{position: [100,100,100]}} shadows="
|
|
36
|
+
<FiberCanvas camera={{position: [100,100,100]}} shadows="variance">
|
|
35
37
|
<MyScene />
|
|
36
38
|
<MapControls makeDefault />
|
|
37
39
|
</FiberCanvas>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Box, Html, Stats } from "@react-three/drei";
|
|
2
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { MathUtils } from "three";
|
|
5
|
+
import { Canvas } from "../canvas/canvas-maplibre";
|
|
6
|
+
import { StoryMap } from "./story-map";
|
|
7
|
+
|
|
8
|
+
export function Default() {
|
|
9
|
+
|
|
10
|
+
const [hovered, hover] = useState(false);
|
|
11
|
+
|
|
12
|
+
return <StoryMap>
|
|
13
|
+
<Canvas latitude={51} longitude={0} frameloop="demand">
|
|
14
|
+
<Html>ola</Html>
|
|
15
|
+
<Box
|
|
16
|
+
args={[500, 500, 500]}
|
|
17
|
+
position={[0, 250, 0]}
|
|
18
|
+
rotation={[0, 45 * MathUtils.DEG2RAD, 0]}
|
|
19
|
+
onPointerOver={() => hover(true)}
|
|
20
|
+
onPointerOut={() => hover(false)}
|
|
21
|
+
material-color={hovered ? 'purple' : 'orange'}
|
|
22
|
+
/>
|
|
23
|
+
<Stats />
|
|
24
|
+
</Canvas>
|
|
25
|
+
</StoryMap>
|
|
26
|
+
}
|
package/src/stories/my-scene.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Box, Plane, useHelper } from "@react-three/drei";
|
|
2
2
|
import { MeshProps, useFrame, useThree } from '@react-three/fiber';
|
|
3
|
+
import { useControls } from "leva";
|
|
3
4
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
4
5
|
import { useCallback, useRef, useState } from 'react';
|
|
5
6
|
import { CameraHelper, MathUtils, Mesh, OrthographicCamera } from "three";
|
|
@@ -8,8 +9,8 @@ export function MyScene() {
|
|
|
8
9
|
return <>
|
|
9
10
|
<Lights />
|
|
10
11
|
<Floor />
|
|
11
|
-
<MyBox position={[-8* 3, 8*1.5, 0]} />
|
|
12
|
-
<MyBox position={[8*3, 8*1.5, 0]} />
|
|
12
|
+
<MyBox position={[-8 * 3, 8 * 1.5, 0]} />
|
|
13
|
+
<MyBox position={[8 * 3, 8 * 1.5, 0]} />
|
|
13
14
|
</>
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -17,18 +18,18 @@ export function MyScene() {
|
|
|
17
18
|
function MyBox(props: MeshProps) {
|
|
18
19
|
const [hovered, hover] = useState(false);
|
|
19
20
|
const mesh = useRef<Mesh>(null)
|
|
20
|
-
const invalidate = useThree(st=>st.invalidate);
|
|
21
|
+
const invalidate = useThree(st => st.invalidate);
|
|
21
22
|
|
|
22
|
-
const onOver = useCallback(()=>{
|
|
23
|
+
const onOver = useCallback(() => {
|
|
23
24
|
hover(true);
|
|
24
25
|
}, [])
|
|
25
26
|
|
|
26
|
-
const onOut = useCallback(()=>{
|
|
27
|
+
const onOut = useCallback(() => {
|
|
27
28
|
hover(false);
|
|
28
29
|
}, [])
|
|
29
30
|
|
|
30
|
-
useFrame((_st, dt)=>{
|
|
31
|
-
if(!mesh.current) return;
|
|
31
|
+
useFrame((_st, dt) => {
|
|
32
|
+
if (!mesh.current) return;
|
|
32
33
|
mesh.current.rotateY(dt);
|
|
33
34
|
invalidate();
|
|
34
35
|
})
|
|
@@ -54,23 +55,29 @@ function MyBox(props: MeshProps) {
|
|
|
54
55
|
}
|
|
55
56
|
function Lights() {
|
|
56
57
|
const cam = useRef<OrthographicCamera>(null);
|
|
58
|
+
const noCam = useRef<OrthographicCamera>(null);
|
|
59
|
+
const { showCamHelper } = useControls({
|
|
60
|
+
showCamHelper: {
|
|
61
|
+
value: false,
|
|
62
|
+
label: 'show camera helper'
|
|
63
|
+
}
|
|
64
|
+
});
|
|
57
65
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
-
useHelper(cam as any, CameraHelper)
|
|
66
|
+
useHelper((showCamHelper ? cam : noCam) as any, CameraHelper)
|
|
59
67
|
const camSize = 100;
|
|
60
68
|
return <>
|
|
61
69
|
<ambientLight intensity={0.5} />
|
|
62
|
-
<directionalLight
|
|
70
|
+
<directionalLight
|
|
63
71
|
castShadow
|
|
64
72
|
position={[2.5, 50, 5]}
|
|
65
73
|
intensity={1.5}
|
|
66
74
|
shadow-mapSize={1024}
|
|
67
|
-
shadow-bias={-0.005}
|
|
68
75
|
>
|
|
69
76
|
<orthographicCamera
|
|
70
77
|
ref={cam}
|
|
71
78
|
attach="shadow-camera"
|
|
72
79
|
args={[-camSize, camSize, -camSize, camSize, 0.1, 100]}
|
|
73
|
-
rotation={[0,90*MathUtils.DEG2RAD,0]}
|
|
80
|
+
rotation={[0, 90 * MathUtils.DEG2RAD, 0]}
|
|
74
81
|
/>
|
|
75
82
|
</directionalLight>
|
|
76
83
|
<pointLight position={[-10, 0, -20]} color="white" intensity={1} />
|
|
@@ -2,9 +2,10 @@ import { ThemeState, useLadleContext } from "@ladle/react";
|
|
|
2
2
|
import { Box, Stats } from "@react-three/drei";
|
|
3
3
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
4
4
|
import { useState } from "react";
|
|
5
|
-
import Map from 'react-map-gl
|
|
5
|
+
import Map from 'react-map-gl';
|
|
6
6
|
import { MathUtils } from "three";
|
|
7
|
-
import { Canvas } from "../canvas/canvas";
|
|
7
|
+
import { Canvas } from "../canvas/canvas-maplibre";
|
|
8
|
+
import MapLibre from "maplibre-gl";
|
|
8
9
|
|
|
9
10
|
export function Default() {
|
|
10
11
|
|
|
@@ -18,6 +19,7 @@ export function Default() {
|
|
|
18
19
|
|
|
19
20
|
return <div style={{ height: '100vh', position: 'relative' }}>
|
|
20
21
|
<Map
|
|
22
|
+
mapLib={MapLibre}
|
|
21
23
|
antialias
|
|
22
24
|
initialViewState={{
|
|
23
25
|
latitude: 51,
|
|
@@ -27,11 +29,11 @@ export function Default() {
|
|
|
27
29
|
}}
|
|
28
30
|
mapStyle={mapStyle}
|
|
29
31
|
>
|
|
30
|
-
<Canvas latitude={51} longitude={0}>
|
|
32
|
+
<Canvas latitude={51} longitude={0} frameloop="demand">
|
|
31
33
|
<Box
|
|
32
|
-
args={[500,500,500]}
|
|
33
|
-
position={[0,250,0]}
|
|
34
|
-
rotation={[0,45 * MathUtils.DEG2RAD,0]}
|
|
34
|
+
args={[500, 500, 500]}
|
|
35
|
+
position={[0, 250, 0]}
|
|
36
|
+
rotation={[0, 45 * MathUtils.DEG2RAD, 0]}
|
|
35
37
|
onPointerOver={() => hover(true)}
|
|
36
38
|
onPointerOut={() => hover(false)}
|
|
37
39
|
material-color={hovered ? 'purple' : 'orange'}
|
|
@@ -39,7 +41,7 @@ export function Default() {
|
|
|
39
41
|
<Stats />
|
|
40
42
|
</Canvas>
|
|
41
43
|
</Map>
|
|
42
|
-
<div style={{position: 'absolute', top: 0, right: 0, left: 80, background: '#ffffffc2', padding: 5}}>
|
|
44
|
+
<div style={{ position: 'absolute', top: 0, right: 0, left: 80, background: '#ffffffc2', padding: 5 }}>
|
|
43
45
|
Hover over the box, it will only render once to change colour, or when you move the camera. Look at the stats to confirm.
|
|
44
46
|
</div>
|
|
45
47
|
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
2
|
+
import { FC, PropsWithChildren } from "react"
|
|
3
|
+
import MapLibre from "maplibre-gl";
|
|
4
|
+
import Map from 'react-map-gl';
|
|
5
|
+
import { useLadleContext, ThemeState } from '@ladle/react';
|
|
6
|
+
|
|
7
|
+
export interface StoryMapProps extends PropsWithChildren {
|
|
8
|
+
latitude?: number,
|
|
9
|
+
longitude?: number,
|
|
10
|
+
zoom?: number,
|
|
11
|
+
pitch?: number,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** `<Map>` styled for stories */
|
|
15
|
+
export const StoryMap: FC<StoryMapProps> = ({
|
|
16
|
+
children
|
|
17
|
+
}) => {
|
|
18
|
+
|
|
19
|
+
const theme = useLadleContext().globalState.theme;
|
|
20
|
+
|
|
21
|
+
const mapStyle = theme === ThemeState.Dark
|
|
22
|
+
? "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
|
|
23
|
+
: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json";
|
|
24
|
+
|
|
25
|
+
return <div style={{ height: '100vh', position: 'relative' }}>
|
|
26
|
+
<Map
|
|
27
|
+
mapLib={MapLibre}
|
|
28
|
+
antialias
|
|
29
|
+
initialViewState={{
|
|
30
|
+
latitude: 51,
|
|
31
|
+
longitude: 0,
|
|
32
|
+
zoom: 13,
|
|
33
|
+
pitch: 60,
|
|
34
|
+
}}
|
|
35
|
+
mapStyle={mapStyle}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</Map>
|
|
39
|
+
</div>
|
|
40
|
+
}
|
package/dist/cjs/vite.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/dist/es/vite.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/index.html
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>Vite + React + TS</title>
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<div id="root"></div>
|
|
11
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
-
</body>
|
|
13
|
-
</html>
|
package/src/canvas/canvas.tsx
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { RenderProps, extend } from "@react-three/fiber";
|
|
2
|
-
import { PropsWithChildren, memo, useEffect, useId, useMemo, useRef } from "react";
|
|
3
|
-
import { Layer } from "react-map-gl/maplibre";
|
|
4
|
-
import * as THREE from "three";
|
|
5
|
-
import { coordsToMatrix } from "./coords-to-matrix";
|
|
6
|
-
import { StateRef } from "./state-ref";
|
|
7
|
-
import { useOnAdd } from "./use-on-add";
|
|
8
|
-
import { useRender } from "./use-render";
|
|
9
|
-
|
|
10
|
-
extend(THREE);
|
|
11
|
-
|
|
12
|
-
export interface CanvasProps extends RenderProps<HTMLCanvasElement>, PropsWithChildren {
|
|
13
|
-
longitude: number,
|
|
14
|
-
latitude: number,
|
|
15
|
-
altitude?: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** react`-three-fiber` canvas inside `MapLibre` */
|
|
19
|
-
export const Canvas = memo<CanvasProps>(({
|
|
20
|
-
longitude, latitude, altitude = 0,
|
|
21
|
-
children, ...renderProps
|
|
22
|
-
})=>{
|
|
23
|
-
const id = useId();
|
|
24
|
-
|
|
25
|
-
const ref : StateRef = useRef();
|
|
26
|
-
|
|
27
|
-
const m4 = useMemo(()=>coordsToMatrix({
|
|
28
|
-
latitude, longitude, altitude
|
|
29
|
-
}), [latitude, longitude, altitude])
|
|
30
|
-
|
|
31
|
-
const {onAdd, onRemove, mounted} = useOnAdd(ref, renderProps);
|
|
32
|
-
|
|
33
|
-
const render = useRender(m4, ref);
|
|
34
|
-
|
|
35
|
-
useEffect(()=>{
|
|
36
|
-
if(!mounted) return;
|
|
37
|
-
if(!ref.current) return;
|
|
38
|
-
ref.current.root.render(<>{children}</>);
|
|
39
|
-
}, [ref, mounted, children])
|
|
40
|
-
|
|
41
|
-
return <Layer
|
|
42
|
-
id={id}
|
|
43
|
-
type="custom"
|
|
44
|
-
renderingMode="3d"
|
|
45
|
-
onAdd={onAdd}
|
|
46
|
-
onRemove={onRemove}
|
|
47
|
-
render={render}
|
|
48
|
-
/>
|
|
49
|
-
})
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { MercatorCoordinate } from "maplibre-gl";
|
|
2
|
-
import { Euler, Matrix4, Quaternion, Vector3 } from "three";
|
|
3
|
-
|
|
4
|
-
const quat = new Quaternion();
|
|
5
|
-
const euler = new Euler();
|
|
6
|
-
const pos = new Vector3();
|
|
7
|
-
const scale = new Vector3();
|
|
8
|
-
|
|
9
|
-
/** calculate Matrix4 from coordinates */
|
|
10
|
-
export function coordsToMatrix ({ longitude, latitude, altitude }: {
|
|
11
|
-
longitude: number, latitude: number, altitude: number
|
|
12
|
-
}) {
|
|
13
|
-
const center = MercatorCoordinate.fromLngLat([longitude, latitude], altitude);
|
|
14
|
-
const scaleUnit = center.meterInMercatorCoordinateUnits();
|
|
15
|
-
pos.set(center.x, center.y, center.z || 0);
|
|
16
|
-
scale.set(scaleUnit, -scaleUnit, scaleUnit);
|
|
17
|
-
quat.setFromEuler(euler.set(-Math.PI * .5, 0, 0));
|
|
18
|
-
return new Matrix4().compose(pos, quat, scale);
|
|
19
|
-
}
|