@needle-tools/engine 3.4.0-alpha → 3.5.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.js +59357 -59090
  3. package/dist/needle-engine.min.js +368 -345
  4. package/dist/needle-engine.umd.cjs +386 -363
  5. package/lib/engine/api.d.ts +1 -0
  6. package/lib/engine/api.js +1 -0
  7. package/lib/engine/api.js.map +1 -1
  8. package/lib/engine/engine_context.d.ts +1 -1
  9. package/lib/engine/engine_context.js +21 -15
  10. package/lib/engine/engine_context.js.map +1 -1
  11. package/lib/engine/engine_context_registry.d.ts +5 -3
  12. package/lib/engine/engine_context_registry.js +10 -2
  13. package/lib/engine/engine_context_registry.js.map +1 -1
  14. package/lib/engine/engine_element.js.map +1 -1
  15. package/lib/engine/engine_element_loading.js +2 -3
  16. package/lib/engine/engine_element_loading.js.map +1 -1
  17. package/lib/engine/engine_input.d.ts +2 -2
  18. package/lib/engine/engine_physics.d.ts +20 -93
  19. package/lib/engine/engine_physics.js +20 -892
  20. package/lib/engine/engine_physics.js.map +1 -1
  21. package/lib/engine/engine_physics.types.js.map +1 -1
  22. package/lib/engine/engine_physics_rapier.d.ts +103 -0
  23. package/lib/engine/engine_physics_rapier.js +1003 -0
  24. package/lib/engine/engine_physics_rapier.js.map +1 -0
  25. package/lib/engine/engine_types.d.ts +50 -1
  26. package/lib/engine/engine_types.js +8 -0
  27. package/lib/engine/engine_types.js.map +1 -1
  28. package/lib/engine-components/Collider.js +6 -6
  29. package/lib/engine-components/Collider.js.map +1 -1
  30. package/lib/engine-components/Joints.js +2 -2
  31. package/lib/engine-components/Joints.js.map +1 -1
  32. package/lib/engine-components/RigidBody.d.ts +0 -1
  33. package/lib/engine-components/RigidBody.js +24 -30
  34. package/lib/engine-components/RigidBody.js.map +1 -1
  35. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +55 -27
  36. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  37. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +8 -2
  38. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +44 -7
  39. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  40. package/lib/engine-components/ui/RectTransform.js +3 -1
  41. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  42. package/lib/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/plugins/vite/config.js +2 -1
  45. package/plugins/vite/defines.js +30 -0
  46. package/plugins/vite/dependency-watcher.js +173 -0
  47. package/plugins/vite/editor-connection.js +37 -39
  48. package/plugins/vite/index.js +5 -1
  49. package/plugins/vite/reload.js +3 -1
  50. package/src/engine/api.ts +1 -0
  51. package/src/engine/codegen/register_types.js +2 -2
  52. package/src/engine/engine_context.ts +32 -23
  53. package/src/engine/engine_context_registry.ts +13 -6
  54. package/src/engine/engine_element.ts +2 -1
  55. package/src/engine/engine_element_loading.ts +2 -3
  56. package/src/engine/engine_input.ts +2 -2
  57. package/src/engine/engine_physics.ts +25 -1020
  58. package/src/engine/engine_physics.types.ts +1 -3
  59. package/src/engine/engine_physics_rapier.ts +1127 -0
  60. package/src/engine/engine_types.ts +66 -4
  61. package/src/engine-components/Collider.ts +6 -6
  62. package/src/engine-components/Joints.ts +2 -2
  63. package/src/engine-components/RigidBody.ts +24 -31
  64. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +62 -30
  65. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +51 -9
  66. package/src/engine-components/ui/RectTransform.ts +3 -2
@@ -1,5 +1,5 @@
1
1
  import { RenderTexture } from "./engine_texture";
2
- import { Camera, Color, Material, Object3D, Vector3, Quaternion, Ray, Scene, Renderer, WebGLRenderer } from "three";
2
+ import { Camera, Color, Material, Object3D, Vector3, Quaternion, Ray, Scene, Renderer, WebGLRenderer, Mesh } from "three";
3
3
  import { RGBAColor } from "../engine-components/js-extensions/RGBAColor";
