@needle-tools/engine 2.59.2-pre → 2.60.0-pre

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 (40) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.js +5442 -5422
  3. package/dist/needle-engine.umd.cjs +99 -99
  4. package/lib/engine/engine_gltf_builtin_components.js +13 -5
  5. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  6. package/lib/engine/engine_serialization_builtin_serializer.d.ts +1 -1
  7. package/lib/engine/engine_serialization_builtin_serializer.js +14 -0
  8. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  9. package/lib/engine/engine_types.d.ts +1 -0
  10. package/lib/engine/engine_types.js.map +1 -1
  11. package/lib/engine/extensions/NEEDLE_render_objects.js +19 -13
  12. package/lib/engine/extensions/NEEDLE_render_objects.js.map +1 -1
  13. package/lib/engine-components/ParticleSystem.d.ts +1 -1
  14. package/lib/engine-components/ParticleSystem.js +16 -8
  15. package/lib/engine-components/ParticleSystem.js.map +1 -1
  16. package/lib/engine-components/ParticleSystemModules.d.ts +2 -0
  17. package/lib/engine-components/ParticleSystemModules.js +2 -2
  18. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  19. package/lib/engine-components/Skybox.d.ts +0 -1
  20. package/lib/engine-components/Skybox.js +2 -5
  21. package/lib/engine-components/Skybox.js.map +1 -1
  22. package/lib/engine-components/timeline/PlayableDirector.js +11 -2
  23. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  24. package/lib/engine-components/timeline/TimelineModels.d.ts +3 -2
  25. package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
  26. package/lib/engine-components/timeline/TimelineTracks.d.ts +1 -0
  27. package/lib/engine-components/timeline/TimelineTracks.js +29 -7
  28. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  29. package/lib/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +2 -2
  31. package/src/engine/engine_gltf_builtin_components.ts +14 -6
  32. package/src/engine/engine_serialization_builtin_serializer.ts +17 -1
  33. package/src/engine/engine_types.ts +1 -0
  34. package/src/engine/extensions/NEEDLE_render_objects.ts +22 -18
  35. package/src/engine-components/ParticleSystem.ts +17 -7
  36. package/src/engine-components/ParticleSystemModules.ts +3 -3
  37. package/src/engine-components/Skybox.ts +2 -4
  38. package/src/engine-components/timeline/PlayableDirector.ts +10 -2
  39. package/src/engine-components/timeline/TimelineModels.ts +4 -3
  40. package/src/engine-components/timeline/TimelineTracks.ts +31 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.59.2-pre",
3
+ "version": "2.60.0-pre",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally.",
5
5
  "main": "dist/needle-engine.umd.cjs",
6
6
  "module": "dist/needle-engine.js",
@@ -56,7 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@babel/runtime": "^7.16.0",
58
58
  "@luncheon/esbuild-plugin-gzip": "^0.1.0",
59
- "@needle-tools/gltf-transform-extensions": "^0.10.9-pre",
59
+ "@needle-tools/gltf-transform-extensions": "^0.10.10-pre",
60
60
  "@needle-tools/needle-component-compiler": "1.9.3",
61
61
  "@needle-tools/helper": "^0.2.1-pre",
62
62
  "@types/three": "0.146.0",
@@ -9,7 +9,7 @@ import { assign, ImplementationInformation, ISerializable, SerializationContext
9
9
  import { NEEDLE_components } from "./extensions/NEEDLE_components";
10
10
  import { debugExtension } from "./engine_default_parameters";
11
11
  import { builtinComponentKeyName } from "./engine_constants";
12
- import { SourceIdentifier } from "./engine_types";
12
+ import { GuidsMap, SourceIdentifier } from "./engine_types";
13
13
  import { UIDProvider } from "./engine_types";
14
14
  import { addNewComponent } from "./engine_components";
15
15
  import { getParam } from "./engine_utils";
@@ -83,9 +83,10 @@ export async function createBuiltinComponents(context: Context, gltfId: SourceId
83
83
  // and this should run before awake and onenable of newly created components
84
84
  if (idProvider) {
85
85
  // TODO: should we do this after setup callbacks now?
86
- recursiveCreateGuids(gltf, idProvider);
86
+ const guidsMap: GuidsMap = {};
87
+ recursiveCreateGuids(gltf, idProvider, guidsMap);
87
88
  for (const scene of gltf.scenes)
88
- recursiveCreateGuids(scene, idProvider);
89
+ recursiveCreateGuids(scene, idProvider, guidsMap);
89
90
  }
90
91
  });
