Needle Engine

Changes between version 3.2.5-alpha.1 and 3.2.6-alpha
Files changed (17) hide show
  1. src/engine-components/api.ts +1 -0
  2. src/engine/api.ts +3 -1
  3. src/engine-components/Component.ts +15 -3
  4. src/engine/engine_context.ts +5 -3
  5. src/engine/engine_element_loading.ts +0 -1
  6. src/engine/engine_mainloop_utils.ts +4 -2
  7. src/engine/engine_rendererdata.ts +0 -236
  8. src/engine/engine_serialization_core.ts +7 -8
  9. src/engine-components/webxr/index.ts +1 -0
  10. src/engine/extensions/NEEDLE_lighting_settings.ts +40 -25
  11. src/engine/extensions/NEEDLE_techniques_webgl.ts +3 -3
  12. src/engine-components/postprocessing/PostProcessingHandler.ts +1 -1
  13. src/engine-components/Renderer.ts +1 -1
  14. src/engine-components/SceneSwitcher.ts +14 -4
  15. src/engine-components/postprocessing/VolumeParameter.ts +7 -6
  16. src/engine/engine_scenelighting.ts +307 -0
  17. src/engine/extensions/index.ts +5 -0
src/engine-components/api.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  // We dont want to export everything in the extensions
5
5
  export * from "./js-extensions/RGBAColor";
6
6
  export * from "./js-extensions/Object3D";
7
+ export * from "./XRFlag"
7
8
 
8
9
  export * from "./export"
9
10
  export * from "./postprocessing"
src/engine/api.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- export * from "./extensions/extensions"
2
+ export * from "./extensions"
3
3
  export * from "./engine_addressables";
4
4
  export * from "./engine_application"
5
5
  export * from "./engine_assetdatabase"
@@ -27,11 +27,13 @@
27
27
  export * from "./engine_playerview"
28
28
  export * from "./engine_physics"
29
29
  export * from "./engine_physics.types"
30
+ export * from "./engine_scenelighting"
30
31
  export * from "./engine_input";
31
32
  export * from "./engine_math";
32
33
  export * from "./js-extensions";
33
34
  export * from "./engine_scenetools";
34
35
  export * from "./engine_serialization";
36
+ export { type ISerializable } from "./engine_serialization_core";
35
37
  export * from "./engine_texture";
36
38
  export * from "./engine_three_utils";
37
39
  export * from "./engine_time";
src/engine-components/Component.ts CHANGED
@@ -470,7 +470,13 @@
470
470
 
471
471
  /** @internal */