4
4
  import { CollisionDetectionMode, PhysicsMaterial, RigidbodyConstraints } from "./engine_physics.types";
5
5
  import { CircularBuffer } from "./engine_utils";
@@ -20,7 +20,6 @@ export interface UIDProvider {
20
20
  generateUUID(): string;
21
21
  }
22
22
 
23
-
24
23
  export declare type CoroutineData = {
25
24
  comp: IComponent,
26
25
  main: Generator,
@@ -31,9 +30,17 @@ export interface ITime {
31
30
  get time(): number;
32
31
  }
33
32
 
33
+ export interface IInput {
34
+ convertScreenspaceToRaycastSpace(vec: Vec2): void;
35
+ }
36
+
37
+ export interface IPhysics {
38
+ engine?: IPhysicsEngine;
39
+ }
40
+
34
41
  export interface IContext {
35
42
  alias?: string | null;
36
- hash?:string;
43
+ hash?: string;
37
44
 
38
45
  scene: Scene;
39
46
  renderer: WebGLRenderer;
@@ -42,6 +49,8 @@ export interface IContext {
42
49
  domElement: HTMLElement;
43
50
 
44
51
  time: ITime;
52
+ input: IInput;
53
+ physics: IPhysics;
45
54
 
46
55
  scripts: IComponent[];
47
56
  scripts_pausedChanged: IComponent[];
@@ -343,4 +352,57 @@ export class Collision {
343
352
  // }
344
353
  // return this._point;
345
354
  // }
346
- }
355
+ }
356
+
357
+ export type RaycastResult = null | { point: Vector3, collider: ICollider, normal?: Vector3 };
358
+
359
+ export class SphereOverlapResult {
360
+ object: Object3D;
361
+ collider: ICollider;
362
+ constructor(object: Object3D, collider: ICollider) {
363
+ this.object = object;
364
+ this.collider = collider;
365
+ }
366
+ }
367
+
368
+
369
+ export interface IPhysicsEngine {
370
+ initialize(ctx: IContext): Promise<boolean>;
371
+ step(dt: number): void;
372
+ postStep();
373
+ get isUpdating(): boolean;
374
+ /** clear all possibly cached data (e.g. mesh data when creating scaled mesh colliders) */
375
+ clearCaches();
376
+
377
+ // raycasting
378
+ /** fast raycast without getting the normal vector */
379
+ raycast(origin: Vec2 | Vec3, direction: Vec3 | undefined, maxDistance: number, solid: boolean): RaycastResult;
380
+ /** raycast that also gets the normal vector. If you don't need it use raycast() */
381
+ raycastAndGetNormal(origin: Vec2 | Vec3, direction: Vec3 | undefined, maxDistance: number, solid: boolean) : RaycastResult;
382
+ sphereOverlap(point: Vector3, radius: number): Array<SphereOverlapResult>;
383
+
384
+ // Collider methods
385
+ addSphereCollider(collider: ICollider, center: Vector3, radius: number);
386
+ addBoxCollider(collider: ICollider, center: Vector3, size: Vector3);
387
+ addCapsuleCollider(collider: ICollider, center: Vector3, radius: number, height: number);
388
+ addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale: Vector3);
389
+
390
+ // Rigidbody methods
391
+ wakeup(rb: IRigidbody);
392
+ updateProperties(rb: IRigidbody);
393
+ resetForces(rb: IRigidbody, wakeup: boolean);
394
+ resetTorques(rb: IRigidbody, wakeup: boolean);
395
+ addForce(rb: IRigidbody, vec: Vec3, wakeup: boolean);
396
+ applyImpulse(rb: IRigidbody, vec: Vec3, wakeup: boolean);
397
+ getLinearVelocity(rb: IRigidbody): Vec3 | null;
398
+ getAngularVelocity(rb: IRigidbody): Vec3 | null;
399
+ setAngularVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean);
400
+ setLinearVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean);
401
+
402
+ updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean);
403
+ removeBody(body: IComponent);
404
+
405
+ // Joints
406
+ addFixedJoint(body1: IRigidbody, body2: IRigidbody)
407
+ addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: Vec3, axis: Vec3)
408
+ }
@@ -40,7 +40,7 @@ export class Collider extends Behaviour implements ICollider {
40
40
  }
41
41
 
