@needle-tools/engine 3.5.12-beta → 3.5.13-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 @@ import { setCameraController } from "../engine/engine_camera";
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 @@ export class OrbitControls extends Behaviour implements ICameraController {
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.5) {
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 @@ export class OrbitControls extends Behaviour implements ICameraController {
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
- box.expandByObject(object);
371
+ for (const object of objects) {
372
+ // ignore Box3Helpers
373
+ if (object instanceof Box3Helper) continue;
374
+ box.expandByObject(object, true);
375
+ }
376
+
377
+ camera.updateMatrixWorld();
378
+ camera.updateProjectionMatrix();
367
379
 
368
- box.getSize(size);
369
380
  box.getCenter(center);
370
381
 
371
- const maxSize = Math.max(size.x, size.y, size.z);
372
- const fitHeightDistance = maxSize / (2 * Math.atan(Math.PI * camera.fov / 360));
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);
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;
375
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(camera.position)
401
+ .sub(cameraWp)
378
402
  .normalize()
379
403
  .multiplyScalar(distance);
380
404
 
@@ -384,13 +408,32 @@ export class OrbitControls extends Behaviour implements ICameraController {
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.position.copy(controls.target).sub(direction);
415
+ setWorldPosition(camera, controls.target.clone().sub(direction));
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
+ }
390
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 @@ import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader";
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, resolveUrl } from "../engine/engine_utils";
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 @@ export class RemoteSkybox extends Behaviour {
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 @@ export class RemoteSkybox extends Behaviour {
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 @@ export class RemoteSkybox extends Behaviour {
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 @@ export class RemoteSkybox extends Behaviour {
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);