@@ -20,6 +20,7 @@
|
|
20
20
|
cam.sourceId = srcId;
|
21
21
|
cam.clearFlags = 2;
|
22
22
|
cam.backgroundColor = new RGBAColor(0.5, 0.5, 0.5, 1);
|
23
|
+
cam.fieldOfView = 35;
|
23
24
|
|
24
25
|
cameraObject.position.x = 0;
|
25
26
|
cameraObject.position.y = 1;
|
@@ -48,7 +49,7 @@
|
|
48
49
|
const orbit = addNewComponent(cameraObject, new OrbitControls(), false) as OrbitControls;
|
49
50
|
orbit.sourceId = "unknown";
|
50
51
|
setTimeout(() => {
|
51
|
-
orbit.fitCameraToObjects(evt.context.scene.children
|
52
|
+
orbit.fitCameraToObjects(evt.context.scene.children);
|
52
53
|
}, 100);
|
53
54
|
}
|
54
55
|
else {
|
@@ -5,14 +5,17 @@
|
|
5
5
|
export { showDebugConsole }
|
6
6
|
export { LogType };
|
7
7
|
|
8
|
+
/** Displays a debug message on screen for a certain amount of time */
|
8
9
|
export function showBalloonMessage(text: string, logType: LogType = LogType.Log) {
|
9
10
|
addLog(logType, text);
|
10
11
|
}
|
11
12
|
|
13
|
+
/** Displays a warning message on screen for a certain amount of time */
|
12
14
|
export function showBalloonWarning(text: string) {
|
13
15
|
showBalloonMessage(text, LogType.Warn);
|
14
16
|
}
|
15
17
|
|
18
|
+
/** True when the application runs on a local url */
|
16
19
|
export function isDevEnvironment(){
|
17
20
|
return isLocalNetwork();
|
18
21
|
}
|
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
// https://github.com/uuidjs/uuid
|
9
9
|
// v5 takes string and namespace
|
10
|
-
import { v5
|
10
|
+
import { v5 } from 'uuid';
|
11
11
|
import { UIDProvider } from "./engine_types";
|
12
12
|
import { IModel } from "./engine_networking_types";
|
13
13
|
import { SendQueue } from "./engine_networking_types";
|
@@ -49,6 +49,12 @@
|
|
49
49
|
return new URLSearchParams(window.location.search);
|
50
50
|
}
|
51
51
|
|
52
|
+
/** Checks if a url parameter exists.
|
53
|
+
* Returns true if it exists but has no value (e.g. ?help)
|
54
|
+
* Returns false if it does not exist
|
55
|
+
* Returns false if it's set to 0 e.g. ?debug=0
|
56
|
+
* Returns the value if it exists e.g. ?message=hello
|
57
|
+
*/
|
52
58
|
export function getParam(paramName: string): string | boolean | number {
|
53
59
|
|
54
60
|
if (saveParams && !requestedParams.includes(paramName))
|
@@ -1,12 +1,12 @@
|
|
1
1
|
import { Behaviour, GameObject } from "./Component";
|
2
2
|
import { Camera } from "./Camera";
|
3
3
|
import { LookAtConstraint } from "./LookAtConstraint";
|
4
|
-
import { getWorldPosition, slerp } from "../engine/engine_three_utils";
|
4
|
+
import { getWorldPosition, getWorldRotation, setWorldPosition, setWorldRotation, slerp } from "../engine/engine_three_utils";
|
5
5
|
import { RaycastOptions } from "../engine/engine_physics";
|
6
6
|
import { serializable } from "../engine/engine_serialization_decorator";
|
7
7
|
import { getParam, isMobileDevice } from "../engine/engine_utils";
|
8
8
|
|
9
|
-
import { Camera as ThreeCamera, Box3, Object3D, PerspectiveCamera, Vector2, Vector3 } from "three";
|
9
|
+
import { Camera as ThreeCamera, Box3, Object3D, PerspectiveCamera, Vector2, Vector3, Box3Helper } from "three";
|
10
10
|
import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
11
11
|
import { AfterHandleInputEvent, EventSystem, EventSystemEvents } from "./ui/EventSystem";
|
12
12
|
import { ICameraController } from "../engine/engine_types";
|
@@ -14,6 +14,7 @@
|
|
14
14
|
import { SyncedTransform } from "./SyncedTransform";
|
15
15
|
|
16
16
|
const freeCam = getParam("freecam");
|
17
|
+
const debugCameraFit = getParam("debugcamerafit");
|
17
18
|
|
18
19
|
const disabledKeys = { LEFT: "", UP: "", RIGHT: "", BOTTOM: "" };
|
19
20
|
let defaultKeys: any = undefined;
|
@@ -351,7 +352,7 @@
|
|
351
352
|
|
352
353
|
// Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
|
353
354
|
// Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
|
354
|
-
fitCameraToObjects(objects: Array<Object3D>, fitOffset: number = 1.
|
355
|
+
fitCameraToObjects(objects: Array<Object3D>, fitOffset: number = 1.1) {
|
355
356
|
const camera = this._cameraObject as PerspectiveCamera;
|
356
357
|
const controls = this._controls as ThreeOrbitControls | null;
|
357
358
|
|
@@ -361,20 +362,43 @@
|
|
361
362
|
const center = new Vector3();
|
362
363
|
const box = new Box3();
|
363
364
|
|
365
|
+
// TODO would be much better to calculate the bounds in camera space instead of world space -
|
366
|
+
// we would get proper view-dependant fit.
|
367
|
+
// Right now it's independent from where the camera is actually looking from,
|
368
|
+
// and thus we're just getting some maximum that will work for sure.
|
369
|
+
|
364
370
|
box.makeEmpty();
|
365
|
-
for (const object of objects)
|
366
|
-
|
371
|
+
for (const object of objects) {
|
372
|
+
// ignore Box3Helpers
|
373
|
+
if (object instanceof Box3Helper) continue;
|
374
|
+
box.expandByObject(object, true);
|
375
|
+
}
|
367
376
|
|
368
|
-
|
377
|
+
camera.updateMatrixWorld();
|
378
|
+
camera.updateProjectionMatrix();
|
379
|
+
|
369
380
|
box.getCenter(center);
|
370
381
|
|
371
|
-
|
372
|
-
|
373
|
-
const fitWidthDistance = fitHeightDistance / camera.aspect;
|
374
|
-
const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
|
382
|
+
// project this box into camera space
|
383
|
+
box.applyMatrix4(camera.matrixWorldInverse);
|
375
384
|
|
385
|
+
box.getSize(size);
|
386
|
+
box.setFromCenterAndSize(center, size);
|
387
|
+
|
388
|
+
const verticalFov = camera.fov;
|
389
|
+
const horizontalFov = 2 * Math.atan(Math.tan(verticalFov * Math.PI / 360 / 2) * camera.aspect) / Math.PI * 360;
|
390
|
+
const fitHeightDistance = size.y / (2 * Math.atan(Math.PI * verticalFov / 360));
|
391
|
+
const fitWidthDistance = size.x / (2 * Math.atan(Math.PI * horizontalFov / 360));
|
392
|
+
|
393
|
+
const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance) + size.z / 2;
|
394
|
+
|
395
|
+
if (debugCameraFit) {
|
396
|
+
console.log("Fit camera to objects", fitHeightDistance, fitWidthDistance, "distance", distance);
|
397
|
+
}
|
398
|
+
|
399
|
+
const cameraWp = getWorldPosition(camera);
|
376
400
|
const direction = controls.target.clone()
|
377
|
-
.sub(
|
401
|
+
.sub(cameraWp)
|
378
402
|
.normalize()
|
379
403
|
.multiplyScalar(distance);
|
380
404
|
|
@@ -384,13 +408,32 @@
|
|
384
408
|
|
385
409
|
camera.near = distance / 100;
|
386
410
|
camera.far = distance * 100;
|
411
|
+
|
412
|
+
camera.updateMatrixWorld();
|
387
413
|
camera.updateProjectionMatrix();
|
388
414
|
|
389
|
-
camera.
|
415
|
+
setWorldPosition(camera, controls.target.clone().sub(direction));
|
390
416
|
|
417
|
+
if (debugCameraFit) {
|
418
|
+
const helper = new Box3Helper(box);
|
419
|
+
this.context.scene.add(helper);
|
420
|
+
setWorldRotation(helper, getWorldRotation(camera));
|
421
|
+
|
422
|
+
if (!this._haveAttachedKeyboardEvents) {
|
423
|
+
this._haveAttachedKeyboardEvents = true;
|
424
|
+
document.body.addEventListener("keydown", (e) => {
|
425
|
+
if (e.code === "KeyF") {
|
426
|
+
this.fitCameraToObjects(objects);
|
427
|
+
}
|
428
|
+
});
|
429
|
+
}
|
430
|
+
}
|
431
|
+
|
391
432
|
controls.update();
|
392
433
|
}
|
393
434
|
|
435
|
+
private _haveAttachedKeyboardEvents: boolean = false;
|
436
|
+
|
394
437
|
// private onPositionDrag(){
|
395
438
|
|
396
439
|
// }
|
@@ -5,10 +5,34 @@
|
|
5
5
|
import { EquirectangularRefractionMapping, sRGBEncoding, Texture, TextureLoader } from "three"
|
6
6
|
import { syncField } from "../engine/engine_networking_auto";
|
7
7
|
import { Camera } from "./Camera";
|
8
|
-
import { getParam
|
8
|
+
import { getParam } from "../engine/engine_utils";
|
9
|
+
import { ContextRegistry } from "../engine/engine_context_registry";
|
9
10
|
|
10
11
|
const debug = getParam("debugskybox");
|
11
12
|
|
13
|
+
ContextRegistry.addContextCreatedCallback((args) => {
|
14
|
+
const context = args.context;
|
15
|
+
const skyboxImage = context.domElement.getAttribute("skybox-image");
|
16
|
+
const environmentImage = context.domElement.getAttribute("environment-image");
|
17
|
+
if (skyboxImage) {
|
18
|
+
if (debug) console.log("Creating remote skybox to load " + skyboxImage);
|
19
|
+
const remote = new RemoteSkybox();
|
20
|
+
remote.url = skyboxImage;
|
21
|
+
remote.allowDrop = false;
|
22
|
+
remote.environment = false;
|
23
|
+
remote.background = true;
|
24
|
+
GameObject.addComponent(context.scene, remote);
|
25
|
+
}
|
26
|
+
if (environmentImage) {
|
27
|
+
const remote = new RemoteSkybox();
|
28
|
+
remote.url = environmentImage;
|
29
|
+
remote.allowDrop = false;
|
30
|
+
remote.environment = true;
|
31
|
+
remote.background = false;
|
32
|
+
GameObject.addComponent(context.scene, remote);
|
33
|
+
}
|
34
|
+
});
|
35
|
+
|
12
36
|
export class RemoteSkybox extends Behaviour {
|
13
37
|
|
14
38
|
@syncField("setSkybox")
|
@@ -53,7 +77,7 @@
|
|
53
77
|
console.warn("Potentially invalid skybox url", this.url, "on", this.name);
|
54
78
|
}
|
55
79
|
|
56
|
-
if(debug) console.log("Remote skybox url?: " + url);
|
80
|
+
if (debug) console.log("Remote skybox url?: " + url);
|
57
81
|
|
58
82
|
if (this._prevUrl === url && this._prevLoadedEnvironment) {
|
59
83
|
this.applySkybox();
|
@@ -81,7 +105,7 @@
|
|
81
105
|
this._loader = new TextureLoader();
|
82
106
|
}
|
83
107
|
|
84
|
-
if(debug) console.log("Loading skybox: " + url);
|
108
|
+
if (debug) console.log("Loading skybox: " + url);
|
85
109
|
const envMap = await this._loader.loadAsync(url);
|
86
110
|
if (!envMap) return;
|
87
111
|
// Check if we're still enabled
|
@@ -110,7 +134,7 @@
|
|
110
134
|
if (this.context.scene.environment !== envMap)
|
111
135
|
this._prevEnvironment = this.context.scene.environment;
|
112
136
|
|
113
|
-
if(debug) console.log("Set remote skybox", this.url);
|
137
|
+
if (debug) console.log("Set remote skybox", this.url);
|
114
138
|
if (this.environment)
|
115
139
|
this.context.scene.environment = envMap;
|
116
140
|
if (this.background && !Camera.backgroundShouldBeTransparent(this.context))
|
@@ -147,12 +171,12 @@
|
|
147
171
|
for (const type of e.dataTransfer.types) {
|
148
172
|
if (type === "text/uri-list") {
|
149
173
|
const url = e.dataTransfer.getData(type);
|
150
|
-
if(debug) console.log(type, url);
|
174
|
+
if (debug) console.log(type, url);
|
151
175
|
let name = new RegExp(/polyhaven.com\/asset_img\/.+?\/(?<name>.+)\.png/).exec(url)?.groups?.name;
|
152
176
|
if (!name) {
|
153
177
|
name = new RegExp(/polyhaven\.com\/a\/(?<name>.+)/).exec(url)?.groups?.name;
|
154
178
|
}
|
155
|
-
if(debug) console.log(name);
|
179
|
+
if (debug) console.log(name);
|
156
180
|
if (name) {
|
157
181
|
const envurl = "https://dl.polyhaven.org/file/ph-assets/HDRIs/exr/1k/" + name + "_1k.exr";
|
158
182
|
this.setSkybox(envurl);
|