围绕枢轴点重复旋转点

Rotate Point around pivot Point repeatedly

一段时间以来,我一直在使用以下函数在我的各种程序中围绕一个轴心点旋转一系列点。

private Point RotatePoint(Point point, Point pivot, double radians)
{
    var cosTheta = Math.Cos(radians);
    var sinTheta = Math.Sin(radians);

    var x = (cosTheta * (point.X - pivot.X) - sinTheta * (point.Y - pivot.Y) + pivot.X);
    var y = (sinTheta * (point.X - pivot.X) + cosTheta * (point.Y - pivot.Y) + pivot.Y);

    return new Point((int)x, (int)y);
}

这一直很有效,直到我尝试少量反复旋转一个形状。例如,这是我在由 4 个点组成的矩形多边形上调用 45° 得到的结果:

foreach (var point in points)
    Rotate(point, center, Math.PI / 180f * 45);

但这是我调用旋转 45 次 1° 得到的结果:

for (var i = 0; i < 45; ++i)
    foreach (var point in points)
        Rotate(point, center, Math.PI / 180f * 1)

我只要调用一次就可以了,而且旋转度数越低好像也越来越差。该功能是否存在缺陷,或者我是否误解了该功能的基本功能?

我怎么能小量重复旋转呢?我可以保存基点并在旋转发生变化时使用它们更新当前点,但这是唯一的方法吗?

如果你愿意你可以使用Media3D只处理矩阵并简化编码。像这样简单的事情就可以了。

public Point3D Rotate(Point3D point, Point3D rotationCenter, Vector3D rotation, double degree)
{
    // create empty matrix
    var matrix = new Matrix3D();

    // translate matrix to rotation point
    matrix.Translate(rotationCenter - new Point3D());

    // rotate it the way we need
    matrix.Rotate(new Quaternion(rotation, degree));

    // apply the matrix to our point
    point = matrix.Transform(point);

    return point;
}

然后您只需调用该方法并指定旋转。假设您使用 2D(如您的示例)并假设我们使用 XY 平面,因此旋转在 Z 方向。您可以执行以下操作:

var rotationPoint = new Point3D(0, 0, 0);
var currentPoint = new Point3D(10, 0, 0);

// rotate the current point around the rotation point in Z by 45 degree
var newPoint = Rotate(currentPoint, rotation, new Vector3D(0, 0, 1), 45d);

由于 RotatePoint() 方法生成的整数舍入,您的 Point 位置度量已关闭。

使用 float 坐标对方法返回值进行简单修正,将产生正确的测量值:

要测试它,请创建一个计时器并将其 Tick 事件注册为 RotateTimerTick():

添加了旋转增量(参见 rotationSpin 字段)以强调运动效果。

PointF pivotPoint = new PointF(100F, 100F);
PointF rotatingPoint = new PointF(50F, 100F);
double rotationSpin = 0D;

private PointF RotatePoint(PointF point, PointF pivot, double radians)
{
    var cosTheta = Math.Cos(radians);
    var sinTheta = Math.Sin(radians);

    var x = (cosTheta * (point.X - pivot.X) - sinTheta * (point.Y - pivot.Y) + pivot.X);
    var y = (sinTheta * (point.X - pivot.X) + cosTheta * (point.Y - pivot.Y) + pivot.Y);

    return new PointF((float)x, (float)y);
}

private void RotateTimerTick(object sender, EventArgs e)
{
    rotationSpin += .5;
    
    if (rotationSpin > 90) rotationSpin = 0;
    rotatingPoint = RotatePoint(rotatingPoint, pivotPoint, (Math.PI / 180f) * rotationSpin);
    Panel1.Invalidate(new Rectangle(new Point(50,50), new Size(110, 110)));
}

private void Panel1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.FillEllipse(Brushes.White, new RectangleF(100, 100, 8, 8));
    e.Graphics.FillEllipse(Brushes.Yellow, new RectangleF(rotatingPoint, new SizeF(8, 8)));
}

这是使用 float 值的结果:

这就是使用 integer 值时发生的情况: