@needle-tools/engine 3.4.0-alpha → 3.5.1-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 (79) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/needle-engine.js +59388 -59097
  3. package/dist/needle-engine.min.js +416 -391
  4. package/dist/needle-engine.umd.cjs +388 -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 +9 -4
  9. package/lib/engine/engine_context.js +57 -32
  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/ReflectionProbe.js +16 -7
  33. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  34. package/lib/engine-components/Renderer.js +3 -4
  35. package/lib/engine-components/Renderer.js.map +1 -1
  36. package/lib/engine-components/RigidBody.d.ts +0 -1
  37. package/lib/engine-components/RigidBody.js +24 -30
  38. package/lib/engine-components/RigidBody.js.map +1 -1
  39. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +52 -26
  40. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  41. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +8 -2
  42. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +44 -7
  43. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  44. package/lib/engine-components/ui/Canvas.js +29 -16
  45. package/lib/engine-components/ui/Canvas.js.map +1 -1
  46. package/lib/engine-components/ui/Layout.js +10 -5
  47. package/lib/engine-components/ui/Layout.js.map +1 -1
  48. package/lib/engine-components/ui/RectTransform.js +8 -3
  49. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  50. package/lib/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +1 -1
  52. package/plugins/vite/config.js +2 -1
  53. package/plugins/vite/defines.js +30 -0
  54. package/plugins/vite/dependency-watcher.js +173 -0
  55. package/plugins/vite/editor-connection.js +37 -39
  56. package/plugins/vite/index.js +5 -1
  57. package/plugins/vite/reload.js +3 -1
  58. package/src/engine/api.ts +1 -0
  59. package/src/engine/codegen/register_types.js +2 -2
  60. package/src/engine/engine_context.ts +75 -42
  61. package/src/engine/engine_context_registry.ts +13 -6
  62. package/src/engine/engine_element.ts +2 -1
  63. package/src/engine/engine_element_loading.ts +2 -3
  64. package/src/engine/engine_input.ts +2 -2
  65. package/src/engine/engine_physics.ts +25 -1020
  66. package/src/engine/engine_physics.types.ts +1 -3
  67. package/src/engine/engine_physics_rapier.ts +1127 -0
  68. package/src/engine/engine_types.ts +66 -4
  69. package/src/engine-components/Collider.ts +6 -6
  70. package/src/engine-components/Joints.ts +2 -2
  71. package/src/engine-components/ReflectionProbe.ts +17 -7
  72. package/src/engine-components/Renderer.ts +5 -5
  73. package/src/engine-components/RendererLightmap.ts +1 -1
  74. package/src/engine-components/RigidBody.ts +24 -31
  75. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +58 -29
  76. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +51 -9
  77. package/src/engine-components/ui/Canvas.ts +29 -16
  78. package/src/engine-components/ui/Layout.ts +10 -5
  79. package/src/engine-components/ui/RectTransform.ts +9 -4
@@ -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
  }
@@ -129,17 +129,28 @@ export class ReflectionProbe extends Behaviour {
129
129
  // otherwise some renderers outside of the probe will be affected or vice versa
130
130
  for (let i = 0; i < _rend.sharedMaterials.length; i++) {
131
131
  const material = _rend.sharedMaterials[i];
132
- if (!material) continue;
133
- if (material["envMap"] === undefined) continue;
134
- if (material["envMap"] === this.texture) {
132
+ if (!material) {
135
133
  continue;
136
134
  }
135
+ if (material["envMap"] === undefined) {
136
+ continue;
137
+ }
138
+
137
139
  let cached = rendererCache[i];
138
140
 
139
141
  // make sure we have the currently assigned material cached (and an up to date clone of that)
140
142
  // TODO: this is causing problems with progressive textures sometimes (depending on the order) when the version changes and we re-create materials over and over. We might want to just set the material envmap instead of making a clone
141
- if (!cached || cached.material !== material || cached.copy.version !== material.version) {
142
- if(debug) console.log("Cloning material", material.name, material.version);
143
+ let isCachedInstance = material === cached?.copy;
144
+ let hasChanged = !cached || cached.material.uuid !== material.uuid || cached.copy.version !== material.version;
145
+ if (!isCachedInstance && hasChanged) {
146
+ if (debug) {
147
+ let reason = "";
148
+ if (!cached) reason = "not cached";
149
+ else if (cached.material !== material) reason = "reference changed; cached instance?: " + isCachedInstance;
150
+ else if (cached.copy.version !== material.version) reason = "version changed";
151
+ console.warn("Cloning material", material.name, material.version, "Reason:", reason, "\n", material.uuid, "\n", cached?.copy.uuid, "\n", _rend.name);
152
+ }
153
+
143
154
  const clone = material.clone();
144
155
  clone.version = material.version;
145
156
 
@@ -158,8 +169,7 @@ export class ReflectionProbe extends Behaviour {
158
169
  clone[$reflectionProbeKey] = this;
159
170
  clone[$originalMaterial] = material;
160
171
 
161
- if (debug)
162
- console.log("Set reflection", _rend.name, _rend.guid);
172
+ if (debug) console.log("Set reflection", _rend.name, _rend.guid);
163
173
  }
164
174
 
165
175
  /** this is the material that we copied and that has the reflection probe */
@@ -605,15 +605,15 @@ export class Renderer extends Behaviour implements IRenderer {
605
605
  }
606
606
  }
607
607
 
608
- }
608
+
609
+ if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
610
+ this._reflectionProbe.onSet(this);
611
+ }
609
612
 
613
+ }
610
614
  onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
611
615
 
612
616
  this.loadProgressiveTextures(material);
613
- // TODO: we might want to just set the material.envMap for one material during rendering instead of cloning the material
614
- if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
615
- this._reflectionProbe.onSet(this);
616
- }
617
617
 
