@needle-tools/engine 4.16.4-next.90e872e → 4.16.4-next.d7334fa

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/gltf-progressive-BJ9OrddA.js +28272 -0
  3. package/dist/gltf-progressive-Ck_bXBr_.umd.cjs +4022 -0
  4. package/dist/gltf-progressive-CqZYu6Hx.min.js +4022 -0
  5. package/dist/loader.worker-CCrD-Ycm.js +3 -0
  6. package/dist/{materialx-CT8J50pg.js → materialx-CEJOMiVq.js} +6 -7
  7. package/dist/{materialx-BrPdNmQT.umd.cjs → materialx-CN_PDqtU.umd.cjs} +1 -1
  8. package/dist/{materialx-D_ofN96q.min.js → materialx-DZYSWLvT.min.js} +1 -1
  9. package/dist/{needle-engine.bundle-dWi9rwQU.umd.cjs → needle-engine.bundle-7jhrheqM.umd.cjs} +159 -159
  10. package/dist/{needle-engine.bundle-j_dgrIWR.min.js → needle-engine.bundle-B1vy0_5d.min.js} +137 -137
  11. package/dist/{needle-engine.bundle-CTDEd5SB.js → needle-engine.bundle-BVUJloKV.js} +4192 -4185
  12. package/dist/needle-engine.d.ts +59 -53
  13. package/dist/needle-engine.js +3 -3
  14. package/dist/needle-engine.min.js +1 -1
  15. package/dist/needle-engine.umd.cjs +1 -1
  16. package/dist/three-examples.js +3294 -4643
  17. package/dist/three-examples.min.js +13 -47
  18. package/dist/three-examples.umd.cjs +12 -46
  19. package/dist/three.js +24892 -15999
  20. package/dist/three.min.js +214 -214
  21. package/dist/three.umd.cjs +208 -208
  22. package/dist/{vendor-DZ45lcA8.min.js → vendor-BPp9F5vR.min.js} +19 -19
  23. package/dist/{vendor-BsRxp-FT.js → vendor-CQMI3jTS.js} +862 -901
  24. package/dist/{vendor-BwxpsdCm.umd.cjs → vendor-CipoooTV.umd.cjs} +20 -20
  25. package/lib/engine/engine_feature_flags.d.ts +3 -3
  26. package/lib/engine/engine_feature_flags.js +3 -5
  27. package/lib/engine/engine_feature_flags.js.map +1 -1
  28. package/lib/engine/engine_lods.d.ts +9 -1
  29. package/lib/engine/engine_lods.js +9 -1
  30. package/lib/engine/engine_lods.js.map +1 -1
  31. package/lib/engine/engine_mainloop_utils.js +49 -60
  32. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  33. package/lib/engine/engine_modules.d.ts +26 -30
  34. package/lib/engine/engine_modules.js +32 -103
  35. package/lib/engine/engine_modules.js.map +1 -1
  36. package/lib/engine/engine_three_utils.js +7 -13
  37. package/lib/engine/engine_three_utils.js.map +1 -1
  38. package/lib/engine/webcomponents/needle-engine.d.ts +6 -2
  39. package/lib/engine/webcomponents/needle-engine.js +56 -41
  40. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  41. package/package.json +3 -2
  42. package/plugins/vite/alias.d.ts +1 -1
  43. package/plugins/vite/alias.js +87 -82
  44. package/plugins/vite/build-pipeline.js +4 -3
  45. package/plugins/vite/dependencies.js +7 -5
  46. package/plugins/vite/drop.js +6 -8
  47. package/plugins/vite/editor-connection.js +2 -1
  48. package/plugins/vite/index.js +12 -0
  49. package/plugins/vite/poster.js +2 -1
  50. package/plugins/vite/reload.js +7 -2
  51. package/src/engine/engine_feature_flags.ts +3 -7
  52. package/src/engine/engine_lods.ts +10 -2
  53. package/src/engine/engine_mainloop_utils.ts +54 -62
  54. package/src/engine/engine_modules.ts +39 -101
  55. package/src/engine/engine_three_utils.ts +7 -15
  56. package/src/engine/webcomponents/needle-engine.ts +62 -47
  57. package/dist/gltf-progressive-DQa78GTA.min.js +0 -10
  58. package/dist/gltf-progressive-LOFTyzy4.umd.cjs +0 -10
  59. package/dist/gltf-progressive-_wvokUUu.js +0 -1528
  60. package/dist/loader.worker-BqODMeeW.js +0 -23
