Needle Engine

Changes between version 3.44.4 and 3.44.5
Files changed (7) hide show
  1. src/engine/engine_input.ts +16 -2
  2. src/engine-components/ui/EventSystem.ts +8 -0
  3. src/engine/export/gltf/index.ts +1 -1
  4. src/needle-engine.ts +2 -1
  5. src/engine/xr/NeedleXRController.ts +7 -5
  6. src/engine-components/webxr/WebARSessionRoot.ts +1 -1
  7. src/engine-components/webxr/controllers/XRControllerMovement.ts +40 -25
src/engine/engine_input.ts CHANGED
@@ -55,6 +55,19 @@
55
55
  buttonName: ButtonName | "none";
56
56
  }
57
57
 
58
+ declare type OnPointerHitsEvent = (args: OnPointerHitEvent) => void;
59
+ declare type OnPointerHitEvent = {
60
+ /** The object that raised the event */
61
+ sender: object;
62
+ /** The pointer event that invoked the event */
63
+ event: NEPointerEvent;
64
+ /** The intersections that were generated from this event (or are associated with this event in any way) */
65
+ hits: Intersection[];
66
+ }
67
+ export interface IPointerHitEventReceiver {
68
+ onPointerHits: OnPointerHitsEvent;
69
+ }
70
+
58
71
  /** An intersection that is potentially associated with a pointer event */
59
72
  export declare type NEPointerEventIntersection = Intersection & { event?: NEPointerEvent };
60
73
 
@@ -64,9 +77,10 @@
64
77
  readonly deviceIndex: number;
65
78
 
66
79
  /** The origin of the event contains a reference to the creator of this event.
67
- * This can be the Needle Engine input system or e.g. a XR controller
80
+ * This can be the Needle Engine input system or e.g. a XR controller.
81
+ * Implement `onPointerHits` to receive the intersections of this event.
68
82
  */
69
- readonly origin: object;
83
+ readonly origin: object & Partial<IPointerHitEventReceiver>;
70
84
 
71
85
  /** the browser event that triggered this event (if any) */
72
86
  readonly source: Event | null;
src/engine-components/ui/EventSystem.ts CHANGED
@@ -187,8 +187,16 @@
187
187
  hit.event = pointerEvent;
188
188
  pointerEvent.intersections.push(hit);
189
189
  }
190
+ if (pointerEvent.origin.onPointerHits) {
191
+ pointerEvent.origin.onPointerHits({
192
+ sender: this,
193
+ event: pointerEvent,
194
+ hits
195
+ });
196
+ }
190
197
  }
191
198
 
199
+
192
200
  if (debug && data.isClick) {
193
201
  showBalloonMessage("EventSystem: " + data.pointerId + " - " + this.context.time.frame + " - Up:" + data.isUp + ", Down:" + data.isDown)
194
202
  }
src/engine/export/gltf/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AnimationClip, Object3D } from "three";
2
- import { GLTFExporter, GLTFExporterOptions } from "three/examples/jsm/exporters/GLTFExporter";
2
+ import { GLTFExporter, GLTFExporterOptions } from "three/examples/jsm/exporters/GLTFExporter.js";
3
3
 
4
4
  import GLTFMeshGPUInstancingExtension from "../../../include/three/EXT_mesh_gpu_instancing_exporter.js";
5
5
  import { AnimationUtils } from "../../engine_animation.js";
src/needle-engine.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  export * from "./engine-schemes/api.js";
8
8
 
9
9
  // make accessible for external javascript
