Skip to content

Commit 2c0875e

Browse files
authored
Gizmo fixes (#7963)
* fix: disabilng flip while already flipped now flips back * feat: updated orbit rotation to use screen space angle for better rotation for slight angles * refactor: replace _selectionScreenPoint with _screenPos and _screenStartPos for better clarity * fix: remove early return in _drawGuideLines to ensure guide lines are always drawn * fix: increase pitch range precision for better control * fix: improve rotation direction calculation in RotateGizmo for accurate user interaction * fix: allow rotation direction to be flipped based on axis in RotateGizmo * fix: remove unused _guideMouseLine initialization in RotateGizmo * fix: add destroy method to MeshLine and RotateGizmo for proper resource cleanup
1 parent ff6c26e commit 2c0875e

File tree

7 files changed

+78
-96
lines changed

7 files changed

+78
-96
lines changed

examples/src/examples/gizmos/transform-rotate.example.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Object.assign(cc, {
9191
rotateDamping: 0.97,
9292
moveDamping: 0.97,
9393
zoomDamping: 0.97,
94-
pitchRange: new pc.Vec2(-89.99, 89.99),
94+
pitchRange: new pc.Vec2(-89.999, 89.999),
9595
zoomRange: new pc.Vec2(2, 10),
9696
enableFly: false
9797
});

examples/src/examples/gizmos/transform-scale.example.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Object.assign(cc, {
9191
rotateDamping: 0.97,
9292
moveDamping: 0.97,
9393
zoomDamping: 0.97,
94-
pitchRange: new pc.Vec2(-89.99, 89.99),
94+
pitchRange: new pc.Vec2(-89.999, 89.999),
9595
zoomRange: new pc.Vec2(2, 10),
9696
enableFly: false
9797
});

examples/src/examples/gizmos/transform-translate.example.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Object.assign(cc, {
9191
rotateDamping: 0.97,
9292
moveDamping: 0.97,
9393
zoomDamping: 0.97,
94-
pitchRange: new pc.Vec2(-89.99, 89.99),
94+
pitchRange: new pc.Vec2(-89.999, 89.999),
9595
zoomRange: new pc.Vec2(2, 10),
9696
enableFly: false
9797
});

src/extras/gizmo/mesh-line.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ class MeshLine {
9393
this.entity.setLocalPosition(dir.mulScalar(0.5 * length).add(from));
9494
this.entity.setLocalScale(this._thickness * scale, length, this._thickness * scale);
9595
}
96+
97+
destroy() {
98+
this._material.destroy();
99+
this.entity.destroy();
100+
}
96101
}
97102

98103
export { MeshLine };

src/extras/gizmo/rotate-gizmo.js

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ const point = new Vec3();
2323
const v1 = new Vec3();
2424
const v2 = new Vec3();
2525
const v3 = new Vec3();
26-
const v4 = new Vec3();
2726
const q1 = new Quat();
2827
const q2 = new Quat();
2928
const color = new Color();
3029

3130
// constants
32-
const ROTATE_FACING_EPSILON = 0.1;
3331
const RING_FACING_EPSILON = 1e-4;
3432
const AXES = /** @type {('x' | 'y' | 'z')[]} */ (['x', 'y', 'z']);
3533