42
42
  onDisable() {
43
- this.context.physics.removeBody(this);
43
+ this.context.physics.engine?.removeBody(this);
44
44
  }
45
45
 
46
46
  }
@@ -55,7 +55,7 @@ export class SphereCollider extends Collider {
55
55
 
56
56
  onEnable() {
57
57
  super.onEnable();
58
- this.context.physics.addSphereCollider(this, this.center, this.radius);
58
+ this.context.physics.engine?.addSphereCollider(this, this.center, this.radius);
59
59
  }
60
60
  }
61
61
 
@@ -68,7 +68,7 @@ export class BoxCollider extends Collider {
68
68
 
69
69
  onEnable() {
70
70
  super.onEnable();
71
- this.context.physics.addBoxCollider(this, this.center, this.size);
71
+ this.context.physics.engine?.addBoxCollider(this, this.center, this.size);
72
72
  }
73
73
  }
74
74
 
@@ -90,7 +90,7 @@ export class MeshCollider extends Collider {
90
90
  }
91
91
  }
92
92
  if (this.sharedMesh?.isMesh) {
93
- this.context.physics.addMeshCollider(this, this.sharedMesh, this.convex, getWorldScale(this.gameObject));
93
+ this.context.physics.engine?.addMeshCollider(this, this.sharedMesh, this.convex, getWorldScale(this.gameObject));
94
94
  }
