How to get the final position after multiple graphical operation

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Linkpy
Party member
Posts: 102
Joined: Fri Aug 29, 2014 6:05 pm
Location: France
Contact:

How to get the final position after multiple graphical operation

Post by Linkpy »

Hello everyone.

It's been a while seen I didn't use Love2D, but I enjoy it.

I've got a question rather simple :
I want to know the exact position of a vector after multiple move/rotation/scale operation. For example :

Code: Select all

lg.translate(10,10)
lg.rotate(90)
lg.scale(2,2)

local final_position = FinalPosition(vec)
In this case, using a vector of (1, 10) should give : (30, 12).

But, how can I do this ? What is the formula ? Or, there is a way to access to the transformation matrix ?
For information, I want to create a node system (like in Unity 3D), using local transformation for each node, stacking each other. And, my UI system will be based on this, so for detecting clicks on an UI element, I need to know the final position of an element.

Thanks for you help !
Founder of NeoShadow Studio. Currently working on the project "Sirami".
github / linkpy
marco.lizza
Citizen
Posts: 52
Joined: Wed Dec 23, 2015 4:03 pm

Re: How to get the final position after multiple graphical operation

Post by marco.lizza »

The "formula" is simply to apply the matrix operations by yourself. I don't think the trasformation matrix can be retrieved from the graphics subsystem.
User avatar
Linkpy
Party member
Posts: 102
Joined: Fri Aug 29, 2014 6:05 pm
Location: France
Contact:

Re: How to get the final position after multiple graphical operation

Post by Linkpy »

Okay thanks, I'll search for this.

Edit : Just another question : I'll make an MoonScript implementation of the SFML's Transform class. This class use a 4x4 matrix, for 2D, should I use a 4x4 or a 3x3 matrix ?
Founder of NeoShadow Studio. Currently working on the project "Sirami".
github / linkpy
User avatar
vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

Re: How to get the final position after multiple graphical operation

Post by vrld »

Linkpy wrote:Edit : Just another question : I'll make an MoonScript implementation of the SFML's Transform class. This class use a 4x4 matrix, for 2D, should I use a 4x4 or a 3x3 matrix ?
In 2D, you can express any affine transformation besides translation (moving) using a 2x2 matrix. If you want to include translation in the matrix, you'll have to a 3x3 matrix and homogeneous coordinates (add a a z-coordinate equal to 1 to the vector). You can also use a 4x4 matrix with all zeros in the fourth row and column, but that would be silly.

Note, however, that you don't need to store the transformation in a matrix. You could, for example, store the transformations in a list and apply them to the input vector one after another. The matrix formulation has the (only) advantage that you can collapse multple chained operations into one single transform.
I have come here to chew bubblegum and kick ass... and I'm all out of bubblegum.

hump | HC | SUIT | moonshine
User avatar
Linkpy
Party member
Posts: 102
Joined: Fri Aug 29, 2014 6:05 pm
Location: France
Contact:

Re: How to get the final position after multiple graphical operation

Post by Linkpy »

It's curious because SFML use 4x4 matrix, using it like a 3x3 for 2D operations... I don"t understand :crazy:

My code is this one : http://pastebin.com/6DRGPF1Q (it is in MoonScript, btw)
It's just a "translation" of this file : https://github.com/SFML/SFML/blob/maste ... nsform.cpp

I just add that this file (Transform.cpp) is for the 2D part of SFML (the graphics module), so there must be a reason why they took a 4x4 matrix.

And, for my nodes, each one will have a translation, a rotation and a scale (applied in this order) and the matrix will be used only for know the real position of a node. I can't apply directly the matrix because the Love2D API don't allow it.

For example :

Code: Select all