@@ -122,14 +120,6 @@ class RotateGizmo extends TransformGizmo {
122120
*/
123121
_selectionStartAngle = 0;
124122

125-
/**
126-
* Internal selection screen point in 2D space.
127-
*
128-
* @type {Vec2}
129-
* @private
130-
*/
131-
_selectionScreenPoint = new Vec2();
132-
133123
/**
134124
* Internal mapping from each attached node to their starting rotation in local space.
135125
*
@@ -154,6 +144,22 @@ class RotateGizmo extends TransformGizmo {
154144
*/
155145
_nodeOffsets = new Map();
156146

147+
/**
148+
* Internal vector for storing the mouse position in screen space.
149+
*
150+
* @type {Vec2}
151+
* @private
152+
*/
153+
_screenPos = new Vec2();
154+
155+
/**
156+
* Internal vector for storing the mouse start position in screen space.
157+
*
158+
* @type {Vec2}
159+
* @private
160+
*/
161+
_screenStartPos = new Vec2();
162+
157163
/**
158164
* Internal vector for the start point of the guide line angle.
159165
*
@@ -222,12 +228,13 @@ class RotateGizmo extends TransformGizmo {
222228
line.entity.enabled = false;
223229
});
224230

225-
this.on(TransformGizmo.EVENT_TRANSFORMSTART, (point, x, y) => {
231+
this.on(TransformGizmo.EVENT_TRANSFORMSTART, (_point, x, y) => {
226232
// store start screen point
227-
this._selectionScreenPoint.set(x, y);
233+
this._screenPos.set(x, y);
234+
this._screenStartPos.set(x, y);
228235

229236
// store start angle
230-
this._selectionStartAngle = this._calculateArcAngle(point, x, y);
237+
this._selectionStartAngle = this._calculateArcAngle(x, y);
231238

232239
// store initial node rotations
233240
this._storeNodeRotations();
@@ -248,17 +255,19 @@ class RotateGizmo extends TransformGizmo {
248255
return;
249256
}
250257

258+
// update screen point
259+
this._screenPos.set(x, y);
260+
251261
if (axis === 'xyz') {
252262
// calculate angle axis and delta and update node rotations
253263
const facingDir = v1.copy(this.facingDir);
254264
const delta = v2.copy(point).sub(this._selectionStartPoint);
255265
const angleAxis = v1.cross(facingDir, delta).normalize();
256-
257-
const angleDelta = screen.set(x, y).distance(this._selectionScreenPoint);
266+
const angleDelta = this._screenPos.distance(this._screenStartPos);
258267
this._setNodeRotations(axis, angleAxis, angleDelta);
259268
} else {
260269
// calculate angle axis and delta and update node rotations
261-
let angleDelta = this._calculateArcAngle(point, x, y) - this._selectionStartAngle;
270+
let angleDelta = this._calculateArcAngle(x, y) - this._selectionStartAngle;
262271
if (this.snap) {
263272
angleDelta = Math.round(angleDelta / this.snapIncrement) * this.snapIncrement;
264273
}
@@ -269,7 +278,6 @@ class RotateGizmo extends TransformGizmo {
269278
this._updateGuidePoints(angleDelta);
270279
this._angleGuide(true);
271280
}
272-
273281
});
274282

275283
this.on(TransformGizmo.EVENT_TRANSFORMEND, () => {
@@ -633,13 +641,12 @@ class RotateGizmo extends TransformGizmo {
633641
}
634642

635643
/**
636-
* @param {Vec3} point - The point.
637644
* @param {number} x - The x coordinate.
638645
* @param {number} y - The y coordinate.
639646
* @returns {number} The angle.
640647
* @protected
641648
*/
642-
_calculateArcAngle(point, x, y) {
649+
_calculateArcAngle(x, y) {
643650
const gizmoPos = this.root.getLocalPosition();
644651

645652
const axis = this._selectedAxis;
@@ -649,49 +656,34 @@ class RotateGizmo extends TransformGizmo {
649656
let angle = 0;
650657

651658
// arc angle
652-
const facingDir = v2.copy(this.facingDir);
659+
const facingDir = this.facingDir;
653660
const facingDot = plane.normal.dot(facingDir);
654-
if (this.orbitRotation || (1 - Math.abs(facingDot)) < ROTATE_FACING_EPSILON) {
655-
// plane facing camera so based on mouse position around gizmo
656-
v1.sub2(point, gizmoPos);
657-
658-
switch (axis) {
659-
case 'x': {
660-
// convert to local space
661-
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
662-
angle = Math.atan2(v1.z, v1.y) * math.RAD_TO_DEG;
663-
break;
664-
}
665-
case 'y': {
666-
// convert to local space
667-
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
668-
angle = Math.atan2(v1.x, v1.z) * math.RAD_TO_DEG;
669-
break;
670-
}
671-
case 'z': {
672-
// convert to local space
673-
q1.copy(this._rootStartRot).invert().transformVector(v1, v1);
674-
angle = Math.atan2(v1.y, v1.x) * math.RAD_TO_DEG;
675-
break;
676-
}
677-
case 'f': {
678-
// convert to camera space
679-
q1.copy(this._camera.entity.getRotation()).invert().transformVector(v1, v1);
680-
angle = Math.sign(facingDot) * Math.atan2(v1.y, v1.x) * math.RAD_TO_DEG;
681-
break;
682-
}
683-
}
661+
if (this.orbitRotation) {
662+
// convert gizmo position to screen space
663+
const screenPos = this._camera.worldToScreen(gizmoPos, v1);
664+
665+
// calculate angle based on mouse position around gizmo
666+
const dir = screen.set(x - screenPos.x, y - screenPos.y).normalize();
667+
angle = Math.sign(facingDot) * Math.atan2(-dir.y, dir.x) * math.RAD_TO_DEG;
684668
} else {
685-
// convert rotation axis to screen space
686-
v1.copy(gizmoPos);
687-
v2.cross(plane.normal, facingDir).normalize().add(gizmoPos);
669+
this._camera.worldToScreen(gizmoPos, v2);
670+
if (axis === 'f' || facingDot > 1 - RING_FACING_EPSILON) {
671+
// determine which size of the ring the mouse is on to flip rotation direction
672+
v1.set(
673+
this._screenStartPos.y >= v2.y ? 1 : -1,
674+
this._screenStartPos.x >= v2.x ? -1 : 1,
675+
0
676+
).normalize();
677+
} else {
678+
// calculate projection vector in world space for rotation axis
679+
const projDir = v1.cross(plane.normal, facingDir).normalize();
688680

689-
// convert world space vectors to screen space
690-
this._camera.worldToScreen(v1, v3);
691-
this._camera.worldToScreen(v2, v4);
681+
// convert to screen space
682+
this._camera.worldToScreen(projDir.add(gizmoPos), v3);
683+
v1.sub2(v3, v2).normalize();
684+
}
692685

693686
// angle is dot product with mouse position
694-
v1.sub2(v4, v3).normalize();
695687
v2.set(x, y, 0);
696688
angle = v1.dot(v2);
697689
}
@@ -735,6 +727,15 @@ class RotateGizmo extends TransformGizmo {
735727

736728
this._shapesLookAtCamera();
737729
}
730+
731+
/**
732+
* @override
733+
*/
734+
destroy() {
735+
this._guideAngleLines.forEach(line => line.destroy());
736+
737+
super.destroy();
738+
}
738739
}
739740

740741
export { RotateGizmo };

src/extras/gizmo/scale-gizmo.js

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -447,36 +447,24 @@ class ScaleGizmo extends TransformGizmo {
447447
// axes
448448
let dot = cameraDir.dot(this.root.right);
449449
this._shapes.x.entity.enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
450-
if (this.flipAxes) {
451-
this._shapes.x.flipped = dot < 0;
452-
}
450+
this._shapes.x.flipped = this.flipAxes && dot < 0;
453451
dot = cameraDir.dot(this.root.up);
454452
this._shapes.y.entity.enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
455-
if (this.flipAxes) {
456-
this._shapes.y.flipped = dot < 0;
457-
}
453+
this._shapes.y.flipped = this.flipAxes && dot < 0;
458454
dot = cameraDir.dot(this.root.forward);
459455
this._shapes.z.entity.enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
460-
if (this.flipAxes) {
461-
this._shapes.z.flipped = dot > 0;
462-
}
456+
this._shapes.z.flipped = this.flipAxes && dot > 0;
463457

464458
// planes
465459
v1.cross(cameraDir, this.root.right);
466460
this._shapes.yz.entity.enabled = 1 - v1.length() > GLANCE_EPSILON;
467-
if (this.flipPlanes) {
468-
this._shapes.yz.flipped = v2.set(0, +(v1.dot(this.root.forward) < 0), +(v1.dot(this.root.up) < 0));
469-
}
461+
this._shapes.yz.flipped = this.flipPlanes ? v2.set(0, +(v1.dot(this.root.forward) < 0), +(v1.dot(this.root.up) < 0)) : Vec3.ZERO;
470462
v1.cross(cameraDir, this.root.forward);
471463
this._shapes.xy.entity.enabled = 1 - v1.length() > GLANCE_EPSILON;
472-
if (this.flipPlanes) {
473-
this._shapes.xy.flipped = v2.set(+(v1.dot(this.root.up) < 0), +(v1.dot(this.root.right) > 0), 0);
474-
}
464+
this._shapes.xy.flipped = this.flipPlanes ? v2.set(+(v1.dot(this.root.up) < 0), +(v1.dot(this.root.right) > 0), 0) : Vec3.ZERO;
475465
v1.cross(cameraDir, this.root.up);
476466
this._shapes.xz.entity.enabled = 1 - v1.length() > GLANCE_EPSILON;
477-
if (this.flipPlanes) {
478-
this._shapes.xz.flipped = v2.set(+(v1.dot(this.root.forward) > 0), 0, +(v1.dot(this.root.right) > 0));
479-
}
467+
this._shapes.xz.flipped = this.flipPlanes ? v2.set(+(v1.dot(this.root.forward) > 0), 0, +(v1.dot(this.root.right) > 0)) : Vec3.ZERO;
480468
}
481469

482470
/**

src/extras/gizmo/translate-gizmo.js

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -427,36 +427,24 @@ class TranslateGizmo extends TransformGizmo {
427427
// axes
428428
let dot = cameraDir.dot(this.root.right);
429429
this._shapes.x.entity.enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
430-
if (this.flipAxes) {
431-
this._shapes.x.flipped = dot < 0;
432-
}
430+
this._shapes.x.flipped = this.flipAxes && dot < 0;
433431
dot = cameraDir.dot(this.root.up);
434432
this._shapes.y.entity.enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
435-
if (this.flipAxes) {
436-
this._shapes.y.flipped = dot < 0;
437-
}
433+
this._shapes.y.flipped = this.flipAxes && dot < 0;
438434
dot = cameraDir.dot(this.root.forward);
439435
this._shapes.z.entity.enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
440-
if (this.flipAxes) {
441-
this._shapes.z.flipped = dot > 0;
442-
}
436+
this._shapes.z.flipped = this.flipAxes && dot > 0;
443437

444438
// planes
445439
v1.cross(cameraDir, this.root.right);
446440
this._shapes.yz.entity.enabled = 1 - v1.length() > GLANCE_EPSILON;
447-
if (this.flipPlanes) {
448-
this._shapes.yz.flipped = v2.set(0, +(v1.dot(this.root.forward) < 0), +(v1.dot(this.root.up) < 0));
449-
}
441+
this._shapes.yz.flipped = this.flipPlanes ? v2.set(0, +(v1.dot(this.root.forward) < 0), +(v1.dot(this.root.up) < 0)) : Vec3.ZERO;
450442
v1.cross(cameraDir, this.root.forward);
451443
this._shapes.xy.entity.enabled = 1 - v1.length() > GLANCE_EPSILON;
452-
if (this.flipPlanes) {
453-
this._shapes.xy.flipped = v2.set(+(v1.dot(this.root.up) < 0), +(v1.dot(this.root.right) > 0), 0);
454-
}
444+
this._shapes.xy.flipped = this.flipPlanes ? v2.set(+(v1.dot(this.root.up) < 0), +(v1.dot(this.root.right) > 0), 0) : Vec3.ZERO;
455445
v1.cross(cameraDir, this.root.up);
456446
this._shapes.xz.entity.enabled = 1 - v1.length() > GLANCE_EPSILON;
457-
if (this.flipPlanes) {
458-
this._shapes.xz.flipped = v2.set(+(v1.dot(this.root.forward) > 0), 0, +(v1.dot(this.root.right) > 0));
459-
}
447+
this._shapes.xz.flipped = this.flipPlanes ? v2.set(+(v1.dot(this.root.forward) > 0), 0, +(v1.dot(this.root.right) > 0)) : Vec3.ZERO;
460448
}
461449

462450
/**

0 commit comments

Comments
 (0)