Skip to content
51 changes: 26 additions & 25 deletions src/extras/gizmo/gizmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import { Layer } from '../../scene/layer.js';
*/

// temporary variables
const tmpV1 = new Vec3();
const tmpV2 = new Vec3();
const tmpM1 = new Mat4();
const tmpM2 = new Mat4();
const tmpR1 = new Ray();
const v = new Vec3();
const position = new Vec3();
const angles = new Vec3();
const dir = new Vec3();
const m1 = new Mat4();
const m2 = new Mat4();
const ray = new Ray();

// constants
const MIN_SCALE = 1e-4;
Expand Down Expand Up @@ -409,9 +411,9 @@ class Gizmo extends EventHandler {
if (this._camera.projection === PROJECTION_PERSPECTIVE) {
const gizmoPos = this.root.getLocalPosition();
const cameraPos = this._camera.entity.getPosition();
return tmpV2.sub2(cameraPos, gizmoPos).normalize();
return dir.sub2(cameraPos, gizmoPos).normalize();
}
return tmpV2.copy(this._camera.entity.forward).mulScalar(-1);
return dir.copy(this._camera.entity.forward).mulScalar(-1);
}

/**
Expand All @@ -421,7 +423,7 @@ class Gizmo extends EventHandler {
get cameraDir() {
const cameraPos = this._camera.entity.getPosition();
const gizmoPos = this.root.getLocalPosition();
return tmpV2.sub2(cameraPos, gizmoPos).normalize();
return dir.sub2(cameraPos, gizmoPos).normalize();
}

/**
Expand Down Expand Up @@ -485,36 +487,36 @@ class Gizmo extends EventHandler {
* @protected
*/
_updatePosition() {
tmpV1.set(0, 0, 0);
position.set(0, 0, 0);
for (let i = 0; i < this.nodes.length; i++) {
const node = this.nodes[i];
tmpV1.add(node.getPosition());
position.add(node.getPosition());
}
tmpV1.mulScalar(1.0 / (this.nodes.length || 1));
position.mulScalar(1.0 / (this.nodes.length || 1));

if (tmpV1.distance(this.root.getLocalPosition()) < UPDATE_EPSILON) {
if (position.distance(this.root.getLocalPosition()) < UPDATE_EPSILON) {
return;
}

this.root.setLocalPosition(tmpV1);
this.fire(Gizmo.EVENT_POSITIONUPDATE, tmpV1);
this.root.setLocalPosition(position);
this.fire(Gizmo.EVENT_POSITIONUPDATE, position);
}

/**
* @protected
*/
_updateRotation() {
tmpV1.set(0, 0, 0);
angles.set(0, 0, 0);
if (this._coordSpace === 'local' && this.nodes.length !== 0) {
tmpV1.copy(this.nodes[this.nodes.length - 1].getEulerAngles());
angles.copy(this.nodes[this.nodes.length - 1].getEulerAngles());
}

if (tmpV1.distance(this.root.getLocalEulerAngles()) < UPDATE_EPSILON) {
if (angles.distance(this.root.getLocalEulerAngles()) < UPDATE_EPSILON) {
return;
}

this.root.setLocalEulerAngles(tmpV1);
this.fire(Gizmo.EVENT_ROTATIONUPDATE, tmpV1);
this.root.setLocalEulerAngles(angles);
this.fire(Gizmo.EVENT_ROTATIONUPDATE, angles);
}

/**
Expand Down Expand Up @@ -548,7 +550,7 @@ class Gizmo extends EventHandler {
_getSelection(x, y) {
const start = this._camera.screenToWorld(x, y, 0);
const end = this._camera.screenToWorld(x, y, this._camera.farClip - this._camera.nearClip);
const dir = tmpV1.copy(end).sub(start).normalize();
const dir = v.copy(end).sub(start).normalize();

const selection = [];
for (let i = 0; i < this.intersectShapes.length; i++) {
Expand All @@ -562,18 +564,17 @@ class Gizmo extends EventHandler {
const { tris, transform, priority } = shape.triData[j];

// combine node world transform with transform of tri relative to parent
const triWTM = tmpM1.copy(parentTM).mul(transform);
const invTriWTM = tmpM2.copy(triWTM).invert();
const triWTM = m1.copy(parentTM).mul(transform);
const invTriWTM = m2.copy(triWTM).invert();

const ray = tmpR1;
invTriWTM.transformPoint(start, ray.origin);
invTriWTM.transformVector(dir, ray.direction);
ray.direction.normalize();

for (let k = 0; k < tris.length; k++) {
if (tris[k].intersectsRay(ray, tmpV1)) {
if (tris[k].intersectsRay(ray, v)) {
selection.push({
dist: triWTM.transformPoint(tmpV1).sub(start).length(),
dist: triWTM.transformPoint(v).sub(start).length(),
meshInstances: shape.meshInstances,
priority: priority
});
Expand Down
125 changes: 73 additions & 52 deletions src/extras/gizmo/rotate-gizmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { SphereShape } from './shape/sphere-shape.js';
*/

// temporary variables
const tmpVa = new Vec2();
const tmpV1 = new Vec3();
const tmpV2 = new Vec3();
const tmpV3 = new Vec3();
const tmpV4 = new Vec3();
const tmpQ1 = new Quat();
const tmpQ2 = new Quat();
const tmpC1 = new Color();
const screen = new Vec2();
const point = new Vec3();
const v1 = new Vec3();
const v2 = new Vec3();
const v3 = new Vec3();
const v4 = new Vec3();
const q1 = new Quat();
const q2 = new Quat();
const color = new Color();

// constants
const ROTATE_FACING_EPSILON = 0.1;
Expand Down Expand Up @@ -249,19 +250,19 @@ class RotateGizmo extends TransformGizmo {

if (axis === 'xyz') {
// calculate angle axis and delta and update node rotations
const facingDir = tmpV1.copy(this.facingDir);
const delta = tmpV2.copy(point).sub(this._selectionStartPoint);
const angleAxis = tmpV1.cross(facingDir, delta).normalize();
const facingDir = v1.copy(this.facingDir);
const delta = v2.copy(point).sub(this._selectionStartPoint);
const angleAxis = v1.cross(facingDir, delta).normalize();

const angleDelta = tmpVa.set(x, y).distance(this._selectionScreenPoint);
const angleDelta = screen.set(x, y).distance(this._selectionScreenPoint);
this._setNodeRotations(axis, angleAxis, angleDelta);
} else {
// calculate angle axis and delta and update node rotations
let angleDelta = this._calculateArcAngle(point, x, y) - this._selectionStartAngle;
if (this.snap) {
angleDelta = Math.round(angleDelta / this.snapIncrement) * this.snapIncrement;
}
const angleAxis = this._dirFromAxis(axis, tmpV1);
const angleAxis = this._dirFromAxis(axis, v1);
this._setNodeRotations(axis, angleAxis, angleDelta);

// update guide points and show angle guide
Expand Down Expand Up @@ -449,14 +450,14 @@ class RotateGizmo extends TransformGizmo {
const isFacing = axis === 'f';

if (isFacing) {
tmpV1.copy(this.facingDir);
v1.copy(this.facingDir);
} else {
tmpV1.set(0, 0, 0);
tmpV1[axis] = 1;
this._rootStartRot.transformVector(tmpV1, tmpV1);
v1.set(0, 0, 0);
v1[axis] = 1;
this._rootStartRot.transformVector(v1, v1);
}
tmpQ1.setFromAxisAngle(tmpV1, angleDelta);
tmpQ1.transformVector(this._guideAngleStart, this._guideAngleEnd);
q1.setFromAxisAngle(v1, angleDelta);
q1.transformVector(this._guideAngleStart, this._guideAngleEnd);
}

/**
Expand All @@ -468,13 +469,13 @@ class RotateGizmo extends TransformGizmo {

if (state && this.dragMode !== 'show' && axis !== 'xyz') {
const gizmoPos = this.root.getLocalPosition();
const color = this._theme.guideBase[axis];
const startColor = tmpC1.copy(color);
const baseColor = this._theme.guideBase[axis];
const startColor = color.copy(baseColor);
startColor.a *= 0.3;
this._guideAngleLines[0].draw(gizmoPos, tmpV1.copy(this._guideAngleStart).add(gizmoPos),
this._guideAngleLines[0].draw(gizmoPos, v1.copy(this._guideAngleStart).add(gizmoPos),
this._scale, startColor);
this._guideAngleLines[1].draw(gizmoPos, tmpV1.copy(this._guideAngleEnd).add(gizmoPos),
this._scale, color);
this._guideAngleLines[1].draw(gizmoPos, v1.copy(this._guideAngleEnd).add(gizmoPos),
this._scale, baseColor);
this._guideAngleLines[0].entity.enabled = true;
this._guideAngleLines[1].entity.enabled = true;
} else {
Expand All @@ -494,15 +495,15 @@ class RotateGizmo extends TransformGizmo {
const azim = Math.atan2(-dir.x, -dir.z) * math.RAD_TO_DEG;
this._shapes.f.entity.setEulerAngles(-elev + 90, azim, 0);
} else {
tmpQ1.copy(this._camera.entity.getRotation()).getEulerAngles(tmpV1);
this._shapes.f.entity.setEulerAngles(tmpV1);
q1.copy(this._camera.entity.getRotation()).getEulerAngles(v1);
this._shapes.f.entity.setEulerAngles(v1);
this._shapes.f.entity.rotateLocal(-90, 0, 0);
}

// axes shapes
let angle, dot, sector;
const facingDir = tmpV1.copy(this.facingDir);
tmpQ1.copy(this.root.getRotation()).invert().transformVector(facingDir, facingDir);
const facingDir = v1.copy(this.facingDir);
q1.copy(this.root.getRotation()).invert().transformVector(facingDir, facingDir);
angle = Math.atan2(facingDir.z, facingDir.y) * math.RAD_TO_DEG;
this._shapes.x.entity.setLocalEulerAngles(0, angle - 90, -90);
angle = Math.atan2(facingDir.x, facingDir.z) * math.RAD_TO_DEG;
Expand Down Expand Up @@ -575,7 +576,7 @@ class RotateGizmo extends TransformGizmo {
const gizmoPos = this.root.getLocalPosition();

// calculate rotation from axis and angle
tmpQ1.setFromAxisAngle(angleAxis, angleDelta);
q1.setFromAxisAngle(angleAxis, angleDelta);

for (let i = 0; i < this.nodes.length; i++) {
const node = this.nodes[i];
Expand All @@ -585,8 +586,8 @@ class RotateGizmo extends TransformGizmo {
if (!rot) {
continue;
}
tmpQ2.copy(rot).mul(tmpQ1);
node.setLocalRotation(tmpQ2);
q2.copy(rot).mul(q1);
node.setLocalRotation(q2);
} else {
const rot = this._nodeRotations.get(node);
if (!rot) {
Expand All @@ -596,13 +597,13 @@ class RotateGizmo extends TransformGizmo {
if (!offset) {
continue;
}
tmpV1.copy(offset);
tmpQ1.transformVector(tmpV1, tmpV1);
tmpQ2.copy(tmpQ1).mul(rot);
v1.copy(offset);
q1.transformVector(v1, v1);
q2.copy(q1).mul(rot);

// FIXME: Rotation via quaternion when scale inverted causes scale warping?
node.setEulerAngles(tmpQ2.getEulerAngles());
node.setPosition(tmpV1.add(gizmoPos));
node.setEulerAngles(q2.getEulerAngles());
node.setPosition(v1.add(gizmoPos));
}
}

Expand All @@ -622,11 +623,10 @@ class RotateGizmo extends TransformGizmo {

const axis = this._selectedAxis;

const point = new Vec3();
const ray = this._createRay(mouseWPos);
const plane = this._createPlane(axis, axis === 'f' || axis === 'xyz', false);
if (!plane.intersectsRay(ray, point)) {
point.copy(this.root.getLocalPosition());
return point;
}

return point;
Expand All @@ -649,30 +649,51 @@ class RotateGizmo extends TransformGizmo {
let angle = 0;

// arc angle
const facingDir = tmpV2.copy(this.facingDir);
const facingDir = v2.copy(this.facingDir);
const facingDot = plane.normal.dot(facingDir);
if (this.orbitRotation || (1 - Math.abs(facingDot)) < ROTATE_FACING_EPSILON) {
// plane facing camera so based on mouse position around gizmo
tmpV1.sub2(point, gizmoPos);
v1.sub2(point, gizmoPos);

// transform point so it's facing the camera
tmpQ1.copy(this._camera.entity.getRotation()).invert().transformVector(tmpV1, tmpV1);

// calculate angle
angle = Math.sign(facingDot) * Math.atan2(tmpV1.y, tmpV1.x) * math.RAD_TO_DEG;
switch (axis) {
case 'x': {
// convert to local space
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
angle = Math.atan2(v1.z, v1.y) * math.RAD_TO_DEG;
break;
}
case 'y': {
// convert to local space
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
angle = Math.atan2(v1.x, v1.z) * math.RAD_TO_DEG;
break;
}
case 'z': {
// convert to local space
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
angle = Math.atan2(v1.y, v1.x) * math.RAD_TO_DEG;
break;
}
case 'f': {
// convert to camera space
q1.copy(this._camera.entity.getRotation()).invert().transformVector(v1, v1);
angle = Math.sign(facingDot) * Math.atan2(v1.y, v1.x) * math.RAD_TO_DEG;
break;
}
}
} else {
// convert rotation axis to screen space
tmpV1.copy(gizmoPos);
tmpV2.cross(plane.normal, facingDir).normalize().add(gizmoPos);
v1.copy(gizmoPos);
v2.cross(plane.normal, facingDir).normalize().add(gizmoPos);

// convert world space vectors to screen space
this._camera.worldToScreen(tmpV1, tmpV3);
this._camera.worldToScreen(tmpV2, tmpV4);
this._camera.worldToScreen(v1, v3);
this._camera.worldToScreen(v2, v4);

// angle is dot product with mouse position
tmpV1.sub2(tmpV4, tmpV3).normalize();
tmpV2.set(x, y, 0);
angle = tmpV1.dot(tmpV2);
v1.sub2(v4, v3).normalize();
v2.set(x, y, 0);
angle = v1.dot(v2);
}

return angle;
Expand Down
Loading