Needle Engine

Changes between version 3.6.4 and 3.6.5
Files changed (8) hide show
  1. src/engine-components/AnimationCurve.ts +31 -3
  2. src/engine-components/postprocessing/Effects/ColorAdjustments.ts +2 -1
  3. src/engine/engine_context.ts +15 -3
  4. src/engine/engine_element_loading.ts +3 -3
  5. src/engine/engine_element.ts +12 -6
  6. src/engine/extensions/NEEDLE_components.ts +7 -2
  7. src/engine-components/NestedGltf.ts +2 -0
  8. src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
src/engine-components/AnimationCurve.ts CHANGED
@@ -42,9 +42,10 @@
42
42
  if (hasNextKeyframe) {
43
43
  const nextKf = this.keys[i+1];
44
44
  // if the next
45
- if(nextKf.time < time) continue;
46
- const t = Mathf.remap(time, kf.time, nextKf.time, 0, 1);
47
- return Mathf.lerp(kf.value, nextKf.value, t);
45
+ if (nextKf.time < time) continue;
46
+ // tangents are set to Infinity if interpolation is set to constant - in that case we should always return the floored value
47
+ if (!isFinite(kf.outTangent) || !isFinite(nextKf.inTangent)) return kf.value;
48
+ return AnimationCurve.interpolateValue(time, kf, nextKf);
48
49
  }
49
50
  else {
50
51
  return kf.value;
@@ -53,4 +54,31 @@
53
54
  }
54
55
  return this.keys[this.keys.length - 1].value;
55
56
  }
57
+
58
+ static interpolateValue(time: number, keyframe1: Keyframe, keyframe2: Keyframe): number {
59
+ const startTime1 = keyframe1.time;
60
+ const startValue1 = keyframe1.value;
61
+ const outTangent1 = keyframe1.outTangent;
62
+
63
+ const startTime2 = keyframe2.time;
64
+ const startValue2 = keyframe2.value;
65
+ const inTangent2 = keyframe2.inTangent;
66
+
67
+ // could be precomputed and stored in the keyframes maybe
68
+ const timeDifference = startTime2 - startTime1;
69
+ const timeDifferenceSquared = timeDifference * timeDifference;
70
+ const timeDifferenceCubed = timeDifferenceSquared * timeDifference;
71
+
72
+ const a = ((outTangent1 + inTangent2) * timeDifference - 2 * (startValue2 - startValue1)) / timeDifferenceCubed;
73
+ const b = (3 * (startValue2 - startValue1) - (inTangent2 + 2 * outTangent1) * timeDifference) / timeDifferenceSquared;
74
+ const c = outTangent1;
75
+ const d = startValue1;
76
+
77
+ const timeDelta = time - startTime1;
78
+ const timeDeltaSquared = timeDelta * timeDelta;
79
+ const timeDeltaCubed = timeDeltaSquared * timeDelta;
80
+
81
+ return a * timeDeltaCubed + b * timeDeltaSquared + c * timeDelta + d;
82
+ }
83
+
56
84
  }
src/engine-components/postprocessing/Effects/ColorAdjustments.ts CHANGED
@@ -63,7 +63,8 @@
63
63
  this.postExposure!.onValueChanged = v => {
64
64
  if (this.context.renderer.toneMapping === NoToneMapping)
65
65
  this.context.renderer.toneMapping = LinearToneMapping;
66
- this.context.renderer.toneMappingExposure = v;
66
+ if (this.postExposure.overrideState)
67
+ this.context.renderer.toneMappingExposure = v;
67
68
  }
68
69
  this.contrast!.onValueChanged = v => {
69
70
  brightnesscontrast.contrast = v;
src/engine/engine_context.ts CHANGED
@@ -99,25 +99,37 @@
99
99
  return VERSION;
100
100
  }
101
101
 
102
+ /** The currently active context. Only set during the update loops */
102
103
  static get Current(): Context {
103
104
  return ContextRegistry.Current as Context;
104
105
  }
105
106
 
