@needle-tools/gltf-progressive 3.6.0-canary.5401de9 → 3.6.0

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.
@@ -4,17 +4,17 @@
4
4
  "main": "src/index.tsx",
5
5
  "dependencies": {
6
6
  "@needle-tools/gltf-progressive": "file:../..",
7
- "@react-three/drei": "^9.0.1",
8
- "@react-three/fiber": "^8.0.6",
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.162.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.162.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",
@@ -8,8 +8,8 @@ import { OrbitControls, useGLTF } from '@react-three/drei'
8
8
  import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js'
9
9
 
10
10
  import { useNeedleProgressive } from '@needle-tools/gltf-progressive'
11
-
12
- const modelUrl = 'https://cloud.needle.tools/-/assets/Z23hmXBZ21QnG-Yt9m7-world/the-forgotten-knight-baked.glb'
11
+ // @ts-ignore shared plain JS example helpers
12
+ import { MODEL_URLS } from '../../shared/example-utils.js'
13
13
 
14
14
  function RoomEnvironmentSetup() {
15
15
  const { gl, scene } = useThree()
@@ -29,9 +29,9 @@ function RoomEnvironmentSetup() {
29
29
  return null
30
30
  }
31
31
 
32
- function Model({ controlsRef }: { controlsRef: React.MutableRefObject<any> }) {
32
+ function Model({ controlsRef, url }: { controlsRef: React.MutableRefObject<any>, url: string }) {
33
33
  const { gl, camera } = useThree()
34
- const { scene } = useGLTF(modelUrl, false, false, (loader) => {
34
+ const { scene } = useGLTF(url, false, false, (loader) => {
35
35
  useNeedleProgressive(loader as any, gl as any)
36
36
  })
37
37
 
@@ -63,17 +63,31 @@ function Model({ controlsRef }: { controlsRef: React.MutableRefObject<any> }) {
63
63
 
64
64
  export default function App() {
65
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])
66
75
 
67
76
  return (
68
- <Canvas
69
- camera={{ position: [0.5, 1.3, 2], fov: 60, near: 0.01, far: 200 }}>
70
- <RoomEnvironmentSetup />
71
- <gridHelper args={[50, 50, 0x444444, 0x666666]} />
72
- <directionalLight position={[-50, 20, 50]} intensity={1} />
73
- <OrbitControls ref={controlsRef} enableDamping dampingFactor={0.08} target={[0, 0.5, 0]} />
74
- <React.Suspense fallback={null}>
75
- <Model controlsRef={controlsRef} />
76
- </React.Suspense>
77
- </Canvas>
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
+ </>
78
92
  )
79
93
  }
@@ -1,4 +1,13 @@
1
- export const DEFAULT_MODEL_URL = "https://cloud.needle.tools/-/assets/Z23hmXBZ21QnG-Yt9m7-world/the-forgotten-knight-baked.glb";
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];
2
11
 
3
12
  export function createExampleState(label) {
4
13
  const state = {
@@ -9,6 +18,12 @@ export function createExampleState(label) {
9
18
  frames: 0,
10
19
  loaded: false,
11
20
  renderer: "",
21
+ currentUrl: "",
22
+ sceneIndex: 0,
23
+ sceneLoads: 0,
24
+ progressiveObjects: 0,
25
+ lodChanges: 0,
26
+ lodChangeTypes: [],
12
27
  };
13
28
  globalThis.__GLTF_PROGRESSIVE_EXAMPLE__ = state;
14
29
 
@@ -43,11 +58,68 @@ export function markError(state, error) {
43
58
  updateStatus(message);
44
59
  }
45
60
 
46
- export function getModelUrl(params = new URLSearchParams(globalThis.location?.search || "")) {
61
+ export function getModelUrl(params = new URLSearchParams(globalThis.location?.search || ""), sceneIndex = 0) {
47
62
  const asset = params.get("asset");
48
63
  if (asset === "minimal") return createMinimalGltfUrl();
49
64
  if (asset) return new URL(asset, globalThis.location?.href).href;
50
- return DEFAULT_MODEL_URL;
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;
51
123
  }
52
124
 
53
125
  export function createMinimalGltfUrl() {
@@ -25,3 +25,20 @@ canvas {
25
25
  font-size: 12px;
26
26
  line-height: 1.3;
27
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
+ }
@@ -1,5 +1,5 @@
1
1
  const DEFAULT_THREE_VERSION = "0.184.0";
2
- const DEFAULT_PROGRESSIVE_VERSION = "3.6.0-canary.027a4c9";
2
+ const DEFAULT_PROGRESSIVE_VERSION = "3.6.0-canary.5401de9";
3
3
 
4
4
  export async function loadRuntime(options = {}) {
5
5
  const params = options.params || new URLSearchParams(options.search || globalThis.location?.search || "");
@@ -29,5 +29,6 @@ export async function loadRuntime(options = {}) {
29
29
  RoomEnvironment: roomEnvironmentModule.RoomEnvironment,
30
30
  useNeedleProgressive: progressiveModule.useNeedleProgressive,
31
31
  LODsManager: progressiveModule.LODsManager,
32
+ NEEDLE_progressive: progressiveModule.NEEDLE_progressive,
32
33
  };
33
34
  }
@@ -9,10 +9,10 @@
9
9
  <script type="importmap">
10
10
  {
11
11
  "imports": {
12
- "three": "https://cdn.jsdelivr.net/npm/three@latest/build/three.module.js",
13
- "three/addons/": "https://cdn.jsdelivr.net/npm/three@latest/examples/jsm/",
14
- "three/examples/": "https://cdn.jsdelivr.net/npm/three@latest/examples/",
15
- "@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"
16
16
  }
17
17
  }
18
18
  </script>
@@ -3,16 +3,23 @@ import {
3
3
  addLoadedScene,
4
4
  createOrbitControls,
5
5
  createExampleState,
6
+ createSceneChangeButton,
7
+ getInitialSceneIndex,
6
8
  getModelUrl,
7
9
  markError,
8
10
  markReady,
11
+ markSceneLoaded,
12
+ markSceneLoading,
13
+ normalizeSceneIndex,
9
14
  resizeRenderer,
10
15
  setupScene,
11
16
  setupRoomEnvironment,
17
+ trackLODChanges,
12
18
  updateStatus,
13
19
  } from "../shared/example-utils.js";
14
20
 
15
21
  const state = createExampleState("webgpu");
22
+ const params = new URLSearchParams(location.search);
16
23
 
17
24
  try {
18
25
  const runtime = await loadRuntime();
@@ -32,20 +39,45 @@ try {
32
39
  });
33
40
  const controls = createOrbitControls(runtime.OrbitControls, camera, canvas);
34
41
 
35
- const loader = new runtime.GLTFLoader();
36
- runtime.useNeedleProgressive(loader, renderer);
42
+ let root = null;
43
+ let sceneIndex = getInitialSceneIndex(params);
44
+ let loadToken = 0;
45
+ let rendererLabel = "";
37
46
 
38
- const root = await new Promise((resolve, reject) => {
39
- loader.load(getModelUrl(), gltf => resolve(addLoadedScene(THREE, scene, gltf, { camera, controls })), undefined, reject);
40
- });
47
+ const firstLoader = new runtime.GLTFLoader();
48
+ const lodsManager = runtime.useNeedleProgressive(firstLoader, renderer);
49
+ trackLODChanges(lodsManager, state);
41
50
 
42
51
  window.addEventListener("resize", () => {
43
52
  resizeRenderer(renderer, camera, window.innerWidth, window.innerHeight, window.devicePixelRatio);
44
53
  });
45
54
 
55
+ async function loadScene(nextIndex) {
56
+ const token = ++loadToken;
57
+ sceneIndex = normalizeSceneIndex(nextIndex);
58
+ const url = getModelUrl(params, sceneIndex);
59
+ markSceneLoading(state, sceneIndex, url);
60
+
61
+ const loader = token === 1 ? firstLoader : new runtime.GLTFLoader();
62
+ if (token !== 1) runtime.useNeedleProgressive(loader, renderer);
63
+
64
+ const nextRoot = await new Promise((resolve, reject) => {
65
+ loader.load(url, gltf => resolve(addLoadedScene(THREE, scene, gltf, { camera, controls })), undefined, reject);
66
+ });
67
+
68
+ if (token !== loadToken) {
69
+ nextRoot.removeFromParent();
70
+ return;
71
+ }
72
+
73
+ root?.removeFromParent();
74
+ root = nextRoot;
75
+ markSceneLoaded(state, runtime, root);
76
+ if (rendererLabel) markReady(state, rendererLabel);
77
+ }
78
+
46
79
  function render() {
47
80
  state.frames += 1;
48
- root.rotation.y += 0.01;
49
81
  controls.update();
50
82
  renderer.render(scene, camera);
51
83
  requestAnimationFrame(render);
@@ -53,7 +85,10 @@ try {
53
85
 
54
86
  const backend = getBackendType(renderer);
55
87
  if (backend !== "webgpu") throw new Error(`Expected WebGPU backend, got ${backend}.`);
56
- markReady(state, backend);
88
+ rendererLabel = backend;
89
+ createSceneChangeButton(() => loadScene(sceneIndex + 1).catch(error => markError(state, error)));
90
+ await loadScene(sceneIndex);
91
+ markReady(state, rendererLabel);
57
92
  render();
58
93
  }
59
94
  catch (error) {
@@ -3,6 +3,7 @@ import {
3
3
  applyCameraState,
4
4
  createExampleState,
5
5
  createOrbitControls,
6
+ createSceneChangeButton,
6
7
  markError,
7
8
  markReady,
8
9
  serializeCamera,
@@ -10,6 +11,8 @@ import {
10
11
  } from "../shared/example-utils.js";
11
12
 
12
13
  const state = createExampleState("worker-rendering");
14
+ const params = new URLSearchParams(location.search);
15
+ const rendererMode = params.get("renderer") === "webgpu" ? "webgpu" : "webgl";
13
16
 
14
17
  try {
15
18
  if (!globalThis.OffscreenCanvas) throw new Error("OffscreenCanvas is not available in this browser.");
@@ -36,9 +39,20 @@ try {
36
39
  sendCamera();
37
40
  markReady(state, message.renderer || "worker-webgl");
38
41
  }
42
+ else if (message.type === "scene-loaded") {
43
+ state.loaded = true;
44
+ state.currentUrl = message.url || "";
45
+ state.sceneIndex = message.sceneIndex || 0;
46
+ state.sceneLoads = message.sceneLoads || state.sceneLoads + 1;
47
+ state.progressiveObjects = message.progressiveObjects || 0;
48
+ }
39
49
  else if (message.type === "frame") {
40
50
  state.frames = message.frames;
41
51
  }
52
+ else if (message.type === "lod-change") {
53
+ state.lodChanges += 1;
54
+ state.lodChangeTypes.push(message.lodType);
55
+ }
42
56
  else if (message.type === "error") {
43
57
  markError(state, message.message || "Worker rendering failed.");
44
58
  }
@@ -67,6 +81,9 @@ try {
67
81
  }
68
82
 
69
83
  controls.addEventListener("change", sendCamera);
84
+ createSceneChangeButton(() => {
85
+ worker.postMessage({ type: "change-scene" });
86
+ });
70
87
 
71
88
  function updateControls() {
72
89
  controls.update();
@@ -82,9 +99,10 @@ try {
82
99
  pixelRatio: window.devicePixelRatio || 1,
83
100
  camera: serializeCamera(camera),
84
101
  search: location.search,
102
+ renderer: rendererMode,
85
103
  }, [offscreenCanvas]);
86
104
  window.addEventListener("resize", postResize);
87
- updateStatus("worker-webgl");
105
+ updateStatus(rendererMode === "webgpu" ? "worker-webgpu" : "worker-webgl");
88
106
  }
89
107
  catch (error) {
90
108
  markError(state, error);
@@ -2,20 +2,44 @@ import { loadRuntime } from "../shared/runtime.js";
2
2
  import {
3
3
  addLoadedScene,
4
4
  applyCameraState,
5
+ getInitialSceneIndex,
5
6
  getModelUrl,
7
+ markSceneLoaded,
8
+ markSceneLoading,
9
+ normalizeSceneIndex,
6
10
  resizeRenderer,
7
11
  serializeCamera,
8
12
  setupScene,
9
13
  setupRoomEnvironment,
14
+ trackLODChanges,
10
15
  } from "../shared/example-utils.js";
11
16
 
12
17
  let renderer;
13
18
  let camera;
19
+ let scene;
14
20
  let root;
21
+ let runtime;
22
+ let THREE;
23
+ let lodsManager;
24
+ let rendererMode = "webgl";
25
+ let rendererLabel = "worker-webgl";
15
26
  let frames = 0;
16
27
  let width = 1;
17
28
  let height = 1;
18
29
  let pixelRatio = 1;
30
+ let params = new URLSearchParams();
31
+ let sceneIndex = 0;
32
+ let sceneLoads = 0;
33
+ let loadToken = 0;
34
+ const workerState = {
35
+ loaded: false,
36
+ currentUrl: "",
37
+ sceneIndex: 0,
38
+ sceneLoads: 0,
39
+ progressiveObjects: 0,
40
+ lodChanges: 0,
41
+ lodChangeTypes: [],
42
+ };
19
43
 
20
44
  self.addEventListener("message", event => {
21
45
  const message = event.data || {};
@@ -34,44 +58,109 @@ self.addEventListener("message", event => {
34
58
  else if (message.type === "camera") {
35
59
  if (camera && message.camera) applyCameraState(camera, message.camera);
36
60
  }
61
+ else if (message.type === "change-scene") {
62
+ loadScene(sceneIndex + 1).catch(error => {
63
+ self.postMessage({ type: "error", message: error?.stack || error?.message || String(error) });
64
+ });
65
+ }
37
66
  });
38
67
 
39
68
  async function init(message) {
40
69
  width = message.width || width;
41
70
  height = message.height || height;
42
71
  pixelRatio = message.pixelRatio || pixelRatio;
72
+ rendererMode = message.renderer === "webgpu" ? "webgpu" : "webgl";
73
+ rendererLabel = rendererMode === "webgpu" ? "worker-webgpu" : "worker-webgl";
43
74
 
44
- const params = new URLSearchParams(message.search || "");
45
- const runtime = await loadRuntime({ params });
46
- const THREE = runtime.THREE;
75
+ params = new URLSearchParams(message.search || "");
76
+ sceneIndex = getInitialSceneIndex(params);
77
+ runtime = await loadRuntime({ params });
78
+ THREE = runtime.THREE;
47
79
 
48
- renderer = new THREE.WebGLRenderer({ canvas: message.canvas, antialias: true });
80
+ renderer = await createRenderer(message.canvas);
49
81
  const setup = setupScene(THREE, width, height);
50
- const scene = setup.scene;
82
+ scene = setup.scene;
51
83
  camera = setup.camera;
52
84
  resizeRenderer(renderer, camera, width, height, pixelRatio);
53
85
  if (message.camera) applyCameraState(camera, message.camera);
54
- setupRoomEnvironment(THREE, runtime.RoomEnvironment, renderer, scene);
86
+ setupRoomEnvironment(THREE, runtime.RoomEnvironment, renderer, scene, rendererMode === "webgpu" ? {
87
+ PMREMGenerator: runtime.THREE_WEBGPU.PMREMGenerator,
88
+ } : undefined);
89
+
90
+ const firstLoad = await loadScene(sceneIndex);
91
+ self.postMessage({ type: "ready", renderer: rendererLabel, camera: serializeCamera(camera), fit: firstLoad?.fit });
92
+
93
+ const requestFrame = callback => setTimeout(() => callback(performance.now()), 16);
94
+ function render() {
95
+ frames += 1;
96
+ renderer.render(scene, camera);
97
+ if (frames === 1 || frames % 10 === 0) self.postMessage({ type: "frame", frames });
98
+ requestFrame(render);
99
+ }
100
+ render();
101
+ }
102
+
103
+ async function createRenderer(canvas) {
104
+ if (rendererMode === "webgpu") {
105
+ if (!self.navigator?.gpu) throw new Error("WebGPU is not available in this worker.");
106
+ const WebGPURenderer = runtime.THREE.WebGPURenderer || runtime.THREE_WEBGPU.WebGPURenderer;
107
+ if (!WebGPURenderer) throw new Error("WebGPURenderer is not available in this Three.js runtime.");
108
+ const webgpuRenderer = new WebGPURenderer({ canvas, antialias: true });
109
+ await webgpuRenderer.init?.();
110
+ const backend = getBackendType(webgpuRenderer);
111
+ if (backend !== "webgpu") throw new Error(`Expected Worker WebGPU backend, got ${backend}.`);
112
+ return webgpuRenderer;
113
+ }
114
+ return new THREE.WebGLRenderer({ canvas, antialias: true });
115
+ }
116
+
117
+ function getBackendType(renderer) {
118
+ const backend = renderer.backend;
119
+ if (backend?.isWebGPUBackend === true) return "webgpu";
120
+ if (backend?.isWebGLBackend === true) return "webgl";
121
+ if (renderer.isWebGPURenderer === true) return "webgpu";
122
+ return backend?.constructor?.name || renderer.constructor?.name || "unknown";
123
+ }
124
+
125
+ async function loadScene(nextIndex) {
126
+ const token = ++loadToken;
127
+ sceneIndex = normalizeSceneIndex(nextIndex);
128
+ const url = getModelUrl(params, sceneIndex);
129
+ markSceneLoading(workerState, sceneIndex, url);
55
130
 
56
131
  const loader = new runtime.GLTFLoader();
57
- runtime.useNeedleProgressive(loader, renderer);
132
+ lodsManager = runtime.useNeedleProgressive(loader, renderer);
133
+ if (sceneLoads === 0) {
134
+ trackLODChanges(lodsManager, workerState, event => {
135
+ self.postMessage({ type: "lod-change", lodType: event.type, level: event.level });
136
+ });
137
+ }
138
+
58
139
  let fit = null;
59
- root = await new Promise((resolve, reject) => {
60
- loader.load(getModelUrl(params), gltf => resolve(addLoadedScene(THREE, scene, gltf, {
140
+ const nextRoot = await new Promise((resolve, reject) => {
141
+ loader.load(url, gltf => resolve(addLoadedScene(THREE, scene, gltf, {
61
142
  camera,
62
143
  onFit: result => fit = result,
63
144
  })), undefined, reject);
64
145
  });
65
146
 
66
- self.postMessage({ type: "ready", renderer: "worker-webgl", camera: serializeCamera(camera), fit });
67
-
68
- const requestFrame = self.requestAnimationFrame || (callback => setTimeout(() => callback(performance.now()), 16));
69
- function render() {
70
- frames += 1;
71
- root.rotation.y += 0.01;
72
- renderer.render(scene, camera);
73
- if (frames % 10 === 0) self.postMessage({ type: "frame", frames });
74
- requestFrame(render);
147
+ if (token !== loadToken) {
148
+ nextRoot.removeFromParent();
149
+ return null;
75
150
  }
76
- render();
151
+
152
+ root?.removeFromParent();
153
+ root = nextRoot;
154
+ sceneLoads += 1;
155
+ markSceneLoaded(workerState, runtime, root);
156
+ workerState.sceneLoads = sceneLoads;
157
+
158
+ self.postMessage({
159
+ type: "scene-loaded",
160
+ url,
161
+ sceneIndex,
162
+ sceneLoads,
163
+ progressiveObjects: workerState.progressiveObjects,
164
+ });
165
+ return { fit };
77
166
  }