Search code examples
c#winformsgraphicsrotationdrawing

Rotate Point around pivot Point repeatedly


For a while now I've been using the following function to rotate a series of Points around a pivot point in various programs of mine.

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);
}

This has always worked great, until I tried to rotate a shape repeatedly by small amounts. For example, this is what I get from calling it for 45° on a rectangular polygon made up of 4 points:

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

enter image description here

But this is what I get by calling rotate 45 times for 1°:

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

enter image description here

As long as I call it only once it's fine, and it also seems like it gets gradually worse the lower the rotation degree is. Is there some flaw in the function, or am I misunderstanding something fundamental about what this function does?

How could I rotate repeatedly by small amounts? I could save the base points and use them to update the current points whenever the rotation changes, but is that the only way?


Solution

  • Your Point position measure is off because of the integer rounding generated by the RotatePoint() method.

    A simple correction in the method returned value, using float coordinates, will produce the correct measure:

    To test it, create a Timer and register its Tick event as RotateTimerTick():

    Added a rotation spin increment (see the rotationSpin Field) to emphasize the motion effect.

    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)));
    }
    

    This is the result using float values:

    enter image description here

    And this is what happens using integer values:

    enter image description here