Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add decompose function #2

Closed
chrvadala opened this issue Feb 22, 2017 · 13 comments · Fixed by #88
Closed

add decompose function #2

chrvadala opened this issue Feb 22, 2017 · 13 comments · Fixed by #88

Comments

@chrvadala
Copy link
Owner

No description provided.

@peebeebee
Copy link

Hi Chrvadala, are you still working on this?
Are you open to let me check this if I can do an implementation and a PRQ?

I saw that https://www.npmjs.com/package/transformation-matrix-js is deprecated, and I'm looking into an alternative, but we do need the decompose function.

@chrvadala
Copy link
Owner Author

This is great feature that I'd like to have, but actually I don't have enough time to work on it.
A PR is welcome ;-)

@chrvadala chrvadala pinned this issue Dec 15, 2018
@chrvadala
Copy link
Owner Author

Hi @peebeebee, do you have some progress on this?

@peebeebee
Copy link

peebeebee commented Jan 14, 2019 via email

@chrvadala
Copy link
Owner Author

you’re right, are you open to submit a PR?

@chrvadala chrvadala linked a pull request Jun 1, 2020 that will close this issue
@hillin
Copy link
Contributor

hillin commented Oct 28, 2021

We've been using the decompose method in PR #60 for a while, but unfortunately it cannot handle transform with flip/mirror/reflect or whatever it is called. Here is our take to implement this method:

export interface Transform {
    readonly translate: Point;
    readonly rotation: number;
    readonly scale: Vector;
}

export class Transform {
    static toMatrix(transform: Transform): Matrix {
        return matrix.compose(
            matrix.translate(transform.translate.x, transform.translate.y),
            matrix.rotate(transform.rotation),
            matrix.scale(transform.scale.x, transform.scale.y));
    }
}


/**
 * Decompose a matrix into rigid transform components (i.e. translate, rotation and scale).
 * Note unlike SlideTransform, scale contains flip, so its components can be negative.
 * @param m
 */
export function decompose(m: Matrix, horizontalFlip = false, verticalFlip = false): Transform {

    // Remove flip from the matrix first - flip could be incorrectly interpreted as 
    // rotations (e.g. h-flip + v-flip = rotate by 180 degrees).
    if (horizontalFlip) {
        if (verticalFlip) {
            m = matrix.compose(m, matrix.scale(-1, -1));
        } else {
            m = matrix.compose(m, matrix.scale(-1, 1));
        }
    } else if (verticalFlip) {
        m = matrix.compose(m, matrix.scale(1, -1));
    }

    const a = m.a, b = m.b;
    const c = m.c, d = m.d;
    let scaleX: number, scaleY: number, rotation: number;
    
    if (a !== 0 || c !== 0) {
        const hypotAc = Math.hypot(a, c);
        scaleX = hypotAc;
        scaleY = (a * d - b * c) / hypotAc;
        const acos = Math.acos(a / hypotAc);
        rotation = c > 0 ? -acos : acos;
    } else if (b !== 0 || d !== 0) {
        const hypotBd = Math.hypot(b, d);
        scaleX = (a * d - b * c) / hypotBd;
        scaleY = hypotBd;
        const acos = Math.acos(b / hypotBd);
        rotation = Math.PI / 2 + (d > 0 ? -acos : acos);
    } else {
        scaleX = 0;
        scaleY = 0;
        rotation = 0;
    }

    // put the flip factors back
    if (horizontalFlip) {
        scaleX = -scaleX;
    }

    if (verticalFlip) {
        scaleY = -scaleY;
    }

    return {
        translate: { x: m.e, y: m.f },
        rotation: rotation,
        scale: { x: scaleX, y: scaleY }
    }
}

Feel free to use it in your code.

@chrvadala
Copy link
Owner Author

That is fantastic! It should close this old issue 🙂
Are you available to submit a PR?
Do you have some references (book, link, algorithm name,...) about this algorithm?

@hillin
Copy link
Contributor

hillin commented Nov 1, 2021

I think we've referenced several posts on Stack Overflow and Mathematics Stack Exchange, and a few other implementations in other libraries (it's ridiculous that it takes so much effort to make such a primitive algorithm work!); and with something homebrew (mostly the flip removing part).

I'd be happy to submit a PR, but what's the code of conduct of tests here? We didn't write any test for this function, it just worked well in our production environment.

@chrvadala
Copy link
Owner Author

I know, this algorithm is really tricky and it is important that it covers all the cases. This is why I asked you about some algorithm references. Unfortunately there are some algorithm that covers just a subset of transformation matrices.
Anyway, let's understand it with testing.

Regarding the tests I think that it will be really useful to create some matrices (leveraging on the other functions available in this library) and verify that they are "decomposable". We have to test both primitive matrices (e.g. translate) and composed matrices (e.g. translate+scale+rotate)

examples

const matrix = translate(42, 42)

expect(decompose(matrix)).toEqual({
        translate: { tx: 20, y: 20 },
        rotation: 0,
        scale: { x: 1, y: 1 }
})

Just one note about the returned object. In order to keep consistency with https://github.com/chrvadala/transformation-matrix#fromdefinitiondefinitionorarrayofdefinition--arraymatrix signature, can I suggest the following alternative signature? What do you think?

{
        translate: { tx: 0, ty: 0 },
        rotation: { angle: 0 },
        scale: { sx: 1, sy: 1 }
}

@hillin
Copy link
Contributor

hillin commented Nov 2, 2021

I‘m totally OK with the alternative signature, in our case we are just reusing some existing structs.

I'll see what I can do with then PR when I can catch some time.

@hillin
Copy link
Contributor

hillin commented Nov 2, 2021

I've created PR #88.
I find it a little bit disorienting when using the flipX and flipY method, which I instinctively thought stands for horizontal flip and vertical flip respectively, while the truth is contrary. Anyways I've adapted the code to this naming convention.

@chrvadala chrvadala linked a pull request Nov 2, 2021 that will close this issue
@chrvadala
Copy link
Owner Author

Released with v2.10.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants