Needle Engine

Changes between version 3.26.0-beta and 3.26.1-beta
Files changed (3) hide show
  1. src/engine-components/ui/Button.ts +1 -1
  2. src/engine-components/ui/EventSystem.ts +33 -14
  3. src/engine-components/ui/PointerEvents.ts +15 -1
src/engine-components/ui/Button.ts CHANGED
@@ -118,7 +118,7 @@
118
118
  }
119
119
 
120
120
  onPointerClick(args: PointerEventData) {
121
- if (!this.interactable || args.pointerId === undefined) return;
121
+ if (!this.interactable || args.pointerId !== 0) return;
122
122
 
123
123
  // Button clicks should only run with left mouse button while using mouse
124
124
  if(args.pointerId !== 0 && this.context.input.getIsMouse(args.pointerId)) return;
src/engine-components/ui/EventSystem.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  import { $shadowDomOwner } from "./BaseUIComponent.js";
15
15
  import { isDevEnvironment, showBalloonMessage } from "../../engine/debug/index.js";
16
16
  import { Mathf } from "../../engine/engine_math.js";
17
+ import { isUIObject } from "./Utils.js";
17
18
 
18
19
  const debug = getParam("debugeventsystem");
19
20
 
@@ -222,22 +223,22 @@
222
223
 
223
224
  // On mouse input has to be always 0 regardless of the button user pressed
224
225
  // because otherwise it would be taken as 3 unique pointers and create OnEnter and OnExit events which is not expected
225
- const pointerId = pointerEvent.pointerType == PointerType.Touch ? pointerEvent.button : 0;
226
+ const id = pointerEvent.pointerType == PointerType.Touch ? pointerEvent.button : 0;
226
227
  const data = new PointerEventData(this.context.input, pointerEvent);
227
228
 
228
229
  data.inputSource = this.context.input;
229
- data.pointerId = pointerId;
230
- data.isClicked = pointerEvent.type == InputEvents.PointerUp && this.context.input.getPointerClicked(pointerId)
230
+ data.pointerId = pointerEvent.button;
231
+ data.isClicked = pointerEvent.type == InputEvents.PointerUp && this.context.input.getPointerClicked(pointerEvent.button)
231
232
  // using the input type directly instead of input API -> otherwise onMove events can sometimes be getPointerUp() == true
232
233
  data.isDown = pointerEvent.type == InputEvents.PointerDown;
233
234
  data.isUp = pointerEvent.type == InputEvents.PointerUp;
234
- data.isPressed = this.context.input.getPointerPressed(pointerId);
235
+ data.isPressed = this.context.input.getPointerPressed(pointerEvent.button);
235
236
 
236
- if (debug && data.isClicked) console.log("CLICK", pointerId);
237
+ if (debug && data.isClicked) console.log("CLICK", data.pointerId);
237
238
 
238
239
  // raycast
239
240
  const options = new RaycastOptions();
240
- options.screenPoint = this.context.input.getPointerPositionRC(pointerId)!;
241
+ options.screenPoint = this.context.input.getPointerPositionRC(id)!;
241
242
 
242
243
  const hits = this.performRaycast(options);
243
244
  if (!hits) return;
@@ -259,17 +260,17 @@
259
260
  // pointer has not hit any object to handle
260
261
 
261
262
  // thus is not hovering over anything
262
- const hoveredData = this.hoveredByID.get(pointerId);
263
+ const hoveredData = this.hoveredByID.get(id);
263
264
  if (hoveredData) {
264
265
  this.triggerOnExit(hoveredData.obj, hoveredData.data);
265
266
  }
266
- this.hoveredByID.delete(pointerId);
267
+ this.hoveredByID.delete(id);
267
268
 
268
269
  // if it was up, it means it doesn't should notify things that it down on before
269
270
  if (data.isUp) {
270
- const pressedData = this.pressedByID.get(pointerId);
271
+ const pressedData = this.pressedByID.get(id);
271
272
  pressedData?.handlers.forEach(h => h.onPointerUp?.call(h, data));
272
- this.pressedByID.delete(pointerId);
273
+ this.pressedByID.delete(id);
273
274
  }
274
275
  }
275
276
 
