Needle Engine

Changes between version 3.25.0 and 3.25.1
Files changed (6) hide show
  1. src/engine-components/Component.ts +10 -0
  2. src/engine/engine_physics_rapier.ts +18 -12
  3. src/engine/engine_three_utils.ts +4 -1
  4. src/engine/engine_types.ts +9 -0
  5. src/engine-components/js-extensions/Object3D.ts +61 -1
  6. src/engine-components/webxr/WebXRImageTracking.ts +19 -6
src/engine-components/Component.ts CHANGED
@@ -286,6 +286,16 @@
286
286
  abstract getComponentsInChildren<T>(type: Constructor<T>, arr?: T[]): Array<T>;
287
287
  abstract getComponentInParent<T>(type: Constructor<T>): T | null;
288
288
  abstract getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>;
289
+
290
+
291
+ abstract get worldPosition(): Vector3
292
+ abstract set worldPosition(val: Vector3);
293
+ abstract set worldQuaternion(val: Quaternion);
294
+ abstract get worldQuaternion(): Quaternion;
295
+ abstract set worldRotation(val: Vector3);
296
+ abstract get worldRotation(): Vector3;
297
+ abstract set worldScale(val: Vector3);
298
+ abstract get worldScale(): Vector3;
289
299
  }
290
300
 
291
301
 
src/engine/engine_physics_rapier.ts CHANGED
@@ -757,19 +757,25 @@
757
757
  desc.setMass(.000001);
758
758
  }
759
759
 
760
- const col = this.world.createCollider(desc, rigidBody);
761
- col[$componentKey] = collider;
762
- collider[$bodyKey] = col;
763
- col.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
764
- // We want to receive collisitons between two triggers too
765
- col.setActiveCollisionTypes(ActiveCollisionTypes.ALL);
760
+ try {
761
+ const col = this.world.createCollider(desc, rigidBody);
762
+ col[$componentKey] = collider;
763
+ collider[$bodyKey] = col;
764
+ col.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
765
+ // We want to receive collisitons between two triggers too
766
+ col.setActiveCollisionTypes(ActiveCollisionTypes.ALL);
766
767
 
767
- // const objectLayerMask = collider.gameObject.layers.mask;
768
- // const mask = objectLayerMask & ~2;
769
- // col.setCollisionGroups(objectLayerMask);
770
- this.objects.push(collider);
771
- this.bodies.push(col);
772
- return col;
768
+ // const objectLayerMask = collider.gameObject.layers.mask;
769
+ // const mask = objectLayerMask & ~2;
770
+ // col.setCollisionGroups(objectLayerMask);
771
+ this.objects.push(collider);
772
+ this.bodies.push(col);
773
+ return col;
774
+ }
775
+ catch (e) {
776
+ console.error("Error creating collider \"" + collider.name + "\"\nError:", e);
777
+ return null;
778
+ }
773
779
  }
774
780
 
775
781
  private getRigidbody(collider: ICollider, _matrix: Matrix4): { rigidBody: RigidBody, useExplicitMassProperties: boolean } {
src/engine/engine_three_utils.ts CHANGED
@@ -46,6 +46,7 @@
46
46
  }
47
47
 
48
48
  const _worldPositions = new CircularBuffer(() => new Vector3(), 100);
49
+ const _lastMatrixWorldUpdateKey = Symbol("lastMatrixWorldUpdateKey");
49
50
 
50
51
  export function getWorldPosition(obj: Object3D, vec: Vector3 | null = null, updateParents: boolean = true): Vector3 {
51
52
  const wp = vec ?? _worldPositions.get();
@@ -53,8 +54,10 @@
53
54
  if (!obj.parent) return wp.copy(obj.position);
54
55
  if (updateParents)
55
56
  obj.updateWorldMatrix(true, false);
56
- if (obj.matrixWorldNeedsUpdate)
57
+ if (obj.matrixWorldNeedsUpdate && obj[_lastMatrixWorldUpdateKey] !== Date.now()) {
58
+ obj[_lastMatrixWorldUpdateKey] = Date.now();
57
59
  obj.updateMatrixWorld();
60
+ }
58
61
  wp.setFromMatrixPosition(obj.matrixWorld);
59
62
  return wp;
60
63
  }
src/engine/engine_types.ts CHANGED
@@ -122,6 +122,15 @@
122
122
  getComponentsInChildren<T>(type: Constructor<T>, arr?: T[]): Array<T>;
123
123
  getComponentInParent<T>(type: Constructor<T>): T | null;
124
124
  getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>;
125
+
126
+ get worldPosition(): Vector3;
127
+ set worldPosition(val: Vector3);
128
+ get worldQuaternion(): Quaternion;
129
+ set worldQuaternion(val: Quaternion);
130
+ get worldRotation(): Vector3;
131
+ set worldRotation(val: Vector3);
132
+ get worldScale(): Vector3;
133
+ set worldScale(val: Vector3);
125
134
  }
126
135
 
127
136
  export interface IHasGuid {
src/engine-components/js-extensions/Object3D.ts CHANGED
@@ -1,8 +1,19 @@
1
1
  import { applyPrototypeExtensions, registerPrototypeExtensions } from "./ExtensionUtils.js";
2
- import { Object3D } from "three";
2
+ import { Object3D, Quaternion, Vector3 } from "three";
3
3
  import type { Constructor, ConstructorConcrete, IComponent, IComponent as Component } from "../../engine/engine_types.js";
4
4
  import { moveComponentInstance, addNewComponent, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, removeComponent } from "../../engine/engine_components.js";
5
5
  import { isActiveSelf, setActive, destroy } from "../../engine/engine_gameobject.js";
6
+ import {
7
+ setWorldPosition,
8
+ getWorldPosition,
9
+ setWorldQuaternion,
10
+ getWorldQuaternion,
11
+ getWorldScale,
12
+ setWorldScale,
13
+ setWorldRotation,
14
+ getWorldRotation
15
+ }
16
+ from "../../engine/engine_three_utils.js";
6
17
 
7
18
  // used to decorate cloned object3D objects with the same added components defined above
8
19
  export function apply(object: Object3D) {
@@ -63,6 +74,7 @@
63
74
  return getComponentsInParent(this, type, arr);
64
75
  }
65
76
 
77
+
66
78
  // this is a fix to allow gameObject active animation be applied to a three object
67
79
  if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "activeSelf")) {
68
80
  Object.defineProperty(Object3D.prototype, "activeSelf", {
@@ -86,5 +98,53 @@
86
98
 
87
99
 
88
100
 
101
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldPosition")) {
102
+ Object.defineProperty(Object3D.prototype, "worldPosition", {
103
+ get: function () {
104
+ return getWorldPosition(this);
105
+ },
106
+ set: function (val: Vector3) {
107
+ setWorldPosition(this, val)
108
+ }
109
+ });
110
+ }
111
+
112
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldQuaternion")) {
113
+ Object.defineProperty(Object3D.prototype, "worldQuaternion", {
114
+ get: function () {
115
+ return getWorldQuaternion(this);
116
+ },
117
+ set: function (val: Quaternion) {
118
+ setWorldQuaternion(this, val)
119
+ }
120
+ });
121
+ }
122
+
123
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldRotation")) {
124
+ Object.defineProperty(Object3D.prototype, "worldRotation", {
125
+ get: function () {
126
+ return getWorldRotation(this);
127
+ },
128
+ set: function (val: Vector3) {
129
+ setWorldRotation(this, val)
130
+ }
131
+ });
132
+ }
133
+
134
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldScale")) {
135
+ Object.defineProperty(Object3D.prototype, "worldScale", {
136
+ get: function () {
137
+ return getWorldScale(this);
138
+ },
139
+ set: function (val: Vector3) {
140
+ setWorldScale(this, val)
141
+ }
142
+ });
143
+ }
144
+
145
+
146
+
147
+
148
+
89
149
  // do this after adding the component extensions
90
150
  registerPrototypeExtensions(Object3D);
src/engine-components/webxr/WebXRImageTracking.ts CHANGED
@@ -21,6 +21,8 @@
21
21
  get model(): WebXRImageTrackingModel { return this._trackedImage; }
22
22
  readonly measuredSize: number;
23
23
  readonly state: "tracked" | "emulated";
24
+ /** realtime since startup, is used to keep the object visible even if tracking is gone for a few frames */
25
+ lastTrackingTime: number = 0;
24
26
 
25
27
  /** Copy the image position to a vector */
26
28
  getPosition(vec: Vector3) {
@@ -149,9 +151,6 @@
149
151
  if (debug) console.log(this)
150
152
  if (!this.trackedImages) return;
151
153
  for (const trackedImage of this.trackedImages) {
152
- if (trackedImage.object?.asset) {
153
- trackedImage.object.asset.visible = false;
154
- }
155
154
  if (trackedImage.image) {
156
155
  if (WebXRImageTracking._imageElements.has(trackedImage.image)) {
157
156
  }
@@ -168,7 +167,7 @@
168
167
  // TODO better would be to do that once we actually need it
169
168
  const canvas = await imageToCanvas(img);
170
169
  if (canvas) {
171
- const blob = await canvas.convertToBlob({type: 'image/png'});
170
+ const blob = await canvas.convertToBlob({ type: 'image/png' });
172
171
  const arrayBuffer = await blob.arrayBuffer();
173
172
 
174
173
  const exporter = GameObject.findObjectOfType(USDZExporter);
@@ -225,6 +224,14 @@
225
224
  }
226
225
 
227
226
  private onXRStarted = (_: any) => {
227
+ if (this.trackedImages) {
228
+ for (const trackedImage of this.trackedImages) {
229
+ if (trackedImage.object?.asset) {
230
+ const obj = trackedImage.object.asset;
231
+ obj.visible = false;
232
+ }
233
+ }
234
+ }
228
235
  // clear out all frame counters for tracking
229
236
  for (const trackedData of this.imageToObjectMap.values()) {
230
237
  trackedData.frames = 0;
@@ -260,6 +267,8 @@
260
267
  if (trackedImage) {
261
268
  const pose = frame.getPose(result.imageSpace, space);
262
269
  const imageData = new WebXRTrackedImage(this, trackedImage, result.image, result.measuredSize, state, pose);
270
+ if (imageData.state === "tracked")
271
+ imageData.lastTrackingTime = this.context.time.realtimeSinceStartup;
263
272
  this.currentImages.push(imageData);
264
273
  }
265
274
  else {
@@ -284,12 +293,16 @@
284
293
  }
285
294
 
286
295
  // disable any objects that are no longer tracked
296
+ const hysteresis = 1;
287
297
  for (const [model, object] of this.imageToObjectMap) {
288
298
  if (!object.object || !model) continue;
289
299
  let found = false;
290
300
  for (const trackedImage of this.currentImages) {
291
- if (trackedImage.state === "tracked" && trackedImage.model === model) {
292
- found = true;
301
+ if (trackedImage.model === model) {
302
+ // Make sure to keep the object visible if it's marked as static OR is tracked OR was tracked very recently (e.g. low framerate or bad tracking on device)
303
+ const timeSinceLastTracking = this.context.time.realtimeSinceStartup - trackedImage.lastTrackingTime;
304
+ if (model.imageDoesNotMove || trackedImage.state === "tracked" || timeSinceLastTracking < hysteresis)
305
+ found = true;
293
306
  break;
294
307
  }
295
308
  }