@@ -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
|
-
|
47
|
-
|
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
|
}
|
@@ -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.
|
66
|
+
if (this.postExposure.overrideState)
|
67
|
+
this.context.renderer.toneMappingExposure = v;
|
67
68
|
}
|
68
69
|
this.contrast!.onValueChanged = v => {
|
69
70
|
brightnesscontrast.contrast = v;
|
@@ -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
|
-
|
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
|
-
|
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;
|
@@ -5,8 +5,8 @@
|
|
5
5
|
import { logoSVG } from "./assets"
|
6
6
|
import { hasCommercialLicense, hasProLicense } from "./engine_license";
|
7
7
|
|
8
|
-
const debug = getParam("
|
9
|
-
const debugRendering = getParam("
|
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 =
|
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";
|
@@ -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 =
|
209
|
+
const allowOverridingDefaultLoading = hasCommercialLicense();
|
209
210
|
// default loading can be overriden by calling preventDefault in the onload start event
|
210
|
-
|
211
|
+
let useDefaultLoading = this.dispatchEvent(new CustomEvent("loadstart", {
|
211
212
|
detail: {
|
212
213
|
context: this._context,
|
213
214
|
alias: alias
|
214
215
|
},
|
215
|
-
cancelable:
|
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)
|
@@ -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(
|
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
|
|
@@ -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
|
}
|
@@ -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
|
}
|