2
votes

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:

BeforeAfter

... 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?

2

2 Answers

1
votes

Apart from possibly an incorrect sign (although that depends on the direction of your coordinate axes), I think the problem is simply that you're using += instead of = when you fill in the tx and ty portions of your affine matrix.

0
votes

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...