618
618
  if (material.envMapIntensity !== undefined) {
619
619
  const factor = this.hasLightmap ? Math.PI : 1;
@@ -1,7 +1,7 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import * as THREE from "three";
3
3
  import { Texture } from "three";
4
- import { Context, OnBeforeRenderCallback } from "../engine/engine_setup";
4
+ import { Context, OnRenderCallback } from "../engine/engine_setup";
5
5
 
6
6
  // this component is automatically added by the Renderer if the object has lightmap uvs AND we have a lightmap
7
7
  // for multimaterial objects GLTF exports a "Group" with the renderer component
@@ -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,8 @@ import {
17
17
  Camera,
18
18
  Color,
19
19
  MeshStandardMaterial,
20
+ sRGBEncoding,
21
+ MeshPhysicalMaterial,
20
22
  } from 'three';
21
23
  import * as fflate from 'three/examples/jsm/libs/fflate.module.js';
22
24
 
@@ -400,13 +402,7 @@ class USDZExporter {
400
402
 
401
403
  async parse( scene, options: USDZExporterOptions = new USDZExporterOptions() ) {
402
404
 
403
- options = Object.assign( {
404
- ar: {
405
- anchoring: { type: 'plane' },
406
- planeAnchoring: { alignment: 'horizontal' }
407
- },
408
- extensions: []
409
- }, options );
405
+ options = Object.assign( new USDZExporterOptions(), options );
410
406
 
411
407
  this.sceneAnchoringOptions = options;
412
408
  // @ts-ignore
@@ -598,7 +594,7 @@ function parseDocument( context: USDZExporterContext ) {
598
594
 
599
595
  writer.appendLine( `token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"` );
600
596
  if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
601
- writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"` );
597
+ writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.planeAnchoring.alignment}"` );
602
598
  // 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
599
  if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
604
600
  writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
@@ -679,21 +675,38 @@ function copyTexture( texture ) {
679
675
 
680
676
  const geometry = new PlaneGeometry( 2, 2, 1, 1 );
681
677
  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
- } );
678
+ uniforms: {
679
+ blitTexture: new Uniform( texture ),
680
+ },
681
+ defines: {
682
+ IS_SRGB: texture.encoding == sRGBEncoding,
683
+ },
684
+ vertexShader: `
685
+ varying vec2 vUv;
686
+ void main(){
687
+ vUv = uv;
688
+ vUv.y = 1. - vUv.y;
689
+ gl_Position = vec4(position.xy * 1.0,0.,.999999);
690
+ }`,
691
+ fragmentShader: `
692
+ uniform sampler2D blitTexture;
693
+ varying vec2 vUv;
694
+
695
+ // took from threejs 05fc79cd52b79e8c3e8dec1e7dca72c5c39983a4
696
+ vec4 conv_LinearTosRGB( in vec4 value ) {
697
+ 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 );
698
+ }
699
+
700
+ void main(){
701
+ gl_FragColor = vec4(vUv.xy, 0, 1);
702
+
703
+ #ifdef IS_SRGB
704
+ gl_FragColor = conv_LinearTosRGB( texture2D( blitTexture, vUv) );
705
+ #else
706
+ gl_FragColor = texture2D( blitTexture, vUv);
707
+ #endif
708
+ }`
709
+ } );
697
710
 