472
472
  __internalEnable(): boolean {
473
- if (this.__didEnable) return false;
473
+ // Don't change enable before awake
474
+ // But a user can change enable during awake
475
+ if (!this.__didAwake) return false;
476
+ if (this.__didEnable) {
477
+ this.__isEnabled = true;
478
+ return false;
479
+ }
474
480
  // console.trace("INTERNAL ENABLE");
475
481
  this.__didEnable = true;
476
482
  this.onEnable();
@@ -480,7 +486,13 @@
480
486
 
481
487
  /** @internal */
482
488
  __internalDisable() {
483
- if (!this.__didEnable) return;
489
+ // Don't change enable before awake
490
+ // But a user can change enable during awake
491
+ if (!this.__didAwake) return;
492
+ if (!this.__didEnable) {
493
+ this.__isEnabled = false;
494
+ return;
495
+ }
484
496
  this.__didEnable = false;
485
497
  this.onDisable();
486
498
  this.__isEnabled = false;
@@ -496,7 +508,7 @@
496
508
 
497
509
 
498
510
  get enabled(): boolean {
499
- return this.__isEnabled ?? true; // if it has no enabled field it is always enabled
511
+ return typeof this.__isEnabled === "boolean" ? this.__isEnabled : true; // if it has no enabled field it is always enabled
500
512
  }
501
513
  set enabled(val: boolean) {
502
514
  // when called from animationclip we receive numbers
src/engine/engine_context.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  import { logHierarchy } from './engine_three_utils';
19
19
 
20
20
  import * as Stats from 'three/examples/jsm/libs/stats.module';
21
- import { RendererData } from './engine_rendererdata';
21
+ import { RendererData as SceneLighting } from './engine_scenelighting';
22
22
  import { Addressables } from './engine_addressables';
23
23
  import { Application } from './engine_application';
24
24
  import { LightDataRegistry, ILightDataRegistry } from './engine_lightdata';
@@ -225,7 +225,9 @@
225
225
  */
226
226
  assets: AssetDatabase;
227
227
  mainLight: ILight | null = null;
228
- rendererData: RendererData;
228
+ /** @deprecated Use sceneLighting */
229
+ get rendererData() { return this.sceneLighting }
230
+ sceneLighting: SceneLighting;
229
231
  addressables: Addressables;
230
232
  lightmaps: ILightDataRegistry;
231
233
  players: PlayerViewManager;
@@ -278,7 +280,7 @@
278
280
  this.physics = new Physics(this);
279
281
  this.connection = new NetworkConnection(this);
280
282
  this.assets = new AssetDatabase();
281
- this.rendererData = new RendererData(this);
283
+ this.sceneLighting = new SceneLighting(this);
282
284
  this.addressables = new Addressables(this);
283
285
  this.lightmaps = new LightDataRegistry(this);
284
286
  this.players = new PlayerViewManager(this);
src/engine/engine_element_loading.ts CHANGED
@@ -176,7 +176,6 @@
176
176
  this._loadingElement = existing || document.createElement("div");
177
177
 
178
178
  const loadingStyle: LoadingStyleOption = this._element.getAttribute("loading-style") as LoadingStyleOption;
179
- console.log(loadingStyle);
180
179
 
181
180
  const hasLicense = hasProLicense();
182
181
  if (!existing) {
src/engine/engine_mainloop_utils.ts CHANGED
@@ -267,8 +267,10 @@
267
267
  if (activeInHierarchy) {
268
268
  if (comp.enabled) {
269
269
  utils.safeInvoke(comp.__internalAwake.bind(comp));
270
- comp["__didEnable"] = true;
271
- comp.onEnable();
270
+ if (comp.enabled) {
271
+ comp["__didEnable"] = true;
272
+ comp.onEnable();
273
+ }
272
274
  }
273
275
  }
274
276
  else {
src/engine/engine_rendererdata.ts DELETED
@@ -1,236 +0,0 @@
1
- import { Vector4, EquirectangularReflectionMapping, sRGBEncoding, WebGLCubeRenderTarget, Texture, LightProbe, Color, SphericalHarmonics3 } from "three";
2
- import { LightProbeGenerator } from "three/examples/jsm/lights/LightProbeGenerator.js"
3
- import { Context } from "./engine_setup";
4
- import { SceneLightSettings } from "./extensions/NEEDLE_lighting_settings";
5
- import { createFlatTexture, createTrilightTexture } from "./engine_shaders";
6
- import { getParam } from "./engine_utils";
7
- import { SourceIdentifier } from "./engine_types";
8
-
9
- const debug = getParam("debugenvlight");
10
-
11
-
12
- export declare type SphericalHarmonicsData = {
13
- array: number[],
14
- texture: WebGLCubeRenderTarget | Texture,
15
- lightProbe?: LightProbe
16
- }
17
-
18
- export enum AmbientMode {
19
- Skybox = 0,
20
- Trilight = 1,
21
- Flat = 3,
22
- Custom = 4,
23
- }
24
-
25
- export enum DefaultReflectionMode {
26
- Skybox = 0,
27
- Custom = 1,
28
- }
29
-
30
- export class RendererData {
31
-
32
- private context: Context;
33
-
34
- constructor(context: Context) {
35
- this.context = context;
36
- this.context.pre_update_callbacks.push(this.preUpdate.bind(this))
37
- }
38
-
39
- private _currentReflectionId?: SourceIdentifier;
40
- private sceneLightSettings?: Map<SourceIdentifier, SceneLightSettings>;
41
-
42
- private preUpdate() {
43
- const time = this.context.time;
44
- this._timevec4.x = time.time;
45
- this._timevec4.y = Math.sin(time.time);
46
- this._timevec4.z = Math.cos(time.time);
47
- this._timevec4.w = time.deltaTime;
48
- }
49
-
50
- private _timevec4: Vector4 = new Vector4();
51
- get timeVec4(): Vector4 {
52
- return this._timevec4;
53
- }
54
-
55
- get environmentIntensity(): number {
56
- if (!this.sceneLightSettings) return 1;
57
- if (!this._currentReflectionId) return 1;
58
- const settings = this.sceneLightSettings.get(this._currentReflectionId);
59
- if(settings)
60
- return settings.ambientIntensity;// * Math.PI * .5;
61
- return 1;
62
- }
63
-
64
- registerSceneLightSettings(sceneLightSettings: SceneLightSettings) {
65
- const sourceId = sceneLightSettings.sourceId;
66
- if(!sourceId){
67
- console.error("Missing source id for scene light settings, can not register:", sceneLightSettings);
68
- return;
69
- }
70
- if (debug) console.log("Register lighting settings", sceneLightSettings?.sourceId, sceneLightSettings);
71
- if (!this.sceneLightSettings) this.sceneLightSettings = new Map();
72
- this.sceneLightSettings.set(sourceId, sceneLightSettings);
73
- }
74
-
75
- registerReflection(sourceId: SourceIdentifier, reflectionTexture: Texture) {
76
- if (debug) console.log("Register reflection", sourceId, reflectionTexture);
77
- const h = new LightData(this.context, reflectionTexture, 1);
78
- this._lighting[sourceId] = h;
79
- }
80
-
81
- getReflection(sourceId: SourceIdentifier): LightData | null | undefined {
82
- return this._lighting[sourceId];
83
- }
84
-
85
- enableReflection(sourceId: SourceIdentifier) {
86
- const previousId = this._currentReflectionId;
87
- this._currentReflectionId = sourceId;
88
- const settings = this.sceneLightSettings?.get(sourceId);
89
-
90
- if (debug) {
91
- console.log("Enable reflection", sourceId, settings ? AmbientMode[settings.ambientMode] : "Unknown ambient mode");
92
- }
93
-
94
- switch (settings?.ambientMode) {
95
- case AmbientMode.Skybox:
96
- case AmbientMode.Custom:
97
- // only set environment reflection when ambient mode is skybox or custom
98
- const existing = this.getReflection(sourceId);
99
- if (existing && existing.Source) {
100
- if (debug) console.log("Setting environment reflection", existing.Source);
101
- const scene = this.context.scene;
102
- const tex = existing.Source;
103
- tex.encoding = sRGBEncoding;
104
- tex.mapping = EquirectangularReflectionMapping;
105
- scene.environment = tex;
106
- return;
107
- }
108
- else if (debug) console.warn("Could not find reflection for source", sourceId);
109
- break;
110
- }
111
-
112
- if (settings?.environmentReflectionSource === DefaultReflectionMode.Custom) {
113
- switch (settings?.ambientMode) {
114
- case AmbientMode.Trilight:
115
- if (settings.ambientTrilight) {
116
- const colors = settings.ambientTrilight;
117
- const tex = createTrilightTexture(colors[0], colors[1], colors[2], 64, 64);
118
- tex.encoding = sRGBEncoding;
119
- tex.mapping = EquirectangularReflectionMapping;
120
- this.context.scene.environment = tex;
121
- }
122
- else console.error("Missing ambient trilight", settings.sourceId);
123
- return;
124
- case AmbientMode.Flat:
125
- if (settings.ambientLight) {
126
- const tex = createFlatTexture(settings.ambientLight, 64);
127
- tex.encoding = sRGBEncoding;
128
- tex.mapping = EquirectangularReflectionMapping;
129
- this.context.scene.environment = tex;
130
- }
131
- else console.error("Missing ambientlight", settings.sourceId);
132
- return;
133
- default:
134
- return;
135
- }
136
- }
137
- }
138
-
139
- disableReflection(sourceId?: SourceIdentifier) {
140
- if (sourceId && sourceId !== this._currentReflectionId) return;
141
- const scene = this.context.scene;
142
- scene.environment = null;
143
- }
144
-
145
- async getSceneLightingData(sourceId: SourceIdentifier): Promise<SphericalHarmonicsData> {
146
- if (debug)
147
- console.log("GET SCENE LIGHT DATA", sourceId);
148
-
149
- // const existing = this.getReflection(sourceId);
150
- // const sh = existing?.getSphericalHarmonicsArray(this.sceneLightSettings?.ambientIntensity ?? 1);
151
- // if (sh) {
152
- // console.log("HAS EXISTING", sh, existing);
153
- // return sh;
154
- // }
155
-
156
- // fallback
157
- if (this._waitPromise) return this._waitPromise;
158
- this._waitPromise = new Promise((res, _rej) => {
159
- let interval = setInterval(async () => {
160
- const ex = this.getReflection(sourceId);
161
- if (ex) {
162
- clearInterval(interval);
163
- res(ex.getSphericalHarmonicsArray(this.environmentIntensity ?? 1)!);
164
- }
165
- }, 10);
166
- });
167
- return this._waitPromise;
168
- }
169
-
170
- private _waitPromise?: Promise<SphericalHarmonicsData>;
171
- private _lighting: { [sourceId: SourceIdentifier]: LightData } = {};
172
-
173
- }
174
-
175
- export class LightData {
176
-
177
- get Source(): Texture { return this._source; }
178
- get Array(): number[] | undefined { return this._sphericalHarmonicsArray; }
179
-
180
- private _context: Context;
181
- private _source: Texture;
182
- private _sphericalHarmonics: SphericalHarmonics3 | null = null;
183
- private _sphericalHarmonicsArray?: number[];
184
- private _ambientScale: number = 1;
185
- private _lightProbe?: LightProbe;
186
-
187
- constructor(context: Context, tex: Texture, ambientScale: number = 1) {
188
- this._context = context;
189
- this._source = tex;
190
- this._ambientScale = ambientScale;
191
- tex.mapping = EquirectangularReflectionMapping;
192
- tex.encoding = sRGBEncoding;
193
- }
194
-
195
- getSphericalHarmonicsArray(intensityFactor: number = 1): SphericalHarmonicsData | null {
196
- if (this._sphericalHarmonicsArray?.length && this._source) {
197
- return { array: this._sphericalHarmonicsArray, texture: this._source, lightProbe: this._lightProbe };
198
- }
199
-
200
- try {
201
- const reflection = this._source;
202
- let rt: WebGLCubeRenderTarget | null = null;
203
- if (reflection) {
204
- if (debug) console.log("GENERATING LIGHT PROBE", reflection, this.Source);
205
- const size = Math.min(reflection.image.width, 512);
206
- const target = new WebGLCubeRenderTarget(size);
207
- rt = target.fromEquirectangularTexture(this._context.renderer, reflection);
208
- this._source = rt.texture;
209
- }
210
-
211
- this._sphericalHarmonicsArray = [];
212
- if (rt) {
213
- const sampledProbe = LightProbeGenerator.fromCubeRenderTarget(this._context.renderer, rt);
214
- this._lightProbe = sampledProbe;
215
- const lightFactor = (this._ambientScale * (intensityFactor * intensityFactor * Math.PI * .5)) - 1;
216
- // console.log(intensityFactor, lightFactor);
217
- this._sphericalHarmonics = sampledProbe.sh;
218
- this._sphericalHarmonicsArray = this._sphericalHarmonics.toArray();
219
- if (this._sphericalHarmonicsArray) {
220
- const factor = ((intensityFactor) / (Math.PI * .5));
221
- for (let i = 0; i < this._sphericalHarmonicsArray.length; i++) {
222
- this._sphericalHarmonicsArray[i] *= factor;
223
- }
224
- sampledProbe.sh.scale(lightFactor);
225
- if (this._source)
226
- return { array: this._sphericalHarmonicsArray, texture: this._source, lightProbe: sampledProbe };
227
- }
228
- }
229
- }
230
- catch (err) {
231
- console.error(err);
232
- }
233
-
234
- return null;
235
- }
236
- }
src/engine/engine_serialization_core.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import { AnimationClip, Material, Mesh, Object3D, Texture } from "three";
4
4
  import { Context } from "./engine_setup";
5
5
  import { isPersistentAsset } from "./extensions/NEEDLE_persistent_assets";
6
- import { SourceIdentifier } from "./engine_types";
6
+ import { ConstructorConcrete, SourceIdentifier } from "./engine_types";
7
7
  import { debugExtension } from "../engine/engine_default_parameters";
8
8
  import { LogType, addLog } from "./debug/debug_overlay";
9
9
  import { isLocalNetwork } from "./engine_networking_utils";
@@ -12,7 +12,6 @@
12
12
  const debug = getParam("debugserializer");
13
13
 
14
14
 
15
- export type Constructor<T> = { new(...args: any[]): T };
16
15
  export declare type NodeToObjectMap = { [nodeId: string]: Object3D };
17
16
  export declare type ObjectToNodeMap = { [uuid: string]: number };
18
17
 
@@ -125,7 +124,7 @@
125
124
  // }
126
125
  // }
127
126
 
128
- constructor(type: Constructor<any> | Constructor<any>[]) {
127
+ constructor(type: ConstructorConcrete<any> | ConstructorConcrete<any>[]) {
129
128
  if (Array.isArray(type)) {
130
129
  for (const key of type)
131
130
  helper.register(key.name, this);
@@ -141,7 +140,7 @@
141
140
 
142
141
 
143
142
  export interface ITypeInformation {
144
- type?: Constructor<any>;
143
+ type?: ConstructorConcrete<any>;
145
144
  }
146
145
 
147
146
  /** holds information if a field was undefined before serialization. This gives us info if we might want to warn the user about missing attributes */
@@ -182,7 +181,7 @@
182
181
  objectToNode?: ObjectToNodeMap;
183
182
  context?: Context;
184
183
  path?: string;
185
- type?: Constructor<any>;
184
+ type?: ConstructorConcrete<any>;
186
185
  /** holds information if a field was undefined before serialization. This gives us info if we might want to warn the user about missing attributes */
187
186
  implementationInformation?: ImplementationInformation;
188
187
 
@@ -193,7 +192,7 @@
193
192
 
194
193
 
195
194
  export interface ISerializable {
196
- $serializedTypes?: { [key: string]: Constructor<any> | ITypeInformation | null };
195
+ $serializedTypes?: { [key: string]: ConstructorConcrete<any> | ITypeInformation | null };
197
196
  // onDeserialize?(context: SerializationContext): void;
198
197
  // example:
199
198
  /* $serializedTypes : {
@@ -337,7 +336,7 @@
337
336
  }
338
337
  // it can also just contain a constructor
339
338
  else {
340
- const constructor = typeInfoOrConstructor as Constructor<any>;
339
+ const constructor = typeInfoOrConstructor as ConstructorConcrete<any>;
341
340
  return deserializeObjectWithType(data, constructor, context, undefined, obj[key]);
342
341
  }
343
342
  }
@@ -460,7 +459,7 @@
460
459
  serializer?: ITypeSerializer
461
460
  }
462
461
 
463
- function deserializeObjectWithType(data: any, typeOrConstructor: Constructor<any>, context: SerializationContext, typeContext?: TypeDeserializeReference, currentValue?: any): any {
462
+ function deserializeObjectWithType(data: any, typeOrConstructor: ConstructorConcrete<any>, context: SerializationContext, typeContext?: TypeDeserializeReference, currentValue?: any): any {
464
463
 
465
464
  // e.g. @serializable((data) => { })
466
465
  let typeIsFunction = typeof typeOrConstructor === "function" && (typeOrConstructor.prototype === undefined);
src/engine-components/webxr/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./WebXR";
2
2
  export * from "./WebXRPlaneTracking";
3
+ export * from "./WebXRImageTracking";
3
4
  export * from "./WebXRController";
src/engine/extensions/NEEDLE_lighting_settings.ts CHANGED
@@ -2,11 +2,12 @@
2
2
  import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader";
3
3
  import { SourceIdentifier } from "../engine_types";
4
4
  import { Behaviour, GameObject } from "../../engine-components/Component";
5
- import { AmbientMode, DefaultReflectionMode } from "../engine_rendererdata";
5
+ import { AmbientMode, DefaultReflectionMode } from "../engine_scenelighting";
6
6
  import { LightmapType } from "./NEEDLE_lightmaps";
7
7
  import { getParam } from "../engine_utils";
8
8
  import { Context } from "../engine_setup";
9
9
  import { LightProbe } from "three";
10
+ import { ContextEvent, ContextRegistry } from "../engine_context_registry";
10
11
 
11
12
  export const EXTENSION_NAME = "NEEDLE_lighting_settings";
12
13
  const debug = getParam("debugenvlight");
@@ -42,7 +43,7 @@
42
43
  const ext: LightingSettings = extensions[EXTENSION_NAME];
43
44
  if (ext) {
44
45
  if (debug)
45
- console.log("Apply \"" + this.name + "\", src: \"" + this.sourceId + "\"", ext);
46
+ console.log("Loaded \"" + this.name + "\", src: \"" + this.sourceId + "\"", ext);
46
47
  let settings: SceneLightSettings | undefined = undefined;
47
48
  // If the result scene has only one child we add the LightingSettingsComponent to that child
48
49
  if (_result.scene.children.length === 1) {
@@ -50,10 +51,9 @@
50
51
  settings = GameObject.addNewComponent(_result.scene.children[0], SceneLightSettings, false);
51
52
  }
52
53
  // if the scene already has multiple children we add it as a new object
53
- else
54
- {
54
+ else {
55
55
  const lightSettings = new Object3D();
56
- lightSettings.name = "Needle LightSettings";
56
+ lightSettings.name = "LightSettings " + this.sourceId;
57
57
  _result.scene.add(lightSettings);
58
58
  settings = GameObject.addNewComponent(lightSettings, SceneLightSettings, false);
59
59
  }
@@ -64,7 +64,6 @@
64
64
  settings.ambientTrilight = ext.ambientTrilight.map(c => new Color().fromArray(c));
65
65
  settings.ambientMode = ext.ambientMode;
66
66
  settings.environmentReflectionSource = ext.environmentReflectionSource;
67
- if (this.context) this.context.rendererData.registerSceneLightSettings(settings);
68
67
  }
69
68
  }
70
69
  return null;
@@ -72,6 +71,12 @@
72
71
 
73
72
  }
74
73
 
74
+ ContextRegistry.registerCallback(ContextEvent.ContextCreated, e => {
75
+ const ctx = e.context as Context;
76
+ const lightingSettings = GameObject.findObjectOfType(SceneLightSettings, ctx as Context);
77
+ if (lightingSettings?.sourceId) ctx.sceneLighting.enable(lightingSettings.sourceId);
78
+ })
79
+
75
80
  // exists once per gltf scene root (if it contains reflection)
76
81
  // when enabled it does currently automatically set the reflection
77
82
  // this might not be desireable
@@ -85,6 +90,7 @@
85
90
 
86
91
  private _hasReflection: boolean = false;
87
92
  private _ambientLightObj?: AmbientLight;
93
+ private _hemisphereLightObj?: HemisphereLight;
88
94
  // used when skybox is used to support ambient intensity for "non custom shaders"
89
95
  private _lightProbeObj?: LightProbe;
90
96
 
@@ -94,11 +100,15 @@
94
100
  const tex = this.context.lightmaps.tryGet(this.sourceId, type, 0);
95
101
  this._hasReflection = tex !== null && tex !== undefined;
96
102
  if (tex)
97
- this.context.rendererData.registerReflection(this.sourceId, tex);
103
+ this.context.sceneLighting.internalRegisterReflection(this.sourceId, tex);
98
104
  }
99
105
 
106
+ this.enabled = false;
107
+ this.context.sceneLighting.internalRegisterSceneLightSettings(this);
108
+
100
109
  if (debug) {
101
110
  window.addEventListener("keydown", evt => {
111
+ if(this.destroyed) return;
102
112
  switch (evt.key) {
103
113
  case "l":
104
114
  this.enabled = !this.enabled;
@@ -108,17 +118,17 @@
108
118
  }
109
119
  }
110
120
 
111
- onEnable() {
112
- const isActive = this.context.mainCameraComponent?.sourceId === this.sourceId;
113
- if (debug) console.log("Enable scene lighting", this.sourceId, isActive, this, this.context.mainCameraComponent?.sourceId);
114
- if (!isActive) {
115
- if(debug) console.warn("This environment light is not active??!", this.context.mainCameraComponent?.sourceId)
116
- this.enabled = false;
117
- return;
118
- }
121
+ onDestroy(): void {
122
+ this.context.sceneLighting.internalUnregisterSceneLightSettings(this);
123
+ }
124
+
125
+ onEnable(): void {
126
+ if (debug) console.warn("💡🟡 >>> Enable lighting", this.sourceId, this);
127
+
119
128
  if (this.ambientMode == AmbientMode.Flat) {
120
129
  if (this.ambientLight && !this._ambientLightObj) {
121
130
  this._ambientLightObj = new AmbientLight(this.ambientLight, this.ambientIntensity);
131
+ if (debug) console.log("Created ambient light", this.sourceId, this._ambientLightObj)
122
132
  }
123
133
  if (this._ambientLightObj) {
124
134
  this.gameObject.add(this._ambientLightObj)
@@ -129,23 +139,24 @@
129
139
  if (this.ambientTrilight) {
130
140
  const ground = this.ambientTrilight[0];
131
141
  const sky = this.ambientTrilight[this.ambientTrilight.length - 1];
132
- const hemisphere = new HemisphereLight(sky, ground, this.ambientIntensity);
133
- this.gameObject.add(hemisphere)
142
+ this._hemisphereLightObj = new HemisphereLight(sky, ground, this.ambientIntensity);
143
+ this.gameObject.add(this._hemisphereLightObj)
134
144
  }
135
145
  }
136
146
  else {
137
147
  if (this._ambientLightObj)
138
148
  this._ambientLightObj.removeFromParent();
149
+ if (this._hemisphereLightObj)
150
+ this._hemisphereLightObj.removeFromParent();
139
151
 
140
152
  // create light probe object
141
153
  if (!this._lightProbeObj) {
142
154
  if (this.sourceId) {
143
- this.context.rendererData.getSceneLightingData(this.sourceId).then(data => {
155
+ this.context.sceneLighting.internalGetSceneLightingData(this.sourceId).then(data => {
144
156
  if (!data) return;
145
157
  this._lightProbeObj = data.lightProbe;
146
158
  if (this.enabled && !this.destroyed && this._lightProbeObj) {
147
- if (debug)
148
- console.log("Add", this.sourceId, data);
159
+ if (debug) console.log("Add", this.sourceId, data);
149
160
  this.gameObject.add(this._lightProbeObj);
150
161
  }
151
162
  });
@@ -159,16 +170,20 @@
159
170
  }
160
171
 
161
172
  if (this.sourceId)
162
- this.context.rendererData.enableReflection(this.sourceId);
173
+ this.context.sceneLighting.internalEnableReflection(this.sourceId);
163
174
 
164
175
  }
165
176
 
166
177
  onDisable() {
167
178
  if (debug)
168
- console.log("Disable envlight:", this.sourceId, this);
169
- if (this._lightProbeObj) this._lightProbeObj.removeFromParent();
170
- if(this._ambientLightObj) this._ambientLightObj.removeFromParent();
179
+ console.warn("💡⚫ <<< Disable lighting:", this.sourceId, this);
180
+ if (this._lightProbeObj)
181
+ this._lightProbeObj.removeFromParent();
182
+ if (this._ambientLightObj)
183
+ this._ambientLightObj.removeFromParent();
184
+ if (this._hemisphereLightObj)
185
+ this._hemisphereLightObj.removeFromParent();
171
186
  if (this.sourceId)
172
- this.context.rendererData.disableReflection(this.sourceId);
187
+ this.context.sceneLighting.internalDisableReflection(this.sourceId);
173
188
  }
174
189
  }
src/engine/extensions/NEEDLE_techniques_webgl.ts CHANGED
@@ -118,7 +118,7 @@
118
118
  console.error("Missing context");
119
119
  return;
120
120
  }
121
- const data = await context.rendererData.getSceneLightingData(this.identifier);
121
+ const data = await context.sceneLighting.internalGetSceneLightingData(this.identifier);
122
122
  if (!data || !data.array) {
123
123
  console.warn("Missing lighting data for custom shader, getSceneLightingData did not return anything");
124
124
  return;
@@ -210,10 +210,10 @@
210
210
  // this._lastFrame = context.time.frame;
211
211
 
212
212
  if (this.uniforms["_TimeParameters"]) {
213
- this.uniforms["_TimeParameters"].value = context.rendererData.timeVec4;
213
+ this.uniforms["_TimeParameters"].value = context.sceneLighting.timeVec4;
214
214
  }
215
215
  else if (this.uniforms["_Time"]) {
216
- this.uniforms["_Time"].value = context.rendererData.timeVec4;
216
+ this.uniforms["_Time"].value = context.sceneLighting.timeVec4;
217
217
  }
218
218
 
219
219
  const mainLight: ILight | null = context.mainLight;
src/engine-components/postprocessing/PostProcessingHandler.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import { showBalloonWarning } from "../../engine/debug/debug";
6
6
  import { Camera } from "../Camera";
7
7
  import { PostProcessingEffect } from "./PostProcessingEffect";
8
- import { Constructor } from "../../engine/engine_serialization_core";
8
+ import { Constructor } from "../../engine/engine_types";
9
9
 
10
10
  const debug = getParam("debugpost");
11
11
 
src/engine-components/Renderer.ts CHANGED
@@ -617,7 +617,7 @@
617
617
 
618
618
  if (material.envMapIntensity !== undefined) {
619
619
  const factor = this.hasLightmap ? Math.PI : 1;
620
- material.envMapIntensity = Math.max(0, this.context.rendererData.environmentIntensity / factor);
620
+ material.envMapIntensity = Math.max(0, this.context.sceneLighting.environmentIntensity / factor);
621
621
  }
622
622
  // if (this._reflectionProbe?.texture) {
623
623
  // material.envMap = this._reflectionProbe.texture;
src/engine-components/SceneSwitcher.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { AssetReference } from "../engine/engine_addressables";
2
- import { NeedleEngineHTMLElement } from "../engine/engine_element";
3
2
  import { InputEvents } from "../engine/engine_input";
4
3
  import { isLocalNetwork } from "../engine/engine_networking_utils";
5
4
  import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry";
@@ -11,9 +10,14 @@
11
10
 
12
11
  const ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME = "scene";
13
12
 
14
- ContextRegistry.registerCallback(ContextEvent.ContextRegistered, _ => {
15
- if (!NeedleEngineHTMLElement.observedAttributes.includes(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME))
16
- NeedleEngineHTMLElement.observedAttributes.push(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
13
+ ContextRegistry.registerCallback(ContextEvent.ContextRegistered, async _ => {
14
+ // We need to defer import to not get issues with circular dependencies
15
+ import("../engine/engine_element").then(res => {
16
+ const webcomponent = res.NeedleEngineHTMLElement;
17
+ if(debug) console.log("SceneSwitcher: registering scene attribute", webcomponent.observedAttributes);
18
+ if (!webcomponent.observedAttributes.includes(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME))
19
+ webcomponent.observedAttributes.push(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
20
+ });
17
21
  });
18
22
 
19
23
  const couldNotLoadScenePromise = Promise.resolve(false);
@@ -42,7 +46,11 @@
42
46
  @serializable()
43
47
  useSwipe: boolean = true;
44
48
 
49
+ /** when enabled will automatically apply the environment scene lights */
50
+ @serializable()
51
+ useSceneLighting: boolean = true;
45
52
 
53
+
46
54
  private _currentIndex: number = -1;
47
55
  private _currentScene: AssetReference | undefined = undefined;
48
56
  private _engineElementOverserver: MutationObserver | undefined = undefined;
@@ -191,6 +199,8 @@
191
199
  }
192
200
  if (this._currentIndex === index) {
193
201
  GameObject.add(scene.asset, this.gameObject);
202
+ if (this.useSceneLighting)
203
+ this.context.sceneLighting.enable(scene)
194
204
  // save the loaded scene as an url parameter
195
205
  if (this.queryParameterName?.length)
196
206
  setParamWithoutReload(this.queryParameterName, index.toString(), this.useHistory);
src/engine-components/postprocessing/VolumeParameter.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  set overrideState(val: boolean) {
18
18
  if (this._active === val) return;
19
19
  this._active = val;
20
- const value = val ? this._lastActiveSetValue : this._defaultValue;
20
+ const value = val ? this._valueRaw : this._defaultValue;
21
21
  this.processValue(value, true);
22
22
  }
23
23
  private _active: boolean = true;
@@ -26,13 +26,13 @@
26
26
 
27
27
  @serializable()
28
28
  get value() {
29
- return this._value;
29
+ return this._valueRaw;
30
30
  }
31
31
  set value(val: any) {
32
32
  this.processValue(val, false);
33
33
  }
34
34
  private _value: any;
35
- private _lastActiveSetValue?: any;
35
+ private _valueRaw?: any;
36
36
 
37
37
 
38
38
  set defaultValue(val: any) {
@@ -43,7 +43,7 @@
43
43
 
44
44
  /** enforce the value to be set and onValueChanged to be called if assigned */
45
45
  __init() {
46
- this.processValue(this._lastActiveSetValue, true);
46
+ this.processValue(this._valueRaw, true);
47
47
  }
48
48
 
49
49
  /** called to modify a changing value before it is saved */
@@ -65,9 +65,10 @@
65
65
  // with the value that is expected to received when the parameter is disabled)
66
66
  this._value = this._defaultValue;
67
67
  val = this._defaultValue;
68
+ this._valueRaw = val;
68
69
  }
69
70
  else {
70
- this._lastActiveSetValue = val;
71
+ this._valueRaw = val;
71
72
  if (this._active && this.valueProcessor)
72
73
  val = this.valueProcessor(val);
73
74
  this._value = val;
@@ -80,7 +81,7 @@
80
81
 
81
82
  private testIfValueChanged(newValue: any): boolean {
82
83
 
83
- if (this._lastActiveSetValue === newValue)
84
+ if (this._valueRaw === newValue)
84
85
  return false;
85
86
 
86
87
  // TODO: may need checks for colors or vectors (check by xyz,rgb because they might come in as anonymous objects via editor modifications)
src/engine/engine_scenelighting.ts ADDED
@@ -0,0 +1,307 @@
1
+ import { Vector4, EquirectangularReflectionMapping, sRGBEncoding, WebGLCubeRenderTarget, Texture, LightProbe, Color, SphericalHarmonics3 } from "three";
2
+ import { LightProbeGenerator } from "three/examples/jsm/lights/LightProbeGenerator.js"
3
+ import { Context } from "./engine_setup";
4
+ import { SceneLightSettings } from "./extensions/NEEDLE_lighting_settings";
5
+ import { createFlatTexture, createTrilightTexture } from "./engine_shaders";
6
+ import { getParam } from "./engine_utils";
7
+ import { SourceIdentifier } from "./engine_types";
8
+ import { AssetReference } from "./engine_addressables";
9
+
10
+ const debug = getParam("debugenvlight");
11
+
12
+
13
+ export declare type SphericalHarmonicsData = {
14
+ array: number[],
15
+ texture: WebGLCubeRenderTarget | Texture,
16
+ lightProbe?: LightProbe
17
+ }
18
+
19
+ export enum AmbientMode {
20
+ Skybox = 0,
21
+ Trilight = 1,
22
+ Flat = 3,
23
+ Custom = 4,
24
+ }
25
+
26
+ export enum DefaultReflectionMode {
27
+ Skybox = 0,
28
+ Custom = 1,
29
+ }
30
+
31
+ export class RendererData {
32
+
33
+ private context: Context;
34
+
35
+ constructor(context: Context) {
36
+ this.context = context;
37
+ this.context.pre_update_callbacks.push(this.preUpdate.bind(this))
38
+ }
39
+
40
+ private _currentLightSettingsId?: SourceIdentifier;
41
+ private _sceneLightSettings?: Map<SourceIdentifier, SceneLightSettings>;
42
+
43
+ private preUpdate() {
44
+ const time = this.context.time;
45
+ this._timevec4.x = time.time;
46
+ this._timevec4.y = Math.sin(time.time);
47
+ this._timevec4.z = Math.cos(time.time);
48
+ this._timevec4.w = time.deltaTime;
49
+ }
50
+
51
+ private _timevec4: Vector4 = new Vector4();
52
+ get timeVec4(): Vector4 {
53
+ return this._timevec4;
54
+ }
55
+
56
+ /** the current environment intensity */
57
+ get environmentIntensity(): number {
58
+ if (!this._sceneLightSettings) return 1;
59
+ if (!this._currentLightSettingsId) return 1;
60
+ const settings = this._sceneLightSettings.get(this._currentLightSettingsId);
61
+ if (settings)
62
+ return settings.ambientIntensity;// * Math.PI * .5;
63
+ return 1;
64
+ }
65
+
66
+ /** Get all currently registered scene light settings */
67
+ get sceneLightSettings() { return this._sceneLightSettings?.values() }
68
+
69
+ /** set the scene lighting from a specific scene. Will disable any previously enabled lighting settings */
70
+ enable(sourceId: SourceIdentifier | AssetReference) {
71
+ if(sourceId instanceof AssetReference)
72
+ sourceId = sourceId.uri;
73
+ const settings = this._sceneLightSettings?.get(sourceId);
74
+ if (!settings) {
75
+ return false;
76
+ }
77
+ if (debug) console.log("Enable scene light settings", sourceId, settings);
78
+ if (sourceId !== this._currentLightSettingsId && this._currentLightSettingsId) {
79
+ this.disable(this._currentLightSettingsId);
80
+ }
81
+ this._currentLightSettingsId = sourceId;
82
+ settings.enabled = true;
83
+ return true;
84
+ }
85
+
86
+ /** disable the lighting of a specific scene, will only have any effect if it is currently active */
87
+ disable(sourceId: SourceIdentifier | AssetReference) {
88
+ if(sourceId instanceof AssetReference)
89
+ sourceId = sourceId.uri;
90
+ if (sourceId === null || sourceId === undefined) return false;
91
+ const settings = this._sceneLightSettings?.get(sourceId);
92
+ if (!settings) {
93
+ return false;
94
+ }
95
+ if (debug) console.log("Disable scene light settings", sourceId, settings);
96
+ settings.enabled = false;
97
+ return true;
98
+ }
99
+
100
+ /** Disables the currently active scene lighting (if any), returns the id of the previously active lighting */
101
+ disableCurrent() : SourceIdentifier | null {
102
+ if (this._currentLightSettingsId) {
103
+ const prev = this._currentLightSettingsId;
104
+ this.disable(this._currentLightSettingsId);
105
+ return prev
106
+ }
107
+ return null;
108
+ }
109
+
110
+
111
+ /** @internal */
112
+ internalRegisterSceneLightSettings(sceneLightSettings: SceneLightSettings) {
113
+ const sourceId = sceneLightSettings.sourceId;
114
+ if (!sourceId) {
115
+ console.error("Missing source id for scene light settings, can not register:", sceneLightSettings);
116
+ return;
117
+ }
118
+ if (debug) console.log("Register " + sceneLightSettings?.sourceId + " lighting", sceneLightSettings);
119
+ if (!this._sceneLightSettings) this._sceneLightSettings = new Map();
120
+ this._sceneLightSettings.set(sourceId, sceneLightSettings);
121
+ }
122
+
123
+ /** @internal */
124
+ internalUnregisterSceneLightSettings(sceneLightSettings: SceneLightSettings) {
125
+ const sourceId = sceneLightSettings.sourceId;
126
+ if (!sourceId) {
127
+ console.error("Missing source id for scene light settings, can not unregister:", sceneLightSettings);
128
+ return;
129
+ }
130
+ if (debug) console.log("Unregister " + sceneLightSettings?.sourceId + " lighting", sceneLightSettings);
131
+ if (!this._sceneLightSettings) return;
132
+ this._sceneLightSettings.delete(sourceId);
133
+ }
134
+
135
+ /** @internal */
136
+ internalRegisterReflection(sourceId: SourceIdentifier, reflectionTexture: Texture) {
137
+ if (debug) console.log("Register reflection", sourceId, reflectionTexture);
138
+ const h = new LightData(this.context, reflectionTexture, 1);
139
+ this._lighting[sourceId] = h;
140
+ }
141
+
142
+ /** @internal */
143
+ internalGetReflection(sourceId: SourceIdentifier): LightData | null | undefined {
144
+ return this._lighting[sourceId];
145
+ }
146
+
147
+ private __currentReflectionId: SourceIdentifier | null = null;
148
+
149
+ /** @internal */
150
+ internalEnableReflection(sourceId: SourceIdentifier) {
151
+ this.__currentReflectionId = sourceId;
152
+ const settings = this._sceneLightSettings?.get(sourceId);
153
+
154
+ if (debug) {
155
+ console.log("Enable reflection", sourceId, settings ? AmbientMode[settings.ambientMode] : "Unknown ambient mode");
156
+ }
157
+
158
+ switch (settings?.ambientMode) {
159
+ case AmbientMode.Skybox:
160
+ case AmbientMode.Custom:
161
+ // only set environment reflection when ambient mode is skybox or custom
162
+ const existing = this.internalGetReflection(sourceId);
163
+ if (existing && existing.Source) {
164
+ if (debug) console.log("Setting environment reflection", existing);
165
+ const scene = this.context.scene;
166
+ const tex = existing.Source;
167
+ tex.encoding = sRGBEncoding;
168
+ tex.mapping = EquirectangularReflectionMapping;
169
+ scene.environment = tex;
170
+ return;
171
+ }
172
+ else if (debug) console.warn("Could not find reflection for source", sourceId);
173
+ break;
174
+ }
175
+
176
+ if (settings?.environmentReflectionSource === DefaultReflectionMode.Custom) {
177
+ switch (settings?.ambientMode) {
178
+ case AmbientMode.Trilight:
179
+ if (settings.ambientTrilight) {
180
+ const colors = settings.ambientTrilight;
181
+ const tex = createTrilightTexture(colors[0], colors[1], colors[2], 64, 64);
182
+ tex.encoding = sRGBEncoding;
183
+ tex.mapping = EquirectangularReflectionMapping;
184
+ this.context.scene.environment = tex;
185
+ }
186
+ else console.error("Missing ambient trilight", settings.sourceId);
187
+ return;
188
+ case AmbientMode.Flat:
189
+ if (settings.ambientLight) {
190
+ const tex = createFlatTexture(settings.ambientLight, 64);
191
+ tex.encoding = sRGBEncoding;
192
+ tex.mapping = EquirectangularReflectionMapping;
193
+ this.context.scene.environment = tex;
194
+ }
195
+ else console.error("Missing ambientlight", settings.sourceId);
196
+ return;
197
+ default:
198
+ return;
199
+ }
200
+ }
201
+ }
202
+
203
+ /** @internal */
204
+ internalDisableReflection(sourceId?: SourceIdentifier) {
205
+ if (sourceId && sourceId !== this.__currentReflectionId) {
206
+ if(debug) console.log("Not disabling reflection for", sourceId, "because it is not the current light settings id", this.__currentReflectionId)
207
+ return;
208
+ }
209
+ if (debug) console.log("Disable reflection", sourceId)
210
+ const scene = this.context.scene;
211
+ scene.environment = null;
212
+ }
213
+
214
+ /** @internal */
215
+ async internalGetSceneLightingData(sourceId: SourceIdentifier): Promise<SphericalHarmonicsData> {
216
+ if (debug)
217
+ console.log("GET SCENE LIGHT DATA", sourceId);
218
+
219
+ // const existing = this.getReflection(sourceId);
220
+ // const sh = existing?.getSphericalHarmonicsArray(this.sceneLightSettings?.ambientIntensity ?? 1);
221
+ // if (sh) {
222
+ // console.log("HAS EXISTING", sh, existing);
223
+ // return sh;
224
+ // }
225
+
226
+ // fallback
227
+ if (this._waitPromise) return this._waitPromise;
228
+ this._waitPromise = new Promise((res, _rej) => {
229
+ let interval = setInterval(async () => {
230
+ const ex = this.internalGetReflection(sourceId);
231
+ if (ex) {
232
+ clearInterval(interval);
233
+ res(ex.getSphericalHarmonicsArray(this.environmentIntensity ?? 1)!);
234
+ }
235
+ }, 10);
236
+ });
237
+ return this._waitPromise;
238
+ }
239
+
240
+ private _waitPromise?: Promise<SphericalHarmonicsData>;
241
+ private _lighting: { [sourceId: SourceIdentifier]: LightData } = {};
242
+
243
+ }
244
+
245
+ export class LightData {
246
+
247
+ get Source(): Texture { return this._source; }
248
+ get Array(): number[] | undefined { return this._sphericalHarmonicsArray; }
249
+
250
+ private _context: Context;
251
+ private _source: Texture;
252
+ private _sphericalHarmonics: SphericalHarmonics3 | null = null;
253
+ private _sphericalHarmonicsArray?: number[];
254
+ private _ambientScale: number = 1;
255
+ private _lightProbe?: LightProbe;
256
+
257
+ constructor(context: Context, tex: Texture, ambientScale: number = 1) {
258
+ this._context = context;
259
+ this._source = tex;
260
+ this._ambientScale = ambientScale;
261
+ tex.mapping = EquirectangularReflectionMapping;
262
+ tex.encoding = sRGBEncoding;
263
+ }
264
+
265
+ getSphericalHarmonicsArray(intensityFactor: number = 1): SphericalHarmonicsData | null {
266
+ if (this._sphericalHarmonicsArray?.length && this._source) {
267
+ return { array: this._sphericalHarmonicsArray, texture: this._source, lightProbe: this._lightProbe };
268
+ }
269
+
270
+ try {
271
+ const reflection = this._source;
272
+ let rt: WebGLCubeRenderTarget | null = null;
273
+ if (reflection) {
274
+ if (debug) console.log("GENERATING LIGHT PROBE", reflection, this.Source);
275
+ const size = Math.min(reflection.image.width, 512);
276
+ const target = new WebGLCubeRenderTarget(size);
277
+ rt = target.fromEquirectangularTexture(this._context.renderer, reflection);
278
+ // Not sure why we did assign the resulting texture here again but this causes rendering to break when toggling env lighting (e.g. on website) because this texture will then be set as the scene.environment
279
+ // this._source = rt.texture;
280
+ }
281
+
282
+ this._sphericalHarmonicsArray = [];
283
+ if (rt) {
284
+ const sampledProbe = LightProbeGenerator.fromCubeRenderTarget(this._context.renderer, rt);
285
+ this._lightProbe = sampledProbe;
286
+ const lightFactor = (this._ambientScale * (intensityFactor * intensityFactor * Math.PI * .5)) - 1;
287
+ // console.log(intensityFactor, lightFactor);
288
+ this._sphericalHarmonics = sampledProbe.sh;
289
+ this._sphericalHarmonicsArray = this._sphericalHarmonics.toArray();
290
+ if (this._sphericalHarmonicsArray) {
291
+ const factor = ((intensityFactor) / (Math.PI * .5));
292
+ for (let i = 0; i < this._sphericalHarmonicsArray.length; i++) {
293
+ this._sphericalHarmonicsArray[i] *= factor;
294
+ }
295
+ sampledProbe.sh.scale(lightFactor);
296
+ if (this._source)
297
+ return { array: this._sphericalHarmonicsArray, texture: this._source, lightProbe: sampledProbe };
298
+ }
299
+ }
300
+ }
301
+ catch (err) {
302
+ console.error(err);
303
+ }
304
+
305
+ return null;
306
+ }
307
+ }
src/engine/extensions/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./extensions"
2
+ export * from "./NEEDLE_animator_controller_model"
3
+ export * from "./NEEDLE_progressive"
4
+ export { CustomShader } from "./NEEDLE_techniques_webgl"
5
+ export { SceneLightSettings } from "./NEEDLE_lighting_settings"