@needle-tools/gltf-progressive 3.6.0-alpha.2 → 3.6.0-beta.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 +7 -0
- package/README.md +42 -8
- package/examples/modelviewer-multiple.html +4 -4
- package/examples/modelviewer.html +4 -4
- package/examples/offscreen/index.html +15 -0
- package/examples/offscreen/main.js +98 -0
- package/examples/react-three-fiber/index.html +2 -2
- package/examples/react-three-fiber/package-lock.json +482 -484
- package/examples/react-three-fiber/package.json +4 -4
- package/examples/react-three-fiber/src/App.tsx +76 -21
- package/examples/react-three-fiber/src/styles.css +2 -4
- package/examples/react-three-fiber/vite.config.js +2 -2
- package/examples/shared/example-utils.js +297 -0
- package/examples/shared/example.css +44 -0
- package/examples/shared/runtime.js +34 -0
- package/examples/threejs/index.html +6 -10
- package/examples/threejs/main.js +5 -23
- package/examples/webgpu/index.html +15 -0
- package/examples/webgpu/main.js +105 -0
- package/examples/worker-rendering/index.html +15 -0
- package/examples/worker-rendering/main.js +109 -0
- package/examples/worker-rendering/worker.js +166 -0
- package/gltf-progressive.js +670 -559
- package/gltf-progressive.min.js +9 -9
- package/gltf-progressive.umd.cjs +9 -9
- package/lib/extension.d.ts +6 -0
- package/lib/extension.js +85 -16
- package/lib/loaders.d.ts +1 -8
- package/lib/loaders.js +15 -2
- package/lib/lods.debug.js +1 -1
- package/lib/lods.manager.d.ts +3 -0
- package/lib/lods.manager.js +62 -18
- package/lib/utils.d.ts +1 -1
- package/lib/utils.internal.d.ts +27 -0
- package/lib/utils.internal.js +68 -25
- package/lib/version.js +1 -1
- package/lib/worker/loader.mainthread.js +6 -4
- package/package.json +8 -3
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
"main": "src/index.tsx",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@needle-tools/gltf-progressive": "file:../..",
|
|
7
|
-
"@react-three/drei": "
|
|
8
|
-
"@react-three/fiber": "
|
|
7
|
+
"@react-three/drei": "9.122.0",
|
|
8
|
+
"@react-three/fiber": "8.18.0",
|
|
9
9
|
"react": "^18.0.0",
|
|
10
10
|
"react-dom": "^18.0.0",
|
|
11
|
-
"three": "0.
|
|
11
|
+
"three": "0.184.0",
|
|
12
12
|
"typescript": "4.7.4"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/react": "18.0.15",
|
|
16
16
|
"@types/react-dom": "17.0.0",
|
|
17
|
-
"@types/three": "0.
|
|
17
|
+
"@types/three": "0.184.0",
|
|
18
18
|
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
|
19
19
|
"@vitejs/plugin-react": "^1.3.0",
|
|
20
20
|
"typescript": "4.1.3",
|
|
@@ -2,37 +2,92 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint-disable */
|
|
4
4
|
import * as React from 'react'
|
|
5
|
+
import * as THREE from 'three'
|
|
5
6
|
import { Canvas, useThree } from '@react-three/fiber'
|
|
7
|
+
import { OrbitControls, useGLTF } from '@react-three/drei'
|
|
8
|
+
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js'
|
|
6
9
|
|
|
7
10
|
import { useNeedleProgressive } from '@needle-tools/gltf-progressive'
|
|
8
|
-
|
|
11
|
+
// @ts-ignore shared plain JS example helpers
|
|
12
|
+
import { MODEL_URLS } from '../../shared/example-utils.js'
|
|
9
13
|
|
|
10
|
-
function
|
|
11
|
-
const { gl } = useThree()
|
|
12
|
-
|
|
14
|
+
function RoomEnvironmentSetup() {
|
|
15
|
+
const { gl, scene } = useThree()
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const pmrem = new THREE.PMREMGenerator(gl)
|
|
19
|
+
const environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture
|
|
20
|
+
scene.environment = environment
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
scene.environment = null
|
|
24
|
+
environment.dispose()
|
|
25
|
+
pmrem.dispose()
|
|
26
|
+
}
|
|
27
|
+
}, [gl, scene])
|
|
28
|
+
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Model({ controlsRef, url }: { controlsRef: React.MutableRefObject<any>, url: string }) {
|
|
33
|
+
const { gl, camera } = useThree()
|
|
13
34
|
const { scene } = useGLTF(url, false, false, (loader) => {
|
|
14
|
-
useNeedleProgressive(loader as any, gl as any)
|
|
35
|
+
useNeedleProgressive(loader as any, gl as any)
|
|
15
36
|
})
|
|
37
|
+
|
|
38
|
+
React.useLayoutEffect(() => {
|
|
39
|
+
const box = new THREE.Box3().setFromObject(scene)
|
|
40
|
+
const size = box.getSize(new THREE.Vector3())
|
|
41
|
+
const center = box.getCenter(new THREE.Vector3())
|
|
42
|
+
const maxSize = Math.max(size.x, size.y, size.z, 0.0001)
|
|
43
|
+
const perspectiveCamera = camera as THREE.PerspectiveCamera
|
|
44
|
+
const distance = maxSize / (2 * Math.tan(THREE.MathUtils.degToRad(perspectiveCamera.fov) / 2)) * 1.55
|
|
45
|
+
const direction = new THREE.Vector3(0.45, 0.35, 1).normalize()
|
|
46
|
+
|
|
47
|
+
perspectiveCamera.position.copy(center).addScaledVector(direction, distance)
|
|
48
|
+
perspectiveCamera.near = Math.max(0.01, distance / 100)
|
|
49
|
+
perspectiveCamera.far = Math.max(100, distance * 100)
|
|
50
|
+
perspectiveCamera.updateProjectionMatrix()
|
|
51
|
+
|
|
52
|
+
const controls = controlsRef.current
|
|
53
|
+
if (controls) {
|
|
54
|
+
controls.target.copy(center)
|
|
55
|
+
controls.minDistance = distance * 0.1
|
|
56
|
+
controls.maxDistance = distance * 10
|
|
57
|
+
controls.update()
|
|
58
|
+
}
|
|
59
|
+
}, [camera, controlsRef, scene])
|
|
60
|
+
|
|
16
61
|
return <primitive object={scene} />
|
|
17
62
|
}
|
|
18
63
|
|
|
19
|
-
|
|
20
64
|
export default function App() {
|
|
65
|
+
const controlsRef = React.useRef<any>(null)
|
|
66
|
+
const [sceneIndex, setSceneIndex] = React.useState(0)
|
|
67
|
+
const modelUrl = MODEL_URLS[sceneIndex % MODEL_URLS.length]
|
|
68
|
+
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
;(globalThis as any).__GLTF_PROGRESSIVE_R3F_EXAMPLE__ = {
|
|
71
|
+
currentUrl: modelUrl,
|
|
72
|
+
sceneIndex,
|
|
73
|
+
}
|
|
74
|
+
}, [modelUrl, sceneIndex])
|
|
75
|
+
|
|
21
76
|
return (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
77
|
+
<>
|
|
78
|
+
<div className="example-toolbar">
|
|
79
|
+
<button type="button" onClick={() => setSceneIndex((sceneIndex + 1) % MODEL_URLS.length)}>Change scene</button>
|
|
80
|
+
</div>
|
|
81
|
+
<Canvas
|
|
82
|
+
camera={{ position: [0.5, 1.3, 2], fov: 60, near: 0.01, far: 200 }}>
|
|
83
|
+
<RoomEnvironmentSetup />
|
|
84
|
+
<gridHelper args={[50, 50, 0x444444, 0x666666]} />
|
|
85
|
+
<directionalLight position={[-50, 20, 50]} intensity={1} />
|
|
86
|
+
<OrbitControls ref={controlsRef} enableDamping dampingFactor={0.08} target={[0, 0.5, 0]} />
|
|
87
|
+
<React.Suspense fallback={null}>
|
|
88
|
+
<Model key={modelUrl} controlsRef={controlsRef} url={modelUrl} />
|
|
89
|
+
</React.Suspense>
|
|
90
|
+
</Canvas>
|
|
91
|
+
</>
|
|
35
92
|
)
|
|
36
93
|
}
|
|
37
|
-
|
|
38
|
-
|
|
@@ -14,7 +14,7 @@ export default defineConfig(async (command) => {
|
|
|
14
14
|
plugins: [
|
|
15
15
|
react(),
|
|
16
16
|
basicSsl(),
|
|
17
|
-
viteCompression({ deleteOriginFile:
|
|
17
|
+
viteCompression({ deleteOriginFile: false }),
|
|
18
18
|
],
|
|
19
19
|
|
|
20
20
|
server: {
|
|
@@ -36,4 +36,4 @@ export default defineConfig(async (command) => {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
export const MODEL_URLS = [
|
|
2
|
+
"https://engine.needle.tools/demos/gltf-progressive/assets/church/model.glb",
|
|
3
|
+
"https://engine.needle.tools/demos/gltf-progressive/assets/putti gruppe/model.glb",
|
|
4
|
+
"https://engine.needle.tools/demos/gltf-progressive/assets/cyberpunk/model.glb",
|
|
5
|
+
"https://engine.needle.tools/demos/gltf-progressive/assets/robot/model.glb",
|
|
6
|
+
"https://engine.needle.tools/demos/gltf-progressive/assets/vase/model.glb",
|
|
7
|
+
"https://engine.needle.tools/demos/gltf-progressive/assets/jupiter_und_ganymed/model.glb",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_MODEL_URL = MODEL_URLS[0];
|
|
11
|
+
|
|
12
|
+
export function createExampleState(label) {
|
|
13
|
+
const state = {
|
|
14
|
+
label,
|
|
15
|
+
done: false,
|
|
16
|
+
ok: false,
|
|
17
|
+
errors: [],
|
|
18
|
+
frames: 0,
|
|
19
|
+
loaded: false,
|
|
20
|
+
renderer: "",
|
|
21
|
+
currentUrl: "",
|
|
22
|
+
sceneIndex: 0,
|
|
23
|
+
sceneLoads: 0,
|
|
24
|
+
progressiveObjects: 0,
|
|
25
|
+
lodChanges: 0,
|
|
26
|
+
lodChangeTypes: [],
|
|
27
|
+
};
|
|
28
|
+
globalThis.__GLTF_PROGRESSIVE_EXAMPLE__ = state;
|
|
29
|
+
|
|
30
|
+
globalThis.addEventListener?.("error", event => {
|
|
31
|
+
state.errors.push(event.message || String(event.error || event));
|
|
32
|
+
});
|
|
33
|
+
globalThis.addEventListener?.("unhandledrejection", event => {
|
|
34
|
+
state.errors.push(event.reason?.message || String(event.reason || event));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function updateStatus(message) {
|
|
41
|
+
const element = globalThis.document?.getElementById("status");
|
|
42
|
+
if (element) element.textContent = message;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function markReady(state, rendererLabel) {
|
|
46
|
+
state.loaded = true;
|
|
47
|
+
state.ok = true;
|
|
48
|
+
state.done = true;
|
|
49
|
+
state.renderer = rendererLabel;
|
|
50
|
+
updateStatus(rendererLabel);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function markError(state, error) {
|
|
54
|
+
const message = error?.stack || error?.message || String(error);
|
|
55
|
+
state.errors.push(message);
|
|
56
|
+
state.ok = false;
|
|
57
|
+
state.done = true;
|
|
58
|
+
updateStatus(message);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getModelUrl(params = new URLSearchParams(globalThis.location?.search || ""), sceneIndex = 0) {
|
|
62
|
+
const asset = params.get("asset");
|
|
63
|
+
if (asset === "minimal") return createMinimalGltfUrl();
|
|
64
|
+
if (asset) return new URL(asset, globalThis.location?.href).href;
|
|
65
|
+
return MODEL_URLS[normalizeSceneIndex(sceneIndex)];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function normalizeSceneIndex(sceneIndex) {
|
|
69
|
+
return ((sceneIndex % MODEL_URLS.length) + MODEL_URLS.length) % MODEL_URLS.length;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getInitialSceneIndex(params = new URLSearchParams(globalThis.location?.search || "")) {
|
|
73
|
+
const scene = Number(params.get("scene"));
|
|
74
|
+
return Number.isFinite(scene) ? normalizeSceneIndex(scene) : 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createSceneChangeButton(onChange) {
|
|
78
|
+
const document = globalThis.document;
|
|
79
|
+
if (!document) return null;
|
|
80
|
+
|
|
81
|
+
const toolbar = document.createElement("div");
|
|
82
|
+
toolbar.className = "example-toolbar";
|
|
83
|
+
const button = document.createElement("button");
|
|
84
|
+
button.type = "button";
|
|
85
|
+
button.textContent = "Change scene";
|
|
86
|
+
button.addEventListener("click", () => onChange());
|
|
87
|
+
toolbar.append(button);
|
|
88
|
+
document.body.append(toolbar);
|
|
89
|
+
return button;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function markSceneLoading(state, sceneIndex, url) {
|
|
93
|
+
state.sceneIndex = normalizeSceneIndex(sceneIndex);
|
|
94
|
+
state.currentUrl = url;
|
|
95
|
+
updateStatus("loading");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function markSceneLoaded(state, runtime, root) {
|
|
99
|
+
state.loaded = true;
|
|
100
|
+
state.sceneLoads += 1;
|
|
101
|
+
state.progressiveObjects = countProgressiveObjects(runtime, root);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function trackLODChanges(lodsManager, state, notify) {
|
|
105
|
+
return lodsManager.addEventListener?.("changed", event => {
|
|
106
|
+
state.lodChanges += 1;
|
|
107
|
+
state.lodChangeTypes.push(event.type);
|
|
108
|
+
if (notify) notify(event);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function countProgressiveObjects(runtime, root) {
|
|
113
|
+
const progressive = runtime.NEEDLE_progressive;
|
|
114
|
+
if (!progressive || !root) return 0;
|
|
115
|
+
|
|
116
|
+
let count = 0;
|
|
117
|
+
root.traverse?.(object => {
|
|
118
|
+
if (object?.isMesh && progressive.hasLODLevelAvailable(object)) count += 1;
|
|
119
|
+
const material = object?.material;
|
|
120
|
+
if (material && progressive.hasLODLevelAvailable(material)) count += 1;
|
|
121
|
+
});
|
|
122
|
+
return count;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function createMinimalGltfUrl() {
|
|
126
|
+
const positions = new Float32Array([
|
|
127
|
+
-0.8, -0.5, 0,
|
|
128
|
+
0.8, -0.5, 0,
|
|
129
|
+
0, 0.8, 0,
|
|
130
|
+
]);
|
|
131
|
+
const normals = new Float32Array([
|
|
132
|
+
0, 0, 1,
|
|
133
|
+
0, 0, 1,
|
|
134
|
+
0, 0, 1,
|
|
135
|
+
]);
|
|
136
|
+
const indices = new Uint16Array([0, 1, 2]);
|
|
137
|
+
const bytes = concatBytes(
|
|
138
|
+
new Uint8Array(positions.buffer),
|
|
139
|
+
new Uint8Array(normals.buffer),
|
|
140
|
+
new Uint8Array(indices.buffer),
|
|
141
|
+
);
|
|
142
|
+
const gltf = {
|
|
143
|
+
asset: { version: "2.0", generator: "gltf-progressive example" },
|
|
144
|
+
scene: 0,
|
|
145
|
+
scenes: [{ nodes: [0] }],
|
|
146
|
+
nodes: [{ mesh: 0, name: "MinimalTriangle" }],
|
|
147
|
+
meshes: [{
|
|
148
|
+
primitives: [{
|
|
149
|
+
attributes: { POSITION: 0, NORMAL: 1 },
|
|
150
|
+
indices: 2,
|
|
151
|
+
material: 0,
|
|
152
|
+
}],
|
|
153
|
+
}],
|
|
154
|
+
materials: [{
|
|
155
|
+
pbrMetallicRoughness: {
|
|
156
|
+
baseColorFactor: [0.2, 0.55, 1, 1],
|
|
157
|
+
roughnessFactor: 0.55,
|
|
158
|
+
metallicFactor: 0,
|
|
159
|
+
},
|
|
160
|
+
}],
|
|
161
|
+
buffers: [{
|
|
162
|
+
uri: `data:application/octet-stream;base64,${base64(bytes)}`,
|
|
163
|
+
byteLength: bytes.byteLength,
|
|
164
|
+
}],
|
|
165
|
+
bufferViews: [
|
|
166
|
+
{ buffer: 0, byteOffset: 0, byteLength: positions.byteLength, target: 34962 },
|
|
167
|
+
{ buffer: 0, byteOffset: positions.byteLength, byteLength: normals.byteLength, target: 34962 },
|
|
168
|
+
{ buffer: 0, byteOffset: positions.byteLength + normals.byteLength, byteLength: indices.byteLength, target: 34963 },
|
|
169
|
+
],
|
|
170
|
+
accessors: [
|
|
171
|
+
{ bufferView: 0, componentType: 5126, count: 3, type: "VEC3", min: [-0.8, -0.5, 0], max: [0.8, 0.8, 0] },
|
|
172
|
+
{ bufferView: 1, componentType: 5126, count: 3, type: "VEC3" },
|
|
173
|
+
{ bufferView: 2, componentType: 5123, count: 3, type: "SCALAR" },
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
return `data:model/gltf+json;charset=utf-8,${encodeURIComponent(JSON.stringify(gltf))}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function setupScene(THREE, width, height) {
|
|
180
|
+
const scene = new THREE.Scene();
|
|
181
|
+
scene.background = new THREE.Color(0x555555);
|
|
182
|
+
|
|
183
|
+
const camera = new THREE.PerspectiveCamera(60, width / height, 0.01, 200);
|
|
184
|
+
camera.position.set(0.5, 1.3, 2);
|
|
185
|
+
|
|
186
|
+
const grid = new THREE.GridHelper(50, 50, 0x444444, 0x666666);
|
|
187
|
+
scene.add(grid);
|
|
188
|
+
|
|
189
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
190
|
+
directionalLight.position.set(-50, 20, 50);
|
|
191
|
+
scene.add(directionalLight);
|
|
192
|
+
|
|
193
|
+
return { scene, camera };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function setupRoomEnvironment(THREE, RoomEnvironment, renderer, scene, options = {}) {
|
|
197
|
+
if (!RoomEnvironment) return () => { };
|
|
198
|
+
const PMREMGenerator = options.PMREMGenerator || THREE.PMREMGenerator;
|
|
199
|
+
const pmremGenerator = new PMREMGenerator(renderer);
|
|
200
|
+
const environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
|
|
201
|
+
scene.environment = environment;
|
|
202
|
+
return () => {
|
|
203
|
+
environment.dispose?.();
|
|
204
|
+
pmremGenerator.dispose();
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function createOrbitControls(OrbitControls, camera, domElement) {
|
|
209
|
+
const controls = new OrbitControls(camera, domElement);
|
|
210
|
+
controls.enableDamping = true;
|
|
211
|
+
controls.dampingFactor = 0.08;
|
|
212
|
+
controls.target.set(0, 0.5, 0);
|
|
213
|
+
controls.update();
|
|
214
|
+
return controls;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function addLoadedScene(THREE, scene, gltf, options = {}) {
|
|
218
|
+
const root = gltf.scene;
|
|
219
|
+
scene.add(root);
|
|
220
|
+
const fit = fitObjectToView(THREE, options.camera, root, options.controls);
|
|
221
|
+
if (options.onFit) options.onFit(fit);
|
|
222
|
+
return root;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function fitObjectToView(THREE, camera, root, controls) {
|
|
226
|
+
if (!camera) return null;
|
|
227
|
+
const box = new THREE.Box3().setFromObject(root);
|
|
228
|
+
const size = box.getSize(new THREE.Vector3());
|
|
229
|
+
const center = box.getCenter(new THREE.Vector3());
|
|
230
|
+
const maxSize = Math.max(size.x, size.y, size.z, 0.0001);
|
|
231
|
+
const distance = maxSize / (2 * Math.tan(THREE.MathUtils.degToRad(camera.fov) / 2)) * 1.55;
|
|
232
|
+
const direction = new THREE.Vector3(0.45, 0.35, 1).normalize();
|
|
233
|
+
|
|
234
|
+
camera.position.copy(center).addScaledVector(direction, distance);
|
|
235
|
+
camera.near = Math.max(0.01, distance / 100);
|
|
236
|
+
camera.far = Math.max(100, distance * 100);
|
|
237
|
+
camera.updateProjectionMatrix();
|
|
238
|
+
|
|
239
|
+
if (controls) {
|
|
240
|
+
controls.target.copy(center);
|
|
241
|
+
controls.minDistance = distance * 0.1;
|
|
242
|
+
controls.maxDistance = distance * 10;
|
|
243
|
+
controls.update();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
target: center.toArray(),
|
|
248
|
+
minDistance: distance * 0.1,
|
|
249
|
+
maxDistance: distance * 10,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function resizeRenderer(renderer, camera, width, height, pixelRatio = 1) {
|
|
254
|
+
renderer.setPixelRatio?.(pixelRatio);
|
|
255
|
+
renderer.setSize(width, height, false);
|
|
256
|
+
camera.aspect = width / height;
|
|
257
|
+
camera.updateProjectionMatrix();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function serializeCamera(camera) {
|
|
261
|
+
return {
|
|
262
|
+
position: camera.position.toArray(),
|
|
263
|
+
quaternion: camera.quaternion.toArray(),
|
|
264
|
+
near: camera.near,
|
|
265
|
+
far: camera.far,
|
|
266
|
+
fov: camera.fov,
|
|
267
|
+
aspect: camera.aspect,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function applyCameraState(camera, state) {
|
|
272
|
+
if (!state) return;
|
|
273
|
+
camera.position.fromArray(state.position);
|
|
274
|
+
camera.quaternion.fromArray(state.quaternion);
|
|
275
|
+
camera.near = state.near;
|
|
276
|
+
camera.far = state.far;
|
|
277
|
+
camera.fov = state.fov;
|
|
278
|
+
camera.aspect = state.aspect;
|
|
279
|
+
camera.updateProjectionMatrix();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function concatBytes(...arrays) {
|
|
283
|
+
const total = arrays.reduce((sum, array) => sum + array.byteLength, 0);
|
|
284
|
+
const result = new Uint8Array(total);
|
|
285
|
+
let offset = 0;
|
|
286
|
+
for (const array of arrays) {
|
|
287
|
+
result.set(array, offset);
|
|
288
|
+
offset += array.byteLength;
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function base64(bytes) {
|
|
294
|
+
let binary = "";
|
|
295
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
296
|
+
return btoa(binary);
|
|
297
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
html,
|
|
2
|
+
body {
|
|
3
|
+
height: 100%;
|
|
4
|
+
margin: 0;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
background: #20242a;
|
|
7
|
+
color: #f5f7fa;
|
|
8
|
+
font-family: system-ui, sans-serif;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
canvas {
|
|
12
|
+
display: block;
|
|
13
|
+
width: 100vw;
|
|
14
|
+
height: 100vh;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.example-status {
|
|
18
|
+
position: fixed;
|
|
19
|
+
left: 12px;
|
|
20
|
+
top: 12px;
|
|
21
|
+
z-index: 10;
|
|
22
|
+
padding: 6px 8px;
|
|
23
|
+
background: rgba(18, 22, 28, 0.78);
|
|
24
|
+
color: #f5f7fa;
|
|
25
|
+
font-size: 12px;
|
|
26
|
+
line-height: 1.3;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.example-toolbar {
|
|
30
|
+
position: fixed;
|
|
31
|
+
right: 12px;
|
|
32
|
+
top: 12px;
|
|
33
|
+
z-index: 10;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.example-toolbar button {
|
|
37
|
+
padding: 6px 9px;
|
|
38
|
+
border: 1px solid rgba(245, 247, 250, 0.35);
|
|
39
|
+
background: rgba(18, 22, 28, 0.78);
|
|
40
|
+
color: #f5f7fa;
|
|
41
|
+
font: inherit;
|
|
42
|
+
font-size: 12px;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const DEFAULT_THREE_VERSION = "0.184.0";
|
|
2
|
+
const DEFAULT_PROGRESSIVE_VERSION = "3.6.0-canary.5401de9";
|
|
3
|
+
|
|
4
|
+
export async function loadRuntime(options = {}) {
|
|
5
|
+
const params = options.params || new URLSearchParams(options.search || globalThis.location?.search || "");
|
|
6
|
+
const runtimeUrl = params.get("runtime");
|
|
7
|
+
if (runtimeUrl) {
|
|
8
|
+
return await import(new URL(runtimeUrl, globalThis.location?.href).href);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const threeVersion = params.get("three") || DEFAULT_THREE_VERSION;
|
|
12
|
+
const threeBase = `https://esm.sh/three@${threeVersion}`;
|
|
13
|
+
const progressiveUrl = params.get("progressive") || `https://esm.sh/@needle-tools/gltf-progressive@${DEFAULT_PROGRESSIVE_VERSION}?deps=three@${threeVersion}`;
|
|
14
|
+
|
|
15
|
+
const [THREE, THREE_WEBGPU, gltfLoaderModule, orbitControlsModule, roomEnvironmentModule, progressiveModule] = await Promise.all([
|
|
16
|
+
import(threeBase),
|
|
17
|
+
import(`${threeBase}/webgpu`),
|
|
18
|
+
import(`${threeBase}/examples/jsm/loaders/GLTFLoader.js`),
|
|
19
|
+
import(`${threeBase}/examples/jsm/controls/OrbitControls.js`),
|
|
20
|
+
import(`${threeBase}/examples/jsm/environments/RoomEnvironment.js`),
|
|
21
|
+
import(progressiveUrl),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
THREE,
|
|
26
|
+
THREE_WEBGPU,
|
|
27
|
+
GLTFLoader: gltfLoaderModule.GLTFLoader,
|
|
28
|
+
OrbitControls: orbitControlsModule.OrbitControls,
|
|
29
|
+
RoomEnvironment: roomEnvironmentModule.RoomEnvironment,
|
|
30
|
+
useNeedleProgressive: progressiveModule.useNeedleProgressive,
|
|
31
|
+
LODsManager: progressiveModule.LODsManager,
|
|
32
|
+
NEEDLE_progressive: progressiveModule.NEEDLE_progressive,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -5,18 +5,14 @@
|
|
|
5
5
|
<meta charset="utf-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
7
|
<title>Threejs Progressive Loading</title>
|
|
8
|
-
<
|
|
9
|
-
body {
|
|
10
|
-
margin: 0;
|
|
11
|
-
}
|
|
12
|
-
</style>
|
|
8
|
+
<link rel="stylesheet" href="../shared/example.css">
|
|
13
9
|
<script type="importmap">
|
|
14
10
|
{
|
|
15
11
|
"imports": {
|
|
16
|
-
"three": "https://cdn.jsdelivr.net/npm/three@
|
|
17
|
-
"three/addons/": "https://cdn.jsdelivr.net/npm/three@
|
|
18
|
-
"three/examples/": "https://cdn.jsdelivr.net/npm/three@
|
|
19
|
-
"@needle-tools/gltf-progressive": "https://cdn.jsdelivr.net/npm/@needle-tools/gltf-progressive/gltf-progressive.min.js"
|
|
12
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.184.0/build/three.module.js",
|
|
13
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.184.0/examples/jsm/",
|
|
14
|
+
"three/examples/": "https://cdn.jsdelivr.net/npm/three@0.184.0/examples/",
|
|
15
|
+
"@needle-tools/gltf-progressive": "https://cdn.jsdelivr.net/npm/@needle-tools/gltf-progressive@3.6.0-canary.5401de9/gltf-progressive.min.js"
|
|
20
16
|
}
|
|
21
17
|
}
|
|
22
18
|
</script>
|
|
@@ -49,4 +45,4 @@
|
|
|
49
45
|
});
|
|
50
46
|
</script>
|
|
51
47
|
|
|
52
|
-
</html>
|
|
48
|
+
</html>
|
package/examples/threejs/main.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
|
|
3
2
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
3
|
+
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
|
|
4
4
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
5
5
|
import { useNeedleProgressive, getRaycastMesh, useRaycastMeshes } from "@needle-tools/gltf-progressive";
|
|
6
|
+
import { fitObjectToView, setupRoomEnvironment } from "../shared/example-utils.js";
|
|
6
7
|
import { Pane } from 'https://cdn.jsdelivr.net/npm/tweakpane@4.0.3/dist/tweakpane.min.js';
|
|
7
8
|
|
|
8
9
|
|
|
@@ -34,6 +35,8 @@ const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
|
34
35
|
directionalLight.position.set(-50, 20, 50);
|
|
35
36
|
scene.add(directionalLight);
|
|
36
37
|
|
|
38
|
+
setupRoomEnvironment(THREE, RoomEnvironment, renderer, scene);
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
// Animate the scene
|
|
39
42
|
function animate() {
|
|
@@ -43,19 +46,6 @@ function animate() {
|
|
|
43
46
|
}
|
|
44
47
|
animate();
|
|
45
48
|
|
|
46
|
-
const environmentTextureUrl = "https://dl.polyhaven.org/file/ph-assets/HDRIs/exr/1k/studio_small_09_1k.exr";
|
|
47
|
-
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
48
|
-
pmremGenerator.compileEquirectangularShader();
|
|
49
|
-
new EXRLoader().load(environmentTextureUrl, texture => {
|
|
50
|
-
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
|
|
51
|
-
scene.environment = envMap;
|
|
52
|
-
texture.dispose();
|
|
53
|
-
pmremGenerator.dispose();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
49
|
const modelUrls = [
|
|
60
50
|
"https://engine.needle.tools/demos/gltf-progressive/assets/putti gruppe/model.glb",
|
|
61
51
|
"https://engine.needle.tools/demos/gltf-progressive/assets/cyberpunk/model.glb",
|
|
@@ -100,15 +90,7 @@ function loadScene() {
|
|
|
100
90
|
currentScene?.removeFromParent();
|
|
101
91
|
currentScene = gltf.scene;
|
|
102
92
|
scene.add(gltf.scene)
|
|
103
|
-
gltf.scene
|
|
104
|
-
|
|
105
|
-
// the church is huge - scaling it down so we don't have a big difference between the models
|
|
106
|
-
if (url.includes("church")) {
|
|
107
|
-
gltf.scene.scale.multiplyScalar(.1);
|
|
108
|
-
}
|
|
109
|
-
else if (url.includes("cyberpunk")) {
|
|
110
|
-
gltf.scene.scale.multiplyScalar(15);
|
|
111
|
-
}
|
|
93
|
+
fitObjectToView(THREE, camera, gltf.scene, orbit);
|
|
112
94
|
|
|
113
95
|
if (gltf.animations?.length) {
|
|
114
96
|
console.log("Playing animation", gltf.animations)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<link rel="icon" href="data:,">
|
|
7
|
+
<link rel="stylesheet" href="../shared/example.css">
|
|
8
|
+
<title>glTF Progressive WebGPU</title>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<canvas id="view"></canvas>
|
|
12
|
+
<div id="status" class="example-status">loading</div>
|
|
13
|
+
<script type="module" src="./main.js"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|