@needle-tools/engine 4.8.8-next.e4d7577 → 4.8.8-next.e80f4d7

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 (78) hide show
  1. package/README.md +6 -2
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-qWPeuyPt.umd.cjs → needle-engine.bundle-DK1r3WiC.umd.cjs} +101 -101
  4. package/dist/{needle-engine.bundle-BQOCkmgC.js → needle-engine.bundle-DfWoUjQM.js} +2037 -2015
  5. package/dist/{needle-engine.bundle-fWjqSVUr.min.js → needle-engine.bundle-IGziSts0.min.js} +95 -95
  6. package/dist/needle-engine.d.ts +7 -0
  7. package/dist/needle-engine.js +2 -2
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/lib/engine/engine_addressables.js +2 -0
  11. package/lib/engine/engine_addressables.js.map +1 -1
  12. package/lib/engine/engine_animation.js +4 -4
  13. package/lib/engine/engine_animation.js.map +1 -1
  14. package/lib/engine/engine_assetdatabase.js +6 -6
  15. package/lib/engine/engine_assetdatabase.js.map +1 -1
  16. package/lib/engine/engine_camera.d.ts +15 -2
  17. package/lib/engine/engine_camera.js +9 -2
  18. package/lib/engine/engine_camera.js.map +1 -1
  19. package/lib/engine/engine_context.d.ts +8 -2
  20. package/lib/engine/engine_context.js +21 -3
  21. package/lib/engine/engine_context.js.map +1 -1
  22. package/lib/engine/engine_create_objects.d.ts +3 -3
  23. package/lib/engine/engine_create_objects.js +5 -4
  24. package/lib/engine/engine_create_objects.js.map +1 -1
  25. package/lib/engine/engine_gameobject.js +2 -2
  26. package/lib/engine/engine_gameobject.js.map +1 -1
  27. package/lib/engine/engine_gltf_builtin_components.js +11 -11
  28. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  29. package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
  30. package/lib/engine/engine_loaders.callbacks.js +1 -0
  31. package/lib/engine/engine_loaders.callbacks.js.map +1 -1
  32. package/lib/engine/extensions/NEEDLE_lighting_settings.js +5 -2
  33. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  34. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  35. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  36. package/lib/engine/js-extensions/Object3D.d.ts +1 -1
  37. package/lib/engine/js-extensions/Vector.d.ts +5 -0
  38. package/lib/engine/js-extensions/Vector.js.map +1 -1
  39. package/lib/engine/webcomponents/needle-engine.d.ts +2 -2
  40. package/lib/engine/webcomponents/needle-engine.js +11 -18
  41. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  42. package/lib/engine-components/ContactShadows.d.ts +12 -2
  43. package/lib/engine-components/ContactShadows.js +24 -4
  44. package/lib/engine-components/ContactShadows.js.map +1 -1
  45. package/lib/engine-components/DropListener.d.ts +4 -3
  46. package/lib/engine-components/DropListener.js +4 -3
  47. package/lib/engine-components/DropListener.js.map +1 -1
  48. package/lib/engine-components/NestedGltf.d.ts +9 -4
  49. package/lib/engine-components/NestedGltf.js +32 -26
  50. package/lib/engine-components/NestedGltf.js.map +1 -1
  51. package/lib/engine-components/OrbitControls.js +2 -4
  52. package/lib/engine-components/OrbitControls.js.map +1 -1
  53. package/lib/engine-components/Renderer.js +2 -1
  54. package/lib/engine-components/Renderer.js.map +1 -1
  55. package/lib/engine-components/Skybox.js +8 -9
  56. package/lib/engine-components/Skybox.js.map +1 -1
  57. package/package.json +2 -2
  58. package/plugins/common/files.js +6 -3
  59. package/plugins/vite/editor-connection.js +4 -4
  60. package/src/engine/engine_addressables.ts +2 -0
  61. package/src/engine/engine_animation.ts +4 -4
  62. package/src/engine/engine_assetdatabase.ts +7 -7
  63. package/src/engine/engine_camera.ts +15 -3
  64. package/src/engine/engine_context.ts +22 -4
  65. package/src/engine/engine_create_objects.ts +8 -7
  66. package/src/engine/engine_gameobject.ts +2 -2
  67. package/src/engine/engine_gltf_builtin_components.ts +12 -11
  68. package/src/engine/engine_loaders.callbacks.ts +1 -0
  69. package/src/engine/extensions/NEEDLE_lighting_settings.ts +5 -2
  70. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  71. package/src/engine/js-extensions/Vector.ts +6 -0
  72. package/src/engine/webcomponents/needle-engine.ts +10 -17
  73. package/src/engine-components/ContactShadows.ts +27 -6
  74. package/src/engine-components/DropListener.ts +4 -3
  75. package/src/engine-components/NestedGltf.ts +33 -24
  76. package/src/engine-components/OrbitControls.ts +2 -6
  77. package/src/engine-components/Renderer.ts +2 -1
  78. package/src/engine-components/Skybox.ts +9 -10
@@ -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
  }
@@ -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
 
