@mapcomponents/three 1.7.2 → 1.7.3
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/dist/README.md +54 -0
- package/dist/assets/3D/godzilla_simple.glb +0 -0
- package/dist/assets/splats/output.splat +0 -0
- package/dist/components/MlThreeGizmo.d.ts +12 -0
- package/dist/components/MlThreeGizmo.d.ts.map +1 -0
- package/dist/components/MlThreeModelLayer/MlThreeModelLayer.cy.d.ts +2 -0
- package/dist/components/MlThreeModelLayer/MlThreeModelLayer.cy.d.ts.map +1 -0
- package/dist/components/MlThreeModelLayer/MlThreeModelLayer.d.ts +12 -0
- package/dist/components/MlThreeModelLayer/MlThreeModelLayer.d.ts.map +1 -0
- package/dist/components/MlThreeObjectControls.d.ts +41 -0
- package/dist/components/MlThreeObjectControls.d.ts.map +1 -0
- package/dist/components/MlThreeSplatLayer/MlThreeSplatLayer.cy.d.ts +2 -0
- package/dist/components/MlThreeSplatLayer/MlThreeSplatLayer.cy.d.ts.map +1 -0
- package/dist/components/MlThreeSplatLayer/MlThreeSplatLayer.d.ts +12 -0
- package/dist/components/MlThreeSplatLayer/MlThreeSplatLayer.d.ts.map +1 -0
- package/dist/contexts/ThreeContext.d.ts +15 -0
- package/dist/contexts/ThreeContext.d.ts.map +1 -0
- package/dist/contexts/ThreeProvider.d.ts +18 -0
- package/dist/contexts/ThreeProvider.d.ts.map +1 -0
- package/dist/cypress/support/commands.d.ts +1 -0
- package/dist/cypress/support/commands.d.ts.map +1 -0
- package/dist/cypress/support/component.d.ts +9 -0
- package/dist/cypress/support/component.d.ts.map +1 -0
- package/dist/decorators/ThreejsContextDecorator.d.ts +3 -0
- package/dist/decorators/ThreejsContextDecorator.d.ts.map +1 -0
- package/dist/hooks/useThreeModel.d.ts +34 -0
- package/dist/hooks/useThreeModel.d.ts.map +1 -0
- package/dist/html2canvas.esm-CUkZERmf.js +22 -0
- package/dist/html2canvas.esm-Dmi1NfiH.mjs +4871 -0
- package/dist/index-CnnmRv4J.mjs +90875 -0
- package/dist/index-DbqgNSpy.js +5442 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es-BPSzkkDP.mjs +6674 -0
- package/dist/index.es-ajYPKLNS.js +18 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +10 -0
- package/dist/lib/ThreejsSceneHelper.d.ts +16 -0
- package/dist/lib/ThreejsSceneHelper.d.ts.map +1 -0
- package/dist/lib/ThreejsSceneRenderer.d.ts +18 -0
- package/dist/lib/ThreejsSceneRenderer.d.ts.map +1 -0
- package/dist/lib/ThreejsUtils.d.ts +14 -0
- package/dist/lib/ThreejsUtils.d.ts.map +1 -0
- package/dist/lib/splats/GaussianSplattingMesh.d.ts +104 -0
- package/dist/lib/splats/GaussianSplattingMesh.d.ts.map +1 -0
- package/dist/lib/splats/GaussianSplattingShaders.d.ts +10 -0
- package/dist/lib/splats/GaussianSplattingShaders.d.ts.map +1 -0
- package/dist/lib/splats/loaders/PlySplatLoader.d.ts +81 -0
- package/dist/lib/splats/loaders/PlySplatLoader.d.ts.map +1 -0
- package/dist/lib/splats/loaders/SplatLoader.d.ts +10 -0
- package/dist/lib/splats/loaders/SplatLoader.d.ts.map +1 -0
- package/dist/lib/utils/coroutine.d.ts +17 -0
- package/dist/lib/utils/coroutine.d.ts.map +1 -0
- package/dist/package.json +26 -0
- package/dist/purify.es-D1I7B1hP.js +2 -0
- package/dist/purify.es-DHbHSKL1.mjs +528 -0
- package/package.json +8 -6
- package/src/components/{MlTransformControls.tsx → MlThreeGizmo.tsx} +4 -4
- package/src/components/MlThreeModelLayer/MlThreeModelLayer.cy.tsx +2 -3
- package/src/components/MlThreeModelLayer/MlThreeModelLayer.stories.tsx +14 -55
- package/src/components/MlThreeModelLayer/MlThreeModelLayer.tsx +30 -133
- package/src/components/MlThreeObjectControls.tsx +289 -0
- package/src/components/MlThreeSplatLayer/MlThreeSplatLayer.cy.tsx +2 -3
- package/src/components/MlThreeSplatLayer/MlThreeSplatLayer.stories.tsx +14 -58
- package/src/components/MlThreeSplatLayer/MlThreeSplatLayer.tsx +26 -138
- package/src/decorators/ThreejsContextDecorator.tsx +1 -1
- package/src/hooks/useThreeModel.tsx +179 -0
- package/src/index.ts +4 -2
- package/vite.config.ts +2 -2
- package/src/components/ThreeObjectControls.tsx +0 -197
- /package/src/{components → contexts}/ThreeContext.tsx +0 -0
- /package/src/{components → contexts}/ThreeProvider.tsx +0 -0
|
@@ -3,9 +3,8 @@ import Button from '@mui/material/Button';
|
|
|
3
3
|
import MlThreeModelLayer from './MlThreeModelLayer';
|
|
4
4
|
import { useMap, TopToolbar, Sidebar } from '@mapcomponents/react-maplibre';
|
|
5
5
|
import ThreejsContextDecorator from '../../decorators/ThreejsContextDecorator';
|
|
6
|
-
import { useThree } from '
|
|
7
|
-
import {
|
|
8
|
-
import ThreejsUtils from '../../lib/ThreejsUtils';
|
|
6
|
+
import { useThree } from '../../contexts/ThreeContext';
|
|
7
|
+
import { MlThreeObjectControls } from '../MlThreeObjectControls';
|
|
9
8
|
import * as THREE from 'three';
|
|
10
9
|
|
|
11
10
|
const storyoptions = {
|
|
@@ -48,17 +47,14 @@ const Lights = () => {
|
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
const Template: any = () => {
|
|
51
|
-
const { worldMatrix } = useThree();
|
|
52
50
|
const [showLayer, setShowLayer] = useState(true);
|
|
53
51
|
const [scale, setScale] = useState(1);
|
|
54
52
|
const [rotation, setRotation] = useState({ x: 90, y: 90, z: 0 });
|
|
55
|
-
const [useMapCoords, setUseMapCoords] = useState(true);
|
|
56
53
|
const [mapPosition, setMapPosition] = useState({ lng: 7.097, lat: 50.7355 });
|
|
57
|
-
const [altitude, setAltitude] = useState(0);
|
|
58
54
|
const [position, setPosition] = useState({ x: 0, y: 0, z: 0 });
|
|
55
|
+
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
59
56
|
const [enableTransformControls, setEnableTransformControls] = useState(false);
|
|
60
57
|
const [transformMode, setTransformMode] = useState<'translate' | 'rotate' | 'scale'>('translate');
|
|
61
|
-
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
62
58
|
|
|
63
59
|
const mapHook = useMap({ mapId: 'map_1' });
|
|
64
60
|
useEffect(() => {
|
|
@@ -68,55 +64,22 @@ const Template: any = () => {
|
|
|
68
64
|
mapHook.map?.setCenter([7.097, 50.7355]);
|
|
69
65
|
}, [mapHook.map]);
|
|
70
66
|
|
|
71
|
-
// Center map on position when switching coordinate modes
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
if (!mapHook.map) return;
|
|
74
|
-
if (useMapCoords) {
|
|
75
|
-
mapHook.map.setCenter([mapPosition.lng, mapPosition.lat]);
|
|
76
|
-
}
|
|
77
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
|
-
}, [useMapCoords, mapHook.map]);
|
|
79
|
-
|
|
80
|
-
const handleTransformChange = (object: THREE.Object3D) => {
|
|
81
|
-
setRotation({
|
|
82
|
-
x: (object.rotation.x * 180) / Math.PI,
|
|
83
|
-
y: (object.rotation.y * 180) / Math.PI,
|
|
84
|
-
z: (object.rotation.z * 180) / Math.PI,
|
|
85
|
-
});
|
|
86
|
-
setScale(object.scale.x);
|
|
87
|
-
|
|
88
|
-
if (useMapCoords && worldMatrix) {
|
|
89
|
-
const [lng, lat, alt] = ThreejsUtils.toMapPosition(worldMatrix, object.position);
|
|
90
|
-
setMapPosition({ lng, lat });
|
|
91
|
-
setAltitude(alt);
|
|
92
|
-
} else {
|
|
93
|
-
setPosition({ x: object.position.x, y: object.position.y, z: object.position.z });
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
67
|
return (
|
|
98
68
|
<>
|
|
99
69
|
<Lights />
|
|
100
70
|
{showLayer && (
|
|
101
71
|
<MlThreeModelLayer
|
|
102
72
|
url="assets/3D/godzilla_simple.glb"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
73
|
+
position={[mapPosition.lng, mapPosition.lat]}
|
|
74
|
+
transform={{
|
|
75
|
+
rotation: {
|
|
76
|
+
x: (rotation.x * Math.PI) / 180,
|
|
77
|
+
y: (rotation.y * Math.PI) / 180,
|
|
78
|
+
z: (rotation.z * Math.PI) / 180,
|
|
79
|
+
},
|
|
80
|
+
scale: scale,
|
|
81
|
+
position: position,
|
|
107
82
|
}}
|
|
108
|
-
scale={scale}
|
|
109
|
-
enableTransformControls={enableTransformControls}
|
|
110
|
-
transformMode={transformMode}
|
|
111
|
-
onTransformChange={handleTransformChange}
|
|
112
|
-
{...(useMapCoords
|
|
113
|
-
? {
|
|
114
|
-
mapPosition: [mapPosition.lng, mapPosition.lat],
|
|
115
|
-
altitude: altitude,
|
|
116
|
-
}
|
|
117
|
-
: {
|
|
118
|
-
position: position,
|
|
119
|
-
})}
|
|
120
83
|
/>
|
|
121
84
|
)}
|
|
122
85
|
|
|
@@ -131,26 +94,22 @@ const Template: any = () => {
|
|
|
131
94
|
}
|
|
132
95
|
/>
|
|
133
96
|
<Sidebar open={sidebarOpen} setOpen={setSidebarOpen} name="3D Model Config">
|
|
134
|
-
<
|
|
97
|
+
<MlThreeObjectControls
|
|
135
98
|
showLayer={showLayer}
|
|
136
99
|
setShowLayer={setShowLayer}
|
|
137
100
|
scale={scale}
|
|
138
101
|
setScale={setScale}
|
|
139
102
|
rotation={rotation}
|
|
140
103
|
setRotation={setRotation}
|
|
141
|
-
useMapCoords={useMapCoords}
|
|
142
|
-
setUseMapCoords={setUseMapCoords}
|
|
143
104
|
mapPosition={mapPosition}
|
|
144
105
|
setMapPosition={setMapPosition}
|
|
145
|
-
altitude={altitude}
|
|
146
|
-
setAltitude={setAltitude}
|
|
147
106
|
position={position}
|
|
148
107
|
setPosition={setPosition}
|
|
108
|
+
layerName="Model"
|
|
149
109
|
enableTransformControls={enableTransformControls}
|
|
150
110
|
setEnableTransformControls={setEnableTransformControls}
|
|
151
111
|
transformMode={transformMode}
|
|
152
112
|
setTransformMode={setTransformMode}
|
|
153
|
-
layerName="Model"
|
|
154
113
|
/>
|
|
155
114
|
</Sidebar>
|
|
156
115
|
</>
|
|
@@ -1,152 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as THREE from 'three';
|
|
1
|
+
import { useMemo } from 'react';
|
|
3
2
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
4
3
|
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
|
|
5
|
-
import {
|
|
6
|
-
import { useThree } from '../ThreeContext';
|
|
7
|
-
import ThreejsUtils from '../../lib/ThreejsUtils';
|
|
8
|
-
import MlTransformControls from '../MlTransformControls';
|
|
4
|
+
import { useThreeModel, UseThreeModelProps, ModelLoader } from '../../hooks/useThreeModel';
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
|
-
* Renders obj or gltf 3D Models on the MapLibreMap
|
|
7
|
+
* Renders obj or gltf 3D Models on the MapLibreMap
|
|
12
8
|
*
|
|
13
9
|
* @component
|
|
14
10
|
*/
|
|
15
11
|
|
|
16
|
-
export
|
|
12
|
+
export type MlThreeModelLayerProps = Omit<UseThreeModelProps, 'loaders'> & {
|
|
17
13
|
mapId?: string;
|
|
18
|
-
|
|
19
|
-
position?: { x: number; y: number; z: number };
|
|
20
|
-
mapPosition?: LngLatLike;
|
|
21
|
-
altitude?: number;
|
|
22
|
-
rotation?: { x: number; y: number; z: number };
|
|
23
|
-
scale?: { x: number; y: number; z: number } | number;
|
|
24
|
-
enableTransformControls?: boolean;
|
|
25
|
-
transformMode?: 'translate' | 'rotate' | 'scale';
|
|
26
|
-
onTransformChange?: (object: THREE.Object3D) => void;
|
|
27
|
-
init?: () => void;
|
|
28
|
-
onDone?: () => void;
|
|
29
|
-
}
|
|
14
|
+
};
|
|
30
15
|
|
|
31
16
|
const MlThreeModelLayer = (props: MlThreeModelLayerProps) => {
|
|
32
|
-
const {
|
|
17
|
+
const { url, position, transform, init, onDone, customLoaders } = props;
|
|
18
|
+
|
|
19
|
+
const loaders = useMemo<Record<string, ModelLoader>>(
|
|
20
|
+
() => ({
|
|
21
|
+
gltf: (url, onLoad) => {
|
|
22
|
+
const loader = new GLTFLoader();
|
|
23
|
+
loader.load(url, (gltf) => onLoad(gltf.scene));
|
|
24
|
+
},
|
|
25
|
+
glb: (url, onLoad) => {
|
|
26
|
+
const loader = new GLTFLoader();
|
|
27
|
+
loader.load(url, (gltf) => onLoad(gltf.scene));
|
|
28
|
+
},
|
|
29
|
+
obj: (url, onLoad) => {
|
|
30
|
+
const loader = new OBJLoader();
|
|
31
|
+
loader.load(url, (obj) => onLoad(obj));
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
[]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
useThreeModel({
|
|
33
38
|
url,
|
|
34
39
|
position,
|
|
35
|
-
|
|
36
|
-
altitude,
|
|
37
|
-
rotation,
|
|
38
|
-
scale,
|
|
39
|
-
enableTransformControls,
|
|
40
|
-
transformMode,
|
|
41
|
-
onTransformChange,
|
|
40
|
+
transform,
|
|
42
41
|
init,
|
|
43
42
|
onDone,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const [model, setModel] = useState<THREE.Object3D | undefined>(undefined);
|
|
48
|
-
|
|
49
|
-
// Use refs for callbacks to avoid re-triggering the effect when they change
|
|
50
|
-
const initRef = useRef(init);
|
|
51
|
-
const onDoneRef = useRef(onDone);
|
|
52
|
-
initRef.current = init;
|
|
53
|
-
onDoneRef.current = onDone;
|
|
54
|
-
|
|
55
|
-
const transformRef = useRef({ position, mapPosition, altitude, rotation, scale });
|
|
56
|
-
transformRef.current = { position, mapPosition, altitude, rotation, scale };
|
|
57
|
-
const worldMatrixInvRef = useRef(worldMatrixInv);
|
|
58
|
-
worldMatrixInvRef.current = worldMatrixInv;
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (!scene) return;
|
|
62
|
-
|
|
63
|
-
if (typeof initRef.current === 'function') {
|
|
64
|
-
initRef.current();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const extension = url.split('.').pop()?.toLowerCase();
|
|
68
|
-
|
|
69
|
-
const onLoad = (object: THREE.Object3D) => {
|
|
70
|
-
const { position, mapPosition, altitude, rotation, scale } = transformRef.current;
|
|
71
|
-
const worldMatrixInv = worldMatrixInvRef.current;
|
|
72
|
-
|
|
73
|
-
if (mapPosition && worldMatrixInv) {
|
|
74
|
-
const scenePos = ThreejsUtils.toScenePosition(worldMatrixInv, mapPosition, altitude);
|
|
75
|
-
object.position.set(scenePos.x, scenePos.y, scenePos.z);
|
|
76
|
-
} else if (position) {
|
|
77
|
-
object.position.set(position.x, position.y, position.z);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (rotation) {
|
|
81
|
-
object.rotation.set(rotation.x, rotation.y, rotation.z);
|
|
82
|
-
}
|
|
83
|
-
if (scale) {
|
|
84
|
-
if (typeof scale === 'number') {
|
|
85
|
-
object.scale.set(scale, scale, scale);
|
|
86
|
-
} else {
|
|
87
|
-
object.scale.set(scale.x, scale.y, scale.z);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
modelRef.current = object;
|
|
92
|
-
scene.add(object);
|
|
93
|
-
setModel(object);
|
|
94
|
-
if (typeof onDoneRef.current === 'function') {
|
|
95
|
-
onDoneRef.current();
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
if (extension === 'glb' || extension === 'gltf') {
|
|
100
|
-
const loader = new GLTFLoader();
|
|
101
|
-
loader.load(url, (gltf) => {
|
|
102
|
-
onLoad(gltf.scene);
|
|
103
|
-
});
|
|
104
|
-
} else if (extension === 'obj') {
|
|
105
|
-
const loader = new OBJLoader();
|
|
106
|
-
loader.load(url, (obj) => {
|
|
107
|
-
onLoad(obj);
|
|
108
|
-
});
|
|
109
|
-
} else {
|
|
110
|
-
console.warn('MlThreeModelLayer: Unsupported file extension', extension);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return () => {
|
|
114
|
-
if (modelRef.current) {
|
|
115
|
-
scene.remove(modelRef.current);
|
|
116
|
-
modelRef.current = undefined;
|
|
117
|
-
setModel(undefined);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
}, [scene, url]);
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
if (!model) return;
|
|
124
|
-
|
|
125
|
-
// Handle position: mapPosition takes precedence over position
|
|
126
|
-
if (mapPosition && worldMatrixInv) {
|
|
127
|
-
const scenePos = ThreejsUtils.toScenePosition(worldMatrixInv, mapPosition, altitude);
|
|
128
|
-
model.position.set(scenePos.x, scenePos.y, scenePos.z);
|
|
129
|
-
} else if (position) {
|
|
130
|
-
model.position.set(position.x, position.y, position.z);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (rotation) {
|
|
134
|
-
model.rotation.set(rotation.x, rotation.y, rotation.z);
|
|
135
|
-
}
|
|
136
|
-
if (scale) {
|
|
137
|
-
if (typeof scale === 'number') {
|
|
138
|
-
model.scale.set(scale, scale, scale);
|
|
139
|
-
} else {
|
|
140
|
-
model.scale.set(scale.x, scale.y, scale.z);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}, [model, position, mapPosition, altitude, rotation, scale, worldMatrixInv]);
|
|
43
|
+
loaders,
|
|
44
|
+
customLoaders,
|
|
45
|
+
});
|
|
144
46
|
|
|
145
|
-
if (enableTransformControls && model) {
|
|
146
|
-
return (
|
|
147
|
-
<MlTransformControls target={model} mode={transformMode} onObjectChange={onTransformChange} />
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
47
|
return null;
|
|
151
48
|
};
|
|
152
49
|
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { useRef, useLayoutEffect, useState } from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import ButtonGroup from '@mui/material/ButtonGroup';
|
|
4
|
+
import Slider from '@mui/material/Slider';
|
|
5
|
+
import Typography from '@mui/material/Typography';
|
|
6
|
+
import Box from '@mui/material/Box';
|
|
7
|
+
import * as THREE from 'three';
|
|
8
|
+
import { LngLatLike } from 'maplibre-gl';
|
|
9
|
+
import MlThreeGizmo from './MlThreeGizmo';
|
|
10
|
+
import { useThree } from '../contexts/ThreeContext';
|
|
11
|
+
import ThreejsUtils from '../lib/ThreejsUtils';
|
|
12
|
+
|
|
13
|
+
export interface ThreeObjectControlsProps {
|
|
14
|
+
showLayer: boolean;
|
|
15
|
+
setShowLayer: (show: boolean) => void;
|
|
16
|
+
scale: number;
|
|
17
|
+
setScale: (scale: number) => void;
|
|
18
|
+
rotation: { x: number; y: number; z: number };
|
|
19
|
+
setRotation: (rotation: { x: number; y: number; z: number }) => void;
|
|
20
|
+
mapPosition: { lng: number; lat: number };
|
|
21
|
+
setMapPosition: (position: { lng: number; lat: number }) => void;
|
|
22
|
+
position: { x: number; y: number; z: number };
|
|
23
|
+
setPosition: (position: { x: number; y: number; z: number }) => void;
|
|
24
|
+
enableTransformControls?: boolean;
|
|
25
|
+
setEnableTransformControls?: (enable: boolean) => void;
|
|
26
|
+
transformMode?: 'translate' | 'rotate' | 'scale';
|
|
27
|
+
setTransformMode?: (mode: 'translate' | 'rotate' | 'scale') => void;
|
|
28
|
+
layerName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const MlThreeObjectControls = ({
|
|
32
|
+
showLayer,
|
|
33
|
+
setShowLayer,
|
|
34
|
+
scale,
|
|
35
|
+
setScale,
|
|
36
|
+
rotation,
|
|
37
|
+
setRotation,
|
|
38
|
+
mapPosition,
|
|
39
|
+
setMapPosition,
|
|
40
|
+
position,
|
|
41
|
+
setPosition,
|
|
42
|
+
enableTransformControls,
|
|
43
|
+
setEnableTransformControls,
|
|
44
|
+
transformMode,
|
|
45
|
+
setTransformMode,
|
|
46
|
+
layerName = 'Layer',
|
|
47
|
+
}: ThreeObjectControlsProps) => {
|
|
48
|
+
const { scene, worldMatrixInv } = useThree();
|
|
49
|
+
const dummyMeshRef = useRef<THREE.Mesh | undefined>(undefined);
|
|
50
|
+
const [dummyMeshReady, setDummyMeshReady] = useState(false);
|
|
51
|
+
|
|
52
|
+
// Create and manage dummy mesh for transform controls
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
if (!scene || !worldMatrixInv || !enableTransformControls) {
|
|
55
|
+
// Clean up dummy mesh when controls are disabled
|
|
56
|
+
if (dummyMeshRef.current) {
|
|
57
|
+
scene?.remove(dummyMeshRef.current);
|
|
58
|
+
dummyMeshRef.current.geometry.dispose();
|
|
59
|
+
(dummyMeshRef.current.material as THREE.Material).dispose();
|
|
60
|
+
dummyMeshRef.current = undefined;
|
|
61
|
+
setDummyMeshReady(false);
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create invisible dummy mesh at the model position
|
|
67
|
+
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
68
|
+
const material = new THREE.MeshBasicMaterial({ visible: false });
|
|
69
|
+
const dummyMesh = new THREE.Mesh(geometry, material);
|
|
70
|
+
|
|
71
|
+
// Position the dummy mesh
|
|
72
|
+
const scenePos = ThreejsUtils.toScenePosition(
|
|
73
|
+
worldMatrixInv,
|
|
74
|
+
[mapPosition.lng, mapPosition.lat] as LngLatLike,
|
|
75
|
+
0
|
|
76
|
+
);
|
|
77
|
+
dummyMesh.position.set(
|
|
78
|
+
scenePos.x + position.x,
|
|
79
|
+
scenePos.y + position.y,
|
|
80
|
+
scenePos.z + position.z
|
|
81
|
+
);
|
|
82
|
+
dummyMesh.rotation.set(
|
|
83
|
+
(rotation.x * Math.PI) / 180,
|
|
84
|
+
(rotation.y * Math.PI) / 180,
|
|
85
|
+
(rotation.z * Math.PI) / 180
|
|
86
|
+
);
|
|
87
|
+
dummyMesh.scale.set(scale, scale, scale);
|
|
88
|
+
|
|
89
|
+
scene.add(dummyMesh);
|
|
90
|
+
dummyMeshRef.current = dummyMesh;
|
|
91
|
+
setDummyMeshReady(true);
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
if (dummyMeshRef.current) {
|
|
95
|
+
scene.remove(dummyMeshRef.current);
|
|
96
|
+
dummyMeshRef.current.geometry.dispose();
|
|
97
|
+
(dummyMeshRef.current.material as THREE.Material).dispose();
|
|
98
|
+
dummyMeshRef.current = undefined;
|
|
99
|
+
setDummyMeshReady(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}, [scene, worldMatrixInv, enableTransformControls]);
|
|
103
|
+
|
|
104
|
+
// Update dummy mesh position when props change (but only when controls are enabled)
|
|
105
|
+
useLayoutEffect(() => {
|
|
106
|
+
if (!dummyMeshRef.current || !worldMatrixInv) return;
|
|
107
|
+
|
|
108
|
+
const scenePos = ThreejsUtils.toScenePosition(
|
|
109
|
+
worldMatrixInv,
|
|
110
|
+
[mapPosition.lng, mapPosition.lat] as LngLatLike,
|
|
111
|
+
0
|
|
112
|
+
);
|
|
113
|
+
dummyMeshRef.current.position.set(
|
|
114
|
+
scenePos.x + position.x,
|
|
115
|
+
scenePos.y + position.y,
|
|
116
|
+
scenePos.z + position.z
|
|
117
|
+
);
|
|
118
|
+
dummyMeshRef.current.rotation.set(
|
|
119
|
+
(rotation.x * Math.PI) / 180,
|
|
120
|
+
(rotation.y * Math.PI) / 180,
|
|
121
|
+
(rotation.z * Math.PI) / 180
|
|
122
|
+
);
|
|
123
|
+
dummyMeshRef.current.scale.set(scale, scale, scale);
|
|
124
|
+
dummyMeshRef.current.updateMatrixWorld(true);
|
|
125
|
+
}, [position, rotation, scale, mapPosition, worldMatrixInv]);
|
|
126
|
+
|
|
127
|
+
const handleObjectChange = (object: THREE.Object3D) => {
|
|
128
|
+
if (!worldMatrixInv) return;
|
|
129
|
+
|
|
130
|
+
// Get the base scene position from map coordinates
|
|
131
|
+
const scenePos = ThreejsUtils.toScenePosition(
|
|
132
|
+
worldMatrixInv,
|
|
133
|
+
[mapPosition.lng, mapPosition.lat] as LngLatLike,
|
|
134
|
+
0
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Calculate the offset position (object position - base scene position)
|
|
138
|
+
setPosition({
|
|
139
|
+
x: object.position.x - scenePos.x,
|
|
140
|
+
y: object.position.y - scenePos.y,
|
|
141
|
+
z: object.position.z - scenePos.z,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Update rotation (convert from radians to degrees)
|
|
145
|
+
setRotation({
|
|
146
|
+
x: (object.rotation.x * 180) / Math.PI,
|
|
147
|
+
y: (object.rotation.y * 180) / Math.PI,
|
|
148
|
+
z: (object.rotation.z * 180) / Math.PI,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Update scale (assuming uniform scale)
|
|
152
|
+
setScale(object.scale.x);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<>
|
|
157
|
+
{dummyMeshReady && dummyMeshRef.current && enableTransformControls && (
|
|
158
|
+
<MlThreeGizmo
|
|
159
|
+
target={dummyMeshRef.current}
|
|
160
|
+
mode={transformMode || 'translate'}
|
|
161
|
+
enabled={enableTransformControls}
|
|
162
|
+
onObjectChange={handleObjectChange}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
<Box sx={{ padding: '10px' }}>
|
|
166
|
+
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', marginBottom: 2 }}>
|
|
167
|
+
<Button
|
|
168
|
+
color="primary"
|
|
169
|
+
variant={showLayer ? 'contained' : 'outlined'}
|
|
170
|
+
onClick={() => setShowLayer(!showLayer)}
|
|
171
|
+
size="small"
|
|
172
|
+
>
|
|
173
|
+
{showLayer ? 'Hide' : 'Show'} {layerName}
|
|
174
|
+
</Button>
|
|
175
|
+
{setEnableTransformControls && (
|
|
176
|
+
<Button
|
|
177
|
+
color="info"
|
|
178
|
+
variant={enableTransformControls ? 'contained' : 'outlined'}
|
|
179
|
+
onClick={() => setEnableTransformControls(!enableTransformControls)}
|
|
180
|
+
size="small"
|
|
181
|
+
>
|
|
182
|
+
3D Gizmo
|
|
183
|
+
</Button>
|
|
184
|
+
)}
|
|
185
|
+
</Box>
|
|
186
|
+
|
|
187
|
+
{setTransformMode && enableTransformControls && (
|
|
188
|
+
<Box sx={{ marginBottom: 2 }}>
|
|
189
|
+
<ButtonGroup variant="outlined" size="small" fullWidth aria-label="transform mode">
|
|
190
|
+
<Button
|
|
191
|
+
variant={transformMode === 'translate' ? 'contained' : 'outlined'}
|
|
192
|
+
onClick={() => setTransformMode('translate')}
|
|
193
|
+
>
|
|
194
|
+
Move
|
|
195
|
+
</Button>
|
|
196
|
+
<Button
|
|
197
|
+
variant={transformMode === 'rotate' ? 'contained' : 'outlined'}
|
|
198
|
+
onClick={() => setTransformMode('rotate')}
|
|
199
|
+
>
|
|
200
|
+
Rotate
|
|
201
|
+
</Button>
|
|
202
|
+
<Button
|
|
203
|
+
variant={transformMode === 'scale' ? 'contained' : 'outlined'}
|
|
204
|
+
onClick={() => setTransformMode('scale')}
|
|
205
|
+
>
|
|
206
|
+
Scale
|
|
207
|
+
</Button>
|
|
208
|
+
</ButtonGroup>
|
|
209
|
+
</Box>
|
|
210
|
+
)}
|
|
211
|
+
<Typography gutterBottom>Scale: {scale.toFixed(2)}</Typography>
|
|
212
|
+
<Slider
|
|
213
|
+
value={scale}
|
|
214
|
+
onChange={(e, newValue) => setScale(newValue as number)}
|
|
215
|
+
min={0.01}
|
|
216
|
+
max={150}
|
|
217
|
+
step={0.01}
|
|
218
|
+
valueLabelDisplay="auto"
|
|
219
|
+
/>
|
|
220
|
+
<Typography gutterBottom>Rotation X: {rotation.x}°</Typography>
|
|
221
|
+
<Slider
|
|
222
|
+
value={rotation.x}
|
|
223
|
+
onChange={(e, newValue) => setRotation({ ...rotation, x: newValue as number })}
|
|
224
|
+
min={0}
|
|
225
|
+
max={360}
|
|
226
|
+
valueLabelDisplay="auto"
|
|
227
|
+
/>
|
|
228
|
+
<Typography gutterBottom>Rotation Y: {rotation.y}°</Typography>
|
|
229
|
+
<Slider
|
|
230
|
+
value={rotation.y}
|
|
231
|
+
onChange={(e, newValue) => setRotation({ ...rotation, y: newValue as number })}
|
|
232
|
+
min={0}
|
|
233
|
+
max={360}
|
|
234
|
+
valueLabelDisplay="auto"
|
|
235
|
+
/>
|
|
236
|
+
<Typography gutterBottom>Rotation Z: {rotation.z}°</Typography>
|
|
237
|
+
<Slider
|
|
238
|
+
value={rotation.z}
|
|
239
|
+
onChange={(e, newValue) => setRotation({ ...rotation, z: newValue as number })}
|
|
240
|
+
min={0}
|
|
241
|
+
max={360}
|
|
242
|
+
valueLabelDisplay="auto"
|
|
243
|
+
/>
|
|
244
|
+
<Typography gutterBottom>Longitude: {mapPosition.lng.toFixed(6)}</Typography>
|
|
245
|
+
<Slider
|
|
246
|
+
value={mapPosition.lng}
|
|
247
|
+
onChange={(e, newValue) => setMapPosition({ ...mapPosition, lng: newValue as number })}
|
|
248
|
+
min={7.09}
|
|
249
|
+
max={7.11}
|
|
250
|
+
step={0.0001}
|
|
251
|
+
valueLabelDisplay="auto"
|
|
252
|
+
/>
|
|
253
|
+
<Typography gutterBottom>Latitude: {mapPosition.lat.toFixed(6)}</Typography>
|
|
254
|
+
<Slider
|
|
255
|
+
value={mapPosition.lat}
|
|
256
|
+
onChange={(e, newValue) => setMapPosition({ ...mapPosition, lat: newValue as number })}
|
|
257
|
+
min={50.73}
|
|
258
|
+
max={50.74}
|
|
259
|
+
step={0.0001}
|
|
260
|
+
valueLabelDisplay="auto"
|
|
261
|
+
/>
|
|
262
|
+
<Typography gutterBottom>Position X: {position.x}</Typography>
|
|
263
|
+
<Slider
|
|
264
|
+
value={position.x}
|
|
265
|
+
onChange={(e, newValue) => setPosition({ ...position, x: newValue as number })}
|
|
266
|
+
min={-100}
|
|
267
|
+
max={100}
|
|
268
|
+
valueLabelDisplay="auto"
|
|
269
|
+
/>
|
|
270
|
+
<Typography gutterBottom>Position Y: {position.y}</Typography>
|
|
271
|
+
<Slider
|
|
272
|
+
value={position.y}
|
|
273
|
+
onChange={(e, newValue) => setPosition({ ...position, y: newValue as number })}
|
|
274
|
+
min={-100}
|
|
275
|
+
max={100}
|
|
276
|
+
valueLabelDisplay="auto"
|
|
277
|
+
/>
|
|
278
|
+
<Typography gutterBottom>Position Z: {position.z}</Typography>
|
|
279
|
+
<Slider
|
|
280
|
+
value={position.z}
|
|
281
|
+
onChange={(e, newValue) => setPosition({ ...position, z: newValue as number })}
|
|
282
|
+
min={-500}
|
|
283
|
+
max={100}
|
|
284
|
+
valueLabelDisplay="auto"
|
|
285
|
+
/>
|
|
286
|
+
</Box>
|
|
287
|
+
</>
|
|
288
|
+
);
|
|
289
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { MapComponentsProvider, MapLibreMap, useMap } from '@mapcomponents/react-maplibre';
|
|
3
|
-
import { ThreeProvider } from '
|
|
3
|
+
import { ThreeProvider } from '../../contexts/ThreeProvider';
|
|
4
4
|
import MlThreeSplatLayer from './MlThreeSplatLayer';
|
|
5
5
|
|
|
6
6
|
const MapExposer = () => {
|
|
@@ -20,8 +20,7 @@ const TestComponent = ({ onDone }: { onDone: () => void }) => {
|
|
|
20
20
|
<ThreeProvider id="three-provider" mapId="map_1">
|
|
21
21
|
<MlThreeSplatLayer
|
|
22
22
|
url="assets/splats/output.splat"
|
|
23
|
-
|
|
24
|
-
scale={1}
|
|
23
|
+
position={[13.404954, 52.520008]}
|
|
25
24
|
onDone={onDone}
|
|
26
25
|
/>
|
|
27
26
|
</ThreeProvider>
|