Needle Engine

Changes between version 3.48.0 and 3.48.1
Files changed (14) hide show
  1. src/engine-components/Animation.ts +0 -1
  2. src/engine-components/AnimatorController.ts +12 -7
  3. src/engine-components/ContactShadows.ts +5 -4
  4. src/engine/debug/debug_overlay.ts +1 -0
  5. src/engine/engine_animation.ts +12 -3
  6. src/engine/engine_element_attributes.ts +13 -1
  7. src/engine/engine_element_overlay.ts +14 -6
  8. src/engine/engine_element.ts +67 -32
  9. src/engine/engine_typestore.ts +26 -8
  10. src/engine/engine_utils_format.ts +19 -2
  11. src/engine/webcomponents/needle menu/needle-menu.ts +11 -3
  12. src/engine-components/Skybox.ts +2 -2
  13. src/engine-components/SpriteRenderer.ts +19 -11
  14. src/engine-components/webxr/WebARSessionRoot.ts +8 -0
src/engine-components/Animation.ts CHANGED
@@ -226,7 +226,6 @@
226
226
  onDisable(): void {
227
227
  if (this.mixer) {
228
228
  this.mixer.stopAllAction();
229
- this.mixer = undefined;
230
229
  }
231
230
  }
232
231
  /** @internal */
src/engine-components/AnimatorController.ts CHANGED
@@ -405,7 +405,7 @@
405
405
  const dur = state.motion.clip!.duration;
406
406
  const normalizedTime = dur <= 0 ? 1 : Math.abs(action.time / dur);
407
407
  let exitTime = transition.exitTime;
408
-
408
+
409
409
  // When the animation is playing backwards we need to check exit time inverted
410
410
  if (action.timeScale < 0) {
411
411
  exitTime = 1 - exitTime;
@@ -752,13 +752,18 @@
752
752
  for (const behaviour of state.behaviours) {
753
753
  if (!behaviour?.typeName) continue;
754
754
  const type = TypeStore.get(behaviour.typeName);
755
- const instance: StateMachineBehaviour = new type() as StateMachineBehaviour;
756
- if (instance.isStateMachineBehaviour) {
757
- instance._context = this.context ?? undefined;
758
- assign(instance, behaviour.properties);
759
- behaviour.instance = instance;
755
+ if (type) {
756
+ const instance: StateMachineBehaviour = new type() as StateMachineBehaviour;
757
+ if (instance.isStateMachineBehaviour) {
758
+ instance._context = this.context ?? undefined;
759
+ assign(instance, behaviour.properties);
760
+ behaviour.instance = instance;
761
+ }
762
+ if (debug) console.log("Created animator controller behaviour", state.name, behaviour.typeName, behaviour.properties, instance);
760
763
  }
761
- if (debug) console.log("Created animator controller behaviour", state.name, behaviour.typeName, behaviour.properties, instance);
764
+ else {
765
+ if (debug || isDevEnvironment()) console.warn("Could not find AnimatorBehaviour type: " + behaviour.typeName);
766
+ }
762
767
  }
763
768
  }
764
769
  }
src/engine-components/ContactShadows.ts CHANGED
@@ -128,9 +128,9 @@
128
128
  * Call to fit the shadows to the scene.
129
129
  */
130
130
  fitShadows() {
131
- if (debug) console.log("Fitting shadows to scene");
132
- setAutoFitEnabled(this.gameObject, false);
133
- const box = getBoundingBox(this.context.scene.children, [this.gameObject]);
131
+ if (debug) console.warn("Fitting shadows to scene");
132
+ setAutoFitEnabled(this.shadowsRoot, false);
133
+ const box = getBoundingBox(this.context.scene.children, [this.shadowsRoot]);
134
134
  // expand box in all directions (except below ground)
135
135
  // 0.75 expands by 75% in each direction
136
136
  // The "32" is pretty much heuristically determined – adjusting the value until we don't get a visible border anymore.
@@ -154,13 +154,14 @@
154
154
  this.shadowsRoot.scale.set(box.max.x - min.x, box.max.y - min.y, box.max.z - min.z);
155
155
  this.applyMinSize();
156
156
  this.shadowsRoot.matrixWorldNeedsUpdate = true;
157
+ if (debug) console.log("Fitted shadows to scene", this.shadowsRoot.scale.clone());
157
158
  }
158
159
 
159
160
 
160
161
  /** @internal */
161
162
  awake() {
162
163
  // ignore self for autofitting
163
- setAutoFitEnabled(this.gameObject, false);
164
+ setAutoFitEnabled(this.shadowsRoot, false);
164
165
  }
165
166
 
166
167
  /** @internal */
src/engine/debug/debug_overlay.ts CHANGED
@@ -194,6 +194,7 @@
194
194
  div[data-needle_engine_debug_overlay] {
195
195
  font-family: 'Roboto Flex', sans-serif;
196
196
  font-weight: 400;
197
+ font-size: 16px;
197
198
  }
198
199
 
199
200
  div[data-needle_engine_debug_overlay] strong {
src/engine/engine_animation.ts CHANGED
@@ -38,7 +38,10 @@
38
38
  * Unregister an animation mixer instance.
39
39
  */
40
40
  unregisterAnimationMixer(mixer: AnimationMixer | null | undefined): void {
41
- if (!mixer) return;
41
+ if (!mixer) {
42
+ console.warn("AnimationsRegistry.unregisterAnimationMixer called with null or undefined mixer")
43
+ return;
44
+ }
42
45
  const index = this.mixers.indexOf(mixer);
43
46
  if (index === -1) return;
44
47
  this.mixers.splice(index, 1);
@@ -87,11 +90,17 @@
87
90
  * @param file The GLTF file to assign the animations from
88
91
  */
89
92
  static assignAnimationsFromFile(file: Pick<GLTF, "animations" | "scene">, opts?: { createAnimationComponent(obj: Object3D, animation: AnimationClip): IAnimationComponent }) {
90
- if (!file || !file.animations) return;
93
+ if (!file || !file.animations) {
94
+ console.debug("No animations found in file");
95
+ return;
96
+ }
91
97
 
92
98
  for (let i = 0; i < file.animations.length; i++) {
93
99
  const animation = file.animations[i];
94
- if (!animation.tracks || animation.tracks.length <= 0) continue;
100
+ if (!animation.tracks || animation.tracks.length <= 0) {
101
+ console.warn("Animation has no tracks");
102
+ continue;
103
+ }
95
104
  for (const t in animation.tracks) {
96
105
  const track = animation.tracks[t];
97
106
  const parsedPath = PropertyBinding.parseTrackName(track.name);
src/engine/engine_element_attributes.ts CHANGED
@@ -47,9 +47,21 @@
47
47
  }
48
48
 
49
49
  type SkyboxAttributes = {
50
- /** URL to .exr, .hdr, .png, .jpg to be used as skybox */
50
+ /** use background-image instead - URL to .exr, .hdr, .png, .jpg to be used as skybox */
51
51
  "skybox-image"?: string,
52
52
  /** URL to .exr, .hdr, .png, .jpg to be used as skybox */
53
+ "background-image"?: string,
54
+ /** Blurs the background image (if any) - value between 0 and 1
55
+ * 0: no blur
56
+ * 1: full blur
57
+ */
58
+ "background-blurriness"?: number,
59
+ /**
60
+ * Background color to be used if no skybox or background image is provided.
61
+ * For example: "skybox-color='#ff0000'" will set the background color to red.
62
+ */
63
+ "background-color"?: string,
64
+ /** URL to .exr, .hdr, .png, .jpg to be used for lighting */
53
65
  "environment-image"?: string,
54
66
  }
55
67
 
src/engine/engine_element_overlay.ts CHANGED
@@ -19,6 +19,7 @@
19
19
  private contentElement: HTMLElement | null = null;
20
20
  private originalDomOverlayParent: ParentNode | null = null;
21
21
 
22
+
22
23
  requestEndAR = () => {
23
24
  this.onRequestedEndAR();
24
25
  }
@@ -151,20 +152,27 @@
151
152
 
152
153
  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
153
154
  svg.classList.add("quit-ar-button");
154
- svg.setAttribute('width', "38px");
155
- svg.setAttribute('height', "38px");
155
+ svg.setAttribute('width', "40px");
156
+ svg.setAttribute('height', "40px");
156
157
  svg.style.cssText = `
157
- background: radial-gradient(circle, rgba(0,0,0,.05) 70%, rgba(0,0,0,0) 100%);
158
+ background: rgba(255, 255, 255, .4);
159
+ -webkit-backdrop-filter: blur(8px);
160
+ backdrop-filter: blur(8px);
158
161
  border-radius: 50%;
162
+ box-shadow: 0 0 5px rgba(0,0,0,.3);
163
+ outline: 1px solid rgba(255, 255, 255, .6);
164
+ display: flex;
165
+ justify-content: center;
166
+ align-items: center;
159
167
  `;
160
168
  fixedButtonContainer.appendChild(svg);
161
169
 
162
170
  var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
163
171
  path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
164
- path.setAttribute('stroke', '#fff');
165
- path.setAttribute('stroke-width', "3px");
172
+ path.setAttribute('stroke', '#000000');
173
+ path.setAttribute('stroke-width', "2px");
166
174
  path.style.cssText = `
167
- filter: drop-shadow(0 0px 1.2px rgba(0,0,0,.7));
175
+ /**filter: drop-shadow(0 0px 1.2px rgba(0,0,0,.7));**/
168
176
  `
169
177
  svg.appendChild(path);
170
178
  if (debug) console.log("Created fallback close button", svg, element);
src/engine/engine_element.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AgXToneMapping, LinearToneMapping, NeutralToneMapping, NoToneMapping } from "three";
1
+ import { AgXToneMapping, Color, LinearToneMapping, NeutralToneMapping, NoToneMapping } from "three";
2
2
 
3
3
  import { getLoader, registerLoader } from "../engine/engine_gltf.js";
4
4
  import { GameObject } from "../engine-components/Component.js";
@@ -40,6 +40,8 @@
40
40
  "ktx2DecoderPath",
41
41
  "tone-mapping",
42
42
  "tone-mapping-exposure",
43
+ "background-blurriness",
44
+ "background-color",
43
45
  ]
44
46
 
45
47
  // https://developers.google.com/web/fundamentals/web-components/customelements
@@ -308,12 +310,24 @@
308
310
  setKtx2TranscoderPath(newValue);
309
311
  break;
310
312
  case "tone-mapping": {
311
- this.applyTonemapping();
313
+ this.applyAttributes();
312
314
  break;
313
315
  }
314
316
  case "tone-mapping-exposure": {
315
- this.applyTonemapping();
317
+ this.applyAttributes();
318
+ break;
316
319
  }
320
+ case "background-blurriness": {
321
+ const value = parseFloat(newValue);
322
+ if (value != undefined && this._context) {
323
+ this._context.scene.backgroundBlurriness = value;
324
+ }
325
+ break;
326
+ }
327
+ case "background-color": {
328
+ this.applyAttributes();
329
+ break;
330
+ }
317
331
  }
318
332
  }
