@needle-tools/engine 4.8.8 → 4.8.9

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 (91) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +50 -45
  3. package/components.needle.json +1 -1
  4. package/dist/{needle-engine.bundle-C071tpzX.js → needle-engine.bundle-BdgNRB4B.js} +2656 -2629
  5. package/dist/{needle-engine.bundle-CK8rTkFm.umd.cjs → needle-engine.bundle-DGca1cic.umd.cjs} +108 -108
  6. package/dist/{needle-engine.bundle--H5L0liy.min.js → needle-engine.bundle-TQyyn3pE.min.js} +109 -109
  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.js +2 -0
  12. package/lib/engine/engine_addressables.js.map +1 -1
  13. package/lib/engine/engine_animation.js +4 -4
  14. package/lib/engine/engine_animation.js.map +1 -1
  15. package/lib/engine/engine_assetdatabase.js +6 -6
  16. package/lib/engine/engine_assetdatabase.js.map +1 -1
  17. package/lib/engine/engine_camera.d.ts +15 -2
  18. package/lib/engine/engine_camera.js +9 -2
  19. package/lib/engine/engine_camera.js.map +1 -1
  20. package/lib/engine/engine_context.d.ts +8 -2
  21. package/lib/engine/engine_context.js +21 -3
  22. package/lib/engine/engine_context.js.map +1 -1
  23. package/lib/engine/engine_create_objects.d.ts +3 -3
  24. package/lib/engine/engine_create_objects.js +5 -4
  25. package/lib/engine/engine_create_objects.js.map +1 -1
  26. package/lib/engine/engine_gameobject.js +2 -2
  27. package/lib/engine/engine_gameobject.js.map +1 -1
  28. package/lib/engine/engine_gltf_builtin_components.js +11 -11
  29. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  30. package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
  31. package/lib/engine/engine_loaders.callbacks.js +1 -0
  32. package/lib/engine/engine_loaders.callbacks.js.map +1 -1
  33. package/lib/engine/extensions/NEEDLE_lighting_settings.js +5 -2
  34. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  35. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  36. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  37. package/lib/engine/js-extensions/Object3D.d.ts +1 -1
  38. package/lib/engine/js-extensions/Vector.d.ts +5 -0
  39. package/lib/engine/js-extensions/Vector.js.map +1 -1
  40. package/lib/engine/js-extensions/index.d.ts +2 -1
  41. package/lib/engine/js-extensions/index.js +1 -1
  42. package/lib/engine/js-extensions/index.js.map +1 -1
  43. package/lib/engine/webcomponents/needle-engine.d.ts +2 -2
  44. package/lib/engine/webcomponents/needle-engine.js +11 -18
  45. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  46. package/lib/engine-components/Camera.d.ts +2 -0
  47. package/lib/engine-components/Camera.js +5 -1
  48. package/lib/engine-components/Camera.js.map +1 -1
  49. package/lib/engine-components/ContactShadows.d.ts +12 -2
  50. package/lib/engine-components/ContactShadows.js +24 -4
  51. package/lib/engine-components/ContactShadows.js.map +1 -1
  52. package/lib/engine-components/DropListener.d.ts +4 -3
  53. package/lib/engine-components/DropListener.js +4 -3
  54. package/lib/engine-components/DropListener.js.map +1 -1
  55. package/lib/engine-components/NestedGltf.d.ts +9 -4
  56. package/lib/engine-components/NestedGltf.js +32 -26
  57. package/lib/engine-components/NestedGltf.js.map +1 -1
  58. package/lib/engine-components/OrbitControls.d.ts +29 -6
  59. package/lib/engine-components/OrbitControls.js +45 -9
  60. package/lib/engine-components/OrbitControls.js.map +1 -1
  61. package/lib/engine-components/Renderer.js +2 -1
  62. package/lib/engine-components/Renderer.js.map +1 -1
  63. package/lib/engine-components/Skybox.js +8 -9
  64. package/lib/engine-components/Skybox.js.map +1 -1
  65. package/lib/engine-components/api.d.ts +1 -0
  66. package/lib/engine-components/api.js.map +1 -1
  67. package/package.json +2 -2
  68. package/plugins/common/files.js +6 -3
  69. package/plugins/vite/editor-connection.js +4 -4
  70. package/src/engine/engine_addressables.ts +2 -0
  71. package/src/engine/engine_animation.ts +4 -4
  72. package/src/engine/engine_assetdatabase.ts +7 -7
  73. package/src/engine/engine_camera.ts +15 -3
  74. package/src/engine/engine_context.ts +22 -4
  75. package/src/engine/engine_create_objects.ts +8 -7
  76. package/src/engine/engine_gameobject.ts +2 -2
  77. package/src/engine/engine_gltf_builtin_components.ts +12 -11
  78. package/src/engine/engine_loaders.callbacks.ts +1 -0
  79. package/src/engine/extensions/NEEDLE_lighting_settings.ts +5 -2
  80. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  81. package/src/engine/js-extensions/Vector.ts +6 -0
  82. package/src/engine/js-extensions/index.ts +3 -2
  83. package/src/engine/webcomponents/needle-engine.ts +10 -17
  84. package/src/engine-components/Camera.ts +7 -1
  85. package/src/engine-components/ContactShadows.ts +27 -6
  86. package/src/engine-components/DropListener.ts +4 -3
  87. package/src/engine-components/NestedGltf.ts +33 -24
  88. package/src/engine-components/OrbitControls.ts +67 -21
  89. package/src/engine-components/Renderer.ts +2 -1
  90. package/src/engine-components/Skybox.ts +9 -10
  91. package/src/engine-components/api.ts +2 -1