107
+ /** @internal this property should not be set by user code */
106
108
  static set Current(context: Context) {
107
109
  ContextRegistry.Current = context;
108
110
  }
109
111
 
110
112
  name: string;
111
113
  alias: string | undefined | null;
112
- isManagedExternally: boolean = false;
114
+ /** When the renderer or camera are managed by an external process (e.g. when running in r3f context) */
115
+ readonly isManagedExternally: boolean = false;
116
+ /** set to true to pause the update loop. You can receive an event for it in your components.
117
+ * Note that script updates will not be called when paused */
113
118
  isPaused: boolean = false;
119
+ /** When enabled the application will run while not visible on the page */
114
120
  runInBackground: boolean = false;
115
- targetFrameRate?: number;
121
+ /**
122
+ * Set to the target framerate you want your application to run in (you can use ?stats to check the fps)
123
+ * Set to undefined if you want to run at the maximum framerate
124
+ */
125
+ targetFrameRate?: number = 60;
116
126
 
117
127
  /** used to append to loaded assets */
118
128
  hash?: string;
119
129
 
130
+ /** the <needle-engine> HTML element */
120
131
  domElement: HTMLElement;
132
+
121
133
  get resolutionScaleFactor() { return this._resolutionScaleFactor; }
122
134
  /** use to scale the resolution up or down of the renderer. default is 1 */
