@@ -23,13 +23,11 @@ const point = new Vec3();
2323const v1 = new Vec3 ( ) ;
2424const v2 = new Vec3 ( ) ;
2525const v3 = new Vec3 ( ) ;
26- const v4 = new Vec3 ( ) ;
2726const q1 = new Quat ( ) ;
2827const q2 = new Quat ( ) ;
2928const color = new Color ( ) ;
3029
3130// constants
32- const ROTATE_FACING_EPSILON = 0.1 ;
3331const RING_FACING_EPSILON = 1e-4 ;
3432const 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
740741export { RotateGizmo } ;
0 commit comments