Matrix transforms producing wrong values

I have a 2D matrix with a few affine transformations and its rotate and scale methods seem to not be working properly.

Output:

matrix
- Got: (a=1.4142135623730951, b=0, c=-0, d=1.4142135623730951, tx=10, ty=20)
- Expected approximation = (a=1.4142135623730951, b=1.414213562373095, c=-1.414213562373095, d=1.4142135623730951, tx=10, ty=20)
matrix.delta_transform_point(&Vector2d(0.0, 0.0))
- Got: (0, -0)
- Expected approximation: (x=0, y=0)
matrix.delta_transform_point(&Vector2d(1.0, 1.0))
- Got: (0, -0)
- Expected approximation: (x=2.220446049250313e-16, y=2.82842712474619)
matrix.transform_point(&Vector2d(0.0, 0.0))
- Got: (10, 20)
- Expected approximation: (x=10, y=20)
matrix.transform_point(&Vector2d(1.0, 1.0))
- Got: (10, 20)
- Expected approximation: (x=10, y=22.82842712474619)

The matrix used for the above tests:

let mut matrix = Matrix2d::default();
matrix.identity();
matrix.rotate(PI / 4.0);
matrix.scale(&Vector2d(2.0, 2.0));
matrix.translate(&Vector2d(10.0, 20.0));

The relevant functions:

pub fn identity(&mut self) {
    self.set_a(1.0);
    self.set_b(0.0);
    self.set_c(0.0);
    self.set_d(1.0);
    self.set_tx(0.0);
    self.set_ty(0.0);
}

pub fn rotate(&mut self, rotation_radians: f64) {
    let cos = f64::cos(rotation_radians);
    let sin = f64::sin(rotation_radians);
    self.set_a(self.a() * cos);
    self.set_b(self.b() * sin);
    self.set_c(self.c() * -sin);
    self.set_d(self.d() * cos);
}

pub fn scale(&mut self, scale: &Vector2d) {
    self.set_a(self.a() * scale.x());
    self.set_d(self.d() * scale.y());
}

If set_a etc does what I think it does, that's not the correct formula for matrix multiplication.

Try something like

pub fn rotate(&mut self, rotation_radians: f64) {
    let cos = f64::cos(rotation_radians);
    let sin = f64::sin(rotation_radians);
    self.set_a(self.a() * cos - self.b() * sin);
    self.set_b(self.a() * sin + self.b() * cos);
    self.set_c(self.c() * cos - self.d() * sin);
    self.set_d(self.c() * sin + self.d() * cos);
    self.set_tx(self.tx() * cos - self.ty() * sin);
    self.set_ty(self.tx() * sin + self.ty() * cos);
}

pub fn scale(&mut self, scale: &Vector2d) {
    self.set_a(self.a() * scale.x());
    self.set_b(self.b() * scale.y());
    self.set_c(self.c() * scale.x());
    self.set_d(self.d() * scale.y());
    self.set_tx(self.tx() * scale.x());
    self.set_ty(self.ty() * scale.y());
}

pub fn translate(&mut self, offset: &Vector2d) {
    self.set_tx(self.tx() + offset.x());
    self.set_ty(self.ty() + offset.y());
}

If this doesn’t do it, feel free to provide (a lot) more details (code and explanation) so we aren’t working with such limited information.

Thanks for answering, though it looks like your code alters more properties than expected in my case. In this case I am following functions in air.Matrix. For example, the ASDoc for air.Matrix.rotate describes:

The rotate() method alters the a , b , c , and d properties of the Matrix object. In matrix notation, this is the same as concatenating the current matrix with the following:

image

Concatenating seems to be described by the air.Matrix.concat() method:

Concatenates a matrix with the current matrix, effectively combining the geometric effects of the two. In mathematical terms, concatenating two matrixes is the same as combining them using matrix multiplication.

That’s exactly what I’m doing. Matrix multiply

equation

and get

(intermediate calculation:)

equation

3 Likes

@steffahn I have tested your above code, but somehow I am not still matching the output of what I get from Adobe AIR :confused:

What I get:

matrix
- Got: (a=1.4142135623730951, b=1.0000000000000002, c=-1.4142135623730951, d=0.4142135623730949, tx=10, ty=20)
- Expected approximation = (a=1.4142135623730951, b=1.414213562373095, c=-1.414213562373095, d=1.4142135623730951, tx=10, ty=20)
matrix.delta_transform_point(&Vector2d(0.0, 0.0))
- Got: (0, -0)
- Expected approximation: (x=0, y=0)
matrix.delta_transform_point(&Vector2d(1.0, 1.0))
- Got: (1.4142135623730954, -0.5857864376269049)
- Expected approximation: (x=2.220446049250313e-16, y=2.82842712474619)
matrix.transform_point(&Vector2d(0.0, 0.0))
- Got: (10, 20)
- Expected approximation: (x=10, y=20)
matrix.transform_point(&Vector2d(1.0, 1.0))
- Got: (11.414213562373096, 19.414213562373096)
- Expected approximation: (x=10, y=22.82842712474619)

