@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,82 +1,360 @@
1
+ import { BufferGeometry, Cache, Camera, Color, Loader, Material, Mesh, MeshStandardMaterial, Object3D } from "three";
2
+ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
3
+ import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
4
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
5
+ import { USDZLoader } from 'three/examples/jsm/loaders/USDZLoader.js';
1
6
 
2
- import { addDracoAndKTX2Loaders as addDracoAndKTX2LoadersGLTFProgressive, configureLoader, createLoaders, setDracoDecoderLocation, setKTX2TranscoderLocation } from '@needle-tools/gltf-progressive';
3
- import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
4
- import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
5
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
6
- import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
7
-
7
+ import { showBalloonMessage } from "./debug/index.js";
8
+ import { getLoader, type INeedleGltfLoader, registerLoader } from "./engine_gltf.js";
9
+ import { createBuiltinComponents, writeBuiltinComponentData } from "./engine_gltf_builtin_components.js";
10
+ import { CustomLoader, registeredModelLoaderCallbacks, ValidLoaderReturnType } from "./engine_loaders.callbacks.js";
11
+ import * as loaders from "./engine_loaders.gltf.js"
12
+ import { registerPrewarmObject } from "./engine_mainloop_utils.js";
13
+ import { SerializationContext } from "./engine_serialization_core.js";
8
14
  import { Context } from "./engine_setup.js"
