Skip to content

Commit 61281f5

Browse files
authored
Gizmo regression fixes (#8079)
* fix: gizmo scale warping fixed on high FOV * feat: add preventDefault flag for pointer events in Gizmo * fix: reverted rotation to be in plane space and fixed slight angle rotations * fix: use last selected node position when transforming in local space * fix: apply angle flip before rotations are set * fix: updated angle flip comment * fix: moved angle flipping to inside calculate angle method
1 parent 387700a commit 61281f5

File tree

2 files changed

+77
-20
lines changed

2 files changed

+77
-20
lines changed

src/extras/gizmo/gizmo.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ class Gizmo extends EventHandler {
242242
*/
243243
intersectShapes = [];
244244

245+
/**
246+
* Flag to indicate whether to call `preventDefault` on pointer events.
247+
*
248+
* @type {boolean}
249+
*/
250+
preventDefault = true;
251+
245252
/**
246253
* Creates a new gizmo layer and adds it to the scene.
247254
*
@@ -445,7 +452,9 @@ class Gizmo extends EventHandler {
445452
}
446453
const selection = this._getSelection(e.offsetX, e.offsetY);
447454
if (selection[0]) {
448-
e.preventDefault();
455+
if (this.preventDefault) {
456+
e.preventDefault();
457+
}
449458
e.stopPropagation();
450459
}
451460

@@ -466,7 +475,9 @@ class Gizmo extends EventHandler {
466475
}
467476
const selection = this._getSelection(e.offsetX, e.offsetY);
468477
if (selection[0]) {
469-
e.preventDefault();
478+
if (this.preventDefault) {
479+
e.preventDefault();
480+
}
470481
e.stopPropagation();
471482
}
472483
this.fire(Gizmo.EVENT_POINTERMOVE, e.offsetX, e.offsetY, selection[0]);
@@ -482,7 +493,9 @@ class Gizmo extends EventHandler {
482493
}
483494
const selection = this._getSelection(e.offsetX, e.offsetY);
484495
if (selection[0]) {
485-
e.preventDefault();
496+
if (this.preventDefault) {
497+
e.preventDefault();
498+
}
486499
e.stopPropagation();
487500
}
488501

@@ -497,11 +510,15 @@ class Gizmo extends EventHandler {
497510
*/
498511
_updatePosition() {
499512
position.set(0, 0, 0);
500-
for (let i = 0; i < this.nodes.length; i++) {
501-
const node = this.nodes[i];
502-
position.add(node.getPosition());
513+
if (this._coordSpace === 'local') {
514+
position.copy(this.nodes[this.nodes.length - 1].getPosition());
515+
} else {
516+
for (let i = 0; i < this.nodes.length; i++) {
517+
const node = this.nodes[i];
518+
position.add(node.getPosition());
519+
}
520+
position.mulScalar(1.0 / (this.nodes.length || 1));
503521
}
504-
position.mulScalar(1.0 / (this.nodes.length || 1));
505522

506523
if (position.equalsApprox(this.root.getLocalPosition(), UPDATE_EPSILON)) {
507524
return;
@@ -539,7 +556,7 @@ class Gizmo extends EventHandler {
539556
if (this._camera.projection === PROJECTION_PERSPECTIVE) {
540557
const gizmoPos = this.root.getLocalPosition();
541558
const cameraPos = this._camera.entity.getPosition();
542-
const dist = gizmoPos.distance(cameraPos);
559+
const dist = v.sub2(gizmoPos, cameraPos).dot(this._camera.entity.forward);
543560
this._scale = Math.tan(0.5 * this._camera.fov * math.DEG_TO_RAD) * dist * PERS_SCALE_RATIO;
544561
} else {
545562
this._scale = this._camera.orthoHeight * ORTHO_SCALE_RATIO;

src/extras/gizmo/rotate-gizmo.js

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { SphereShape } from './shape/sphere-shape.js';
1818
*/
1919

2020
// temporary variables
21-
const screen = new Vec2();
2221
const point = new Vec3();
2322
const v1 = new Vec3();
2423
const v2 = new Vec3();
@@ -242,13 +241,13 @@ class RotateGizmo extends TransformGizmo {
242241
line.entity.enabled = false;
243242
});
244243

245-
this.on(TransformGizmo.EVENT_TRANSFORMSTART, (_point, x, y) => {
244+
this.on(TransformGizmo.EVENT_TRANSFORMSTART, (point, x, y) => {
246245
// store start screen point
247246
this._screenPos.set(x, y);
248247
this._screenStartPos.set(x, y);
249248

250249
// store start angle
251-
this._selectionStartAngle = this._calculateArcAngle(x, y);
250+
this._selectionStartAngle = this._calculateArcAngle(point, x, y);
252251

253252
// store initial node rotations
254253
this._storeNodeRotations();
@@ -281,7 +280,8 @@ class RotateGizmo extends TransformGizmo {
281280
this._setNodeRotations(axis, angleAxis, angleDelta);
282281
} else {
283282
// calculate angle axis and delta and update node rotations
284-
let angleDelta = this._calculateArcAngle(x, y) - this._selectionStartAngle;
283+
let angleDelta = this._calculateArcAngle(point, x, y) - this._selectionStartAngle;
284+
285285
if (this.snap) {
286286
angleDelta = Math.round(angleDelta / this.snapIncrement) * this.snapIncrement;
287287
}
@@ -669,21 +669,30 @@ class RotateGizmo extends TransformGizmo {
669669

670670
const ray = this._createRay(mouseWPos);
671671
const plane = this._createPlane(axis, axis === 'f' || axis === 'xyz', false);
672+
672673
if (!plane.intersectsRay(ray, point)) {
673-
// use gizmo position if ray does not intersect to position angle guide correctly
674-
return point.copy(this.root.getLocalPosition());
674+
// if no intersection, try inverting the ray direction
675+
ray.direction.mulScalar(-1);
676+
const intersection = plane.intersectsRay(ray, point);
677+
ray.direction.mulScalar(-1);
678+
679+
if (!intersection) {
680+
// use gizmo position if ray does not intersect to position angle guide correctly
681+
return point.copy(this.root.getLocalPosition());
682+
}
675683
}
676684

677685
return point;
678686
}
679687

680688
/**
689+
* @param {Vec3} point - The point.
681690
* @param {number} x - The x coordinate.
682691
* @param {number} y - The y coordinate.
683692
* @returns {number} The angle.
684693
* @protected
685694
*/
686-
_calculateArcAngle(x, y) {
695+
_calculateArcAngle(point, x, y) {
687696
const gizmoPos = this.root.getLocalPosition();
688697

689698
const axis = this._selectedAxis;
@@ -721,12 +730,43 @@ class RotateGizmo extends TransformGizmo {
721730
break;
722731
}
723732
case 'orbit': {
724-
// convert gizmo position to screen space§
725-
const screenPos = this._camera.worldToScreen(gizmoPos, v1);
733+
// plane facing camera so based on mouse position around gizmo
734+
v1.sub2(point, gizmoPos);
735+
736+
switch (axis) {
737+
case 'x': {
738+
// convert to local space
739+
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
740+
angle = Math.atan2(v1.z, v1.y) * math.RAD_TO_DEG;
741+
break;
742+
}
743+
case 'y': {
744+
// convert to local space
745+
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
746+
angle = Math.atan2(v1.x, v1.z) * math.RAD_TO_DEG;
747+
break;
748+
}
749+
case 'z': {
750+
// convert to local space
751+
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
752+
angle = Math.atan2(v1.y, v1.x) * math.RAD_TO_DEG;
753+
break;
754+
}
755+
case 'f': {
756+
// convert to camera space
757+
q1.copy(this._camera.entity.getRotation()).invert().transformVector(v1, v1);
758+
angle = Math.sign(facingDot) * Math.atan2(v1.y, v1.x) * math.RAD_TO_DEG;
759+
break;
760+
}
761+
}
762+
763+
// intersection point can be behind camera, so need to check to flip angle delta
764+
const dir = v1.sub2(point, this._camera.entity.getPosition()).normalize();
765+
const dot = dir.dot(this._camera.entity.forward);
766+
if (dot < 0) {
767+
angle += 180;
768+
}
726769

727-
// calculate angle based on mouse position around gizmo
728-
const dir = screen.set(x - screenPos.x, y - screenPos.y).normalize();
729-
angle = Math.sign(facingDot) * Math.atan2(-dir.y, dir.x) * math.RAD_TO_DEG;
730770
break;
731771
}
732772
}

0 commit comments

Comments
 (0)