Skip to content

[Doc] Clarifications on the TRSSF canonical order #40

@xuy

Description

@xuy

In the wiki doc on shape adjustment, the doc showed an example

shape foo {
    bar [ x 1 y 2 rot 30 s 2 0.5 skew 10 0 flip 45 ]
}

and in the text below it explained that in this case the adjustment order is to translate first, then rotate, and so on.

I was a bit confused by the text, because if we translate bar first to (1, 2), a rotation would move it around the origin, not (1, 2). The same logic goes to sizing, where it would move to (2, 1).

Fundamentally, the confusion is on the order of applying the affine transformation. If the affine matrix for the translate operation is T, and the R for rotate, then the behavior of CF suggests T * R (shape), while the text says R * T(shape).

I dug around the source code to see how this behavior is implemented. The ASTmodification::makeCanonical function in astexpression.cpp would sort all transformations in canonical order, while ASTmodTerm::evaluate would then apply those transformations. The code would call m.m_transform.premultiply(T) where I believe m is the existing affine transformation and T is the shape transformation. When combined with the canonical order, I believe we are concatenating the transformation matrix as

M * T * R * S * S * F (shape)

where M is the existing/external transformation.

While the order of concatenating the shape transformations is indeed TRSSF, when we do it step by step, we do need to apply the transformations in the reverse order, i.e. F S S R T.

I constructed the following example to illustrate the behavior.

startshape board

m = 5

shape board {
    grid []
    // marks the origin
    SQUARE [b 0.6]
    // If the applying order is TRSSF, then the R here would be no-op on SQUARE
    // However the r is applied to turn a 3x1 bar to 1x3.
    // Indicating R is applied after S.
    SQUARE[x 2 1 s 3 1 r 90]
}

// Draw a horizontal line y = k
path yline(number k)
{
    MOVETO(-m, k)
    LINETO(m, k)
    STROKE(0.01)[]
}

// Draw a vertical line x = k
path xline(number k)
{
    MOVETO(k, -m)
    LINETO(k, m)
    STROKE(0.01)[]
}

shape grid
{
    loop i = -5, 6, 1 [] xline(i) []
    loop i = -5, 6, 1 [] yline(i) []
}

Would like to use your help to clarify if my understanding is correct.

I am happy to send over a small PR to update the wiki.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions