Needle Engine

Changes between version 3.39.0-alpha.3 and 3.39.0-alpha.4
Files changed (6) hide show
  1. src/engine-components/ContactShadows.ts +2 -0
  2. src/engine/engine_utils_screenshot.ts +4 -1
  3. src/engine-components/ui/InputField.ts +13 -4
  4. src/engine-components/OrbitControls.ts +23 -7
  5. src/engine-components/ShadowCatcher.ts +3 -0
  6. src/engine-components/webxr/WebARSessionRoot.ts +23 -1
src/engine-components/ContactShadows.ts CHANGED
@@ -182,6 +182,7 @@
182
182
  });
183
183
  this.plane = new Mesh(planeGeometry, planeMaterial);
184
184
  this.plane.scale.y = - 1;
185
+ this.plane.layers.set(2);
185
186
  this.gameObject.add(this.plane);
186
187
 
187
188
  if (this.plane) this.plane.renderOrder = 1;
@@ -195,6 +196,7 @@
195
196
  // .rotateX(Math.PI)
196
197
  .translateY(-0.0001);
197
198
  this.occluderMesh.renderOrder = -100;
199
+ this.occluderMesh.layers.set(2);
198
200
  this.gameObject.add(this.occluderMesh);
199
201
 
200
202
  // the plane onto which to blur the texture
src/engine/engine_utils_screenshot.ts CHANGED
@@ -26,7 +26,10 @@
26
26
  }
27
27
 
28
28
 
29
- declare type ScreenshotOptions = {
29
+ /**
30
+ * Options for the {@link screenshot2} function.
31
+ */
32
+ export declare type ScreenshotOptions = {
30
33
  /**
31
34
  * The context to take the screenshot from. If not provided, the current context will be used.
32
35
  */
src/engine-components/ui/InputField.ts CHANGED
@@ -35,6 +35,7 @@
35
35
  private static active: InputField | null = null;
36
36
  private static activeTime: number = -1;
37
37
  private static htmlField: HTMLInputElement | null = null;
38
+ private static htmlFieldFocused: boolean = false;
38
39
 
39
40
  private inputEventFn: any;
40
41
  private _iosEventFn: any;
@@ -56,6 +57,8 @@
56
57
  InputField.htmlField.style.caretColor = "transparent";
57
58
  InputField.htmlField.style.outline = "none";
58
59
  InputField.htmlField.classList.add("ar");
60
+ InputField.htmlField.onfocus = () => InputField.htmlFieldFocused = true;
61
+ InputField.htmlField.onblur = () => InputField.htmlFieldFocused = false;
59
62
  // TODO: instead of this we should add it to the shadowdom?
60
63
  document.body.append(InputField.htmlField);
61
64
  }
@@ -70,7 +73,7 @@
70
73
  }
71
74
  if (isiOS()) {
72
75
  this._iosEventFn = this.processInputOniOS.bind(this);
73
- window.addEventListener("touchend", this._iosEventFn);
76
+ window.addEventListener("click", this._iosEventFn);
74
77
  }
75
78
  }
76
79
 
@@ -80,7 +83,7 @@
80
83
  // InputField.htmlField?.removeEventListener("change", this.inputEventFn);
81
84
  this.onDeselected();
82
85
  if (this._iosEventFn) {
83
- window.removeEventListener("touchend", this._iosEventFn);
86
+ window.removeEventListener("click", this._iosEventFn);
84
87
  }
85
88
  }
86
89
 
