@needle-tools/engine 4.8.7 → 4.8.8-next.4b53212

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 (118) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +55 -42
  3. package/components.needle.json +1 -1
  4. package/dist/{needle-engine.bundle-B0qaChJt.js → needle-engine.bundle-9ZWPsgka.js} +7089 -7009
  5. package/dist/{needle-engine.bundle-CZBThBDy.min.js → needle-engine.bundle-B-wxYHwV.min.js} +144 -144
  6. package/dist/{needle-engine.bundle-lBmpWgFp.umd.cjs → needle-engine.bundle-CxUl1z77.umd.cjs} +147 -147
  7. package/dist/needle-engine.d.ts +7 -0
  8. package/dist/needle-engine.js +2 -2
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/lib/engine/engine_addressables.d.ts +12 -12
  12. package/lib/engine/engine_addressables.js +32 -23
  13. package/lib/engine/engine_addressables.js.map +1 -1
  14. package/lib/engine/engine_animation.d.ts +1 -3
  15. package/lib/engine/engine_animation.js +18 -12
  16. package/lib/engine/engine_animation.js.map +1 -1
  17. package/lib/engine/engine_assetdatabase.js +6 -6
  18. package/lib/engine/engine_assetdatabase.js.map +1 -1
  19. package/lib/engine/engine_camera.d.ts +23 -3
  20. package/lib/engine/engine_camera.js +34 -2
  21. package/lib/engine/engine_camera.js.map +1 -1
  22. package/lib/engine/engine_context.d.ts +15 -0
  23. package/lib/engine/engine_context.js +33 -0
  24. package/lib/engine/engine_context.js.map +1 -1
  25. package/lib/engine/engine_create_objects.d.ts +3 -3
  26. package/lib/engine/engine_create_objects.js +5 -4
  27. package/lib/engine/engine_create_objects.js.map +1 -1
  28. package/lib/engine/engine_gameobject.js +2 -2
  29. package/lib/engine/engine_gameobject.js.map +1 -1
  30. package/lib/engine/engine_gltf_builtin_components.js +11 -11
  31. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  32. package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
  33. package/lib/engine/engine_loaders.callbacks.js +1 -0
  34. package/lib/engine/engine_loaders.callbacks.js.map +1 -1
  35. package/lib/engine/engine_loaders.js +15 -11
  36. package/lib/engine/engine_loaders.js.map +1 -1
  37. package/lib/engine/extensions/NEEDLE_lighting_settings.js +5 -2
  38. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  39. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  40. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  41. package/lib/engine/js-extensions/Object3D.d.ts +1 -1
  42. package/lib/engine/js-extensions/Vector.d.ts +5 -0
  43. package/lib/engine/js-extensions/Vector.js.map +1 -1
  44. package/lib/engine/webcomponents/needle-engine.attributes.d.ts +1 -0
  45. package/lib/engine/webcomponents/needle-engine.d.ts +2 -2
  46. package/lib/engine/webcomponents/needle-engine.js +19 -21
  47. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  48. package/lib/engine-components/Animation.js +2 -1
  49. package/lib/engine-components/Animation.js.map +1 -1
  50. package/lib/engine-components/AnimationUtilsAutoplay.js +1 -6
  51. package/lib/engine-components/AnimationUtilsAutoplay.js.map +1 -1
  52. package/lib/engine-components/Camera.d.ts +2 -0
  53. package/lib/engine-components/Camera.js +5 -1
  54. package/lib/engine-components/Camera.js.map +1 -1
  55. package/lib/engine-components/ContactShadows.d.ts +12 -2
  56. package/lib/engine-components/ContactShadows.js +24 -4
  57. package/lib/engine-components/ContactShadows.js.map +1 -1
  58. package/lib/engine-components/DropListener.d.ts +21 -15
  59. package/lib/engine-components/DropListener.js +38 -34
  60. package/lib/engine-components/DropListener.js.map +1 -1
  61. package/lib/engine-components/LookAtConstraint.d.ts +5 -1
  62. package/lib/engine-components/LookAtConstraint.js +8 -0
  63. package/lib/engine-components/LookAtConstraint.js.map +1 -1
  64. package/lib/engine-components/NestedGltf.d.ts +9 -4
  65. package/lib/engine-components/NestedGltf.js +32 -26
  66. package/lib/engine-components/NestedGltf.js.map +1 -1
  67. package/lib/engine-components/OrbitControls.d.ts +30 -9
  68. package/lib/engine-components/OrbitControls.js +55 -18
  69. package/lib/engine-components/OrbitControls.js.map +1 -1
  70. package/lib/engine-components/Renderer.js +2 -1
  71. package/lib/engine-components/Renderer.js.map +1 -1
  72. package/lib/engine-components/Skybox.js +8 -9
  73. package/lib/engine-components/Skybox.js.map +1 -1
  74. package/lib/engine-components/api.d.ts +1 -0
  75. package/lib/engine-components/api.js.map +1 -1
  76. package/lib/engine-components/export/usdz/Extension.d.ts +1 -1
  77. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +1 -1
  78. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  79. package/lib/engine-components/export/usdz/USDZExporter.d.ts +7 -0
  80. package/lib/engine-components/export/usdz/USDZExporter.js +8 -1
  81. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  82. package/lib/engine-components/webxr/WebXRImageTracking.d.ts +4 -2
  83. package/lib/engine-components/webxr/WebXRImageTracking.js +117 -81
  84. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  85. package/package.json +3 -3
  86. package/plugins/common/files.js +6 -3
  87. package/plugins/vite/alias.js +45 -23
  88. package/plugins/vite/editor-connection.js +4 -4
  89. package/src/engine/engine_addressables.ts +46 -33
  90. package/src/engine/engine_animation.ts +20 -12
  91. package/src/engine/engine_assetdatabase.ts +7 -7
  92. package/src/engine/engine_camera.ts +54 -3
  93. package/src/engine/engine_context.ts +39 -1
  94. package/src/engine/engine_create_objects.ts +8 -7
  95. package/src/engine/engine_gameobject.ts +2 -2
  96. package/src/engine/engine_gltf_builtin_components.ts +12 -11
  97. package/src/engine/engine_loaders.callbacks.ts +1 -0
  98. package/src/engine/engine_loaders.ts +18 -13
  99. package/src/engine/extensions/NEEDLE_lighting_settings.ts +5 -2
  100. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  101. package/src/engine/js-extensions/Vector.ts +6 -0
  102. package/src/engine/webcomponents/needle-engine.attributes.ts +2 -0
  103. package/src/engine/webcomponents/needle-engine.ts +21 -21
  104. package/src/engine-components/Animation.ts +1 -1
  105. package/src/engine-components/AnimationUtilsAutoplay.ts +1 -6
  106. package/src/engine-components/Camera.ts +7 -1
  107. package/src/engine-components/ContactShadows.ts +27 -6
  108. package/src/engine-components/DropListener.ts +44 -34
  109. package/src/engine-components/LookAtConstraint.ts +9 -1
  110. package/src/engine-components/NestedGltf.ts +33 -24
  111. package/src/engine-components/OrbitControls.ts +80 -28
  112. package/src/engine-components/Renderer.ts +2 -1
  113. package/src/engine-components/Skybox.ts +9 -10
  114. package/src/engine-components/api.ts +2 -1
  115. package/src/engine-components/export/usdz/Extension.ts +1 -1
  116. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +1 -1
  117. package/src/engine-components/export/usdz/USDZExporter.ts +21 -12
  118. package/src/engine-components/webxr/WebXRImageTracking.ts +138 -90