91
92
 
@@ -94,22 +95,29 @@ export async function createBuiltinComponents(context: Context, gltfId: SourceId
94
95
  // console.log("finished creating builtin components", gltf.scene?.name, gltf);
95
96
  }
96
97
 
97
- function recursiveCreateGuids(obj: GameObject, idProvider: UIDProvider | null) {
98
+ function recursiveCreateGuids(obj: GameObject, idProvider: UIDProvider | null, guidsMap: GuidsMap) {
98
99
  if (idProvider === null) return;
99
100
  if (!obj) return;
101
+ const prev = obj.guid;
100
102
  obj.guid = idProvider.generateUUID();
103
+ if (prev && prev !== "invalid")
104
+ guidsMap[prev] = obj.guid;
101
105
  // console.log(obj);
102
106
  if (obj && obj.userData && obj.userData.components) {
103
107
  for (const comp of obj.userData.components) {
104
108
  if (comp === null) continue;
109
+ const prev = comp.guid;
105
110
  comp.guid = idProvider.generateUUID();
106
- // console.log(comp.guid)
111
+ if (prev && prev !== "invalid")
112
+ guidsMap[prev] = comp.guid;
113
+ if (comp.resolveGuids)
114
+ comp.resolveGuids(guidsMap);
107
115
  }
108
116
 
109
117
  }
110
118
  if (obj.children) {
111
119
  for (const child of obj.children) {
112
- recursiveCreateGuids(child as GameObject, idProvider);
120
+ recursiveCreateGuids(child as GameObject, idProvider, guidsMap);
113
121
  }
114
122
  }
115
123
  }
@@ -70,7 +70,23 @@ class ObjectSerializer extends TypeSerializer {
70
70
  return undefined;
71
71
  }
72
72
 
73
- onDeserialize(data: ObjectData, context: SerializationContext) {
73
+ onDeserialize(data: ObjectData | string | null, context: SerializationContext) {
74
+
75
+ if (typeof data === "string") {
76
+ // ACTUALLY: this is already handled by the extension_utils where we resolve json pointers recursively
77
+ // if(data.startsWith("/nodes/")){
78
+ // const node = parseInt(data.substring("/nodes/".length));
79
+ // if (context.nodeToObject) {
80
+ // const res = context.nodeToObject[node];
81
+ // if (debugExtension)
82
+ // console.log("Deserialized object reference?", data, res, context?.nodeToObject);
83
+ // if (!res) console.warn("Did not find node: " + data, context.nodeToObject, context.object);
84
+ // return res;
85
+ // }
86
+ // }
87
+ return undefined;
88
+ }
89
+
74
90
  if (data) {
75
91
  if (data.node !== undefined && context.nodeToObject) {
76
92
  const res = context.nodeToObject[data.node];
@@ -151,6 +151,7 @@ export declare interface ICamera extends IComponent {
151
151
  nearClipPlane: number;
152
152
  farClipPlane: number;
153
153
  backgroundColor: RGBAColor | null;
154
+ backgroundBlurriness: number | undefined;
154
155
  clearFlags: number;
155
156
  aspect: number;
156
157
  fieldOfView: number;
@@ -45,6 +45,8 @@ declare type StencilSettingsModel = {
45
45
  zFailOp: number;
46
46
  }
47
47
 
48
+ const $stencils = Symbol("stencils");
49
+
48
50
  export class NEEDLE_render_objects implements GLTFLoaderPlugin {
49
51
 
50
52
  private static stencils: { [key: string]: StencilSettingsModel[] } = {};
@@ -52,31 +54,33 @@ export class NEEDLE_render_objects implements GLTFLoaderPlugin {
52
54
  static applyStencil(obj?: IRenderer | null) {
53
55
  if (!obj) return;
54
56
  const source = obj.sourceId;
55
- if (debug)
56
- console.log(source, NEEDLE_render_objects.stencils);
57
+ if (debug) console.log(source, NEEDLE_render_objects.stencils);
57
58
  if (!source) return;
58
59
  const settings = NEEDLE_render_objects.stencils[source];
59
60
  if (!settings) return;
60
61
  for (let i = settings.length - 1; i >= 0; i--) {
61
62
  const stencil: StencilSettingsModel = settings[i];
62
63
  if (matchesLayer(stencil.layer, obj)) {
63
- if (debug)
64
- console.log(stencil);
65
- const mat = obj.sharedMaterial?.clone();
66
- if (mat) {
67
- obj.sharedMaterial = mat;
68
- // you can have 50 renderer features per event until this breaks
69
- obj.gameObject.renderOrder = stencil.event * 1000 + stencil.index * 50;
70
-
71
- mat.stencilWrite = true;
72
- mat.stencilWriteMask = 255;
73
- mat.stencilFuncMask = 255;
74
- mat.stencilRef = stencil.value;
75
- mat.stencilFunc = stencil.compareFunc;
76
- mat.stencilZPass = stencil.passOp;
77
- mat.stencilFail = stencil.failOp;
78
- mat.stencilZFail = stencil.zFailOp;
64
+ if (debug) console.log(stencil);
65
+ for (let i = 0; i < obj.sharedMaterials.length; i++) {
66
+ let mat = obj.sharedMaterials[i];
67
+ if (mat) {
68
+ // if (!mat[$stencils])
69
+ mat = mat.clone();
70
+ mat[$stencils] = true;
71
+ mat.stencilWrite = true;
72
+ mat.stencilWriteMask = 255;
73
+ mat.stencilFuncMask = 255;
74
+ mat.stencilRef = stencil.value;
75
+ mat.stencilFunc = stencil.compareFunc;
76
+ mat.stencilZPass = stencil.passOp;
77
+ mat.stencilFail = stencil.failOp;
78
+ mat.stencilZFail = stencil.zFailOp;
79
+ obj.sharedMaterials[i] = mat;
80
+ }
79
81
  }
82
+ // you can have 50 renderer features per event until this breaks
83
+ obj.gameObject.renderOrder = stencil.event * 1000 + stencil.index * 50;
80
84
  break;
81
85
  }
82
86
  }
@@ -73,12 +73,22 @@ export class ParticleSystemRenderer extends Behaviour {
73
73
  return this.particleMaterial;
74
74
  }
75
75
 
76
- getMesh() {
76
+ getMesh(renderMode?: ParticleSystemRenderMode) {
77
77
  let geo: BufferGeometry | null = null;
78
- if (this.particleMesh instanceof Mesh) {
79
- geo = this.particleMesh.geometry;
78
+ if (renderMode === ParticleSystemRenderMode.HorizontalBillboard) {
79
+ geo = new THREE.BoxGeometry(1, 1, 0);
80
80
  }
81
- if (geo === null) geo = new THREE.BoxGeometry(1, 1, 1);
81
+ else if (renderMode === ParticleSystemRenderMode.VerticalBillboard) {
82
+ geo = new THREE.BoxGeometry(1, 0, 1);
83
+ }
84
+
85
+ if (!geo) {
86
+ if (this.particleMesh instanceof Mesh) {
87
+ geo = this.particleMesh.geometry;
88
+ }
89
+ if (geo === null) geo = new THREE.BoxGeometry(1, 1, 0);
90
+ }
91
+
82
92
  const res = new Mesh(geo, this.getMaterial());
83
93
  return res;
84
94
  }
@@ -494,7 +504,7 @@ class ParticleSystemInterface implements ParticleSystemParameters {
494
504
  onlyUsedByOther?: boolean | undefined;
495
505
  readonly behaviors: Behavior[] = [];
496
506
  get instancingGeometry() {
497
- return this.system.renderer.getMesh().geometry;
507
+ return this.system.renderer.getMesh(this.system.renderer.renderMode).geometry;
498
508
  }
499
509
  get renderMode() {
500
510
  if (this.system.trails["enabled"] === true) {
@@ -503,8 +513,8 @@ class ParticleSystemInterface implements ParticleSystemParameters {
503
513
  switch (this.system.renderer.renderMode) {
504
514
  case ParticleSystemRenderMode.Billboard: return RenderMode.BillBoard;
505
515
  // case ParticleSystemRenderMode.Stretch: return RenderMode.Stretch;
506
- // case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.HorizontalBillboard;
507
- // case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.VerticalBillboard;
516
+ case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.LocalSpace;
517
+ case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.LocalSpace;
508
518
  case ParticleSystemRenderMode.Mesh: return RenderMode.LocalSpace;
509
519
  }
510
520
  return RenderMode.BillBoard;
@@ -36,8 +36,8 @@ export interface IParticleSystem {
36
36
  export enum ParticleSystemRenderMode {
37
37
  Billboard = 0,
38
38
  // Stretch = 1,
39
- // HorizontalBillboard = 2,
40
- // VerticalBillboard = 3,
39
+ HorizontalBillboard = 2,
40
+ VerticalBillboard = 3,
41
41
  Mesh = 4,
42
42
  // None = 5,
43
43
  }
@@ -1117,7 +1117,7 @@ export class RotationOverLifetimeModule {
1117
1117
  @serializable()
1118
1118
  zMultiplier!: number;
1119
1119
 
1120
- evaluate(t01: number, t:number): number {
1120
+ evaluate(t01: number, t: number): number {
1121
1121
  if (!this.enabled) return 0;
1122
1122
  if (!this.separateAxes) {
1123
1123
  const rot = this.z.evaluate(t01, t) * -1;
@@ -18,9 +18,6 @@ export class RemoteSkybox extends Behaviour {
18
18
  @serializable()
19
19
  background: boolean = true;
20
20
 
21
- @serializable()
22
- backgroundBlurriness: number = 0;
23
-
24
21
  @serializable()
25
22
  environment: boolean = true;
26
23
 
@@ -84,7 +81,8 @@ export class RemoteSkybox extends Behaviour {
84
81
  this._prevLoadedEnvironment = envMap;
85
82
  const nameIndex = url.lastIndexOf("/");
86
83
  envMap.name = url.substring(nameIndex >= 0 ? nameIndex + 1 : 0);
87
- this.context.scene.backgroundBlurriness = this.backgroundBlurriness;
84
+ if (this.context.mainCameraComponent?.backgroundBlurriness !== undefined)
85
+ this.context.scene.backgroundBlurriness = this.context.mainCameraComponent.backgroundBlurriness;
88
86
  }
89
87
 
90
88
 
@@ -8,6 +8,7 @@ import * as Models from "./TimelineModels";
8
8
  import * as Tracks from "./TimelineTracks";
9
9
  import { deepClone, getParam } from '../../engine/engine_utils';
10
10
  import { GuidsMap } from '../../engine/engine_types';
11
+ import { Object3D } from 'three';
11
12
 
12
13
  const debug = getParam("debugtimeline");
13
14
 
@@ -371,10 +372,17 @@ export class PlayableDirector extends Behaviour {
371
372
  }
372
373
  // only handle animation tracks
373
374
  if (track.type === Models.TrackType.Animation) {
374
- if (track.clips.length <= 0) continue;
375
+ if (track.clips.length <= 0) {
376
+ if(debug) console.warn("Animation track has no clips", track);
377
+ continue;
378
+ }
375
379
  // loop outputs / bindings, they should contain animator references
376
380
  for (let i = track.outputs.length - 1; i >= 0; i--) {
377
- const binding = track.outputs[i] as Animator;
381
+ let binding = track.outputs[i] as Animator;
382
+ if(binding instanceof Object3D){
383
+ const anim = GameObject.getOrAddComponent(binding, Animator);
384
+ if(anim) binding = anim;
385
+ }
378
386
  if (typeof binding.enabled === "boolean") binding.enabled = false;
379
387
  const animationClips = binding?.gameObject?.animations;
380
388
  if (animationClips) {
@@ -51,7 +51,8 @@ export declare type ClipModel = {
51
51
  easeInDuration: number;
52
52
  easeOutDuration: number;
53
53
  preExtrapolationMode: ClipExtrapolation;
54
- postExtrapolationMode: ClipExtrapolation
54
+ postExtrapolationMode: ClipExtrapolation;
55
+ reversed?: boolean;
55
56
  }
56
57
 
57
58
  export declare type AnimationClipModel = {
@@ -59,8 +60,8 @@ export declare type AnimationClipModel = {
59
60
  loop: boolean;
60
61
  duration: number;
61
62
  removeStartOffset: boolean;
62
- position: Vec3 | THREE.Vector3;
63
- rotation: Quat | THREE.Quaternion;
63
+ position?: Vec3 | THREE.Vector3;
64
+ rotation?: Quat | THREE.Quaternion;
64
65
  }
65
66
 
66
67
  export declare type AudioClipModel = {
@@ -213,7 +213,7 @@ export class AnimationTrackHandler extends TrackHandler {
213
213
  const clipData = model.asset as Models.AnimationClipModel;
214
214
  const pos = clipData.position as any;
215
215
  const rot = clipData.rotation as any;
216
- if (pos.x !== undefined) {
216
+ if (pos && pos.x !== undefined) {
217
217
  if (!pos.isVector3) {
218
218
  clipData.position = new Vector3(pos.x, pos.y, pos.z);
219
219
  }
@@ -254,6 +254,7 @@ export class AnimationTrackHandler extends TrackHandler {
254
254
  private _tempPos = new THREE.Vector3();
255
255
  private _summedRot = new THREE.Quaternion();
256
256
  private _tempRot = new THREE.Quaternion();
257
+ private _clipRotQuat = new THREE.Quaternion();
257
258
 
258
259
  evaluate(time: number) {
259
260
  if (this.track.muted) return;
@@ -315,7 +316,9 @@ export class AnimationTrackHandler extends TrackHandler {
315
316
  }
316
317
  }
317
318
 
318
- action.time = t;
319
+ if(model.reversed === true) action.time = action.getClip().duration - t;
320
+ else action.time = t;
321
+
319
322
  action.timeScale = 0;
320
323
  const effectiveWeight = weight * this.director.weight;
321
324
  action.weight = effectiveWeight;
@@ -335,8 +338,10 @@ export class AnimationTrackHandler extends TrackHandler {
335
338
  const tempRot = this._tempRot.identity();
336
339
 
337
340
  const clipOffsetRot = clipModel.rotation as Quaternion;
338
- const clipRot = new THREE.Quaternion();
339
- clipRot.slerp(clipOffsetRot, weight);
341
+ if (clipOffsetRot) {
342
+ this._clipRotQuat.identity();
343
+ this._clipRotQuat.slerp(clipOffsetRot, weight);
344
+ }
340
345
 
341
346
  const offsets = this._actionOffsets[i];
342
347
  if (offsets.hasOffsets) {
@@ -345,7 +350,10 @@ export class AnimationTrackHandler extends TrackHandler {
345
350
  tempPos.copy(offsets.rootPositionOffset);
346
351
  else tempPos.set(0, 0, 0);
347
352
 
348
- tempPos.applyQuaternion(summedRot).applyQuaternion(clipRot)
353
+ tempPos.applyQuaternion(summedRot);
354
+ if (this._clipRotQuat)
355
+ tempPos.applyQuaternion(this._clipRotQuat);
356
+
349
357
  if (offsets.rootQuaternionOffset) {
350
358
  // console.log(new THREE.Euler().setFromQuaternion(offsets.rootQuaternionOffset).y.toFixed(2));
351
359
  tempRot.copy(offsets.rootQuaternionOffset);
@@ -355,10 +363,12 @@ export class AnimationTrackHandler extends TrackHandler {
355
363
  }
356
364
  }
357
365
 
358
- totalRotation.multiply(clipRot);
366
+ if (this._clipRotQuat)
367
+ totalRotation.multiply(this._clipRotQuat);
359
368
  totalRotation.multiply(summedRot);
360
369
 
361
- summedPos.add(clipModel.position as THREE.Vector3);
370
+ if (clipModel.position)
371
+ summedPos.add(clipModel.position as THREE.Vector3);
362
372
  totalPosition.add(summedPos);
363
373
  }
364
374
 
@@ -506,7 +516,7 @@ export class AudioTrackHandler extends TrackHandler {
506
516
  }
507
517
  }
508
518
  evaluate(time: number) {
509
- if(muteAudioTracks) return;
519
+ if (muteAudioTracks) return;
510
520
  if (this.track.muted) return;
511
521
  for (let i = 0; i < this.models.length; i++) {
512
522
  const model = this.models[i];
@@ -521,6 +531,19 @@ export class AudioTrackHandler extends TrackHandler {
521
531
  audio.offset = model.clipIn + (time - model.start) * model.timeScale;
522
532
  audio.play();
523
533
  }
534
+ else {
535
+ const targetOffset = model.clipIn + (time - model.start) * model.timeScale;
536
+ // seems it's non-trivial to get the right time from audio sources;
537
+ // https://github.com/mrdoob/three.js/blob/master/src/audio/Audio.js#L170
538
+ const currentTime = audio.context.currentTime - audio["_startedAt"] + audio.offset;
539
+ const diff = Math.abs(targetOffset - currentTime);
540
+
541
+ if (diff > 0.3) {
542
+ audio.offset = targetOffset;
543
+ audio.stop();
544
+ audio.play();
545
+ }
546
+ }
524
547
  let vol = model.asset.volume;
525
548
  if (model.easeInDuration > 0) {
526
549
  const easeIn = Math.min((time - model.start) / model.easeInDuration, 1);