319
333
 
@@ -453,7 +467,7 @@
453
467
  this._context.alias = alias;
454
468
  this._createContextPromise = this._context.create(args);
455
469
  const success = await this._createContextPromise;
456
- this.applyTonemapping();
470
+ this.applyAttributes();
457
471
 
458
472
  if (debug) console.warn("--------------\nNeedle Engine: finished loading " + loadId + "\n", filesToLoad, `Aborted? ${controller.signal.aborted}`);
459
473
  if (this._loadId !== loadId || controller.signal.aborted) {
@@ -477,36 +491,57 @@
477
491
  }));
478
492
  }
479
493
 
480
- private applyTonemapping() {
481
- if (!this._context?.renderer) return;
494
+ private applyAttributes() {
495
+ // set tonemapping if configured
496
+ if (this._context.renderer) {
497
+ const attribute = this.getAttribute("tonemapping") || this.getAttribute("tone-mapping") as TonemappingAttributeOptions | null | undefined;
498
+ switch (attribute?.toLowerCase()) {
499
+ case "none":
500
+ this._context.renderer.toneMapping = NoToneMapping;
501
+ break;
502
+ case "linear":
503
+ this._context.renderer.toneMapping = LinearToneMapping;
504
+ break;
505
+ case "neutral":
506
+ this._context.renderer.toneMapping = NeutralToneMapping;
507
+ break;
508
+ case "agx":
509
+ this._context.renderer.toneMapping = AgXToneMapping;
510
+ break;
511
+ default:
512
+ if (attribute !== null && attribute !== undefined) {
513
+ console.warn("Invalid tone-mapping attribute: " + attribute);
514
+ }
515
+ }
482
516
 
483
- // set tonemapping if configured
484
- const attribute = this.getAttribute("tonemapping") || this.getAttribute("tone-mapping") as TonemappingAttributeOptions | null | undefined;
485
- switch (attribute?.toLowerCase()) {
486
- case "none":
487
- this._context.renderer.toneMapping = NoToneMapping;
488
- break;
489
- case "linear":
490
- this._context.renderer.toneMapping = LinearToneMapping;
491
- break;
492
- case "neutral":
493
- this._context.renderer.toneMapping = NeutralToneMapping;
494
- break;
495
- case "agx":
496
- this._context.renderer.toneMapping = AgXToneMapping;
497
- break;
498
- default:
499
- if (attribute !== null && attribute !== undefined) {
500
- console.warn("Invalid tone-mapping attribute: " + attribute);
501
- }
517
+ const exposure = this.getAttribute("tone-mapping-exposure");
518
+ if (exposure !== null && exposure !== undefined) {
519
+ const value = parseFloat(exposure);
520
+ if (!isNaN(value))
521
+ this._context.renderer.toneMappingExposure = value;
522
+ }
502
523
  }
503
524
 
504
- const exposure = this.getAttribute("tone-mapping-exposure");
505
- if (exposure !== null && exposure !== undefined) {
506
- const value = parseFloat(exposure);
507
- if (!isNaN(value))
508
- this._context.renderer.toneMappingExposure = value;
525
+ const backgroundBlurriness = this.getAttribute("background-blurriness");
526
+ if (backgroundBlurriness !== null && backgroundBlurriness !== undefined) {
527
+ const value = parseFloat(backgroundBlurriness);
528
+ if (value !== undefined && this._context) {
529
+ this._context.scene.backgroundBlurriness = value;
530
+ }
509
531
  }
532
+
533
+ const backgroundColor = this.getAttribute("background-color");
534
+ if (this._context) {
535
+ if (typeof backgroundColor === "string" && backgroundColor.length > 0) {
536
+ const currentBackground = this._context.scene.background;
537
+ if (currentBackground instanceof Color) {
538
+ currentBackground.set(backgroundColor);
539
+ }
540
+ else {
541
+ this._context.scene.background = new Color(backgroundColor);
542
+ }
543
+ }
544
+ }
510
545
  }
511
546
 
512
547
  private onXRSessionStarted = () => {
@@ -774,7 +809,7 @@
774
809
  const isIgnored = ignoredCharacters.includes(c);
775
810
  if (isIgnored) continue;
776
811
  const isFirstCharacter = displayName.length === 0;
777
-
812
+
778
813
  if (isFirstCharacter) {
779
814
  c = c.toUpperCase();
780
815
  }
@@ -784,7 +819,7 @@
784
819
  if (lastCharacterWasSpace) {
785
820
  c = c.toUpperCase();
786
821
  }
787
-
822
+
788
823
  lastCharacterWasSpace = false;
789
824
  displayName += c;
790
825
 
src/engine/engine_typestore.ts CHANGED
@@ -2,20 +2,24 @@
2
2
 
3
3
  const debug = getParam("debugtypes");
4
4
 
5
+ declare type Type = new (...args: any[]) => any;
5
6
 
6
7
  class _TypeStore {
7
8
 
8
- private _types = {};
9
+ private _types: Map<string, Type> = new Map();
9
10
 
10
11
  constructor() {
11
12
  if (debug) console.warn("TypeStore: Created", this);
12
13
  }
13
14
 
14
- public add(key, type) {
15
+ /**
16
+ * add a type to the store
17
+ */
18
+ public add(key: string, type: Type) {
15
19
  if (debug) console.warn("ADD TYPE", key);
16
- const existing = this._types[key];
17
- if (existing === undefined)
18
- this._types[key] = type;
20
+ const existing = this._types.get(key);
21
+ if (!existing)
22
+ this._types.set(key, type);
19
23
  else {
20
24
  if (debug) {
21
25
  if (existing !== type) {
@@ -25,9 +29,23 @@
25
29
  }
26
30
  }
27
31
 
28
- public get(key) {
29
- return this._types[key];
32
+ /**
33
+ * @returns the type for the given key if registered
34
+ */
35
+ public get(key: string): Type | null {
36
+ return this._types.get(key) || null;
30
37
  }
38
+
39
+ /**
40
+ * @returns the key/name for the given type if registered
41
+ */
42
+ public getKey(type: Type): string | null {
43
+ for (const [key, value] of this._types) {
44
+ if (value === type)
45
+ return key;
46
+ }
47
+ return null;
48
+ }
31
49
  }
32
50
 
33
51
  export const $BuiltInTypeFlag = Symbol("BuiltInType");
@@ -40,7 +58,7 @@
40
58
  *
41
59
  * `export class MyType extends Behaviour { ... }`
42
60
  */
43
- export const registerType = function (constructor: Function) {
61
+ export const registerType = function (constructor: Type) {
44
62
  if (!TypeStore.get(constructor.name))
45
63
  TypeStore.add(constructor.name, constructor);
46
64
  }
src/engine/engine_utils_format.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { isDevEnvironment } from "./debug/debug.js";
1
2
  import { getParam } from "./engine_utils.js";
2
3
 
3
4
  const debug = getParam("debugfileformat");
@@ -135,9 +136,25 @@
135
136
  console.debug("OBJ detected");
136
137
  return "obj";
137
138
  }
139
+ // Check if it starts with "v " (vertex) or "f " (face) indicating that it's an OBJ file
140
+ // If the OBJ file does lack a header altogether and no other check matched
141
+ else if ((bytes[0] == 118 && bytes[1] == 32) || (bytes[0] == 102 && bytes[1] == 32)) {
142
+ console.debug("OBJ detected (the file has no header and starts with vertex or face)");
143
+ return "obj";
144
+ }
145
+ // Check if the file starts with "# File exported by ZBrush"
146
+ else if(bytes[0] == 35 && bytes[1] == 32 && bytes[2] == 70 && bytes[3] == 105 && bytes[4] == 108 && bytes[5] == 101 && bytes[6] == 32 && bytes[7] == 101 && bytes[8] == 120 && bytes[9] == 112 && bytes[10] == 111 && bytes[11] == 114 && bytes[12] == 116 && bytes[13] == 101 && bytes[14] == 100 && bytes[15] == 32 && bytes[16] == 98 && bytes[17] == 121 && bytes[18] == 32 && bytes[19] == 90 && bytes[20] == 66 && bytes[21] == 114 && bytes[22] == 117 && bytes[23] == 115 && bytes[24] == 104) {
147
+ console.debug("OBJ detected (exported by ZBrush)");
148
+ return "obj";
149
+ }
138
150
  else {
139
- // const text = new TextDecoder().decode(data.slice(0, 9));
140
- console.debug("Could not determine file type from binary data", bytes);
151
+ if (isDevEnvironment() || debug) {
152
+ const text = new TextDecoder().decode(data.slice(0, 16));
153
+ console.debug("Could not determine file type from binary data: \"" + text + "...\"", bytes);
154
+ }
155
+ else {
156
+ console.debug("Could not determine file type from binary data", bytes);
157
+ }
141
158
  }
142
159
 
143
160
  // const text = new TextDecoder().decode(data);
src/engine/webcomponents/needle menu/needle-menu.ts CHANGED
@@ -44,6 +44,7 @@
44
44
  priority?: number;
45
45
  /** Experimental. Allows to put two buttons in one row for the compact layout */
46
46
  class?: "row2";
47
+ title?: string;
47
48
  }
48
49
 
49
50
  /**
@@ -302,6 +303,9 @@
302
303
  top: auto;
303
304
  bottom: min(30px, 10vh);
304
305
  }
306
+ #root.top {
307
+ top: calc(.7rem + env(safe-area-inset-top));
308
+ }
305
309
 
306
310
  .wrapper {
307
311
  position: relative;
@@ -367,8 +371,8 @@
367
371
  flex-direction: row;
368
372
  }
369
373
 
370
- .options > button, ::slotted(button) {
371
- height: 2.25rem;
374
+ .options > *, ::slotted(*) {
375
+ max-height: 2.25rem;
372
376
  padding: .4rem .5rem;
373
377
  }
374
378
 
@@ -563,6 +567,7 @@
563
567
  .compact .options > * {
564
568
  font-size: 1.2rem;
565
569
  padding: .6rem .5rem;
570
+ width: 100%;
566
571
  }
567
572
  .compact.has-options .logo {
568
573
  border: none;
@@ -858,6 +863,9 @@
858
863
  button.textContent = node.label;
859
864
  button.onclick = node.onClick;
860
865
  button.setAttribute("priority", node.priority?.toString() ?? "0");
866
+ if (node.title) {
867
+ button.title = node.title;
868
+ }
861
869
  if (node.icon) {
862
870
  const icon = getIconElement(node.icon);
863
871
  if (node.iconSide === "right") {
@@ -988,7 +996,7 @@
988
996
  if (!this._domElement) return;
989
997
 
990
998
  const width = this._domElement.clientWidth;
991
- if (width < 500) {
999
+ if (width < 100) {
992
1000
  clearTimeout(this._timeoutHandle!);
993
1001
  this.root.classList.add("compact");
994
1002
  this.foldout.classList.add("floating-panel-style");
src/engine-components/Skybox.ts CHANGED
@@ -39,7 +39,7 @@
39
39
 
40
40
  ContextRegistry.addContextCreatedCallback((args) => {
41
41
  const context = args.context;
42
- const skyboxImage = context.domElement.getAttribute("skybox-image");
42
+ const skyboxImage = context.domElement.getAttribute("skybox-image") || context.domElement.getAttribute("background-image");
43
43
  const environmentImage = context.domElement.getAttribute("environment-image");
44
44
  const promises = new Array<Promise<any>>();
45
45
  if (skyboxImage) {
@@ -48,7 +48,7 @@
48
48
  // if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
49
49
  // checks if we have this attribute set and then sets the skybox clearflags accordingly
50
50
  // if the user has a GLB with a camera but set to solid color then the skybox image is not visible -> we will just warn then and not override the camera settings
51
- if (context.mainCameraComponent?.clearFlags !== ClearFlags.Skybox) console.warn("\"skybox-image\" attribute has no effect: camera clearflags are not set to \"Skybox\"");
51
+ if (context.mainCameraComponent?.clearFlags !== ClearFlags.Skybox) console.warn("\"skybox-image\"/\"background-image\" attribute has no effect: camera clearflags are not set to \"Skybox\"");
52
52
  const promise = createRemoteSkyboxComponent(context, skyboxImage, true, false, "skybox-image");
53
53
  promises.push(promise);
54
54
  }
src/engine-components/SpriteRenderer.ts CHANGED
@@ -69,6 +69,18 @@
69
69
  y!: number;
70
70
  }
71
71
 
72
+ function updateTextureIfNecessary(tex: Texture) {
73
+ if (!tex) return;
74
+ if (tex.colorSpace != SRGBColorSpace) {
75
+ tex.colorSpace = SRGBColorSpace;
76
+ tex.needsUpdate = true;
77
+ }
78
+ if (tex.minFilter == NearestFilter && tex.magFilter == NearestFilter) {
79
+ tex.anisotropy = 1;
80
+ tex.needsUpdate = true;
81
+ }
82
+ }
83
+
72
84
  /**
73
85
  * A sprite is a mesh that represents a 2D image
74
86
  */
@@ -115,10 +127,7 @@
115
127
  get material() {
116
128
  if (!this._material) {
117
129
  if (this.texture) {
118
- this.texture.colorSpace = SRGBColorSpace;
119
- if (this.texture.minFilter == NearestFilter && this.texture.magFilter == NearestFilter)
120
- this.texture.anisotropy = 1;
121
- this.texture.needsUpdate = true;
130
+ updateTextureIfNecessary(this.texture);
122
131
  }
123
132
  this._material = new MeshBasicMaterial({
124
133
  map: this.texture,
@@ -204,10 +213,7 @@
204
213
  const sprite = this.spriteSheet.sprites[index];
205
214
  const tex = sprite?.texture;
206
215
  if (!tex) return;
207
- tex.colorSpace = SRGBColorSpace;
208
- if (tex.minFilter == NearestFilter && tex.magFilter == NearestFilter)
209
- tex.anisotropy = 1;
210
- tex.needsUpdate = true;
216
+ updateTextureIfNecessary(tex);
211
217
 
212
218
  if (!sprite["__hasLoadedProgressive"]) {
213
219
  sprite["__hasLoadedProgressive"] = true;
@@ -318,10 +324,12 @@
318
324
  if (!this._spriteSheet) {
319
325
  this._spriteSheet = SpriteData.create();
320
326
  }
321
- this._spriteSheet.sprite = value;
322
- this.updateSprite();
327
+ if (this._spriteSheet.sprite != value) {
328
+ this._spriteSheet.sprite = value;
329
+ this.updateSprite();
330
+ }
323
331
  }
324
- else {
332
+ else if (value != this._spriteSheet) {
325
333
  this._spriteSheet = value;
326
334
  this.updateSprite();
327
335
  }
src/engine-components/webxr/WebARSessionRoot.ts CHANGED
@@ -680,6 +680,14 @@
680
680
  private _didMultitouch: boolean = false;
681
681
 
682
682
  private touchStart = (evt: TouchEvent) => {
683
+ if (evt.defaultPrevented) return;
684
+
685
+ // let isValidTouch = true;
686
+ // isValidTouch = evt.target === this.context.domElement || evt.target === this.context.renderer.domElement;
687
+ // if (!isValidTouch) {
688
+ // return;
689
+ // }
690
+
683
691
  for (let i = 0; i < evt.changedTouches.length; i++) {
684
692
  const touch = evt.changedTouches[i];
685
693
  // if a user starts swiping in the top area of the screen