@needle-tools/engine 3.19.0 → 3.19.1

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 +3777 -3704
  3. package/dist/needle-engine.light.js +3690 -3617
  4. package/dist/needle-engine.light.min.js +114 -114
  5. package/dist/needle-engine.light.umd.cjs +110 -110
  6. package/dist/needle-engine.min.js +109 -109
  7. package/dist/needle-engine.umd.cjs +111 -111
  8. package/lib/engine-components/CharacterController.js +9 -8
  9. package/lib/engine-components/CharacterController.js.map +1 -1
  10. package/lib/engine-components/ParticleSystem.d.ts +4 -1
  11. package/lib/engine-components/ParticleSystem.js +22 -10
  12. package/lib/engine-components/ParticleSystem.js.map +1 -1
  13. package/lib/engine-components/SceneSwitcher.js +10 -1
  14. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  15. package/lib/engine-components/ui/Canvas.d.ts +5 -1
  16. package/lib/engine-components/ui/Canvas.js +22 -4
  17. package/lib/engine-components/ui/Canvas.js.map +1 -1
  18. package/lib/engine-components/ui/EventSystem.d.ts +1 -1
  19. package/lib/engine-components/ui/EventSystem.js +7 -3
  20. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  21. package/lib/engine-components/ui/Interfaces.d.ts +6 -0
  22. package/lib/engine-components/ui/Interfaces.js.map +1 -1
  23. package/lib/engine-components/ui/Text.d.ts +4 -3
  24. package/lib/engine-components/ui/Text.js +14 -5
  25. package/lib/engine-components/ui/Text.js.map +1 -1
  26. package/lib/engine-components/webxr/WebARSessionRoot.d.ts +6 -1
  27. package/lib/engine-components/webxr/WebARSessionRoot.js +30 -4
  28. package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
  29. package/lib/engine-components/webxr/WebXR.js +3 -2
  30. package/lib/engine-components/webxr/WebXR.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/engine-components/CharacterController.ts +13 -9
  33. package/src/engine-components/ParticleSystem.ts +19 -14
  34. package/src/engine-components/SceneSwitcher.ts +9 -1
  35. package/src/engine-components/ui/Canvas.ts +25 -6
  36. package/src/engine-components/ui/EventSystem.ts +7 -3
  37. package/src/engine-components/ui/Interfaces.ts +7 -0
  38. package/src/engine-components/ui/Text.ts +16 -7
  39. package/src/engine-components/webxr/WebARSessionRoot.ts +35 -5
  40. package/src/engine-components/webxr/WebXR.ts +3 -2
