Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: I was looking at SFML Transform.cpp and Transformable.cpp  (Read 6140 times)

0 Members and 2 Guests are viewing this topic.

Critkeeper

  • Jr. Member
  • **
  • Posts: 62
    • View Profile
I was looking at SFML Transform.cpp and Transformable.cpp
« on: September 21, 2015, 07:35:06 am »
Transformable code keeps track of what transforms have been applied and updates the transform if need be.

Transform code provides methods for combined translation and rotation, and combined translation and scale.

For example, suppose we have a method called translate and rotate that does what you'd expect, and which returns a fresh new Transform with the given data.

Rotating around a point with the method rotate(angle, centerX, centerY) will return a transform that is exactly the same as matrix multiplication of translate(centerX, centerY) * rotate(angle, 0, 0) * translate(-centerX, -centerY). In fact thats how the matrix expression for the combined translation rotation was generated. Formula for basic rotation around the origin, scaling around the origin, shearing around the origin and translating can be found on wikipedia, and they can be matrix multiplied to generate other matrices.

"Rotating around a given center" or "Scaling around a given center" are so common that it makes sense for us to combine the formula and provide it as rotate(angle, centerX, centerY), which is exactly what SFML does.

So I was wondering why not just combine all of the transforms that way and throw in shear for good measure, using the standard opengl style of matrix order? For example, a shear around a point, followed by a rotation around a different point, followed by a scale around a third point, followed by a translation:

Code: [Select]
typedef
struct
{
        float v[16];
} Transform;

