Needle Engine

Changes between version 3.25.1 and 3.25.2
Files changed (5) hide show
  1. src/engine-components/Component.ts +8 -3
  2. src/engine/engine_three_utils.ts +7 -0
  3. src/engine/engine_types.ts +5 -1
  4. src/engine-components/js-extensions/Object3D.ts +23 -2
  5. src/engine-components/webxr/WebXRImageTracking.ts +15 -14
src/engine-components/Component.ts CHANGED
@@ -26,9 +26,6 @@
26
26
  // Added to the threejs Object3D prototype
27
27
  abstract destroy();
28
28
 
29
- // The actual implementation / prototype of threejs is modified in js-extensions/Object3D
30
- abstract get transform(): Object3D;
31
-
32
29
  public static isDestroyed(go: Object3D): boolean {
33
30
  return isDestroyed(go);
34
31
  }
@@ -274,6 +271,10 @@
274
271
 
275
272
  // these are implemented via threejs object extensions
276
273
  abstract activeSelf: boolean;
274
+
275
+ // The actual implementation / prototype of threejs is modified in js-extensions/Object3D
276
+ abstract get transform(): GameObject;
277
+
277
278
  /** creates a new component on this gameObject */
278
279
  abstract addNewComponent<T>(type: ConstructorConcrete<T>): T | null;
279
280
  /** adds an existing component to this gameObject */
@@ -296,6 +297,10 @@
296
297
  abstract get worldRotation(): Vector3;
297
298
  abstract set worldScale(val: Vector3);
298
299
  abstract get worldScale(): Vector3;
300
+
301
+ abstract get worldForward(): Vector3;
302
+ abstract get worldRight(): Vector3;
303
+ abstract get worldUp(): Vector3;
299
304
  }
300
305
 
301
306
 
src/engine/engine_three_utils.ts CHANGED
@@ -45,6 +45,13 @@
45
45
  object.lookAt(lookTarget);
46
46
  }
47
47
 
48
+
49
+ const _tempVecs = new CircularBuffer(() => new Vector3(), 100);
50
+ export function getTempVector() {
51
+ return _tempVecs.get();
52
+ }
53
+
54
+
48
55
  const _worldPositions = new CircularBuffer(() => new Vector3(), 100);
49
56
  const _lastMatrixWorldUpdateKey = Symbol("lastMatrixWorldUpdateKey");
50
57
 
src/engine/engine_types.ts CHANGED
@@ -111,7 +111,7 @@
111
111
  * @augments Object3D
112
112
  * @tutorial https://fwd.needle.tools/needle-engine/docs/transform
113
113
  * */
114
- get transform(): Object3D;
114
+ get transform(): IGameObject;
115
115
 
116
116
  addNewComponent<T>(type: Constructor<T>): T | null;
117
117
  removeComponent(comp: IComponent): IComponent;
@@ -131,6 +131,10 @@
131
131
  set worldRotation(val: Vector3);
132
132
  get worldScale(): Vector3;
133
133
  set worldScale(val: Vector3);
134
+
135
+ get worldForward(): Vector3;
136
+ get worldRight(): Vector3;
137
+ get worldUp(): Vector3;
134
138
  }
135
139
 
136
140
  export interface IHasGuid {
src/engine-components/js-extensions/Object3D.ts CHANGED
@@ -11,7 +11,8 @@
11
11
  getWorldScale,
12
12
  setWorldScale,
13
13
  setWorldRotation,
14
- getWorldRotation
14
+ getWorldRotation,
15
+ getTempVector
15
16
  }
16
17
  from "../../engine/engine_three_utils.js";
17
18
 
@@ -142,9 +143,29 @@
142
143
  });
143
144
  }
144
145
 
