@@ -87,8 +87,9 @@
|
|
87
87
|
/**@deprecated use setBool */
|
88
88
|
SetBool(name: string | number, val: boolean) { this.setBool(name, val); }
|
89
89
|
setBool(name: string | number, value: boolean) {
|
90
|
+
if(this.runtimeAnimatorController?.getBool(name) !== value)
|
91
|
+
this._parametersAreDirty = true;
|
90
92
|
this.runtimeAnimatorController?.setBool(name, value);
|
91
|
-
this._parametersAreDirty = true;
|
92
93
|
}
|
93
94
|
|
94
95
|
/**@deprecated use getBool */
|
@@ -100,8 +101,9 @@
|
|
100
101
|
/**@deprecated use setFloat */
|
101
102
|
SetFloat(name: string | number, val: number) { this.setFloat(name, val); }
|
102
103
|
setFloat(name: string | number, val: number) {
|
104
|
+
if(this.runtimeAnimatorController?.getFloat(name) !== val)
|
105
|
+
this._parametersAreDirty = true;
|
103
106
|
this.runtimeAnimatorController?.setFloat(name, val);
|
104
|
-
this._parametersAreDirty = true;
|
105
107
|
}
|
106
108
|
|
107
109
|
/**@deprecated use getFloat */
|
@@ -113,8 +115,9 @@
|
|
113
115
|
/**@deprecated use setInteger */
|
114
116
|
SetInteger(name: string | number, val: number) { this.setInteger(name, val); }
|
115
117
|
setInteger(name: string | number, val: number) {
|
118
|
+
if(this.runtimeAnimatorController?.getInteger(name) !== val)
|
119
|
+
this._parametersAreDirty = true;
|
116
120
|
this.runtimeAnimatorController?.setInteger(name, val);
|
117
|
-
this._parametersAreDirty = true;
|
118
121
|
}
|
119
122
|
|
120
123
|
/**@deprecated use getInteger */
|
@@ -483,8 +483,9 @@
|
|
483
483
|
|
484
484
|
offsetNormalized = Math.max(0, Math.min(1, offsetNormalized));
|
485
485
|
if (state.cycleOffsetParameter) {
|
486
|
-
|
486
|
+
let val = this.getFloat(state.cycleOffsetParameter);
|
487
487
|
if (typeof val === "number") {
|
488
|
+
if (val < 0) val += 1;
|
488
489
|
offsetNormalized += val;
|
489
490
|
offsetNormalized %= 1;
|
490
491
|
}
|
@@ -494,13 +495,13 @@
|
|
494
495
|
offsetNormalized += state.cycleOffset
|
495
496
|
offsetNormalized %= 1;
|
496
497
|
}
|
497
|
-
|
498
498
|
if (action.isRunning())
|
499
499
|
action.stop();
|
500
500
|
action.reset();
|
501
501
|
action.enabled = true;
|
502
502
|
const duration = state.motion.clip!.duration;
|
503
|
-
|
503
|
+
// if we are looping to the same state we don't want to offset the current start time
|
504
|
+
action.time = isSelf ? 0 : offsetNormalized * duration;
|
504
505
|
if (action.timeScale < 0) action.time = duration - action.time;
|
505
506
|
action.clampWhenFinished = true;
|
506
507
|
action.setLoop(LoopOnce, 0);
|
@@ -1012,7 +1013,7 @@
|
|
1012
1013
|
|
1013
1014
|
}
|
1014
1015
|
onDeserialize(data: AnimatorControllerModel & { __type?: string }, context: SerializationContext) {
|
1015
|
-
if (context.type === AnimatorController && data
|
1016
|
+
if (context.type === AnimatorController && data?.__type === "AnimatorController")
|
1016
1017
|
return new AnimatorController(data);
|
1017
1018
|
return undefined;
|
1018
1019
|
}
|
@@ -271,10 +271,10 @@
|
|
271
271
|
image.setupState(disabledState);
|
272
272
|
}
|
273
273
|
|
274
|
-
private getFinalColor(col: RGBAColor, col2?: RGBAColor) {
|
274
|
+
private getFinalColor(col: RGBAColor, col2?: RGBAColor): RGBAColor {
|
275
275
|
if (col2) {
|
276
|
-
return col.clone().multiply(col2);
|
276
|
+
return col.clone().multiply(col2).convertLinearToSRGB() as RGBAColor;
|
277
277
|
}
|
278
|
-
return col.clone();
|
278
|
+
return col.clone().convertLinearToSRGB() as RGBAColor;
|
279
279
|
}
|
280
280
|
}
|
@@ -11,6 +11,7 @@
|
|
11
11
|
import { EquirectangularReflectionMapping, OrthographicCamera, PerspectiveCamera, Ray, SRGBColorSpace, Vector3 } from "three";
|
12
12
|
import { OrbitControls } from "./OrbitControls.js";
|
13
13
|
import { RenderTexture } from "../engine/engine_texture.js";
|
14
|
+
import { Texture } from "three";
|
14
15
|
|
15
16
|
export enum ClearFlags {
|
16
17
|
Skybox = 1,
|
@@ -172,18 +173,18 @@
|
|
172
173
|
|
173
174
|
private _backgroundColor?: RGBAColor;
|
174
175
|
private _fov?: number;
|
175
|
-
private _cam:
|
176
|
+
private _cam: PerspectiveCamera | OrthographicCamera | null = null;
|
176
177
|
private _clearFlags: ClearFlags = ClearFlags.SolidColor;
|
177
178
|
private _skybox?: CameraSkybox;
|
178
179
|
|
179
|
-
public get cam():
|
180
|
+
public get cam(): PerspectiveCamera | OrthographicCamera {
|
180
181
|
if (this.activeAndEnabled)
|
181
182
|
this.buildCamera();
|
182
183
|
return this._cam!;
|
183
184
|
}
|
184
185
|
|
185
|
-
private static _origin:
|
186
|
-
private static _direction:
|
186
|
+
private static _origin: Vector3 = new Vector3();
|
187
|
+
private static _direction: Vector3 = new Vector3();
|
187
188
|
public screenPointToRay(x: number, y: number, ray?: Ray): Ray {
|
188
189
|
const cam = this.cam;
|
189
190
|
const origin = Camera._origin;
|
@@ -234,6 +235,11 @@
|
|
234
235
|
|
235
236
|
onBeforeRender() {
|
236
237
|
if (this._cam) {
|
238
|
+
|
239
|
+
// because the background color may be animated!
|
240
|
+
if (this._clearFlags === ClearFlags.SolidColor)
|
241
|
+
this.applyClearFlagsIfIsActiveCamera();
|
242
|
+
|
237
243
|
if (this._targetTexture) {
|
238
244
|
if (this.context.isManagedExternally) {
|
239
245
|
// TODO: rendering with r3f renderer does throw an shader error for some reason?
|
@@ -264,7 +270,7 @@
|
|
264
270
|
const cameraAlreadyCreated = this.gameObject["isCamera"];
|
265
271
|
|
266
272
|
// TODO: when exporting from blender we already have a camera in the children
|
267
|
-
let cam:
|
273
|
+
let cam: PerspectiveCamera | OrthographicCamera | null = null;
|
268
274
|
if (cameraAlreadyCreated) {
|
269
275
|
cam = this.gameObject as any;
|
270
276
|
cam?.layers.enableAll();
|
@@ -272,7 +278,7 @@
|
|
272
278
|
this._fov = cam.fov;
|
273
279
|
}
|
274
280
|
else
|
275
|
-
cam = this.gameObject.children[0] as
|
281
|
+
cam = this.gameObject.children[0] as PerspectiveCamera | OrthographicCamera | null;
|
276
282
|
if (cam && cam.isCamera) {
|
277
283
|
if (cam instanceof PerspectiveCamera) {
|
278
284
|
if (this._fov)
|
@@ -370,25 +376,28 @@
|
|
370
376
|
static backgroundShouldBeTransparent(context: Context) {
|
371
377
|
const session = context.renderer.xr?.getSession();
|
372
378
|
if (!session) return false;
|
379
|
+
if (typeof session["_transparent"] === "boolean") {
|
380
|
+
return session["_transparent"];
|
381
|
+
}
|
373
382
|
const environmentBlendMode = session.environmentBlendMode;
|
374
383
|
if (debug)
|
375
384
|
showBalloonMessage("Environment blend mode: " + environmentBlendMode + " on " + navigator.userAgent);
|
376
|
-
|
377
|
-
|
385
|
+
let transparent = environmentBlendMode === 'additive' || environmentBlendMode === 'alpha-blend';
|
378
386
|
if (context.xrSessionMode === XRSessionMode.ImmersiveAR) {
|
379
387
|
if (environmentBlendMode === "opaque") {
|
380
388
|
// workaround for Quest 2 returning opaque when it should be alpha-blend
|
381
389
|
// check user agent if this is the Quest browser and return true if so
|
382
390
|
if (navigator.userAgent?.includes("OculusBrowser")) {
|
383
|
-
|
391
|
+
transparent = true;
|
384
392
|
}
|
385
393
|
// Mozilla WebXR Viewer
|
386
394
|
else if (navigator.userAgent?.includes("Mozilla") && navigator.userAgent?.includes("Mobile WebXRViewer/v2")) {
|
387
|
-
|
395
|
+
transparent = true;
|
388
396
|
}
|
389
397
|
}
|
390
398
|
}
|
391
399
|
|
400
|
+
session["_transparent"] = transparent;
|
392
401
|
return transparent;
|
393
402
|
}
|
394
403
|
}
|
@@ -397,7 +406,7 @@
|
|
397
406
|
class CameraSkybox {
|
398
407
|
|
399
408
|
private _camera: Camera;
|
400
|
-
private _skybox?:
|
409
|
+
private _skybox?: Texture;
|
401
410
|
|
402
411
|
get context() { return this._camera?.context; }
|
403
412
|
|
@@ -406,9 +415,12 @@
|
|
406
415
|
}
|
407
416
|
|
408
417
|
enable() {
|
409
|
-
this._skybox = this.context.lightmaps.tryGetSkybox(this._camera.sourceId) as
|
418
|
+
this._skybox = this.context.lightmaps.tryGetSkybox(this._camera.sourceId) as Texture;
|
410
419
|
if (!this._skybox) {
|
411
|
-
|
420
|
+
if (!this["_did_log_failed_to_find_skybox"]) {
|
421
|
+
this["_did_log_failed_to_find_skybox"] = true;
|
422
|
+
console.warn(`Camera \"${this._camera.name}\" failed to load/find skybox texture`, this._camera.sourceId, this.context.lightmaps, "Current background: ", this.context.scene.background);
|
423
|
+
}
|
412
424
|
if (debug || isDevEnvironment())
|
413
425
|
showBalloonWarning(`Camera \"${this._camera.name}\" has no skybox texture`);
|
414
426
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
import { RGBAColor } from "./js-extensions/RGBAColor.js";
|
6
6
|
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
|
7
7
|
import { getCameraController } from "../engine/engine_camera.js";
|
8
|
-
import { Camera } from "./Camera.js";
|
8
|
+
import { Camera, ClearFlags } from "./Camera.js";
|
9
9
|
import { NeedleEngineHTMLElement } from "../engine/engine_element.js";
|
10
10
|
import { getParam } from "../engine/engine_utils.js";
|
11
11
|
import { Context } from "../engine/engine_context.js";
|
@@ -22,12 +22,13 @@
|
|
22
22
|
|
23
23
|
const camInstance = new Camera();
|
24
24
|
camInstance.sourceId = evt.files?.[0]?.src ?? "unknown"
|
25
|
-
|
26
|
-
if
|
27
|
-
|
25
|
+
|
26
|
+
// Set the clearFlags to a skybox if we have one OR if the user set a skybox image attribute
|
27
|
+
if(evt.context.domElement.getAttribute("skybox-image")?.length || 0 > 0 || (evt.context as Context).lightmaps.tryGetSkybox(camInstance.sourceId))
|
28
|
+
camInstance.clearFlags = ClearFlags.Skybox;
|
28
29
|
else
|
29
30
|
// TODO provide a nice default skybox
|
30
|
-
camInstance.clearFlags =
|
31
|
+
camInstance.clearFlags = ClearFlags.SolidColor;
|
31
32
|
camInstance.backgroundColor = new RGBAColor(0.5, 0.5, 0.5, 1);
|
32
33
|
camInstance.fieldOfView = 35;
|
33
34
|
// TODO: can we store the backgroundBlurriness in the gltf file somewhere except inside the camera?
|
@@ -51,6 +51,7 @@
|
|
51
51
|
export { ColorBySpeedModule } from "../ParticleSystemModules.js";
|
52
52
|
export { ColorOverLifetimeModule } from "../ParticleSystemModules.js";
|
53
53
|
export { Component } from "../Component.js";
|
54
|
+
export { ContactShadows } from "../ContactShadows.js";
|
54
55
|
export { ControlTrackHandler } from "../timeline/TimelineTracks.js";
|
55
56
|
export { CustomBranding } from "../export/usdz/USDZExporter.js";
|
56
57
|
export { Deletable } from "../DeleteBox.js";
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
const debug = getParam("debugdebug");
|
6
6
|
let hide = false;
|
7
|
-
if (getParam("noerrors")) hide = true;
|
7
|
+
if (getParam("noerrors") || getParam("nooverlaymessages")) hide = true;
|
8
8
|
|
9
9
|
const globalErrorContainerKey = "needle_engine_global_error_container";
|
10
10
|
|
@@ -7,6 +7,8 @@
|
|
7
7
|
ContextCreationStart = "ContextCreationStart",
|
8
8
|
/** Called when the context has been created, before the first frame */
|
9
9
|
ContextCreated = "ContextCreated",
|
10
|
+
/** Called after the first frame has been rendered after creation */
|
11
|
+
ContextFirstFrameRendered = "ContextFirstFrameRendered",
|
10
12
|
/** Called when the context has been destroyed */
|
11
13
|
ContextDestroyed = "ContextDestroyed",
|
12
14
|
/** Called when the context could not find a camera during creation */
|
@@ -173,6 +173,13 @@
|
|
173
173
|
*/
|
174
174
|
targetFrameRate?: number | { value?: number };
|
175
175
|
|
176
|
+
/** Use a higher number for more accurate physics simulation.
|
177
|
+
* When undefined physics steps will be 1 for mobile devices and 5 for desktop devices
|
178
|
+
* Set to 0 to disable physics updates
|
179
|
+
* TODO: changing physics steps is currently not supported because then forces that we get from the character controller and rigidbody et al are not correct anymore - this needs to be properly tested before making this configureable
|
180
|
+
*/
|
181
|
+
private physicsSteps?: number = 1;
|
182
|
+
|
176
183
|
/** used to append to loaded assets */
|
177
184
|
hash?: string;
|
178
185
|
|
@@ -941,6 +948,14 @@
|
|
941
948
|
}
|
942
949
|
}
|
943
950
|
|
951
|
+
/** Call to **manually** perform physics steps.
|
952
|
+
* By default the context uses the `physicsSteps` property to perform steps during the update loop
|
953
|
+
* If you just want to increase the accuracy of physics you can instead set the `physicsSteps` property to a higher value
|
954
|
+
* */
|
955
|
+
public updatePhysics(steps: number) {
|
956
|
+
this.internalUpdatePhysics(steps);
|
957
|
+
}
|
958
|
+
|
944
959
|
private _lastTimestamp = 0;
|
945
960
|
private _accumulatedTime = 0;
|
946
961
|
private _dispatchReadyAfterFrame = false;
|
@@ -1039,18 +1054,12 @@
|
|
1039
1054
|
this.executeCoroutines(FrameEvent.LateUpdate);
|
1040
1055
|
if (this.onHandlePaused()) return false;
|
1041
1056
|
|
1042
|
-
if (this.
|
1043
|
-
|
1044
|
-
const dt = this.time.deltaTime / physicsSteps;
|
1045
|
-
for (let i = 0; i < physicsSteps; i++) {
|
1046
|
-
this._currentFrameEvent = FrameEvent.PrePhysicsStep;
|
1047
|
-
this.executeCoroutines(FrameEvent.PrePhysicsStep);
|
1048
|
-
this.physics.engine.step(dt);
|
1049
|
-
this._currentFrameEvent = FrameEvent.PostPhysicsStep;
|
1050
|
-
this.executeCoroutines(FrameEvent.PostPhysicsStep);
|
1051
|
-
}
|
1052
|
-
this.physics.engine.postStep();
|
1057
|
+
if (this.physicsSteps === undefined) {
|
1058
|
+
this.physicsSteps = 1;
|
1053
1059
|
}
|
1060
|
+
if (this.physics.engine && this.physicsSteps > 0) {
|
1061
|
+
this.internalUpdatePhysics(this.physicsSteps);
|
1062
|
+
}
|
1054
1063
|
|
1055
1064
|
if (this.onHandlePaused()) return false;
|
1056
1065
|
|
@@ -1085,6 +1094,21 @@
|
|
1085
1094
|
return true;
|
1086
1095
|
}
|
1087
1096
|
|
1097
|
+
private internalUpdatePhysics(steps: number) {
|
1098
|
+
if (!this.physics.engine) return false;
|
1099
|
+
const physicsSteps = steps;
|
1100
|
+
const dt = this.time.deltaTime / physicsSteps;
|
1101
|
+
for (let i = 0; i < physicsSteps; i++) {
|
1102
|
+
this._currentFrameEvent = FrameEvent.PrePhysicsStep;
|
1103
|
+
this.executeCoroutines(FrameEvent.PrePhysicsStep);
|
1104
|
+
this.physics.engine.step(dt);
|
1105
|
+
this._currentFrameEvent = FrameEvent.PostPhysicsStep;
|
1106
|
+
this.executeCoroutines(FrameEvent.PostPhysicsStep);
|
1107
|
+
}
|
1108
|
+
this.physics.engine.postStep();
|
1109
|
+
return true;
|
1110
|
+
}
|
1111
|
+
|
1088
1112
|
private internalOnRender() {
|
1089
1113
|
if (!this.isManagedExternally) {
|
1090
1114
|
looputils.runPrewarm(this);
|
@@ -1127,6 +1151,7 @@
|
|
1127
1151
|
if (this._dispatchReadyAfterFrame) {
|
1128
1152
|
this._dispatchReadyAfterFrame = false;
|
1129
1153
|
this.domElement.dispatchEvent(new CustomEvent("ready"));
|
1154
|
+
ContextRegistry.dispatchCallback(ContextEvent.ContextFirstFrameRendered, this);
|
1130
1155
|
}
|
1131
1156
|
}
|
1132
1157
|
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import { PlaneGeometry, MeshBasicMaterial, DoubleSide, Mesh, Material, MeshStandardMaterial, BoxGeometry, SphereGeometry } from "three"
|
1
|
+
import { PlaneGeometry, MeshBasicMaterial, DoubleSide, Mesh, Material, MeshStandardMaterial, BoxGeometry, SphereGeometry, ColorRepresentation } from "three"
|
2
2
|
|
3
|
-
|
4
3
|
export enum PrimitiveType {
|
5
4
|
Quad = 0,
|
6
5
|
Cube = 1,
|
@@ -16,20 +15,21 @@
|
|
16
15
|
|
17
16
|
static createPrimitive(type: PrimitiveType, opts?: ObjectOptions): Mesh {
|
18
17
|
let obj: Mesh;
|
18
|
+
const color = 0xffffff;
|
19
19
|
switch (type) {
|
20
20
|
case PrimitiveType.Quad:
|
21
21
|
const quadGeo = new PlaneGeometry(1, 1, 1, 1);
|
22
|
-
const quadMat = opts?.material ?? new
|
22
|
+
const quadMat = opts?.material ?? new MeshStandardMaterial({ color: color });
|
23
23
|
obj = new Mesh(quadGeo, quadMat);
|
24
24
|
break;
|
25
25
|
case PrimitiveType.Cube:
|
26
26
|
const boxGeo = new BoxGeometry(1, 1, 1);
|
27
|
-
const boxMat = opts?.material ?? new MeshStandardMaterial({ color:
|
27
|
+
const boxMat = opts?.material ?? new MeshStandardMaterial({ color: color });
|
28
28
|
obj = new Mesh(boxGeo, boxMat);
|
29
29
|
break;
|
30
30
|
case PrimitiveType.Sphere:
|
31
31
|
const sphereGeo = new SphereGeometry(.5, 16, 16);
|
32
|
-
const sphereMat = opts?.material ?? new MeshStandardMaterial({ color:
|
32
|
+
const sphereMat = opts?.material ?? new MeshStandardMaterial({ color: color });
|
33
33
|
obj = new Mesh(sphereGeo, sphereMat);
|
34
34
|
break;
|
35
35
|
}
|
@@ -3,11 +3,13 @@
|
|
3
3
|
import { getWorldPosition, lookAtObject, setWorldPositionXYZ } from './engine_three_utils.js';
|
4
4
|
import type { Vec3, Vec4 } from './engine_types.js';
|
5
5
|
import ThreeMeshUI, { Inline, Text } from "three-mesh-ui"
|
6
|
+
import { getParam } from './engine_utils.js';
|
6
7
|
|
7
8
|
const _tmp = new Vector3();
|
8
9
|
const _tmp2 = new Vector3();
|
9
10
|
const _quat = new Quaternion();
|
10
11
|
|
12
|
+
const debug = getParam("debuggizmos");
|
11
13
|
const defaultColor: ColorRepresentation = 0x888888;
|
12
14
|
|
13
15
|
export type LabelHandle = {
|
@@ -167,7 +169,8 @@
|
|
167
169
|
if (backgroundColor && typeof backgroundColor === "string" && backgroundColor?.length >= 8 && backgroundColor.startsWith("#")) {
|
168
170
|
opacity = parseInt(backgroundColor.substring(7), 16) / 255;
|
169
171
|
backgroundColor = backgroundColor.substring(0, 7);
|
170
|
-
|
172
|
+
if (debug)
|
173
|
+
console.log(backgroundColor, opacity);
|
171
174
|
}
|
172
175
|
else if (typeof backgroundColor === "object" && backgroundColor["a"] !== undefined) {
|
173
176
|
opacity = backgroundColor["a"]
|
@@ -184,13 +187,13 @@
|
|
184
187
|
backgroundColor: backgroundColor ?? undefined,
|
185
188
|
backgroundOpacity: opacity,
|
186
189
|
textContent: text,
|
187
|
-
borderRadius:
|
188
|
-
padding:
|
190
|
+
borderRadius: 1 * size,
|
191
|
+
padding: 1 * size,
|
189
192
|
});
|
190
193
|
const global = this;
|
191
194
|
const labelHandle = element as LabelHandle & Text;
|
192
195
|
labelHandle.setText = function (str: string) {
|
193
|
-
this.set({ textContent: str });
|
196
|
+
this.set({ textContent: str, whiteSpace: 'pre' });
|
194
197
|
global.tmuiNeedsUpdate = true;
|
195
198
|
};
|
196
199
|
}
|
@@ -201,6 +204,7 @@
|
|
201
204
|
backgroundColor: backgroundColor ?? undefined,
|
202
205
|
backgroundOpacity: opacity,
|
203
206
|
textContent: text,
|
207
|
+
whiteSpace: 'pre',
|
204
208
|
});
|
205
209
|
// const handle = element as any as LabelHandle;
|
206
210
|
// handle.setText(text);
|
@@ -18,7 +18,7 @@
|
|
18
18
|
import { foreachComponent } from './engine_gameobject.js';
|
19
19
|
|
20
20
|
import { ActiveCollisionTypes, ActiveEvents, CoefficientCombineRule, Ball, Collider, ColliderDesc, EventQueue, JointData, QueryFilterFlags, RigidBody, RigidBodyType, ShapeColliderTOI, World, Ray, ShapeType, Cuboid } from '@dimforge/rapier3d-compat';
|
21
|
-
import { CollisionDetectionMode, PhysicsMaterial, PhysicsMaterialCombine } from '../engine/engine_physics.types.js';
|
21
|
+
import { CollisionDetectionMode, type PhysicsMaterial, PhysicsMaterialCombine } from '../engine/engine_physics.types.js';
|
22
22
|
import { Gizmos } from './engine_gizmos.js';
|
23
23
|
import { Mathf } from './engine_math.js';
|
24
24
|
import { SphereOverlapResult } from './engine_types.js';
|
@@ -748,7 +748,8 @@
|
|
748
748
|
// otherwise rapier will compute the mass properties based on the collider shape and density
|
749
749
|
// https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
|
750
750
|
if (useExplicitMassProperties) {
|
751
|
-
desc.setDensity(
|
751
|
+
desc.setDensity(.000001);
|
752
|
+
desc.setMass(.000001);
|
752
753
|
}
|
753
754
|
|
754
755
|
const col = this.world.createCollider(desc, rigidBody);
|
@@ -886,7 +887,7 @@
|
|
886
887
|
rigidbody.setAdditionalMass(rb.mass, false);
|
887
888
|
for (let i = 0; i < rigidbody.numColliders(); i++) {
|
888
889
|
const col = rigidbody.collider(i);
|
889
|
-
col.setDensity(0);
|
890
|
+
col.setDensity(0.0000001);
|
890
891
|
}
|
891
892
|
rigidbody.recomputeMassPropertiesFromColliders();
|
892
893
|
}
|
@@ -105,6 +105,7 @@
|
|
105
105
|
export async function parseSync(context: Context, data: string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined> {
|
106
106
|
if (typeof path !== "string") {
|
107
107
|
console.warn("Parse gltf binary without path, this might lead to errors in resolving extensions. Please provide the source path of the gltf/glb file", path, typeof path);
|
108
|
+
path = "";
|
108
109
|
}
|
109
110
|
if (printGltf) console.log("Parse glTF", path)
|
110
111
|
const loader = await createGLTFLoader(path, context);
|
@@ -24,21 +24,10 @@
|
|
24
24
|
// @generate-component
|
25
25
|
export class GltfExportBox extends BoxHelperComponent {
|
26
26
|
sceneRoot?: THREE.Object3D;
|
27
|
-
|
28
|
-
start() {
|
29
|
-
this.startCoroutine(this.updateGltfBox());
|
30
|
-
}
|
31
|
-
|
32
|
-
*updateGltfBox() {
|
33
|
-
while (true) {
|
34
|
-
for (let i = 0; i < 10; i++) yield;
|
35
|
-
|
36
|
-
}
|
37
|
-
}
|
38
27
|
}
|
39
28
|
|
40
29
|
export class GltfExport extends Behaviour {
|
41
|
-
|
30
|
+
|
42
31
|
@serializable()
|
43
32
|
binary: boolean = true;
|
44
33
|
|
@@ -55,7 +44,11 @@
|
|
55
44
|
this.objects = [this.context.scene];
|
56
45
|
|
57
46
|
const opts = { binary: this.binary, pivot: GltfExport.calculateCenter(this.objects) };
|
58
|
-
const res = await this.export(this.objects, opts)
|
47
|
+
const res = await this.export(this.objects, opts).catch(err => {
|
48
|
+
console.error(err);
|
49
|
+
return false;
|
50
|
+
})
|
51
|
+
if (res === false) return false;
|
59
52
|
|
60
53
|
if (!this.binary) {
|
61
54
|
if (!name.endsWith(".gltf"))
|
@@ -67,6 +60,7 @@
|
|
67
60
|
GltfExport.saveArrayBuffer(res, name);
|
68
61
|
else
|
69
62
|
GltfExport.saveJson(res, name);
|
63
|
+
return true;
|
70
64
|
}
|
71
65
|
|
72
66
|
async export(objectsToExport: Object3D[], opts?: ExportOptions): Promise<any> {
|
@@ -79,12 +73,11 @@
|
|
79
73
|
if (!this.exporter) {
|
80
74
|
// Instantiate a exporter
|
81
75
|
this.exporter = new GLTFExporter();
|
82
|
-
//@ts-ignore
|
83
76
|
this.exporter.register(writer => new GLTFMeshGPUInstancingExtension(writer));
|
84
77
|
|
85
|
-
|
86
|
-
|
87
|
-
this.ext.registerExport(this.exporter);
|
78
|
+
// TODO
|
79
|
+
// this.ext = new NEEDLE_components();
|
80
|
+
// this.ext.registerExport(this.exporter);
|
88
81
|
}
|
89
82
|
|
90
83
|
GltfExport.filterTopmostParent(objectsToExport);
|
@@ -128,7 +121,8 @@
|
|
128
121
|
});
|
129
122
|
|
130
123
|
const serializationContext = new SerializationContext(exportScene);
|
131
|
-
this.ext
|
124
|
+
if (this.ext)
|
125
|
+
this.ext.context = serializationContext;
|
132
126
|
|
133
127
|
return new Promise((resolve, reject) => {
|
134
128
|
if (debugExport) console.log("Starting glTF export.")
|
@@ -33,6 +33,7 @@
|
|
33
33
|
this._color = new RGBAColor(1, 1, 1, 1);
|
34
34
|
}
|
35
35
|
this._color.copy(col);
|
36
|
+
this.onColorChanged();
|
36
37
|
}
|
37
38
|
|
38
39
|
private _alphaFactor: number = 1;
|
@@ -44,9 +45,12 @@
|
|
44
45
|
return this._alphaFactor;
|
45
46
|
}
|
46
47
|
|
48
|
+
private sRGBColor: Color = new Color(1, 0, 1);
|
47
49
|
protected onColorChanged() {
|
48
50
|
if (this.uiObject) {
|
49
|
-
|
51
|
+
this.sRGBColor.copy(this._color);
|
52
|
+
this.sRGBColor.convertLinearToSRGB();
|
53
|
+
_colorStateObject.backgroundColor = this.sRGBColor;
|
50
54
|
_colorStateObject.backgroundOpacity = this._color.alpha * this._alphaFactor;
|
51
55
|
this.applyEffects(_colorStateObject, this._alphaFactor);
|
52
56
|
this.uiObject.set(_colorStateObject);
|
@@ -164,6 +168,8 @@
|
|
164
168
|
this.controlsChildLayout = false;
|
165
169
|
this._currentlyCreatingPanel = false;
|
166
170
|
this.onAfterCreated();
|
171
|
+
|
172
|
+
this.onColorChanged();
|
167
173
|
}
|
168
174
|
|
169
175
|
protected onBeforeCreate(_opts: any) { }
|
@@ -192,7 +198,7 @@
|
|
192
198
|
this.setOptions({ backgroundOpacity: 0 });
|
193
199
|
if (tex) {
|
194
200
|
// workaround for https://github.com/needle-tools/needle-engine-support/issues/109
|
195
|
-
if (tex.colorSpace === SRGBColorSpace) {
|
201
|
+
// if (tex.colorSpace === SRGBColorSpace || !tex.colorSpace || true) {
|
196
202
|
if (Graphic.textureCache.has(tex)) {
|
197
203
|
tex = Graphic.textureCache.get(tex)!;
|
198
204
|
} else {
|
@@ -209,7 +215,7 @@
|
|
209
215
|
tex = clone;
|
210
216
|
}
|
211
217
|
}
|
212
|
-
}
|
218
|
+
// }
|
213
219
|
this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
|
214
220
|
}
|
215
221
|
else {
|
@@ -452,8 +452,9 @@
|
|
452
452
|
const indexString = val.substring("/textures/".length);
|
453
453
|
const texIndex = Number.parseInt(indexString);
|
454
454
|
if (texIndex >= 0) {
|
455
|
-
|
455
|
+
let tex = await this.parser.getDependency("texture", texIndex);
|
456
456
|
if (tex instanceof Texture) {
|
457
|
+
tex = tex.clone();
|
457
458
|
tex.colorSpace = LinearSRGBColorSpace;
|
458
459
|
tex.needsUpdate = true;
|
459
460
|
}
|
@@ -509,6 +509,7 @@
|
|
509
509
|
|
510
510
|
const $colorLerpFactor = Symbol("colorLerpFactor");
|
511
511
|
const tempColor = new RGBAColor(1, 1, 1, 1);
|
512
|
+
const col = new RGBAColor(1, 1, 1, 1);
|
512
513
|
class ColorBehaviour extends ParticleSystemBaseBehaviour {
|
513
514
|
type: string = "NeedleColor";
|
514
515
|
|
@@ -517,7 +518,7 @@
|
|
517
518
|
|
518
519
|
private _init(particle: Particle) {
|
519
520
|
const materialColor = this.system.renderer.particleMaterial;
|
520
|
-
|
521
|
+
col.copy(this.system.main.startColor.evaluate(Math.random()));
|
521
522
|
if (materialColor?.color) {
|
522
523
|
tempColor.copy(materialColor.color);
|
523
524
|
col.multiply(tempColor)
|
@@ -607,15 +608,26 @@
|
|
607
608
|
}
|
608
609
|
return factor;
|
609
610
|
}
|
611
|
+
private flatWhiteTexture?: THREE.Texture;
|
612
|
+
private clonedTexture: { original?: THREE.Texture, clone?: THREE.Texture } = { original: undefined, clone: undefined };
|
610
613
|
get texture(): THREE.Texture {
|
611
614
|
const mat = this.material;
|
612
615
|
if (mat && mat["map"]) {
|
613
|
-
const
|
614
|
-
|
615
|
-
|
616
|
-
|
616
|
+
const original = mat["map"]! as THREE.Texture;
|
617
|
+
// cache the last original one so we're not creating tons of clones
|
618
|
+
if (this.clonedTexture.original !== original || !this.clonedTexture.clone)
|
619
|
+
{
|
620
|
+
const tex = original.clone();
|
621
|
+
tex.premultiplyAlpha = false;
|
622
|
+
tex.colorSpace = THREE.LinearSRGBColorSpace;
|
623
|
+
this.clonedTexture.original = original;
|
624
|
+
this.clonedTexture.clone = tex;
|
625
|
+
}
|
626
|
+
return this.clonedTexture.clone;
|
617
627
|
}
|
618
|
-
|
628
|
+
if (!this.flatWhiteTexture)
|
629
|
+
this.flatWhiteTexture = createFlatTexture(new RGBAColor(1, 1, 1, 1), 1)
|
630
|
+
return this.flatWhiteTexture;
|
619
631
|
}
|
620
632
|
get startTileIndex() { return new TextureSheetStartFrameGenerator(this.system); }
|
621
633
|
get uTileCount() { return this.anim.enabled ? this.anim?.numTilesX : undefined }
|
@@ -560,6 +560,8 @@
|
|
560
560
|
const audio = new Tracks.AudioTrackHandler();
|
561
561
|
audio.director = this;
|
562
562
|
audio.track = track;
|
563
|
+
audio.audioSource = track.outputs.find(o => o instanceof AudioSource) as AudioSource;
|
564
|
+
|
563
565
|
this._audioTracks.push(audio);
|
564
566
|
if (!audioListener) {
|
565
567
|
// If the scene doesnt have an AudioListener we add one to the main camera
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { TypeStore } from "./../engine_typestore.js"
|
2
|
-
|
2
|
+
|
3
3
|
// Import types
|
4
4
|
import { __Ignore } from "../../engine-components/codegen/components.js";
|
5
5
|
import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
|
@@ -53,6 +53,7 @@
|
|
53
53
|
import { ColorBySpeedModule } from "../../engine-components/ParticleSystemModules.js";
|
54
54
|
import { ColorOverLifetimeModule } from "../../engine-components/ParticleSystemModules.js";
|
55
55
|
import { Component } from "../../engine-components/Component.js";
|
56
|
+
import { ContactShadows } from "../../engine-components/ContactShadows.js";
|
56
57
|
import { ControlTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
57
58
|
import { CustomBranding } from "../../engine-components/export/usdz/USDZExporter.js";
|
58
59
|
import { Deletable } from "../../engine-components/DeleteBox.js";
|
@@ -217,7 +218,7 @@
|
|
217
218
|
import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering.js";
|
218
219
|
import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
|
219
220
|
import { XRState } from "../../engine-components/XRFlag.js";
|
220
|
-
|
221
|
+
|
221
222
|
// Register types
|
222
223
|
TypeStore.add("__Ignore", __Ignore);
|
223
224
|
TypeStore.add("ActionBuilder", ActionBuilder);
|
@@ -271,6 +272,7 @@
|
|
271
272
|
TypeStore.add("ColorBySpeedModule", ColorBySpeedModule);
|
272
273
|
TypeStore.add("ColorOverLifetimeModule", ColorOverLifetimeModule);
|
273
274
|
TypeStore.add("Component", Component);
|
275
|
+
TypeStore.add("ContactShadows", ContactShadows);
|
274
276
|
TypeStore.add("ControlTrackHandler", ControlTrackHandler);
|
275
277
|
TypeStore.add("CustomBranding", CustomBranding);
|
276
278
|
TypeStore.add("Deletable", Deletable);
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import { Behaviour, GameObject } from "./Component.js";
|
2
2
|
import { RGBAColor } from "./js-extensions/RGBAColor.js";
|
3
|
+
import { ShadowMaterial, AdditiveBlending, Material, MeshBasicMaterial, Mesh, MeshStandardMaterial } from "three";
|
4
|
+
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
|
+
import { ObjectUtils, PrimitiveType } from "../engine/engine_create_objects.js";
|
3
6
|
import { Renderer } from "./Renderer.js";
|
4
|
-
import { ShadowMaterial, AdditiveBlending, Material } from "three";
|
5
|
-
import { serializable } from "../engine/engine_serialization_decorator.js";
|
6
7
|
|
7
8
|
enum ShadowMode {
|
8
9
|
ShadowMask = 0,
|
@@ -20,8 +21,44 @@
|
|
20
21
|
@serializable(RGBAColor)
|
21
22
|
shadowColor: RGBAColor = new RGBAColor(0, 0, 0, 1);
|
22
23
|
|
24
|
+
private targetMesh?: Mesh;
|
25
|
+
|
23
26
|
awake() {
|
27
|
+
// if there's no geometry, make a basic quad
|
28
|
+
if (!(this.gameObject instanceof Mesh)) {
|
29
|
+
const quad = ObjectUtils.createPrimitive(PrimitiveType.Quad, {
|
30
|
+
name: "ShadowCatcher",
|
31
|
+
material: new MeshStandardMaterial({
|
32
|
+
// HACK heuristic to get approx. the same colors out as with the current default ShadowCatcher material
|
33
|
+
// not clear why this is needed; assumption is that the Renderer component does something we're not respecting here
|
34
|
+
color: 0x999999,
|
35
|
+
roughness: 1,
|
36
|
+
metalness: 0,
|
37
|
+
transparent: true,
|
38
|
+
})
|
39
|
+
});
|
40
|
+
quad.receiveShadow = true;
|
41
|
+
quad.geometry.rotateX(-Math.PI / 2);
|
24
42
|
|
43
|
+
// TODO breaks shadow catching right now
|
44
|
+
// const renderer = new Renderer();
|
45
|
+
// renderer.receiveShadows = true;
|
46
|
+
// GameObject.addComponent(quad, Renderer);
|
47
|
+
|
48
|
+
this.gameObject.add(quad);
|
49
|
+
this.targetMesh = quad;
|
50
|
+
}
|
51
|
+
else if (this.gameObject instanceof Mesh && this.gameObject.material) {
|
52
|
+
// make sure we have a unique material to work with
|
53
|
+
this.gameObject.material = this.gameObject.material.clone();
|
54
|
+
this.targetMesh = this.gameObject;
|
55
|
+
}
|
56
|
+
|
57
|
+
if(!this.targetMesh) {
|
58
|
+
console.warn("ShadowCatcher: no mesh to apply shadow catching to. Groups are currently not supported.");
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
|
25
62
|
switch (this.mode) {
|
26
63
|
case ShadowMode.ShadowMask:
|
27
64
|
this.applyShadowMaterial();
|
@@ -33,7 +70,6 @@
|
|
33
70
|
this.applyOccluderMaterial();
|
34
71
|
break;
|
35
72
|
}
|
36
|
-
|
37
73
|
}
|
38
74
|
|
39
75
|
// Custom blending, diffuse-only lighting blended onto the scene additively.
|
@@ -42,31 +78,30 @@
|
|
42
78
|
// Works even better with an additional black-ish gradient to darken parts of the AR scene
|
43
79
|
// so that lights become more visible on bright surfaces.
|
44
80
|
applyLightBlendMaterial() {
|
45
|
-
|
46
|
-
if (renderer) {
|
47
|
-
const material = renderer.sharedMaterial;
|
48
|
-
material.blending = AdditiveBlending;
|
49
|
-
this.applyMaterialOptions(material);
|
50
|
-
material.onBeforeCompile = (shader) => {
|
51
|
-
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical.glsl.js#L181
|
52
|
-
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib.js#LL284C11-L284C11
|
53
|
-
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/shadow.glsl.js#L40
|
54
|
-
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl.js#L2
|
55
|
-
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js#L281
|
81
|
+
if (!this.targetMesh) return;
|
56
82
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
`
|
69
|
-
|
83
|
+
const material = this.targetMesh.material as Material;
|
84
|
+
material.blending = AdditiveBlending;
|
85
|
+
this.applyMaterialOptions(material);
|
86
|
+
material.onBeforeCompile = (shader) => {
|
87
|
+
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical.glsl.js#L181
|
88
|
+
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib.js#LL284C11-L284C11
|
89
|
+
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/shadow.glsl.js#L40
|
90
|
+
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl.js#L2
|
91
|
+
// see https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js#L281
|
92
|
+
|
93
|
+
shader.fragmentShader = shader.fragmentShader.replace("vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;",
|
94
|
+
`vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;
|
95
|
+
// diffuse-only lighting with overdrive to somewhat compensate
|
96
|
+
// for the loss of indirect lighting and to make it more visible.
|
97
|
+
vec3 direct = (reflectedLight.directDiffuse + reflectedLight.directSpecular) * 6.6;
|
98
|
+
float max = max(direct.r, max(direct.g, direct.b));
|
99
|
+
|
100
|
+
// early out - we're simply returning direct lighting and some alpha based on it so it can
|
101
|
+
// be blended onto the scene.
|
102
|
+
gl_FragColor = vec4(direct, max);
|
103
|
+
return;
|
104
|
+
`);
|
70
105
|
}
|
71
106
|
}
|
72
107
|
|
@@ -74,17 +109,16 @@
|
|
74
109
|
// doesn't take light attenuation into account.
|
75
110
|
// works great for Directional Lights.
|
76
111
|
applyShadowMaterial() {
|
77
|
-
|
78
|
-
|
79
|
-
if (renderer.sharedMaterial?.type !== "ShadowMaterial") {
|
112
|
+
if (this.targetMesh) {
|
113
|
+
if ((this.targetMesh.material as Material).type !== "ShadowMaterial") {
|
80
114
|
const material = new ShadowMaterial();
|
81
115
|
material.color = this.shadowColor;
|
82
116
|
material.opacity = this.shadowColor.alpha;
|
83
117
|
this.applyMaterialOptions(material);
|
84
|
-
|
118
|
+
this.targetMesh.material = material;
|
85
119
|
}
|
86
120
|
else {
|
87
|
-
const material =
|
121
|
+
const material = this.targetMesh.material as ShadowMaterial;
|
88
122
|
material.color = this.shadowColor;
|
89
123
|
material.opacity = this.shadowColor.alpha;
|
90
124
|
this.applyMaterialOptions(material);
|
@@ -93,9 +127,13 @@
|
|
93
127
|
}
|
94
128
|
|
95
129
|
applyOccluderMaterial() {
|
96
|
-
|
97
|
-
|
98
|
-
|
130
|
+
if (this.targetMesh) {
|
131
|
+
let material = this.targetMesh.material as Material;
|
132
|
+
if (!material) {
|
133
|
+
const mat = new MeshBasicMaterial();
|
134
|
+
this.targetMesh.material = mat;
|
135
|
+
material = mat;
|
136
|
+
}
|
99
137
|
material.depthWrite = true;
|
100
138
|
material.stencilWrite = true;
|
101
139
|
material.colorWrite = false;
|
@@ -4,7 +4,7 @@
|
|
4
4
|
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
|
5
5
|
import { EquirectangularRefractionMapping, NeverDepth, SRGBColorSpace, sRGBEncoding, Texture, TextureLoader } from "three"
|
6
6
|
import { syncField } from "../engine/engine_networking_auto.js";
|
7
|
-
import { Camera } from "./Camera.js";
|
7
|
+
import { Camera, ClearFlags } from "./Camera.js";
|
8
8
|
import { PromiseAllWithErrors, addAttributeChangeCallback, getParam, removeAttributeChangeCallback } from "../engine/engine_utils.js";
|
9
9
|
import { ContextRegistry } from "../engine/engine_context_registry.js";
|
10
10
|
import { registerObservableAttribute } from "../engine/engine_element_extras.js";
|
@@ -43,12 +43,18 @@
|
|
43
43
|
const promises = new Array<Promise<any>>();
|
44
44
|
|
45
45
|
if (skyboxImage) {
|
46
|
-
if (debug)
|
46
|
+
if (debug)
|
47
|
+
console.log("Creating remote skybox to load " + skyboxImage);
|
48
|
+
// if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
|
49
|
+
// checks if we have this attribute set and then sets the skybox clearflags accordingly
|
50
|
+
// if the user has a GLB with a camera but set to solid color then the skybox image is not visible -> we will just warn then and not override the camera settings
|
51
|
+
if(context.mainCameraComponent?.clearFlags !== ClearFlags.Skybox) console.warn("\"skybox-image\" attribute has no effect: camera clearflags are not set to \"Skybox\"");
|
47
52
|
const promise = createRemoteSkyboxComponent(context, skyboxImage, true, false, "skybox-image");
|
48
53
|
promises.push(promise);
|
49
54
|
}
|
50
55
|
if (environmentImage) {
|
51
|
-
if (debug)
|
56
|
+
if (debug)
|
57
|
+
console.log("Creating remote environment to load " + environmentImage);
|
52
58
|
const promise = createRemoteSkyboxComponent(context, environmentImage, false, true, "environment-image");
|
53
59
|
promises.push(promise);
|
54
60
|
}
|
@@ -137,8 +143,8 @@
|
|
137
143
|
}
|
138
144
|
|
139
145
|
async setSkybox(url: string | undefined | null) {
|
140
|
-
if (!this.activeAndEnabled) return;
|
141
|
-
if (!url) return;
|
146
|
+
if (!this.activeAndEnabled) return false;
|
147
|
+
if (!url) return false;
|
142
148
|
if (!url?.endsWith(".hdr") && !url.endsWith(".exr") && !url.endsWith(".jpg") && !url.endsWith(".png") && !url.endsWith(".jpeg")) {
|
143
149
|
console.warn("Potentially invalid skybox url", this.url, "on", this.name);
|
144
150
|
}
|
@@ -147,7 +153,7 @@
|
|
147
153
|
|
148
154
|
if (this._prevUrl === url && this._prevLoadedEnvironment) {
|
149
155
|
this.applySkybox();
|
150
|
-
return;
|
156
|
+
return true;
|
151
157
|
}
|
152
158
|
else {
|
153
159
|
this._prevLoadedEnvironment?.dispose();
|
@@ -156,9 +162,9 @@
|
|
156
162
|
this._prevUrl = url;
|
157
163
|
|
158
164
|
const envMap = await this.loadTexture(url);
|
159
|
-
if (!envMap) return;
|
165
|
+
if (!envMap) return false;
|
160
166
|
// Check if we're still enabled
|
161
|
-
if (!this.enabled) return;
|
167
|
+
if (!this.enabled) return false;
|
162
168
|
// Update the current url
|
163
169
|
this.url = url;
|
164
170
|
const nameIndex = url.lastIndexOf("/");
|
@@ -168,6 +174,7 @@
|
|
168
174
|
}
|
169
175
|
this._prevLoadedEnvironment = envMap;
|
170
176
|
this.applySkybox();
|
177
|
+
return true;
|
171
178
|
}
|
172
179
|
|
173
180
|
private async loadTexture(url: string) {
|
@@ -90,9 +90,11 @@
|
|
90
90
|
this.uiObject?.set({ fontSize: val });
|
91
91
|
}
|
92
92
|
|
93
|
-
|
93
|
+
private sRGBTextColor: Color = new Color(1, 0, 1);
|
94
94
|
protected onColorChanged(): void {
|
95
|
-
this.
|
95
|
+
this.sRGBTextColor.copy(this.color);
|
96
|
+
this.sRGBTextColor.convertLinearToSRGB();
|
97
|
+
this.uiObject?.set({ color: this.sRGBTextColor, fontOpacity: this.color.alpha });
|
96
98
|
}
|
97
99
|
|
98
100
|
onParentRectTransformChanged(): void {
|
@@ -37,6 +37,12 @@
|
|
37
37
|
return str;
|
38
38
|
}
|
39
39
|
|
40
|
+
// TODO: remove once we update to TypeScript 5 that has proper types for OffscreenCanvas
|
41
|
+
declare type OffsetCanvasExt = OffscreenCanvas & {
|
42
|
+
convertToBlob: (options?: any) => Promise<Blob>;
|
43
|
+
|
44
|
+
}
|
45
|
+
|
40
46
|
class USDObject {
|
41
47
|
|
42
48
|
static USDObject_export_id = 0;
|
@@ -481,7 +487,7 @@
|
|
481
487
|
|
482
488
|
if ( canvas ) {
|
483
489
|
|
484
|
-
const blob = await
|
490
|
+
const blob = await canvas.convertToBlob( {type: isRGBA ? 'image/png' : 'image/jpeg', quality: 0.95 } );
|
485
491
|
files[ `textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}` ] = new Uint8Array( await blob.arrayBuffer() );
|
486
492
|
|
487
493
|
} else {
|
@@ -814,11 +820,9 @@
|
|
814
820
|
// max. canvas size on Safari is still 4096x4096
|
815
821
|
const scale = 4096 / Math.max( image.width, image.height );
|
816
822
|
|
817
|
-
const canvas =
|
818
|
-
canvas.width = image.width * Math.min( 1, scale );
|
819
|
-
canvas.height = image.height * Math.min( 1, scale );
|
823
|
+
const canvas = new OffscreenCanvas( image.width * Math.min( 1, scale ), image.height * Math.min( 1, scale ) );
|
820
824
|
|
821
|
-
const context = canvas.getContext( '2d' );
|
825
|
+
const context = canvas.getContext( '2d' ) as OffscreenCanvasRenderingContext2D;
|
822
826
|
if (!context) throw new Error('Could not get canvas 2D context');
|
823
827
|
|
824
828
|
if ( flipY === true ) {
|
@@ -854,7 +858,7 @@
|
|
854
858
|
|
855
859
|
}
|
856
860
|
|
857
|
-
return canvas;
|
861
|
+
return canvas as OffsetCanvasExt;
|
858
862
|
|
859
863
|
} else {
|
860
864
|
|
@@ -31,6 +31,7 @@
|
|
31
31
|
clips?: Array<ClipModel>;
|
32
32
|
markers?: Array<MarkerModel>;
|
33
33
|
trackOffset?: TrackOffset;
|
34
|
+
volume?: number;
|
34
35
|
}
|
35
36
|
|
36
37
|
declare type Vec3 = { x: number, y: number, z: number };
|
@@ -564,12 +564,13 @@
|
|
564
564
|
const muteAudioTracks = getParam("mutetimeline");
|
565
565
|
|
566
566
|
export class AudioTrackHandler extends TrackHandler {
|
567
|
+
|
567
568
|
models: Array<Models.ClipModel> = [];
|
568
|
-
|
569
569
|
listener!: AudioListener;
|
570
570
|
audio: Array<Audio> = [];
|
571
571
|
audioContextTimeOffset: Array<number> = [];
|
572
572
|
lastTime: number = 0;
|
573
|
+
audioSource?:AudioSource;
|
573
574
|
|
574
575
|
private _audioLoader: AudioLoader | null = null;
|
575
576
|
|
@@ -642,6 +643,7 @@
|
|
642
643
|
for (let i = 0; i < this.models.length; i++) {
|
643
644
|
const model = this.models[i];
|
644
645
|
const audio = this.audio[i];
|
646
|
+
const asset = model.asset as Models.AudioClipModel;
|
645
647
|
// only trigger loading for tracks that are CLOSE to being played
|
646
648
|
if ((!audio || !audio.buffer) && this.isInTimeRange(model, time - 1, time + 1)) {
|
647
649
|
this.handleAudioLoading(model, audio);
|
@@ -649,7 +651,7 @@
|
|
649
651
|
if (AudioSource.userInteractionRegistered === false) continue;
|
650
652
|
if (audio === null || !audio.buffer) continue;
|
651
653
|
audio.playbackRate = this.director.context.time.timeScale * this.director.speed;
|
652
|
-
audio.loop =
|
654
|
+
audio.loop = asset.loop;
|
653
655
|
if (time >= model.start && time <= model.end && time < this.director.duration) {
|
654
656
|
if (this.director.isPlaying == false) {
|
655
657
|
if (audio.isPlaying)
|
@@ -673,7 +675,11 @@
|
|
673
675
|
audio.play(playTimeOffset);
|
674
676
|
}
|
675
677
|
}
|
676
|
-
let vol =
|
678
|
+
let vol = asset.volume as number;
|
679
|
+
|
680
|
+
if(this.track.volume !== undefined)
|
681
|
+
vol *= this.track.volume;
|
682
|
+
|
677
683
|
if (isMuted) vol = 0;
|
678
684
|
if (model.easeInDuration > 0) {
|
679
685
|
const easeIn = Math.min((time - model.start) / model.easeInDuration, 1);
|
@@ -164,7 +164,7 @@
|
|
164
164
|
// TODO better would be to do that once we actually need it
|
165
165
|
const canvas = await imageToCanvas(img);
|
166
166
|
if (canvas) {
|
167
|
-
const blob = await
|
167
|
+
const blob = await canvas.convertToBlob({type: 'image/png'});
|
168
168
|
const arrayBuffer = await blob.arrayBuffer();
|
169
169
|
|
170
170
|
const exporter = GameObject.findObjectOfType(USDZExporter);
|
@@ -345,9 +345,9 @@
|
|
345
345
|
}
|
346
346
|
|
347
347
|
private buildLocalAvatar() {
|
348
|
-
if (this.localAvatar) return;
|
348
|
+
if (this.localAvatar || !this.webXR) return;
|
349
349
|
const connectionId = this.context.connection?.connectionId ?? this.k_LocalAvatarNoNetworkingGuid;
|
350
|
-
this.localAvatar = new WebXRAvatar(this.context, connectionId, this.webXR
|
350
|
+
this.localAvatar = new WebXRAvatar(this.context, connectionId, this.webXR);
|
351
351
|
this.localAvatar.isLocalAvatar = true;
|
352
352
|
this.localAvatar.setAvatarOverride(this.getAvatarId());
|
353
353
|
this.avatars[this.localAvatar.guid] = this.localAvatar;
|
@@ -0,0 +1,249 @@
|
|
1
|
+
import { Behaviour } from "./Component.js";
|
2
|
+
import { serializable } from "../engine/engine_serialization_decorator.js";
|
3
|
+
|
4
|
+
import { CustomBlending, DoubleSide, Group, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MinEquation, OrthographicCamera, PlaneGeometry, ShaderMaterial, WebGLRenderTarget } from "three";
|
5
|
+
import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
|
6
|
+
import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
|
7
|
+
|
8
|
+
// Adapted from https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html.
|
9
|
+
|
10
|
+
// Improved with
|
11
|
+
// - ground occluder
|
12
|
+
// - backface shadowing (slightly less than front faces)
|
13
|
+
// - node can simply be scaled in Y to adjust max. ground height
|
14
|
+
|
15
|
+
export class ContactShadows extends Behaviour {
|
16
|
+
|
17
|
+
@serializable()
|
18
|
+
darkness: number = 0.5;
|
19
|
+
@serializable()
|
20
|
+
opacity: number = 0.5;
|
21
|
+
@serializable()
|
22
|
+
blur: number = 4.0;
|
23
|
+
@serializable()
|
24
|
+
occludeBelowGround: boolean = true;
|
25
|
+
@serializable()
|
26
|
+
backfaceShadows: boolean = true;
|
27
|
+
|
28
|
+
private shadowCamera?: OrthographicCamera;
|
29
|
+
private shadowGroup?: Group;
|
30
|
+
|
31
|
+
private renderTarget?: WebGLRenderTarget;
|
32
|
+
private renderTargetBlur?: WebGLRenderTarget;
|
33
|
+
|
34
|
+
private plane?: Mesh;
|
35
|
+
private occluderMesh?: Mesh;
|
36
|
+
private blurPlane?: Mesh;
|
37
|
+
|
38
|
+
private depthMaterial?: MeshDepthMaterial;
|
39
|
+
private horizontalBlurMaterial?: ShaderMaterial;
|
40
|
+
private verticalBlurMaterial?: ShaderMaterial;
|
41
|
+
|
42
|
+
awake(): void {
|
43
|
+
const textureSize = 512;
|
44
|
+
|
45
|
+
this.shadowGroup = new Group();
|
46
|
+
this.gameObject.add(this.shadowGroup);
|
47
|
+
|
48
|
+
// the render target that will show the shadows in the plane texture
|
49
|
+
this.renderTarget = new WebGLRenderTarget(textureSize, textureSize);
|
50
|
+
this.renderTarget.texture.generateMipmaps = false;
|
51
|
+
|
52
|
+
// the render target that we will use to blur the first render target
|
53
|
+
this.renderTargetBlur = new WebGLRenderTarget(textureSize, textureSize);
|
54
|
+
this.renderTargetBlur.texture.generateMipmaps = false;
|
55
|
+
|
56
|
+
// make a plane and make it face up
|
57
|
+
const planeGeometry = new PlaneGeometry(1, 1).rotateX(Math.PI / 2);
|
58
|
+
//@ts-ignore
|
59
|
+
if (this.gameObject.isMesh) {
|
60
|
+
this.plane = this.gameObject as any as Mesh;
|
61
|
+
const mat = this.plane!.material as MeshBasicMaterial;
|
62
|
+
mat.map = this.renderTarget.texture;
|
63
|
+
// When someone makes a custom mesh, they can set these values right on the material.
|
64
|
+
// mat.opacity = this.state.plane.opacity;
|
65
|
+
// mat.transparent = true;
|
66
|
+
// mat.depthWrite = false;
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
const planeMaterial = new MeshBasicMaterial({
|
70
|
+
map: this.renderTarget.texture,
|
71
|
+
opacity: this.opacity,
|
72
|
+
color: 0x000000,
|
73
|
+
transparent: true,
|
74
|
+
depthWrite: false,
|
75
|
+
});
|
76
|
+
this.plane = new Mesh(planeGeometry, planeMaterial);
|
77
|
+
this.plane.scale.y = - 1;
|
78
|
+
this.gameObject.add(this.plane);
|
79
|
+
}
|
80
|
+
if (this.plane) this.plane.renderOrder = 1;
|
81
|
+
|
82
|
+
|
83
|
+
if (this.occludeBelowGround) {
|
84
|
+
this.occluderMesh = new Mesh(this.plane.geometry, new MeshBasicMaterial({
|
85
|
+
depthWrite: true,
|
86
|
+
stencilWrite: true,
|
87
|
+
colorWrite: false,
|
88
|
+
})).rotateX(Math.PI).translateY(0.0001);
|
89
|
+
this.occluderMesh.renderOrder = -100;
|
90
|
+
this.gameObject.add(this.occluderMesh);
|
91
|
+
}
|
92
|
+
|
93
|
+
// the plane onto which to blur the texture
|
94
|
+
this.blurPlane = new Mesh(planeGeometry);
|
95
|
+
this.blurPlane.visible = false;
|
96
|
+
this.shadowGroup.add(this.blurPlane);
|
97
|
+
|
98
|
+
// max. ground distance is controlled via object scale
|
99
|
+
this.shadowCamera = new OrthographicCamera(-1 /2, 1/2, 1/2, -1/2, 0, 1.0);
|
100
|
+
this.shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up
|
101
|
+
this.shadowGroup.add(this.shadowCamera);
|
102
|
+
|
103
|
+
// like MeshDepthMaterial, but goes from black to transparent
|
104
|
+
this.depthMaterial = new MeshDepthMaterial();
|
105
|
+
this.depthMaterial.userData.darkness = { value: this.darkness };
|
106
|
+
// this will properly overlap calculated shadows
|
107
|
+
this.depthMaterial.blending = CustomBlending;
|
108
|
+
this.depthMaterial.blendEquation = MaxEquation;
|
109
|
+
if (this.backfaceShadows)
|
110
|
+
this.depthMaterial.side = DoubleSide;
|
111
|
+
|
112
|
+
// this.depthMaterial.blendEquation = MinEquation;
|
113
|
+
this.depthMaterial.onBeforeCompile = shader => {
|
114
|
+
if (!this.depthMaterial) return;
|
115
|
+
shader.uniforms.darkness = this.depthMaterial.userData.darkness;
|
116
|
+
shader.fragmentShader = /* glsl */`
|
117
|
+
uniform float darkness;
|
118
|
+
${shader.fragmentShader.replace(
|
119
|
+
'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
|
120
|
+
// we're scaling the shadow value down a bit when it's a backface (looks better)
|
121
|
+
'gl_FragColor = vec4( vec3( 1.0 ), ( 1.0 - fragCoordZ ) * darkness * opacity * (gl_FrontFacing ? 1.0 : 0.66) );'
|
122
|
+
)}
|
123
|
+
`;
|
124
|
+
};
|
125
|
+
|
126
|
+
this.depthMaterial.depthTest = false;
|
127
|
+
this.depthMaterial.depthWrite = false;
|
128
|
+
|
129
|
+
this.horizontalBlurMaterial = new ShaderMaterial(HorizontalBlurShader);
|
130
|
+
this.horizontalBlurMaterial.depthTest = false;
|
131
|
+
|
132
|
+
this.verticalBlurMaterial = new ShaderMaterial(VerticalBlurShader);
|
133
|
+
this.verticalBlurMaterial.depthTest = false;
|
134
|
+
|
135
|
+
this.shadowGroup.visible = false;
|
136
|
+
}
|
137
|
+
|
138
|
+
onDestroy(): void {
|
139
|
+
// dispose the render targets
|
140
|
+
this.renderTarget?.dispose();
|
141
|
+
this.renderTargetBlur?.dispose();
|
142
|
+
|
143
|
+
// dispose the materials
|
144
|
+
this.depthMaterial?.dispose();
|
145
|
+
this.horizontalBlurMaterial?.dispose();
|
146
|
+
this.verticalBlurMaterial?.dispose();
|
147
|
+
|
148
|
+
// dispose the geometries
|
149
|
+
this.blurPlane?.geometry.dispose();
|
150
|
+
this.plane?.geometry.dispose();
|
151
|
+
this.occluderMesh?.geometry.dispose();
|
152
|
+
}
|
153
|
+
|
154
|
+
onBeforeRender(_frame: XRFrame | null): void {
|
155
|
+
const scene = this.context.scene;
|
156
|
+
const renderer = this.context.renderer;
|
157
|
+
const initialRenderTarget = renderer.getRenderTarget();
|
158
|
+
|
159
|
+
if (!this.renderTarget || !this.renderTargetBlur ||
|
160
|
+
!this.depthMaterial || !this.shadowCamera ||
|
161
|
+
!this.blurPlane || !this.shadowGroup || !this.plane ||
|
162
|
+
!this.horizontalBlurMaterial || !this.verticalBlurMaterial)
|
163
|
+
return;
|
164
|
+
|
165
|
+
//@ts-ignore
|
166
|
+
if (this.gameObject.isMesh)
|
167
|
+
this.gameObject.visible = false;
|
168
|
+
|
169
|
+
// Idea: shear the shadowCamera matrix to add some light direction to the ground shadows
|
170
|
+
/*
|
171
|
+
const mat = this.shadowCamera.projectionMatrix.clone();
|
172
|
+
this.shadowCamera.projectionMatrix.multiply(new Matrix4().makeShear(0, 0, 0, 0, 0, 0));
|
173
|
+
*/
|
174
|
+
|
175
|
+
this.shadowGroup.visible = true;
|
176
|
+
if (this.occluderMesh) this.occluderMesh.visible = false;
|
177
|
+
const planeWasVisible = this.plane.visible;
|
178
|
+
this.plane.visible = false;
|
179
|
+
|
180
|
+
// remove the background
|
181
|
+
const initialBackground = scene.background;
|
182
|
+
scene.background = null;
|
183
|
+
|
184
|
+
// force the depthMaterial to everything
|
185
|
+
scene.overrideMaterial = this.depthMaterial;
|
186
|
+
|
187
|
+
// set renderer clear alpha
|
188
|
+
const initialClearAlpha = renderer.getClearAlpha();
|
189
|
+
renderer.setClearAlpha(0);
|
190
|
+
|
191
|
+
// render to the render target to get the depths
|
192
|
+
renderer.setRenderTarget(this.renderTarget);
|
193
|
+
renderer.render(scene, this.shadowCamera);
|
194
|
+
|
195
|
+
// for the shearing idea
|
196
|
+
// this.shadowCamera.projectionMatrix.copy(mat);
|
197
|
+
|
198
|
+
// and reset the override material
|
199
|
+
scene.overrideMaterial = null;
|
200
|
+
|
201
|
+
this.blurShadow(this.blur);
|
202
|
+
|
203
|
+
// a second pass to reduce the artifacts
|
204
|
+
// (0.4 is the minimum blur amout so that the artifacts are gone)
|
205
|
+
this.blurShadow(this.blur * 0.4);
|
206
|
+
|
207
|
+
this.shadowGroup.visible = false;
|
208
|
+
if (this.occluderMesh) this.occluderMesh.visible = true;
|
209
|
+
this.plane.visible = planeWasVisible;
|
210
|
+
|
211
|
+
// reset and render the normal scene
|
212
|
+
renderer.setRenderTarget(initialRenderTarget);
|
213
|
+
renderer.setClearAlpha(initialClearAlpha);
|
214
|
+
scene.background = initialBackground;
|
215
|
+
}
|
216
|
+
|
217
|
+
// renderTarget --> blurPlane (horizontalBlur) --> renderTargetBlur --> blurPlane (verticalBlur) --> renderTarget
|
218
|
+
private blurShadow(amount) {
|
219
|
+
if (!this.blurPlane || !this.shadowCamera ||
|
220
|
+
!this.renderTarget || !this.renderTargetBlur ||
|
221
|
+
!this.horizontalBlurMaterial || !this.verticalBlurMaterial)
|
222
|
+
return;
|
223
|
+
|
224
|
+
this.blurPlane.visible = true;
|
225
|
+
|
226
|
+
// blur horizontally and draw in the renderTargetBlur
|
227
|
+
this.blurPlane.material = this.horizontalBlurMaterial;
|
228
|
+
(this.blurPlane.material as ShaderMaterial).uniforms.tDiffuse.value = this.renderTarget.texture;
|
229
|
+
this.horizontalBlurMaterial.uniforms.h.value = amount * 1 / 256;
|
230
|
+
|
231
|
+
const renderer = this.context.renderer;
|
232
|
+
|
233
|
+
const currentRt = renderer.getRenderTarget();
|
234
|
+
renderer.setRenderTarget(this.renderTargetBlur);
|
235
|
+
renderer.render(this.blurPlane, this.shadowCamera);
|
236
|
+
|
237
|
+
// blur vertically and draw in the main renderTarget
|
238
|
+
this.blurPlane.material = this.verticalBlurMaterial;
|
239
|
+
(this.blurPlane.material as ShaderMaterial).uniforms.tDiffuse.value = this.renderTargetBlur.texture;
|
240
|
+
this.verticalBlurMaterial.uniforms.v.value = amount * 1 / 256;
|
241
|
+
|
242
|
+
renderer.setRenderTarget(this.renderTarget);
|
243
|
+
renderer.render(this.blurPlane, this.shadowCamera);
|
244
|
+
|
245
|
+
this.blurPlane.visible = false;
|
246
|
+
|
247
|
+
renderer.setRenderTarget(currentRt);
|
248
|
+
}
|
249
|
+
}
|