Needle Engine

Changes between version 3.36.5-beta and 3.36.6-beta
Files changed (3) hide show
  1. src/engine/api.ts +2 -1
  2. src/engine/engine_camera.ts +3 -2
  3. src/engine-components/webxr/WebARSessionRoot.ts +58 -9
src/engine/api.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  export * from "./engine_addressables.js";
4
4
  export * from "./engine_application.js";
5
5
  export * from "./engine_assetdatabase.js";
6
+ export { getCameraController,setAutoFitEnabled, setCameraController, useForAutoFit } from "./engine_camera.js"
6
7
  export * from "./engine_components.js";
7
8
  export * from "./engine_components_internal.js";
8
9
  export * from "./engine_components_internal.js";
@@ -20,7 +21,7 @@
20
21
  export * from "./engine_hot_reload.js";
21
22
  export * from "./engine_input.js";
22
23
  export { InstancingUtil } from "./engine_instancing.js";
23
- export { hasCommercialLicense,hasIndieLicense, hasProLicense } from "./engine_license.js";
24
+ export { hasCommercialLicense, hasIndieLicense, hasProLicense } from "./engine_license.js";
24
25
  export * from "./engine_lifecycle_api.js";
25
26
  export * from "./engine_math.js";
26
27
  export * from "./engine_networking.js";
src/engine/engine_camera.ts CHANGED
@@ -32,8 +32,9 @@
32
32
  return obj[autofit] !== false;
33
33
  }
34
34
 
35
- /** @internal */
35
+ /**
36
+ * Enable or disable autofitting for the given object
37
+ */
36
38
  export function setAutoFitEnabled(obj: Object3D, enabled: boolean): void {
37
39
  obj[autofit] = enabled;
38
-
39
40
  }
src/engine-components/webxr/WebARSessionRoot.ts CHANGED
@@ -1,14 +1,13 @@
1
- import { AxesHelper, DoubleSide, Matrix4, Mesh, MeshBasicMaterial, Object3D, Plane, Quaternion, Raycaster, RingGeometry, Scene, Vector3 } from "three";
1
+ import { AxesHelper, DoubleSide, Matrix4, Mesh, MeshBasicMaterial, Object3D, Plane, Raycaster, RingGeometry, Scene, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment, showBalloonWarning } from "../../engine/debug/index.js";
4
4
  import { AssetReference } from "../../engine/engine_addressables.js";
5
5
  import { Context } from "../../engine/engine_context.js";
6
- import { ObjectUtils, PrimitiveType } from "../../engine/engine_create_objects.js";
7
6
  import { destroy, instantiate } from "../../engine/engine_gameobject.js";
8
7
  import { InputEventQueue, NEPointerEvent } from "../../engine/engine_input.js";
9
8
  import { serializable } from "../../engine/engine_serialization_decorator.js";
10
9
  import type { IComponent, IGameObject } from "../../engine/engine_types.js";
11
- import { getParam, isAndroidDevice, isMobileDevice } from "../../engine/engine_utils.js";
10
+ import { getParam, isAndroidDevice } from "../../engine/engine_utils.js";
12
11
  import { NeedleXRController, type NeedleXREventArgs, type NeedleXRHitTestResult, NeedleXRSession } from "../../engine/engine_xr.js";
13
12
  import { Behaviour, GameObject } from "../Component.js";
14
13
 
@@ -190,6 +189,7 @@
190
189
 
191
190
  }
