Search code examples
javascriptmatrixrotationaffinetransform

How can I set the rotation of a matrix to an absolute value in radians around a given point?


I have a javascript Matrix class for affine transforms, and a function to set the rotation to an absolute number of radians, around a given center:

this.setRotation = function (radians, center) {
    var cos = Math.cos(radians);
    var sin = Math.sin(radians);
    this.a = cos;
    this.b = sin;
    this.c = -sin;
    this.d = cos;
    this.tx += center.x - center.x * cos + center.y * sin;
    this.ty += center.y - center.x * sin - center.y * cos;
}

I'm trying to rotate around the center of the object itself, so I'm passing in a center point of half the object's width, and half its height, so for a 100 x 100 object I'm passing in 50, 50.

If my object starts from a rotation of zero, this works fine:

Before After

... but if I rotate the shape again, or start with a rotation of other than zero, the tx and ty values end up wrong:

Position moved

What's wrong with my formula above? Setting the rotation seems to be accurate, but not the tx and ty.

I have seen a few other questions on this subject, in particular this one, but nothing that has helped.

Update To add some numbers to this:

If I begin with a 100x100 rectangle, positioned at 100,100, then my initial matrix is: {Matrix: [a:1, b:0, c:0, d:1, tx:100, ty:100]}

To rotate this clockwise 45 degrees, I feed the above function 0.7853981633974483 (45 degrees in radians), and the center: {Point: [x:50, y: 50]}

This produces the following matrix: {Matrix: [a:0.7071067812, b:0.7071067812, c:-0.7071067812, d:0.7071067812, tx:150, ty:79.28932188]} which is exactly right.

But if I then start with that matrix, and try to return it to zero, by feeding the function arguments of 0 and {Point: [x:70.71067812000001, y: 70.71067812000001]} (the center of the new, rotated shape), then the output is {Matrix: [a:1, b:0, c:0, d:1, tx:150, ty:79.28932188]}, which is the correct rotation but not the correct translation. I'm expecting to get {Matrix: [a:1, b:0, c:0, d:1, tx:100, ty:100]}

I've tried several other variants of the function, including using the center of the rectangle in the parent coordinate space 150,150 instead of the local center of 50,50, and replacing += with = as suggested below, but nothing seems to help. If I comment out the tx calculation then my shape rotates beautifully around it's origin, so the issue must be either with the tx/ty calculation, or with the center point that I'm passing in.

Does anyone have any further ideas?


Solution

  • My problem was to do with getting the object back to the origin - removing the top left coordinates as well as half the height and width before rotating, then adding them back again.

    I found it easier to use a rotate function than a setRotate function, and ended up with the following code:

    // Matrix class functions:
    this.rotate = function (radians) {
        var cos = parseFloat(Math.cos(radians).toFixed(10));
        var sin = parseFloat(Math.sin(radians).toFixed(10));
        a = this.a,
        b = this.b,
        c = this.c,
        d = this.d,
        tx = this.tx,
        ty = this.ty;
        this.a = a * cos - b * sin;
        this.b = a * sin + b * cos;
        this.c = c * cos - d * sin;
        this.d = c * sin + d * cos;
        this.tx = tx * cos - ty * sin;
        this.ty = tx * sin + ty * cos;
    }
    
    this.setRotation = function (radians) {
        var rotation = this.rotation();
        this.rotate(radians - rotation);
    }
    
    this.translate = function (tx, ty) {
        this.tx += tx;
        this.ty += ty;
    }
    
    // Called from outside the class
    var transformedBounds = pe.Model.ReadTransformedBounds(selection[0]);
    var itemTransform = pe.Model.ReadItemTransform(selection[0]);
    
    // Cache the coordinates before translating them away:
    var ox = transformedBounds.Left + (transformedBounds.Width / 2); 
    var oy = transformedBounds.Top + (transformedBounds.Height / 2);
    itemTransform.translate(-ox, -oy);
    
    // Rotate:
    itemTransform.setRotation(radians);
    
    // Restore the translation:
    itemTransform.translate(ox, oy);
    

    All of which is probably pretty obvious if you can actually do sums, but I post it here in case anyone has a day as dim as mine...