在改变旋转中心后计算新变换

时间:2017-08-23 05:31:43

标签: wpf vb.net matrix rotation transformation

短版

给定变换矩阵M1,原始旋转中心P1和新的旋转中心P2,计算新变换矩阵M2的正确方法是什么,以保持对象的当前位置轮换完好无损?

长版

我正在开发一个矢量绘图应用程序,允许用户使用Thumb控件旋转对象。旋转在对象中心周围很好。我已将RenderTransformOrigin设置为0.5,0.5,因此只需将RenderTransform设置为<RotateTransform Angle="{Binding Rotation}" />即可为我完成工作。

现在我需要允许用户更改轴心点或旋转中心。我添加了另一个Thumb,允许用户将枢轴点放在他们喜欢的任何地方。我的想法是简单地将此拇指的位置绑定到RenderTransformOrigin的{​​{1}}或CenterXCenterY属性,以开始围绕新中心旋转。

然而问题是,只要用户将枢轴滑块移动到新位置,RotateTransform就会计算新的变换矩阵并相应地移动对象,这会适得其反,因为枢轴拇指只应该动作旋转操作期间的旋转中心。

在过去2周的大部分时间里,我已经意识到我需要为我的物体计算新的变换矩阵,在移动旋转中心的同时保持当前位置和旋转不变。是否有内置或自定义方式来做到这一点?

修改

为了进一步澄清,以下是它应该如何:

  1. 用户删除一个新对象,比如一个矩形。
  2. 默认情况下,枢轴点为0.5,0.5,即对象的中心。
  3. 用户使用旋转拇指旋转对象。由于枢轴点位于中心,物体围绕其中心旋转。用户将旋转设置为45度并离开旋转拇指。
  4. 用户现在抓住 Pivot thumb 并拖动它。理想情况下,这应该只移动枢轴拇指,不得以任何方式影响对象的当前位置或旋转,但目前确实如此,因为我的RotateTransform的CenterX和CenterY被绑定到枢轴的位置。
  5. 我可以避免将RotateTransform直接绑定到Pivot的位置,但无论在何时将新中心分配给它,RotateTransform都会计算新位置并相应地移动对象。
  6. 我的想法是,在分配时,我们必须使用新的枢轴计算新的变换矩阵,并在分配旋转中心之前将其分配给对象。

1 个答案:

答案 0 :(得分:1)

在上面的评论中回答@KamilNowak的请求,从那时起已经过去了很长时间,但是我只是设法从我的代码存储库中挖掘了这一点。希望这可以帮助您找到前进的方向。基本上,您需要在更改枢轴点的位置后调用此函数(位于vb.net中,但您应该可以翻译):

'_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

希望这不像泥那么清澈,您可以使用它来谋福利。