@@ -18,7 +18,7 @@ import { Addressables } from './engine_addressables.js';
18
18
  import { AnimationsRegistry } from './engine_animation.js';
19
19
  import { Application } from './engine_application.js';
20
20
  import { AssetDatabase } from './engine_assetdatabase.js';
21
- import { FocusRect, updateCameraFocusRect } from './engine_camera.js';
21
+ import { FocusRect, FocusRectSettings, updateCameraFocusRect } from './engine_camera.js';
22
22
  import { VERSION } from './engine_constants.js';
23
23
  import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
24
24
  import { WaitForPromise } from './engine_coroutine.js';
@@ -1378,11 +1378,26 @@ export class Context implements IContext {
1378
1378
  * Set a rect or dom element. The camera center will be moved to the center of the rect.
1379
1379
  * This is useful if you have Needle Engine embedded in a HTML layout and while you want the webgl background to fill e.g. the whole screen you want to move the camera center to free space.
1380
1380
  * For that you can simply pass in the rect or HMTL div that you want the camera to center on.
1381
+ * @param rect The focus rect or null to disable
1382
+ * @param settings Optional settings for the focus rect. These will override the `focusRectSettings` property
1381
1383
  */
1382
- public setCameraFocusRect(rect: FocusRect | null) {
1384
+ public setCameraFocusRect(rect: FocusRect | null, settings?: Partial<FocusRectSettings>) {
1383
1385
  this._focusRect = rect;
1386
+ if (settings) {
1387
+ Object.assign(this.focusRectSettings, settings);
1388
+ }
1384
1389
  }
1385
1390
  get focusRect() { return this._focusRect; }
1391
+ /** Settings when a focus rect is set. Use `setCameraFocusRect(...)` to do so.
1392
+ * This can be used to offset the renderer center e.g. to a specific DOM element.
1393
+ */
1394
+ readonly focusRectSettings: FocusRectSettings = {
1395
+ /** Controls how fast the rect is centered. Smaller values mean the rect is centered faster.
1396
+ * A minimum value of 0 means the rect is centered instantly.
1397
+ * @default .05
1398
+ */
1399
+ damping: .05
1400
+ };
1386
1401
  private _focusRect: FocusRect | null = null;
1387
1402
 
1388
1403
 
@@ -1514,8 +1529,11 @@ export class Context implements IContext {
1514
1529
  if (this.isVisibleToUser || this.runInBackground) {
1515
1530
 
1516
1531
  if (this._focusRect) {
1517
- if (this.mainCamera instanceof PerspectiveCamera)
1518
- updateCameraFocusRect(this._focusRect, this.time.deltaTime / .05, this.mainCamera, this.renderer);
1532
+ if (this.mainCamera instanceof PerspectiveCamera) {
1533
+ const settings = this.focusRectSettings;
1534
+ const dt = settings.damping > 0 ? this.time.deltaTime / settings.damping : 1;
1535
+ updateCameraFocusRect(this._focusRect, dt, this.mainCamera, this.renderer);
1536
+ }
1519
1537
  }
1520
1538
 
1521
1539
  this._currentFrameEvent = FrameEvent.OnBeforeRender;
@@ -42,13 +42,13 @@ export type ObjectOptions = {
42
42
  /**
43
43
  * The position of the object in local space
44
44
  */
45
- position?: Vec3 | [number, number, number],
45
+ position?: Partial<Vec3> | [number, number, number],
46
46
  /** The rotation of the object in local space */
47
- rotation?: Vec3 | [number, number, number],
47
+ rotation?: Partial<Vec3> | [number, number, number],
48
48
  /**
49
49
  * The scale of the object in local space
50
50
  */
51
- scale?: Vec3 | number | [number, number, number],
51
+ scale?: Partial<Vec3> | number | [number, number, number],
52
52
  /**
53
53
  * If the object should receive shadows
54
54
  * @default true
@@ -251,14 +251,15 @@ export class ObjectUtils {
251
251
  if (opts?.position) {
252
252
  if (Array.isArray(opts.position))
253
253
  obj.position.set(opts.position[0], opts.position[1], opts.position[2]);
254
- else
255
- obj.position.set(opts.position.x, opts.position.y, opts.position.z);
254
+ else {
255
+ obj.position.set(opts.position.x || 0, opts.position.y || 0, opts.position.z || 0);
256
+ }
256
257
  }
257
258
  if (opts?.rotation) {
258
259
  if (Array.isArray(opts.rotation))
259
260
  obj.rotation.set(opts.rotation[0], opts.rotation[1], opts.rotation[2]);
260
261
  else
261
- obj.rotation.set(opts.rotation.x, opts.rotation.y, opts.rotation.z);
262
+ obj.rotation.set(opts.rotation.x || 0, opts.rotation.y || 0, opts.rotation.z || 0);
262
263
  }
263
264
  if (opts?.scale) {
264
265
  if (typeof opts.scale === "number")
@@ -267,7 +268,7 @@ export class ObjectUtils {
267
268
  obj.scale.set(opts.scale[0], opts.scale[1], opts.scale[2]);
268
269
  }
269
270
  else {
270
- obj.scale.set(opts.scale.x, opts.scale.y, opts.scale.z);
271
+ obj.scale.set(opts.scale.x || 1, opts.scale.y || 1, opts.scale.z || 1);
271
272
 
272
273
  }
273
274
  }
@@ -166,9 +166,9 @@ export function destroy(instance: Object3D | Component, recursive: boolean = tru
166
166
  setDestroyed(obj, true);
167
167
  if (dispose) {
168
168
  disposeObjectResources(obj);
169
+ // This needs to be called after disposing because it removes the references to resources
170
+ __internalRemoveReferences(obj);
169
171
  }
170
- // This needs to be called after disposing because it removes the references to resources
171
- __internalRemoveReferences(obj);
172
172
  }
173
173
  destroyed_objects.length = 0;
174
174
  destroyed_components.length = 0;
@@ -72,13 +72,13 @@ export async function createBuiltinComponents(context: Context, gltfId: SourceId
72
72
 
73
73
  if (gltf.scenes) {
74
74
  for (const scene of gltf.scenes) {
75
- await onCreateBuiltinComponents(serializationContext, scene, deserializeQueue, lateResolve);
75
+ await onCreateBuiltinComponents(serializationContext, scene, deserializeQueue, lateResolve, 0);
76
76
  }
77
77
  }
78
78
  // TODO: when is the gltf here an object3d?
79
79
  if (gltf.children) {
80
80
  for (const ch of gltf.children) {
81
- await onCreateBuiltinComponents(serializationContext, ch, deserializeQueue, lateResolve);
81
+ await onCreateBuiltinComponents(serializationContext, ch, deserializeQueue, lateResolve, 0);
82
82
  }
83
83
  }
84
84
 
@@ -202,7 +202,7 @@ const unknownComponentsBuffer: Array<string> = [];
202
202
 
203
203
 
204
204
  async function onCreateBuiltinComponents(context: SerializationContext, obj: Object3D,
205
- deserialize: DeserializeData[], lateResolve: LateResolveCallback[]) {
205
+ deserialize: DeserializeData[], lateResolve: LateResolveCallback[], level: number) {
206
206
  if (!obj) return;
207
207
 
208
208
  // iterate injected data
@@ -267,20 +267,21 @@ async function onCreateBuiltinComponents(context: SerializationContext, obj: Obj
267
267
  }
268
268
  // console.debug("finished adding gltf builtin components", obj);
269
269
  }
270
- if (unknownComponentsBuffer.length > 0) {
271
- const unknown = unknownComponentsBuffer.join(", ");
272
- console.warn("unknown components: " + unknown);
273
- unknownComponentsBuffer.length = 0;
274
- if (isLocalNetwork())
275
- showBalloonMessage(`<strong>Unknown components in scene</strong>:\n\n${unknown}\n\nThis could mean you forgot to add a npmdef to your ExportInfo\n<a href="https://engine.needle.tools/docs/project_structure.html#creating-and-installing-a-npmdef" target="_blank">documentation</a>`, LogType.Warn);
276
- }
277
270
  }
278
271
 
279
272
  if (obj.children) {
280
273
  for (const ch of obj.children) {
281
- await onCreateBuiltinComponents(context, ch, deserialize, lateResolve);
274
+ await onCreateBuiltinComponents(context, ch, deserialize, lateResolve, level + 1);
282
275
  }
283
276
  }
277
+
278
+ if (unknownComponentsBuffer.length > 0 && level === 0) {
279
+ const unknown = unknownComponentsBuffer.join(", ");
280
+ console.warn(`Unknown components in scene: ${unknown}`);
281
+ unknownComponentsBuffer.length = 0;
282
+ if (isLocalNetwork())
283
+ showBalloonMessage(`<strong>Unknown components in scene</strong>:\n\n${unknown}\n\nThis could mean you forgot to add a npmdef to your ExportInfo\n<a href="https://engine.needle.tools/docs/project_structure.html#creating-and-installing-a-npmdef" target="_blank">documentation</a>`, LogType.Warn);
284
+ }
284
285
  }
285
286
 
286
287
  function handleDeserialization(data: DeserializeData, context: SerializationContext) {
@@ -98,6 +98,7 @@ export namespace NeedleEngineModelLoader {
98
98
  * }
99
99
  * return null;
100
100
  * });
101
+ * ```
101
102
  */
102
103
  export function onCreateCustomModelLoader(callback: CustomLoaderCallback, opts?: CustomLoaderOptions) {
103
104
  const entry = { name: opts?.name, priority: opts?.priority ?? 0, callback };
@@ -169,8 +169,11 @@ export class SceneLightSettings extends Behaviour {
169
169
  this._hemisphereLightObj.removeFromParent();
170
170
  }
171
171
 
172
- if (this.sourceId)
173
- this.context.sceneLighting.internalEnableReflection(this.sourceId);
172
+ if (this.sourceId) {
173
+ if (!this.context.domElement.getAttribute("environment-image")) {
174
+ this.context.sceneLighting.internalEnableReflection(this.sourceId);
175
+ }
176
+ }
174
177
  }
175
178
 
176
179
  onDisable() {
@@ -70,7 +70,7 @@ export class NEEDLE_lightmaps implements GLTFLoaderPlugin {
70
70
  console.log(entry);
71
71
  let res: Promise<any> | null = null;
72
72
  // Check if the pointer is a json pointer:
73
- if (entry.pointer.startsWith("/textures/")) {
73
+ if (entry.pointer.startsWith("/textures/") || /** legacy support e.g. SOC */entry.pointer.startsWith("textures/")) {
74
74
  if (debug) console.log("Load texture from gltf", entry.pointer);
75
75
  res = resolveReferences(this.parser, entry.pointer).then(res => this.resolveTexture(entry, res));
76
76
  }
@@ -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);
@@ -1,5 +1,6 @@
1
1
  import "./Layers.js";
2
2
  import "./Camera.js";
3
3
  export * from "./ExtensionUtils.js";
4
- export * from "./Object3D.js";
5
- export * from "./RGBAColor.js";
4
+ export { apply } from "./Object3D.js";
5
+ export * from "./RGBAColor.js";
6
+ export { } from "./Vector.js";
@@ -55,8 +55,8 @@ const observedAttributes = [
55
55
  * The needle engine web component creates and manages a needle engine context which is responsible for rendering a 3D scene using threejs.
56
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).
57
57
  * The needle engine context is accessible via the context property on the needle engine element (e.g. document.querySelector("needle-engine").context).
58
- * @link https://engine.needle.tools/docs/reference/needle-engine-attributes
59
- *
58
+ * See {@link https://engine.needle.tools/docs/reference/needle-engine-attributes}
59
+ *
60
60
  * @example
61
61
  * <needle-engine src="https://example.com/scene.glb"></needle-engine>
62
62
  * @example
@@ -65,7 +65,7 @@ const observedAttributes = [
65
65
  export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
66
66
 
67
67
  static get observedAttributes() {
68
- return observedAttributes
68
+ return observedAttributes;
69
69
  }
70
70
 
71
71
  public get loadingProgress01(): number { return this._loadingProgress01; }
@@ -95,7 +95,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
95
95
  /**
96
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.
97
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).
98
- * @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
99
99
  */
100
100
  public getContext(): Promise<Context> {
101
101
  return new Promise((res, _rej) => {
@@ -328,19 +328,12 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
328
328
  break;
329
329
 
330
330
  case "tonemapping":
331
- case "tone-mapping": {
332
- this.applyAttributes();
333
- break;
334
- }
335
- case "tone-mapping-exposure": {
336
- this.applyAttributes();
337
- break;
338
- }
339
- case "background-blurriness": {
340
- this.applyAttributes();
341
- break;
342
- }
343
- case "environment-intensity": {
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
  }
@@ -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());
@@ -399,10 +399,11 @@ export class DropListener extends Behaviour {
399
399
  private _abort: AbortController | null = null;
400
400
 
401
401
  /**
402
- * Processes dropped files, loads them as 3D models, and handles networking if enabled
403
- * 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.
404
405
  * @param fileList Array of dropped files
405
- * @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
406
407
  */
407
408
  private async addFromFiles(fileList: Array<File>, ctx: DropContext) {
408
409
  if (debug) console.log("Add files", fileList)
@@ -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