dotNET
dotNET

Reputation: 35450

Computing new transform after changing center of rotation

Short version

Given a transform matrix M1, original center of rotation P1 and new center of rotation P2, what is the correct way to compute the new transform matrix M2 so as to keep object's current position and rotation intact?

Long version

I'm working on a vector drawing application that allows user to rotate objects using Thumb controls. Rotation works fine around the center of the object. I have set RenderTransformOrigin to 0.5,0.5, so simply setting RenderTransform to <RotateTransform Angle="{Binding Rotation}" /> does the job for me.

Now I need to allow user to change the pivot point, or center of rotation. I added another Thumb that allows users to place the pivot point anywhere they like. My idea was to simply bind this thumb's location to RenderTransformOrigin or CenterX and CenterY properties of my RotateTransform to start rotating around the new center.

The problem however is that as soon as user moves the pivot thumb to a new location, RotateTransform computes the new transform matrix and moves the object accordingly, which is counter-productive, since the pivot thumb should only act as the center of rotation during the rotate operation.

After having spent the better part of the past 2 weeks with this, I have realized that I need to compute the new transform matrix for my object the keeps the current position and rotation intact while moving the center of rotation. Is there a built-in or custom way to do that?

Edit

Just to clarify it further, here is how it should go:

  1. User drops a new object, say a rectangle.
  2. Pivot point by default is at 0.5, 0.5, i.e. center of the object.
  3. User rotates the object using Rotation thumb. Object rotates around its center since pivot point is at the center. User sets rotation to 45 degrees and leaves the rotation thumb.
  4. User now grabs the Pivot thumb and drags it around. Ideally this should only move the pivot thumb and must not affect the object's current position or rotation in any way, but currently it does because my RotateTransform's CenterX and CenterY are bound to pivot's position.
  5. I can avoid direct binding of RotateTransform to Pivot's position, but at whatever point I assign the new center to it, the RotateTransform will compute the new position and move object accordingly.
  6. My idea is that at the point of assignment, we must compute the new transformation matrix using the new pivot and assign it to the object before assigning the center of rotation.

Upvotes: 1

Views: 733

Answers (1)

dotNET
dotNET

Reputation: 35450

Answering @KamilNowak's request in the comments above, a long time has passed since then, but I just managed to dig this out from my code repository. Hope this helps you find your way forward. You basically need to call this function AFTER you change the location of your pivot point (is in vb.net but u should be able translate):

'_Designer is the main Canvas control on which all drawing objects are placed
Friend Sub AdjustLocationAfterPivotShift(_Designer As Canvas)
  'RectangularObject is the underlying VM object that represents my shape
  Dim RO = DirectCast(Me.DataContext, RectangularObject)

  'My control template has Thumb elements for translation, rotation, scaling and pivot. Here I'm using rotate and pivot thumbs. Use your controls instead.
  Dim DIRotateThumb As RotateThumb = VisualTreeExtensions.GetVisualDescendent(Of RotateThumb)(Me)
  Dim _RotateHandle = DirectCast(DIRotateThumb.Template.FindName("PART_RotateEllipse", DIRotateThumb), Shapes.Ellipse)

  Dim RotateHandlePos As Point = _RotateHandle.TranslatePoint(New Point(5, 5), _Designer)
  Dim RotateCenterPos As Point = Me.TranslatePoint(New Point(RO.PivotX + RO.Size.Width / 2, RO.PivotY + RO.Size.Height / 2), _Designer)
  'ROTATION_HANDLE_OFFSET is vertical distance between control and rotation thumb
  Dim RotationThumbOffsetFromPivot As New Point(-RO.PivotX, -RO.PivotY - Me.ActualHeight / 2 - ROTATION_HANDLE_OFFSET)

  RO.Rotation = Me.ComputeAngle(RotateCenterPos, RotateHandlePos, RotationThumbOffsetFromPivot)
  Dim NewLocation As Point = ComputeLocationDelta(New Point(RO.PivotX, RO.PivotY), RO.LastPivot.ToPoint(), RO.Rotation)

  RO.Location.X += NewLocation.X
  RO.Location.Y += NewLocation.Y

  'Set LastPivot to current pivot location after adjusting location
  RO.LastPivot.X = RO.PivotX
  RO.LastPivot.Y = RO.PivotY
End Sub

''' <summary>
''' Computes X and Y delta that must be added to object's current location to 
    account for pivot's changed location.
''' </summary>
''' <param name="currentPivot"></param>
''' <param name="originalPivot"></param>
''' <param name="angle"></param>
''' <returns></returns>
Private Function ComputeLocationDelta(currentPivot As Point, originalPivot As 
  Point, angle As Double) As Point
  Dim h = currentPivot.X - originalPivot.X
  Dim v = currentPivot.Y - originalPivot.Y
  Dim trans As System.Windows.Vector = RotateVector2d(h, v, D2R(angle))

  Return New Point(trans.X - h, trans.Y - v)
End Function

Friend Function ComputeAngle(center As Point, pos As Point, offset As Point) As Double
  Dim xDiff = pos.X - center.X
  Dim yDiff = pos.Y - center.Y

  Dim offsetAngle = Math.Atan2(offset.Y, offset.X) * 180 / Math.PI
  Return Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI - offsetAngle
End Function

Friend Shared Function RotateVector2d(x0 As Double, y0 As Double, rad As Double) As Vector
  Dim result As New Vector With {
    .X = x0 * Math.Cos(rad) - y0 * Math.Sin(rad),
    .Y = x0 * Math.Sin(rad) + y0 * Math.Cos(rad)
  }
  Return result
End Function

Friend Shared Function D2R(degree As Double) As Double
  Return (degree Mod 360) * Math.PI / 180
End Function

Hope this is not as clear as mud and you can use it to your benefit.

Upvotes: 2

Related Questions