Transform
transform
(       float shcx
,       float shcy
,       float shvx
,       float shvy
,       float rcx
,       float rcy
,       float angle
,       float sccx
,       float sccy
,       float scvx
,       float scvy
,       float tx
,       float ty
)
{
        const float cosine = cos (angle);
        const float sine = sin (angle);
        const float a = -shvy * scvx * sine + scvx * cosine;
        const float b = shvx * scvx * cosine - scvx * sine;
        const float c = shvy * scvy * cosine + scvy * sine;
        const float d = shvx * scvy * sine + scvy * cosine;
        const float x = tx + scvx * (rcx * (1 - cosine) + rcy * sine) + shvy * scvx * shcy * sine - shvx * scvx * shcx * cosine + sccx * (1 - scvx);
        const float y = ty + scvy * (rcy * (1 - cosine) + rcy * sine) - shvx * scvy * shcx * sine - shvy * scvy * shcy * cosine + sccy * (1 - scvy);
        const Transform r =
        {       a,      c,      0,      0
        ,       b,      d,      0       0
        ,       0,      0,      1,      0
        ,       x,      y,      0,      1
        };
        return r;
}
(notice in the above struct initialization you have to conceptually flip the math matrix across the perceived diagonal. If you format it in a list and look at the indexes you will see that it maps correctly to opengl's way of doing things)

I don't know if this will be useful to anyone here, or whether using it (well namely the method of generating it) could speed up the implementation of SFML Transformable as I haven't really given it much thought.

Here is how I generated the equations:




The left most matrix is obviously a scale(k,h) around point(s,t), the right most is a shear(f,g) around point (q,r), and the middle is rotation(theta) around point(x,y).

The resulting matrix is specifically what you would get for first shearing, then rotating, then scaling around specific points.
The function I provided tacks on translation after doing those three things in that specific order (translation as a last operation is fairly simple and not worth typing out so I didn't, in general you would need to multiply the transformation matrix as well)

Note that you get a different matrix if you want to do those things in a different order. All in all there are 4 operations, so there are 4! = 24 unique transformation matrices, which would be generated similar to the above one, gotten by multiplying 4 matrices together, one matrix for each fundamental kind of operation: rotation about a point, translation, shear about a point and scale about a point, in all possible different orders.

if you had functions for each possible order, 24 functions in all, you could compute a transform for an arbitrary shear around a point, rotation around a point, scale around a point and translation in whatever order all while only making a single function call. And 24 isn't so high that you'd thrash the I-Cache.

Just food for thought.
« Last Edit: September 21, 2015, 07:41:37 am by Critkeeper »
if(CritKeeper.askQuestion()){return question.why();}

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #1 on: September 21, 2015, 07:52:58 am »
Your intent is bit lost among the verbosity of the post. So do you ask if we could add functions to do transformations around a specified point?
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #2 on: September 21, 2015, 08:15:19 am »
Quote
if you had functions for each possible order, 24 functions in all, you could compute a transform for an arbitrary shear around a point, rotation around a point, scale around a point and translation in whatever order all while only making a single function call.
Ok... and why would we do that? What's your point? You should have started this very long post by explaining your intentions, instead of losing us in your maths ;)
Laurent Gomila - SFML developer

Critkeeper

  • Jr. Member
  • **
  • Posts: 62
    • View Profile
Re: I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #3 on: September 22, 2015, 12:36:39 am »
Quote
if you had functions for each possible order, 24 functions in all, you could compute a transform for an arbitrary shear around a point, rotation around a point, scale around a point and translation in whatever order all while only making a single function call.
Ok... and why would we do that? What's your point? You should have started this very long post by explaining your intentions, instead of losing us in your maths ;)

It would just make the library a bit faster and allow users to use all the transformation capabilities of SFML without doing matrix multiplication even once.

Typical libraries provide translation, rotation, and scale. (most don't include shear)
In order to rotate around a point you have to do two matrix multiplications. One to translate the point so that it overlaps the origin, then rotate, then translate back-- this produces a transform matrix that is effectively the same as those transformations in that order; all you have to do is multiply your vector by that transform matrix and voila, you just rotated about a specific point.

SFML provides a function that uses that specific transform matrix, called rotate and which takes both the angle and the point about which to rotate. Ditto for scale. It also provides normal translation.

If you still want to combine scale and translation, or scale and rotation, or rotation and scale, in a specific order, then you have to do old fashion matrix multiplication by using the provided multiplication operator.

Matrix multiplication is kind of slow. Part of the reason SFML provides rotations about a point is because it is convenient, the other reason is that it is faster to just use a premultplied matrix like that than it is to do basic tranlsation, then basic rotation, then translate back. Doing the former involves multplying a matrix by a vector and one function call. Doing the latter involves multiplying a vector by a matrix, and multplying a matrix by a matrix by a matrix, and 4 function calls.

Basically what I am saying is we can take it a step further. Instead of having "rotate about a point" and "scale about a point" and "translate"

We could have:

"RotateScaleTranslate( args for rotation, args for scale, args for translation)"
"RotateTranslateScale( args for rotation, args for scale, args for translation)"
"ScaleRotateTranslate( args for rotation, args for scale, args for translation)"
"ScaleTranslateRotate( args for rotation, args for scale, args for translation)"
"TranslateRotateScale( args for rotation, args for scale, args for translation)"
"TranslateScaleRotate( args for rotation, args for scale, args for translation)"

For example, the first function first rotates, then scales, and then translates. If the implementation did matrix*matrix multiplication underneath in order to generate the appropriate transform matrix, then there wouldn't be much point in using it aside from the fact that you don't have to mess around with the multiplication operator yourself in order to do rotation then scale then translation. With what I am recommending, it would NOT do matrix*matrix multiplication underneath, the transformation matrix would be precomputed and baked into the function.

If you think about it, there are no other combinations of doing these operations. There are only 6 permutations for the given 3 operations, and translation operations can be added by adding the operands (ditto for scale and rotation) so strictly speaking the user really shouldn't have to fool around with matrix multiplication in order to do combinations of those transformations in a specific order; he just needs to know about those 6 specific transformation matrices and thats all.

Thats what I'm saying. The user shouldn't have to fool around with matrix multiplication in order to do all the combinations of transforms that exist in 2d in SFML for scale, translate, and rotate.

If you want to throw in shear, the number jumps from 6 to 24 (compare 3! and 4!), that still isn't too bad, but you can't really afford to throw in another transformation operation beyond that.
« Last Edit: September 22, 2015, 04:00:06 am by Critkeeper »
if(CritKeeper.askQuestion()){return question.why();}

K.F

  • Jr. Member
  • **
  • Posts: 55
    • View Profile
    • Email
Re: I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #4 on: September 22, 2015, 02:29:20 am »
You are basing your reasoning on it bieng 2d, and using the CPU, while SFML uses opengl which is in 3d only. How are you going to implement this in the GPU without requiring OpenCL, CUDA or the recent OpenGL compute shader?

Critkeeper

  • Jr. Member
  • **
  • Posts: 62
    • View Profile
Re: I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #5 on: September 22, 2015, 02:53:57 am »
I'm not basing my reasoning on it being calculated on the cpu.

It doesn't matter where it is calculated, and as a matter of fact SFML does calculate transforms on the CPU. It just happens to format its matrices in a way that conforms with opengl.
if(CritKeeper.askQuestion()){return question.why();}

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #6 on: September 22, 2015, 09:13:12 am »
SFML already optimizes the most used scenarios:
- rotating around a point
- scaling around a point
- combining all the sf::Transformable components (which probably represents 99% of what people use)

And unless you prove it with actual profiling, I don't think matrix multiplication will ever cause a performance issue. It's just a few multiplications and additions, which is ridiculous compared to the time taken by a single draw call.

So no, we're not going to add these ugly functions, unless you have some concrete stuff to prove your point ;)
Laurent Gomila - SFML developer

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: I was looking at SFML Transform.cpp and Transformable.cpp
« Reply #7 on: September 22, 2015, 12:28:13 pm »
If you are actually computing resource-intensive transforms, you can resort to a linear algebra library and pass the 3x3 matrix to sf::Transform. Then you don't have the 4th dimension, which is not needed in 2D, but SFML uses it to comply with OpenGL. There are plenty of linear algebra libraries in C++, and they're usually heavily optimized and outperform SFML anyway.

TranslateRotateScale() and all its variants are incredibly ugly, and a design for combinatorial explosion is not too smart in the first place. There are much better ways in modern C++ to optimize matrix operations (e.g. expression templates). Have a look at Eigen for example.

But I still have the feeling this is another example of premature optimization without practical implications ;)
« Last Edit: September 22, 2015, 01:09:49 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development: