@needle-tools/engine 4.5.0-alpha.1 → 4.5.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/{needle-engine.bundle-3d05185b.js → needle-engine.bundle-1b8f44f4.js} +4945 -4907
  3. package/dist/{needle-engine.bundle-b2e17f0e.light.min.js → needle-engine.bundle-56f095f1.light.min.js} +125 -119
  4. package/dist/{needle-engine.bundle-d7d53476.light.umd.cjs → needle-engine.bundle-9fe9a394.light.umd.cjs} +137 -131
  5. package/dist/{needle-engine.bundle-e4ae93a2.min.js → needle-engine.bundle-baacde19.min.js} +125 -119
  6. package/dist/{needle-engine.bundle-c44e02c7.light.js → needle-engine.bundle-d710d96f.light.js} +4947 -4909
  7. package/dist/{needle-engine.bundle-f496c70e.umd.cjs → needle-engine.bundle-ef2b8438.umd.cjs} +135 -129
  8. package/dist/needle-engine.js +467 -471
  9. package/dist/needle-engine.light.js +467 -471
  10. package/dist/needle-engine.light.min.js +1 -1
  11. package/dist/needle-engine.light.umd.cjs +1 -1
  12. package/dist/needle-engine.min.js +1 -1
  13. package/dist/needle-engine.umd.cjs +1 -1
  14. package/lib/engine/api.d.ts +2 -1
  15. package/lib/engine/api.js +2 -1
  16. package/lib/engine/api.js.map +1 -1
  17. package/lib/engine/engine_context_registry.d.ts +2 -2
  18. package/lib/engine/engine_context_registry.js +2 -2
  19. package/lib/engine/engine_context_registry.js.map +1 -1
  20. package/lib/engine/engine_gltf.d.ts +2 -2
  21. package/lib/engine/engine_gltf_builtin_components.d.ts +5 -1
  22. package/lib/engine/engine_gltf_builtin_components.js +2 -2
  23. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  24. package/lib/engine/engine_loaders.callbacks.d.ts +62 -0
  25. package/lib/engine/engine_loaders.callbacks.js +56 -0
  26. package/lib/engine/engine_loaders.callbacks.js.map +1 -0
  27. package/lib/engine/engine_loaders.d.ts +44 -9
  28. package/lib/engine/engine_loaders.gltf.d.ts +13 -0
  29. package/lib/engine/engine_loaders.gltf.js +63 -0
  30. package/lib/engine/engine_loaders.gltf.js.map +1 -0
  31. package/lib/engine/engine_loaders.js +305 -48
  32. package/lib/engine/engine_loaders.js.map +1 -1
  33. package/lib/engine/engine_types.d.ts +7 -1
  34. package/lib/engine/engine_types.js +7 -0
  35. package/lib/engine/engine_types.js.map +1 -1
  36. package/lib/engine/engine_utils_format.d.ts +5 -3
  37. package/lib/engine/engine_utils_format.js +26 -10
  38. package/lib/engine/engine_utils_format.js.map +1 -1
  39. package/lib/engine/extensions/extensions.d.ts +3 -2
  40. package/lib/engine/extensions/extensions.js +10 -6
  41. package/lib/engine/extensions/extensions.js.map +1 -1
  42. package/lib/engine/webcomponents/needle-engine.attributes.d.ts +3 -6
  43. package/lib/engine/webcomponents/needle-engine.js +2 -2
  44. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  45. package/lib/engine/webcomponents/needle-engine.loading.js +26 -34
  46. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  47. package/lib/engine-components/AvatarLoader.js +1 -1
  48. package/lib/engine-components/AvatarLoader.js.map +1 -1
  49. package/lib/engine-components/ContactShadows.d.ts +12 -0
  50. package/lib/engine-components/ContactShadows.js +23 -0
  51. package/lib/engine-components/ContactShadows.js.map +1 -1
  52. package/lib/engine-components/webxr/controllers/XRControllerModel.js +4 -3
  53. package/lib/engine-components/webxr/controllers/XRControllerModel.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/engine/api.ts +2 -1
  56. package/src/engine/engine_context_registry.ts +2 -2
  57. package/src/engine/engine_gltf.ts +2 -2
  58. package/src/engine/engine_gltf_builtin_components.ts +7 -7
  59. package/src/engine/engine_loaders.callbacks.ts +88 -0
  60. package/src/engine/engine_loaders.gltf.ts +82 -0
  61. package/src/engine/engine_loaders.ts +332 -54
  62. package/src/engine/engine_types.ts +34 -18
  63. package/src/engine/engine_utils_format.ts +32 -14
  64. package/src/engine/extensions/extensions.ts +12 -7
  65. package/src/engine/webcomponents/needle-engine.attributes.ts +3 -6
  66. package/src/engine/webcomponents/needle-engine.loading.ts +28 -36
  67. package/src/engine/webcomponents/needle-engine.ts +2 -2
  68. package/src/engine-components/AvatarLoader.ts +1 -1
  69. package/src/engine-components/ContactShadows.ts +24 -0
  70. package/src/engine-components/webxr/controllers/XRControllerModel.ts +4 -5
  71. package/src/engine/engine_scenetools.ts +0 -379
