@needle-tools/engine 4.5.0-alpha.2 → 4.5.0-alpha.4

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 (39) hide show
  1. package/CHANGELOG.md +23 -2
  2. package/dist/{needle-engine.bundle-a52706c5.umd.cjs → needle-engine.bundle-1ea82d1b.umd.cjs} +74 -73
  3. package/dist/{needle-engine.bundle-f3c8cffc.light.umd.cjs → needle-engine.bundle-5799046c.light.umd.cjs} +74 -73
  4. package/dist/{needle-engine.bundle-53f80c62.js → needle-engine.bundle-613ac05e.js} +757 -750
  5. package/dist/{needle-engine.bundle-1526f05b.light.js → needle-engine.bundle-82027067.light.js} +944 -937
  6. package/dist/{needle-engine.bundle-15b19b2c.light.min.js → needle-engine.bundle-cb8d3ee9.light.min.js} +74 -73
  7. package/dist/{needle-engine.bundle-2024e2b3.min.js → needle-engine.bundle-e8936122.min.js} +74 -73
  8. package/dist/needle-engine.js +24 -24
  9. package/dist/needle-engine.light.js +24 -24
  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/engine_components.js +6 -14
  15. package/lib/engine/engine_components.js.map +1 -1
  16. package/lib/engine/engine_context_registry.d.ts +2 -2
  17. package/lib/engine/engine_context_registry.js +2 -2
  18. package/lib/engine/engine_context_registry.js.map +1 -1
  19. package/lib/engine/engine_loaders.callbacks.d.ts +43 -9
  20. package/lib/engine/engine_loaders.callbacks.js +42 -12
  21. package/lib/engine/engine_loaders.callbacks.js.map +1 -1
  22. package/lib/engine/engine_loaders.js +18 -15
  23. package/lib/engine/engine_loaders.js.map +1 -1
  24. package/lib/engine/engine_utils_format.d.ts +5 -5
  25. package/lib/engine/engine_utils_format.js +31 -31
  26. package/lib/engine/engine_utils_format.js.map +1 -1
  27. package/lib/engine-components/ContactShadows.d.ts +12 -0
  28. package/lib/engine-components/ContactShadows.js +23 -0
  29. package/lib/engine-components/ContactShadows.js.map +1 -1
  30. package/lib/engine-components/webxr/controllers/XRControllerModel.js.map +1 -1
  31. package/package.json +1 -1
  32. package/plugins/vite/dependencies.js +1 -1
  33. package/src/engine/engine_components.ts +4 -12
  34. package/src/engine/engine_context_registry.ts +2 -2
  35. package/src/engine/engine_loaders.callbacks.ts +63 -17
  36. package/src/engine/engine_loaders.ts +17 -15
  37. package/src/engine/engine_utils_format.ts +45 -34
  38. package/src/engine-components/ContactShadows.ts +24 -0
  39. package/src/engine-components/webxr/controllers/XRControllerModel.ts +0 -2
@@ -1,11 +1,12 @@
1
1
  import { BufferGeometry, Object3D } from "three";
2
2
  import { Context } from "./engine_setup.js";
3
3
  import { CustomModel } from "./engine_types.js";
4
- import { type FileType } from "./engine_utils_format.js";
4
+ import { type NeedleMimetype } from "./engine_utils_format.js";
5
5
 
6
6
  export type ValidLoaderReturnType = CustomModel | Object3D | BufferGeometry;
7
7
 
8
8
 
9
+
9
10
  /** @internal */