698
711
  const mesh = new Mesh( geometry, material );
699
712
  mesh.frustumCulled = false;
@@ -705,7 +718,9 @@ function copyTexture( texture ) {
705
718
  renderer.clear();
706
719
  renderer.render( scene, cam );
707
720
 
708
- return new Texture( renderer.domElement );
721
+ const tex = new Texture( renderer.domElement );
722
+ tex.encoding = texture.encoding;
723
+ return tex;
709
724
 
710
725
  }
711
726
 
@@ -825,7 +840,10 @@ export function buildXform( model, writer, context ) {
825
840
  }
826
841
 
827
842
  if ( geometry )
828
- writer.beginBlock( `def Xform "${name}" (prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>)` );
843
+ writer.beginBlock( `def Xform "${name}" (
844
+ prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>
845
+ prepend apiSchemas = ["MaterialBindingAPI"]
846
+ )` );
829
847
  else if ( camera )
830
848
  writer.beginBlock( `def Camera "${name}"` );
831
849
  else
@@ -929,11 +947,11 @@ function buildMesh( geometry ) {
929
947
  )
930
948
  point3f[] points = [${buildVector3Array( attributes.position, count )}]
931
949
  ${attributes.uv ?
932
- `float2[] primvars:st = [${buildVector2Array( attributes.uv, count )}] (
950
+ `texCoord2f[] primvars:st = [${buildVector2Array( attributes.uv, count )}] (
933
951
  interpolation = "vertex"
934
952
  )` : '' }
935
953
  ${attributes.uv2 ?
936
- `float2[] primvars:st2 = [${buildVector2Array( attributes.uv2, count )}] (
954
+ `texCoord2f[] primvars:st2 = [${buildVector2Array( attributes.uv2, count )}] (
937
955
  interpolation = "vertex"
938
956
  )` : '' }
939
957
  uniform token subdivisionScheme = "none"
@@ -1051,7 +1069,7 @@ ${array.join( '' )}
1051
1069
 
1052
1070
  }
1053
1071
 
1054
- function buildMaterial( material, textures ) {
1072
+ function buildMaterial( material: MeshStandardMaterial, textures ) {
1055
1073
 
1056
1074
  // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
1057
1075
 
@@ -1090,6 +1108,10 @@ function buildMaterial( material, textures ) {
1090
1108
  const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
1091
1109
  const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
1092
1110
 
1111
+ const needsTextureScale = mapType !== 'normal' && (color && (color.r !== 1 || color.g !== 1 || color.b !== 1 || opacity !== 1)) || false;
1112
+ const needsNormalScaleAndBias = mapType === 'normal';
1113
+ const normalScaleValueString = (material.normalScale ? material.normalScale.x * 2 : 2).toFixed( PRECISION );
1114
+
1093
1115
  return `
1094
1116
  ${needsTextureTransform ? `def Shader "Transform2d_${mapType}" (
1095
1117
  sdrMetadata = {
@@ -1108,8 +1130,15 @@ function buildMaterial( material, textures ) {
1108
1130
  {
1109
1131
  uniform token info:id = "UsdUVTexture"
1110
1132
  asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
1133
+ token inputs:sourceColorSpace = "${ texture.colorSpace === 'srgb' ? 'sRGB' : 'raw' }"
1111
1134
  float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
1135
+ ${needsTextureScale ? `
1112
1136
  float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})
1137
+ ` : `` }
1138
+ ${needsNormalScaleAndBias ? `
1139
+ float4 inputs:scale = (${normalScaleValueString}, ${normalScaleValueString}, ${normalScaleValueString}, 1)
1140
+ float4 inputs:bias = (-1, -1, -1, 0)
1141
+ ` : `` }
1113
1142
  token inputs:wrapS = "${wrapS}"
1114
1143
  token inputs:wrapT = "${wrapT}"
1115
1144
  float outputs:r
@@ -1221,7 +1250,7 @@ function buildMaterial( material, textures ) {
1221
1250
 
1222
1251
  }
1223
1252
 
1224
- if ( material.isMeshPhysicalMaterial ) {
1253
+ if ( material instanceof MeshPhysicalMaterial ) {
1225
1254
 
1226
1255
  inputs.push( `${pad}float inputs:clearcoat = ${material.clearcoat}` );
1227
1256
  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