@@ -52,6 +52,12 @@ export class ParticleSystemRenderer extends Behaviour {
52
52
  @serializable()
53
53
  minParticleSize!: number;
54
54
 
55
+ @serializable()
56
+ velocityScale?: number;
57
+ @serializable()
58
+ cameraVelocityScale?: number;
59
+ @serializable()
60
+ lengthScale?: number;
55
61
 
56
62
  start() {
57
63
  if (this.maxParticleSize !== .5 && this.minParticleSize !== 0) {
@@ -99,15 +105,8 @@ export class ParticleSystemRenderer extends Behaviour {
99
105
  return material;
100
106
  }
101
107
 
102
- getMesh(renderMode?: ParticleSystemRenderMode) {
108
+ getMesh(_renderMode?: ParticleSystemRenderMode) {
103
109
  let geo: BufferGeometry | null = null;
104
- if (renderMode === ParticleSystemRenderMode.HorizontalBillboard) {
105
- geo = new THREE.BoxGeometry(1, 1, 0);
106
- }
107
- else if (renderMode === ParticleSystemRenderMode.VerticalBillboard) {
108
- geo = new THREE.BoxGeometry(1, 0, 1);
109
- }
110
-
111
110
  if (!geo) {
112
111
  if (this.particleMesh instanceof Mesh) {
113
112
  geo = this.particleMesh.geometry;
@@ -556,7 +555,7 @@ class ParticleSystemInterface implements ParticleSystemParameters {
556
555
  }
557
556
 
558
557
  get prewarm() { return false; } // force disable three.quark prewarm, we have our own!
559
- get material() { return this.system.renderer.getMaterial(this.system.trails.enabled) as Material;}
558
+ get material() { return this.system.renderer.getMaterial(this.system.trails.enabled) as Material; }
560
559
  get layers() { return this.system.gameObject.layers; }
561
560
 
562
561
  update() {
@@ -590,8 +589,8 @@ class ParticleSystemInterface implements ParticleSystemParameters {
590
589
  switch (this.system.renderer.renderMode) {
591
590
  case ParticleSystemRenderMode.Billboard: return RenderMode.BillBoard;
592
591
  case ParticleSystemRenderMode.Stretch: return RenderMode.StretchedBillBoard;
593
- case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.BillBoard;
594
- case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.BillBoard;
592
+ case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.HorizontalBillBoard;
593
+ case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.VerticalBillBoard;
595
594
  case ParticleSystemRenderMode.Mesh: return RenderMode.Mesh;
596
595
  }
597
596
  return RenderMode.BillBoard;
@@ -600,7 +599,13 @@ class ParticleSystemInterface implements ParticleSystemParameters {
600
599
  startLength: new ConstantValue(220),
601
600
  followLocalOrigin: false,
602
601
  };
603
- get speedFactor() { return this.system.main.simulationSpeed; }
602
+ get speedFactor() {
603
+ let factor = this.system.main.simulationSpeed;
604
+ if (this.system.renderer?.renderMode === ParticleSystemRenderMode.Stretch) {
605
+ factor *= this.system.renderer.velocityScale ?? 1;
606
+ }
607
+ return factor;
608
+ }
604
609
  get texture(): THREE.Texture {
605
610
  const mat = this.material;
606
611
  if (mat && mat["map"]) {
@@ -882,7 +887,7 @@ export class ParticleSystem extends Behaviour implements IParticleSystem {
882
887
  awake(): void {
883
888
  this._renderer = this.gameObject.getComponent(ParticleSystemRenderer) as ParticleSystemRenderer;
884
889
 
885
- if (!this.main) {
890
+ if (!this.main) {
886
891
  throw new Error("Not Supported: ParticleSystem needs a serialized MainModule. Creating new particle systems at runtime is currently not supported.");
887
892
  }
888
893
 
@@ -986,7 +991,7 @@ export class ParticleSystem extends Behaviour implements IParticleSystem {
986
991
  private onSimulate(dt: number) {
987
992
  if (this._batchSystem) {
988
993
  let needsUpdate = this.context.time.frameCount % 60 === 0;
989
- if(this._lastBatchesCount !== this._batchSystem.batches.length) {
994
+ if (this._lastBatchesCount !== this._batchSystem.batches.length) {
990
995
  this._lastBatchesCount = this._batchSystem.batches.length;
991
996
  needsUpdate = true;
992
997
  }
@@ -467,8 +467,16 @@ export class SceneSwitcher extends Behaviour {
467
467
  }
468
468
  }
469
469
 
470
- private tryGetSceneEventListener(obj: Object3D): ISceneEventListener | null {
470
+ private tryGetSceneEventListener(obj: Object3D, level: number = 0): ISceneEventListener | null {
471
471
  const sceneListener = GameObject.foreachComponent(obj, c => (c as any as ISceneEventListener).sceneClosing ? c : null) as ISceneEventListener | null;
472
+ // if we didnt find any component with the listener on the root object
473
+ // we also check the first level of its children because a scene might be a group
474
+ if (level === 0 && !sceneListener && obj.children.length) {
475
+ for (const ch of obj.children) {
476
+ const res = this.tryGetSceneEventListener(ch, level + 1);
477
+ if (res) return res;
478
+ }
479
+ }
472
480
  return sceneListener;
473
481
  }
474
482
  }
@@ -5,7 +5,7 @@ import { BaseUIComponent, UIRootComponent } from "./BaseUIComponent.js";
5
5
  import { GameObject } from "../Component.js";
6
6
  import { Matrix4, Object3D } from "three";
7
7
  import { RectTransform } from "./RectTransform.js";
8
- import { ICanvas, ILayoutGroup, IRectTransform } from "./Interfaces.js";
8
+ import { ICanvas, ICanvasEventReceiver, ILayoutGroup, IRectTransform } from "./Interfaces.js";
9
9
  import { Camera } from "../Camera.js";
10
10
  import { EventSystem } from "./EventSystem.js";
11
11
  import * as ThreeMeshUI from 'three-mesh-ui'
@@ -189,15 +189,26 @@ export class Canvas extends UIRootComponent implements ICanvas {
189
189
  this._layoutGroups.delete(obj);
190
190
  }
191
191
 
192
- onBeforeRenderRoutine = () => {
192
+ private _receivers: ICanvasEventReceiver[] = [];
193
+ registerEventReceiver(receiver: ICanvasEventReceiver) {
194
+ this._receivers.push(receiver);
195
+ }
196
+ unregisterEventReceiver(receiver: ICanvasEventReceiver) {
197
+ const index = this._receivers.indexOf(receiver);
198
+ if (index !== -1) {
199
+ this._receivers.splice(index, 1);
200
+ }
201
+ }
193
202
 
203
+ onBeforeRenderRoutine = () => {
194
204
  if (this.context.isInVR) {
195
205
  this.onUpdateRenderMode();
196
206
  this.handleLayoutUpdates();
197
207
  // TODO TMUI @swingingtom - For VR this is so we don't have text clipping
198
208
  this.shadowComponent?.updateMatrixWorld(true);
199
209
  this.shadowComponent?.updateWorldMatrix(true, true);
200
- EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context);
210
+ this.invokeBeforeRenderEvents();
211
+ EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
201
212
  return;
202
213
  }
203
214
 
@@ -214,6 +225,7 @@ export class Canvas extends UIRootComponent implements ICanvas {
214
225
  // TODO: we might need to optimize this. This is here to make sure the TMUI text clipping matrices are correct. Ideally the text does use onBeforeRender and apply the clipping matrix there so we dont have to force update all the matrices here
215
226
  this.shadowComponent?.updateMatrixWorld(true);
216
227
  this.shadowComponent?.updateWorldMatrix(true, true);
228
+ this.invokeBeforeRenderEvents();
217
229
  EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context);
218
230
  }
219
231
  }
@@ -236,7 +248,8 @@ export class Canvas extends UIRootComponent implements ICanvas {
236
248
  this.handleLayoutUpdates();
237
249
  this.shadowComponent?.updateMatrixWorld(true);
238
250
  // this.handleLayoutUpdates();
239
- EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context);
251
+ this.invokeBeforeRenderEvents();
252
+ EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
240
253
  this.context.renderer.render(this.gameObject, this.context.mainCamera);
241
254
  this.context.renderer.autoClear = prevAutoClearDepth;
242
255
  this.context.renderer.autoClearColor = prevAutoClearColor;
@@ -245,6 +258,12 @@ export class Canvas extends UIRootComponent implements ICanvas {
245
258
  this._lastMatrixWorld?.copy(this.gameObject.matrixWorld);
246
259
  }
247
260
 
261
+ private invokeBeforeRenderEvents() {
262
+ for (const receiver of this._receivers) {
263
+ receiver.onBeforeCanvasRender?.(this);
264
+ }
265
+ }
266
+
248
267
  private handleLayoutUpdates() {
249
268
  if (this._lastMatrixWorld === null) {
250
269
  this._lastMatrixWorld = new Matrix4();
@@ -309,8 +328,8 @@ export class Canvas extends UIRootComponent implements ICanvas {
309
328
  let camera = this.context.mainCameraComponent;
310
329
  let planeDistance: number = 10;
311
330
  if (camera && camera.nearClipPlane > 0 && camera.farClipPlane > 0) {
312
- // TODO: this is a hack/workaround for event system currently only passing events to the nearest object
313
- planeDistance = Mathf.lerp(camera.nearClipPlane, camera.farClipPlane, .15);
331
+ // TODO: this is a hack/workaround for event system currently only passing events to the nearest object so we move the canvas close to the nearplane
332
+ planeDistance = Mathf.lerp(camera.nearClipPlane, camera.farClipPlane, .01);
314
333
  }
315
334
  if (this._renderMode === RenderMode.ScreenSpaceCamera) {
316
335
  if (this.worldCamera)
@@ -68,8 +68,8 @@ export class EventSystem extends Behaviour {
68
68
  }
69
69
 
70
70
  //@ts-ignore
71
- static ensureUpdateMeshUI(instance, context: Context) {
72
- MeshUIHelper.update(instance, context);
71
+ static ensureUpdateMeshUI(instance, context: Context, force: boolean = false) {
72
+ MeshUIHelper.update(instance, context, force);
73
73
  }
74
74
  static markUIDirty(_context: Context) {
75
75
  MeshUIHelper.markDirty();
@@ -533,7 +533,11 @@ class MeshUIHelper {
533
533
  this.needsUpdate = true;
534
534
  }
535
535
 
536
- static update(threeMeshUI: any, context: Context) {
536
+ static update(threeMeshUI: any, context: Context, force: boolean = false) {
537
+ if (force) {
538
+ threeMeshUI.update();
539
+ return;
540
+ }
537
541
  const currentFrame = context.time.frameCount;
538
542
  for (const lu of this.lastUpdateFrame) {
539
543
  if (lu.context === context) {
@@ -6,6 +6,8 @@ export interface ICanvas extends IComponent {
6
6
  get screenspace(): boolean;
7
7
  registerTransform(rt: IRectTransform);
8
8
  unregisterTransform(rt: IRectTransform);
9
+ registerEventReceiver(receiver: ICanvasEventReceiver);
10
+ unregisterEventReceiver(receiver: ICanvasEventReceiver);
9
11
  }
10
12
 
11
13
  export interface ICanvasGroup {
@@ -39,6 +41,11 @@ export interface ILayoutGroup extends IComponent {
39
41
  updateLayout();
40
42
  }
41
43
 
44
+ export interface ICanvasEventReceiver {
45
+ /** Called before the canvas is rendering */
46
+ onBeforeCanvasRender?(canvas: ICanvas);
47
+ }
48
+
42
49
  // export abstract class LayoutGroup extends Behaviour implements IRectTransformChangedReceiver, ILayoutGroup {
43
50
  // get isLayoutGroup(): boolean {
44
51
  // return true;
@@ -6,7 +6,7 @@ import { updateRenderSettings } from './Utils.js';
6
6
  import { Canvas } from './Canvas.js';
7
7
  import { serializable } from '../../engine/engine_serialization_decorator.js';
8
8
  import { getParam, resolveUrl } from '../../engine/engine_utils.js';
9
- import { ICanvas, IHasAlphaFactor } from './Interfaces.js';
9
+ import { ICanvas, ICanvasEventReceiver, IHasAlphaFactor } from './Interfaces.js';
10
10
 
11
11
  const debug = getParam("debugtext");
12
12
 
@@ -38,7 +38,7 @@ export enum FontStyle {
38
38
  BoldAndItalic = 3,
39
39
  }
40
40
 
41
- export class Text extends Graphic implements IHasAlphaFactor {
41
+ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiver {
42
42
 
43
43
  @serializable()
44
44
  alignment: TextAnchor = TextAnchor.UpperLeft;
@@ -102,11 +102,15 @@ export class Text extends Graphic implements IHasAlphaFactor {
102
102
  }
103
103
  }
104
104
 
105
- onBeforeRender(): void {
106
- // TODO TMUI @swingingtom this is so we don't have text clipping
107
- if (this.uiObject && (this.Canvas?.screenspace || this.context.isInVR)) {
108
- this.updateOverflow();
109
- }
105
+ // onBeforeRender(): void {
106
+ // // TODO TMUI @swingingtom this is so we don't have text clipping
107
+ // if (this.uiObject && (this.Canvas?.screenspace || this.context.isInVR)) {
108
+ // this.updateOverflow();
109
+ // }
110
+ // }
111
+ onBeforeCanvasRender(_canvas: ICanvas) {
112
+ // ensure the text clipping matrix is updated (this was a problem with multiple screenspace canvases due to canvas reparenting)
113
+ this.updateOverflow();
110
114
  }
111
115
 
112
116
  private updateOverflow() {
@@ -220,6 +224,11 @@ export class Text extends Graphic implements IHasAlphaFactor {
220
224
  }
221
225
 
222
226
  setTimeout(() => this.markDirty(), 10);
227
+ this.canvas?.registerEventReceiver(this);
228
+ }
229
+ onDisable(): void {
230
+ super.onDisable();
231
+ this.canvas?.unregisterEventReceiver(this);
223
232
  }
224
233
 
225
234
  private getAlignment(opts: ThreeMeshUIEveryOptions): ThreeMeshUIEveryOptions {
@@ -46,6 +46,7 @@ export class WebARSessionRoot extends Behaviour {
46
46
  private _isTouching: boolean = false;
47
47
  private _rigStartPose: Matrix4 | undefined | null = null;
48
48
  private _gotFirstHitTestResult: boolean = false;
49
+ private _anchor: XRAnchor | null = null;
49
50
 
50
51
  onBegin(session: XRSession) {
51
52
  this._placementPose = null;
@@ -54,6 +55,7 @@ export class WebARSessionRoot extends Behaviour {
54
55
  this._startPose = this.gameObject.matrix.clone();
55
56
  this._rigStartPose = this.rig?.matrix.clone();
56
57
  this._gotFirstHitTestResult = false;
58
+ this._anchor = null;
57
59
  session.addEventListener('selectstart', this._selectStartFn);
58
60
  session.addEventListener('selectend', this._selectEndFn);
59
61
  // setTimeout(() => this.gameObject.visible = false, 1000); // TODO test on phone AR and Hololens if this was still needed
@@ -73,7 +75,8 @@ export class WebARSessionRoot extends Behaviour {
73
75
  this.dispatchEvent(new CustomEvent('onBeginSession'));
74
76
  }
75
77
 
76
- onUpdate(rig: Object3D | null, _session: XRSession, pose: XRPose | null | undefined): boolean {
78
+ onUpdate(rig: Object3D | null, _session: XRSession, hit: XRHitTestResult | null, pose: XRPose | null | undefined): boolean {
79
+
77
80
 
78
81
  if (pose && !this._placementPose) {
79
82
 
@@ -89,6 +92,8 @@ export class WebARSessionRoot extends Behaviour {
89
92
 
90
93
  if (this.webAR) this.webAR.setReticleActive(false);
91
94
  this.placeAt(rig, poseMatrix);
95
+ if (hit && pose)
96
+ this.onCreatePlacementAnchor(hit, pose);
92
97
  return true;
93
98
  }
94
99
  }
@@ -104,11 +109,34 @@ export class WebARSessionRoot extends Behaviour {
104
109
  // }
105
110
  }
106
111
 
112
+ private async onCreatePlacementAnchor(hit: XRHitTestResult, pose: XRPose) {
113
+ this._anchor = null;
114
+ hit.createAnchor?.call(hit, pose.transform)?.then(anchor => {
115
+ if (this.context.isInAR)
116
+ this._anchor = anchor;
117
+ });
118
+ }
119
+
120
+ private _anchorMatrix: Matrix4 = new Matrix4();
121
+ onBeforeRender(frame: XRFrame | null): void {
122
+ if (frame && this._anchor && this._rig) {
123
+ const referenceSpace = this.context.renderer.xr.getReferenceSpace();
124
+ if (referenceSpace) {
125
+ const pose = frame.getPose(this._anchor.anchorSpace, referenceSpace);
126
+ if (pose) {
127
+ const poseMatrix = this._anchorMatrix.fromArray(pose.transform.matrix).invert();
128
+ this.placeAt(this._rig, poseMatrix);
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ private _invertedSessionRootMatrix: Matrix4 = new Matrix4();
107
135
  placeAt(rig: Object3D | null, mat: Matrix4) {
108
136
  if (!this._placementPose) this._placementPose = new Matrix4();
109
137
  this._placementPose.copy(mat);
110
138
  // apply session root offset
111
- const invertedSessionRoot = this.gameObject.matrixWorld.clone().invert();
139
+ const invertedSessionRoot = this._invertedSessionRootMatrix.copy(this.gameObject.matrixWorld).invert();
112
140
  this._placementPose.premultiply(invertedSessionRoot);
113
141
  if (rig) {
114
142
 
@@ -132,6 +160,7 @@ export class WebARSessionRoot extends Behaviour {
132
160
  this._placementPose = null;
133
161
  this.gameObject.visible = false;
134
162
  this.gameObject.matrixAutoUpdate = false;
163
+ this._anchor = null;
135
164
  if (this._startPose) {
136
165
  this.gameObject.matrix.copy(this._startPose);
137
166
  }
@@ -171,9 +200,10 @@ export class WebARSessionRoot extends Behaviour {
171
200
  }
172
201
  // we apply the transform to the rig because we want to move the user's position for easy networking
173
202
  rig.matrixAutoUpdate = false;
174
- rig.matrix.multiplyMatrices(new Matrix4().makeScale(scale, scale, scale), this._placementPose);
203
+ rig.matrix.multiplyMatrices(tempMatrix.makeScale(scale, scale, scale), this._placementPose);
175
204
  rig.matrix.decompose(rig.position, rig.quaternion, rig.scale);
176
205
  rig.updateMatrixWorld();
177
- console.log("Place", rig.position);
178
206
  }
179
- }
207
+ }
208
+
209
+ const tempMatrix = new Matrix4();
@@ -122,6 +122,7 @@ export class WebXR extends Behaviour {
122
122
  options.domOverlay = { root: domOverlayRoot };
123
123
  options.optionalFeatures.push('dom-overlay')
124
124
  options.optionalFeatures.push('hit-test');
125
+ options.optionalFeatures.push('anchors');
125
126
  }
126
127
  else {
127
128
  console.warn("No dom overlay root found, HTML overlays on top of screen-based AR will not work.");
@@ -712,7 +713,7 @@ export class WebAR {
712
713
  const pose = hit.getPose(referenceSpace);
713
714
 
714
715
  if (this.sessionRoot) {
715
- const didPlace = this.sessionRoot.onUpdate(this.webxr.Rig, session, pose);
716
+ const didPlace = this.sessionRoot.onUpdate(this.webxr.Rig, session, hit, pose);
716
717
  this.didPlaceARSessionRoot = didPlace;
717
718
  }
718
719
 
@@ -730,7 +731,7 @@ export class WebAR {
730
731
  }
731
732
 
732
733
  } else {
733
- this.sessionRoot?.onUpdate(this.webxr.Rig, session, null);
734
+ this.sessionRoot?.onUpdate(this.webxr.Rig, session, null, null);
734
735
  if (this.reticle)
735
736
  this.reticle.visible = false;
736
737
  }