@@ -10,6 +10,12 @@ export function apply(object: Vector3) {
10
10
  }
11
11
  }
12
12
 
13
+ // NOTE: keep in sync with method declarations below
14
+ declare module 'three' {
15
+ export interface Vector3 {
16
+ slerp(end: Vector3, t: number): Vector3;
17
+ }
18
+ }
13
19
 
14
20
  Vector3.prototype["slerp"] = function (end: Vector3, t: number) {
15
21
  return slerp(this, end, t);
@@ -60,6 +60,8 @@ type SkyboxAttributes = {
60
60
  "background-color"?: string,
61
61
  /** URL to .exr, .hdr, .png, .jpg to be used for lighting */
62
62
  "environment-image"?: string,
63
+
64
+ "environment-intensity"?: number,
63
65
  }
64
66
 
65
67
  export type TonemappingAttributeOptions = "none" | "linear" | "neutral" | "agx";
@@ -45,6 +45,7 @@ const observedAttributes = [
45
45
  "tone-mapping-exposure",
46
46
  "background-blurriness",
47
47
  "background-color",
48
+ "environment-intensity",
48
49
  ]
49
50
 
50
51
  // https://developers.google.com/web/fundamentals/web-components/customelements
@@ -54,8 +55,8 @@ const observedAttributes = [
54
55
  * The needle engine web component creates and manages a needle engine context which is responsible for rendering a 3D scene using threejs.
55
56
  * The needle engine context is created when the src attribute is set and disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
56
57
  * The needle engine context is accessible via the context property on the needle engine element (e.g. document.querySelector("needle-engine").context).
57
- * @link https://engine.needle.tools/docs/reference/needle-engine-attributes
58
- *
58
+ * See {@link https://engine.needle.tools/docs/reference/needle-engine-attributes}
59
+ *
59
60
  * @example
60
61
  * <needle-engine src="https://example.com/scene.glb"></needle-engine>
61
62
  * @example
@@ -64,7 +65,7 @@ const observedAttributes = [
64
65
  export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
65
66
 
66
67
  static get observedAttributes() {
67
- return observedAttributes
68
+ return observedAttributes;
68
69
  }
69
70
 
70
71
  public get loadingProgress01(): number { return this._loadingProgress01; }
@@ -94,7 +95,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
94
95
  /**
95
96
  * Get the current context for this web component instance. The context is created when the src attribute is set and the loading has finished.
96
97
  * The context is disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
97
- * @returns {Promise<Context>} a promise that resolves to the context when the loading has finished
98
+ * @returns a promise that resolves to the context when the loading has finished
98
99
  */
99
100
  public getContext(): Promise<Context> {
100
101
  return new Promise((res, _rej) => {
@@ -325,22 +326,14 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
325
326
  if (debug) console.log("ktx2DecoderPath", newValue);
326
327
  setKtx2TranscoderPath(newValue);
327
328
  break;
328
- case "tone-mapping": {
329
- this.applyAttributes();
330
- break;
331
- }
332
- case "tone-mapping-exposure": {
333
- this.applyAttributes();
334
- break;
335
- }
336
- case "background-blurriness": {
337
- const value = parseFloat(newValue);
338
- if (value != undefined && this._context) {
339
- this._context.scene.backgroundBlurriness = value;
340
- }
341
- break;
342
- }
343
- case "background-color": {
329
+
330
+ case "tonemapping":
331
+ case "tone-mapping":
332
+ case "tone-mapping-exposure":
333
+ case "background-blurriness":
334
+ case "background-color":
335
+ case "environment-intensity":
336
+ {
344
337
  this.applyAttributes();
345
338
  break;
346
339
  }
@@ -552,11 +545,18 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
552
545
  const backgroundBlurriness = this.getAttribute("background-blurriness");
553
546
  if (backgroundBlurriness !== null && backgroundBlurriness !== undefined) {
554
547
  const value = parseFloat(backgroundBlurriness);
555
- if (value !== undefined && this._context) {
548
+ if (!isNaN(value) && this._context) {
556
549
  this._context.scene.backgroundBlurriness = value;
557
550
  }
558
551
  }
559
552
 
553
+ const environmentIntensity = this.getAttribute("environment-intensity");
554
+ if (environmentIntensity != undefined && this._context) {
555
+ const value = parseFloat(environmentIntensity);
556
+ if (!isNaN(value) && this._context)
557
+ this._context.scene.environmentIntensity = value;
558
+ }
559
+
560
560
  const backgroundColor = this.getAttribute("background-color");
561
561
  if (this._context?.renderer) {
562
562
  if (typeof backgroundColor === "string" && backgroundColor.length > 0) {
@@ -67,7 +67,7 @@ export class Animation extends Behaviour implements IAnimationComponent {
67
67
  get isAnimationComponent(): boolean { return true; }
68
68
  addClip(clip: AnimationClip) {
69
69
  if (!this.animations) this.animations = [];
70
- this.animations.push(clip);
70
+ if (!this.animations.includes(clip)) this.animations.push(clip);
71
71
  }
72
72
 
73
73
  /**
@@ -1,7 +1,6 @@
1
1
  import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
2
2
 
3
3
  import { AnimationUtils } from "../engine/engine_animation.js";
4
- import { addComponent } from "../engine/engine_components.js";
5
4
  import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
6
5
  import { Animation } from "./Animation.js";
7
6
  import { Animator } from "./Animator.js";
@@ -29,11 +28,7 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreated, args => {
29
28
  return undefined;
30
29
  }, true);
31
30
  if (hasAnimation !== true) {
32
- AnimationUtils.assignAnimationsFromFile(file.file as GLTF, {
33
- createAnimationComponent: (obj, _clip) => {
34
- return addComponent(obj, Animation);
35
- },
36
- });
31
+ AnimationUtils.autoplayAnimations(file.file as GLTF);
37
32
  }
38
33
  }
39
34
  }
@@ -9,6 +9,7 @@ import { RenderTexture } from "../engine/engine_texture.js";
9
9
  import { getTempColor, getWorldPosition } from "../engine/engine_three_utils.js";
10
10
  import type { ICamera } from "../engine/engine_types.js"
11
11
  import { getParam } from "../engine/engine_utils.js";
12
+ import { NeedleXREventArgs } from "../engine/engine_xr.js";
12
13
  import { RGBAColor } from "../engine/js-extensions/index.js";
13
14
  import { Behaviour, GameObject } from "./Component.js";
14
15
  import { OrbitControls } from "./OrbitControls.js";
@@ -446,6 +447,11 @@ export class Camera extends Behaviour implements ICamera {
446
447
  this.context.removeCamera(this);
447
448
  }
448
449
 
450
+ onLeaveXR(_args: NeedleXREventArgs): void {
451
+ // Restore previous FOV
452
+ this.fieldOfView = this._fov;
453
+ }
454
+
449
455
 
450
456
  /** @internal */
451
457
  onBeforeRender() {
@@ -553,7 +559,7 @@ export class Camera extends Behaviour implements ICamera {
553
559
  }
554
560
 
555
561
  // restore previous fov (e.g. when user was in VR or AR and the camera's fov has changed)
556
- this.fieldOfView = this._fov;
562
+ this.fieldOfView = this.fieldOfView;
557
563
 
558
564
  if (debug) {
559
565
  const msg = `[Camera] Apply ClearFlags: ${ClearFlags[this._clearFlags]} - \"${this.name}\"`;
@@ -1,4 +1,4 @@
1
- import { BackSide, CustomBlending, DoubleSide, FrontSide, Group, Material, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, Vector3, WebGLRenderTarget } from "three";
1
+ import { BackSide, CustomBlending, DoubleSide, FrontSide, Group, Material, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, Vector3, Vector3Like, WebGLRenderTarget } from "three";
2
2
  import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
3
3
  import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
4
4
 
@@ -27,7 +27,14 @@ onStart(ctx => {
27
27
  shadows.darkness = intensity;
28
28
  }
29
29
  }
30
- })
30
+ });
31
+
32
+
33
+ type FitParameters = {
34
+ object?: Object3D | Object3D[];
35
+ positionOffset?: Partial<Vector3Like>;
36
+ scaleFactor?: Partial<Vector3Like>;
37
+ }
31
38
 
32
39
  // Adapted from https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html.
33
40
 
@@ -50,7 +57,7 @@ export class ContactShadows extends Behaviour {
50
57
  * @param context The context to create the contact shadows in.
51
58
  * @returns The instance of the contact shadows.
52
59
  */
53
- static auto(context?: Context): ContactShadows {
60
+ static auto(context?: Context, params?: FitParameters): ContactShadows {
54
61
  if (!context) context = Context.Current;
55
62
  if (!context) {
56
63
  throw new Error("No context provided and no current context set.");
@@ -65,12 +72,13 @@ export class ContactShadows extends Behaviour {
65
72
  this._instances.set(context, instance);
66
73
  }
67
74
  context.scene.add(instance.gameObject);
68
- instance.fitShadows();
75
+ instance.fitShadows(params);
69
76
  return instance;
70
77
  }
71
78
 
72
79
  /**
73
80
  * When enabled the contact shadows component will be created to fit the whole scene.
81
+ * @default false
74
82
  */
75
83
  @serializable()
76
84
  autoFit: boolean = false;
@@ -107,12 +115,14 @@ export class ContactShadows extends Behaviour {
107
115
 
108
116
  /**
109
117
  * The minimum size of the shadows box
118
+ * @default undefined
110
119
  */
111
120
  minSize?: Partial<Vec3>;
112
121
 
113
122
  /**
114
123
  * When enabled the shadows will not be updated automatically. Use `needsUpdate()` to update the shadows manually.
115
124
  * This is useful when you want to update the shadows only when the scene changes.
125
+ * @default false
116
126
  */
117
127
  manualUpdate: boolean = false;
118
128
  /**
@@ -150,10 +160,11 @@ export class ContactShadows extends Behaviour {
150
160
  /**
151
161
  * Call to fit the shadows to the scene.
152
162
  */
153
- fitShadows() {
163
+ fitShadows(params: FitParameters = {}) {
154
164
  if (debug) console.warn("Fitting shadows to scene");
155
165
  setAutoFitEnabled(this.shadowsRoot, false);
156
- const box = getBoundingBox(this.context.scene.children, [this.shadowsRoot]);
166
+ const objectToFit = params.object || this.context.scene;
167
+ const box = getBoundingBox(objectToFit, [this.shadowsRoot]);
157
168
  // expand box in all directions (except below ground)
158
169
  // 0.75 expands by 75% in each direction
159
170
  // The "32" is pretty much heuristically determined – adjusting the value until we don't get a visible border anymore.
@@ -175,6 +186,16 @@ export class ContactShadows extends Behaviour {
175
186
  // We can't move GroundProjection down because of immersive-ar mesh/plane tracking where occlusion would otherwise hide GroundProjection
176
187
  this.shadowsRoot.position.set((min.x + box.max.x) / 2, min.y - offset, (min.z + box.max.z) / 2);
177
188
  this.shadowsRoot.scale.set(box.max.x - min.x, box.max.y - min.y, box.max.z - min.z);
189
+ if (params.positionOffset) {
190
+ if (params.positionOffset.x !== undefined) this.shadowsRoot.position.x += params.positionOffset.x;
191
+ if (params.positionOffset.y !== undefined) this.shadowsRoot.position.y += params.positionOffset.y;
192
+ if (params.positionOffset.z !== undefined) this.shadowsRoot.position.z += params.positionOffset.z;
193
+ }
194
+ if (params.scaleFactor) {
195
+ if (params.scaleFactor.x !== undefined) this.shadowsRoot.scale.x *= params.scaleFactor.x;
196
+ if (params.scaleFactor.y !== undefined) this.shadowsRoot.scale.y *= params.scaleFactor.y;
197
+ if (params.scaleFactor.z !== undefined) this.shadowsRoot.scale.z *= params.scaleFactor.z;
198
+ }
178
199
  this.applyMinSize();
179
200
  this.shadowsRoot.matrixWorldNeedsUpdate = true;
180
201
  if (debug) console.log("Fitted shadows to scene", this.shadowsRoot.scale.clone());
@@ -143,13 +143,6 @@ const blobKeyName = "blob";
143
143
  */
144
144
  export class DropListener extends Behaviour {
145
145
 
146
- /**
147
- * When enabled, the DropListener will automatically synchronize dropped files to other connected clients.
148
- * When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
149
- */
150
- @serializable()
151
- useNetworking: boolean = true;
152
-
153
146
  /**
154
147
  * When assigned, the DropListener will only accept files that are dropped on this specific object.
155
148
  * This allows creating designated drop zones in your scene.
@@ -159,7 +152,10 @@ export class DropListener extends Behaviour {
159
152
 
160
153
  /**
161
154
  * When enabled, dropped objects will be automatically scaled to fit within the volume defined by fitVolumeSize.
162
- * Useful for ensuring dropped models appear at an appropriate scale.
155
+ * Useful for ensuring dropped models appear at an appropriate scale.
156
+ *
157
+ * **Tip**: Use the handy `fitObjectIntoVolume` function (`import { fitObjectIntoVolume } from "@needle-tools/engine"`) for custom fitting needs.
158
+ *
163
159
  * @default false
164
160
  */
165
161
  @serializable()
@@ -180,6 +176,14 @@ export class DropListener extends Behaviour {
180
176
  @serializable()
181
177
  placeAtHitPosition: boolean = true;
182
178
 
179
+ /**
180
+ * When enabled, the DropListener will automatically synchronize dropped files to other connected clients.
181
+ * When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
182
+ * @default false
183
+ */
184
+ @serializable()
185
+ useNetworking: boolean = false;
186
+
183
187
  /**
184
188
  * Event list that gets invoked after a file has been successfully added to the scene.
185
189
  * Receives {@link DropListenerOnDropArguments} containing the added object and related information.
@@ -193,6 +197,27 @@ export class DropListener extends Behaviour {
193
197
  @serializable(EventList)
194
198
  onDropped: EventList<DropListenerOnDropArguments> = new EventList();
195
199
 
200
+ /**
201
+ * Loads a file from the given URL and adds it to the scene.
202
+ * @returns A promise that resolves to the loaded object or null if loading failed.
203
+ */
204
+ loadFromURL(url: string, data?: { point?: Vec3, size?: Vec3 }): Promise<Object3D | null> {
205
+ return this.addFromUrl(url, { screenposition: new Vector2(), point: data?.point, size: data?.size, }, false);
206
+ }
207
+
208
+ /**
209
+ * Forgets all previously added objects.
210
+ * The droplistener will then not be able to remove previously added objects.
211
+ */
212
+ forgetObjects() {
213
+ this.removePreviouslyAddedObjects(false);
214
+ }
215
+
216
+
217
+
218
+
219
+ // #region internals
220
+
196
221
  /** @internal */
197
222
  onEnable(): void {
198
223
  this.context.renderer.domElement.addEventListener("dragover", this.onDrag);
@@ -208,21 +233,6 @@ export class DropListener extends Behaviour {
208
233
  this.context.connection.stopListen("droplistener", this.onNetworkEvent);
209
234
  }
210
235
 
211
- /**
212
- * Loads a file from the given URL and adds it to the scene.
213
- */
214
- loadFromURL(url: string, data?: { point?: Vec3, size?: Vec3 }) {
215
- this.addFromUrl(url, { screenposition: new Vector2(), point: data?.point, size: data?.size, }, true);
216
- }
217
-
218
- /**
219
- * Forgets all previously added objects.
220
- * The droplistener will then not be able to remove previously added objects.
221
- */
222
- forgetObjects() {
223
- this.removePreviouslyAddedObjects(false);
224
- }
225
-
226
236
  /**
227
237
  * Handles network events received from other clients containing information about dropped objects
228
238
  * @param evt Network event data containing object information, position, and content URL
@@ -326,7 +336,7 @@ export class DropListener extends Behaviour {
326
336
  }
327
337
  }
328
338
  if (files.length > 0) {
329
- await this.addDroppedFiles(files, ctx);
339
+ await this.addFromFiles(files, ctx);
330
340
  }
331
341
  }
332
342
 
@@ -359,6 +369,7 @@ export class DropListener extends Behaviour {
359
369
  // Ignore dropped images
360
370
  const lowercaseUrl = url.toLowerCase();
361
371
  if (lowercaseUrl.endsWith(".hdr") || lowercaseUrl.endsWith(".hdri") || lowercaseUrl.endsWith(".exr") || lowercaseUrl.endsWith(".png") || lowercaseUrl.endsWith(".jpg") || lowercaseUrl.endsWith(".jpeg")) {
372
+ console.warn(`Fileformat is not supported: ${lowercaseUrl}`);
362
373
  return null;
363
374
  }
364
375
 
@@ -374,7 +385,7 @@ export class DropListener extends Behaviour {
374
385
  });
375
386
  if (res && this._addedObjects.length <= 0) {
376
387
  ctx.url = url;
377
- const obj = this.addObject(res, ctx, isRemote);
388
+ const obj = this.onObjectLoaded(res, ctx, isRemote);
378
389
  return obj;
379
390
  }
380
391
  }
@@ -388,12 +399,13 @@ export class DropListener extends Behaviour {
388
399
  private _abort: AbortController | null = null;
389
400
 
390
401
  /**
391
- * Processes dropped files, loads them as 3D models, and handles networking if enabled
392
- * Creates an abort controller to cancel previous uploads if new files are dropped
402
+ * Processes dropped files and loads them as 3D models.
403
+ * When enabled, it also handles network drops (sending files between clients).
404
+ * Automatically handles cancelling previous uploads if new files are dropped.
393
405
  * @param fileList Array of dropped files
394
- * @param ctx Context information about where the drop occurred
406
+ * @param ctx Context information about where on the screen or in 3D space the drop occurred
395
407
  */
396
- private async addDroppedFiles(fileList: Array<File>, ctx: DropContext) {
408
+ private async addFromFiles(fileList: Array<File>, ctx: DropContext) {
397
409
  if (debug) console.log("Add files", fileList)
398
410
  if (!Array.isArray(fileList)) return;
399
411
  if (!fileList.length) return;
@@ -427,7 +439,7 @@ export class DropListener extends Behaviour {
427
439
  if (res) {
428
440
  this.dispatchEvent(new CustomEvent(DropListenerEvents.FileDropped, { detail: file }));
429
441
  ctx.file = file;
430
- const obj = this.addObject(res, ctx, false);
442
+ const obj = this.onObjectLoaded(res, ctx, false);
431
443
 
432
444
  // handle uploading the dropped object and networking the event
433
445
  if (obj && this.context.connection.isConnected && this.useNetworking) {
@@ -478,7 +490,7 @@ export class DropListener extends Behaviour {
478
490
  * @param isRemote Whether this object was shared from a remote client
479
491
  * @returns The added object or null if adding failed
480
492
  */
481
- private addObject(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
493
+ private onObjectLoaded(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
482
494
 
483
495
  const { model, contentMD5 } = data;
484
496
 
@@ -522,9 +534,7 @@ export class DropListener extends Behaviour {
522
534
  }
523
535
  }
524
536
 
525
- AnimationUtils.assignAnimationsFromFile(model, {
526
- createAnimationComponent: obj => addComponent(obj, Animation)
527
- });
537
+ AnimationUtils.autoplayAnimations(model);
528
538
 
529
539
  const evt = new DropListenerAddedEvent({
530
540
  sender: this,
@@ -1,4 +1,4 @@
1
- import { Object3D } from "three";
1
+ import { Object3D, Vector3 } from "three";
2
2
 
3
3
  import { serializable } from "../engine/engine_serialization_decorator.js";
4
4
  import { Behaviour } from "./Component.js";
@@ -23,4 +23,12 @@ export class LookAtConstraint extends Behaviour {
23
23
  */
24
24
  @serializable(Object3D)
25
25
  sources: Object3D[] = [];
26
+
27
+ /**
28
+ * Set the position of the constraint.
29
+ */
30
+ setConstraintPosition(worldPosition: Vector3) {
31
+ const source = this.sources[0];
32
+ if (source) source.worldPosition = worldPosition;
33
+ }
26
34
  }
@@ -4,6 +4,7 @@ import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.j
4
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
5
5
  import { getParam } from "../engine/engine_utils.js";
6
6
  import { Behaviour } from "../engine-components/Component.js";
7
+ import { EventList } from "./EventList.js";
7
8
 
8
9
  const debug = getParam("debugnestedgltf");
9
10
 
@@ -11,17 +12,21 @@ const debug = getParam("debugnestedgltf");
11
12
  * It will load the gltf file and instantiate it as a child of the parent of the GameObject that has this component
12
13
  */
13
14
  export class NestedGltf extends Behaviour {
14
- /**
15
- * A reference to the gltf file that should be loaded
16
- */
15
+
16
+ /** A reference to the gltf file that should be loaded */
17
17
  @serializable(AssetReference)
18
18
  filePath?: AssetReference;
19
19
 
20
+ /** Invoked when the nested glTF file has been instantiated */
21
+ @serializable(EventList)
22
+ loaded: EventList<{ component: NestedGltf, instance: any, asset: AssetReference }> = new EventList();
23
+
20
24
  /**
21
25
  * EXPERIMENTAL for cloud asset loading
22
26
  */
23
27
  loadAssetInParent = true;
24
28
 
29
+
25
30
  private _isLoadingOrDoneLoading: boolean = false;
26
31
 
27
32
  /** Register a callback that will be called when the progress of the loading changes */
@@ -31,7 +36,7 @@ export class NestedGltf extends Behaviour {
31
36
 
32
37
  /** Begin loading the referenced gltf file in filePath */
33
38
  preload() {
34
- this.filePath?.preload();
39
+ return this.filePath?.preload() || null;
35
40
  }
36
41
 
37
42
  /** @internal */
@@ -41,27 +46,31 @@ export class NestedGltf extends Behaviour {
41
46
 
42
47
  const parent = this.gameObject.parent;
43
48
  if (parent) {
44
- this._isLoadingOrDoneLoading = true;
45
- const opts = new InstantiateOptions();
46
- // we need to provide stable guids for creating nested gltfs
47
- opts.idProvider = new InstantiateIdProvider(this.hash(this.guid));
48
- opts.parent = this.loadAssetInParent !== false ? parent : this.gameObject;
49
- this.gameObject.updateMatrix();
50
- const matrix = this.gameObject.matrix;
51
- if (debug) console.log("Load nested:", this.filePath?.url ?? this.filePath, this.gameObject.position);
52
- const res = await this.filePath?.instantiate?.call(this.filePath, opts);
53
- if (debug) console.log("Nested loaded:", this.filePath?.url ?? this.filePath, res);
54
- if (res && this.loadAssetInParent !== false) {
55
- res.matrixAutoUpdate = false;
56
- res.matrix.identity();
57
- res.applyMatrix4(matrix);
58
- res.matrixAutoUpdate = true;
59
- res.layers.disableAll();
60
- res.layers.set(this.layer);
61
-
62
- this.dispatchEvent(new CustomEvent("loaded", { detail: { instance: res, assetReference: this.filePath } }));
49
+
50
+ if (this.filePath) {
51
+
52
+ this._isLoadingOrDoneLoading = true;
53
+ const opts = new InstantiateOptions();
54
+ // we need to provide stable guids for creating nested gltfs
55
+ opts.idProvider = new InstantiateIdProvider(this.hash(this.guid));
56
+ opts.parent = this.loadAssetInParent !== false ? parent : this.gameObject;
57
+ this.gameObject.updateMatrix();
58
+ const matrix = this.gameObject.matrix;
59
+ if (debug) console.log("Load nested:", this.filePath?.url ?? this.filePath, this.gameObject.position);
60
+ const res = await this.filePath?.instantiate?.call(this.filePath, opts);
61
+ if (debug) console.log("Nested loaded:", this.filePath?.url ?? this.filePath, res);
62
+ if (res && this.loadAssetInParent !== false) {
63
+ res.matrixAutoUpdate = false;
64
+ res.matrix.identity();
65
+ res.applyMatrix4(matrix);
66
+ res.matrixAutoUpdate = true;
67
+ res.layers.disableAll();
68
+ res.layers.set(this.layer);
69
+ this.loaded.invoke({ component: this, instance: res, asset: this.filePath });
70
+ }
71
+ if (debug) console.log("Nested loading done:", this.filePath?.url ?? this.filePath, res);
63
72
  }
64
- if (debug) console.log("Nested loading done:", this.filePath?.url ?? this.filePath, res);
73
+
65
74
  }
66
75
  }
67
76