10
- import { Context, loadSync } from "./engine/api.js";
10
+ import { Context, loadSync, NeedleXRSession } from "./engine/api.js";
11
11
  const Needle = {
12
12
  Context: Context,
13
13
  glTF: {
@@ -29,6 +29,7 @@
29
29
  import * as Components from "./engine-components/codegen/components.js";
30
30
  registerGlobal(Components);
31
31
 
32
+ Needle["NeedleXRSession"] = NeedleXRSession;
32
33
 
33
34
  import { GameObject } from "./engine-components/Component.js";
34
35
  for (const method of Object.getOwnPropertyNames(GameObject)) {
src/engine/xr/NeedleXRController.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import { RGBAColor } from "../../engine-components/js-extensions/RGBAColor.js";
5
5
  import { Context } from "../engine_context.js";
6
6
  import { Gizmos } from "../engine_gizmos.js";
7
- import { type InputEventNames, InputEvents, NEPointerEvent, type NEPointerEventInit } from "../engine_input.js";
7
+ import { type InputEventNames, InputEvents, IPointerHitEventReceiver, NEPointerEvent, type NEPointerEventInit } from "../engine_input.js";
8
8
  import { getTempQuaternion, getTempVector, getWorldQuaternion } from "../engine_three_utils.js";
9
9
  import type { ButtonName, IGameObject, Vec3, XRControllerButtonName, XRGestureName } from "../engine_types.js";
10
10
  import { getParam } from "../engine_utils.js";
@@ -70,7 +70,7 @@
70
70
  * Inputs will also be emitted as pointer events on `this.context.input` - so you can receive controller inputs on objects using the appropriate input events on your components (e.g. `onPointerDown`, `onPointerUp` etc) - use the `pointerType` property to check if the event is from a controller or not
71
71
  * @link https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource
72
72
  */
73
- export class NeedleXRController {
73
+ export class NeedleXRController implements IPointerHitEventReceiver {
74
74
  /** the Needle XR Session */
75
75
  readonly xr: NeedleXRSession;
76
76
  get context() { return this.xr.context; }
@@ -274,6 +274,9 @@
274
274
  }
275
275
  }
276
276
 
277
+ onPointerHits = _evt => {
278
+ }
279
+
277
280
  onUpdate(frame: XRFrame) {
278
281
  this.onUpdateFrame(frame);
279
282
  this.updateInputEvents();
@@ -737,12 +740,11 @@
737
740
  private onUpdateMove() {
738
741
  let didMove = false;
739
742
  const dist = this._lastPointerMovePosition.distanceTo(this.gripWorldPosition);
740
- if (dist > .02) didMove = true;
743
+ if (dist > .05 * this.xr.rigScale) didMove = true;
741
744
  if (!didMove) {
742
745
  const angle = this._lastPointerMoveQuaternion.angleTo(this.gripWorldQuaternion);
743
- if (angle > .02) didMove = true;
746
+ if (angle > .01) didMove = true;
744
747
  }
745
-
746
748
  if (didMove) {
747
749
  this._didMoveLastFrame = true;
748
750
  this._lastPointerMovePosition.copy(this.gripWorldPosition);
src/engine-components/webxr/WebARSessionRoot.ts CHANGED
@@ -278,7 +278,7 @@
278
278
  else {
279
279
  reticle = new Mesh(
280
280
  new RingGeometry(0.07, 0.09, 32).rotateX(- Math.PI / 2),
281
- new MeshBasicMaterial({ side: DoubleSide })
281
+ new MeshBasicMaterial({ side: DoubleSide, depthTest: false, depthWrite: false, transparent: true, opacity: 1, color: 0xffffff })
282
282
  ) as any as IGameObject;
283
283
  reticle.name = "AR Placement Reticle";
284
284
  }
src/engine-components/webxr/controllers/XRControllerMovement.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AdditiveBlending, BufferAttribute, Color, DoubleSide, Line, Line3, Mesh, MeshBasicMaterial, Object3D, RingGeometry, SubtractiveBlending, Vector3 } from "three";
1
+ import { AdditiveBlending, BufferAttribute, Color, DoubleSide, Intersection, Line, Line3, Mesh, MeshBasicMaterial, Object3D, RingGeometry, SphereGeometry, SubtractiveBlending, Vector3 } from "three";
2
2
  import { Line2 } from "three/examples/jsm/lines/Line2.js";
3
3
  import { LineGeometry } from "three/examples/jsm/lines/LineGeometry.js";
4
4
  import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
@@ -23,10 +23,10 @@
23
23
  export class XRControllerMovement extends Behaviour implements XRMovementBehaviour {
24
24
 
25
25
  /** Movement speed in meters per second
26
- * @default 1
26
+ * @default 1.5
27
27
  */
28
28
  @serializable()
29
- movementSpeed = 1;
29
+ movementSpeed = 1.5;
30
30
 
31
31
  /** How many degrees to rotate the XR rig when using the rotation trigger
32
32
  * @default 60
@@ -186,7 +186,7 @@
186
186
  for (let i = 0; i < session.controllers.length; i++) {
187
187
  const ctrl = session.controllers[i];
188
188
  let line = this._lines[i];
189
- if (!ctrl.connected || !ctrl.isTracking ||
189
+ if (!ctrl.connected || !ctrl.isTracking ||
190
190
  !ctrl.ray || ctrl.targetRayMode === "transient-pointer" ||
191
191
  !ctrl.hasSelectEvent
192
192
  ) {
@@ -218,17 +218,26 @@
218
218
 
219
219
  protected renderHits(session: NeedleXRSession) {
220
220
  for (const disc of this._hitDiscs) {
221
- if (disc) disc.visible = false;
221
+ if (!disc) continue;
222
+ const ctrl = disc["controller"];
223
+ if (!ctrl || !ctrl.connected || !ctrl.isTracking) {
224
+ disc.visible = false;
225
+ continue;
226
+ }
222
227
  }
223
228
  for (let i = 0; i < session.controllers.length; i++) {
224
229
  const ctrl = session.controllers[i];
225
230
  if (!ctrl.connected || !ctrl.isTracking || !ctrl.ray || !ctrl.hasSelectEvent) continue;
226
231
 
227
232
  // save performance by only raycasting every nth frame
228
- if (this.context.time.frame % 2 !== 0) {
233
+ const interval = this.context.time.smoothedFps >= 59 ? 1 : 10;
234
+ if ((this.context.time.frame + ctrl.index) % interval !== 0) {
229
235
  const disc = this._hitDiscs[i];
230
236
  // if the disc had a hit last frame, we can show it again
231
- if (disc && disc["hit"]) disc.visible = true;
237
+ if (disc && disc["hit"]) {
238
+ disc.visible = true;
239
+ this.hitPointerSetPosition(ctrl, disc, disc["hit"].distance);
240
+ }
232
241
  continue;
233
242
  }
234
243
 
@@ -237,7 +246,10 @@
237
246
 
238
247
  let disc = this._hitDiscs[i];
239
248
  if (disc) // save the hit object on the disc
249
+ {
250
+ disc["controller"] = ctrl;
240
251
  disc["hit"] = hit;
252
+ }
241
253
 
242
254
  if (hit) {
243
255
  const rigScale = (session.rigScale ?? 1);
@@ -258,14 +270,12 @@
258
270
  disc["hit"] = hit;
259
271
 
260
272
  if (hit.normal) {
261
- const factor = 0.02 * rigScale;
262
- disc.position.set(0, 0, -.1 * factor).applyQuaternion(ctrl.rayWorldQuaternion);
263
- disc.position.add(hit.point);
273
+ this.hitPointerSetPosition(ctrl, disc, hit.distance);
264
274
  const worldNormal = hit.normal.applyQuaternion(getWorldQuaternion(hit.object));
265
275
  disc.quaternion.setFromUnitVectors(up, worldNormal);
266
276
  }
267
277
  else {
268
- disc.position.add(hit.point);
278
+ this.hitPointerSetPosition(ctrl, disc, hit.distance);
269
279
  }
270
280
 
271
281
  if (disc.parent !== this.context.scene) {
@@ -279,6 +289,10 @@
279
289
  }
280
290
  }
281
291
  }
292
+ private hitPointerSetPosition(ctrl: NeedleXRController, disc: Object3D, distance: number) {
293
+ disc.position.copy(ctrl.rayWorldPosition)
294
+ disc.position.add(getTempVector(0, 0, distance - .01).applyQuaternion(ctrl.rayWorldQuaternion));
295
+ }
282
296
 
283
297
  protected hitPointRaycastFilter: RaycastTestObjectCallback = (obj: Object3D) => {
284
298
  // by default dont raycast ont skinned meshes using the hit point raycast (because it is a big performance hit and only a visual indicator)
@@ -290,11 +304,12 @@
290
304
  protected createHitPointObject(): Object3D {
291
305
  var container = new Object3D();
292
306
  const disc = new Mesh(
293
- new RingGeometry(.3, 0.5, 32).rotateX(- Math.PI / 2),
307
+ new SphereGeometry(.3, 6, 6),// new RingGeometry(.3, 0.5, 32).rotateX(- Math.PI / 2),
294
308
  new MeshBasicMaterial({
295
309
  color: 0xeeeeee,
296
310
  opacity: .7,
297
311
  transparent: true,
312
+ depthTest: false,
298
313
  side: DoubleSide,
299
314
  })
300
315
  );
@@ -302,19 +317,19 @@
302
317
  disc.layers.enable(2);
303
318
  container.add(disc);
304
319
 
305
- const disc2 = new Mesh(
306
- new RingGeometry(.43, 0.5, 32).rotateX(- Math.PI / 2),
307
- new MeshBasicMaterial({
308
- color: 0x000000,
309
- opacity: .2,
310
- transparent: true,
311
- side: DoubleSide,
312
- })
313
- );
314
- disc2.layers.disableAll();
315
- disc2.layers.enable(2);
316
- disc2.position.y = .01;
317
- container.add(disc2);
320
+ // const disc2 = new Mesh(
321
+ // new RingGeometry(.43, 0.5, 32).rotateX(- Math.PI / 2),
322
+ // new MeshBasicMaterial({
323
+ // color: 0x000000,
324
+ // opacity: .2,
325
+ // transparent: true,
326
+ // side: DoubleSide,
327
+ // })
328
+ // );
329
+ // disc2.layers.disableAll();
330
+ // disc2.layers.enable(2);
331
+ // disc2.position.y = .01;
332
+ // container.add(disc2);
318
333
  return container;
319
334
  }