@needle-tools/engine 4.12.5 → 4.13.0-next.2d429c0

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 (63) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/custom-elements.json +18 -1
  4. package/dist/{gltf-progressive-BqUnxvCx.umd.cjs → gltf-progressive-BURrJW0U.umd.cjs} +1 -1
  5. package/dist/{gltf-progressive-CSaX5HQb.min.js → gltf-progressive-DHLDFNvQ.min.js} +1 -1
  6. package/dist/{gltf-progressive-ChnIhDXx.js → gltf-progressive-eiJCrjLb.js} +3 -3
  7. package/dist/materialx-CnsT47BY.umd.cjs +90 -0
  8. package/dist/materialx-DTdTjiOL.min.js +90 -0
  9. package/dist/materialx-DUlX6ZVi.js +4642 -0
  10. package/dist/{needle-engine.bundle-Cj66livk.js → needle-engine.bundle-B9mInB8V.js} +3275 -3186
  11. package/dist/{needle-engine.bundle-Cnemui9H.umd.cjs → needle-engine.bundle-ChxvQROR.umd.cjs} +96 -96
  12. package/dist/{needle-engine.bundle-B_IGIr6Z.min.js → needle-engine.bundle-F7f5MEhj.min.js} +129 -129
  13. package/dist/needle-engine.d.ts +23 -1
  14. package/dist/needle-engine.js +415 -414
  15. package/dist/needle-engine.min.js +1 -1
  16. package/dist/needle-engine.umd.cjs +1 -1
  17. package/dist/{postprocessing-12-UW7je.min.js → postprocessing-BVNrgYZK.min.js} +1 -1
  18. package/dist/{postprocessing-B3Hu0Ryi.umd.cjs → postprocessing-CI2TjWpu.umd.cjs} +1 -1
  19. package/dist/{postprocessing-R535krvT.js → postprocessing-DdM-tz1j.js} +2 -2
  20. package/dist/{three-BzxwLtUE.umd.cjs → three-BW2s1Yl-.umd.cjs} +25 -25
  21. package/dist/{three-DMvLgxja.min.js → three-I__hSXzr.min.js} +26 -26
  22. package/dist/{three-D9pcFbxc.js → three-VvRoMeIN.js} +22 -0
  23. package/dist/{three-examples-F0MJj0vr.js → three-examples-BhfOE7NG.js} +1 -1
  24. package/dist/{three-examples-CjSwCv_b.umd.cjs → three-examples-Bpfu6ke_.umd.cjs} +1 -1
  25. package/dist/{three-examples-CIv2roOA.min.js → three-examples-D8zAE_7t.min.js} +1 -1
  26. package/dist/{three-mesh-ui-BLnJQzMl.umd.cjs → three-mesh-ui-BU55xDxJ.umd.cjs} +1 -1
  27. package/dist/{three-mesh-ui-BllgajJz.min.js → three-mesh-ui-C3QbemOV.min.js} +1 -1
  28. package/dist/{three-mesh-ui-DYyiRn5Y.js → three-mesh-ui-CcMp-FQm.js} +1 -1
  29. package/dist/{vendor-BIFy-gRe.js → vendor-BiyIZ61v.js} +1 -1
  30. package/dist/{vendor-BFgQSG2m.umd.cjs → vendor-COVQl0b8.umd.cjs} +1 -1
  31. package/dist/{vendor-ChgmXMYr.min.js → vendor-DW7zqjuT.min.js} +1 -1
  32. package/lib/engine/engine_loaders.js +7 -1
  33. package/lib/engine/engine_loaders.js.map +1 -1
  34. package/lib/engine/engine_modules.d.ts +9 -0
  35. package/lib/engine/engine_modules.js +25 -0
  36. package/lib/engine/engine_modules.js.map +1 -1
  37. package/lib/engine/engine_utils_format.d.ts +1 -1
  38. package/lib/engine/engine_utils_format.js +4 -1
  39. package/lib/engine/engine_utils_format.js.map +1 -1
  40. package/lib/engine/extensions/NEEDLE_materialx.d.ts +29 -0
  41. package/lib/engine/extensions/NEEDLE_materialx.js +115 -0
  42. package/lib/engine/extensions/NEEDLE_materialx.js.map +1 -0
  43. package/lib/engine/extensions/extensions.js +2 -0
  44. package/lib/engine/extensions/extensions.js.map +1 -1
  45. package/lib/engine/extensions/index.d.ts +1 -0
  46. package/lib/engine/extensions/index.js +1 -0
  47. package/lib/engine/extensions/index.js.map +1 -1
  48. package/lib/engine-components/ContactShadows.js +15 -0
  49. package/lib/engine-components/ContactShadows.js.map +1 -1
  50. package/lib/engine-components/Skybox.js +1 -1
  51. package/lib/engine-components/Skybox.js.map +1 -1
  52. package/package.json +3 -2
  53. package/plugins/vite/dependencies.js +9 -4
  54. package/plugins/vite/index.js +0 -2
  55. package/src/engine/engine_loaders.ts +9 -2
  56. package/src/engine/engine_modules.ts +24 -0
  57. package/src/engine/engine_utils_format.ts +6 -2
  58. package/src/engine/extensions/NEEDLE_materialx.ts +142 -0
  59. package/src/engine/extensions/extensions.ts +2 -0
  60. package/src/engine/extensions/index.ts +2 -1
  61. package/src/engine-components/ContactShadows.ts +15 -0
  62. package/src/engine-components/Skybox.ts +1 -1
  63. package/plugins/vite/materialx.js +0 -32