Here is more of my code:

...

    pub fn delta_transform_point(&mut self, point: &Vector2d) -> Vector2d {
        Vector2d(self.a(), self.d()) * Vector2d(self.b(), self.c()) * *point

        // If this line is uncommented, fix the following:
        // * Ignore `tx` and `ty`
        // * Maybe convert the matrix into a different form matrix due to coordinates terms
        //
        // Vector2d::from_nalgebra_point(self.to_nalgebra_matrix().transform_point(&point.to_nalgebra_point()))
    }

    pub fn transform_point(&mut self, point: &Vector2d) -> Vector2d {
        self.delta_transform_point(point) + Vector2d(self.tx, self.ty)
    }

...

This is what I had in ActionScript:

package {
    import flash.display.Sprite;
    import flash.geom.*;
    public class Main extends Sprite {
        public function Main() {
            const matrix: Matrix = new Matrix();
            matrix.identity();
            matrix.rotate(Math.PI / 4);
            matrix.scale(2, 2);
            matrix.translate(10, 20);
            trace(matrix.toString());
            trace(matrix.deltaTransformPoint(new Point(0, 0)).toString());
            trace(matrix.deltaTransformPoint(new Point(1, 1)).toString());
            trace(matrix.transformPoint(new Point(0, 0)).toString());
            trace(matrix.transformPoint(new Point(1, 1)).toString());
            /*
                (a=1.4142135623730951, b=1.414213562373095, c=-1.414213562373095, d=1.4142135623730951, tx=10, ty=20)
                (x=0, y=0)
                (x=2.220446049250313e-16, y=2.82842712474619)
                (x=10, y=20)
                (x=10, y=22.82842712474619)
            */
        }
    }
}

This of course isn’t true of my code, but I doubt the docs are accurate here, regardless; the rotate method isn’t clearly documented as to whether the rotation occurs logically before or after the remaining transformation, but even so, the scale() function docs claim only a and d to be modified, which should be wrong regardless of order of operations :thinking:

2 Likes

Ah, my bad… there’s a mistake in that things like

    self.set_a(self.a() * cos - self.b() * sin);
    self.set_b(self.a() * sin + self.b() * cos);

use the updated value for self.a() in the second line.

pub fn rotate(&mut self, rotation_radians: f64) {
    let cos = f64::cos(rotation_radians);
    let sin = f64::sin(rotation_radians);

    let new_a = self.a() * cos - self.b() * sin;
    let new_b = self.a() * sin + self.b() * cos;
    let new_c = self.c() * cos - self.d() * sin;
    let new_d = self.c() * sin + self.d() * cos;
    let new_tx = self.tx() * cos - self.ty() * sin;
    let new_ty = self.tx() * sin + self.ty() * cos;

    self.set_a(new_a);
    self.set_b(new_b);
    self.set_c(new_c);
    self.set_d(new_d);
    self.set_tx(new_tx);
    self.set_ty(new_ty);
}

pub fn scale(&mut self, scale: &Vector2d) {
    let new_a = self.a() * scale.x();
    let new_b = self.b() * scale.y();
    let new_c = self.c() * scale.x();
    let new_d = self.d() * scale.y();
    let new_tx = self.tx() * scale.x();
    let new_ty = self.ty() * scale.y();

    self.set_a(new_a);
    self.set_b(new_b);
    self.set_c(new_c);
    self.set_d(new_d);
    self.set_tx(new_tx);
    self.set_ty(new_ty);
}

Rust Playground

3 Likes

Thanks, @steffahn! That solved the issue! Now just the transform_point function that is not working. I have put it in the playground: Rust Playground.

pub fn delta_transform_point(&self, point: &Vector2d) -> Vector2d {
    Vector2d(
        self.a() * point.x() + self.c() * point.y(),
        self.b() * point.x() + self.d() * point.y(),
    )
}

That prints zero for the X coordinate for one of the cases. I have also attempted:

Vector2d(
    self.a() * point.x() + self.c() * point.x(),
    self.d() * point.y() + self.b() * point.y(),
)

If you're referring to this case:

matrix.delta_transform_point(&Vector2d(1.0, 1.0))
- Got: (0.0000000000000002220446049250313, 2.82842712474619)
- Expected approximation: (x=2.220446049250313e-16, y=2.82842712474619)

Then this look correct. Notice that the actionscript result ends in e-16, meaning the 2.22... is multiplied by 10^-16.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.