@@ -1044,15 +1044,13 @@ export class OrbitControls extends Behaviour implements ICameraController {
1044
1044
  // and thus we're just getting some maximum that will work for sure.
1045
1045
  const box = getBoundingBox(objects, undefined, this._camera?.threeCamera?.layers);
1046
1046
  const boxCopy = box.clone();
1047
-
1048
- camera.updateMatrixWorld();
1049
- camera.updateProjectionMatrix();
1050
1047
  box.getCenter(center);
1051
1048
 
1052
1049
  const box_size = new Vector3();
1053
1050
  box.getSize(box_size);
1054
1051
 
1055
1052
  // project this box into camera space
1053
+ camera.updateMatrixWorld();
1056
1054
  box.applyMatrix4(camera.matrixWorldInverse);
1057
1055
 
1058
1056
  box.getSize(size);
@@ -1104,6 +1102,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
1104
1102
  // TODO: this doesnt take the Camera component nearClipPlane into account
1105
1103
  camera.near = (distance / 100);
1106
1104
  camera.far = boundsMax + distance * 10;
1105
+ camera.updateProjectionMatrix();
1107
1106
 
1108
1107
  // adjust maxZoom so that the ground projection radius is always inside
1109
1108
  if (groundprojection) {
@@ -1116,9 +1115,6 @@ export class OrbitControls extends Behaviour implements ICameraController {
1116
1115
  if (currentZoom < this.minZoom) this.minZoom = currentZoom * 0.9;
1117
1116
  if (currentZoom > this.maxZoom) this.maxZoom = currentZoom * 1.1;
1118
1117
 
1119
- camera.updateMatrixWorld();
1120
- camera.updateProjectionMatrix();
1121
-
1122
1118
  const direction = center.clone();
1123
1119
  if (options.fitDirection) {
1124
1120
  direction.sub(new Vector3().copy(options.fitDirection).multiplyScalar(1_000_000));
@@ -706,7 +706,8 @@ export class Renderer extends Behaviour implements IRenderer {
706
706
  }
707
707
 
708
708
  if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
709
- this._reflectionProbe.onSet(this);
709
+ if (!this._lightmaps?.length) // Currently reflectionprobes cant be used with lightmaps
710
+ this._reflectionProbe.onSet(this);
710
711
  }
711
712
  // since three 163 we need to set the envMap to the scene envMap if it is not set
712
713
  // otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903
@@ -48,21 +48,20 @@ function createRemoteSkyboxComponent(context: IContext, url: string, skybox: boo
48
48
  const promises = new Array<Promise<any>>();
49
49
  ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, (args) => {
50
50
  const context = args.context;
51
- const skyboxImage = context.domElement.getAttribute("background-image");
51
+ const backgroundImage = context.domElement.getAttribute("background-image");
52
52
  const environmentImage = context.domElement.getAttribute("environment-image");
53
- const envAndSkyboxAreSame = skyboxImage === environmentImage;
54
-
55
- if (skyboxImage && !envAndSkyboxAreSame) {
56
- if (debug) console.log("Creating remote skybox to load " + skyboxImage);
53
+
54
+ if (backgroundImage) {
55
+ if (debug) console.log("Creating RemoteSkybox to load background " + backgroundImage);
57
56
  // if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
58
57
  // checks if we have this attribute set and then sets the skybox clearflags accordingly
59
58
  // if the user has a GLB with a camera but set to solid color then the skybox image is not visible -> we will just warn then and not override the camera settings
60
- const promise = createRemoteSkyboxComponent(context, skyboxImage, true, false, "background-image");
59
+ const promise = createRemoteSkyboxComponent(context, backgroundImage, true, false, "background-image");
61
60
  if (promise) promises.push(promise);
62
61
  }
63
62
  if (environmentImage) {
64
- if (debug) console.log("Creating remote environment to load " + environmentImage);
65
- const promise = createRemoteSkyboxComponent(context, environmentImage, envAndSkyboxAreSame, true, "environment-image");
63
+ if (debug) console.log("Creating RemoteSkybox to load environment " + environmentImage);
64
+ const promise = createRemoteSkyboxComponent(context, environmentImage, false, true, "environment-image");
66
65
  if (promise) promises.push(promise);
67
66
  }
68
67
  });
@@ -200,7 +199,7 @@ export class RemoteSkybox extends Behaviour {
200
199
  console.warn("Potentially invalid skybox URL: \"" + name + "\" on " + (this.name || this.gameObject?.name || "context"));
201
200
  }
202
201
 
203
- if (debug) console.log("Set remote skybox url: " + url);
202
+ if (debug) console.log("Set RemoteSkybox url: " + url);
204
203
 
205
204
  if (this._prevUrl === url && this._prevLoadedEnvironment) {
206
205
  this.apply();
@@ -258,7 +257,7 @@ export class RemoteSkybox extends Behaviour {
258
257
  this._prevBackground = this.context.scene.background;
259
258
  if (this.context.scene.environment !== envMap)
260
259
  this._prevEnvironment = this.context.scene.environment;
261
- if (debug) console.log("Set remote skybox", this.url, !Camera.backgroundShouldBeTransparent(this.context));
260
+ if (debug) console.log("Set RemoteSkybox (" + ((this.environment && this.background) ? "environment and background" : this.environment ? "environment" : this.background ? "background" : "none") + ")", this.url, !Camera.backgroundShouldBeTransparent(this.context));
262
261
  if (this.environment)
263
262
  this.context.scene.environment = envMap;
264
263
  if (this.background && !Camera.backgroundShouldBeTransparent(this.context))