@@ -1,4 +1,5 @@
1
1
  import { isDevEnvironment } from "./debug/index.js";
2
+ import { NeedleEngineModelLoader, registeredFileTypeCallbacks } from "./engine_loaders.callbacks.js";
2
3
  import { getParam } from "./engine_utils.js";
3
4
 
4
5
  const debug = getParam("debugfileformat");
@@ -6,7 +7,7 @@ const debug = getParam("debugfileformat");
6
7
  /**
7
8
  * The supported file types that can be determined by the engine. Used in {@link tryDetermineFileTypeFromURL} and {@link tryDetermineFileTypeFromBinary}
8
9
  */
9
- export declare type FileType = "gltf" | "glb" | "vrm" | "fbx" | "obj" | "usdz" | "usd" | "usda" | "unknown";
10
+ export declare type FileType = "gltf" | "glb" | "vrm" | "fbx" | "obj" | "usdz" | "usd" | "usda" | "unknown" | ({} & string);
10
11
 
11
12
  /**
12
13
  * Tries to determine the file type of a file from its URL
@@ -20,7 +21,9 @@ export declare type FileType = "gltf" | "glb" | "vrm" | "fbx" | "obj" | "usdz" |
20
21
  * const fileType = await tryDetermineFileTypeFromURL(url);
21
22
  * console.log(fileType); // "glb"
22
23
  */
