@needle-tools/engine 2.35.5-pre → 2.36.0-pre

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 (115) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/needle-engine.d.ts +160 -110
  3. package/dist/needle-engine.js +370 -365
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +61 -56
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/debug/debug_overlay.js +12 -1
  8. package/lib/engine/debug/debug_overlay.js.map +1 -1
  9. package/lib/engine/engine_element_loading.js +1 -1
  10. package/lib/engine/engine_element_loading.js.map +1 -1
  11. package/lib/engine/engine_gameobject.d.ts +1 -0
  12. package/lib/engine/engine_gameobject.js +13 -1
  13. package/lib/engine/engine_gameobject.js.map +1 -1
  14. package/lib/engine/engine_gltf_builtin_components.js +4 -0
  15. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  16. package/lib/engine/engine_mainloop_utils.d.ts +1 -1
  17. package/lib/engine/engine_mainloop_utils.js +7 -3
  18. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  19. package/lib/engine/engine_physics.d.ts +29 -28
  20. package/lib/engine/engine_physics.js +85 -86
  21. package/lib/engine/engine_physics.js.map +1 -1
  22. package/lib/engine/engine_serialization_core.js +14 -6
  23. package/lib/engine/engine_serialization_core.js.map +1 -1
  24. package/lib/engine/engine_setup.js +1 -1
  25. package/lib/engine/engine_setup.js.map +1 -1
  26. package/lib/engine/engine_time.d.ts +1 -0
  27. package/lib/engine/engine_time.js +1 -0
  28. package/lib/engine/engine_time.js.map +1 -1
  29. package/lib/engine/engine_types.d.ts +1 -0
  30. package/lib/engine/engine_types.js.map +1 -1
  31. package/lib/engine/engine_typestore.d.ts +1 -0
  32. package/lib/engine/engine_typestore.js +1 -0
  33. package/lib/engine/engine_typestore.js.map +1 -1
  34. package/lib/engine/engine_utils.js +1 -1
  35. package/lib/engine/engine_utils.js.map +1 -1
  36. package/lib/engine/extensions/NEEDLE_animator_controller_model.js.map +1 -1
  37. package/lib/engine/extensions/NEEDLE_techniques_webgl.js +5 -0
  38. package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
  39. package/lib/engine-components/Animation.d.ts +5 -1
  40. package/lib/engine-components/Animation.js +21 -0
  41. package/lib/engine-components/Animation.js.map +1 -1
  42. package/lib/engine-components/AnimatorController.d.ts +1 -0
  43. package/lib/engine-components/AnimatorController.js +14 -7
  44. package/lib/engine-components/AnimatorController.js.map +1 -1
  45. package/lib/engine-components/BoxHelperComponent.d.ts +2 -2
  46. package/lib/engine-components/BoxHelperComponent.js +27 -9
  47. package/lib/engine-components/BoxHelperComponent.js.map +1 -1
  48. package/lib/engine-components/Component.d.ts +2 -1
  49. package/lib/engine-components/Component.js +11 -5
  50. package/lib/engine-components/Component.js.map +1 -1
  51. package/lib/engine-components/GroundProjection.d.ts +2 -0
  52. package/lib/engine-components/GroundProjection.js +18 -6
  53. package/lib/engine-components/GroundProjection.js.map +1 -1
  54. package/lib/engine-components/ReflectionProbe.d.ts +22 -0
  55. package/lib/engine-components/ReflectionProbe.js +134 -0
  56. package/lib/engine-components/ReflectionProbe.js.map +1 -0
  57. package/lib/engine-components/Renderer.d.ts +13 -2
  58. package/lib/engine-components/Renderer.js +96 -45
  59. package/lib/engine-components/Renderer.js.map +1 -1
  60. package/lib/engine-components/WebARSessionRoot.d.ts +7 -7
  61. package/lib/engine-components/WebARSessionRoot.js +7 -7
  62. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  63. package/lib/engine-components/WebXR.d.ts +9 -8
  64. package/lib/engine-components/WebXR.js +40 -24
  65. package/lib/engine-components/WebXR.js.map +1 -1
  66. package/lib/engine-components/WebXRAvatar.d.ts +4 -5
  67. package/lib/engine-components/WebXRAvatar.js +9 -8
  68. package/lib/engine-components/WebXRAvatar.js.map +1 -1
  69. package/lib/engine-components/WebXRController.d.ts +21 -21
  70. package/lib/engine-components/WebXRController.js +79 -63
  71. package/lib/engine-components/WebXRController.js.map +1 -1
  72. package/lib/engine-components/WebXRGrabRendering.d.ts +3 -3
  73. package/lib/engine-components/WebXRGrabRendering.js +2 -2
  74. package/lib/engine-components/WebXRGrabRendering.js.map +1 -1
  75. package/lib/engine-components/WebXRSync.d.ts +8 -8
  76. package/lib/engine-components/WebXRSync.js +15 -15
  77. package/lib/engine-components/WebXRSync.js.map +1 -1
  78. package/lib/engine-components/codegen/components.d.ts +1 -0
  79. package/lib/engine-components/codegen/components.js +1 -0
  80. package/lib/engine-components/codegen/components.js.map +1 -1
  81. package/lib/engine-components/ui/EventSystem.d.ts +1 -0
  82. package/lib/engine-components/ui/EventSystem.js +21 -1
  83. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  84. package/package.json +1 -1
  85. package/src/engine/codegen/register_types.js +293 -0
  86. package/src/engine/debug/debug_overlay.ts +9 -2
  87. package/src/engine/engine_element_loading.ts +1 -1
  88. package/src/engine/engine_gameobject.ts +17 -4
  89. package/src/engine/engine_gltf_builtin_components.ts +5 -1
  90. package/src/engine/engine_mainloop_utils.ts +7 -3
  91. package/src/engine/engine_physics.ts +130 -130
  92. package/src/engine/engine_serialization_core.ts +14 -7
  93. package/src/engine/engine_setup.ts +1 -1
  94. package/src/engine/engine_time.ts +2 -0
  95. package/src/engine/engine_types.ts +1 -0
  96. package/src/engine/engine_typestore.ts +2 -0
  97. package/src/engine/engine_utils.ts +3 -2
  98. package/src/engine/extensions/EXT_texture_exr.js +1 -1
  99. package/src/engine/extensions/NEEDLE_animator_controller_model.ts +2 -1
  100. package/src/engine/extensions/NEEDLE_techniques_webgl.ts +7 -0
  101. package/src/engine-components/Animation.ts +16 -1
  102. package/src/engine-components/AnimatorController.ts +19 -9
  103. package/src/engine-components/BoxHelperComponent.ts +29 -9
  104. package/src/engine-components/Component.ts +11 -5
  105. package/src/engine-components/GroundProjection.ts +22 -7
  106. package/src/engine-components/ReflectionProbe.ts +141 -0
  107. package/src/engine-components/Renderer.ts +796 -737
  108. package/src/engine-components/WebARSessionRoot.ts +16 -16
  109. package/src/engine-components/WebXR.ts +53 -48
  110. package/src/engine-components/WebXRAvatar.ts +16 -16
  111. package/src/engine-components/WebXRController.ts +129 -107
  112. package/src/engine-components/WebXRGrabRendering.ts +6 -6
  113. package/src/engine-components/WebXRSync.ts +20 -20
  114. package/src/engine-components/codegen/components.ts +1 -0
  115. package/src/engine-components/ui/EventSystem.ts +26 -3
