From e4d2f6a7b61d4797844ec083b9cbebf4f64a8c1b Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Thu, 1 Aug 2024 12:12:32 +0200 Subject: [PATCH 01/13] backport [ff-three #3](https://github.com/Smithsonian/ff-three/pull/3) in merged submodules code --- libs/ff-three/source/CameraController.ts | 41 +++++++++++++++++++----- libs/ff-three/source/math.ts | 3 ++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/libs/ff-three/source/CameraController.ts b/libs/ff-three/source/CameraController.ts index 8255262f..c68ed53a 100644 --- a/libs/ff-three/source/CameraController.ts +++ b/libs/ff-three/source/CameraController.ts @@ -10,6 +10,9 @@ import { Vector3, Matrix4, Box3, + Spherical, + Euler, + Quaternion, } from "three"; import math from "@ff/core/math"; @@ -30,6 +33,9 @@ const _mat4 = new Matrix4(); const _box3 = new Box3(); const _vec3a = new Vector3(); const _vec3b = new Vector3(); +const _vec3c = new Vector3(); +const _eua = new Euler(); +const _quat = new Quaternion(); enum EControllerMode { Orbit, FirstPerson } enum EManipMode { Off, Pan, Orbit, Dolly, Zoom, PanDolly, Roll } @@ -42,6 +48,7 @@ export default class CameraController implements IManip orbit = new Vector3(0, 0, 0); offset = new Vector3(0, 0, 50); + pivot = new Vector3(0, 0, 0); minOrbit = new Vector3(-90, -Infinity, -Infinity); maxOrbit = new Vector3(90, Infinity, Infinity); @@ -146,15 +153,22 @@ export default class CameraController implements IManip this.viewportHeight = height; } + /** + * Copy the object's matrix into the controller's properties + * effectively the inverse operation of updateCamera + */ updateController(object?: Object3D, adaptLimits?: boolean) { const camera = this.camera; object = object || camera; - const orbit = this.orbit; const offset = this.offset; - threeMath.decomposeOrbitMatrix(object.matrix, orbit, offset); - this.orbit.multiplyScalar(threeMath.RAD2DEG); + object.matrix.decompose(_vec3b, _quat, _vec3c); + //Rotation + _eua.setFromQuaternion(_quat, "YXZ"); + _vec3a.setFromEuler(_eua).multiplyScalar(threeMath.RAD2DEG); + this.orbit.copy(_vec3a); + this.offset.copy(_vec3b.sub(this.pivot).applyQuaternion(_quat.invert())); if (adaptLimits) { this.minOffset.min(offset); @@ -179,12 +193,16 @@ export default class CameraController implements IManip // rotate box to camera space _vec3a.copy(this.orbit).multiplyScalar(math.DEG2RAD); + _quat.setFromEuler(_eua.setFromVector3(_vec3a)); _vec3b.setScalar(0); - threeMath.composeOrbitMatrix(_vec3a, _vec3b, _mat4); + _vec3c.setScalar(1); + //Ignore the pivot point for now + _mat4.compose(_vec3b, _quat, _vec3c); _box3.copy(box).applyMatrix4(_mat4.transpose()); _box3.getSize(_vec3a); _box3.getCenter(_vec3b); + _vec3b.sub(this.pivot); offset.x = _vec3b.x; offset.y = _vec3b.y; @@ -221,7 +239,17 @@ export default class CameraController implements IManip } _vec3a.copy(this.orbit).multiplyScalar(math.DEG2RAD); - _vec3b.copy(this.offset); + _eua.setFromVector3(_vec3a, "YXZ"); + _quat.setFromEuler(_eua); + //Position, relative to pivot point + _vec3b.copy(this.offset).applyEuler(_eua).add(this.pivot); + //Keep scale + _vec3c.setFromMatrixScale(object.matrix); + //Compose everything + object.matrix.compose(_vec3b, _quat, _vec3c); + + + object.matrixWorldNeedsUpdate = true; if (camera.isOrthographicCamera) { _vec3b.z = this.maxOffset.z; // fixed distance = maxOffset.z @@ -230,9 +258,6 @@ export default class CameraController implements IManip camera.updateProjectionMatrix(); } - threeMath.composeOrbitMatrix(_vec3a, _vec3b, object.matrix); - object.matrixWorldNeedsUpdate = true; - return true; } diff --git a/libs/ff-three/source/math.ts b/libs/ff-three/source/math.ts index 0545a3e4..346fea48 100644 --- a/libs/ff-three/source/math.ts +++ b/libs/ff-three/source/math.ts @@ -37,6 +37,9 @@ const math = { DEG2RAD: 0.01745329251994329576923690768489, RAD2DEG: 57.295779513082320876798154814105, + /** + * one-step orbit matrix composition when the pivot point is (0, 0, 0). + */ composeOrbitMatrix: function(orientation: Vector3, offset: Vector3, result?: Matrix4): Matrix4 { const pitch = orientation.x; From 727d4360ffd71069d4871e577152bc4929a3049a Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Mon, 15 Apr 2024 11:07:29 +0200 Subject: [PATCH 02/13] add navigation type setting --- source/client/components/CVOrbitNavigation.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index 8befc70b..ca6e8ebd 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -25,7 +25,7 @@ import CScene, { IRenderContext } from "@ff/scene/components/CScene"; import CTransform, { ERotationOrder } from "@ff/scene/components/CTransform"; import { EProjection } from "@ff/three/UniversalCamera"; -import { INavigation } from "client/schema/setup"; +import { ENavigationType, TNavigationType, INavigation } from "client/schema/setup"; import CVScene from "./CVScene"; import CVAssetManager from "./CVAssetManager"; @@ -68,6 +68,7 @@ export default class CVOrbitNavigation extends CObject3D protected static readonly ins = { enabled: types.Boolean("Settings.Enabled", true), + type: types.Enum("Settings.Type", ENavigationType, ENavigationType.Orbit), pointerEnabled: types.Boolean("Settings.PointerEnabled", true), promptEnabled: types.Boolean("Settings.PromptEnabled", true), isInUse: types.Boolean("Camera.IsInUse", false), @@ -108,6 +109,7 @@ export default class CVOrbitNavigation extends CObject3D get settingProperties() { return [ this.ins.enabled, + this.ins.type, this.ins.orbit, this.ins.offset, this.ins.autoZoom, @@ -359,6 +361,7 @@ export default class CVOrbitNavigation extends CObject3D this.ins.copyValues({ enabled: !!data.enabled, + type: ENavigationType[data.type] || ENavigationType.Orbit, autoZoom: !!data.autoZoom, autoRotation: !!data.autoRotation, lightsFollowCamera: !!data.lightsFollowCamera, @@ -381,7 +384,7 @@ export default class CVOrbitNavigation extends CObject3D data.autoRotation = ins.autoRotation.value; data.lightsFollowCamera = ins.lightsFollowCamera.value; - data.type = "Orbit"; + data.type = ENavigationType[ins.type.value] as TNavigationType; data.orbit = { orbit: ins.orbit.cloneValue(), From 47969ef6eeb8b8450e756fc23ade4ab3e30ef44f Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Mon, 15 Apr 2024 13:38:17 +0200 Subject: [PATCH 03/13] prototype orbit pivot --- source/client/components/CVOrbitNavigation.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index ca6e8ebd..ed5fa33f 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -81,6 +81,7 @@ export default class CVOrbitNavigation extends CObject3D autoZoom: types.Boolean("Settings.AutoZoom", true), orbit: types.Vector3("Current.Orbit", [ -25, -25, 0 ]), offset: types.Vector3("Current.Offset", [ 0, 0, 100 ]), + pivot: types.Vector3("Current.Pivot", [ 0, 0, 0 ]), minOrbit: types.Vector3("Limits.Min.Orbit", [ -90, -Infinity, -Infinity ]), minOffset: types.Vector3("Limits.Min.Offset", [ -Infinity, -Infinity, 0.1 ]), maxOrbit: types.Vector3("Limits.Max.Orbit", [ 90, Infinity, Infinity ]), @@ -112,6 +113,7 @@ export default class CVOrbitNavigation extends CObject3D this.ins.type, this.ins.orbit, this.ins.offset, + this.ins.pivot, this.ins.autoZoom, this.ins.autoRotation, this.ins.autoRotationSpeed, @@ -170,7 +172,7 @@ export default class CVOrbitNavigation extends CObject3D const cameraComponent = this._scene.activeCameraComponent; const camera = cameraComponent ? cameraComponent.camera : null; - const { projection, preset, orbit, offset } = ins; + const { projection, preset, orbit, offset, pivot } = ins; // camera projection if (cameraComponent && projection.changed) { @@ -202,9 +204,10 @@ export default class CVOrbitNavigation extends CObject3D const { minOrbit, minOffset, maxOrbit, maxOffset} = ins; // orbit, offset and limits - if (orbit.changed || offset.changed) { + if (orbit.changed || offset.changed || pivot.changed) { controller.orbit.fromArray(orbit.value); controller.offset.fromArray(offset.value); + controller.pivot.fromArray(pivot.value); } if (minOrbit.changed || minOffset.changed || maxOrbit.changed || maxOffset.changed) { From 2829fb3ebc76a56b1a93fc5d9a7ff7f824af843b Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Mon, 15 Apr 2024 15:41:58 +0200 Subject: [PATCH 04/13] handle double-click pivot move --- source/client/components/CVOrbitNavigation.ts | 17 +++++++++++++++++ source/client/ui/SceneView.ts | 3 +++ 2 files changed, 20 insertions(+) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index ed5fa33f..b5ed009f 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -30,6 +30,8 @@ import { ENavigationType, TNavigationType, INavigation } from "client/schema/set import CVScene from "./CVScene"; import CVAssetManager from "./CVAssetManager"; import CVARManager from "./CVARManager"; +import CVModel2 from "./CVModel2"; +import { getMeshTransform } from "client/utils/Helpers"; //////////////////////////////////////////////////////////////////////////////// @@ -129,6 +131,7 @@ export default class CVOrbitNavigation extends CObject3D return [ this.ins.orbit, this.ins.offset, + this.ins.pivot, ]; } @@ -147,6 +150,7 @@ export default class CVOrbitNavigation extends CObject3D super.create(); this.system.on(["pointer-down", "pointer-up", "pointer-move"], this.onPointer, this); + this.system.on("double-click", this.onDoubleClick, this); this.system.on("wheel", this.onTrigger, this); this.system.on("keydown", this.onKeyboard, this); @@ -428,6 +432,19 @@ export default class CVOrbitNavigation extends CObject3D this._hasChanged = true; } + protected onDoubleClick(event: ITriggerEvent){ + if(event.component.typeName != "CVModel2") return; + const model = event.component as CVModel2; + + const meshTransform = getMeshTransform(model.object3D, event.object3D); + const invMeshTransform = meshTransform.clone().invert(); + const bounds = model.localBoundingBox.clone().applyMatrix4(meshTransform); + // add mesh parent transforms in this branch + let localPosition = event.view.pickPosition(event as any, bounds).applyMatrix4(invMeshTransform); + this.ins.pivot.setValue(localPosition.toArray()); + this.ins.offset.setValue([0, 0, this.ins.offset.value[2]]); + } + protected onTrigger(event: ITriggerEvent) { const viewport = event.viewport; diff --git a/source/client/ui/SceneView.ts b/source/client/ui/SceneView.ts index 31ef915d..e2e90415 100644 --- a/source/client/ui/SceneView.ts +++ b/source/client/ui/SceneView.ts @@ -71,6 +71,7 @@ export default class SceneView extends SystemView this.ownerDocument.addEventListener("pointermove", this.manipTarget.onPointerMove); // To catch out of frame drag releases this.ownerDocument.addEventListener("pointerup", this.onPointerUpOrCancel); // To catch out of frame drag releases this.ownerDocument.addEventListener("pointercancel", this.onPointerUpOrCancel); // To catch out of frame drag releases + this.addEventListener("dblclick", this.manipTarget.onDoubleClick); this.addEventListener("wheel", this.manipTarget.onWheel); this.addEventListener("contextmenu", this.manipTarget.onContextMenu); this.addEventListener("keydown", this.manipTarget.onKeyDown); @@ -168,6 +169,7 @@ export default class SceneView extends SystemView this.ownerDocument.addEventListener("pointermove", this.manipTarget.onPointerMove); // To catch out of frame drag releases this.ownerDocument.addEventListener("pointerup", this.onPointerUpOrCancel); // To catch out of frame drag releases this.ownerDocument.addEventListener("pointercancel", this.onPointerUpOrCancel); // To catch out of frame drag releases + this.addEventListener("dblclick", this.manipTarget.onDoubleClick); this.addEventListener("wheel", this.manipTarget.onWheel); this.addEventListener("contextmenu", this.manipTarget.onContextMenu); this.addEventListener("keydown", this.manipTarget.onKeyDown); @@ -186,6 +188,7 @@ export default class SceneView extends SystemView this.ownerDocument.removeEventListener("pointermove", this.manipTarget.onPointerMove); // To catch out of frame drag releases this.ownerDocument.removeEventListener("pointerup", this.onPointerUpOrCancel); // To catch out of frame drag releases this.ownerDocument.removeEventListener("pointercancel", this.onPointerUpOrCancel); // To catch out of frame drag releases + this.removeEventListener("dblclick", this.manipTarget.onDoubleClick); this.removeEventListener("wheel", this.manipTarget.onWheel); this.removeEventListener("contextmenu", this.manipTarget.onContextMenu); this.removeEventListener("keydown", this.manipTarget.onKeyDown); From c0ae81a143812c7a65f584a242a73884c96fcd54 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Tue, 30 Apr 2024 09:58:45 +0200 Subject: [PATCH 05/13] add model matrix to event coordinates to take into account the scene's pose --- source/client/components/CVOrbitNavigation.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index b5ed009f..d4694d30 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Box3 } from "three"; +import { Box3, Euler, Matrix4, Vector3 } from "three"; import CObject3D, { Node, types } from "@ff/scene/components/CObject3D"; @@ -32,6 +32,7 @@ import CVAssetManager from "./CVAssetManager"; import CVARManager from "./CVARManager"; import CVModel2 from "./CVModel2"; import { getMeshTransform } from "client/utils/Helpers"; +import { RAD2DEG } from "three/src/math/MathUtils"; //////////////////////////////////////////////////////////////////////////////// @@ -433,16 +434,22 @@ export default class CVOrbitNavigation extends CObject3D } protected onDoubleClick(event: ITriggerEvent){ - if(event.component.typeName != "CVModel2") return; + if(event.component?.typeName != "CVModel2") return; const model = event.component as CVModel2; - const meshTransform = getMeshTransform(model.object3D, event.object3D); + + const camPos = new Vector3().fromArray(this.ins.pivot.value).add(new Vector3().fromArray(this.ins.offset.value)); + + //Add CVNode's transform const invMeshTransform = meshTransform.clone().invert(); const bounds = model.localBoundingBox.clone().applyMatrix4(meshTransform); // add mesh parent transforms in this branch - let localPosition = event.view.pickPosition(event as any, bounds).applyMatrix4(invMeshTransform); + let localPosition = event.view.pickPosition(event as any, bounds).applyMatrix4(invMeshTransform).applyMatrix4(model.object3D.matrix); + console.debug("Local click position : (%f,%f,%f)", localPosition.x, localPosition.y, localPosition.z); + //New pivot is straight-up where the user clicked this.ins.pivot.setValue(localPosition.toArray()); - this.ins.offset.setValue([0, 0, this.ins.offset.value[2]]); + //we compute the new orbit and offset.z values to keep the camera mostly in place + this.ins.offset.setValue([0, 0, localPosition.distanceTo(camPos)]); } protected onTrigger(event: ITriggerEvent) From 71a1d432be59f15a2c300c2ba753697ac624abf5 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Tue, 30 Apr 2024 12:35:49 +0200 Subject: [PATCH 06/13] Fix : offset compensation, mesh transform support --- source/client/components/CVOrbitNavigation.ts | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index d4694d30..9da9d8e3 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Box3, Euler, Matrix4, Vector3 } from "three"; +import { Box3, Euler, Matrix4, Quaternion, Vector3 } from "three"; import CObject3D, { Node, types } from "@ff/scene/components/CObject3D"; @@ -32,7 +32,7 @@ import CVAssetManager from "./CVAssetManager"; import CVARManager from "./CVARManager"; import CVModel2 from "./CVModel2"; import { getMeshTransform } from "client/utils/Helpers"; -import { RAD2DEG } from "three/src/math/MathUtils"; +import { DEG2RAD, RAD2DEG } from "three/src/math/MathUtils"; //////////////////////////////////////////////////////////////////////////////// @@ -437,19 +437,47 @@ export default class CVOrbitNavigation extends CObject3D if(event.component?.typeName != "CVModel2") return; const model = event.component as CVModel2; const meshTransform = getMeshTransform(model.object3D, event.object3D); - - const camPos = new Vector3().fromArray(this.ins.pivot.value).add(new Vector3().fromArray(this.ins.offset.value)); + let pos = new Vector3(), rot = new Quaternion(), scale = new Vector3(); + model.transform.object3D.matrix.decompose(pos, rot, scale) //Add CVNode's transform const invMeshTransform = meshTransform.clone().invert(); const bounds = model.localBoundingBox.clone().applyMatrix4(meshTransform); - // add mesh parent transforms in this branch - let localPosition = event.view.pickPosition(event as any, bounds).applyMatrix4(invMeshTransform).applyMatrix4(model.object3D.matrix); - console.debug("Local click position : (%f,%f,%f)", localPosition.x, localPosition.y, localPosition.z); + // add mesh's "pose". + let localPosition = event.view.pickPosition(event as any, bounds) + .applyMatrix4(invMeshTransform) //Add internal transform + .applyMatrix4(model.object3D.matrix) //Add mesh "pose" + .applyMatrix4(model.transform.object3D.matrix) //Add mesh's "transform" (attached CTransform) + + const orbit = new Vector3().fromArray(this.ins.orbit.value).multiplyScalar(DEG2RAD); + const pivot = new Vector3().fromArray(this.ins.pivot.value); + + //we compute the new orbit and offset.z values to keep the camera in place + let orbitRad = new Euler().setFromVector3(orbit, "YXZ"); + let orbitQuat = new Quaternion().setFromEuler(orbitRad); + //Offset from pivot with applied rotation + const offset = new Vector3().fromArray(this.ins.offset.value).applyQuaternion(orbitQuat); + //Current camera absolute position + const camPos = pivot.clone().add(offset); + //We want the camera position to stay the same with the new parameters + //First we need to get the path from the camera to the new pivot + const clickToCam = camPos.clone().sub(localPosition); + //We then use it to "look at" the new pivot + orbitQuat.setFromUnitVectors( + new Vector3(0, 0, 1), + clickToCam.clone().normalize(), + ); + + //Rotation + orbitRad.setFromQuaternion(orbitQuat, "YXZ"); + const orbitAngles = new Vector3().setFromEuler(orbitRad).multiplyScalar(RAD2DEG); + + //New pivot is straight-up where the user clicked this.ins.pivot.setValue(localPosition.toArray()); - //we compute the new orbit and offset.z values to keep the camera mostly in place - this.ins.offset.setValue([0, 0, localPosition.distanceTo(camPos)]); + //We always keep roll as-it-was because it tends to add up in disorienting ways + this.ins.orbit.setValue([orbitAngles.x, orbitAngles.y, this.ins.orbit.value[2]]); + this.ins.offset.setValue([0, 0, clickToCam.length()]); } protected onTrigger(event: ITriggerEvent) From d7427ca050a180e662806cc00dc55452128bc515 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Tue, 30 Apr 2024 15:11:48 +0200 Subject: [PATCH 07/13] proper scene file storage for pivot point. Update schema. --- source/client/components/CVOrbitNavigation.ts | 7 ++-- source/client/schema/json/setup.schema.json | 38 +++++++++++++++++-- source/client/schema/setup.ts | 1 + 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index 9da9d8e3..da668d0c 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -71,7 +71,6 @@ export default class CVOrbitNavigation extends CObject3D protected static readonly ins = { enabled: types.Boolean("Settings.Enabled", true), - type: types.Enum("Settings.Type", ENavigationType, ENavigationType.Orbit), pointerEnabled: types.Boolean("Settings.PointerEnabled", true), promptEnabled: types.Boolean("Settings.PromptEnabled", true), isInUse: types.Boolean("Camera.IsInUse", false), @@ -113,7 +112,6 @@ export default class CVOrbitNavigation extends CObject3D get settingProperties() { return [ this.ins.enabled, - this.ins.type, this.ins.orbit, this.ins.offset, this.ins.pivot, @@ -369,12 +367,12 @@ export default class CVOrbitNavigation extends CObject3D this.ins.copyValues({ enabled: !!data.enabled, - type: ENavigationType[data.type] || ENavigationType.Orbit, autoZoom: !!data.autoZoom, autoRotation: !!data.autoRotation, lightsFollowCamera: !!data.lightsFollowCamera, orbit: orbit.orbit, offset: orbit.offset, + pivot: orbit.pivot || [ 0, 0, 0 ], minOrbit: _replaceNull(orbit.minOrbit, -Infinity), maxOrbit: _replaceNull(orbit.maxOrbit, Infinity), minOffset: _replaceNull(orbit.minOffset, -Infinity), @@ -392,11 +390,12 @@ export default class CVOrbitNavigation extends CObject3D data.autoRotation = ins.autoRotation.value; data.lightsFollowCamera = ins.lightsFollowCamera.value; - data.type = ENavigationType[ins.type.value] as TNavigationType; + data.type = "Orbit"; data.orbit = { orbit: ins.orbit.cloneValue(), offset: ins.offset.cloneValue(), + pivot: ins.pivot.cloneValue(), minOrbit: ins.minOrbit.cloneValue(), maxOrbit: ins.maxOrbit.cloneValue(), minOffset: ins.minOffset.cloneValue(), diff --git a/source/client/schema/json/setup.schema.json b/source/client/schema/json/setup.schema.json index 21bc197d..a0499906 100644 --- a/source/client/schema/json/setup.schema.json +++ b/source/client/schema/json/setup.schema.json @@ -6,6 +6,20 @@ "description": "Tours and settings for explorer documents (background, interface, etc.)", "definitions": { + "vector3nullable": { + "description": "3-component vector where each member can be null", + "$id": "#vector3", + "type": "array", + "items": { + "oneOf":[ + {"type": "number"}, + {"type": "null"} + ] + }, + "minItems": 3, + "maxItems": 3, + "default": [ 0, 0, 0 ] + }, "viewer": { "type": "object", "properties": { @@ -101,11 +115,29 @@ "type": "boolean" }, "orbit": { - "$comment": "TODO: Implement", - "type": "object", "properties": { - + "orbit":{ + "$ref": "./common.schema.json#/definitions/vector3" + }, + "offset": { + "$ref": "./common.schema.json#/definitions/vector3" + }, + "pivot": { + "$ref": "./common.schema.json#/definitions/vector3" + }, + "minOrbit": { + "$ref": "#/definitions/vector3nullable" + }, + "maxOrbit": { + "$ref": "#/definitions/vector3nullable" + }, + "minOffset": { + "$ref": "#/definitions/vector3nullable" + }, + "maxOffset": { + "$ref": "#/definitions/vector3nullable" + } } }, "walk": { diff --git a/source/client/schema/setup.ts b/source/client/schema/setup.ts index 1dc2b7cf..dbbf625f 100644 --- a/source/client/schema/setup.ts +++ b/source/client/schema/setup.ts @@ -96,6 +96,7 @@ export interface IOrbitNavigation { orbit: number[]; offset: number[]; + pivot?: number[]; minOrbit: number[]; maxOrbit: number[]; minOffset: number[]; From d5c4d666c9fbca1c951140517c4f1bcfe66e07a0 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Mon, 17 Jun 2024 11:57:06 +0200 Subject: [PATCH 08/13] merge OrbitNavigation and Camera projection property --- source/client/components/CVOrbitNavigation.ts | 8 +------- source/client/components/CVViewTool.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index da668d0c..3c6a0776 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -75,7 +75,6 @@ export default class CVOrbitNavigation extends CObject3D promptEnabled: types.Boolean("Settings.PromptEnabled", true), isInUse: types.Boolean("Camera.IsInUse", false), preset: types.Enum("Camera.ViewPreset", EViewPreset, EViewPreset.None), - projection: types.Enum("Camera.Projection", EProjection, EProjection.Perspective), lightsFollowCamera: types.Boolean("Navigation.LightsFollowCam", true), autoRotation: types.Boolean("Navigation.AutoRotation", false), autoRotationSpeed: types.Number("Navigation.AutoRotationSpeed", 10), @@ -175,13 +174,8 @@ export default class CVOrbitNavigation extends CObject3D const cameraComponent = this._scene.activeCameraComponent; const camera = cameraComponent ? cameraComponent.camera : null; - const { projection, preset, orbit, offset, pivot } = ins; + const { preset, orbit, offset, pivot } = ins; - // camera projection - if (cameraComponent && projection.changed) { - camera.setProjection(projection.getValidatedValue()); - cameraComponent.ins.projection.setValue(projection.value, true); - } // camera preset if (preset.changed && preset.value !== EViewPreset.None) { diff --git a/source/client/components/CVViewTool.ts b/source/client/components/CVViewTool.ts index a5da5306..04ad38c9 100644 --- a/source/client/components/CVViewTool.ts +++ b/source/client/components/CVViewTool.ts @@ -63,7 +63,7 @@ export class ViewToolView extends ToolView const navigation = document.setup.navigation; const language = document.setup.language; - const projection = navigation.ins.projection; + const projection = navigation.scene.activeCameraComponent.ins.projection; const preset = navigation.ins.preset; const zoom = navigation.ins.zoomExtents; From ab114cdc26063de6dcb581ea7f34b7f2957cac22 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Mon, 17 Jun 2024 12:08:13 +0200 Subject: [PATCH 09/13] retain pivot point when saving annotation view --- source/client/components/CVAnnotationsTask.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/client/components/CVAnnotationsTask.ts b/source/client/components/CVAnnotationsTask.ts index b9d466f4..26694a07 100644 --- a/source/client/components/CVAnnotationsTask.ts +++ b/source/client/components/CVAnnotationsTask.ts @@ -162,13 +162,15 @@ export default class CVAnnotationsTask extends CVTask { const machine = this._machine; const props = machine.getTargetProperties(); - const orbitIdx = props.findIndex((elem) => {return elem.name == "Orbit"}); - const offsetIdx = props.findIndex((elem) => {return elem.name == "Offset"}); + const retainIdx = []; + for(let i = 0; i < props.length; i++) { + if(["Pivot", "Orbit", "Offset"].includes(props[i].name)) retainIdx.push(i); + } // set non camera properties to null to skip them const values = machine.getCurrentValues(); values.forEach((v, idx) => { - if(idx != orbitIdx && idx != offsetIdx) { + if(!retainIdx.includes(idx)) { values[idx] = null; } }); From e8f4bf9eaf9ef70463f037cc592f7ffe59ce333b Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Wed, 3 Jul 2024 11:11:18 +0200 Subject: [PATCH 10/13] fix a bug where if a mesh was the children of another node, the parent's transform wouldn't be taken into account when calculating pick position --- source/client/components/CVOrbitNavigation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index 3c6a0776..a5810fde 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -440,7 +440,7 @@ export default class CVOrbitNavigation extends CObject3D let localPosition = event.view.pickPosition(event as any, bounds) .applyMatrix4(invMeshTransform) //Add internal transform .applyMatrix4(model.object3D.matrix) //Add mesh "pose" - .applyMatrix4(model.transform.object3D.matrix) //Add mesh's "transform" (attached CTransform) + .applyMatrix4(model.transform.object3D.matrixWorld) //Add mesh's "transform" (attached CTransform) const orbit = new Vector3().fromArray(this.ins.orbit.value).multiplyScalar(DEG2RAD); const pivot = new Vector3().fromArray(this.ins.pivot.value); From 342ab8fe2b156e4196616e2bc90b74b4de610812 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Wed, 4 Sep 2024 14:22:57 +0200 Subject: [PATCH 11/13] add internal _projection value to CVOrbitNavigation to force update when necessary --- source/client/components/CVOrbitNavigation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index a5810fde..cf7fe2bd 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -101,6 +101,7 @@ export default class CVOrbitNavigation extends CObject3D private _isAutoZooming = false; private _autoRotationStartTime = null; private _initYOrbit = null; + private _projection :EProjection = null; constructor(node: Node, id: string) { @@ -264,7 +265,8 @@ export default class CVOrbitNavigation extends CObject3D controller.camera = cameraComponent.camera; const transform = cameraComponent.transform; - const forceUpdate = this.changed || ins.autoRotation.value || ins.promptActive.value; + + const forceUpdate = this.changed || this._projection != cameraComponent.ins.projection.value || ins.autoRotation.value || ins.promptActive.value; if ((ins.autoRotation.value || ins.promptActive.value) && this._autoRotationStartTime) { const now = performance.now(); @@ -301,6 +303,7 @@ export default class CVOrbitNavigation extends CObject3D } if (controller.updateCamera(transform.object3D, forceUpdate)) { + this._projection = cameraComponent.ins.projection.value; controller.orbit.toArray(ins.orbit.value); ins.orbit.set(true); controller.offset.toArray(ins.offset.value); From 442f0fbc4498155b99f8960d794ba85864898b06 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Thu, 5 Sep 2024 11:57:50 +0200 Subject: [PATCH 12/13] better zoomExtents with pivot --- libs/ff-three/source/CameraController.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/libs/ff-three/source/CameraController.ts b/libs/ff-three/source/CameraController.ts index c68ed53a..8c64618e 100644 --- a/libs/ff-three/source/CameraController.ts +++ b/libs/ff-three/source/CameraController.ts @@ -191,18 +191,31 @@ export default class CameraController implements IManip return; } + + // _vec3a.copy(this.orbit).multiplyScalar(math.DEG2RAD); + // _eua.setFromVector3(_vec3a, "YXZ"); + // _quat.setFromEuler(_eua); + // //Position, relative to pivot point + // _vec3b.copy(this.offset).applyEuler(_eua).add(this.pivot); + // //Keep scale + // _vec3c.setFromMatrixScale(object.matrix); + // //Compose everything + // object.matrix.compose(_vec3b, _quat, _vec3c); + + // rotate box to camera space _vec3a.copy(this.orbit).multiplyScalar(math.DEG2RAD); _quat.setFromEuler(_eua.setFromVector3(_vec3a)); _vec3b.setScalar(0); _vec3c.setScalar(1); - //Ignore the pivot point for now + //Ignore the pivot point for now. Rotate the box into camera space _mat4.compose(_vec3b, _quat, _vec3c); - _box3.copy(box).applyMatrix4(_mat4.transpose()); _box3.getSize(_vec3a); _box3.getCenter(_vec3b); - _vec3b.sub(this.pivot); + + _vec3c.copy(this.pivot).applyMatrix4(_mat4); + _vec3b.sub(_vec3c); offset.x = _vec3b.x; offset.y = _vec3b.y; From 5c92136af2f5be6f264f9068fdf3ddeaab627350 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Thu, 17 Oct 2024 11:26:21 +0200 Subject: [PATCH 13/13] dblclick on touch devices --- source/client/components/CVOrbitNavigation.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/source/client/components/CVOrbitNavigation.ts b/source/client/components/CVOrbitNavigation.ts index cf7fe2bd..7d81877b 100644 --- a/source/client/components/CVOrbitNavigation.ts +++ b/source/client/components/CVOrbitNavigation.ts @@ -102,6 +102,7 @@ export default class CVOrbitNavigation extends CObject3D private _autoRotationStartTime = null; private _initYOrbit = null; private _projection :EProjection = null; + private _clickDebounce :number = null constructor(node: Node, id: string) { @@ -149,7 +150,6 @@ export default class CVOrbitNavigation extends CObject3D super.create(); this.system.on(["pointer-down", "pointer-up", "pointer-move"], this.onPointer, this); - this.system.on("double-click", this.onDoubleClick, this); this.system.on("wheel", this.onTrigger, this); this.system.on("keydown", this.onKeyboard, this); @@ -417,15 +417,26 @@ export default class CVOrbitNavigation extends CObject3D return; } - if (this.ins.enabled.value && this._scene.activeCameraComponent) { - if (event.type === "pointer-down" && window.getSelection().type !== "None") { + if (!this.ins.enabled.value || !this._scene.activeCameraComponent) { + return; + } + + if (event.type === "pointer-down" ) { + if(window.getSelection().type !== "None"){ window.getSelection().removeAllRanges(); } - this._controller.setViewportSize(viewport.width, viewport.height); - this._controller.onPointer(event); - event.stopPropagation = true; + const ts = event.originalEvent.timeStamp; + if(ts < this._clickDebounce + 400){ + this.onDoubleClick({...event, type: "double-click", wheel: 0}); + this._clickDebounce = 0; + }else{ + this._clickDebounce = ts; + } } + this._controller.setViewportSize(viewport.width, viewport.height); + this._controller.onPointer(event); + event.stopPropagation = true; this._hasChanged = true; }