9
- import { getParam } from "./engine_utils.js";
15
+ import { postprocessFBXMaterials } from "./engine_three_utils.js";
16
+ import { CustomModel, isGLTFModel, Model, type UIDProvider } from "./engine_types.js";
17
+ import * as utils from "./engine_utils.js";
18
+ import { tryDetermineFileTypeFromURL } from "./engine_utils_format.js"
19
+ import { invokeLoadedImportPluginHooks, registerComponentExtension, registerExtensions } from "./extensions/extensions.js";
20
+ import { NEEDLE_components } from "./extensions/NEEDLE_components.js";
21
+
22
+ /** @internal */
23
+ export class NeedleLoader implements INeedleGltfLoader {
24
+ createBuiltinComponents(context: Context, gltfId: string, gltf: any, seed: number | UIDProvider | null, extension?: NEEDLE_components | undefined) {
25
+ return createBuiltinComponents(context, gltfId, gltf, seed, extension);
26
+ }
27
+ writeBuiltinComponentData(comp: any, context: SerializationContext) {
28
+ return writeBuiltinComponentData(comp, context);
29
+ }
30
+ parseSync(context: Context, data: string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<Model | undefined> {
31
+ return parseSync(context, data, path, seed);
32
+ }
33
+ loadSync(context: Context, url: string, sourceId: string, seed: number | UIDProvider | null, prog?: ((ProgressEvent: any) => void) | undefined): Promise<Model | undefined> {
34
+ return loadSync(context, url, sourceId, seed, prog);
35
+ }
36
+ }
37
+ registerLoader(NeedleLoader); // Register the loader
38
+
39
+
40
+ const printGltf = utils.getParam("printGltf") || utils.getParam("printgltf");
41
+ const debugFileTypes = utils.getParam("debugfileformat");
42
+
10
43
 
11
- const debug = getParam("debugdecoders");
12
44
 
13
- let loaders: null | { dracoLoader: DRACOLoader, ktx2Loader: KTX2Loader, meshoptDecoder: typeof MeshoptDecoder } = null;
14
45
 
15
- function ensureLoaders() {
16
- if (!loaders) {
17
- const res = createLoaders(null);
18
- loaders = { dracoLoader: res.dracoLoader, ktx2Loader: res.ktx2Loader, meshoptDecoder: res.meshoptDecoder };
46
+ export async function onCreateLoader(url: string, context: Context): Promise<CustomLoader | GLTFLoader | FBXLoader | USDZLoader | OBJLoader | null> {
47
+
48
+ const type = await tryDetermineFileTypeFromURL(url, { useExtension: true }) || "unknown";
49
+ if (debugFileTypes) console.debug(`Determined file type: '${type}' for url '${url}'`);
50
+
51
+ for (const callback of registeredModelLoaderCallbacks) {
52
+ const loader = callback({ context, url, filetype: type });
53
+ if (loader) {
54
+ console.debug("Using custom loader");
55
+ return loader;
56
+ }
19
57
  }
20
- return loaders;
21
- }
22
58
 
23
- export function setDracoDecoderPath(path: string | undefined) {
24
- if (path !== undefined && typeof path === "string") {
25
- setDracoDecoderLocation(path);
59
+ switch (type) {
60
+ default:
61
+ console.warn(`Unknown file type: ${type}`);
62
+ case "unknown":
63
+ {
64
+ console.warn("Unknown file type. Assuming glTF:", url);
65
+ const loader = new GLTFLoader();
66
+ await registerExtensions(loader, context, url);
67
+ return loader;
68
+ }
69
+ case "fbx":
70
+ return new FBXLoader();
71
+ case "obj":
72
+ return new OBJLoader();
73
+ case "usd":
74
+ case "usda":
75
+ case "usdz":
76
+ {
77
+ console.warn(type.toUpperCase() + " files are not supported.");
78
+ // return new USDZLoader();
79
+ return null;
80
+ }
81
+ case "gltf":
82
+ case "glb":
83
+ case "vrm":
84
+ {
85
+ const loader = new GLTFLoader();
86
+ await registerExtensions(loader, context, url);
87
+ return loader;
88
+ }
26
89
  }
27
90
  }
28
91
 
29
- export function setDracoDecoderType(type: string | undefined) {
30
- if (type !== undefined && typeof type === "string") {
31
- if (type !== "js") {
32
- const loaders = ensureLoaders();
33
- if (debug) console.log("Setting draco decoder type to", type);
34
- loaders.dracoLoader.setDecoderConfig({ type: type });
35
- }
36
- }
92
+ /**
93
+ * Load a 3D model file from a remote URL
94
+ * @param url URL to glTF, FBX or OBJ file
95
+ * @param options
96
+ * @returns A promise that resolves to the loaded model or undefined if the loading failed
97
+ */
98
+ export function loadAsset(url: string, options?: { context?: Context, path?: string, seed?: number, onprogress?: (evt: ProgressEvent) => void }): Promise<Model | undefined> {
99
+ return loadSync(options?.context || Context.Current, url, url, options?.seed || null, options?.onprogress);
37
100
  }
38
101
 
39
- export function setKtx2TranscoderPath(path: string) {
40
- if (path !== undefined && typeof path === "string") {
41
- setKTX2TranscoderLocation(path);
102
+ /** Load a gltf file from a url. This is the core method used by Needle Engine to load gltf files. All known extensions are registered here.
103
+ * @param context The current context
104
+ * @param data The gltf data as string or ArrayBuffer
105
+ * @param path The path to the gltf file
106
+ * @param seed The seed for generating unique ids
107
+ * @returns The loaded gltf object
108
+ */
109
+ export async function parseSync(context: Context, data: string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<Model | undefined> {
110
+ if (typeof path !== "string") {
111
+ console.warn("Parse gltf binary without path, this might lead to errors in resolving extensions. Please provide the source path of the gltf/glb file", path, typeof path);
112
+ path = "";
42
113
  }
43
- }
114
+ if (printGltf) console.log("Parse glTF", path)
115
+ const loader = await onCreateLoader(path, context);
116
+ if (!loader) {
117
+ return undefined;
118
+ }
119
+
120
+ const { componentsExtension } = onBeforeLoad(loader, context);
44
121
 
45
- export function setMeshoptDecoder(_meshoptDecoder: any) {
46
- if (_meshoptDecoder !== undefined) {
47
- const loaders = ensureLoaders();
48
- loaders.meshoptDecoder = _meshoptDecoder;
122
+ // Handle OBJ Loader
123
+ if (loader instanceof OBJLoader) {
124
+ if (typeof data !== "string") { data = new TextDecoder().decode(data); }
125
+ const res = loader.parse(data);
126
+ return await onAfterLoaded(loader, context, path, res, seed, componentsExtension);
127
+ }
128
+ // Handle any other loader that is not a GLTFLoader
129
+ const isNotGLTF = !(loader instanceof GLTFLoader);
130
+ if (isNotGLTF) {
131
+ if (loader.parse === undefined) {
132
+ console.error("Loader does not support parse");
133
+ return undefined;
134
+ }
135
+ const res = loader.parse(data, path);
136
+ return await onAfterLoaded(loader, context, path, res, seed, componentsExtension);
49
137
  }
138
+
139
+ return new Promise((resolve, reject) => {
140
+ try {
141
+
142
+ // GltfLoader expects a base path for resolving referenced assets
143
+ // https://threejs.org/docs/#examples/en/loaders/GLTFLoader.parse
144
+ // so we make sure that "path" is never a file path
145
+ let gltfLoaderPath = path.split("?")[0].trimEnd();
146
+ // This assumes that the path is a FILE path and not already a directory
147
+ // (it does not end with "/") – see https://linear.app/needle/issue/NE-6075
148
+ // strip file from path
149
+ const parts = gltfLoaderPath.split("/");
150
+ // check if the last part is a /, otherwise remove it
151
+ if (parts.length > 0 && parts[parts.length - 1] !== "")
152
+ parts.pop();
153
+ gltfLoaderPath = parts.join("/");
154
+ if (!gltfLoaderPath.endsWith("/")) gltfLoaderPath += "/";
155
+
156
+ loader.resourcePath = gltfLoaderPath;
157
+ loader.parse(data, "", async res => {
158
+ const model = await onAfterLoaded(loader, context, path, res, seed, componentsExtension);
159
+ resolve(model);
160
+
161
+ }, err => {
162
+ console.error("Loading asset at \"" + path + "\" failed\n", err);
163
+ resolve(undefined);
164
+ });
165
+ }
166
+ catch (err) {
167
+ console.error(err);
168
+ reject(err);
169
+ }
170
+ });
50
171
  }
51
172
 
52
173
  /**
53
- * Add Draco, Meshopt and KTX2 loaders to a GLTFLoader instance.
54
- * @param loader The GLTFLoader instance to add the loaders to.
55
- * @param context The context object containing the renderer.
56
- * @returns The GLTFLoader instance with the loaders added.
174
+ * Load a gltf file from a url. This is the core method used by Needle Engine to load gltf files. All known extensions are registered here.
175
+ * @param context The current context
176
+ * @param url The url to the gltf file
177
+ * @param sourceId The source id of the gltf file - this is usually the url
178
+ * @param seed The seed for generating unique ids
179
+ * @param prog A progress callback
180
+ * @returns The loaded gltf object
57
181
  */
58
- export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Pick<Context, "renderer">) {
182
+ export async function loadSync(context: Context, url: string, sourceId: string, seed: number | UIDProvider | null, prog?: (ProgressEvent) => void): Promise<Model | undefined> {
59
183
 
60
- const loaders = ensureLoaders();
184
+ checkIfUserAttemptedToLoadALocalFile(url);
61
185
 
62
- if (context.renderer) {
63
- loaders.ktx2Loader.detectSupport(context.renderer);
186
+ // better to create new loaders every time
187
+ // (maybe we can cache them...)
188
+ // but due to the async nature and potentially triggering multiple loads at the same time
189
+ // we need to make sure the extensions dont override each other
190
+ // creating new loaders should not be expensive as well
191
+ const loader = await onCreateLoader(url, context);
192
+ if (!loader) {
193
+ return undefined;
64
194
  }
65
- else
66
- console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures will probably fail");
67
195
 
68
- addDracoAndKTX2LoadersGLTFProgressive(loader);
196
+ const { componentsExtension } = onBeforeLoad(loader, context);
69
197
 
70
- if (!loader.dracoLoader)
71
- loader.setDRACOLoader(loaders.dracoLoader);
72
- if (!(loader as any).ktx2Loader)
73
- loader.setKTX2Loader(loaders.ktx2Loader);
74
- if (!(loader as any).meshoptDecoder)
75
- loader.setMeshoptDecoder(loaders.meshoptDecoder);
198
+ // Handle any loader that is not a GLTFLoader
199
+ if (!(loader instanceof GLTFLoader)) {
200
+ const res = await loader.loadAsync(url, prog);
201
+ return await onAfterLoaded(loader, context, url, res, seed, componentsExtension);
202
+ }
76
203
 
77
- configureLoader(loader, {
78
- progressive: true,
204
+ return new Promise((resolve, reject) => {
205
+ try {
206
+ loader.load(url, async res => {
207
+ const model = await onAfterLoaded(loader, context, sourceId, res, seed, componentsExtension);
208
+ resolve(model);
209
+ }, evt => {
210
+ prog?.call(loader, evt);
211
+ }, err => {
212
+ console.error("Loading asset at \"" + url + "\" failed\n", err);
213
+ resolve(undefined);
214
+ });
215
+ }
216
+ catch (err) {
217
+ console.error(err);
218
+ reject(err);
219
+ }
79
220
  });
221
+ }
222
+
223
+ /** Call before loading a model */
224
+ function onBeforeLoad(loader: Loader | CustomLoader, context: Context): { componentsExtension: NEEDLE_components | null } {
225
+ const componentsExtension = registerComponentExtension(loader);
226
+ if (loader instanceof GLTFLoader) {
227
+ loaders.addDracoAndKTX2Loaders(loader, context);
228
+ }
229
+ return { componentsExtension };
230
+ }
80
231
 
81
- return loader;
232
+
233
+ /** Call after a 3d model has been loaded to compile shaders and construct the needle engine model structure with relevant metadata (if necessary) */
234
+ async function onAfterLoaded(loader: Loader | CustomLoader, context: Context, gltfId: string, model: ValidLoaderReturnType, seed: number | null | UIDProvider, componentsExtension: NEEDLE_components | null): Promise<Model> {
235
+ if (printGltf) console.warn("Loaded", gltfId, model);
236
+
237
+ // Handle loader was registered but no model was returned - should not completely break the engine
238
+ if (model == null) {
239
+ console.error(`Loaded model is null '${gltfId}' - please make sure the loader is registered correctly`);
240
+ return {
241
+ scene: new Object3D(),
242
+ animations: [],
243
+ scenes: []
244
+ };
245
+ }
246
+
247
+ // Handle OBJ or FBX loader results
248
+ if (model instanceof Object3D) {
249
+ model = {
250
+ scene: model,
251
+ animations: model.animations,
252
+ scenes: [model]
253
+ }
254
+ }
255
+ // Handle STL loader results
256
+ else if (model instanceof BufferGeometry) {
257
+ const mat = new MeshStandardMaterial({
258
+ color: new Color(0xdddddd)
259
+ });
260
+ const mesh = new Mesh(model, mat);
261
+ model = {
262
+ scene: mesh,
263
+ animations: [],
264
+ scenes: [mesh]
265
+ }
266
+ }
267
+
268
+
269
+ // Remove query parameters from gltfId
270
+ if (gltfId.includes("?")) {
271
+ gltfId = gltfId.split("?")[0];
272
+ }
273
+
274
+ // assign animations of loaded glTF to all scenes
275
+ for (const scene of model.scenes) {
276
+ if (scene && !scene.animations?.length) {
277
+ scene.animations = [...model.animations];
278
+ }
279
+ }
280
+
281
+ // E.g. fbx material cleanup
282
+ postprocessLoadedFile(loader, model);
283
+
284
+ // load components
285
+ if (isGLTFModel(model)) {
286
+ invokeLoadedImportPluginHooks(gltfId, model, context);
287
+ await getLoader().createBuiltinComponents(context, gltfId, model, seed, componentsExtension || undefined);
288
+ }
289
+
290
+ // Warmup the scene
291
+ await compileAsync(model.scene, context, context.mainCamera);
292
+
293
+ return model;
82
294
  }
295
+
296
+ async function compileAsync(scene: Object3D, context: Context, camera?: Camera | null) {
297
+ if (!camera) camera = context.mainCamera;
298
+ try {
299
+ if (camera) {
300
+ await context.renderer.compileAsync(scene, camera, context.scene)
301
+ .catch(err => {
302
+ console.warn(err.message);
303
+ });
304
+ }
305
+ else
306
+ registerPrewarmObject(scene, context);
307
+ }
308
+ catch (err: Error | any) {
309
+ console.warn(err?.message || err);
310
+ }
311
+ }
312
+
313
+ function checkIfUserAttemptedToLoadALocalFile(url: string) {
314
+ const fullurl = new URL(url, window.location.href).href;
315
+ if (fullurl.startsWith("file://")) {
316
+ const msg = "Hi - it looks like you are trying to load a local file which will not work. You need to use a webserver to serve your files.\nPlease refer to the documentation on <a href=\"https://fwd.needle.tools/needle-engine/docs/local-server\">https://docs.needle.tools</a> or ask for help in our <a href=\"https://discord.needle.tools\">discord community</a>";
317
+ showBalloonMessage(msg);
318
+ console.warn(msg)
319
+ }
320
+ }
321
+
322
+ // function _downloadGltf(data: string | ArrayBuffer) {
323
+ // if (typeof data === "string") {
324
+ // const a = document.createElement("a") as HTMLAnchorElement;
325
+ // a.href = data;
326
+ // a.download = data.split("/").pop()!;
327
+ // a.click();
328
+ // }
329
+ // else {
330
+ // const blob = new Blob([data], { type: "application/octet-stream" });
331
+ // const url = window.URL.createObjectURL(blob);
332
+ // const a = document.createElement("a") as HTMLAnchorElement;
333
+ // a.href = url;
334
+ // a.download = "download.glb";
335
+ // a.click();
336
+ // }
337
+ // }
338
+
339
+ /**
340
+ * Postprocess the loaded file. This is used to apply any custom postprocessing to the loaded file.
341
+ */
342
+ function postprocessLoadedFile(loader: object, result: Model) {
343
+
344
+ if (loader instanceof FBXLoader || loader instanceof OBJLoader) {
345
+
346
+ let obj: Object3D | Model = result;
347
+ if (!(obj instanceof Object3D)) {
348
+ obj = (result as GLTF).scene;
349
+ }
350
+
351
+ obj.traverse((child) => {
352
+ const mesh = child as Mesh;
353
+ // See https://github.com/needle-tools/three.js/blob/b8df3843ff123ac9dc0ed0d3ccc5b568f840c804/examples/webgl_loader_multiple.html#L377
354
+ if (mesh?.isMesh) {
355
+ postprocessFBXMaterials(mesh, mesh.material as Material);
356
+ }
357
+ });
358
+ }
359
+ }
360
+
@@ -1,5 +1,5 @@
1
1
  import type { QueryFilterFlags, World } from "@dimforge/rapier3d-compat";
2
- import { AnimationClip, Color, Material, Mesh, Object3D, Quaternion } from "three";
2
+ import { AnimationClip, Color, Loader, Material, Mesh, Object3D, Quaternion } from "three";
3
3
  import { Vector3 } from "three";
4
4
  import { type GLTF as THREE_GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
5
5
 
@@ -21,8 +21,10 @@ type NoInternals<T> = FilterStartingWith<T, "_">;
21
21
  type NoInternalNeedleEngineState<T> = Omit<T, "destroyed" | "gameObject" | "activeAndEnabled" | "context" | "isComponent" | "scene" | "up" | "forward" | "right" | "worldRotation" | "worldEuler" | "worldPosition" | "worldQuaternion">;
22
22
  export type ComponentInit<T> = Partial<NoInternalNeedleEngineState<NoInternals<NoUndefinedNoFunctions<T>>>>
23
23
 
24
+
24
25
  /** GLTF, GLB or VRM */
25
26
  export type GLTF = THREE_GLTF;
27
+
26
28
  /** FBX type */
27
29
  export type FBX = {
28
30
  animations: AnimationClip[];
@@ -35,8 +37,22 @@ export type OBJ = {
35
37
  scene: Object3D;
36
38
  scenes: Object3D[];
37
39
  }
40
+ export type CustomModel = {
41
+ animations: AnimationClip[];
42
+ scene: Object3D;
43
+ scenes: Object3D[];
44
+ }
45
+
38
46
  /** All possible model types that Needle Engine can load */
39
- export type Model = (GLTF | FBX | OBJ);
47
+ export type Model = (GLTF | FBX | OBJ | CustomModel);
48
+
49
+ export function isGLTFModel(model: Model): model is GLTF {
50
+ const gltf = model as GLTF;
51
+ if (gltf.parser && gltf.parser.json) {
52
+ return true;
53
+ }
54
+ return false;
55
+ }
40
56
 
41
57
  /** A loaded model */
42
58
  export type LoadedModel = {
@@ -218,7 +234,7 @@ export interface IComponent extends IHasGuid {
218
234
  get worldQuaternion(): Quaternion;
219
235
  }
220
236
 
221
- export function isComponent(obj:any) : obj is IComponent {
237
+ export function isComponent(obj: any): obj is IComponent {
222
238
  return obj && obj.isComponent;
223
239
  }
224
240
 
@@ -547,14 +563,14 @@ export interface IPhysicsEngine {
547
563
  * @param collider The collider component to add
548
564
  */
549
565
  addSphereCollider(collider: ICollider);
550
-
566
+
551
567
  /**
552
568
  * Adds a box collider to the physics world
553
569
  * @param collider The collider component to add
554
570
  * @param size The size of the box
555
571
  */
556
572
  addBoxCollider(collider: ICollider, size: Vector3);
557
-
573
+
558
574
  /**
559
575
  * Adds a capsule collider to the physics world
560
576
  * @param collider The collider component to add
@@ -562,7 +578,7 @@ export interface IPhysicsEngine {
562
578
  * @param height The height of the capsule
563
579
  */
564
580
  addCapsuleCollider(collider: ICollider, radius: number, height: number);
565
-
581
+
566
582
  /**
567
583
  * Adds a mesh collider to the physics world
568
584
  * @param collider The collider component to add
@@ -584,34 +600,34 @@ export interface IPhysicsEngine {
584
600
  * @param rb The rigidbody to wake up
585
601
  */
586
602
  wakeup(rb: IRigidbody);
587
-
603
+
588
604
  /**
589
605
  * Checks if a rigidbody is currently sleeping
590
606
  * @param rb The rigidbody to check
591
607
  * @returns Whether the rigidbody is sleeping or undefined if cannot be determined
592
608
  */
593
609
  isSleeping(rb: IRigidbody): boolean | undefined;
594
-
610
+
595
611
  /**
596
612
  * Updates the physical properties of a rigidbody or collider
597
613
  * @param rb The rigidbody or collider to update
598
614
  */
599
615
  updateProperties(rb: IRigidbody | ICollider);
600
-
616
+
601
617
  /**
602
618
  * Resets all forces acting on a rigidbody
603
619
  * @param rb The rigidbody to reset forces on
604
620
  * @param wakeup Whether to wake up the rigidbody
605
621
  */
606
622
  resetForces(rb: IRigidbody, wakeup: boolean);
607
-
623
+
608
624
  /**
609
625
  * Resets all torques acting on a rigidbody
610
626
  * @param rb The rigidbody to reset torques on
611
627
  * @param wakeup Whether to wake up the rigidbody
612
628
  */
613
629
  resetTorques(rb: IRigidbody, wakeup: boolean);
614
-
630
+
615
631
  /**
616
632
  * Adds a continuous force to a rigidbody
617
633
  * @param rb The rigidbody to add force to
@@ -619,7 +635,7 @@ export interface IPhysicsEngine {
619
635
  * @param wakeup Whether to wake up the rigidbody
620
636
  */
621
637
  addForce(rb: IRigidbody, vec: Vec3, wakeup: boolean);
622
-
638
+
623
639
  /**
624
640
  * Applies an instantaneous impulse to a rigidbody
625
641
  * @param rb The rigidbody to apply impulse to
@@ -627,21 +643,21 @@ export interface IPhysicsEngine {
627
643
  * @param wakeup Whether to wake up the rigidbody
628
644
  */
629
645
  applyImpulse(rb: IRigidbody, vec: Vec3, wakeup: boolean);
630
-
646
+
631
647
  /**
632
648
  * Gets the linear velocity of a rigidbody or the rigidbody attached to a collider
633
649
  * @param rb The rigidbody or collider to get velocity from
634
650
  * @returns The linear velocity vector or null if not available
635
651
  */
636
652
  getLinearVelocity(rb: IRigidbody | ICollider): Vec3 | null;
637
-
653
+
638
654
  /**
639
655
  * Gets the angular velocity of a rigidbody
640
656
  * @param rb The rigidbody to get angular velocity from
641
657
  * @returns The angular velocity vector or null if not available
642
658
  */
643
659
  getAngularVelocity(rb: IRigidbody): Vec3 | null;
644
-
660
+
645
661
  /**
646
662
  * Sets the angular velocity of a rigidbody
647
663
  * @param rb The rigidbody to set angular velocity for
@@ -649,7 +665,7 @@ export interface IPhysicsEngine {
649
665
  * @param wakeup Whether to wake up the rigidbody
650
666
  */
651
667
  setAngularVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean);
652
-
668
+
653
669
  /**
654
670
  * Sets the linear velocity of a rigidbody
655
671
  * @param rb The rigidbody to set linear velocity for
@@ -665,13 +681,13 @@ export interface IPhysicsEngine {
665
681
  * @param rotation Whether to update the rotation
666
682
  */
667
683
  updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean);
668
-
684
+
669
685
  /**
670
686
  * Removes a physics body from the simulation
671
687
  * @param body The component whose physics body should be removed
672
688
  */
673
689
  removeBody(body: IComponent);
674
-
690
+
675
691
  /**
676
692
  * Gets the physics body for a component
677
693
  * @param obj The collider or rigidbody component