@needle-tools/engine 4.9.0 → 4.9.2

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 (37) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/{needle-engine.bundle-B1gr_nQ0.min.js → needle-engine.bundle-DwspSk2O.min.js} +95 -95
  3. package/dist/{needle-engine.bundle-DrlDKOar.umd.cjs → needle-engine.bundle-WLYWw5OF.umd.cjs} +89 -89
  4. package/dist/{needle-engine.bundle-BikYBC35.js → needle-engine.bundle-XBRfMDwo.js} +1992 -1952
  5. package/dist/needle-engine.js +2 -2
  6. package/dist/needle-engine.min.js +1 -1
  7. package/dist/needle-engine.umd.cjs +1 -1
  8. package/dist/{vendor-8le8g4MS.umd.cjs → vendor-BtJpSuCj.umd.cjs} +1 -1
  9. package/dist/{vendor-yDFiCnCw.min.js → vendor-XJ9xiwrv.min.js} +1 -1
  10. package/dist/{vendor-Msb9AgYE.js → vendor-k9i6CeGi.js} +871 -870
  11. package/lib/engine/engine_gameobject.d.ts +7 -7
  12. package/lib/engine/engine_gameobject.js +88 -27
  13. package/lib/engine/engine_gameobject.js.map +1 -1
  14. package/lib/engine/engine_networking_instantiate.js +23 -6
  15. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  16. package/lib/engine/engine_serialization_core.d.ts +2 -2
  17. package/lib/engine/engine_serialization_core.js +7 -7
  18. package/lib/engine/engine_serialization_core.js.map +1 -1
  19. package/lib/engine/engine_serialization_decorator.js +2 -1
  20. package/lib/engine/engine_serialization_decorator.js.map +1 -1
  21. package/lib/engine-components/Animation.js +1 -1
  22. package/lib/engine-components/Animation.js.map +1 -1
  23. package/lib/engine-components/Renderer.d.ts +7 -6
  24. package/lib/engine-components/Renderer.js +3 -0
  25. package/lib/engine-components/Renderer.js.map +1 -1
  26. package/lib/engine-components/web/ScrollFollow.d.ts +19 -1
  27. package/lib/engine-components/web/ScrollFollow.js +20 -1
  28. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  29. package/package.json +2 -2
  30. package/plugins/vite/build.js +3 -0
  31. package/src/engine/engine_gameobject.ts +105 -38
  32. package/src/engine/engine_networking_instantiate.ts +21 -6
  33. package/src/engine/engine_serialization_core.ts +9 -9
  34. package/src/engine/engine_serialization_decorator.ts +2 -1
  35. package/src/engine-components/Animation.ts +1 -1
  36. package/src/engine-components/Renderer.ts +16 -10
  37. package/src/engine-components/web/ScrollFollow.ts +20 -1
@@ -1,4 +1,4 @@
1
- import { Object3D, Quaternion, Vector3 } from "three";
1
+ import { Euler, Object3D, Quaternion, Vector3 } from "three";
2
2
  // https://github.com/uuidjs/uuid
3
3
  // v5 takes string and namespace
4
4
  import { v5 } from 'uuid';
