1

I'd like to update a list of points (PointFs) by performing a rotation (around a new origin) and translating each point by an amount that is proportional to its current distance from the origin (so not an absolute translation).

I currently do this for each point in turn but performance is poor when moving more than a handful of points.

I'd like to make the transformation more efficient so wanted to use a matrix. The rotation is no problem, but I don't know how to do the proportional translation.

Can I do this with an affine matrix? Is there some other way to do the transformation more efficiently?

UPDATED

Here's my current code. I've changed it a little so at least it does use a matrix for the rotation. Note the translation is based on a ratio, so points closer to the centre won't move as far as points further away:

    private void DragPointsAroundCentre(PointF centre, PointF priorLocation, PointF newLocation, PointF[] otherPoints)
    {
        // calculate the angle and length of the transformation from the original location
        var priorLength = Maths.Distance(centre, priorLocation);
        var newLength = Maths.Distance(centre, newLocation);

        var lengthRatio = newLength / priorLength;
        var rotationAngle = (float)Maths.Angle(centre, priorLocation, newLocation);

        // apply the rotation to the other points
        Rotate(otherPoints, rotationAngle, centre);

        // apply an equivalent translation to the other points
        for (int i = 0; i < otherPoints.Length ; i++)
        {
            var translation = GetPointOnLine(centre, otherPoints[i], (float) lengthRatio);
            otherPoints[i].X = translation.X;
            otherPoints[i].Y = translation.Y;
        }
    }

    private static void Rotate(PointF[] points, float angle, PointF center)
    {
        using (Matrix m = new Matrix())
        {
            m.RotateAt(angle, center);
            m.TransformPoints(points);
        }
    }

    // gets a point from a relative position on a line using the specified ratio
    private static PointF GetPointOnLine(PointF origin, PointF point, float ratio)
    {
        return new PointF(
            origin.X + (point.X - origin.X) * ratio,
            origin.Y + (point.Y - origin.Y) * ratio);
    }
nopskazoid
  • 169
  • 2
  • 10
  • look up affine matrices: – Pieter Geerkens Dec 15 '14 at 12:31
  • If you posted your current code it would be easier to give you an answer. As far as the translation is concerned it is always absolute. If you want to move the points proportional you'll have to calculate the values for their translations. Scaling could albo be possible here and it would move the points proportionaly but for this you have to reveal more details. – t3chb0t Dec 15 '14 at 13:03
  • I edidted my answer and added a simple `Prallel.For` loop. – t3chb0t Dec 15 '14 at 16:54
  • I added another example (see **EDIT-2**) using one matrix for all transformations. – t3chb0t Dec 15 '14 at 19:34

1 Answers1

3

This is the code I use for transformations. I hope this helps you:

class Program
{
    static void Main(string[] args)
    {
        PointF[] points = new PointF[] 
        { 
            new PointF(1, 0), 
            new PointF(0, 1) 
        };

        float angle = 90; // in degrees
        PointF center = new PointF(1, 1);
        Rotate(points, angle, center);

        float offset = 10;
        PointF vector = new PointF(1, 1);
        Translate(points, offset, vector);
    }

    static void Rotate(PointF[] points, float angle, PointF center)
    {
        using (Matrix m = new Matrix())
        {
            m.RotateAt(angle, center);
            m.TransformPoints(points);
        }
    }

    // Translates point along the specified vector.
    static void Translate(PointF[] points, float offset, PointF vector)
    {
        float magnitude = (float)Math.Sqrt((vector.X * vector.X) + (vector.Y * vector.Y)); // = length
        vector.X /= magnitude;
        vector.Y /= magnitude;
        PointF translation = new PointF()
        {
            X = offset * vector.X,
            Y = offset * vector.Y
        };
        using (Matrix m = new Matrix())
        {
            m.Translate(translation.X, translation.Y);
            m.TransformPoints(points);
        }
    }
}

If you need the transformation to be very efficient you can combine both transformation matrices into one and transform all points only once.

EDIT:

You can use for example a simple parallel loop to make it a little bit faster. But even for 30.000.000 points the difference is not too big in this case (my case 4 cpu cores). But it depends of course how often do you process them.