@@ -293,19 +294,32 @@
293
294
  * For example if an event component has only an onPointerClick method we don't need to raycast during movement events
294
295
  * */
295
296
  private shouldRaycastObject = (obj: Object3D): RaycastTestObjectReturnType => {
297
+
298
+ // check if this object is actually a UI shadow hierarchy object
299
+ let shadowComponent: Object3D | null = null;
300
+ const isUI = isUIObject(obj);
301
+ // if yes we want to grab the actual object that is the owner of the shadow dom
302
+ // and check that object for the event component
303
+ if (isUI) {
304
+ shadowComponent = obj[$shadowDomOwner]?.gameObject;
305
+ }
306
+
296
307
  // check if the object was seen previously
297
- if (this._testObjectsCache.has(obj)) {
308
+ if (this._testObjectsCache.has(obj) || (shadowComponent && this._testObjectsCache.has(shadowComponent))) {
298
309
  // if yes we check if it was previously stored as "YES WE NEED TO RAYCAST THIS"
299
310
  const prev = this._testObjectsCache.get(obj)!;
300
- if (!prev) return "continue in children"
311
+ if (prev === false) return "continue in children"
301
312
  return true;
302
313
  }
303
314
  else {
304
315
  // the object was not yet seen so we test if it has an event component
305
- const hasEventComponent = hasPointerEventComponent(obj);
316
+ let hasEventComponent = hasPointerEventComponent(obj);
317
+ if (!hasEventComponent && shadowComponent) hasEventComponent = hasPointerEventComponent(shadowComponent);
318
+
306
319
  if (hasEventComponent) {
307
- // console.log("YES RAYCAST", obj.name)
308
320
  // it has an event component: we add it and all its children to the cache
321
+ // we don't need to do the same for the shadow component hierarchy
322
+ // because the next object that will be detecting that the shadow owner was already seen
309
323
  this._testObjectsCache.set(obj, true);
310
324
  obj.traverse((o) => {
311
325
  this._testObjectsCache.set(o, true);
@@ -353,6 +367,11 @@
353
367
  hits = this.sortCandidates(hits);
354
368
  for (const hit of hits) {
355
369
  const { object } = hit;
370
+ args.point = hit.point;
371
+ args.normal = hit.normal;
372
+ args.face = hit.face;
373
+ args.distance = hit.distance;
374
+ args.instanceId = hit.instanceId;
356
375
  if (this.handleEventOnObject(object, args)) {
357
376
  return true;
358
377
  }
src/engine-components/ui/PointerEvents.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GameObject } from "../Component.js";
2
2
  import { Input, NEPointerEvent } from "../../engine/engine_input.js";
3
- import { Object3D } from "three";
3
+ import { Face, Object3D, Vector3 } from "three";
4
4
 
5
5
  export interface IInputEventArgs {
6
6
  get used(): boolean;
@@ -33,8 +33,18 @@
33
33
  this._event?.stopImmediatePropagation();
34
34
  }
35
35
 
36
+ /** Who initiated this event */
36
37
  inputSource: Input | any;
38
+
39
+ /** The object this event hit or interacted with */
37
40
  object!: THREE.Object3D;
41
+ /** The world position of this event */
42
+ point?: Vector3;
43
+ /** The world normal of this event */
44
+ normal?: Vector3;
45
+ face?: Face | null;
46
+ distance?: number;
47
+ instanceId?: number;
38
48
 
39
49
  pointerId: number | undefined;
40
50
  isDown: boolean | undefined;
@@ -42,6 +52,9 @@
42
52
  isPressed: boolean | undefined;
43
53
  isClicked: boolean | undefined;
44
54
 
55
+ /** mouse button 0 === LEFT, 1 === MIDDLE, 2 === RIGHT */
56
+ readonly button: number | string;
57
+
45
58
  private input: Input;
46
59
 
47
60
  private _event?: NEPointerEvent;
@@ -50,6 +63,7 @@
50
63
  constructor(input: Input, event?: NEPointerEvent) {
51
64
  this._event = event;
52
65
  this.input = input;
66
+ this.button = event?.button ?? 0;
53
67
  }
54
68
 
55
69
  clone() {