@@ -254,12 +254,27 @@ export function syncInstantiate(object: GameObject | Object3D, opts: SyncInstant
254
254
  if (opts.deleteOnDisconnect === true)
255
255
  model.deleteStateOnDisconnect = true;
256
256
  if (originalOpts) {
257
- if (originalOpts.position)
258
- model.position = { x: originalOpts.position.x, y: originalOpts.position.y, z: originalOpts.position.z };
259
- if (originalOpts.rotation)
257
+ if (originalOpts.position) {
258
+ if (Array.isArray(originalOpts.position)) {
259
+ model.position = { x: originalOpts.position[0], y: originalOpts.position[1], z: originalOpts.position[2] };
260
+ }
261
+ else model.position = { x: originalOpts.position.x, y: originalOpts.position.y, z: originalOpts.position.z };
262
+ }
263
+ if (originalOpts.rotation) {
264
+ if (originalOpts.rotation instanceof Euler) {
265
+ originalOpts.rotation = new Quaternion().setFromEuler(originalOpts.rotation);
266
+ }
267
+ else if (originalOpts.rotation instanceof Array) {
268
+ originalOpts.rotation = new Quaternion().fromArray(originalOpts.rotation);
269
+ }
260
270
  model.rotation = { x: originalOpts.rotation.x, y: originalOpts.rotation.y, z: originalOpts.rotation.z, w: originalOpts.rotation.w };
261
- if (originalOpts.scale)
262
- model.scale = { x: originalOpts.scale.x, y: originalOpts.scale.y, z: originalOpts.scale.z };
271
+ }
272
+ if (originalOpts.scale) {
273
+ if (Array.isArray(originalOpts.scale)) {
274
+ model.scale = { x: originalOpts.scale[0], y: originalOpts.scale[1], z: originalOpts.scale[2] };
275
+ }
276
+ else model.scale = { x: originalOpts.scale.x, y: originalOpts.scale.y, z: originalOpts.scale.z };
277
+ }
263
278
  }
264
279
  if (!model.position)
265
280
  model.position = { x: go.position.x, y: go.position.y, z: go.position.z };
@@ -658,10 +658,10 @@ export const $isAssigningProperties = Symbol("assigned component properties");
658
658
  * @param key the key that is being assigned
659
659
  * @param value the value that is being assigned
660
660
  */
661
- type OnAssign = (source: object, key: string, value: any) => any;
661
+ type AssignedCallback = (source: object, key: string, oldValue: any, newValue: any) => any;
662
662
 
663
663
  /** Object.assign behaviour but check if property is writeable (e.g. getter only properties are skipped) */
664
- export function assign(target: any, source: any, info?: ImplementationInformation, opts?: { onAssign?: OnAssign }) {
664
+ export function assign(target: any, source: any, info?: ImplementationInformation, opts?: { onAssigned?: AssignedCallback }) {
665
665
  if (source === undefined || source === null) return;
666
666
  if (target === undefined || target === null) return;
667
667
 
@@ -699,13 +699,13 @@ export function assign(target: any, source: any, info?: ImplementationInformatio
699
699
  // arrow functions are defined as properties on the object
700
700
  continue;
701
701
  }
702
- if (!desc || desc.writable === true) {
703
- const value = opts?.onAssign ? opts.onAssign(source, key, source[key]) : source[key];
704
- target[key] = value;
705
- }
706
- else if (desc?.set !== undefined) {
707
- const value = opts?.onAssign ? opts.onAssign(source, key, source[key]) : source[key];
708
- target[key] = value;
702
+ if (!desc || desc.writable === true || desc.set !== undefined) {
703
+ const newValue = source[key];
704
+ const oldValue = target[key];
705
+ target[key] = newValue;
706
+ if (opts?.onAssigned) {
707
+ opts.onAssigned(target, key, oldValue, newValue);
708
+ }
709
709
  }
710
710
  }
711
711
  delete target[$isAssigningProperties];
@@ -27,7 +27,8 @@ export const serializable = function <T>(type?: Constructor<T> | null | Array<Co
27
27
 
28
28
  return function (_target: any, _propertyKey: string | { name: string }) {
29
29
  if (!_target) {
30
- console.error("Found @serializable decorator without a target");
30
+ const propertyName = typeof _propertyKey === 'string' ? _propertyKey : _propertyKey.name;
31
+ console.warn(`@serializable without a target at '${propertyName}'.`);
31
32
  return;
32
33
  }
33
34
  // The _propertyKey parameter is a string in TS4 with experimentalDecorators
@@ -181,7 +181,7 @@ export class Animation extends Behaviour implements IAnimationComponent {
181
181
  }
182
182
  }
183
183
  get animations(): AnimationClip[] {
184
- return this.gameObject.animations || this._tempAnimationsArray || [];
184
+ return this.gameObject?.animations || this._tempAnimationsArray || [];
185
185
  }
186
186
 
187
187
  private mixer: AnimationMixer | undefined = undefined;
@@ -1,5 +1,5 @@
1
1
  import { getRaycastMesh } from "@needle-tools/gltf-progressive";
2
- import { AxesHelper, Material, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
2
+ import { AxesHelper, Material, Mesh, MeshBasicMaterial, MeshPhysicalMaterial, MeshStandardMaterial, Object3D, RawShaderMaterial, ShaderMaterial, SkinnedMesh, Texture, Vector4 } from "three";
3
3
 
4
4
  import { showBalloonWarning } from "../engine/debug/index.js";
5
5
  import { getComponent, getOrAddComponent } from "../engine/engine_components.js";
@@ -48,6 +48,8 @@ export enum RenderState {
48
48
  Front = 2,
49
49
  }
50
50
 
51
+ type SharedMaterial = (Material & Partial<MeshStandardMaterial> & Partial<MeshPhysicalMaterial> & Partial<ShaderMaterial> & Partial<RawShaderMaterial>);
52
+
51
53
 
52
54
  // support sharedMaterials[index] assigning materials directly to the objects
53
55
  class SharedMaterialArray implements ISharedMaterials {
@@ -185,7 +187,7 @@ class SharedMaterialArray implements ISharedMaterials {
185
187
  this.changed = true;
186
188
  }
187
189
 
188
- private getMaterial(index: number): Material | null {
190
+ private getMaterial(index: number): SharedMaterial | null {
189
191
  index = this.resolveIndex(index);
190
192
  if (index < 0) return null;
191
193
  const obj = this._targets;
@@ -302,11 +304,11 @@ export class Renderer extends Behaviour implements IRenderer {
302
304
  return this._sharedMeshes;
303
305
  }
304
306
 
305
- get sharedMaterial(): Material {
306
- return this.sharedMaterials[0];
307
+ get sharedMaterial(): SharedMaterial {
308
+ return this.sharedMaterials[0] as SharedMaterial;
307
309
  }
308
310
 
309
- set sharedMaterial(mat: Material) {
311
+ set sharedMaterial(mat: SharedMaterial) {
310
312
  const cur = this.sharedMaterials[0];
311
313
  if (cur === mat) return;
312
314
  this.sharedMaterials[0] = mat;
@@ -314,12 +316,12 @@ export class Renderer extends Behaviour implements IRenderer {
314
316
  }
315
317
 
316
318
  /**@deprecated please use sharedMaterial */
317
- get material(): Material {
318
- return this.sharedMaterials[0];
319
+ get material(): SharedMaterial {
320
+ return this.sharedMaterials[0] as SharedMaterial;
319
321
  }
320
322
 
321
323
  /**@deprecated please use sharedMaterial */
322
- set material(mat: Material) {
324
+ set material(mat: SharedMaterial) {
323
325
  this.sharedMaterial = mat;
324
326
  }
325
327
 
@@ -329,10 +331,10 @@ export class Renderer extends Behaviour implements IRenderer {
329
331
  private _probeAnchorLastFrame?: Object3D;
330
332
 
331
333
  // this is just available during deserialization
332
- private set sharedMaterials(_val: Array<Material | null>) {
334
+ private set sharedMaterials(_val: Array<SharedMaterial | null>) {
333
335
  // TODO: elements in the array might be missing at the moment which leads to problems if an index is serialized
334
336
  if (!this._originalMaterials) {
335
- this._originalMaterials = _val as Material[];
337
+ this._originalMaterials = _val as SharedMaterial[];
336
338
  }
337
339
  else if (_val) {
338
340
  let didWarn = false;
@@ -353,6 +355,10 @@ export class Renderer extends Behaviour implements IRenderer {
353
355
 
354
356
  //@ts-ignore
355
357
  get sharedMaterials(): SharedMaterialArray {
358
+
359
+ // @ts-ignore during deserialization code might access this property *before* the setter and then create an empty array
360
+ if(!this.__didAwake) return null;
361
+
356
362
  if (!this._sharedMaterials || !this._sharedMaterials.is(this)) {
357
363
  if (!this._originalMaterials) this._originalMaterials = [];
358
364
  this._sharedMaterials = new SharedMaterialArray(this, this._originalMaterials);
@@ -28,18 +28,36 @@ type ScrollFollowEvent = {
28
28
  * The ScrollFollow component allows you to link the scroll position of the page (or a specific element) to one or more target objects.
29
29
  * This can be used to create scroll-based animations, audio playback, or other effects. For example you can link the scroll position to a timeline (PlayableDirector) to create scroll-based storytelling effects or to an Animator component to change the animation state based on scroll.
30
30
  *
31
+ * Assign {@link target} objects to the component to have them updated based on the current scroll position (check the 'target' property for supported types).
32
+ *
31
33
  * @link Example at https://scrollytelling-2-z23hmxby7c6x-u30ld.needle.run/
34
+ * @link Template at https://github.com/needle-engine/scrollytelling-template
35
+ *
36
+ * ## How to use with an Animator
37
+ * 1. Create an Animator component and set up a float parameter named "scroll".
38
+ * 2. Create transitions between animation states based on the "scroll" parameter (e.g. from 0 to 1).
39
+ * 3. Add a ScrollFollow component to the same GameObject or another GameObject in the scene.
40
+ * 4. Assign the Animator component to the ScrollFollow's target property.
41
+ *
42
+ * ## How to use with a PlayableDirector (timeline)
43
+ * 1. Create a PlayableDirector component and set up a timeline asset.
44
+ * 2. Add a ScrollFollow component to the same GameObject or another GameObject in the scene.
45
+ * 3. Assign the PlayableDirector component to the ScrollFollow's target property.
46
+ * 4. The timeline will now scrub based on the scroll position of the page.
32
47
  */
33
48
  export class ScrollFollow extends Behaviour {
34
49
 
35
50
  /**
36
- * Target object(s) to follow the scroll position of the page. If null, the main camera is used.
51
+ * Target object(s) to follow the scroll position of the page.
37
52
  *
38
53
  * Supported target types:
39
54
  * - PlayableDirector (timeline), the scroll position will be mapped to the timeline time
40
55
  * - Animator, the scroll position will be set to a float parameter named "scroll"
41
56
  * - Animation, the scroll position will be mapped to the animation time
42
57
  * - AudioSource, the scroll position will be mapped to the audio time
58
+ * - SplineWalker, the scroll position will be mapped to the position01 property
59
+ * - Light, the scroll position will be mapped to the intensity property
60
+ * - Object3D, the object will move vertically based on the scroll position
43
61
  * - Any object with a `scroll` property (number or function)
44
62
  */
45
63
  @serializable([Behaviour, Object3D])
@@ -204,6 +222,7 @@ export class ScrollFollow extends Behaviour {
204
222
  }
205
223
  const bounds = target["needle:scrollbounds"] as Box3;
206
224
  if (bounds) {
225
+ // TODO: remap position to use upper screen edge and lower edge instead of center
207
226
  target.position.y = -bounds.min.y - value * (bounds.max.y - bounds.min.y);
208
227
  }
209
228
  }