95
95
  else {
96
96
  const group = this.sharedMesh as any as Group;
@@ -99,7 +99,7 @@ export class MeshCollider extends Collider {
99
99
  for (const ch in group.children) {
100
100
  const child = group.children[ch] as Mesh;
101
101
  if (child.isMesh) {
102
- this.context.physics.addMeshCollider(this, child, this.convex, getWorldScale(this.gameObject));
102
+ this.context.physics.engine?.addMeshCollider(this, child, this.convex, getWorldScale(this.gameObject));
103
103
  }
104
104
  }
105
105
  }
@@ -119,7 +119,7 @@ export class CapsuleCollider extends Collider {
119
119
 
120
120
  onEnable() {
121
121
  super.onEnable();
122
- this.context.physics.addCapsuleCollider(this, this.center, this.height, this.radius);
122
+ this.context.physics.engine?.addCapsuleCollider(this, this.center, this.height, this.radius);
123
123
  }
124
124
 
125
125
  }
@@ -32,7 +32,7 @@ export abstract class Joint extends Behaviour {
32
32
  export class FixedJoint extends Joint {
33
33
 
34
34
  protected createJoint(self: Rigidbody, other: Rigidbody) {
35
- this.context.physics.addFixedJoint(self, other);
35
+ this.context.physics.engine?.addFixedJoint(self, other);
36
36
  }
37
37
  }
38
38
 
@@ -46,7 +46,7 @@ export class HingeJoint extends Joint {
46
46
 
47
47
  protected createJoint(self: Rigidbody, other: Rigidbody) {
48
48
  if (this.axis && this.anchor)
49
- this.context.physics.addHingeJoint(self, other, this.anchor, this.axis);
49
+ this.context.physics.engine?.addHingeJoint(self, other, this.anchor, this.axis);
50
50
  }
51
51
 
52
52
  }
@@ -89,7 +89,7 @@ class TransformWatch {
89
89
  this.position = {};
90
90
  // this.position = this.obj.position.clone();
91
91
  this._positionWatch.subscribeWrite((val, prop) => {
92
- if (this.context.physics.isUpdating || this.mute) return;
92
+ if (this.context.physics.engine?.isUpdating || this.mute) return;
93
93
  const prev = this.position![prop];
94
94
  if (Math.abs(prev - val) < .00001) return;
95
95
  this.position![prop] = val;
@@ -103,7 +103,7 @@ class TransformWatch {
103
103
  this.quaternion = {};
104
104
  // this.quaternion = this.obj.quaternion.clone();
105
105
  this._rotationWatch.subscribeWrite((val, prop) => {
106
- if (this.context.physics.isUpdating || this.mute) return;
106
+ if (this.context.physics.engine?.isUpdating || this.mute) return;
107
107
  const prev = this.quaternion![prop];
108
108
  if (Math.abs(prev - val) < .00001) return;
109
109
  this.quaternion![prop] = val;
@@ -245,11 +245,11 @@ export class Rigidbody extends Behaviour implements IRigidbody {
245
245
 
246
246
  onDisable() {
247
247
  this._watch?.stop();
248
- this.context.physics.removeBody(this);
248
+ this.context.physics.engine?.removeBody(this);
249
249
  }
250
250
 
251
251
  onDestroy(): void {
252
- this.context.physics.removeBody(this);
252
+ this.context.physics.engine?.removeBody(this);
253
253
  }
254
254
 
255
255
  onValidate() {
@@ -261,12 +261,12 @@ export class Rigidbody extends Behaviour implements IRigidbody {
261
261
  while (true) {
262
262
  if (this._propertiesChanged) {
263
263
  this._propertiesChanged = false;
264
- this.context.physics.updateProperties(this);
264
+ this.context.physics.engine?.updateProperties(this);
265
265
  }
266
266
  if (this._watch?.isDirty) {
267
267
  this._watch.mute = true;
268
268
  this._watch.applyValues();
269
- this.context.physics.updateBody(this, this._watch.positionChanged, this._watch.rotationChanged);
269
+ this.context.physics.engine?.updateBody(this, this._watch.positionChanged, this._watch.rotationChanged);
270
270
  this._watch.reset();
271
271
  }
272
272
  else this._watch?.syncValues();
@@ -275,10 +275,6 @@ export class Rigidbody extends Behaviour implements IRigidbody {
275
275
  }
276
276
  }
277
277
 
278
- private get body() {
279
- return this.context.physics.internal_getRigidbody(this);
280
- }
281
-
282
278
  public teleport(pt: { x: number, y: number, z: number }, localspace: boolean = true) {
283
279
  this._watch?.reset(true);
284
280
  if (localspace) this.gameObject.position.set(pt.x, pt.y, pt.z);
@@ -288,11 +284,11 @@ export class Rigidbody extends Behaviour implements IRigidbody {
288
284
  }
289
285
 
290
286
  public resetForces() {
291
- this.body?.resetForces(true);
287
+ this.context.physics.engine?.resetForces(this, true);
292
288
  }
293
289
 
294
290
  public resetTorques() {
295
- this.body?.resetTorques(true);
291
+ this.context.physics.engine?.resetTorques(this, true);
296
292
  }
297
293
 
298
294
  public resetVelocities() {
@@ -306,24 +302,24 @@ export class Rigidbody extends Behaviour implements IRigidbody {
306
302
  }
307
303
 
308
304
  public wakeUp() {
309
- this.body?.wakeUp();
305
+ this.context.physics.engine?.wakeup(this);
310
306
  }
311
307
 
312
308
  public applyForce(vec: Vector3, _rel?: THREE.Vector3) {
313
- this.body?.addForce(vec, true);
309
+ this.context.physics.engine?.addForce(this, vec, true);
314
310
  }
315
311
 
316
312
  public applyImpulse(vec: Vector3) {
317
- this.body?.applyImpulse(vec, true);
313
+ this.context.physics.engine?.applyImpulse(this, vec, true);
318
314
  }
319
315
 
320
316
  public setForce(x: number, y: number, z: number) {
321
- this.body?.resetForces(true);
322
- this.body?.addForce({ x, y, z }, true);
317
+ this.context.physics.engine?.resetForces(this, true);
318
+ this.context.physics.engine?.addForce(this, { x, y, z }, true);
323
319
  }
324
320
 
325
321
  public getVelocity(): Vector3 {
326
- const vel = this.body?.linvel();
322
+ const vel = this.context.physics.engine?.getLinearVelocity(this);
327
323
  if (!vel) return this._currentVelocity.set(0, 0, 0);
328
324
  this._currentVelocity.x = vel.x;
329
325
  this._currentVelocity.y = vel.y;
@@ -334,25 +330,25 @@ export class Rigidbody extends Behaviour implements IRigidbody {
334
330
  public setVelocity(x: number | Vector3, y?: number, z?: number) {
335
331
  if (x instanceof Vector3) {
336
332
  const vec = x;
337
- this.body?.setLinvel(vec, true);
333
+ this.context.physics.engine?.setLinearVelocity(this,vec, true);
338
334
  return;
339
335
  }
340
336
  if (y === undefined || z === undefined) return;
341
- this.body?.setLinvel({ x: x, y: y, z: z }, true);
337
+ this.context.physics.engine?.setLinearVelocity(this, { x: x, y: y, z: z }, true);
342
338
  }
343
339
 
344
340
  public setAngularVelocity(x: number | Vector3, y?: number, z?: number) {
345
341
  if (x instanceof Vector3) {
346
342
  const vec = x;
347
- this.body?.setAngvel(vec, true);
343
+ this.context.physics.engine?.setAngularVelocity(this, vec, true);
348
344
  return;
349
345
  }
350
346
  if (y === undefined || z === undefined) return;
351
- this.body?.setAngvel({ x: x, y: y, z: z }, true);
347
+ this.context.physics.engine?.setAngularVelocity(this, { x: x, y: y, z: z }, true);
352
348
  }
353
349
 
354
350
  public getAngularVelocity(): Vector3 {
355
- const vel = this.body?.angvel();
351
+ const vel = this.context.physics.engine?.getAngularVelocity(this);
356
352
  if (!vel) return this._currentVelocity.set(0, 0, 0);
357
353
  this._currentVelocity.x = vel.x;
358
354
  this._currentVelocity.y = vel.y;
@@ -381,13 +377,10 @@ export class Rigidbody extends Behaviour implements IRigidbody {
381
377
 
382
378
 
383
379
  private captureVelocity() {
384
- if (this.body) {
385
- const wp = getWorldPosition(this.gameObject);
386
- Rigidbody.tempPosition.copy(wp);
387
- const vel = wp.sub(this._lastPosition);
388
- this._lastPosition.copy(Rigidbody.tempPosition);
389
- this._smoothedVelocity.lerp(vel, this.context.time.deltaTime / .1);
390
- // this._smoothedVelocity.set(0, 1 / this.context.time.deltaTime, 0);
391
- }
380
+ const wp = getWorldPosition(this.gameObject);
381
+ Rigidbody.tempPosition.copy(wp);
382
+ const vel = wp.sub(this._lastPosition);
383
+ this._lastPosition.copy(Rigidbody.tempPosition);
384
+ this._smoothedVelocity.lerp(vel, this.context.time.deltaTime / .1);
392
385
  }
393
386
  }
@@ -17,6 +17,9 @@ import {
17
17
  Camera,
18
18
  Color,
19
19
  MeshStandardMaterial,
20
+ LinearEncoding,
21
+ sRGBEncoding,
22
+ MeshPhysicalMaterial,
20
23
  } from 'three';
21
24
  import * as fflate from 'three/examples/jsm/libs/fflate.module.js';
22
25
 
@@ -400,13 +403,7 @@ class USDZExporter {
400
403
 
401
404
  async parse( scene, options: USDZExporterOptions = new USDZExporterOptions() ) {
402
405
 
403
- options = Object.assign( {
404
- ar: {
405
- anchoring: { type: 'plane' },
406
- planeAnchoring: { alignment: 'horizontal' }
407
- },
408
- extensions: []
409
- }, options );
406
+ options = Object.assign( new USDZExporterOptions(), options );
410
407
 
411
408
  this.sceneAnchoringOptions = options;
412
409
  // @ts-ignore
@@ -598,7 +595,7 @@ function parseDocument( context: USDZExporterContext ) {
598
595
 
599
596
  writer.appendLine( `token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"` );
600
597
  if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
601
- writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"` );
598
+ writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.planeAnchoring.alignment}"` );
602
599
  // bit hacky as we don't have a callback here yet. Relies on the fact that the image is named identical in the ImageTracking extension.
603
600
  if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
604
601
  writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
@@ -679,21 +676,38 @@ function copyTexture( texture ) {
679
676
 
680
677
  const geometry = new PlaneGeometry( 2, 2, 1, 1 );
681
678
  const material = new ShaderMaterial( {
682
- uniforms: { blitTexture: new Uniform( texture ) },
683
- vertexShader: `
684
- varying vec2 vUv;
685
- void main(){
686
- vUv = uv;
687
- gl_Position = vec4(position.xy * 1.0,0.,.999999);
688
- }`,
689
- fragmentShader: `
690
- uniform sampler2D blitTexture;
691
- varying vec2 vUv;
692
- void main(){
693
- gl_FragColor = vec4(vUv.xy, 0, 1);
694
- gl_FragColor = texture2D( blitTexture, vUv);
695
- }`
696
- } );
679
+ uniforms: {
680
+ blitTexture: new Uniform( texture ),
681
+ },
682
+ defines: {
683
+ IS_SRGB: texture.encoding == sRGBEncoding,
684
+ },
685
+ vertexShader: `
686
+ varying vec2 vUv;
687
+ void main(){
688
+ vUv = uv;
689
+ vUv.y = 1. - vUv.y;
690
+ gl_Position = vec4(position.xy * 1.0,0.,.999999);
691
+ }`,
692
+ fragmentShader: `
693
+ uniform sampler2D blitTexture;
694
+ varying vec2 vUv;
695
+
696
+ // took from threejs 05fc79cd52b79e8c3e8dec1e7dca72c5c39983a4
697
+ vec4 conv_LinearTosRGB( in vec4 value ) {
698
+ 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 );
699
+ }
700
+
701
+ void main(){
702
+ gl_FragColor = vec4(vUv.xy, 0, 1);
703
+
704
+ #ifdef IS_SRGB
705
+ gl_FragColor = conv_LinearTosRGB( texture2D( blitTexture, vUv) );
706
+ #else
707
+ gl_FragColor = texture2D( blitTexture, vUv);
708
+ #endif
709
+ }`
710
+ } );
697
711
 
698
712
  const mesh = new Mesh( geometry, material );
699
713
  mesh.frustumCulled = false;
@@ -705,7 +719,9 @@ function copyTexture( texture ) {
705
719
  renderer.clear();
706
720
  renderer.render( scene, cam );
707
721
 
708
- return new Texture( renderer.domElement );
722
+ const tex = new Texture( renderer.domElement );
723
+ tex.encoding = texture.encoding;
724
+ return tex;
709
725
 
710
726
  }
711
727
 
@@ -825,7 +841,10 @@ export function buildXform( model, writer, context ) {
825
841
  }
826
842
 
827
843
  if ( geometry )
828
- writer.beginBlock( `def Xform "${name}" (prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>)` );
844
+ writer.beginBlock( `def Xform "${name}" (
845
+ prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>
846
+ prepend apiSchemas = ["MaterialBindingAPI"]
847
+ )` );
829
848
  else if ( camera )
830
849
  writer.beginBlock( `def Camera "${name}"` );
831
850
  else
@@ -929,11 +948,11 @@ function buildMesh( geometry ) {
929
948
  )
930
949
  point3f[] points = [${buildVector3Array( attributes.position, count )}]
931
950
  ${attributes.uv ?
932
- `float2[] primvars:st = [${buildVector2Array( attributes.uv, count )}] (
951
+ `texCoord2f[] primvars:st = [${buildVector2Array( attributes.uv, count )}] (
933
952
  interpolation = "vertex"
934
953
  )` : '' }
935
954
  ${attributes.uv2 ?
936
- `float2[] primvars:st2 = [${buildVector2Array( attributes.uv2, count )}] (
955
+ `texCoord2f[] primvars:st2 = [${buildVector2Array( attributes.uv2, count )}] (
937
956
  interpolation = "vertex"
938
957
  )` : '' }
939
958
  uniform token subdivisionScheme = "none"
@@ -1051,7 +1070,7 @@ ${array.join( '' )}
1051
1070
 
1052
1071
  }
1053
1072
 
1054
- function buildMaterial( material, textures ) {
1073
+ function buildMaterial( material: MeshStandardMaterial, textures ) {
1055
1074
 
1056
1075
  // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
1057
1076
 
@@ -1090,6 +1109,13 @@ function buildMaterial( material, textures ) {
1090
1109
  const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
1091
1110
  const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
1092
1111
 
1112
+ const rawTextureExtra = `(
1113
+ colorSpace = "Raw"
1114
+ )`;
1115
+ const needsTextureScale = mapType !== 'normal' && (color && (color.r !== 1 || color.g !== 1 || color.b !== 1 || opacity !== 1)) || false;
1116
+ const needsNormalScaleAndBias = mapType === 'normal';
1117
+ const normalScaleValueString = (material.normalScale ? material.normalScale.x * 2 : 2).toFixed( PRECISION );
1118
+
1093
1119
  return `
1094
1120
  ${needsTextureTransform ? `def Shader "Transform2d_${mapType}" (
1095
1121
  sdrMetadata = {
@@ -1107,9 +1133,15 @@ function buildMaterial( material, textures ) {
1107
1133
  def Shader "Texture_${texture.id}_${mapType}"
1108
1134
  {
1109
1135
  uniform token info:id = "UsdUVTexture"
1110
- asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
1136
+ asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@ ${mapType === 'normal' ? rawTextureExtra : ''}
1111
1137
  float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
1138
+ ${needsTextureScale ? `
1112
1139
  float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})
1140
+ ` : `` }
1141
+ ${needsNormalScaleAndBias ? `
1142
+ float4 inputs:scale = (${normalScaleValueString}, ${normalScaleValueString}, ${normalScaleValueString}, 1)
1143
+ float4 inputs:bias = (-1, -1, -1, 0)
1144
+ ` : `` }
1113
1145
  token inputs:wrapS = "${wrapS}"
1114
1146
  token inputs:wrapT = "${wrapT}"
1115
1147
  float outputs:r
@@ -1221,7 +1253,7 @@ function buildMaterial( material, textures ) {
1221
1253
 
1222
1254
  }
1223
1255
 
1224
- if ( material.isMeshPhysicalMaterial ) {
1256
+ if ( material instanceof MeshPhysicalMaterial ) {
1225
1257
 
1226
1258
  inputs.push( `${pad}float inputs:clearcoat = ${material.clearcoat}` );
1227
1259
  inputs.push( `${pad}float inputs:clearcoatRoughness = ${material.clearcoatRoughness}` );
@@ -6,11 +6,11 @@ import { IPointerClickHandler } from "../../../../ui/PointerEvents";
6
6
  import { RegisteredAnimationInfo, UsdzAnimation } from "../Animation";
7
7
  import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../../../../../engine/engine_three_utils";
8
8
 
9
- import { Object3D, Material, Vector3, Quaternion } from "three";
9
+ import { Object3D, Material, Vector3, Quaternion, AnimationAction } from "three";
10
10
  import { USDObject } from "../../ThreeUSDZExporter";
11
11
 
12
12
  import { BehaviorExtension, UsdzBehaviour } from "./Behaviour";
13
- import { ActionBuilder, ActionModel, BehaviorModel, MotionType, Space, TriggerBuilder } from "./BehavioursBuilder";
13
+ import { ActionBuilder, ActionModel, BehaviorModel, IBehaviorElement, MotionType, Space, TriggerBuilder } from "./BehavioursBuilder";
14
14
 
15
15
  export class ChangeTransformOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
16
16
 
@@ -417,6 +417,12 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
417
417
 
418
418
  @serializable()
419
419
  stateName?: string;
420
+
421
+ @serializable()
422
+ stateNameAfterPlaying?: string;
423
+
424
+ @serializable()
425
+ loopAfterPlaying: boolean = false;
420
426
 
421
427
  onPointerClick() {
422
428
  if (!this.target) return;
@@ -425,22 +431,53 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
425
431
  }
426
432
 
427
433
  private selfModel: any;
428
- private registeredAnimationModel: any;
429
- private registeredAnimation?: RegisteredAnimationInfo;
434
+
435
+ private stateAnimationModel: any;
436
+ private stateAnimation?: RegisteredAnimationInfo;
437
+
438
+ private stateAfterPlayingAnimationModel: any;
439
+ private stateAfterPlayingAnimation?: RegisteredAnimationInfo;
430
440
 
431
441
  createBehaviours(_ext, model, _context) {
432
442
  if (model.uuid === this.gameObject.uuid)
433
443
  this.selfModel = model;
434
444
  }
435
445
 
446
+ private static animationActions: ActionModel[] = [];
447
+
448
+ onAfterHierarchy() {
449
+ PlayAnimationOnClick.animationActions = [];
450
+ }
451
+
436
452
  afterCreateDocument(ext, context) {
437
- if (!this.registeredAnimation || !this.registeredAnimationModel) return;
453
+ if (!this.stateAnimation || !this.stateAnimationModel) return;
438
454
  const document = context.document;
439
455
  document.traverse(model => {
440
- if (model.uuid === this.target?.uuid && this.registeredAnimation) {
456
+ // TODO we should probably check if a startAnimationAction already exists, and not have duplicates of identical ones;
457
+ // looks like otherwise we're getting some animation overlap that doesn't look good.
458
+ if (model.uuid === this.target?.uuid && this.stateAnimation) {
459
+ const sequence: IBehaviorElement[] = [];
460
+ let startAction = PlayAnimationOnClick.animationActions.find(a => a.affectedObjects == model && a.start == this.stateAnimation!.start && a.duration == this.stateAnimation!.duration);
461
+ if (!startAction) {
462
+ startAction = ActionBuilder.startAnimationAction(model, this.stateAnimation.start, this.stateAnimation.duration) as ActionModel;
463
+ PlayAnimationOnClick.animationActions.push(startAction);
464
+ }
465
+ sequence.push(startAction);
466
+
467
+ if (this.stateAfterPlayingAnimation && this.stateAfterPlayingAnimationModel) {
468
+ let endAction = PlayAnimationOnClick.animationActions.find(a => a.affectedObjects == model && a.start == this.stateAfterPlayingAnimation!.start && a.duration == this.stateAfterPlayingAnimation!.duration);
469
+ if (!endAction) {
470
+ endAction = ActionBuilder.startAnimationAction(model, this.stateAfterPlayingAnimation.start, this.stateAfterPlayingAnimation.duration) as ActionModel;
471
+ PlayAnimationOnClick.animationActions.push(endAction);
472
+ }
473
+ const idleAnim = ActionBuilder.sequence(endAction);
474
+ if (this.loopAfterPlaying)
475
+ idleAnim.makeLooping();
476
+ sequence.push(idleAnim);
477
+ }
441
478
  const playAnimationOnTap = new BehaviorModel("tap " + this.name + " for " + this.stateName + " on " + this.target?.name,
442
479
  TriggerBuilder.tapTrigger(this.selfModel),
443
- ActionBuilder.startAnimationAction(model, this.registeredAnimation.start, this.registeredAnimation.duration)
480
+ ActionBuilder.sequence(...sequence)
444
481
  );
445
482
  ext.addBehavior(playAnimationOnTap);
446
483
  }
@@ -449,9 +486,14 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
449
486
 
450
487
  createAnimation(ext, model, _context) {
451
488
  if (this.target && this.animator) {
489
+
452
490
  const state = this.animator?.runtimeAnimatorController?.findState(this.stateName);
453
- this.registeredAnimationModel = model;
454
- this.registeredAnimation = ext.registerAnimation(this.target, state?.motion.clip);
491
+ this.stateAnimationModel = model;
492
+ this.stateAnimation = ext.registerAnimation(this.target, state?.motion.clip);
493
+
494
+ const stateAfter = this.animator?.runtimeAnimatorController?.findState(this.stateNameAfterPlaying);
495
+ this.stateAfterPlayingAnimationModel = model;
496
+ this.stateAfterPlayingAnimation = ext.registerAnimation(this.target, stateAfter?.motion.clip);
455
497
  }
456
498
  }
457
499
 
@@ -191,7 +191,8 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
191
191
 
192
192
  const uiobject = this.shadowComponent;
193
193
  if (!uiobject) return;
194
- this._parentRectTransform = GameObject.getComponentInParent(this.gameObject.parent!, RectTransform) as RectTransform;
194
+ if (!this.gameObject.parent) return;
195
+ this._parentRectTransform = GameObject.getComponentInParent(this.gameObject.parent, RectTransform) as RectTransform;
195
196
 
196
197
  this._transformNeedsUpdate = false;
197
198
  this.lastMatrix.copy(this.gameObject.matrix);
@@ -228,7 +229,7 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
228
229
  else {
229
230
  // We have to rotate the canvas when it's in worldspace
230
231
  const canvas = this.Root as any as ICanvas;
231
- if (!canvas.screenspace) uiobject.rotation.y = Math.PI;
232
+ if (canvas && !canvas.screenspace) uiobject.rotation.y = Math.PI;
232
233
  }
233
234
 
234
235
  // iterate other components on this object that might need to know about the transform change