23
- export async function tryDetermineFileTypeFromURL(url: string, useExtension: boolean = true): Promise<FileType> {
24
+ export async function tryDetermineFileTypeFromURL(url: string, opts: { useExtension: boolean }): Promise<FileType> {
25
+
26
+ const { useExtension = true } = opts;
24
27
 
25
28
  if (useExtension) {
26
29
  // We want to save on requests so we first check the file extension if there's any
@@ -59,11 +62,12 @@ export async function tryDetermineFileTypeFromURL(url: string, useExtension: boo
59
62
  }
60
63
  }
61
64
 
62
-
65
+
63
66
  // If the URL doesnt contain a filetype we need to check the header
64
67
  // This is the case for example if we load a file from a data url
68
+ const originalUrl = url;
65
69
 
66
- if(url.startsWith("blob:")) {
70
+ if (url.startsWith("blob:")) {
67
71
  // We can't modify the blob URL
68
72
  }
69
73
  else {
@@ -84,7 +88,7 @@ export async function tryDetermineFileTypeFromURL(url: string, useExtension: boo
84
88
 
85
89
  if (header?.ok) {
86
90
  const data = await header.arrayBuffer();
87
- const res = tryDetermineFileTypeFromBinary(data, header);
91
+ const res = tryDetermineFileTypeFromBinary(originalUrl, data, header);
88
92
  if (debug) console.log("Determined file type from header: " + res);
89
93
  return res;
90
94
  }
@@ -96,7 +100,7 @@ export async function tryDetermineFileTypeFromURL(url: string, useExtension: boo
96
100
  /** Attempts to determine the file type of a binary file by looking at the first few bytes of the file.
97
101
  * @hidden
98
102
  */
99
- export function tryDetermineFileTypeFromBinary(data: ArrayBuffer, response: Response): FileType {
103
+ export function tryDetermineFileTypeFromBinary(url: string, data: ArrayBuffer, response: Response): FileType {
100
104
 
101
105
  if (data.byteLength < 4) {
102
106
  return "unknown";
@@ -193,14 +197,6 @@ export function tryDetermineFileTypeFromBinary(data: ArrayBuffer, response: Resp
193
197
  console.debug("OBJ detected (mtllib)");
194
198
  return "obj";
195
199
  }
196
-
197
- if (isDevEnvironment() || debug) {
198
- const text = new TextDecoder().decode(data.slice(0, 16));
199
- console.debug("Could not determine file type from binary data: \"" + text + "...\"", bytes);
200
- }
201
- else {
202
- console.debug("Could not determine file type from binary data", bytes);
203
- }
204
200
  // const text = new TextDecoder().decode(data);
205
201
  // if (text.startsWith("Kaydara FBX")) {
206
202
  // return "fbx";
@@ -209,5 +205,27 @@ export function tryDetermineFileTypeFromBinary(data: ArrayBuffer, response: Resp
209
205
  // return "gltf";
210
206
  // }
211
207
 
208
+ for (const callback of registeredFileTypeCallbacks) {
209
+ const mimetype = callback({
210
+ url: url,
211
+ response: response,
212
+ contentType: response.headers.get("content-type"),
213
+ bytes: bytes
214
+ })
215
+ if (mimetype) {
216
+ if (debug) console.debug(`Mimetype callback returned: ${mimetype}`);
217
+ return mimetype;
218
+ }
219
+ }
220
+
221
+
222
+ if (isDevEnvironment() || debug) {
223
+ const text = new TextDecoder().decode(data.slice(0, Math.min(data.byteLength, 32)));
224
+ 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);
225
+ }
226
+ else {
227
+ console.debug(`Could not determine file type from binary data`);
228
+ }
229
+
212
230
  return "unknown";
213
231
  }
@@ -1,3 +1,4 @@
1
+ import { Loader } from "three";
1
2
  import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
2
3
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
3
4
 
@@ -66,13 +67,17 @@ export function removeCustomImportExtensionType(ext: INeedleGLTFExtensionPlugin)
66
67
 
67
68
 
68
69
  /** Registers the Needle Engine components extension */
69
- export function registerComponentExtension(loader: GLTFLoader): NEEDLE_components {
70
- const ext = new NEEDLE_components();
71
- loader.register(p => {
72
- ext.parser = p;
70
+ export function registerComponentExtension(loader: GLTFLoader | Loader | object): NEEDLE_components | null {
71
+ if (loader instanceof GLTFLoader) {
72
+ const ext = new NEEDLE_components();
73
+ loader.register(p => {
74
+ ext.parser = p;
75
+ return ext;
76
+ });
73
77
  return ext;
74
- });
75
- return ext;
78
+ }
79
+
80
+ return null;
76
81
  }
77
82
 
78
83
 
@@ -130,7 +135,7 @@ export function registerExportExtensions(exp: GLTFExporter, context: Context) {
130
135
  }
131
136
 
132
137
  /** @internal */
133
- export function invokeAfterImportPluginHooks(url: string, gltf: GLTF, context: Context) {
138
+ export function invokeLoadedImportPluginHooks(url: string, gltf: GLTF, context: Context) {
134
139
  for (const ext of _plugins)
135
140
  if (ext.onLoaded) ext.onLoaded(url, gltf, context);
136
141
  }
@@ -31,19 +31,16 @@ type MainAttributes = {
31
31
  }
32
32
 
33
33
  type LoadingAttributes = {
34
- "loading-style"?: "light" | "dark",
35
- /** Pro feature */
34
+ /** Set a custom loading background color (e.g. "red" or "#dd00ff" or "rgba(18, 224, 224, 0.5)") */
35
+ "loading-background"?: string,
36
+ /** Pro features */
36
37
  "hide-loading-overlay"?: boolean,
37
38
  /** Pro feature */
38
- "loading-background-color"?: string,
39
- /** Pro feature */
40
39
  "loading-logo-src"?: string,
41
40
  /** Pro feature */
42
41
  "primary-color"?: string,
43
42
  /** Pro feature */
44
43
  "secondary-color"?: string,
45
- /** Pro feature */
46
- "loading-text-color"?: string,
47
44
  }
48
45
 
49
46
  type SkyboxAttributes = {
@@ -197,10 +197,12 @@ export class EngineLoadingView implements ILoadingViewHandler {
197
197
  this._loadingElement.style.height = "100%";
198
198
  this._loadingElement.style.left = "0";
199
199
  this._loadingElement.style.top = "0";
200
- if (loadingStyle === "light")
201
- this._loadingElement.style.backgroundColor = "#ddd";
200
+ const loadingBackgroundColor = this._element.getAttribute("loading-background");
201
+ if (loadingBackgroundColor) {
202
+ this._loadingElement.style.background = loadingBackgroundColor;
203
+ }
202
204
  else
203
- this._loadingElement.style.backgroundColor = "#222";
205
+ this._loadingElement.style.backgroundColor = "transparent";
204
206
  this._loadingElement.style.display = "flex";
205
207
  this._loadingElement.style.alignItems = "center";
206
208
  this._loadingElement.style.justifyContent = "center";
@@ -214,16 +216,6 @@ export class EngineLoadingView implements ILoadingViewHandler {
214
216
  this._loadingElement.style.color = "rgba(0,0,0,.6)";
215
217
  else
216
218
  this._loadingElement.style.color = "rgba(255,255,255,.3)";
217
- if (hasLicense && this._element) {
218
- const loadingBackgroundColor = this._element.getAttribute("loading-background-color");
219
- if (loadingBackgroundColor) {
220
- this._loadingElement.style.backgroundColor = loadingBackgroundColor;
221
- }
222
- const textColor = this._element.getAttribute("loading-text-color");
223
- if (textColor) {
224
- this._loadingElement.style.color = textColor;
225
- }
226
- }
227
219
  }
228
220
 
229
221
  const className = this._loadingElementOptions?.className ?? EngineLoadingView.LoadingContainerClassName;
@@ -302,8 +294,8 @@ export class EngineLoadingView implements ILoadingViewHandler {
302
294
  const getGradientPos = function (t: number): string {
303
295
  return Mathf.lerp(0, maxWidth, t) + "%";
304
296
  }
305
- this._loadingBar.style.background =
306
- `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`;
297
+ this._loadingBar.style.background = "#66A22F";
298
+ // `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`;
307
299
  this._loadingBar.style.backgroundAttachment = "fixed";
308
300
  this._loadingBar.style.width = "0%";
309
301
  this._loadingBar.style.height = "100%";
@@ -321,27 +313,27 @@ export class EngineLoadingView implements ILoadingViewHandler {
321
313
  }
322
314
  }
323
315
 
324
- this._loadingTextContainer = document.createElement("div");
325
- this._loadingTextContainer.style.display = "flex";
326
- this._loadingTextContainer.style.justifyContent = "center";
327
- this._loadingTextContainer.style.marginTop = ".2rem";
328
- details.appendChild(this._loadingTextContainer);
329
-
330
- const messageContainer = document.createElement("div");
331
- this._messageContainer = messageContainer;
332
- messageContainer.style.display = "flex";
333
- messageContainer.style.fontSize = ".8rem";
334
- messageContainer.style.paddingTop = ".1rem";
335
- // messageContainer.style.border = "1px solid rgba(255,255,255,.1)";
336
- messageContainer.style.justifyContent = "center";
337
- details.appendChild(messageContainer);
338
-
339
- if (hasLicense && this._element) {
340
- const loadingTextColor = this._element.getAttribute("loading-text-color");
341
- if (loadingTextColor) {
342
- messageContainer.style.color = loadingTextColor;
343
- }
344
- }
316
+ // this._loadingTextContainer = document.createElement("div");
317
+ // this._loadingTextContainer.style.display = "flex";
318
+ // this._loadingTextContainer.style.justifyContent = "center";
319
+ // this._loadingTextContainer.style.marginTop = ".2rem";
320
+ // details.appendChild(this._loadingTextContainer);
321
+
322
+ // const messageContainer = document.createElement("div");
323
+ // this._messageContainer = messageContainer;
324
+ // messageContainer.style.display = "flex";
325
+ // messageContainer.style.fontSize = ".8rem";
326
+ // messageContainer.style.paddingTop = ".1rem";
327
+ // // messageContainer.style.border = "1px solid rgba(255,255,255,.1)";
328
+ // messageContainer.style.justifyContent = "center";
329
+ // details.appendChild(messageContainer);
330
+
331
+ // if (hasLicense && this._element) {
332
+ // const loadingTextColor = this._element.getAttribute("loading-text-color");
333
+ // if (loadingTextColor) {
334
+ // messageContainer.style.color = loadingTextColor;
335
+ // }
336
+ // }
345
337
 
346
338
  this.handleRuntimeLicense(this._loadingElement);
347
339
 
@@ -4,8 +4,8 @@ import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
4
4
  import { PUBLIC_KEY, VERSION } from "../engine_constants.js";
5
5
  import { registerLoader } from "../engine_gltf.js";
6
6
  import { hasCommercialLicense } from "../engine_license.js";
7
- import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "../engine_loaders.js";
8
- import { NeedleLoader } from "../engine_scenetools.js";
7
+ import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "../engine_loaders.gltf.js";
8
+ import { NeedleLoader } from "../engine_loaders.js";
9
9
  import { Context, ContextCreateArgs } from "../engine_setup.js";
10
10
  import { type INeedleEngineComponent, type LoadedModel } from "../engine_types.js";
11
11
  import { getParam } from "../engine_utils.js";
@@ -3,7 +3,7 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
3
3
 
4
4
  import { InstantiateOptions } from "../engine/engine_gameobject.js";
5
5
  import { getLoader } from "../engine/engine_gltf.js";
6
- import * as loaders from "../engine/engine_loaders.js"
6
+ import * as loaders from "../engine/engine_loaders.gltf.js"
7
7
  import { Context } from "../engine/engine_setup.js";
8
8
  import * as utils from "../engine/engine_utils.js"
9
9
  import { GameObject } from "./Component.js";
@@ -110,6 +110,21 @@ export class ContactShadows extends Behaviour {
110
110
  */
111
111
  minSize?: Partial<Vec3>;
112
112
 
113
+ /**
114
+ * When enabled the shadows will not be updated automatically. Use `needsUpdate()` to update the shadows manually.
115
+ * This is useful when you want to update the shadows only when the scene changes.
116
+ */
117
+ manualUpdate: boolean = false;
118
+ /**
119
+ * Call this method to update the shadows manually. The update will be done in the next frame.
120
+ */
121
+ set needsUpdate(val: boolean) {
122
+ this._needsUpdate = val;
123
+ }
124
+ get needsUpdate(): boolean {
125
+ return this._needsUpdate;
126
+ }
127
+ private _needsUpdate: boolean = false;
113
128
 
114
129
  /** All shadow objects are parented to this object.
115
130
  * The gameObject itself should not be transformed because we want the ContactShadows object e.g. also have a GroundProjectedEnv component
@@ -288,6 +303,10 @@ export class ContactShadows extends Behaviour {
288
303
  else this.applyMinSize();
289
304
  }
290
305
 
306
+ onEnable(): void {
307
+ this._needsUpdate = true;
308
+ }
309
+
291
310
  /** @internal */
292
311
  onDestroy(): void {
293
312
  const instance = ContactShadows._instances.get(this.context);
@@ -313,6 +332,11 @@ export class ContactShadows extends Behaviour {
313
332
  /** @internal */
314
333
  onBeforeRender(_frame: XRFrame | null): void {
315
334
 
335
+ if (this.manualUpdate) {
336
+ if (!this._needsUpdate) return;
337
+ }
338
+ this._needsUpdate = false;
339
+
316
340
  if (!this.renderTarget || !this.renderTargetBlur ||
317
341
  !this.depthMaterial || !this.shadowCamera ||
318
342
  !this.blurPlane || !this.shadowGroup || !this.plane ||
@@ -3,13 +3,11 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
3
3
  import { XRControllerModelFactory } from "three/examples/jsm/webxr/XRControllerModelFactory.js";
4
4
  import { XRHandMeshModel } from "three/examples/jsm/webxr/XRHandMeshModel.js";
5
5
 
6
- import { showBalloonWarning } from "../../../engine/debug/index.js";
7
6
  import { AssetReference } from "../../../engine/engine_addressables.js";
8
7
  import { setDontDestroy } from "../../../engine/engine_gameobject.js";
9
8
  import { Gizmos } from "../../../engine/engine_gizmos.js";
10
9
  import { getLoader } from "../../../engine/engine_gltf.js";
11
- import { createBuiltinComponents } from "../../../engine/engine_gltf_builtin_components.js";
12
- import { addDracoAndKTX2Loaders } from "../../../engine/engine_loaders.js";
10
+ import { addDracoAndKTX2Loaders } from "../../../engine/engine_loaders.gltf.js";
13
11
  import { serializable } from "../../../engine/engine_serialization_decorator.js";
14
12
  import type { IGameObject, SourceIdentifier } from "../../../engine/engine_types.js";
15
13
  import { getParam } from "../../../engine/engine_utils.js";
@@ -306,7 +304,7 @@ export class XRControllerModel extends Behaviour {
306
304
  // @ts-ignore
307
305
  const handmesh = new XRHandMeshModel(handObject, hand, loader.path, filename, loader, (object: Object3D) => {
308
306
 
309
- const gltf = componentsExtension.gltf;
307
+ const gltf = componentsExtension?.gltf;
310
308
  // The XRHandMeshController removes the hand from the gltf before calling this callback
311
309
  // we need this in the GLTF scene however for creating the builtin components
312
310
  if (gltf?.scene.children?.length === 0) {
@@ -314,7 +312,8 @@ export class XRControllerModel extends Behaviour {
314
312
  }
315
313
 
316
314
  // console.log(controller.side, componentsExtension.gltf, object, componentsExtension.gltf.scene?.children)
317
- getLoader().createBuiltinComponents(comp.context, comp.sourceId || filename, componentsExtension.gltf, null, componentsExtension);
315
+ if (componentsExtension?.gltf)
316
+ getLoader().createBuiltinComponents(comp.context, comp.sourceId || filename, componentsExtension.gltf, null, componentsExtension);
318
317
 
319
318
  // The hand mesh should not receive raycasts
320
319
  object.traverse(child => {