123
135
  set resolutionScaleFactor(val: number) {
@@ -754,7 +766,7 @@
754
766
 
755
767
  if (this.isInXR === false && this.targetFrameRate !== undefined) {
756
768
  this._accumulatedTime += this._framerateClock.getDelta();
757
- if (this._accumulatedTime < (1 / this.targetFrameRate)) {
769
+ if (this._accumulatedTime < (1 / (this.targetFrameRate + 1))) {
758
770
  return;
759
771
  }
760
772
  this._accumulatedTime = 0;
src/engine/engine_element_loading.ts CHANGED
@@ -5,8 +5,8 @@
5
5
  import { logoSVG } from "./assets"
6
6
  import { hasCommercialLicense, hasProLicense } from "./engine_license";
7
7
 
8
- const debug = getParam("debugloadingbar");
9
- const debugRendering = getParam("debugloadingbarrendering");
8
+ const debug = getParam("debugloading");
9
+ const debugRendering = getParam("debugloadingrendering");
10
10
 
11
11
  declare type LoadingStyleOption = "dark" | "light";
12
12
 
@@ -192,7 +192,7 @@
192
192
  this._loadingElement.style.display = "flex";
193
193
  this._loadingElement.style.alignItems = "center";
194
194
  this._loadingElement.style.justifyContent = "center";
195
- this._loadingElement.style.zIndex = "1000";
195
+ this._loadingElement.style.zIndex = Number.MAX_SAFE_INTEGER.toString();
196
196
  this._loadingElement.style.flexDirection = "column";
197
197
  this._loadingElement.style.pointerEvents = "none";
198
198
  this._loadingElement.style.color = "white";
src/engine/engine_element.ts CHANGED
@@ -8,8 +8,9 @@
8
8
  import { NeedleGltfLoader } from "./engine_scenetools";
9
9
  import { GLTF, INeedleEngineComponent, LoadedGLTF } from "./engine_types";
10
10
  import { isLocalNetwork } from "./engine_networking_utils";
11
- import { showBalloonWarning } from "./debug";
11
+ import { isDevEnvironment, showBalloonWarning } from "./debug";
12
12
  import { destroy } from "./engine_gameobject";
13
+ import { hasCommercialLicense } from "./engine_license";
13
14
 
14
15
  //
15
16
  // registering loader here too to make sure it's imported when using engine via vanilla js
@@ -111,7 +112,7 @@
111
112
  this.setAttribute("src", global);
112
113
  }
113
114
  }
114
-
115
+
115
116
  // we have to wait because codegen does set the src attribute when it's loaded
116
117
  // which might happen after the element is connected
117
118
  // if the `src` is then still null we want to initialize the default scene
@@ -181,7 +182,7 @@
181
182
 
182
183
  if (!this.isConnected) return;
183
184
  if (!this._context) {
184
- if(debug) console.warn("Create new context");
185
+ if (debug) console.warn("Create new context");
185
186
  this._context = new Context({ domElement: this });
186
187
  }
187
188
 
@@ -205,15 +206,20 @@
205
206
  this.classList.add("loading");
206
207
 
207
208
  // Loading start events
208
- const allowOverridingDefaultLoading = true;
209
+ const allowOverridingDefaultLoading = hasCommercialLicense();
209
210
  // default loading can be overriden by calling preventDefault in the onload start event
210
- const useDefaultLoading = this.dispatchEvent(new CustomEvent("loadstart", {
211
+ let useDefaultLoading = this.dispatchEvent(new CustomEvent("loadstart", {
211
212
  detail: {
212
213
  context: this._context,
213
214
  alias: alias
214
215
  },
215
- cancelable: allowOverridingDefaultLoading
216
+ cancelable: true
216
217
  }));
218
+ if (useDefaultLoading === false && !allowOverridingDefaultLoading) {
219
+ useDefaultLoading = true;
220
+ console.warn("Needle Engine: You need a commercial license to override the default loading view. Visit https://needle.tools/pricing to buy a license.");
221
+ if(isDevEnvironment()) showBalloonWarning("You need a <a target=\"_blank\" href=\"https://needle.tools/pricing\">commercial license</a> to override the default loading view");
222
+ }
217
223
  if (!this._loadingView && useDefaultLoading)
218
224
  this._loadingView = new EngineLoadingView(this);
219
225
  if (useDefaultLoading)
src/engine/extensions/NEEDLE_components.ts CHANGED
@@ -212,14 +212,19 @@
212
212
  console.log("Serialized data", JSON.parse(JSON.stringify(serializedData)));
213
213
 
214
214
  if (serializedData && this.parser) {
215
- tasks.push(resolveReferences(this.parser, serializedData));
215
+ tasks.push(
216
+ resolveReferences(this.parser, serializedData)
217
+ .catch(e => console.error(`Error while resolving references (see console for details)\n`, e, obj, serializedData))
218
+ );
216
219
  }
217
220
 
218
221
  obj.userData = obj.userData || {};
219
222
  obj.userData[builtinComponentKeyName] = obj.userData[builtinComponentKeyName] || [];
220
223
  obj.userData[builtinComponentKeyName].push(serializedData);
221
224
  }
222
- await Promise.all(tasks);
225
+ await Promise.all(tasks).catch((e) => {
226
+ console.error("Error while loading components", e);
227
+ });
223
228
  }
224
229
  }
225
230
 
src/engine-components/NestedGltf.ts CHANGED
@@ -43,6 +43,8 @@
43
43
  res.matrixAutoUpdate = true;
44
44
  res.layers.disableAll();
45
45
  res.layers.set(this.layer);
46
+
47
+ this.dispatchEvent(new CustomEvent("loaded", { detail: {instance: res, assetReference: this.filePath }}));
46
48
  }
47
49
  if (debug) console.log("Nested loading done:", this.filePath?.uri ?? this.filePath, res);
48
50
  }
src/engine-components/export/usdz/ThreeUSDZExporter.ts CHANGED
@@ -1299,7 +1299,7 @@
1299
1299
 
1300
1300
  } else {
1301
1301
 
1302
- inputs.push( `${pad}float inputs:roughness = ${material.roughness}` );
1302
+ inputs.push( `${pad}float inputs:roughness = ${material.roughness !== undefined ? material.roughness : 1 }` );
1303
1303
 
1304
1304
  }
1305
1305
 
@@ -1311,7 +1311,7 @@
1311
1311
 
1312
1312
  } else {
1313
1313
 
1314
- inputs.push( `${pad}float inputs:metallic = ${material.metalness}` );
1314
+ inputs.push( `${pad}float inputs:metallic = ${material.metalness !== undefined ? material.metalness : 0 }` );
1315
1315
 
1316
1316
  }