Root ( at (0; 0) )
 + - - Parent ( at (10; 10) )
       + - - Child ( at (10; 10), relative to the parent's position )
For drawing, I'll use only love.graphics.translate for position nodes in the right position, but, the node will have a method "GetGlobalPosition" which will use the matrix for getting the matrix (maybe will a little caching for optimizing the computations).
Founder of NeoShadow Studio. Currently working on the project "Sirami".
github / linkpy
User avatar
pgimeno
Party member
Posts: 3549
Joined: Sun Oct 18, 2015 2:58 pm

Re: How to get the final position after multiple graphical operation

Post by pgimeno »

You can always use your own functions for rotation, translation, shear and scaling, to keep track of Löve's:

Code: Select all

-- first two rows- third row assumed to be 0, 0, 1
local t11, t12, t13, t21, t22, t23 = 1, 0, 0, 0, 1, 0

function translate(x, y)
  t13, t23 = t11*x + t12*y + t13, t21*x + t22*y + t23
  love.graphics.translate(x, y)
end

function rotate(a)
  local c, s = math.cos(a), math.sin(a)
  t11,t12,t21,t22 = t11*c + t12*s, t12*c - t11*s, t21*c + t22*s, t22*c - t21*s
  love.graphics.rotate(a)
end

function scale(sx, sy)
  t11, t12, t21, t22 = t11*sx, t12*sy, t21*sx, t22*sy
  love.graphics.scale(sx, sy)
end

function shear(sx, sy)
  t11, t12, t21, t22 = t11 + t12*sy, t11*sx + t12, t21 + t22*sy, t21*sx + t22
  love.graphics.shear(sx, sy)
end

function origin()
  t11, t12, t13, t21, t22, t23 = 1, 0, 0, 0, 1, 0
  love.graphics.origin()
end

function local2scr(x, y)
  return x*t11 + y*t12 + t13, x*t21 + y*t22 + t23
end

function scr2local(x, y)
  x, y = x - t13, y - t23
  local invdet = 1 / (t11*t22 - t12*t21)
  return (t22*x - t12*y) * invdet, (t11*y - t21*x) * invdet
end

function getMatrix()
  return t11, t12, t13, t21, t22, t23
end
Remember to call origin() (or just reset the matrix) at the beginning of love.draw() because the default [wiki]love.run[/wiki] internally calls love.graphics.origin() right before calling love.draw().

Example:

Code: Select all

function love.draw()
  origin() -- must be at the beginning of love.draw before any other drawing operation

  -- apply some random transformations
  translate(400, 300)
  scale(2, 2)
  translate(5, 7)
  rotate(0.3)
  scale(0.6, 0.7)
  shear(0.3, 0.2)

  -- draw a rectangle here
  love.graphics.setColor(255, 255, 255, 50)
  love.graphics.rectangle("fill", 11, 21, 31, 32)
  -- translate the coordinates of the rectangle's origin and opposite point
  local x, y = local2scr(11, 21)
  local x2, y2 = local2scr(11+31, 21+32)
  -- sanity check for scr2local: x3, y3 should equal 11+31, 21+32
  x3, y3 = scr2local(x2, y2)

  -- cache the matrix
  local t = {getMatrix()}

  origin()
  love.graphics.setColor(255, 255, 255, 255)
  -- draw a point at each corner of the rectangle
  love.graphics.rectangle("fill", x, y, 1, 1)
  love.graphics.rectangle("fill", x2, y2, 1, 1)
  -- print the matrix
  love.graphics.print(string.format("%g %g %g\n%g %g %g", unpack(t)), 10, 10)
  -- this should print 11+31=42 and 21+32=53
  love.graphics.print(string.format("%g %g", x3, y3), 10, 50)
end

function love.keypressed(k)
  if k == "escape" then love.event.quit() end
end
(Edited to get rid of the tables and matrix multiply code, and add example)
User avatar
Linkpy
Party member
Posts: 102
Joined: Fri Aug 29, 2014 6:05 pm
Location: France
Contact:

Re: How to get the final position after multiple graphical operation

Post by Linkpy »

Oh, thanks you for the example !

I tried to use the same matrix used by SFML (so a 4x4). This is the code I used for SFML :

Code: Select all

	Transform m1;
	Transform m2;

	m1.translate(5, 5);
	m2.translate(5, 5);

	dump(m1.getMatrix());
	dump(m2.getMatrix());

	cout << "---" << endl;

	m1.combine(m2);

	dump(m1.getMatrix());
(Dump just print the matrix to the output)

And in my unittest :

Code: Select all

		m1 = Matrix!
		m2 = Matrix!

		m1\Translate Vector 5, 5
		m2\Translate Vector 5, 5

		m1\Dump!
		m2\Dump!

		m3 = m1\Combined m2

		m3\Dump!
Output :

Code: Select all

SFML :

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
5, 5, 0, 1

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
5, 5, 0, 1
---

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
10, 10, 0, 1

Unittest :

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
5, 5, 0, 1

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
5, 5, 0, 1

1, 0, 0, 10
0, 1, 0, 10
0, 0, 1, 0
0, 0, 0, 1
I don't understand why my matrix doesn't work as expected (it only work for one operation, but for two or more the result is wrong). I'll try to use a 3x3 matrix with your example.

Edit : I have some question :

Code: Select all

function scale(sx, sy)
  t11, t12, t21, t22 = t11*sx, t12*sy, t21*sx, t22*sy
  love.graphics.scale(sx, sy)
end
I don't use this form for me, I think. I create a matrix (let says center_x = center_y = 0) :

Code: Select all

scale_x, 0,       center_x * (1 - scale_x),
0,       scale_y, center_y * (1 - scale_y),
0,       0,       1
And then multiply this matrix with the current one, but I see that you multiply t11 and t21 for the X scaling factor. It comes from the multiplication ? And by the way, I used the 3x3 matrix now and it works well, the unittest have passed.
Founder of NeoShadow Studio. Currently working on the project "Sirami".
github / linkpy
User avatar
pgimeno
Party member
Posts: 3549
Joined: Sun Oct 18, 2015 2:58 pm

Re: How to get the final position after multiple graphical operation

Post by pgimeno »

Linkpy wrote:I don't understand why my matrix doesn't work as expected (it only work for one operation, but for two or more the result is wrong). I'll try to use a 3x3 matrix with your example.
It doesn't seem to follow a consistent criterion:

Code: Select all

Vector2f Transform::transformPoint(float x, float y) const
{
    return Vector2f(m_matrix[0] * x + m_matrix[4] * y + m_matrix[12],
                    m_matrix[1] * x + m_matrix[5] * y + m_matrix[13]);
}
seems to imply that the displacement terms are in the last row, while

Code: Select all

Transform& Transform::translate(float x, float y)
{
    Transform translation(1, 0, x,
                          0, 1, y,
                          0, 0, 1);

    return combine(translation);
}
seems to take the criterion that the displacement terms are in the last column. In fact, judging by the transformPoint function, this shouldn't happen:

Code: Select all

1, 0, 0, 10
0, 1, 0, 10
0, 0, 1, 0
0, 0, 0, 1
With that criterion, the result should be:

Code: Select all

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
10, 10, 0, 1
Furthermore, using a 3D matrix for 2D is overkill, especially for a library that claims to be fast.

tl;dr: SFML seems buggy.

Edited to address your edit:
Linkpy wrote:Edit : I have some question :

Code: Select all

function scale(sx, sy)
  t11, t12, t21, t22 = t11*sx, t12*sy, t21*sx, t22*sy
  love.graphics.scale(sx, sy)
end
I don't use this form for me, I think. I create a matrix (let says center_x = center_y = 0) :

Code: Select all

scale_x, 0,       center_x * (1 - scale_x),
0,       scale_y, center_y * (1 - scale_y),
0,       0,       1
And then multiply this matrix with the current one, but I see that you multiply t11 and t21 for the X scaling factor. It comes from the multiplication ? And by the way, I used the 3x3 matrix now and it works well, the unittest have passed.
Yes, it comes from the multiplication. I right-multiply the matrices to compose them.

Code: Select all

[t11 t12 t13]   [sx  0   0]   [t11*sx  t12*sy  t13]
[t21 t22 t23] * [0   sy  0] = [t21*sx  t22*sy  t23]
[0   0   1  ]   [0   0   1]   [0       0       1  ]
User avatar
Linkpy
Party member
Posts: 102
Joined: Fri Aug 29, 2014 6:05 pm
Location: France
Contact:

Re: How to get the final position after multiple graphical operation

Post by Linkpy »

pgimeno wrote: In fact, judging by the transformPoint function, this shouldn't happen:

Code: Select all

1, 0, 0, 10
0, 1, 0, 10
0, 0, 1, 0
0, 0, 0, 1
With that criterion, the result should be:

Code: Select all

1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
10, 10, 0, 1
The first matrix was generated by my code... So yes there were a problem in my code :crazy:
But thanks you ! It was very useful ! :D
Founder of NeoShadow Studio. Currently working on the project "Sirami".
github / linkpy
marco.lizza
Citizen
Posts: 52
Joined: Wed Dec 23, 2015 4:03 pm

Re: How to get the final position after multiple graphical operation

Post by marco.lizza »

Linkpy wrote:It's curious because SFML use 4x4 matrix, using it like a 3x3 for 2D operations... I don"t understand :crazy:
My guess is they uses 4x4 matrices in order to apply perspective.

If you are interested (as I think) in 2D operations, augmented 3x3 matrices suffice.

However, please keep in mind that the rendered and your results may differ due a bit due to internal number representation.
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 74 guests