Needle Engine

Changes between version 3.22.3 and 3.22.4
Files changed (6) hide show
  1. src/engine-components/AnimatorController.ts +9 -8
  2. src/engine/debug/debug_overlay.ts +1 -1
  3. src/engine/engine_context.ts +7 -1
  4. src/engine/engine_utils.ts +5 -1
  5. src/engine-components/export/usdz/ThreeUSDZExporter.ts +91 -36
  6. src/engine-components/webxr/WebXRController.ts +27 -14
src/engine-components/AnimatorController.ts CHANGED
@@ -326,14 +326,6 @@
326
326
  // console.log("All conditions are met", transition);
327
327
  }
328
328
 
329
- // disable triggers
330
- for (const cond of transition.conditions) {
331
- const param = this.model.parameters.find(p => p.name === cond.parameter);
332
- if (param?.type === AnimatorControllerParameterType.Trigger) {
333
- param.value = false;
334
- }
335
- }
336
-
337
329
  if (action) {
338
330
  const dur = state.motion.clip!.duration;
339
331
  const normalizedTime = dur <= 0 ? 1 : Math.abs(action.time / dur);
@@ -346,7 +338,16 @@
346
338
  else {
347
339
  makeTransition = true;
348
340
  }
341
+
349
342
  if (makeTransition) {
343
+ // disable triggers for this transition
344
+ for (const cond of transition.conditions) {
345
+ const param = this.model.parameters.find(p => p.name === cond.parameter);
346
+ if (param?.type === AnimatorControllerParameterType.Trigger && param.value) {
347
+ param.value = false;
348
+ }
349
+ }
350
+
350
351
  // if (transition.hasExitTime && transition.exitTime >= .9999)
351
352
  action.clampWhenFinished = true;
352
353
  // else action.clampWhenFinished = false;
src/engine/debug/debug_overlay.ts CHANGED
@@ -197,7 +197,7 @@
197
197
  padding-top: 0px;
198
198
  max-width: 70%;
199
199
  max-height: calc(100% - 5px);
200
- z-index: 1000;
200
+ z-index: 9999999999;
201
201
  pointer-events: scroll;
202
202
  display: flex;
203
203
  align-items: end;
src/engine/engine_context.ts CHANGED
@@ -30,7 +30,7 @@
30
30
  import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
31
31
  import { delay, getParam } from './engine_utils.js';
32
32
  import { VERSION } from './engine_constants.js';
33
- import { isDevEnvironment, LogType, showBalloonMessage, showBalloonWarning } from './debug/index.js';
33
+ import { isDevEnvironment, LogType, showBalloonError, showBalloonMessage, showBalloonWarning } from './debug/index.js';
34
34
  import { getLoader } from './engine_gltf.js';
35
35
  import { isLocalNetwork } from './engine_networking_utils.js';
36
36
  import { WaitForPromise } from './engine_coroutine.js';
@@ -451,14 +451,20 @@
451
451
  async create(opts?: ContextCreateArgs) {
452
452
  try {
453
453
  this._isCreating = true;
454
+ window.addEventListener("unhandledrejection", this.onUnhandledRejection)
454
455
  const res = await this.internalOnCreate(opts);
455
456
  return res;
456
457
  }
457
458
  finally {
459
+ window.removeEventListener("unhandledrejection", this.onUnhandledRejection)
458
460
  this._isCreating = false;
459
461
  }
460
462
  }
461
463
 
464
+ private onUnhandledRejection = (event: PromiseRejectionEvent) => {
465
+ this.domElement.dispatchEvent(new CustomEvent("error", { detail: event.reason }));
466
+ };
467
+
462
468
  /** Will destroy all scenes and objects in the scene
463
469
  */
464
470
  clear() {
src/engine/engine_utils.ts CHANGED
@@ -586,7 +586,11 @@
586
586
  anyFailed: boolean,
587
587
  results: Array<T | PromiseErrorResult>
588
588
  }> {
589
- const results = await Promise.allSettled(promise);
589
+ const results = await Promise.allSettled(promise).catch(err => {
590
+ return [
591
+ new PromiseErrorResult(err.message)
592
+ ];
593
+ })
590
594
  let anyFailed: boolean = false;
591
595
  const res = results.map(x => {
592
596
  if ("value" in x) return x.value;
src/engine-components/export/usdz/ThreeUSDZExporter.ts CHANGED
@@ -462,16 +462,20 @@
462
462
  files[ modelFileName ] = fflate.strToU8( final );
463
463
  context.output = '';
464
464
 
465
+ const decompressionRenderer = new WebGLRenderer( { antialias: false, alpha: true } );
466
+
465
467
  for ( const id in textures ) {
466
468
 
467
469
  let texture = textures[ id ];
468
- const isRGBA = texture.format === 1023;
470
+
471
+ const isRGBA = formatsWithAlphaChannel.includes( texture.format );
472
+
469
473
  if ( texture.isCompressedTexture ) {
470
474
 
471
- texture = copyTexture( texture );
475
+ texture = decompressGpuTexture( texture, 4096, decompressionRenderer );
472
476
 
473
477
  }
474
-
478
+
475
479
  // TODO add readback options for textures that don't have texture.image
476
480
  const canvas = await imageToCanvas( texture.image );
477
481
 
@@ -488,6 +492,8 @@
488
492
 
489
493
  }
490
494
 
495
+ decompressionRenderer.dispose();
496
+
491
497
  // 64 byte alignment
492
498
  // https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109
493
499
 
@@ -715,58 +721,80 @@
715
721
  }
716
722
 
717
723
  }
724
+ let _renderer: WebGLRenderer | null = null;
725
+ let fullscreenQuadGeometry: PlaneGeometry | null;
726
+ let fullscreenQuadMaterial: ShaderMaterial | null;
727
+ let fullscreenQuad: Mesh | null;
718
728
 
719
- function copyTexture( texture : Texture ) {
729
+ function decompressGpuTexture( texture, maxTextureSize = Infinity, renderer: WebGLRenderer | null = null ) {
720
730
 
721
- const geometry = new PlaneGeometry( 2, 2, 1, 1 );
722
- const material = new ShaderMaterial( {
723
- uniforms: {
724
- blitTexture: new Uniform( texture ),
725
- },
726
- defines: {
727
- IS_SRGB: texture.colorSpace == SRGBColorSpace,
728
- },
729
- vertexShader: `
731
+ if ( ! fullscreenQuadGeometry ) fullscreenQuadGeometry = new PlaneGeometry( 2, 2, 1, 1 );
732
+ if ( ! fullscreenQuadMaterial ) fullscreenQuadMaterial = new ShaderMaterial( {
733
+ uniforms: { blitTexture: new Uniform( texture ) },
734
+ vertexShader: `
730
735
  varying vec2 vUv;
731
736
  void main(){
732
737
  vUv = uv;
733
738
  vUv.y = 1. - vUv.y;
734
739
  gl_Position = vec4(position.xy * 1.0,0.,.999999);
735
740
  }`,
736
- fragmentShader: `
741
+ fragmentShader: `
737
742
  uniform sampler2D blitTexture;
738
743
  varying vec2 vUv;
739
744
 
740
- // took from threejs 05fc79cd52b79e8c3e8dec1e7dca72c5c39983a4
741
- vec4 conv_LinearTosRGB( in vec4 value ) {
742
- return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
743
- }
744
-
745
745
  void main(){
746
746
  gl_FragColor = vec4(vUv.xy, 0, 1);
747
747
 
748
748
  #ifdef IS_SRGB
749
- gl_FragColor = conv_LinearTosRGB( texture2D( blitTexture, vUv) );
749
+ gl_FragColor = LinearTosRGB( texture2D( blitTexture, vUv) );
750
750
  #else
751
751
  gl_FragColor = texture2D( blitTexture, vUv);
752
752
  #endif
753
753
  }`
754
- } );
754
+ } );
755
755
 
756
- const mesh = new Mesh( geometry, material );
757
- mesh.frustumCulled = false;
758
- const cam = new PerspectiveCamera();
759
- const scene = new Scene();
760
- scene.add( mesh );
761
- const renderer = new WebGLRenderer( { antialias: false } );
762
- renderer.setSize( texture.image.width, texture.image.height );
756
+ fullscreenQuadMaterial.uniforms.blitTexture.value = texture;
757
+ fullscreenQuadMaterial.defines.IS_SRGB = texture.colorSpace == SRGBColorSpace;
758
+ fullscreenQuadMaterial.needsUpdate = true;
759
+
760
+ if ( ! fullscreenQuad ) {
761
+
762
+ fullscreenQuad = new Mesh( fullscreenQuadGeometry, fullscreenQuadMaterial );
763
+ fullscreenQuad.frustumCulled = false;
764
+
765
+ }
766
+
767
+ const _camera = new PerspectiveCamera();
768
+ const _scene = new Scene();
769
+ _scene.add( fullscreenQuad );
770
+
771
+ if ( ! renderer ) {
772
+
773
+ renderer = _renderer = new WebGLRenderer( { antialias: false, alpha: true } );
774
+
775
+ }
776
+
777
+ renderer.setSize( Math.min( texture.image.width, maxTextureSize ), Math.min( texture.image.height, maxTextureSize ) );
763
778
  renderer.clear();
764
- renderer.render( scene, cam );
779
+ renderer.render( _scene, _camera );
765
780
 
766
- const tex = new Texture( renderer.domElement );
767
- tex.colorSpace = texture.colorSpace;
768
- return tex;
781
+ const readableTexture = new Texture( renderer.domElement );
769
782
 
783
+ readableTexture.minFilter = texture.minFilter;
784
+ readableTexture.magFilter = texture.magFilter;
785
+ readableTexture.wrapS = texture.wrapS;
786
+ readableTexture.wrapT = texture.wrapT;
787
+ readableTexture.name = texture.name;
788
+
789
+ if ( _renderer ) {
790
+
791
+ _renderer.dispose();
792
+ _renderer = null;
793
+
794
+ }
795
+
796
+ return readableTexture;
797
+
770
798
  }
771
799
 
772
800
 
@@ -1134,13 +1162,13 @@
1134
1162
 
1135
1163
  function buildTexture( texture, mapType, color: Color | undefined = undefined, opacity: number | undefined = undefined ) {
1136
1164
 
1137
- const id = texture.id + ( color ? '_' + color.getHexString() : '' ) + ( opacity ? '_' + opacity : '' );
1165
+ const id = texture.id + ( color ? '_' + color.getHexString() : '' ) + ( opacity !== undefined ? '_' + opacity : '' );
1138
1166
 
1139
1167
  textures[ id ] = texture;
1140
1168
 
1141
1169
  const uv = texture.channel > 0 ? 'st' + texture.channel : 'st';
1142
1170
 
1143
- const isRGBA = texture.format === 1023;
1171
+ const isRGBA = formatsWithAlphaChannel.includes( texture.format );
1144
1172
 
1145
1173
  const WRAPPINGS = {
1146
1174
  1000: 'repeat', // RepeatWrapping
@@ -1213,7 +1241,7 @@
1213
1241
  token inputs:sourceColorSpace = "${ texture.colorSpace === 'srgb' ? 'sRGB' : 'raw' }"
1214
1242
  float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
1215
1243
  ${needsTextureScale ? `
1216
- float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})
1244
+ float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity !== undefined ? opacity : '1'})
1217
1245
  ` : `` }
1218
1246
  ${needsNormalScaleAndBias ? `
1219
1247
  float4 inputs:scale = (${normalScaleValueString}, ${normalScaleValueString}, ${normalScaleValueString}, 1)
@@ -1396,4 +1424,31 @@
1396
1424
 
1397
1425
  }
1398
1426
 
1399
- export { USDZExporter, USDZExporterContext, USDWriter, USDObject, buildMatrix, USDDocument, makeNameSafe as makeNameSafeForUSD, imageToCanvas };
1427
+ const formatsWithAlphaChannel = [
1428
+ // uncompressed formats with alpha channel
1429
+ 1023, // RGBAFormat
1430
+ // compressed formats with alpha channel
1431
+ 33777, // RGBA_S3TC_DXT1_Format
1432
+ 33778, // RGBA_S3TC_DXT3_Format
1433
+ 33779, // RGBA_S3TC_DXT5_Format
1434
+ 35842, // RGBA_PVRTC_4BPPV1_Format
1435
+ 35843, // RGBA_PVRTC_2BPPV1_Format
1436
+ 37496, // RGBA_ETC2_EAC_Format
1437
+ 37808, // RGBA_ASTC_4x4_Format
1438
+ 37809, // RGBA_ASTC_5x4_Format
1439
+ 37810, // RGBA_ASTC_5x5_Format
1440
+ 37811, // RGBA_ASTC_6x5_Format
1441
+ 37812, // RGBA_ASTC_6x6_Format
1442
+ 37813, // RGBA_ASTC_8x5_Format
1443
+ 37814, // RGBA_ASTC_8x6_Format
1444
+ 37815, // RGBA_ASTC_8x8_Format
1445
+ 37816, // RGBA_ASTC_10x5_Format
1446
+ 37817, // RGBA_ASTC_10x6_Format
1447
+ 37818, // RGBA_ASTC_10x8_Format
1448
+ 37819, // RGBA_ASTC_10x10_Format
1449
+ 37820, // RGBA_ASTC_12x10_Format
1450
+ 37821, // RGBA_ASTC_12x12_Format
1451
+ 36492, // RGBA_BPTC_Format
1452
+ ];
1453
+
1454
+ export { USDZExporter, USDZExporterContext, USDWriter, USDObject, buildMatrix, USDDocument, makeNameSafe as makeNameSafeForUSD, imageToCanvas, decompressGpuTexture };
src/engine-components/webxr/WebXRController.ts CHANGED
@@ -168,6 +168,8 @@
168
168
  public input: XRInputSource | null = null;
169
169
  public type: ControllerType = ControllerType.PhysicalDevice;
170
170
  public showRaycastLine: boolean = true;
171
+ public enableRaycasts: boolean = true;
172
+ public enableDefaultControls: boolean = true;
171
173
 
172
174
  get isUsingHands(): boolean {
173
175
  const r = this.input?.hand;
@@ -415,14 +417,23 @@
415
417
  this.handPointerModel.cursorObject.visible = false;
416
418
  }
417
419
 
418
- if (this.raycastLine) {
419
- this.raycastUpdate(this.raycastLine, wp);
420
+ // perform raycasts
421
+ if(this.enableRaycasts)
422
+ {
423
+ if (this.raycastLine) {
424
+ this.raycastUpdate(this.raycastLine, wp);
425
+ }
426
+
427
+ this.lastHit = this.updateLastHit();
428
+
429
+ if (this.grabbed) {
430
+ this.grabbed.update();
431
+ }
420
432
  }
421
-
422
- this.lastHit = this.updateLastHit();
423
-
424
- if (this.grabbed) {
425
- this.grabbed.update();
433
+ else { // hide line when raycasting is disabled
434
+ if (this.raycastLine) {
435
+ this.raycastLine.visible = false;
436
+ }
426
437
  }
427
438
 
428
439
  this._selectionPressedLastFrame = this._selectionPressed;
@@ -454,14 +465,16 @@
454
465
 
455
466
  const buttons = this.input?.gamepad?.buttons;
456
467
 
457
- switch (this.input.handedness) {
458
- case "left":
459
- this.movementUpdate(rig, buttons);
460
- break;
468
+ if(this.enableDefaultControls) {
469
+ switch (this.input.handedness) {
470
+ case "left":
471
+ this.movementUpdate(rig, buttons);
472
+ break;
461
473
 
462
- case "right":
463
- this.rotationUpdate(rig, buttons);
464
- break;
474
+ case "right":
475
+ this.rotationUpdate(rig, buttons);
476
+ break;
477
+ }
465
478
  }
466
479
  }