@@ -1,50 +1,46 @@
1
- import { createWriteStream, existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
1
+ import { createWriteStream, existsSync, mkdirSync } from 'fs';
2
2
  import path from 'path';
3
3
  import { needleLog } from './logging.js';
4
4
 
5
5
  const projectDir = process.cwd() + "/";
6
6
 
7
7
  /**
8
- * @typedef {"auto-resolve" | ((res: string, packageName: string, index: number, path: string) => string | null | void)} PackageResolveValue
8
+ * @typedef {"auto-resolve" | ((res: string, packageName: string, index: number, importPath: string) => string | null | void)} PackageResolveValue
9
9
  */
10
10
 
11
- /** these are alias callbacks as in the vite.alias dictionary
12
- * the first argument is the already resoled absolute path (it is only invoked if the path was found in node_modules)
13
- * the 2,3,4 args are the same as in vite.alias (packageName, index, path);
14
- */
15
11
  /**
16
12
  * @type {Record<string, PackageResolveValue>}
17
13
  */
18
14
  const packages_to_resolve = {
19
15
  // We are currently overriding "three" resolution to ensure that all dependencies resolve to the same three.js version.
20
- // This is hacky, but the alternative is potentially having conflicting three.js versions since some packages are
16
+ // This is hacky, but the alternative is potentially having conflicting three.js versions since some packages are
21
17
  // stubborn with their peer dependencies or just slow (slower as we) with updating.
22
18
  // NOT adding this allows node.js to correctly resolve `exports` specified in three.js package.json;
23
19
  // since we're overriding resolution here we need to manually resolve the subset of exports that we use.
24
- 'three/addons': (res, packageName, index, path) => {
20
+ 'three/addons': (res, packageName, index, importPath) => {
25
21
  return "three/examples/jsm";
26
22
  },
27
- 'three/nodes': (res, packageName, index, path) => {
23
+ 'three/nodes': (res, packageName, index, importPath) => {
28
24
  return "three/examples/jsm/nodes/Nodes.js";
29
25
  },
30
26
  'three': "auto-resolve",
31
27
 
32
28
  // Handle all previous imports where users did import using @needle-engine/src
33
- '@needle-tools/engine/src': (res, packageName, index, path) => {
29
+ '@needle-tools/engine/src': (res, packageName, index, importPath) => {
34
30
  // resolve old engine/src imports UNLESS it's the asap plugin (the asap plugin currently only exists in the src folder)
35
- if (!path.startsWith("@needle-tools/engine/src/asap")) {
31
+ if (!importPath.startsWith("@needle-tools/engine/src/asap")) {
36
32
  return res + "/../lib";
37
33
  }
38
34
  },
39
- /*
40
- Removed the previously present @needle-tools/engine entry
35
+ /*
36
+ Removed the previously present @needle-tools/engine entry
41
37
  because this is automatically done by vite according to whatever we define in our package.json exports
42
38
  This did previously prevent us from declaring proper exports in package.json
43
39
  */
44
- '@needle-tools/engine': (res, packageName, index, _path) => {
40
+ '@needle-tools/engine': (res, packageName, index, importPath) => {
45
41
  // Check if the import is something like @needle-tools/engine/engine/engine_utils
46
42
  // in which case we want to resolve into the lib directory
47
- if (_path.startsWith("@needle-tools/engine/engine")) {
43
+ if (importPath.startsWith("@needle-tools/engine/engine")) {
48
44
  return res + "/lib";
49
45
  }
50
46
  const node_modules_path = path.resolve(projectDir, 'node_modules', '@needle-tools/engine');
@@ -60,10 +56,6 @@ const packages_to_resolve = {
60
56
  'three-mesh-bvh': "auto-resolve",
61
57
  'postprocessing': "auto-resolve",
62
58
  '@dimforge/rapier3d-compat': "auto-resolve",
63
-
64
- // Note: this isnt necessary anymore since we exclude needle-engine from optimization when locally installed in the dev server
65
- // '@needle-tools/gltf-progressive': "auto-resolve",
66
- // '@needle-tools/materialx': "auto-resolve",
67
59
  }
68
60
 
69
61
  /**
@@ -98,32 +90,91 @@ export function needleViteAlias(command, config, userSettings) {
98
90
  log("Logging to:", outputFilePath);
99
91
  }
100
92
 
93
+ // Pre-compute resolved paths for each package
94
+ /** @type {Array<{name: string, fullpath: string, pathExists: boolean, isNestedInEngine: boolean, callback: ((res: string, packageName: string, index: number, importPath: string) => string | null | void) | null}>} */
95
+ const resolvers = [];
96
+ /** @type {Array<{find: string, replacement: string}>} Aliases for nested engine auto-resolve packages */
97
+ const nestedAliases = [];
98
+ for (const name in packages_to_resolve) {
99
+ const value = packages_to_resolve[name];
100
+ const callback = typeof value === "function" ? value : null;
101
+ const isAutoResolve = value === "auto-resolve";
102
+
103
+ let fullpath = path.resolve(projectDir, 'node_modules', name);
104
+ let isNestedInEngine = false;
105
+ {
106
+ const pathInEngine = path.resolve(projectDir, 'node_modules', "@needle-tools/engine", "node_modules", name);
107
+ if (existsSync(pathInEngine)) {
108
+ fullpath = pathInEngine;
109
+ isNestedInEngine = true;
110
+ }
111
+ }
112
+ const pathExists = existsSync(fullpath);
113
+ resolvers.push({ name, fullpath, pathExists, isNestedInEngine, callback });
114
+
115
+ // For auto-resolve packages nested in the engine, use resolve.alias so both
116
+ // the optimizer and resolveId see the same path. This ensures Vite pre-bundles
117
+ // these deps (handling CJS interop) while keeping a single copy.
118
+ if (isAutoResolve && isNestedInEngine && pathExists) {
119
+ nestedAliases.push({ find: name, replacement: fullpath });
120
+ }
121
+ }
122
+
123
+ const workingDirectory = `${process.cwd()}/`;
101
124
 
102
125
  /** @type {import("vite").Plugin} */
103
126
  const aliasPlugin = {
104
127
  name: "needle-alias",
128
+ // Register resolve.alias for nested engine packages so Vite's optimizer
129
+ // can find and pre-bundle them (with proper CJS interop).
105
130
  config(config) {
106
- if (debug) log('ProjectDirectory: ' + projectDir);
107
- if (!config.resolve) config.resolve = {};
108
- if (!config.resolve.alias) config.resolve.alias = {};
109
- const aliasDict = config.resolve.alias;
110
-
111
- for (const name in packages_to_resolve) {
112
- if (!aliasDict[name]) {
113
- addPathResolver(name, aliasDict, packages_to_resolve[name], debug ?? false);
131
+ if (nestedAliases.length > 0) {
132
+ if (!config.resolve) config.resolve = {};
133
+ // resolve.alias can be an array of {find,replacement} or a Record<string,string>.
134
+ // Convert to array format if needed (works in Vite 2-8).
135
+ if (!config.resolve.alias) {
136
+ config.resolve.alias = [];
137
+ }
138
+ else if (!Array.isArray(config.resolve.alias)) {
139
+ // Convert Record<string,string> to array format
140
+ const entries = Object.entries(config.resolve.alias);
141
+ config.resolve.alias = entries.map(([find, replacement]) => ({ find, replacement }));
142
+ }
143
+ for (const alias of nestedAliases) {
144
+ config.resolve.alias.push(alias);
145
+ needleLog("needle-alias", `Adding resolve.alias: ${alias.find} → ${alias.replacement.split('node_modules/').pop()}`);
114
146
  }
115
147
  }
148
+ },
149
+ // Use resolveId instead of resolve.alias to support both Rollup and Rolldown (Vite 8+).
150
+ // Rolldown does not support function-based alias replacements.
151
+ enforce: 'pre',
152
+ async resolveId(id, _importer, _options) {
153
+ for (const { name, fullpath, pathExists, isNestedInEngine, callback } of resolvers) {
154
+ // Check if import matches this resolver (exact match or starts with name/)
155
+ if (id !== name && !id.startsWith(name + '/')) continue;
156
+
157
+ if (callback !== null) {
158
+ const overrideResult = callback(fullpath, name, 0, id);
159
+ if (typeof overrideResult === "string") {
160
+ if (debug) log(`Redirecting "${id}" → "${overrideResult}"`);
161
+ // Let Vite resolve the result further (handles directories, package.json exports, etc.)
162
+ return this.resolve(overrideResult, _importer, { skipSelf: true });
163
+ }
164
+ }
116
165
 
117
-
118
- if (debug) {
119
- const testResults = [];
120
- for (const name in aliasDict) {
121
- const entry = aliasDict[name];
122
- let res = entry;
123
- if (typeof entry === "function") res = entry(name, 0, name);
124
- testResults.push({ name, entry: res });
166
+ // For auto-resolve packages in the project's own node_modules,
167
+ // let Vite handle resolution naturally (preserves CJS interop / optimizeDeps).
168
+ // Only redirect when the package is nested inside @needle-tools/engine/node_modules.
169
+ if (pathExists && (callback !== null || isNestedInEngine)) {
170
+ if (debug) log(`Resolved "${id}" → "${fullpath.substring(workingDirectory.length).replaceAll("\\", "/")}"`);
171
+ // Rewrite the import to resolve from the target directory, letting Vite handle package.json/exports
172
+ const subpath = id.substring(name.length); // e.g. "" or "/something"
173
+ const rewritten = fullpath + subpath;
174
+ return this.resolve(rewritten, _importer, { skipSelf: true });
125
175
  }
126
- log('Aliases: ' + JSON.stringify(testResults));
176
+
177
+ break;
127
178
  }
128
179
  },
129
180
  }
@@ -145,9 +196,6 @@ export function needleViteAlias(command, config, userSettings) {
145
196
  if (importer.includes("node_modules/.vite")) importer = ".vite" + importer.split("node_modules/.vite")[1];
146
197
  }
147
198
 
148
- // could filter here, e.g. for things related to three
149
- // if (id.includes("three")) return;
150
-
151
199
  // verbose logging for all imports
152
200
  if (lastImporter !== importer) {
153
201
  lastImporter = importer;
@@ -159,47 +207,4 @@ export function needleViteAlias(command, config, userSettings) {
159
207
  }
160
208
  if (debug) return [debuggingPlugin, aliasPlugin];
161
209
  return [aliasPlugin];
162
-
163
-
164
- /**
165
- * Adds a path resolver to the alias dictionary.
166
- * @param {string} name - The name of the package to resolve.
167
- * @param {import("vite").AliasOptions} aliasDict - The alias dictionary to add the resolver to.
168
- * @param {PackageResolveValue | null} value - A callback function to override the default resolution behavior.
169
- * @param {boolean} debug - Whether to log debug information.
170
- * @returns {void}
171
- */
172
- function addPathResolver(name, aliasDict, value, debug) {
173
- // If a package at the node_modules path exist we resolve the request there
174
- // introduced in 89a50718c38940abb99ee16c5e029065e41d7d65
175
- const callback = typeof value === "function" ? value : null;
176
-
177
- let fullpath = path.resolve(projectDir, 'node_modules', name);
178
- // if (!existsSync(path.resolve(fullpath, "package.json")))
179
- {
180
- const pathInEngine = path.resolve(projectDir, 'node_modules', "@needle-tools/engine", "node_modules", name);
181
- if (existsSync(pathInEngine)) {
182
- fullpath = pathInEngine;
183
- }
184
- }
185
-
186
- const workingDirectory = `${process.cwd()}/`;
187
- const pathExists = existsSync(fullpath);
188
-
189
- aliasDict[name] = (packageName, index, path) => {
190
- if (callback !== null) {
191
- const overrideResult = callback(fullpath, packageName, index, path);
192
- if (typeof overrideResult === "string")
193
- if (existsSync(overrideResult)) {
194
- if (debug && overrideResult !== packageName) log(`[needle-alias] Resolved \"${path}\" → \"${overrideResult.substring(workingDirectory.length).replaceAll("\\", "/")}\"`);
195
- return overrideResult;
196
- }
197
- }
198
-
199
- if (pathExists) {
200
- if (debug && path !== packageName) log(`[needle-alias] Resolved \"${path}\" → \"${fullpath.substring(workingDirectory.length).replaceAll("\\", "/")}\"`);
201
- return fullpath;
202
- }
203
- }
204
- }
205
210
  }
@@ -130,9 +130,10 @@ export async function needleBuildPipeline(command, config, userSettings) {
130
130
  if (verboseOutput) {
131
131
  log("Apply:", env);
132
132
  }
133
- // Don't run for SSR builds (e.g. sveltekit).
134
- // Unfortunately this is always falls in vite 4.3 so we can not rely on it solely
135
- if (env.ssrBuild) return false;
133
+ // Don't run for SSR builds (e.g. sveltekit).
134
+ // Unfortunately this is always false in vite 4.3 so we can not rely on it solely
135
+ // Vite 8+ renamed ssrBuild to isSsrBuild
136
+ if (env.isSsrBuild ?? env.ssrBuild) return false;
136
137
  // Dont run if there's already a build pipeline task running
137
138
  if (env.command === "build") {
138
139
  increaseMaxWaitTime(verboseOutput);
@@ -103,14 +103,16 @@ function handleManualChunks(config) {
103
103
  if (!config.build) {
104
104
  config.build = {};
105
105
  }
106
- if (!config.build.rollupOptions) {
107
- config.build.rollupOptions = {};
106
+ // Support both rolldownOptions (Vite 8+) and rollupOptions (Vite 7 and earlier)
107
+ const optionsKey = 'rolldownOptions' in (config.build) ? 'rolldownOptions' : 'rollupOptions';
108
+ if (!config.build[optionsKey]) {
109
+ config.build[optionsKey] = {};
108
110
  }
109
- if (!config.build.rollupOptions.output) {
110
- config.build.rollupOptions.output = {};
111
+ if (!config.build[optionsKey].output) {
112
+ config.build[optionsKey].output = {};
111
113
  }
112
114
 
113
- const rollupOutput = config.build.rollupOptions.output;
115
+ const rollupOutput = config.build[optionsKey].output;
114
116
 
115
117
  if (Array.isArray(rollupOutput)) {
116
118
  // append the manualChunks function to the array
@@ -42,16 +42,14 @@ export function needleDrop(command, config, userSettings) {
42
42
  ];
43
43
  },
44
44
  },
45
- configureServer(/** @type {{ ws: { on(event: string, cb: (...args: unknown[]) => void): void, send(type: string, data?: unknown): void } }} */ server) {
45
+ configureServer(/** @type {import('vite').ViteDevServer} */ server) {
46
+ const hot = server.hot ?? server.ws;
46
47
 
47
- server.ws.on('needle:drop-file', async (/** @type {unknown} */ data, /** @type {unknown} */ client) => {
48
- server.ws.send('needle-editor:drop-file', data);
48
+ hot.on('needle:drop-file', async (/** @type {unknown} */ data, /** @type {unknown} */ client) => {
49
+ hot.send('needle-editor:drop-file', data);
49
50
  });
50
- // TODO: not sure how we can receive it with the normal vite server
51
- // server.ws.on("custom", (data, client) => {
52
- // console.log(data);
53
- // })
54
51
 
52
+ // Raw WebSocket access for editor messages
55
53
  server.ws.on('connection', (/** @type {{ on(event: string, cb: (...args: unknown[]) => void): void }} */ socket, /** @type {unknown} */ request) => {
56
54
  socket.on('message', async (/** @type {Buffer | string} */ bytes) => {
57
55
  try {
@@ -59,7 +57,7 @@ export function needleDrop(command, config, userSettings) {
59
57
  if (message && message.startsWith("{")) {
60
58
  const obj = /** @type {{ type: string, data: unknown }} */ (JSON.parse(message));
61
59
  if (obj.type === "needle-editor:exported-file") {
62
- server.ws.send(obj.type, obj.data);
60
+ hot.send(obj.type, obj.data);
63
61
  }
64
62
  }
65
63
  }
@@ -86,8 +86,9 @@ function createPlugin(isInstalled) {
86
86
  },
87
87
 
88
88
  configureServer(server) {
89
- try
89
+ try
90
90
  {
91
+ // Raw WebSocket access needed for direct socket communication
91
92
  server.ws.on('connection', (socket, _request) => {
92
93
 
93
94
  // console.log("Send editor sync status: " + isInstalled);
@@ -158,5 +158,17 @@ export async function needlePlugins(command, config = undefined, userSettings =
158
158
 
159
159
  array.push(await editorConnection(command, config, userSettings, array));
160
160
  array.push(needleDependencyWatcher(command, config, userSettings));
161
+
162
+ // Ensure the process exits on SIGINT (Ctrl+C) since plugin timers/sockets can keep the event loop alive
163
+ if (command === "serve") {
164
+ process.on('SIGINT', () => {
165
+ // Wait a moment to allow plugins to clean up if needed, then exit
166
+ setTimeout(() => {
167
+ console.debug('\nGoodbye!');
168
+ process.exit();
169
+ }, 1000);
170
+ });
171
+ }
172
+
161
173
  return array;
162
174
  }
@@ -24,7 +24,8 @@ export function needlePoster(command, config, userSettings) {
24
24
  return {
25
25
  name: 'needle:poster',
26
26
  configureServer(server) {
27
- server.ws.on('needle:screenshot', async (data, client) => {
27
+ const hot = server.hot ?? server.ws;
28
+ hot.on('needle:screenshot', async (data, client) => {
28
29
  if (userSettings.noPoster) return;
29
30
  if (!data?.data) {
30
31
  console.warn("Received empty screenshot data, ignoring");
@@ -112,10 +112,15 @@ const posterPath = getPosterPath();
112
112
  let reloadIsScheduled = false;
113
113
  const lockFileName = "needle.lock";
114
114
 
115
+ /** @param {import('vite').ViteDevServer} server */
116
+ function getHot(server) {
117
+ return server.hot ?? server.ws;
118
+ }
119
+
115
120
  /** @param {import('vite').ViteDevServer} server @param {string} [file] */
116
121
  function notifyClientWillReload(server, file) {
117
122
  console.log("Send reload notification");
118
- server.ws.send('needle:reload', { type: 'will-reload', file: file });
123
+ getHot(server).send('needle:reload', { type: 'will-reload', file: file });
119
124
  }
120
125
 
121
126
  /**
@@ -236,7 +241,7 @@ async function scheduleReload(server, level = 0) {
236
241
  lastReloadTime = Date.now();
237
242
  const readableTime = new Date(lastReloadTime).toLocaleTimeString();
238
243
  console.log("< Reloading... " + readableTime)
239
- server.ws.send({
244
+ getHot(server).send({
240
245
  type: 'full-reload',
241
246
  path: '*'
242
247
  });
@@ -1,8 +1,4 @@
1
1
 
2
-
3
- export namespace NEEDLE_ENGINE_FEATURE_FLAGS {
4
-
5
- // eslint-disable-next-line prefer-const
6
- export let experimentalSmartHierarchyUpdate = false;
7
-
8
- }
2
+ export const NEEDLE_ENGINE_FEATURE_FLAGS = {
3
+ experimentalSmartHierarchyUpdate: false,
4
+ };
@@ -14,8 +14,10 @@ const _tempBox: Box3 = new Box3();
14
14
  const _tempSphere: Sphere = new Sphere();
15
15
 
16
16
  /**
17
- * Needle Engine LODs manager. Wrapper around the internal LODs manager.
18
- * It uses the @needle-tools/gltf-progressive package to manage LODs.
17
+ * Needle Engine LODs manager. Wrapper around the internal LODs manager.
18
+ * It uses the [@needle-tools/gltf-progressive](https://npmjs.com/package/@needle-tools/gltf-progressive) package to manage LODs.
19
+ *
20
+ * For lower-level control (e.g. configuring max concurrent loading tasks, queue settings, or other progressive loading specifics), use {@link NEEDLE_progressive} directly.
19
21
  * @link https://npmjs.com/package/@needle-tools/gltf-progressive
20
22
  */
21
23
  export class LODsManager implements NEEDLE_progressive_plugin {
@@ -37,6 +39,11 @@ export class LODsManager implements NEEDLE_progressive_plugin {
37
39
  return this._lodsManager;
38
40
  }
39
41
 
42
+ /**
43
+ * The interval (in seconds) at which the bounding volumes of skinned meshes are automatically updated.
44
+ * If set to 0, automatic updates are disabled and bounding volumes will only be updated when the mesh is loaded or when the `updateSkinnedMeshBounds` method is called manually.
45
+ * @default 0
46
+ */
40
47
  get skinnedMeshAutoUpdateBoundsInterval() {
41
48
  return this._lodsManager?.skinnedMeshAutoUpdateBoundsInterval || this._settings.skinnedMeshAutoUpdateBoundsInterval || 0;
42
49
  }
@@ -57,6 +64,7 @@ export class LODsManager implements NEEDLE_progressive_plugin {
57
64
  this.applySettings();
58
65
  }
59
66
 
67
+ /** @internal */
60
68
  constructor(context: Context) {
61
69
  this.context = context;
62
70
  }
@@ -270,6 +270,10 @@ export function isNeedleXRSessionEventReceiver(script: any, mode: XRSessionMode
270
270
  }
271
271
 
272
272
 
273
+
274
+
275
+ // #region activeInHierarchy
276
+
273
277
  let needsUpdate = true;
274
278
  export function markHierarchyDirty() {
275
279
  needsUpdate = true;
@@ -304,7 +308,7 @@ export function updateIsActive(obj?: Object3D, force: boolean = false) {
304
308
  }
305
309
  }
306
310
 
307
- function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarchy: boolean, allowEventCall: boolean, level: number = 0) {
311
+ function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarchy: boolean, allowEventCall: boolean, level: number = 0): boolean {
308
312
  if (level > 1000) {
309
313
  console.warn("Hierarchy is too deep (> 1000 level) - will abort updating active state");
310
314
  return false;
@@ -336,74 +340,70 @@ function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarc
336
340
  go[constants.activeInHierarchyFieldName] = activeInHierarchy;
337
341
 
338
342
  if (debugHierarchy)
339
- console.warn("ACTIVE CHANGE", go.name, activeSelf, go.visible, activeInHierarchy, "changed?" + changed, go);
343
+ console.warn("ACTIVE CHANGE", { name: go.name, activeSelf, visible: go.visible, activeInHierarchy, changed, go });
340
344
  if (allowEventCall) {
341
- perComponent(go, comp => {
342
- if (activeInHierarchy) {
343
- if (comp.enabled) {
344
- safeInvoke(comp.__internalAwake.bind(comp));
345
+ const components = go.userData?.components;
346
+ if (components) {
347
+ for (let ci = 0, cl = components.length; ci < cl; ci++) {
348
+ const comp = components[ci];
349
+ if (activeInHierarchy) {
345
350
  if (comp.enabled) {
346
- comp.__internalEnable();
351
+ try { comp.__internalAwake(); }
352
+ catch (err) { console.error(err); }
353
+ if (comp.enabled) {
354
+ comp.__internalEnable();
355
+ }
347
356
  }
348
357
  }
349
- }
350
- else {
351
- if (comp["__didAwake"] && comp.enabled) {
352
- comp["__didEnable"] = false;
353
- comp.onDisable();
358
+ else {
359
+ if (comp["__didAwake"] && comp.enabled) {
360
+ comp["__didEnable"] = false;
361
+ comp.onDisable();
362
+ }
354
363
  }
355
364
  }
356
- });
365
+ }
357
366
  }
358
367
  }
359
-
360
- let success = true;
361
- if (go.children) {
362
- for (const ch of go.children) {
363
- const res = updateIsActiveInHierarchyRecursiveRuntime(ch, activeInHierarchy, allowEventCall, level + 1);
364
- if (res === false) success = false;
368
+ const children = go.children;
369
+ if (children) {
370
+ // When this node is inactive and hasn't changed, skip children that are already
371
+ // marked inactive. Only recurse into children that still have a stale active=true
372
+ // (e.g. after reparenting into this inactive subtree).
373
+ if (!changed && !activeInHierarchy) {
374
+ let success = true;
375
+ for (let i = 0, l = children.length; i < l; i++) {
376
+ const ch = children[i];
377
+ if (ch[constants.activeInHierarchyFieldName] !== false) {
378
+ if (updateIsActiveInHierarchyRecursiveRuntime(ch, false, allowEventCall, level + 1) === false)
379
+ success = false;
380
+ }
381
+ }
382
+ return success;
365
383
  }
384
+ let success = true;
385
+ for (let i = 0, l = children.length; i < l; i++) {
386
+ if (updateIsActiveInHierarchyRecursiveRuntime(children[i], activeInHierarchy, allowEventCall, level + 1) === false)
387
+ success = false;
388
+ }
389
+ return success;
366
390
  }
367
- return success;
391
+ return true;
368
392
  }
369
393
 
370
- // function tryFindActiveStateInParent(obj: Object3D): boolean {
371
- // let current: Object3D | undefined | null = obj;
372
- // while (current) {
373
- // const activeState = current[constants.activeInHierarchyFieldName];
374
- // if (activeState !== undefined) return activeState;
375
- // if (current instanceof Scene && !current.parent) {
376
- // return true;
377
- // }
378
- // current = current.parent;
379
- // }
380
- // return false;
381
- // }
382
-
383
- // let isRunning = false;
384
- // // Prevent: https://github.com/needle-tools/needle-tiny/issues/641
385
- // const temporyChildArrayBuffer: Array<Array<Object3D>> = [];
386
- // export function* iterateChildrenSafe(obj: Object3D) {
387
- // if (!obj || !obj.children) yield null;
388
- // // if(isRunning) return;
389
- // // isRunning = true;
390
- // const arr = temporyChildArrayBuffer.pop() || [];
391
- // arr.push(...obj.children);
392
- // for (const ch of arr) {
393
- // yield ch;
394
- // }
395
- // // isRunning = false;
396
- // arr.length = 0;
397
- // temporyChildArrayBuffer.push(arr);
398
- // }
394
+
395
+
399
396
 
400
397
  /** @internal */
401
398
  export function updateActiveInHierarchyWithoutEventCall(go: Object3D) {
399
+ if (!go) {
400
+ console.error("GO is null");
401
+ return;
402
+ }
402
403
  let activeInHierarchy = true;
404
+ let foundScene = false;
403
405
  let current: Object3D | null = go;
404
- let foundScene: boolean = false;
405
406
  while (current) {
406
- if (!current) break;
407
407
  if (current.type === "Scene") foundScene = true;
408
408
  if (!isActiveSelf(current)) {
409
409
  activeInHierarchy = false;
@@ -411,22 +411,14 @@ export function updateActiveInHierarchyWithoutEventCall(go: Object3D) {
411
411
  }
412
412
  current = current.parent;
413
413
  }
414
- if (!go) {
415
- console.error("GO is null");
416
- return;
417
- }
418
414
  go[constants.activeInHierarchyFieldName] = activeInHierarchy && foundScene;
419
415
  }
420
416
 
421
- function perComponent(go: Object3D, evt: (comp: IComponent) => void) {
422
- if (go.userData?.components) {
423
- for (const comp of go.userData.components) {
424
- evt(comp);
425
- }
426
- }
427
- }
428
417
 
429
418
 
419
+
420
+ // #region prewarm
421
+
430
422
  const prewarmList: Map<IContext, Object3D[]> = new Map();
431
423
  const $prewarmedFlag = Symbol("prewarmFlag");
432
424
  const $waitingForPrewarm = Symbol("waitingForPrewarm");