@@ -118,7 +121,7 @@
118
121
  private *activeLoop() {
119
122
  this.onSelected();
120
123
  while (InputField.active === this) {
121
- if (this.context.input.getPointerUp(0)) {
124
+ if (this.context.input.getPointerClicked(0)) {
122
125
  if (this.context.time.time - InputField.activeTime > 0.2) {
123
126
  break;
124
127
  }
@@ -227,8 +230,14 @@
227
230
 
228
231
  private selectInputField() {
229
232
  if (InputField.htmlField) {
233
+ if (debug) console.log("Focus Inputfield", InputField.htmlFieldFocused)
230
234
  InputField.htmlField.setSelectionRange(InputField.htmlField.value.length, InputField.htmlField.value.length);
231
- InputField.htmlField.focus();
235
+ if (isiOS())
236
+ InputField.htmlField.focus({ preventScroll: true });
237
+ else {
238
+ // on Andoid if we don't focus in a timeout the keyboard will close the second time we click the InputField
239
+ setTimeout(() => InputField.htmlField?.focus(), 1);
240
+ }
232
241
  }
233
242
  }
234
243
 
src/engine-components/OrbitControls.ts CHANGED
@@ -363,7 +363,9 @@
363
363
  if (this.clickBackgroundToFitScene <= 1 || dt < this.clickBackgroundToFitScene * .15) {
364
364
  this._clickOnBackgroundCount += 1;
365
365
  if (this._clickOnBackgroundCount >= this.clickBackgroundToFitScene - 1)
366
- this.fitCamera(this.context.scene.children, undefined, false);
366
+ this.fitCamera(this.context.scene.children, {
367
+ immediate: false
368
+ });
367
369
  }
368
370
  else {
369
371
  this._clickOnBackgroundCount = 0;
@@ -677,19 +679,31 @@
677
679
  // Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
678
680
  /** Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
679
681
  * @param objects The objects to fit the camera to - defaults to the scene if not provided
680
- * @param fitOffset A factor to multiply the distance to the objects by. Default is 1.1
681
- * @param immediate If true the camera will move immediately to the new position, otherwise it will lerp
682
+ * @param options Options for fitting the camera
683
+ * @param options.fitOffset A factor to multiply the distance to the objects by. Default is 1.1
684
+ * @param options.immediate If true the camera will move immediately to the new position, otherwise it will lerp
685
+ * @param options.centerCamera If true the camera will move to a point in the center of the objects in the y axis
682
686
  */
683
- fitCamera(objects?: Array<Object3D>, fitOffset: undefined | number = undefined, immediate: boolean = true) {
687
+ fitCamera(objects?: Array<Object3D>, options: {
688
+ fitOffset?: number,
689
+ immediate?: boolean,
690
+ centerCamera?: "none" | "y"
691
+ } = {
692
+ fitOffset: undefined,
693
+ immediate: true
694
+ }) {
684
695
 
685
696
  if (this.context.isInXR) {
686
697
  // camera fitting in XR is not supported
687
698
  return;
688
699
  }
689
700
 
701
+ let { fitOffset, immediate, centerCamera } = options;
702
+
690
703
  if (fitOffset == undefined) {
691
704
  fitOffset = 1.1;
692
705
  }
706
+
693
707
  const camera = this._cameraObject as PerspectiveCamera;
694
708
  const controls = this._controls as ThreeOrbitControls | null;
695
709
  if (!objects?.length) objects = this.context.scene.children;
@@ -760,10 +774,12 @@
760
774
  const cameraWp = getWorldPosition(camera);
761
775
  const direction = center.clone();
762
776
  direction.sub(cameraWp);
763
- direction.y = 0;
777
+ if (centerCamera === "y")
778
+ direction.y = 0;
764
779
  direction.normalize();
765
780
  direction.multiplyScalar(distance);
766
- direction.y += -verticalOffset * 4 * size.y;
781
+ if (centerCamera === "y")
782
+ direction.y += -verticalOffset * 4 * size.y;
767
783
 
768
784
  if (camera.parent) {
769
785
  const cameraLocalPosition = camera.parent!.worldToLocal(center.clone().sub(direction));
@@ -785,7 +801,7 @@
785
801
  this._haveAttachedKeyboardEvents = true;
786
802
  document.body.addEventListener("keydown", (e) => {
787
803
  if (e.code === "KeyF") {
788
- this.fitCamera(objects, fitOffset, immediate);
804
+ this.fitCamera(objects, { fitOffset, immediate });
789
805
  }
790
806
  });
791
807
  }
src/engine-components/ShadowCatcher.ts CHANGED
@@ -69,6 +69,9 @@
69
69
  console.warn("ShadowCatcher: no mesh to apply shadow catching to. Groups are currently not supported.");
70
70
  return;
71
71
  }
72
+
73
+ // Shadowcatcher mesh isnt raycastable
74
+ this.targetMesh.layers.set(2);
72
75
 
73
76
  switch (this.mode) {
74
77
  case ShadowMode.ShadowMask:
src/engine-components/webxr/WebARSessionRoot.ts CHANGED
@@ -17,14 +17,36 @@
17
17
 
18
18
  const invertForwardMatrix = new Matrix4().makeRotationY(Math.PI);
19
19
 
20
- // TODO: webarsessionroot needs to place the rig (and not itself)
21
20
 
22
21
  /**
23
22
  * The WebARSessionRoot is the root object for a WebAR session and used to place the scene in AR.
24
23
  * It is also responsible for scaling the user in AR and optionally creating a XR anchor for the scene placement.
24
+ * @example
25
+ * ```ts
26
+ * WebARSessionRoot.onPlaced((args) => {
27
+ * console.log("Scene has been placed in AR");
28
+ * });
29
+ * ```
25
30
  */
26
31
  export class WebARSessionRoot extends Behaviour {
27
32
 
33
+ private static _eventListeners: { [key: string]: Array<(args: { instance: WebARSessionRoot }) => void> } = {};
34
+ /**
35
+ * Event that is called when the scene has been placed in AR.
36
+ * @param cb the callback that is called when the scene has been placed
37
+ * @returns a function to remove the event listener
38
+ */
39
+ static onPlaced(cb: (args: { instance: WebARSessionRoot }) => void) {
40
+ const event = "placed";
41
+ if (!this._eventListeners[event]) this._eventListeners[event] = [];
42
+ this._eventListeners[event].push(cb);
43
+ return () => {
44
+ const index = this._eventListeners[event].indexOf(cb);
45
+ if (index >= 0) this._eventListeners[event].splice(index, 1);
46
+ }
47
+ }
48
+
49
+
28
50
  /** The scale of a user in AR:
29
51
  * Note: a large value makes the scene appear smaller
30
52
  * @default 1