192
191
  else {
192
+ // Update anchors, if any
193
193
  if (this._anchor && args.xr.referenceSpace) {
194
194
  const pose = args.xr.frame.getPose(this._anchor.anchorSpace, args.xr.referenceSpace);
195
195
  if (pose && this.context.time.frame % 20 === 0) {
@@ -204,7 +204,7 @@
204
204
  }
205
205
  }
206
206
 
207
- // scene has been placed
207
+ // Scene has been placed
208
208
  if (this.arTouchTransform) {
209
209
  if (!this.userInput) this.userInput = new WebXRSessionRootUserInput(this.context);
210
210
  this.userInput?.enable();
@@ -255,9 +255,22 @@
255
255
  reticle.visible = false;
256
256
  }
257
257
 
258
- reticle.position.lerp(hit.position, this.context.time.deltaTime / .1);
259
- reticle.quaternion.slerp(hit.quaternion, this.context.time.deltaTime / .05);
258
+ reticle["lastPos"] = reticle["lastPos"] || hit.position.clone();
259
+ reticle["lastQuat"] = reticle["lastQuat"] || hit.quaternion.clone();
260
+ // reticle["targetPos"] = reticle["targetPos"] || hit.position.clone();
261
+ // reticle["targetQuat"] = reticle["targetQuat"] || hit.quaternion.clone();
262
+
263
+ // TODO we likely want the reticle itself to be placed _exactly_ and then the visuals being lerped,
264
+ // Right now this leads to a "rotation glitch" when going from a horizontal to a vertical surface
265
+ reticle.position.copy(reticle["lastPos"].lerp(hit.position, this.context.time.deltaTime / .1));
266
+ reticle["lastPos"].copy(reticle.position);
267
+ reticle.quaternion.copy(reticle["lastQuat"].slerp(hit.quaternion, this.context.time.deltaTime / .05));
268
+ reticle["lastQuat"].copy(reticle.quaternion);
269
+
270
+ // TODO make sure original reticle asset scale is respected, or document it should be uniformly scaled
271
+ // scale *= this.customReticle?.asset?.scale?.x || 1;
260
272
  reticle.scale.set(scale, scale, scale);
273
+
261
274
  // if (this.invertForward) {
262
275
  // reticle.rotateY(Math.PI);
263
276
  // }
@@ -317,6 +330,10 @@
317
330
 
318
331
  this.onRevertSceneChanges();
319
332
 
333
+ // TODO: we should probably use the non-lerped position and quaternion here
334
+ reticle.position.copy(reticle["lastPos"]);
335
+ reticle.quaternion.copy(reticle["lastQuat"]);
336
+
320
337
  this.onApplyPose(reticle);
321
338
 
322
339
  if (this.useXRAnchor) {
@@ -362,7 +379,12 @@
362
379
  }
363
380
  }
364
381
 
382
+ private upVec: Vector3 = new Vector3(0, 1, 0);
383
+ private lookPoint: Vector3 = new Vector3();
384
+ private worldUpVec: Vector3 = new Vector3(0, 1, 0);
385
+
365
386
  private applyViewBasedTransform(reticle: Object3D) {
387
+
366
388
  // Make reticle face the user to unify the placement experience across devices.
367
389
  // The pose that we're receiving from the hit test varies between devices:
368
390
  // - Quest: currently aligned to the mesh that was hit (depends on room setup), has changed a couple times
@@ -372,10 +394,37 @@
372
394
  const reticleGo = reticle as GameObject;
373
395
  const camWP = camGo.worldPosition;
374
396
  const reticleWp = reticleGo.worldPosition;
375
- // const distance = camWP.distanceTo(reticleWp);
376
- camWP.y = reticleWp.y;
377
- reticle.lookAt(camWP);
397
+
398
+ this.upVec.set(0,1,0).applyQuaternion(reticle.quaternion);
378
399
 
400
+ // upVec may be pointing AWAY from us, we have to flip it if that's the case
401
+ const camPos = camGo.worldPosition;
402
+ if (camPos) {
403
+ const camToReticle = reticle.position.clone().sub(camPos);
404
+ const angle = camToReticle.angleTo(this.upVec);
405
+ if (angle < Math.PI / 2) {
406
+ this.upVec.negate();
407
+ }
408
+ }
409
+
410
+ const upAngle = this.upVec.angleTo(this.worldUpVec) * 180 / Math.PI;
411
+ // For debugging look angle for AR placement
412
+ // Gizmos.DrawDirection(reticle.position, upVec, "blue", 0.1);
413
+ // Gizmos.DrawLabel(reticle.position, upAngle.toFixed(2), 0.1);
414
+
415
+ const angleForWallPlacement = 30;
416
+ if ((upAngle > angleForWallPlacement && upAngle < 180 - angleForWallPlacement) ||
417
+ (upAngle < -angleForWallPlacement && upAngle > -180 + angleForWallPlacement)) {
418
+
419
+ this.lookPoint.copy(reticle.position).add(this.upVec);
420
+ this.lookPoint.y = reticle.position.y;
421
+ reticle.lookAt(this.lookPoint);
422
+ }
423
+ else {
424
+ camWP.y = reticleWp.y;
425
+ reticle.lookAt(camWP);
426
+ }
427
+
379
428
  // TODO: ability to scale the reticle so that we can fit the scene depending on the view angle or distance to the reticle.
380
429
  // Currently, doing this leads to wrong placement of the scene.
381
430
  /*