10
11
  export type CustomLoader = {
11
12
  /** The name of the loader. This is used for debugging purposes. */
@@ -16,12 +17,12 @@ export type CustomLoader = {
16
17
  parse: (buffer: ArrayBuffer | string, path: string) => ValidLoaderReturnType,
17
18
  };
18
19
 
19
- type CustomLoaderCallback = (args: { context: Context, url: string, filetype: FileType }) => CustomLoader | null | undefined | void;
20
+ type CustomLoaderCallback = (args: { context: Context, url: string, mimetype: NeedleMimetype }) => CustomLoader | Promise<CustomLoader> | null | undefined | void;
20
21
  /** @internal */
21
- export const registeredModelLoaderCallbacks: Array<CustomLoaderCallback> = [];
22
+ export const registeredModelLoaderCallbacks: Array<{ name?: string, priority: number, callback: CustomLoaderCallback }> = [];
22
23
 
23
24
 
24
- type FileTypeCallback = (args: {
25
+ type MimetypeCallback = (args: {
25
26
  /** The URL of the file to load */
26
27
  url: string,
27
28
  /** The response of the range request with the first few bytes of the file (bytes are available in the 'args.bytes' property of this callback) */
@@ -30,22 +31,58 @@ type FileTypeCallback = (args: {
30
31
  contentType: string | null,
31
32
  /** The first few bytes of the file as a Uint8Array */
32
33
  bytes: Uint8Array
33
- }) => FileType | null;
34
+ }) => NeedleMimetype | null;
34
35
  /** @internal */
35
- export const registeredFileTypeCallbacks: Array<FileTypeCallback> = [];
36
+ export const registeredMimetypeCallbacks: Array<MimetypeCallback> = [];
36
37
 
37
38
 
38
39
  /**
39
40
  * NeedleEngineModelLoader is a namespace that provides functions to register custom model loaders and mimetype callbacks.
40
41
  * It allows you to create custom loaders for specific file types and determine the mimetype of files based on their content.
42
+ * @example
43
+ * ```ts
44
+ * import { NeedleEngineModelLoader } from "@needle-tools/engine";
45
+ * import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
46
+ *
47
+ * NeedleEngineModelLoader.onCreateCustomModelLoader(args => {
48
+ * if (args.mimetype === "model/stl") {
49
+ * return new STLLoader();
50
+ * }
51
+ * });
52
+ *
53
+ * NeedleEngineModelLoader.onDetermineModelMimetype((args) => {
54
+ * // detect stl mimetype
55
+ * const bytes = args.bytes;
56
+ * if (bytes[0] === 0x73 && bytes[1] === 0x74 && bytes[2] === 0x6c) {
57
+ * return "model/stl";
58
+ * }
59
+ * return null;
60
+ * });
61
+ * ```
41
62
  */
42
63
  export namespace NeedleEngineModelLoader {
43
64
 
44
- // export type ModelLoaderPlugin = {
45
- // name: string,
46
- // canLoad: (url: string, mimetype: string) => boolean,
65
+ // export type Plugin = {
66
+ // readonly name: string;
67
+ // canLoad: (mimetype: string, url: string) => boolean,
68
+ // createLoader: (url: string, mimetype: string) => Promise<CustomLoader>,
69
+ // }
70
+
71
+ // export function registerLoaderPlugin(plugin: Plugin) {
47
72
  // }
48
-
73
+
74
+
75
+
76
+ type CustomLoaderOptions = {
77
+ /** The name of the loader. This is used for debugging purposes. */
78
+ name?: string,
79
+ /**
80
+ * The priority of the loader. Higher priority loaders will be called first.
81
+ * @default 0
82
+ */
83
+ priority?: number,
84
+ }
85
+
49
86
  /**
50
87
  * Register a custom loader callback. For every file that is requested this callback is called with the url and mimetype. It should return a custom loader or null if it does not want to handle the file.
51
88
  * @param callback The callback to register
@@ -60,10 +97,19 @@ export namespace NeedleEngineModelLoader {
60
97
  * return null;
61
98
  * });
62
99
  */
63
- export function onCreateCustomModelLoader(callback: CustomLoaderCallback) {
64
- registeredModelLoaderCallbacks.push(callback);
100
+ export function onCreateCustomModelLoader(callback: CustomLoaderCallback, opts?: CustomLoaderOptions) {
101
+ const entry = { name: opts?.name, priority: opts?.priority ?? 0, callback };
102
+ registeredModelLoaderCallbacks.push(entry);
103
+
104
+ // sort plugins by priority. Higher priority first
105
+ registeredModelLoaderCallbacks.sort((a, b) => {
106
+ if (a.priority === b.priority) return 0;
107
+ if (a.priority > b.priority) return -1;
108
+ return 1;
109
+ });
110
+
65
111
  return () => {
66
- const index = registeredModelLoaderCallbacks.indexOf(callback);
112
+ const index = registeredModelLoaderCallbacks.indexOf(entry);
67
113
  if (index >= 0) {
68
114
  registeredModelLoaderCallbacks.splice(index, 1);
69
115
  }
@@ -76,12 +122,12 @@ export namespace NeedleEngineModelLoader {
76
122
  * @returns A function to unregister the callback
77
123
  *
78
124
  */
79
- export function onDetermineModelFiletype(callback: FileTypeCallback): (() => void) {
80
- registeredFileTypeCallbacks.push(callback);
125
+ export function onDetermineModelMimetype(callback: MimetypeCallback): (() => void) {
126
+ registeredMimetypeCallbacks.push(callback);
81
127
  return () => {
82
- const index = registeredFileTypeCallbacks.indexOf(callback);
128
+ const index = registeredMimetypeCallbacks.indexOf(callback);
83
129
  if (index >= 0) {
84
- registeredFileTypeCallbacks.splice(index, 1);
130
+ registeredMimetypeCallbacks.splice(index, 1);
85
131
  }
86
132
  }
87
133
  }
@@ -15,7 +15,7 @@ import { Context } from "./engine_setup.js"
15
15
  import { postprocessFBXMaterials } from "./engine_three_utils.js";
16
16
  import { CustomModel, isGLTFModel, Model, type UIDProvider } from "./engine_types.js";
17
17
  import * as utils from "./engine_utils.js";
18
- import { tryDetermineFileTypeFromURL } from "./engine_utils_format.js"
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
21
 
@@ -45,42 +45,44 @@ const debugFileTypes = utils.getParam("debugfileformat");
45
45
 
46
46
  export async function onCreateLoader(url: string, context: Context): Promise<CustomLoader | GLTFLoader | FBXLoader | USDZLoader | OBJLoader | null> {
47
47
 
48
- const type = await tryDetermineFileTypeFromURL(url, { useExtension: true }) || "unknown";
48
+ const type = await tryDetermineMimetypeFromURL(url, { useExtension: true }) || "unknown";
49
49
  if (debugFileTypes) console.debug(`Determined file type: '${type}' for url '${url}'`);
50
50
 
51
- for (const callback of registeredModelLoaderCallbacks) {
52
- const loader = callback({ context, url, filetype: type });
51
+ for (const entry of registeredModelLoaderCallbacks) {
52
+ const { callback } = entry;
53
+ let loader = callback({ context, url, mimetype: type });
54
+ if (loader instanceof Promise) await loader;
53
55
  if (loader) {
54
- console.debug("Using custom loader");
56
+ console.debug(`Using custom loader (${entry.name}) for url '${url}'`);
55
57
  return loader;
56
58
  }
57
59
  }
58
60
 
59
61
  switch (type) {
60
62
  default:
61
- console.warn(`Unknown file type: ${type}`);
62
63
  case "unknown":
63
64
  {
64
- console.warn("Unknown file type. Assuming glTF:", url);
65
+ console.warn(`Unknown file type (${type}). Needle Engine will fallback to the GLTFLoader - To support more model formats please create a Needle loader plugin.\nUse import { NeedleEngineModelLoader } from \"@needle-tools/engine\" namespace to register your loader.`, url);
65
66
  const loader = new GLTFLoader();
66
67
  await registerExtensions(loader, context, url);
67
68
  return loader;
68
69
  }
69
- case "fbx":
70
+ case "model/fbx":
71
+ case "model/vnd.autodesk.fbx":
70
72
  return new FBXLoader();
71
- case "obj":
73
+ case "model/obj":
72
74
  return new OBJLoader();
73
- case "usd":
74
- case "usda":
75
- case "usdz":
75
+ case "model/vnd.usdz+zip":
76
+ case "model/vnd.usd+zip":
77
+ case "model/vnd.usda+zip":
76
78
  {
77
79
  console.warn(type.toUpperCase() + " files are not supported.");
78
80
  // return new USDZLoader();
79
81
  return null;
80
82
  }
81
- case "gltf":
82
- case "glb":
83
- case "vrm":
83
+ case "model/gltf+json":
84
+ case "model/gltf-binary":
85
+ case "model/vrm":
84
86
  {
85
87
  const loader = new GLTFLoader();
86
88
  await registerExtensions(loader, context, url);
@@ -1,13 +1,24 @@
1
1
  import { isDevEnvironment } from "./debug/index.js";
2
- import { NeedleEngineModelLoader, registeredFileTypeCallbacks } from "./engine_loaders.callbacks.js";
2
+ import { registeredMimetypeCallbacks } from "./engine_loaders.callbacks.js";
3
3
  import { getParam } from "./engine_utils.js";
4
4
 
5
5
  const debug = getParam("debugfileformat");
6
-
7
6
  /**
8
- * The supported file types that can be determined by the engine. Used in {@link tryDetermineFileTypeFromURL} and {@link tryDetermineFileTypeFromBinary}
7
+ * The supported file types that can be determined by the engine. Used in {@link tryDetermineMimetypeFromURL} and {@link tryDetermineMimetypeFromBinary}
9
8
  */
10
- export declare type FileType = "gltf" | "glb" | "vrm" | "fbx" | "obj" | "usdz" | "usd" | "usda" | "unknown" | ({} & string);
9
+ export type NeedleMimetype = "unknown" |
10
+ "model/gltf+json" |
11
+ "model/gltf-binary" |
12
+ "model/vrm" |
13
+ "model/vnd.usdz+zip" |
14
+ "model/vnd.usd+zip" |
15
+ "model/vnd.usda+zip" |
16
+ "model/fbx" |
17
+ "model/vnd.autodesk.fbx" |
18
+ "model/obj" |
19
+ `model/${string}`
20
+ | (string & {})
21
+
11
22
 
12
23
  /**
13
24
  * Tries to determine the file type of a file from its URL
@@ -21,7 +32,7 @@ export declare type FileType = "gltf" | "glb" | "vrm" | "fbx" | "obj" | "usdz" |
21
32
  * const fileType = await tryDetermineFileTypeFromURL(url);
22
33
  * console.log(fileType); // "glb"
23
34
  */
24
- export async function tryDetermineFileTypeFromURL(url: string, opts: { useExtension: boolean }): Promise<FileType> {
35
+ export async function tryDetermineMimetypeFromURL(url: string, opts: { useExtension: boolean }): Promise<NeedleMimetype> {
25
36
 
26
37
  const { useExtension = true } = opts;
27
38
 
@@ -44,21 +55,21 @@ export async function tryDetermineFileTypeFromURL(url: string, opts: { useExtens
44
55
  if (debug) console.debug("Use file extension to determine type: " + ext);
45
56
  switch (ext) {
46
57
  case "GLTF":
47
- return "gltf";
58
+ return "model/gltf+json"
48
59
  case "VRM":
49
- return "vrm";
60
+ return "model/vrm";
50
61
  case "GLB":
51
- return "glb";
62
+ return "model/gltf-binary";
52
63
  case "FBX":
53
- return "fbx";
64
+ return "model/fbx";
54
65
  case "USD":
55
- return "usd";
66
+ return "model/vnd.usd+zip";
56
67
  case "USDA":
57
- return "usda";
68
+ return "model/vnd.usda+zip";
58
69
  case "USDZ":
59
- return "usdz";
70
+ return "model/vnd.usdz+zip";
60
71
  case "OBJ":
61
- return "obj";
72
+ return "model/obj";
62
73
  }
63
74
  }
64
75
 
@@ -88,7 +99,7 @@ export async function tryDetermineFileTypeFromURL(url: string, opts: { useExtens
88
99
 
89
100
  if (header?.ok) {
90
101
  const data = await header.arrayBuffer();
91
- const res = tryDetermineFileTypeFromBinary(originalUrl, data, header);
102
+ const res = tryDetermineMimetypeFromBinary(originalUrl, data, header);
92
103
  if (debug) console.log("Determined file type from header: " + res);
93
104
  return res;
94
105
  }
@@ -100,7 +111,7 @@ export async function tryDetermineFileTypeFromURL(url: string, opts: { useExtens
100
111
  /** Attempts to determine the file type of a binary file by looking at the first few bytes of the file.
101
112
  * @hidden
102
113
  */
103
- export function tryDetermineFileTypeFromBinary(url: string, data: ArrayBuffer, response: Response): FileType {
114
+ export function tryDetermineMimetypeFromBinary(url: string, data: ArrayBuffer, response: Response): NeedleMimetype {
104
115
 
105
116
  if (data.byteLength < 4) {
106
117
  return "unknown";
@@ -112,67 +123,67 @@ export function tryDetermineFileTypeFromBinary(url: string, data: ArrayBuffer, r
112
123
  console.warn("Trying to determine file type from binary data\n", "\"" + new TextDecoder().decode(data) + "\"\n", bytes);
113
124
  }
114
125
 
115
- // GLTF or GLB
116
- if (bytes[0] == 103 && bytes[1] == 108 && bytes[2] == 84 && bytes[3] == 70) {
126
+ // GLTF
127
+ if (bytes[0] == 103 && bytes[1] == 108 && bytes[2] == 84 && bytes[3] == 70 && bytes[4] == 10) {
128
+ // GLTF
117
129
  console.debug("GLTF detected");
118
- return "glb";
130
+ return "model/gltf+json";
131
+ }
132
+ // GLB
133
+ else if (bytes[0] == 103 && bytes[1] == 108 && bytes[2] == 84 && bytes[3] == 70 && bytes[4] == 98) {
134
+ // GLB
135
+ console.debug("GLB detected");
136
+ return "model/gltf-binary";
119
137
  }
120
138
  // USDZ
121
139
  if (bytes[0] == 80 && bytes[1] == 75 && bytes[2] == 3 && bytes[3] == 4) {
122
140
  console.debug("USDZ detected");
123
- return "usdz";
141
+ return "model/vnd.usdz+zip";
124
142
  }
125
143
  // USD
126
144
  if (bytes[0] == 80 && bytes[1] == 88 && bytes[2] == 82 && bytes[3] == 45 && bytes[4] == 85 && bytes[5] == 83 && bytes[6] == 68 && bytes[7] == 67) {
127
145
  console.debug("Binary USD detected");
128
- return "usd";
146
+ return "model/vnd.usd+zip";
129
147
  }
130
148
  // USDA: check if the file starts with #usda
131
149
  else if (bytes[0] == 35 && bytes[1] == 117 && bytes[2] == 115 && bytes[3] == 100 && bytes[4] == 97) {
132
150
  console.debug("ASCII USD detected");
133
- return "usda";
151
+ return "model/vnd.usda+zip";
134
152
  }
135
153
  // FBX
136
154
  if (bytes[0] == 75 && bytes[1] == 97 && bytes[2] == 121 && bytes[3] == 100 && bytes[4] == 97 && bytes[5] == 114 && bytes[6] == 97 && bytes[7] == 32) {
137
155
  console.debug("Binary FBX detected");
138
- return "fbx";
156
+ return "model/fbx";
139
157
  }
140
158
  // ASCII FBX
141
159
  else if (bytes[0] == 59 && bytes[1] == 32 && bytes[2] == 70 && bytes[3] == 66 && bytes[4] == 88 && bytes[5] == 32) {
142
160
  console.debug("ASCII FBX detected");
143
- return "fbx";
161
+ return "model/fbx";
144
162
  }
145
163
  // OBJ - in this case exported from blender it starts with "# Blender" - we only check the first 10 bytes, technically it could still be a different file so we should do this check at the end
146
164
  else if (bytes[0] == 35 && bytes[1] == 32 && bytes[2] == 66 && bytes[3] == 108 && bytes[4] == 101 && bytes[5] == 110 && bytes[6] == 100 && bytes[7] == 101 && bytes[8] == 114 && bytes[9] == 32) {
147
165
  console.debug("OBJ detected");
148
- return "obj";
166
+ return "model/obj";
149
167
  }
150
168
  // Check if it starts "# Alias OBJ"
151
169
  else if (bytes[0] == 35 && bytes[1] == 32 && bytes[2] == 65 && bytes[3] == 108 && bytes[4] == 105 && bytes[5] == 97 && bytes[6] == 115 && bytes[7] == 32 && bytes[8] == 79 && bytes[9] == 66 && bytes[10] == 74) {
152
170
  console.debug("OBJ detected");
153
- return "obj";
171
+ return "model/obj";
154
172
  }
155
173
  else if (response.headers.has("content-type")) {
156
174
  const content_type = response.headers.get("content-type");
157
175
  console.debug("Content-Type: " + content_type);
158
176
  switch (content_type) {
159
177
  case "model/gltf+json":
160
- return "gltf";
161
178
  case "model/gltf-binary":
162
- return "glb";
163
179
  case "model/vrm":
164
- return "vrm";
165
180
  case "model/vnd.usdz+zip":
166
- return "usdz";
167
181
  case "model/vnd.usd+zip":
168
- return "usd";
169
182
  case "model/vnd.usda+zip":
170
- return "usda";
171
183
  case "model/fbx":
172
184
  case "model/vnd.autodesk.fbx":
173
- return "fbx";
174
185
  case "model/obj":
175
- return "obj";
186
+ return content_type;
176
187
  // case "model/stl":
177
188
  // return "stl";
178
189
  case "text/plain":
@@ -205,7 +216,7 @@ export function tryDetermineFileTypeFromBinary(url: string, data: ArrayBuffer, r
205
216
  // return "gltf";
206
217
  // }
207
218
 
208
- for (const callback of registeredFileTypeCallbacks) {
219
+ for (const callback of registeredMimetypeCallbacks) {
209
220
  const mimetype = callback({
210
221
  url: url,
211
222
  response: response,
@@ -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,12 +3,10 @@ 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
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";