@@ -18,6 +18,7 @@ import * as utils from "./engine_utils.js";
18
18
  import { tryDetermineMimetypeFromURL } from "./engine_utils_format.js"
19
19
  import { invokeLoadedImportPluginHooks, registerComponentExtension, registerExtensions } from "./extensions/extensions.js";
20
20
  import { NEEDLE_components } from "./extensions/NEEDLE_components.js";
21
+ import { MaterialXLoader } from "./extensions/NEEDLE_materialx.js";
21
22
 
22
23
  /** @internal */
23
24
  export class NeedleLoader implements INeedleGltfLoader {
@@ -43,7 +44,7 @@ const debugFileTypes = utils.getParam("debugfileformat");
43
44
 
44
45
 
45
46
 
46
- async function onCreateLoader(url: string, context: Context, sourceId: SourceIdentifier): Promise<CustomLoader | GLTFLoader | FBXLoader | USDZLoader | OBJLoader | null> {
47
+ async function onCreateLoader(url: string, context: Context, sourceId: SourceIdentifier): Promise<CustomLoader | GLTFLoader | FBXLoader | USDZLoader | OBJLoader | MaterialXLoader | null> {
47
48
 
48
49
  const type = await tryDetermineMimetypeFromURL(url, { useExtension: true }) || "unknown";
49
50
  if (debugFileTypes) console.debug(`Determined file type: '${type}' for url '${url}'`, { registeredModelLoaderCallbacks });
@@ -91,6 +92,12 @@ async function onCreateLoader(url: string, context: Context, sourceId: SourceIde
91
92
  await registerExtensions(loader, context, url, sourceId);
92
93
  return loader;
93
94
  }
95
+
96
+ case "application/materialx+xml":
97
+ {
98
+ const loader = new MaterialXLoader();
99
+ return loader;
100
+ }
94
101
  }
95
102
  }
96
103
 
@@ -133,7 +140,7 @@ export async function parseSync(context: Context, data: string | ArrayBuffer, pa
133
140
  // Handle any other loader that is not a GLTFLoader
134
141
  const isNotGLTF = !(loader instanceof GLTFLoader);
135
142
  if (isNotGLTF) {
136
- if (loader.parse === undefined) {
143
+ if (!("parse" in loader) || typeof loader.parse !== "function") {
137
144
  console.error("Loader does not support parse");
138
145
  return undefined;
139
146
  }
@@ -8,6 +8,30 @@
8
8
  */
9
9
  export namespace MODULES {
10
10
 
11
+ export namespace MaterialX {
12
+ export type TYPE = typeof import("@needle-tools/materialx");
13
+ export let MODULE: TYPE;
14
+ export let MAYBEMODULE: TYPE | null = null;
15
+
16
+ const callbacks: Array<(module: TYPE) => void> = [];
17
+ /** Wait for the module to be loaded (doesn't trigger a load) */
18
+ export function ready(): Promise<TYPE> {
19
+ if (MODULE) return Promise.resolve(MODULE);
20
+ return new Promise((resolve) => { callbacks.push(resolve); });
21
+ };
22
+ /** Load the module */
23
+ export async function load(): Promise<TYPE> {
24
+ if (MODULE) return MODULE;
25
+ const module = await import("@needle-tools/materialx");
26
+ MODULE = module;
27
+ MAYBEMODULE = module;
28
+ callbacks.forEach((callback) => callback(module));
29
+ callbacks.length = 0;
30
+ return module;
31
+ }
32
+
33
+ }
34
+
11
35
  export namespace RAPIER_PHYSICS {
12
36
  export type TYPE = typeof import("@dimforge/rapier3d-compat");
13
37
  export let MODULE: TYPE;
@@ -16,7 +16,8 @@ export type NeedleMimetype = "unknown" | "unsupported" |
16
16
  "model/vnd.usdc" |
17
17
  "model/fbx" |
18
18
  "model/vnd.autodesk.fbx" |
19
- "model/obj"
19
+ "model/obj" |
20
+ "application/materialx+xml"
20
21
  | (string & {})
21
22
 
22
23
 
@@ -95,6 +96,8 @@ export async function tryDetermineMimetypeFromURL(url: string, opts: { useExtens
95
96
  return "model/vnd.usdz+zip";
96
97
  case "OBJ":
97
98
  return "model/obj";
99
+ case "MTLX":
100
+ return "application/materialx+xml";
98
101
  }
99
102
  }
100
103
 
@@ -222,6 +225,7 @@ export function tryDetermineMimetypeFromBinary(url: string, data: ArrayBuffer, r
222
225
  case "model/fbx":
223
226
  case "model/vnd.autodesk.fbx":
224
227
  case "model/obj":
228
+ case "application/materialx+xml":
225
229
  return content_type;
226
230
  // case "model/stl":
227
231
  // return "stl";
@@ -271,7 +275,7 @@ export function tryDetermineMimetypeFromBinary(url: string, data: ArrayBuffer, r
271
275
 
272
276
  if (isDevEnvironment() || debug) {
273
277
  const text = new TextDecoder().decode(data.slice(0, Math.min(data.byteLength, 32)));
274
- console.warn(`Could not determine file type.\n\nConsider registering a custom loader via the 'onCreateCustomModelLoader' callback: 'NeedleEngineModelLoader.onCreateCustomModelLoader(args => { })'\n\nContent-Type: \"${response.headers.get("content-type")}\n\"Text: \"${text}\"\nBinary:`, bytes);
278
+ console.warn(`Could not determine file type.\n\nConsider registering a custom loader via the 'onCreateCustomModelLoader' callback: 'NeedleEngineModelLoader.onCreateCustomModelLoader(args => { })'\n\nContent-Type: \"${response.headers.get("content-type")}\"\n\"Text: \"${text}\"\nBinary:`, bytes);
275
279
  }
276
280
  else {
277
281
  console.debug(`Could not determine file type from binary data`);
@@ -0,0 +1,142 @@
1
+ import { Loader, LoadingManager, Material, Object3D, TextureLoader } from "three";
2
+ import { GLTFLoader, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader";
3
+ import { ObjectUtils } from "../engine_create_objects";
4
+ import { MODULES } from "../engine_modules";
5
+ import { IContext } from "../engine_types";
6
+
7
+
8
+ // #region Utils
9
+
10
+ export namespace MaterialX {
11
+
12
+
13
+ /**
14
+ * Utility function to load a MaterialX material from a URL. This can be used in your own code to load MaterialX materials outside of the glTF loading process. The URL should point to a MaterialX XML file.
15
+ */
16
+ export async function loadFromUrl(urlOrXML: string,
17
+ opts?: {
18
+ url?: string,
19
+ loadingManager?: LoadingManager,
20
+ materialNameOrIndex?: number | string
21
+ }
22
+ ): Promise<import("three").Material | null> {
23
+
24
+ if (!urlOrXML) throw new Error("URL or XML string is required to load a MaterialX material");
25
+
26
+ // Ensure the MaterialX module is loaded
27
+ const module = await MODULES.MaterialX.load();
28
+
29
+ // Check if the input is an XML string or a URL
30
+ // And fetch the XML content if it's a URL
31
+ const isXmlString = urlOrXML.trimStart().startsWith("<");
32
+ const xml = isXmlString ? urlOrXML : await fetch(urlOrXML).then(r => r.text()).catch(console.error);
33
+ if (!xml) {
34
+ console.warn("Failed to load MaterialX file from url", urlOrXML);
35
+ return null;
36
+ }
37
+
38
+ // For relative texture paths we might need to detect the base directory of the material file.
39
+ // We can only do this if we have a URL (not an XML string) and if the URL is not a data URL. In that case we can use the URL to determine the base path for textures.
40
+ // This can be used by the loader callback to resolve texture paths relative to the material file.
41
+ let dir: string | undefined = undefined;
42
+ if (opts?.url || !isXmlString) {
43
+ const parts = (opts?.url || urlOrXML).split('/');
44
+ parts.pop();
45
+ dir = parts.join('/');
46
+ }
47
+
48
+ const textureLoader = new TextureLoader();
49
+ return module.Experimental_API.createMaterialXMaterial(xml, opts?.materialNameOrIndex ?? 0, {
50
+ getTexture: async url => {
51
+ if (!url.startsWith("http") && !url.startsWith("data:") && !url.startsWith("blob:") && !url.startsWith("file:")) {
52
+ if (dir) {
53
+ url = dir + "/" + url;
54
+ }
55
+ }
56
+ return textureLoader.loadAsync(url).catch(e => {
57
+ console.warn(`Failed to load texture for MaterialX material ${url}`, e);
58
+ });
59
+ }
60
+ }, {
61
+ cacheKey: urlOrXML,
62
+ })
63
+ }
64
+ }
65
+
66
+
67
+ // #region Loader
68
+
69
+ export class MaterialXLoader extends Loader<Object3D | null> {
70
+
71
+ loadAsync(url: string, onProgress?: ((event: ProgressEvent<EventTarget>) => void) | undefined): Promise<Object3D> {
72
+ return new Promise((resolve, reject) => {
73
+ this.load(url, resolve, onProgress, reject);
74
+ });
75
+ }
76
+
77
+ load(url: string, onLoad: (data: Object3D) => void, onProgress?: ((event: ProgressEvent<EventTarget>) => void) | undefined, onError?: ((err: unknown) => void) | undefined): void {
78
+ onProgress?.({ type: "progress", loaded: 0, total: 0 } as ProgressEvent);
79
+
80
+ MaterialX.loadFromUrl(url, {
81
+ }).then(mat => {
82
+ if (mat) {
83
+ onLoad(this.onLoaded(mat));
84
+ }
85
+ else {
86
+ onError?.(new Error("Failed to load MaterialX material from url: " + url));
87
+ }
88
+ });
89
+ }
90
+
91
+ private onLoaded(mat: Material): Object3D {
92
+ const shaderball = ObjectUtils.createPrimitive("ShaderBall", { material: mat });
93
+ return shaderball;
94
+ }
95
+ }
96
+
97
+
98
+
99
+ // #region GLTF Extension
100
+
101
+ export class NEEDLE_materialx implements GLTFLoaderPlugin {
102
+
103
+ get name(): string {
104
+ return "materialx-loading-helper";
105
+ }
106
+
107
+ constructor(
108
+ private readonly context: IContext,
109
+ private readonly loader: GLTFLoader,
110
+ private readonly url: string,
111
+ private readonly parser: GLTFParser,
112
+ ) {
113
+ }
114
+
115
+ private mtlxLoader?: import("@needle-tools/materialx").MaterialXLoader;
116
+
117
+ async beforeRoot() {
118
+ const mtlxExtension = this.parser.json.extensions?.["NEEDLE_materials_mtlx"];
119
+ if (mtlxExtension) {
120
+ const module = await MODULES.MaterialX.load();
121
+ try {
122
+ this.mtlxLoader = new module.MaterialXLoader(this.parser, {
123
+ cacheKey: `${this.url}:materialx`,
124
+ parameters: {
125
+ precision: this.context.renderer?.capabilities.precision as any,
126
+ }
127
+ }, {
128
+ getFrame: () => this.context.time.frame,
129
+ getTime: () => this.context.time.time,
130
+ })
131
+ }
132
+ catch (error) {
133
+ console.error(error);
134
+ }
135
+ }
136
+ }
137
+
138
+ loadMaterial(index) {
139
+ if (this.mtlxLoader) return this.mtlxLoader.loadMaterial(index);
140
+ return null;
141
+ }
142
+ }
@@ -12,6 +12,7 @@ import { EXT_texture_exr } from "./EXT_texture_exr.js";
12
12
  import { NEEDLE_components } from "./NEEDLE_components.js";
13
13
  import { NEEDLE_gameobject_data } from "./NEEDLE_gameobject_data.js";
14
14
  import { NEEDLE_lighting_settings } from "./NEEDLE_lighting_settings.js";
15
+ import { NEEDLE_materialx } from "./NEEDLE_materialx.js";
15
16
  import { NEEDLE_persistent_assets } from "./NEEDLE_persistent_assets.js";
16
17
  import { NEEDLE_progressive } from "./NEEDLE_progressive.js";
17
18
  import { NEEDLE_render_objects } from "./NEEDLE_render_objects.js";
@@ -136,6 +137,7 @@ export async function registerExtensions(loader: GLTFLoader, context: Context, u
136
137
  loader.register(p => new NEEDLE_render_objects(p, sourceId));
137
138
  loader.register(p => new NEEDLE_progressive(p));
138
139
  loader.register(p => new EXT_texture_exr(p));
140
+ loader.register(p => new NEEDLE_materialx(context, loader, url, p));
139
141
  if (isResourceTrackingEnabled()) loader.register(p => new InternalUsageTrackerPlugin(p))
140
142
 
141
143
  await KHR_ANIMATIONPOINTER_IMPORT.catch(_ => { })
@@ -3,4 +3,5 @@ export * from "./extensions.js"
3
3
  export * from "./NEEDLE_animator_controller_model.js"
4
4
  export { SceneLightSettings } from "./NEEDLE_lighting_settings.js"
5
5
  export * from "./NEEDLE_progressive.js"
6
- export { CustomShader } from "./NEEDLE_techniques_webgl.js"
6
+ export { CustomShader } from "./NEEDLE_techniques_webgl.js"
7
+ export { MaterialX } from "./NEEDLE_materialx.js"
@@ -74,6 +74,7 @@ export class ContactShadows extends Behaviour {
74
74
  let instance = this._instances.get(context);
75
75
  if (!instance || instance.destroyed) {
76
76
  const obj = new Object3D();
77
+ obj.name = "ContactShadows";
77
78
  instance = addComponent(obj, ContactShadows, {
78
79
  autoFit: false,
79
80
  occludeBelowGround: false
@@ -295,7 +296,12 @@ export class ContactShadows extends Behaviour {
295
296
  this.shadowCamera = new OrthographicCamera(-1 / 2, 1 / 2, 1 / 2, -1 / 2, near, far);
296
297
  this.shadowCamera.layers.enableAll();
297
298
  this.shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up
299
+ // Disable automatic matrix world updates because Camera.updateMatrixWorld
300
+ // resets scale to 1 which would break the shadow camera transform
301
+ this.shadowCamera.matrixWorldAutoUpdate = false;
298
302
  this.shadowGroup.add(this.shadowCamera);
303
+ // Update the local matrix after setting position/rotation
304
+ this.shadowCamera.updateMatrix();
299
305
 
300
306
  // like MeshDepthMaterial, but goes from black to transparent
301
307
  this.depthMaterial = new MeshDepthMaterial();
@@ -442,6 +448,15 @@ export class ContactShadows extends Behaviour {
442
448
  }
443
449
  }
444
450
 
451
+ // Manually update shadow camera's matrix world without calling Camera.updateMatrixWorld
452
+ // (which would reset scale to 1 for glTF conformance)
453
+ if (this.shadowCamera.parent) {
454
+ this.shadowCamera.matrixWorld.multiplyMatrices(this.shadowCamera.parent.matrixWorld, this.shadowCamera.matrix);
455
+ } else {
456
+ this.shadowCamera.matrixWorld.copy(this.shadowCamera.matrix);
457
+ }
458
+ this.shadowCamera.matrixWorldInverse.copy(this.shadowCamera.matrixWorld).invert();
459
+
445
460
  // render to the render target to get the depths
446
461
  renderer.setRenderTarget(this.renderTarget);
447
462
  renderer.clear();
@@ -443,7 +443,7 @@ function tryParseMagicSkyboxName(str: string | null | undefined, environment: bo
443
443
  return useLowRes ? value.url_low : value.url;
444
444
  }
445
445
  else if (typeof str === "string" && str?.length && (isDevEnvironment() || debug)) {
446
- const noUrlOrFile = !str.startsWith("http") && !str.startsWith("file:") && !str.startsWith("blob:") && !str.startsWith("data:");
446
+ const noUrlOrFile = !str.startsWith("http") && !str.startsWith("file:") && !str.startsWith("blob:") && !str.startsWith("data:") && !str.startsWith("/")
447
447
  if(noUrlOrFile) {
448
448
  console.warn(`RemoteSkybox: Unknown magic skybox name "${str}". Valid names are: ${Object.keys(MagicSkyboxNames).map(n => `"${n}"`).join(", ")}`);
449
449
  }
@@ -1,32 +0,0 @@
1
- import { existsSync } from 'fs';
2
-
3
- const materialx_packagejson_path = "node_modules/@needle-tools/materialx/package.json";
4
- const materialx_import_chunk = `
5
- import { useNeedleMaterialX } from "@needle-tools/materialx/needle";
6
- useNeedleMaterialX();
7
- `
8
-
9
- /**
10
- * Vite plugin to automatically setup the MaterialX loader for Needle Engine.
11
- * @param {string} command
12
- * @param {object} config
13
- * @param {import('../types/userconfig.js').userSettings} userSettings
14
- * @returns {import('vite').Plugin}
15
- */
16
- export const needleMaterialXLoader = (command, config, userSettings) => {
17
-
18
- return {
19
- name: 'needle-materialx-loader',
20
- transform: (code, id) => {
21
- if (id.endsWith("src/main.ts")) {
22
- if (userSettings?.loadMaterialX !== false && existsSync(materialx_packagejson_path)) {
23
- if (!code.includes("@needle-tools/materialx")) {
24
- console.log("[needle-materialx-loader] Adding MaterialX import to main.ts");
25
- code = materialx_import_chunk + "\n" + code;
26
- }
27
- }
28
- }
29
- return code;
30
- }
31
- }
32
- }