@@ -102,6 +102,7 @@ export declare interface ILight extends IComponent {
102
102
 
103
103
  export declare interface ISharedMaterials {
104
104
  [num:number] : Material;
105
+ get length() : number;
105
106
  }
106
107
 
107
108
  export declare interface IRenderer extends IComponent {
@@ -18,4 +18,6 @@ class _TypeStore {
18
18
  }
19
19
  }
20
20
 
21
+ export const $BuiltInTypeFlag = Symbol("BuiltInType");
22
+
21
23
  export const TypeStore = new _TypeStore();
@@ -226,6 +226,7 @@ export interface IWatch {
226
226
  dispose();
227
227
  }
228
228
 
229
+
229
230
  // TODO: make it possible to add multiple watches to the same object property
230
231
  class WatchImpl implements IWatch {
231
232
  subscribeWrite(callback: WriteCallback) {
@@ -236,14 +237,14 @@ class WatchImpl implements IWatch {
236
237
  constructor(object: object, prop: string) {
237
238
  this._object = object;
238
239
  this._prop = prop;
239
- this._wrapperProp = "__$_NEEDLE_" + prop;
240
+ this._wrapperProp = Symbol("$" + prop);
240
241
  this.apply();
241
242
  }
242
243
 
243
244
  private _applied: boolean = false;
244
245
  private _object: any;
245
246
  private _prop: string;
246
- private _wrapperProp: string;
247
+ private _wrapperProp: symbol;
247
248
 
248
249
  apply() {
249
250
  if (this._applied) return;
@@ -32,7 +32,7 @@ export class EXT_texture_exr {
32
32
  const extension = textureDef.extensions[ name ];
33
33
 
34
34
  // TODO should the loader be cached here?
35
- let loader = new EXRLoader(parser.options.manager);
35
+ const loader = new EXRLoader(parser.options.manager);
36
36
 
37
37
  if(debug) console.log("EXT_texture_exr.loadTexture", extension, loader);
38
38
 
@@ -97,7 +97,7 @@ export declare type Motion = {
97
97
  action_loopback?: AnimationAction,
98
98
  }
99
99
 
100
- export function createMotion(name:string, id? : InstantiateIdProvider): Motion {
100
+ export function createMotion(name: string, id?: InstantiateIdProvider): Motion {
101
101
  return {
102
102
  name: "",
103
103
  isLooping: false,
@@ -123,6 +123,7 @@ export declare type Transition = {
123
123
  hasExitTime: number,
124
124
  destinationState: number | State,
125
125
  conditions: Condition[],
126
+ // isAny?: boolean
126
127
  }
127
128
 
128
129
  export declare type Condition = {
@@ -203,6 +203,9 @@ export class CustomShader extends RawShaderMaterial {
203
203
  if (this.uniforms["_TimeParameters"]) {
204
204
  this.uniforms["_TimeParameters"].value = context.rendererData.timeVec4;
205
205
  }
206
+ else if (this.uniforms["_Time"]) {
207
+ this.uniforms["_Time"].value = context.rendererData.timeVec4;
208
+ }
206
209
 
207
210
  const mainLight: ILight | null = context.mainLight;
208
211
  if (mainLight) {
@@ -327,6 +330,10 @@ export class NEEDLE_techniques_webgl implements GLTFLoaderPlugin {
327
330
 
328
331
  const uniforms: {} = {};
329
332
  const techniqueUniforms = technique.uniforms;
333
+
334
+ if (vert.includes("_Time"))
335
+ uniforms["_Time"] = { value: new THREE.Vector4(0, 0, 0, 0) };
336
+
330
337
  for (const u in techniqueUniforms) {
331
338
  const uniformName = u;
332
339
  // const uniformValues = techniqueUniforms[u];
@@ -1,9 +1,10 @@
1
1
  import { Behaviour } from "./Component";
2
2
  import * as THREE from 'three'
3
- import { AnimationAction, AnimationClip } from "three";
3
+ import { AnimationAction, AnimationClip, Vector2 } from "three";
4
4
  import { MixerEvent } from "./Animator";
5
5
  import { serializeable } from "../engine/engine_serialization_decorator";
6
6
  import { InstancingUtil } from "../engine/engine_instancing";
7
+ import { Mathf } from "../engine/engine_math";
7
8
 
8
9
  export declare class PlayOptions {
9
10
  fadeDuration?: number;
@@ -12,6 +13,8 @@ export declare class PlayOptions {
12
13
  startTime?: number;
13
14
  endTime?: number;
14
15
  clampWhenFinished?: boolean;
16
+ minMaxSpeed?: Vector2;
17
+ minMaxOffsetNormalized?: Vector2;
15
18
  }
16
19
 
17
20
  export class Animation extends Behaviour {
@@ -21,6 +24,12 @@ export class Animation extends Behaviour {
21
24
  @serializeable()
22
25
  randomStartTime: boolean = true;
23
26
 
27
+ @serializeable(Vector2)
28
+ minMaxSpeed?: Vector2;
29
+
30
+ @serializeable(Vector2)
31
+ minMaxOffsetNormalized?: Vector2;
32
+
24
33
  private _tempAnimationClipBeforeGameObjectExisted: AnimationClip | null = null;
25
34
  get clip(): AnimationClip | null {
26
35
  return this.animations?.length ? this.animations[0] : null;
@@ -120,6 +129,9 @@ export class Animation extends Behaviour {
120
129
  console.error("Could not find clip", clipOrNumber)
121
130
  return;
122
131
  }
132
+ if(!options) options = {};
133
+ if(!options.minMaxOffsetNormalized) options.minMaxOffsetNormalized = this.minMaxOffsetNormalized;
134
+ if(!options.minMaxSpeed) options.minMaxSpeed = this.minMaxSpeed;
123
135
  for (const act of this.actions) {
124
136
  if (act.getClip() === clip) {
125
137
  return this.internalOnPlay(act, options);
@@ -150,6 +162,9 @@ export class Animation extends Behaviour {
150
162
  action.enabled = true;
151
163
  action.time = 0;
152
164
  action.timeScale = 1;
165
+ const clip = action.getClip();
166
+ if(options?.minMaxOffsetNormalized) action.time = Mathf.lerp(options.minMaxOffsetNormalized.x, options.minMaxOffsetNormalized.y, Math.random()) * clip.duration;
167
+ if(options?.minMaxSpeed) action.timeScale = Mathf.lerp(options.minMaxSpeed.x, options.minMaxSpeed.y, Math.random());
153
168
  if (options?.clampWhenFinished) action.clampWhenFinished = true;
154
169
  if (options?.startTime !== undefined) action.time = options.startTime;
155
170
 
@@ -188,6 +188,8 @@ export class AnimatorController {
188
188
 
189
189
  private evaluateTransitions() {
190
190
 
191
+ const currentLayer = 0;
192
+
191
193
  let didEnterStateThisFrame = false;
192
194
  if (!this._activeState) {
193
195
  this.setStartTransition();
@@ -200,8 +202,11 @@ export class AnimatorController {
200
202
  let index = 0;
201
203
  for (const transition of state.transitions) {
202
204
  ++index;
203
- // transition without exit time and without condition are ignored
204
- if (!transition.hasExitTime && transition.conditions.length <= 0) continue;
205
+ // transition without exit time and without condition that transition to itself are ignored
206
+ if (!transition.hasExitTime && transition.conditions.length <= 0) {
207
+ // if (this._activeState && this.getState(transition.destinationState, currentLayer)?.hash === this._activeState.hash)
208
+ continue;
209
+ }
205
210
 
206
211
  let allConditionsAreMet = true;
207
212
  for (const cond of transition.conditions) {
@@ -213,7 +218,7 @@ export class AnimatorController {
213
218
  if (!allConditionsAreMet) continue;
214
219
 
215
220
  if (debug && allConditionsAreMet) {
216
- console.log("All conditions are met", transition.conditions, action);
221
+ console.log("All conditions are met", transition);
217
222
  }
218
223
 
219
224
  // disable triggers
@@ -279,17 +284,22 @@ export class AnimatorController {
279
284
 
280
285
  }
281
286
 
287
+ private getState(state : State | number, layerIndex:number) : State | null {
288
+ if (typeof state === "number") {
289
+ if (state == -1) state = this.model.layers[layerIndex].stateMachine.defaultState; // exit state -> entry state
290
+ state = this.model.layers[layerIndex].stateMachine.states[state];
291
+ }
292
+ return state;
293
+ }
294
+
282
295
  private transitionTo(state: State | number, durationInSec: number, offsetNormalized: number) {
283
296
 
284
297
  if (!this.animator) return;
285
298
 
286
299
  const layerIndex = 0;
287
300
 
288
- if (typeof state === "number") {
289
- if (state == -1) state = this.model.layers[layerIndex].stateMachine.defaultState; // exit state -> entry state
290
- state = this.model.layers[layerIndex].stateMachine.states[state];
291
- }
292
-
301
+ state = this.getState(state, layerIndex) as State;
302
+
293
303
  if (!state?.motion || !state.motion.clip) {
294
304
  // if(debug) console.warn("State has no clip or motion", state);
295
305
  return;
@@ -409,7 +419,7 @@ export class AnimatorController {
409
419
  const sm = layer.stateMachine;
410
420
  for (let index = 0; index < sm.states.length; index++) {
411
421
  const state = sm.states[index];
412
-
422
+
413
423
  // ensure we have a motion even if none was exported
414
424
  if (!state.motion) {
415
425
  state.motion = createMotion(state.name);
@@ -9,12 +9,12 @@ const gizmos = getParam("gizmos");
9
9
  export class BoxHelperComponent extends Behaviour {
10
10
 
11
11
  private box: THREE.Box3 | null = null;
12
- private testBox: THREE.Box3 = new THREE.Box3();
12
+ private static testBox: THREE.Box3 = new THREE.Box3();
13
13
  private _lastMatrixUpdateFrame: number = -1;
14
14
  private static _position: THREE.Vector3 = new THREE.Vector3();
15
15
  private static _size: THREE.Vector3 = new THREE.Vector3(.01, .01, .01);
16
16
 
17
- public isInBox(obj: THREE.Object3D): boolean | undefined {
17
+ public isInBox(obj: THREE.Object3D, scaleFactor? : number): boolean | undefined {
18
18
  if (!obj) return undefined;
19
19
 
20
20
  // if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
@@ -23,22 +23,39 @@ export class BoxHelperComponent extends Behaviour {
23
23
  if (!this.box) {
24
24
  this.box = new THREE.Box3();
25
25
  }
26
- this.updateBox(false);
27
26
 
28
- if (obj.type === "Mesh")
29
- this.testBox.setFromObject(obj);
27
+ if (obj.type === "Mesh") {
28
+ BoxHelperComponent.testBox.setFromObject(obj);
29
+ }
30
+ else if (obj.type === "Group") {
31
+ if (obj.children.length > 0) {
32
+ BoxHelperComponent.testBox.setFromCenterAndSize(
33
+ BoxHelperComponent._position.set(0, 0, 0),
34
+ BoxHelperComponent._size.set(0, 0, 0)
35
+ );
36
+ for (let i = 0; i < obj.children.length; i++) {
37
+ const ch = obj.children[i];
38
+ if (ch.type === "Mesh") {
39
+ BoxHelperComponent.testBox.expandByObject(obj);
40
+ }
41
+ }
42
+ }
43
+ }
30
44
  else {
31
45
  const wp = getWorldPosition(obj, BoxHelperComponent._position);
32
46
  const size = getWorldScale(obj, BoxHelperComponent._size);
33
- this.testBox.setFromCenterAndSize(wp, size);
47
+ if(scaleFactor !== undefined) size.multiplyScalar(scaleFactor);
48
+ BoxHelperComponent.testBox.setFromCenterAndSize(wp, size);
34
49
  }
35
50
 
51
+ // console.log("Obj box:", obj.name, obj.uuid, BoxHelperComponent.testBox.getCenter(new THREE.Vector3()), BoxHelperComponent.testBox.getSize(new THREE.Vector3()),
52
+ // "\nTest box:", this.box.getCenter(new THREE.Vector3()), this.box.getSize(new THREE.Vector3()), this.context.time.frameCount);
36
53
  // this.tp.copy(obj.geometry.boundingBox);
37
54
  // // this.tp.setFromCenterAndSize(obj.position, obj.scale);
38
55
  // this.tp.applyMatrix4(obj.matrixWorld);
39
56
  // console.log(this.tp);
40
57
  // console.log(this.box?.min, this.box?.max, this.tp.min, this.tp.max);
41
- return this.box?.intersectsBox(this.testBox);
58
+ return this.box?.intersectsBox(BoxHelperComponent.testBox);
42
59
  }
43
60
 
44
61
  public intersects(box: THREE.Box3): boolean {
@@ -51,9 +68,12 @@ export class BoxHelperComponent extends Behaviour {
51
68
  this.box = new THREE.Box3();
52
69
  }
53
70
  if (force || this.context.time.frameCount != this._lastMatrixUpdateFrame) {
71
+ const firstUpdate = this._lastMatrixUpdateFrame < 0;
54
72
  this._lastMatrixUpdateFrame = this.context.time.frameCount;
55
- this.box.setFromCenterAndSize(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 1, 1));
56
- this.box.applyMatrix4(this.gameObject.matrixWorld);
73
+ const updateParents: boolean = firstUpdate; // updating parents seems to cause falsely calculated positions sometimes?
74
+ const wp = getWorldPosition(this.gameObject, BoxHelperComponent._position, updateParents);
75
+ const size = getWorldScale(this.gameObject, BoxHelperComponent._size);
76
+ this.box.setFromCenterAndSize(wp, size);
57
77
  }
58
78
  return this.box;
59
79
  }
@@ -8,7 +8,7 @@ import { Object3D } from "three";
8
8
  import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instantiate";
9
9
  import { ConstructorConcrete, SourceIdentifier, IComponent, IGameObject, Constructor, GuidsMap, UIDProvider, Collision } from "../engine/engine_types";
10
10
  import { addNewComponentInstance, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, moveComponentInstance, removeComponent } from "../engine/engine_components";
11
- import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing } from "../engine/engine_gameobject";
11
+ import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive } from "../engine/engine_gameobject";
12
12
 
13
13
 
14
14
  // export interface ISerializationCallbackReceiver {
@@ -23,10 +23,10 @@ abstract class GameObject extends THREE.Object3D implements THREE.Object3D, IGam
23
23
 
24
24
  guid: string | undefined;
25
25
 
26
- public static setActive(go: THREE.Object3D, active: boolean, processStart: boolean = true) {
26
+ public static setActive(go: THREE.Object3D, active: boolean, processStart: boolean = true, setVisible: boolean = true) {
27
27
  if (!go) return;
28
- go.visible = active;
29
- main.updateActiveInHierarchyWithoutEventCall(go);
28
+ setActive(go, active, setVisible);
29
+ main.updateIsActive(go);
30
30
  if (active && processStart)
31
31
  main.processStart(Context.Current, go);
32
32
  }
@@ -82,10 +82,12 @@ abstract class GameObject extends THREE.Object3D implements THREE.Object3D, IGam
82
82
  context = Context.Current;
83
83
  }
84
84
  parent.add(instance);
85
- main.updateActiveInHierarchyWithoutEventCall(instance);
85
+ setActive(instance, true);
86
+ main.updateIsActive(instance);
86
87
  if (context) {
87
88
  GameObject.foreachComponent(instance, (comp: Component) => {
88
89
  main.addScriptToArrays(comp, context!);
90
+ if(comp.__internalDidAwakeAndStart) return;
89
91
  if (context!.new_script_start.includes(comp) === false) {
90
92
  context!.new_script_start.push(comp as Behaviour);
91
93
  }
@@ -102,6 +104,8 @@ abstract class GameObject extends THREE.Object3D implements THREE.Object3D, IGam
102
104
  public static remove(instance: THREE.Object3D | null | undefined) {
103
105
  if (!instance) return;
104
106
  instance.parent?.remove(instance);
107
+ setActive(instance, false);
108
+ main.updateIsActive(instance);
105
109
  GameObject.foreachComponent(instance, (comp) => {
106
110
  main.processRemoveFromScene(comp);
107
111
  }, true);
@@ -354,6 +358,8 @@ class Component implements IComponent, EventTarget {
354
358
  protected __isEnabled: boolean | undefined = undefined;
355
359
  private __destroyed: boolean = false;
356
360
 
361
+ get __internalDidAwakeAndStart() { return this.__didAwake && this.__didStart; }
362
+
357
363
  __internalNewInstanceCreated() {
358
364
  this.__didAwake = false;
359
365
  this.__didStart = false;
@@ -41,12 +41,13 @@ export class GroundProjectedEnv extends Behaviour {
41
41
 
42
42
 
43
43
  onEnable() {
44
- this.updateProjection();
45
- if (!this._watcher) {
46
- this._watcher = new Watch(this.context.scene, "environment");
47
- this._watcher.subscribeWrite(this.updateProjection.bind(this));
48
- }
49
- this._watcher.apply();
44
+ // TODO: if we do this in the first frame we can not disable it again. Something buggy with the watch?!
45
+ if (this.context.time.frameCount > 0)
46
+ this.updateAndCreate();
47
+ }
48
+
49
+ start() {
50
+ this.updateAndCreate();
50
51
  }
51
52
 
52
53
  onDisable() {
@@ -54,8 +55,22 @@ export class GroundProjectedEnv extends Behaviour {
54
55
  this.env?.removeFromParent();
55
56
  }
56
57
 
58
+ private updateAndCreate() {
59
+ this.updateProjection();
60
+ if (!this._watcher) {
61
+ this._watcher = new Watch(this.context.scene, "environment");
62
+ this._watcher.subscribeWrite(_ => {
63
+ this.updateProjection();
64
+ });
65
+ }
66
+ this._watcher.apply();
67
+ }
68
+
57
69
  updateProjection() {
58
- if (!this.context.scene.environment) return;
70
+ if (!this.context.scene.environment) {
71
+ this.env?.removeFromParent();
72
+ return;
73
+ }
59
74
  if (!this.env || this.context.scene.environment !== this._lastEnvironment) {
60
75
  console.log("Create/Update Ground Projection", this.context.scene.environment.name);
61
76
  this.env = new GroundProjection(this.context.scene.environment);
@@ -0,0 +1,141 @@
1
+ import { Behaviour } from "./Component";
2
+ import { Box3, Color, EquirectangularReflectionMapping, LineBasicMaterial, Material, MeshStandardMaterial, Object3D, sRGBEncoding, Texture, Vector3, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
3
+ import { serializeable } from "../engine/engine_serialization";
4
+ import { Context } from "../engine/engine_setup";
5
+ import { getWorldPosition, getWorldScale } from "../engine/engine_three_utils";
6
+ import { IRenderer } from "../engine/engine_types";
7
+ import { BoxHelperComponent } from "./BoxHelperComponent";
8
+ import { getParam } from "../engine/engine_utils";
9
+
10
+ export const debug = getParam("debugreflectionprobe");
11
+ const disable = getParam("noreflectionprobe");
12
+
13
+ export class ReflectionProbe extends Behaviour {
14
+
15
+ private static _probes: Map<Context, ReflectionProbe[]> = new Map();
16
+
17
+ public static get(object: Object3D | null | undefined, context: Context, isAnchor:boolean): ReflectionProbe | null {
18
+ if (!object || object.isObject3D !== true) return null;
19
+ if (disable) return null;
20
+ const probes = ReflectionProbe._probes.get(context);
21
+ if (probes) {
22
+ for (const probe of probes) {
23
+ if (!probe.__didAwake) probe.__internalAwake();
24
+ if (probe.enabled && probe.isInBox(object, isAnchor ? .00000001 : undefined)) {
25
+ if (debug) console.log("Found reflection probe", object.name, probe.name);
26
+ return probe;
27
+ }
28
+ }
29
+ }
30
+ if (debug)
31
+ console.debug("Did not find reflection probe", object.name, isAnchor, object);
32
+ return null;
33
+ }
34
+
35
+
36
+
37
+ private _texture!: Texture;
38
+ set texture(tex: Texture) {
39
+ this._texture = tex;
40
+ }
41
+ get texture(): Texture {
42
+ return this._texture;
43
+ }
44
+
45
+ @serializeable(Vector3)
46
+ center?: Vector3;
47
+ @serializeable(Vector3)
48
+ size?: Vector3;
49
+
50
+ private _boxHelper?: BoxHelperComponent;
51
+
52
+ private isInBox(obj: Object3D, scaleFactor?: number) {
53
+ return this._boxHelper?.isInBox(obj, scaleFactor);
54
+ }
55
+
56
+ constructor() {
57
+ super();
58
+ if (!ReflectionProbe._probes.has(this.context)) {
59
+ ReflectionProbe._probes.set(this.context, []);
60
+ }
61
+ ReflectionProbe._probes.get(this.context)?.push(this);
62
+ }
63
+
64
+ awake() {
65
+ this._boxHelper = this.gameObject.addNewComponent(BoxHelperComponent) as BoxHelperComponent;
66
+ this._boxHelper.updateBox(true);
67
+ if (debug)
68
+ this._boxHelper.showHelper(0x555500, true);
69
+
70
+ if (this.texture) {
71
+ this.texture.mapping = EquirectangularReflectionMapping;
72
+ this.texture.encoding = sRGBEncoding;
73
+ // this.texture.rotation = Math.PI;
74
+ // this.texture.flipY = true;
75
+ this.texture.needsUpdate = true;
76
+ }
77
+ }
78
+
79
+ onDestroy() {
80
+ const probes = ReflectionProbe._probes.get(this.context);
81
+ if (probes) {
82
+ const index = probes.indexOf(this);
83
+ if (index >= 0) {
84
+ probes.splice(index, 1);
85
+ }
86
+ }
87
+ }
88
+
89
+
90
+ // when objects are rendered and they share material
91
+ // and some need reflection probe and some don't
92
+ // we need to make sure we don't override the material but use a copy
93
+
94
+ private static _rendererMaterialsCache: Map<IRenderer, Array<{ orig: Material, copy: Material }>> = new Map();
95
+
96
+ onSet(_rend: IRenderer) {
97
+ if (disable) return;
98
+ if (_rend.sharedMaterials?.length <= 0) return;
99
+ if (!this.texture) return;
100
+
101
+ let rendererCache = ReflectionProbe._rendererMaterialsCache.get(_rend);
102
+ if (!rendererCache) {
103
+ rendererCache = [];
104
+ ReflectionProbe._rendererMaterialsCache.set(_rend, rendererCache);
105
+ }
106
+
107
+ // TODO: dont clone material for every renderer that uses reflection probes, we can do it once per material when they use the same reflection texture
108
+
109
+ // need to make sure materials are not shared when using reflection probes
110
+ // otherwise some renderers outside of the probe will be affected or vice versa
111
+ for (let i = 0; i < _rend.sharedMaterials.length; i++) {
112
+ const orig = _rend.sharedMaterials[i];
113
+ if (!orig) continue;
114
+ if (orig["envMap"] === undefined) continue;
115
+ let cached = rendererCache[i];
116
+ let copy = cached?.copy;
117
+ if (!cached) {
118
+ const clone = orig.clone();
119
+ copy = clone;
120
+ rendererCache.push({ orig, copy });
121
+
122
+ copy["__reflection_probe"] = this;
123
+ copy["envMap"] = this.texture;
124
+
125
+ if (debug)
126
+ console.log("Set reflection", _rend.name, _rend.guid);
127
+ }
128
+ _rend.sharedMaterials[i] = copy;
129
+ }
130
+ }
131
+
132
+ onUnset(_rend: IRenderer) {
133
+ const rendererCache = ReflectionProbe._rendererMaterialsCache.get(_rend);
134
+ if (rendererCache) {
135
+ for (let i = 0; i < rendererCache.length; i++) {
136
+ const cached = rendererCache[i];
137
+ _rend.sharedMaterials[i] = cached.orig;
138
+ }
139
+ }
140
+ }
141
+ }