Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Movable Camera pivot point Fix #265 #266

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions libs/ff-three/source/CameraController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
Vector3,
Matrix4,
Box3,
Spherical,
Euler,
Quaternion,
} from "three";

import math from "@ff/core/math";
Expand All @@ -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 }
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -177,15 +191,32 @@ 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);
threeMath.composeOrbitMatrix(_vec3a, _vec3b, _mat4);

_vec3c.setScalar(1);
//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);

_vec3c.copy(this.pivot).applyMatrix4(_mat4);
_vec3b.sub(_vec3c);

offset.x = _vec3b.x;
offset.y = _vec3b.y;

Expand Down Expand Up @@ -221,7 +252,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
Expand All @@ -230,9 +271,6 @@ export default class CameraController implements IManip
camera.updateProjectionMatrix();
}

threeMath.composeOrbitMatrix(_vec3a, _vec3b, object.matrix);
object.matrixWorldNeedsUpdate = true;

return true;
}

Expand Down
3 changes: 3 additions & 0 deletions libs/ff-three/source/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions source/client/components/CVAnnotationsTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});
Expand Down
97 changes: 81 additions & 16 deletions source/client/components/CVOrbitNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

import { Box3 } from "three";
import { Box3, Euler, Matrix4, Quaternion, Vector3 } from "three";

import CObject3D, { Node, types } from "@ff/scene/components/CObject3D";

Expand All @@ -25,11 +25,14 @@ 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";
import CVARManager from "./CVARManager";
import CVModel2 from "./CVModel2";
import { getMeshTransform } from "client/utils/Helpers";
import { DEG2RAD, RAD2DEG } from "three/src/math/MathUtils";

////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -72,14 +75,14 @@ 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),
zoomExtents: types.Event("Settings.ZoomExtents"),
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 ]),
Expand All @@ -98,6 +101,8 @@ export default class CVOrbitNavigation extends CObject3D
private _isAutoZooming = false;
private _autoRotationStartTime = null;
private _initYOrbit = null;
private _projection :EProjection = null;
private _clickDebounce :number = null

constructor(node: Node, id: string)
{
Expand All @@ -110,6 +115,7 @@ export default class CVOrbitNavigation extends CObject3D
this.ins.enabled,
this.ins.orbit,
this.ins.offset,
this.ins.pivot,
this.ins.autoZoom,
this.ins.autoRotation,
this.ins.autoRotationSpeed,
Expand All @@ -125,6 +131,7 @@ export default class CVOrbitNavigation extends CObject3D
return [
this.ins.orbit,
this.ins.offset,
this.ins.pivot,
];
}

Expand Down Expand Up @@ -168,13 +175,8 @@ export default class CVOrbitNavigation extends CObject3D
const cameraComponent = this._scene.activeCameraComponent;
const camera = cameraComponent ? cameraComponent.camera : null;

const { projection, preset, orbit, offset } = 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) {
Expand All @@ -200,9 +202,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) {
Expand Down Expand Up @@ -262,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();
Expand Down Expand Up @@ -299,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);
Expand Down Expand Up @@ -364,6 +369,7 @@ export default class CVOrbitNavigation extends CObject3D
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),
Expand All @@ -386,6 +392,7 @@ export default class CVOrbitNavigation extends CObject3D
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(),
Expand All @@ -410,18 +417,76 @@ 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;
}

protected onDoubleClick(event: ITriggerEvent){
if(event.component?.typeName != "CVModel2") return;
const model = event.component as CVModel2;
const meshTransform = getMeshTransform(model.object3D, event.object3D);
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'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.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);

//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 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)
{
const viewport = event.viewport;
Expand Down
2 changes: 1 addition & 1 deletion source/client/components/CVViewTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ViewToolView extends ToolView<CVViewTool>
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;

Expand Down
Loading