class Program
{
    static void Main(string[] args)
    {
        int pointCount = 30000000;
        PointF[] otherPoints = new PointF[pointCount];
        Random rnd = new Random();
        for (int i = 0; i < pointCount; i++)
        {
            otherPoints[i] = new Point(rnd.Next(), rnd.Next());
        }

        PointF centre = new PointF(3, 3);
        float lengthRatio = 7.3f;

        // apply an equivalent translation to the other points
        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i < otherPoints.Length; i++)
        {
            var translation = GetPointOnLine(centre, otherPoints[i], (float)lengthRatio);
            otherPoints[i].X = translation.X;
            otherPoints[i].Y = translation.Y;
        }
        sw.Stop();
        Console.WriteLine("Single thread: {0} sec.", sw.Elapsed.TotalSeconds);

        sw.Reset();
        sw.Start();
        Parallel.For(0, pointCount, i =>
        {
            var translation = GetPointOnLine(centre, otherPoints[i], (float)lengthRatio);
            otherPoints[i].X = translation.X;
            otherPoints[i].Y = translation.Y;

        });
        sw.Stop();
        Console.WriteLine("Multi thread: {0} sec.", sw.Elapsed.TotalSeconds);
        Console.ReadKey();
    }

    // gets a point from a relative position on a line using the specified ratio
    private static PointF GetPointOnLine(PointF origin, PointF point, float ratio)
    {
        return new PointF(
            origin.X + (point.X - origin.X) * ratio,
            origin.Y + (point.Y - origin.Y) * ratio);
    }
}

EDIT-2:

I found a transformation that is exacly the same as yours and transforms the points in only one loop using a single matrix. Here's the code for both the old and the new transformation:

class Program
{
    static void Main(string[] args)
    {
        PointF[] points1 = new PointF[] 
        { 
            new PointF(1f, 0f),
            new PointF(0f, 1f),
            new PointF(1f, 1f),
            new PointF(2f, 2f),
        };
        PointF[] points2 = new PointF[]
        { 
            new PointF(1f, 0f),
            new PointF(0f, 1f),
            new PointF(1f, 1f),
            new PointF(2f, 2f),
        };

        PointF center = new PointF(2f, 2f);

        float priorLength = 4f;
        float newLength = 5f;

        float lengthRatio = newLength / priorLength;

        float rotationAngle = 45f;

        Transformation_old(points1, rotationAngle, center, lengthRatio);
        Transformation_new(points2, rotationAngle, center, lengthRatio);

        Console.ReadKey();
    }

    static void Transformation_old(PointF[] points, float rotationAngle, PointF center, float lengthRatio)
    {
        Rotate(points, rotationAngle, center);

        for (int i = 0; i < points.Length; i++)
        {
            var translation = GetPointOnLine(center, points[i], lengthRatio);
            points[i].X = translation.X;
            points[i].Y = translation.Y;
        }
    }

    static void Rotate(PointF[] points, float angle, PointF center)
    {
        using (Matrix m = new Matrix())
        {
            m.RotateAt(angle, center);
            m.TransformPoints(points);
        }
    }

    private static PointF GetPointOnLine(PointF origin, PointF point, float ratio)
    {
        return new PointF(
            origin.X + (point.X - origin.X) * ratio,
            origin.Y + (point.Y - origin.Y) * ratio);
    }

    // Uses only a single matrix and a single transformation:
    static void Transformation_new(PointF[] points, float rotationAngle, PointF center, float lengthRatio)
    {
        using (Matrix m = new Matrix())
        {
            m.RotateAt(rotationAngle, center, MatrixOrder.Prepend);

            // Replaces GetPointOnLine
            m.Translate(center.X, center.Y, MatrixOrder.Prepend);
            m.Scale(lengthRatio, lengthRatio, MatrixOrder.Prepend);
            m.Translate(-center.X, -center.Y, MatrixOrder.Prepend);

            m.TransformPoints(points);
        }
    }
}
t3chb0t
  • 16,340
  • 13
  • 78
  • 118