146
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldForward")) {
147
+ Object.defineProperty(Object3D.prototype, "worldForward", {
148
+ get: function () {
149
+ return getTempVector().set(0, 0, 1).applyQuaternion(getWorldQuaternion(this));
150
+ },
151
+ });
152
+ }
153
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldRight")) {
154
+ Object.defineProperty(Object3D.prototype, "worldRight", {
155
+ get: function () {
156
+ return getTempVector().set(1, 0, 0).applyQuaternion(getWorldQuaternion(this));
157
+ },
158
+ });
159
+ }
160
+ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldUp")) {
161
+ Object.defineProperty(Object3D.prototype, "worldUp", {
162
+ get: function () {
163
+ return getTempVector().set(0, 1, 0).applyQuaternion(getWorldQuaternion(this));
164
+ },
165
+ });
166
+ }
145
167
 
146
168
 
147
169
 
148
-
149
170
  // do this after adding the component extensions
150
171
  registerPrototypeExtensions(Object3D);
src/engine-components/webxr/WebXRImageTracking.ts CHANGED
@@ -21,8 +21,6 @@
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;
26
24
 
27
25
  /** Copy the image position to a vector */
28
26
  getPosition(vec: Vector3) {
@@ -238,7 +236,7 @@
238
236
  }
239
237
  };
240
238
 
241
- private readonly imageToObjectMap = new Map<WebXRImageTrackingModel, { object: GameObject | null, frames: number }>();
239
+ private readonly imageToObjectMap = new Map<WebXRImageTrackingModel, { object: GameObject | null, frames: number, lastTrackingTime:number }>();
242
240
  private readonly currentImages: WebXRTrackedImage[] = [];
243
241
 
244
242
 
@@ -267,8 +265,6 @@
267
265
  if (trackedImage) {
268
266
  const pose = frame.getPose(result.imageSpace, space);
269
267
  const imageData = new WebXRTrackedImage(this, trackedImage, result.image, result.measuredSize, state, pose);
270
- if (imageData.state === "tracked")
271
- imageData.lastTrackingTime = this.context.time.realtimeSinceStartup;
272
268
  this.currentImages.push(imageData);
273
269
  }
274
270
  else {
@@ -293,21 +289,23 @@
293
289
  }
294
290
 
295
291
  // disable any objects that are no longer tracked
296
- const hysteresis = 1;
297
- for (const [model, object] of this.imageToObjectMap) {
298
- if (!object.object || !model) continue;
292
+ /** time in millis */
293
+ const hysteresis = 1000;
294
+ for (const [key, value] of this.imageToObjectMap) {
295
+ if (!value.object || !key) continue;
299
296
  let found = false;
300
297
  for (const trackedImage of this.currentImages) {
301
- if (trackedImage.model === model) {
298
+ if (trackedImage.model === key) {
302
299
  // 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)
300
+ const timeSinceLastTracking = Date.now() - value.lastTrackingTime;
301
+ if (key.imageDoesNotMove || trackedImage.state === "tracked" || timeSinceLastTracking <= hysteresis) {
305
302
  found = true;
306
- break;
303
+ break;
304
+ }
307
305
  }
308
306
  }
309
307
  if (!found) {
310
- GameObject.setActive(object.object, false);
308
+ GameObject.setActive(value.object, false);
311
309
  }
312
310
  }
313
311
  }
@@ -318,12 +316,13 @@
318
316
 
319
317
  for (const image of images) {
320
318
  const model = image.model;
319
+ const isTracked = image.state === "tracked";
321
320
  // don't do anything if we don't have an object to track - can be handled externally through events
322
321
  if (!model.object) continue;
323
322
 
324
323
  let trackedData = this.imageToObjectMap.get(model);
325
324
  if (trackedData === undefined) {
326
- trackedData = { object: null, frames: 0 };
325
+ trackedData = { object: null, frames: 0, lastTrackingTime: Date.now() };
327
326
  this.imageToObjectMap.set(model, trackedData);
328
327
 
329
328
  model.object.loadAssetAsync().then((asset: GameObject | null) => {
@@ -347,6 +346,8 @@
347
346
  }
348
347
  else {
349
348
  trackedData.frames++;
349
+ if(isTracked)
350
+ trackedData.lastTrackingTime = Date.now();
350
351
 
351
352
  // TODO we could do a bit more here: e.g. sample for the first 1s or so of getting pose data
352
353
  // to improve the tracking quality a bit.