@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
|
@@ -5,10 +5,7 @@ import Link from '@mui/material/Link';
|
|
|
5
5
|
import MlThreeSplatLayer from './MlThreeSplatLayer';
|
|
6
6
|
import { useMap, TopToolbar, Sidebar } from '@mapcomponents/react-maplibre';
|
|
7
7
|
import MlThreeJsContextDecorator from '../../decorators/ThreejsContextDecorator';
|
|
8
|
-
import {
|
|
9
|
-
import { useThree } from '../ThreeContext';
|
|
10
|
-
import ThreejsUtils from '../../lib/ThreejsUtils';
|
|
11
|
-
import * as THREE from 'three';
|
|
8
|
+
import { MlThreeObjectControls } from '../MlThreeObjectControls';
|
|
12
9
|
|
|
13
10
|
const storyoptions = {
|
|
14
11
|
title: 'MapComponents/MlThreeSplatLayer',
|
|
@@ -25,17 +22,14 @@ const storyoptions = {
|
|
|
25
22
|
export default storyoptions;
|
|
26
23
|
|
|
27
24
|
const Template: any = () => {
|
|
28
|
-
const { worldMatrix } = useThree();
|
|
29
25
|
const [showLayer, setShowLayer] = useState(true);
|
|
30
26
|
const [scale, setScale] = useState(100);
|
|
31
27
|
const [rotation, setRotation] = useState({ x: 270, y: 0, z: 5 });
|
|
32
|
-
const [useMapCoords, setUseMapCoords] = useState(true);
|
|
33
28
|
const [mapPosition, setMapPosition] = useState({ lng: 7.0968, lat: 50.736 });
|
|
34
|
-
const [
|
|
35
|
-
const [
|
|
29
|
+
const [position, setPosition] = useState({ x: 0, y: 0, z: 30 });
|
|
30
|
+
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
36
31
|
const [enableTransformControls, setEnableTransformControls] = useState(false);
|
|
37
32
|
const [transformMode, setTransformMode] = useState<'translate' | 'rotate' | 'scale'>('translate');
|
|
38
|
-
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
39
33
|
|
|
40
34
|
const mapHook = useMap({ mapId: 'map_1' });
|
|
41
35
|
useEffect(() => {
|
|
@@ -45,57 +39,23 @@ const Template: any = () => {
|
|
|
45
39
|
mapHook.map?.setCenter([7.096614581535903, 50.736500960686556]);
|
|
46
40
|
}, [mapHook.map]);
|
|
47
41
|
|
|
48
|
-
// Center map on position when switching coordinate modes
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
if (!mapHook.map) return;
|
|
51
|
-
if (useMapCoords) {
|
|
52
|
-
mapHook.map.setCenter([mapPosition.lng, mapPosition.lat]);
|
|
53
|
-
}
|
|
54
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
-
}, [useMapCoords, mapHook.map]);
|
|
56
|
-
|
|
57
|
-
const handleTransformChange = (object: THREE.Object3D) => {
|
|
58
|
-
setRotation({
|
|
59
|
-
x: (object.rotation.x * 180) / Math.PI,
|
|
60
|
-
y: (object.rotation.y * 180) / Math.PI,
|
|
61
|
-
z: (object.rotation.z * 180) / Math.PI,
|
|
62
|
-
});
|
|
63
|
-
setScale(object.scale.x);
|
|
64
|
-
|
|
65
|
-
if (useMapCoords && worldMatrix) {
|
|
66
|
-
const [lng, lat, alt] = ThreejsUtils.toMapPosition(worldMatrix, object.position);
|
|
67
|
-
setMapPosition({ lng, lat });
|
|
68
|
-
setAltitude(parseFloat(alt.toFixed(2)));
|
|
69
|
-
} else {
|
|
70
|
-
setPosition({ x: object.position.x, y: object.position.y, z: object.position.z });
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
42
|
return (
|
|
75
43
|
<>
|
|
76
44
|
{showLayer && (
|
|
77
45
|
<MlThreeSplatLayer
|
|
78
46
|
url="assets/splats/output.splat"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
47
|
+
position={[mapPosition.lng, mapPosition.lat]}
|
|
48
|
+
transform={{
|
|
49
|
+
rotation: {
|
|
50
|
+
x: (rotation.x * Math.PI) / 180,
|
|
51
|
+
y: (rotation.y * Math.PI) / 180,
|
|
52
|
+
z: (rotation.z * Math.PI) / 180,
|
|
53
|
+
},
|
|
54
|
+
scale: scale,
|
|
55
|
+
position: position,
|
|
83
56
|
}}
|
|
84
|
-
scale={scale}
|
|
85
|
-
enableTransformControls={enableTransformControls}
|
|
86
|
-
transformMode={transformMode}
|
|
87
|
-
onTransformChange={handleTransformChange}
|
|
88
|
-
{...(useMapCoords
|
|
89
|
-
? {
|
|
90
|
-
mapPosition: [mapPosition.lng, mapPosition.lat],
|
|
91
|
-
altitude: altitude,
|
|
92
|
-
}
|
|
93
|
-
: {
|
|
94
|
-
position: position,
|
|
95
|
-
})}
|
|
96
57
|
/>
|
|
97
58
|
)}
|
|
98
|
-
|
|
99
59
|
<TopToolbar
|
|
100
60
|
unmovableButtons={
|
|
101
61
|
<Button
|
|
@@ -107,26 +67,22 @@ const Template: any = () => {
|
|
|
107
67
|
}
|
|
108
68
|
/>
|
|
109
69
|
<Sidebar open={sidebarOpen} setOpen={setSidebarOpen} name="Splat Config">
|
|
110
|
-
<
|
|
70
|
+
<MlThreeObjectControls
|
|
111
71
|
showLayer={showLayer}
|
|
112
72
|
setShowLayer={setShowLayer}
|
|
113
73
|
scale={scale}
|
|
114
74
|
setScale={setScale}
|
|
115
75
|
rotation={rotation}
|
|
116
76
|
setRotation={setRotation}
|
|
117
|
-
useMapCoords={useMapCoords}
|
|
118
|
-
setUseMapCoords={setUseMapCoords}
|
|
119
77
|
mapPosition={mapPosition}
|
|
120
78
|
setMapPosition={setMapPosition}
|
|
121
|
-
altitude={altitude}
|
|
122
|
-
setAltitude={setAltitude}
|
|
123
79
|
position={position}
|
|
124
80
|
setPosition={setPosition}
|
|
81
|
+
layerName="Splat"
|
|
125
82
|
enableTransformControls={enableTransformControls}
|
|
126
83
|
setEnableTransformControls={setEnableTransformControls}
|
|
127
84
|
transformMode={transformMode}
|
|
128
85
|
setTransformMode={setTransformMode}
|
|
129
|
-
layerName="Splat"
|
|
130
86
|
/>
|
|
131
87
|
<Typography
|
|
132
88
|
variant="body2"
|
|
@@ -1,157 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
import { LngLatLike } from 'maplibre-gl';
|
|
4
|
-
import { useThree } from '../ThreeContext';
|
|
1
|
+
import { useMemo } from 'react';
|
|
5
2
|
import { SplatLoader } from '../../lib/splats/loaders/SplatLoader';
|
|
6
3
|
import { PlySplatLoader } from '../../lib/splats/loaders/PlySplatLoader';
|
|
7
|
-
import
|
|
8
|
-
import MlTransformControls from '../MlTransformControls';
|
|
4
|
+
import { useThreeModel, UseThreeModelProps, ModelLoader } from '../../hooks/useThreeModel';
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
|
-
* Renders splat 3D Models on the MapLibreMap
|
|
7
|
+
* Renders splat 3D Models on the MapLibreMap
|
|
12
8
|
*
|
|
13
9
|
* @component
|
|
14
10
|
*/
|
|
15
11
|
|
|
16
|
-
export
|
|
12
|
+
export type MlThreeSplatLayerProps = 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 MlThreeSplatLayer = (props: MlThreeSplatLayerProps) => {
|
|
32
|
-
const {
|
|
17
|
+
const { url, position, transform, init, onDone, customLoaders } = props;
|
|
18
|
+
|
|
19
|
+
const loaders = useMemo<Record<string, ModelLoader>>(
|
|
20
|
+
() => ({
|
|
21
|
+
splat: (url, onLoad) => {
|
|
22
|
+
const loader = new SplatLoader();
|
|
23
|
+
loader.load(url, (splatMesh) => onLoad(splatMesh));
|
|
24
|
+
},
|
|
25
|
+
ply: (url, onLoad) => {
|
|
26
|
+
const loader = new PlySplatLoader();
|
|
27
|
+
loader.load(url, (splatMesh) => onLoad(splatMesh));
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
[]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
useThreeModel({
|
|
33
34
|
url,
|
|
34
35
|
position,
|
|
35
|
-
|
|
36
|
-
altitude,
|
|
37
|
-
rotation,
|
|
38
|
-
scale,
|
|
39
|
-
enableTransformControls,
|
|
40
|
-
transformMode,
|
|
41
|
-
onTransformChange,
|
|
36
|
+
transform,
|
|
42
37
|
init,
|
|
43
38
|
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
|
-
object.updateMatrixWorld(true);
|
|
91
|
-
|
|
92
|
-
modelRef.current = object;
|
|
93
|
-
scene.add(object);
|
|
94
|
-
setModel(object);
|
|
95
|
-
if (typeof onDoneRef.current === 'function') {
|
|
96
|
-
onDoneRef.current();
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (extension === 'splat') {
|
|
101
|
-
const loader = new SplatLoader();
|
|
102
|
-
loader.load(url, (splatMesh) => {
|
|
103
|
-
onLoad(splatMesh);
|
|
104
|
-
});
|
|
105
|
-
} else if (extension === 'ply') {
|
|
106
|
-
const loader = new PlySplatLoader();
|
|
107
|
-
loader.load(url, (splatMesh) => {
|
|
108
|
-
onLoad(splatMesh);
|
|
109
|
-
});
|
|
110
|
-
} else {
|
|
111
|
-
console.warn('MlThreeSplatLayer: Unsupported file extension', extension);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return () => {
|
|
115
|
-
if (modelRef.current) {
|
|
116
|
-
scene.remove(modelRef.current);
|
|
117
|
-
if ('dispose' in modelRef.current && typeof modelRef.current.dispose === 'function') {
|
|
118
|
-
(modelRef.current as any).dispose();
|
|
119
|
-
}
|
|
120
|
-
modelRef.current = undefined;
|
|
121
|
-
setModel(undefined);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
}, [scene, url]);
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (!model) return;
|
|
128
|
-
|
|
129
|
-
// Handle position: mapPosition takes precedence over position
|
|
130
|
-
if (mapPosition && worldMatrixInv) {
|
|
131
|
-
const scenePos = ThreejsUtils.toScenePosition(worldMatrixInv, mapPosition, altitude);
|
|
132
|
-
model.position.set(scenePos.x, scenePos.y, scenePos.z);
|
|
133
|
-
} else if (position) {
|
|
134
|
-
model.position.set(position.x, position.y, position.z);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (rotation) {
|
|
138
|
-
model.rotation.set(rotation.x, rotation.y, rotation.z);
|
|
139
|
-
}
|
|
140
|
-
if (scale) {
|
|
141
|
-
if (typeof scale === 'number') {
|
|
142
|
-
model.scale.set(scale, scale, scale);
|
|
143
|
-
} else {
|
|
144
|
-
model.scale.set(scale.x, scale.y, scale.z);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
model.updateMatrixWorld(true);
|
|
148
|
-
}, [model, position, mapPosition, altitude, rotation, scale, worldMatrixInv]);
|
|
39
|
+
loaders,
|
|
40
|
+
customLoaders,
|
|
41
|
+
});
|
|
149
42
|
|
|
150
|
-
if (enableTransformControls && model) {
|
|
151
|
-
return (
|
|
152
|
-
<MlTransformControls target={model} mode={transformMode} onObjectChange={onTransformChange} />
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
43
|
return null;
|
|
156
44
|
};
|
|
157
45
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { LngLatLike } from 'maplibre-gl';
|
|
4
|
+
import { useThree } from '../contexts/ThreeContext';
|
|
5
|
+
import ThreejsUtils from '../lib/ThreejsUtils';
|
|
6
|
+
|
|
7
|
+
export interface ThreeModelTransform {
|
|
8
|
+
rotation?: { x: number; y: number; z: number };
|
|
9
|
+
scale?: { x: number; y: number; z: number } | number;
|
|
10
|
+
position?: { x: number; y: number; z: number };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ModelLoader = (url: string, onSuccess: (object: THREE.Object3D) => void) => void;
|
|
14
|
+
|
|
15
|
+
export interface UseThreeModelProps {
|
|
16
|
+
url: string;
|
|
17
|
+
position: LngLatLike;
|
|
18
|
+
transform?: ThreeModelTransform;
|
|
19
|
+
init?: () => void;
|
|
20
|
+
onDone?: () => void;
|
|
21
|
+
loaders: Record<string, ModelLoader>;
|
|
22
|
+
customLoaders?: Record<string, ModelLoader>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Recursively dispose of Three.js object resources to prevent memory leaks.
|
|
27
|
+
*/
|
|
28
|
+
const disposeObject = (obj: THREE.Object3D): void => {
|
|
29
|
+
if ((obj as any).geometry) {
|
|
30
|
+
(obj as any).geometry.dispose();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ((obj as any).material) {
|
|
34
|
+
const material = (obj as any).material;
|
|
35
|
+
if (Array.isArray(material)) {
|
|
36
|
+
material.forEach((m) => m.dispose());
|
|
37
|
+
} else {
|
|
38
|
+
material.dispose();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if ('dispose' in obj && typeof (obj as any).dispose === 'function') {
|
|
43
|
+
(obj as any).dispose();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Hook to manage loading, transforming, and rendering a 3D model in the MapLibre/Three.js context.
|
|
49
|
+
*/
|
|
50
|
+
export const useThreeModel = (props: UseThreeModelProps) => {
|
|
51
|
+
const { url, position, transform, init, onDone, loaders, customLoaders } = props;
|
|
52
|
+
const { scene, worldMatrixInv } = useThree();
|
|
53
|
+
const [model, setModel] = useState<THREE.Object3D | undefined>(undefined);
|
|
54
|
+
const modelRef = useRef<THREE.Object3D | undefined>(undefined);
|
|
55
|
+
|
|
56
|
+
const initRef = useRef(init);
|
|
57
|
+
const onDoneRef = useRef(onDone);
|
|
58
|
+
initRef.current = init;
|
|
59
|
+
onDoneRef.current = onDone;
|
|
60
|
+
|
|
61
|
+
const transformRef = useRef({ position, transform });
|
|
62
|
+
transformRef.current = { position, transform };
|
|
63
|
+
const worldMatrixInvRef = useRef(worldMatrixInv);
|
|
64
|
+
worldMatrixInvRef.current = worldMatrixInv;
|
|
65
|
+
|
|
66
|
+
const allLoaders = useMemo(() => ({ ...loaders, ...customLoaders }), [loaders, customLoaders]);
|
|
67
|
+
|
|
68
|
+
const updateModelTransform = useCallback(
|
|
69
|
+
(object: THREE.Object3D, currentWorldMatrixInv: THREE.Matrix4 | undefined) => {
|
|
70
|
+
const { position: currentPosition, transform: currentTransform } = transformRef.current;
|
|
71
|
+
|
|
72
|
+
if (currentPosition && currentWorldMatrixInv) {
|
|
73
|
+
const scenePos = ThreejsUtils.toScenePosition(currentWorldMatrixInv, currentPosition, 0);
|
|
74
|
+
object.position.set(scenePos.x, scenePos.y, scenePos.z);
|
|
75
|
+
|
|
76
|
+
if (currentTransform?.position) {
|
|
77
|
+
object.position.x += currentTransform.position.x;
|
|
78
|
+
object.position.y += currentTransform.position.y;
|
|
79
|
+
object.position.z += currentTransform.position.z;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (currentTransform?.rotation) {
|
|
84
|
+
object.rotation.set(
|
|
85
|
+
currentTransform.rotation.x,
|
|
86
|
+
currentTransform.rotation.y,
|
|
87
|
+
currentTransform.rotation.z
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (currentTransform?.scale) {
|
|
92
|
+
if (typeof currentTransform.scale === 'number') {
|
|
93
|
+
object.scale.set(currentTransform.scale, currentTransform.scale, currentTransform.scale);
|
|
94
|
+
} else {
|
|
95
|
+
object.scale.set(
|
|
96
|
+
currentTransform.scale.x,
|
|
97
|
+
currentTransform.scale.y,
|
|
98
|
+
currentTransform.scale.z
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
object.updateMatrixWorld(true);
|
|
104
|
+
},
|
|
105
|
+
[]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const cleanup = useCallback(() => {
|
|
109
|
+
if (modelRef.current && scene) {
|
|
110
|
+
scene.remove(modelRef.current);
|
|
111
|
+
modelRef.current.traverse(disposeObject);
|
|
112
|
+
disposeObject(modelRef.current);
|
|
113
|
+
modelRef.current = undefined;
|
|
114
|
+
setModel(undefined);
|
|
115
|
+
}
|
|
116
|
+
}, [scene]);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!scene) return;
|
|
120
|
+
|
|
121
|
+
if (typeof initRef.current === 'function') {
|
|
122
|
+
initRef.current();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let extension = '';
|
|
126
|
+
try {
|
|
127
|
+
const urlObj = new URL(url, window.location.origin);
|
|
128
|
+
extension = urlObj.pathname.split('.').pop()?.toLowerCase() || '';
|
|
129
|
+
} catch (e) {
|
|
130
|
+
extension = url.split('.').pop()?.toLowerCase() || '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const loader = allLoaders[extension];
|
|
134
|
+
if (!loader) {
|
|
135
|
+
console.warn(
|
|
136
|
+
`useThreeModel: No loader found for file extension "${extension}". Supported extensions: ${Object.keys(allLoaders).join(', ')}`
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let isCanceled = false;
|
|
142
|
+
|
|
143
|
+
const handleLoad = (object: THREE.Object3D) => {
|
|
144
|
+
if (isCanceled) {
|
|
145
|
+
object.traverse(disposeObject);
|
|
146
|
+
disposeObject(object);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (modelRef.current) {
|
|
151
|
+
cleanup();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
modelRef.current = object;
|
|
155
|
+
updateModelTransform(object, worldMatrixInvRef.current);
|
|
156
|
+
scene.add(object);
|
|
157
|
+
setModel(object);
|
|
158
|
+
|
|
159
|
+
if (typeof onDoneRef.current === 'function') {
|
|
160
|
+
onDoneRef.current();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
loader(url, handleLoad);
|
|
165
|
+
|
|
166
|
+
return () => {
|
|
167
|
+
isCanceled = true;
|
|
168
|
+
cleanup();
|
|
169
|
+
};
|
|
170
|
+
}, [url, scene, allLoaders, cleanup, updateModelTransform]);
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (model) {
|
|
174
|
+
updateModelTransform(model, worldMatrixInv);
|
|
175
|
+
}
|
|
176
|
+
}, [model, position, transform, worldMatrixInv, updateModelTransform]);
|
|
177
|
+
|
|
178
|
+
return model;
|
|
179
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './lib/ThreejsUtils';
|
|
2
2
|
export * from './lib/ThreejsSceneHelper';
|
|
3
3
|
export * from './lib/ThreejsSceneRenderer';
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
4
|
+
export * from './contexts/ThreeContext';
|
|
5
|
+
export * from './contexts/ThreeProvider';
|
|
6
6
|
export { default as MlThreeModelLayer } from './components/MlThreeModelLayer/MlThreeModelLayer';
|
|
7
7
|
export { default as MlThreeSplatLayer } from './components/MlThreeSplatLayer/MlThreeSplatLayer';
|
|
8
|
+
export { default as MlThreeGizmo } from './components/MlThreeGizmo';
|
|
9
|
+
export { MlThreeObjectControls } from './components/MlThreeObjectControls';
|
package/vite.config.ts
CHANGED
|
@@ -26,7 +26,7 @@ export default defineConfig(() => ({
|
|
|
26
26
|
// Configuration for building your library.
|
|
27
27
|
// See: https://vitejs.dev/guide/build.html#library-mode
|
|
28
28
|
build: {
|
|
29
|
-
outDir: '
|
|
29
|
+
outDir: 'dist',
|
|
30
30
|
emptyOutDir: true,
|
|
31
31
|
reportCompressedSize: true,
|
|
32
32
|
commonjsOptions: {
|
|
@@ -39,7 +39,7 @@ export default defineConfig(() => ({
|
|
|
39
39
|
fileName: 'index',
|
|
40
40
|
// Change this to the formats you want to support.
|
|
41
41
|
// Don't forget to update your package.json as well.
|
|
42
|
-
formats: ['es' as const],
|
|
42
|
+
formats: ['es' as const, 'cjs' as const],
|
|
43
43
|
},
|
|
44
44
|
rollupOptions: {
|
|
45
45
|
// External packages that should not be bundled into your library.
|