Understanding draw operations and transformations [SOLVED]

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.
Post Reply
randomnovice
Party member
Posts: 126
Joined: Sat May 09, 2015 9:15 pm

Understanding draw operations and transformations [SOLVED]

Post by randomnovice »

Hi folks,

This is probably a basic question but hey, we're all learning!

- I'd like to draw a card made up for many individual components: images and text.
- I would like to then be able to scale and rotate that card.

My problem comes that individually scale and rotating the components it would be very difficult to work out a central point to work as the origin, especially as I think love.graphics.draw asks for an offset from the centre.

I think what I'm looking for is something along the lines of using canvases - but that's completely new to me?
Can I create a master drawn object, made up of lots of little drawn components, then scale and rotate it all in one go?
How would I go about doing something like that?

Disfunctional code example attached: behold Lazy Cat!

Thanks in advance.
Attachments
Creature Cards.love
(231.08 KiB) Downloaded 82 times
Last edited by randomnovice on Sat Mar 16, 2019 10:30 am, edited 1 time in total.
User avatar
pgimeno
Party member
Posts: 3548
Joined: Sun Oct 18, 2015 2:58 pm

Re: Understanding draw operations and transformations

Post by pgimeno »

Yeah, canvases are one way to go. The basic mechanism is as follows:

- Activate the canvas (love.graphics.setCanvas)
- Draw the card to the canvas as if you were drawing to the screen (l.g.printf, l.g.draw, etc) with normal ("alphamultiply") blending mode.
- Deactivate the canvas (same call as above)
- Now you have the canvas ready to draw as if it was a regular image. Just be careful to use premultiplied blending mode to draw it to the screen, in case it has transparency anywhere (including the corners, e.g. use this is you're making the corners round).

But don't do this every frame! Canvas switching is slow. If you need to reuse canvases, make sure to clear the canvas with black colour and zero alpha before using, if any part of the canvas is transparent.
randomnovice
Party member
Posts: 126
Joined: Sat May 09, 2015 9:15 pm

Re: Understanding draw operations and transformations

Post by randomnovice »

Thanks for the tip. I've begun trying to get my head around them but that drawback of not calling too often could be a challenge - individual components of the canvas may change frequently.

I'm still a bit confused by love.graphics.draw to be honest.
I'm getting some really strange behaviour.

I thought that these two commands would present an image in the same place but rotated around its centre.

Code: Select all

love.graphics.draw(image, 100, 100)  
love.graphics.draw(image, 100, 100, angle, 1, 1, image:getWidth()/2, image:getHeight()/2) 
However I'm finding that as soon as any transformation is applied (even a scaling of 1:1) the image jumps to have it's origin coordinates at the centre of the rotation?
User avatar
pgimeno
Party member
Posts: 3548
Joined: Sun Oct 18, 2015 2:58 pm

Re: Understanding draw operations and transformations

Post by pgimeno »

randomnovice wrote: Fri Mar 15, 2019 8:43 pm Thanks for the tip. I've begun trying to get my head around them but that drawback of not calling too often could be a challenge - individual components of the canvas may change frequently.
Well, what I had in mind is that if you have 20 cards to draw and you need to switch canvas 20 times every frame, that is going to hurt performance. There's a lot of "it depends". I don't know what you need exactly.
randomnovice wrote: Fri Mar 15, 2019 8:43 pm I'm still a bit confused by love.graphics.draw to be honest.
I'm getting some really strange behaviour.

I thought that these two commands would present an image in the same place but rotated around its centre.

Code: Select all

love.graphics.draw(image, 100, 100)  
love.graphics.draw(image, 100, 100, angle, 1, 1, image:getWidth()/2, image:getHeight()/2) 
Not really.

The origin point is not just the centre of rotation. It's also the "hook", the relative point within the image that will be placed at the coordinates you pass. For example, if you use love.graphics.draw(image, 0, 0, 0, 1, 1, image:getWidth()/2, image:getHeight()/2), the centre of the image will be drawn at 0,0 therefore only the bottom right 1/4 of the image will be visible.
randomnovice
Party member
Posts: 126
Joined: Sat May 09, 2015 9:15 pm

Re: Understanding draw operations and transformations

Post by randomnovice »

Well, what I had in mind is that if you have 20 cards to draw and you need to switch canvas 20 times every frame, that is going to hurt performance. There's a lot of "it depends". I don't know what you need exactly.
Thank you, you guessed correctly. In my (simple) plan I had 80 cards drawn on the screen at the same time (every frame). I could bring that number down however as many of the cards are on top of one another in the stack.
The origin point is not just the centre of rotation. It's also the "hook", the relative point within the image that will be placed at the coordinates you pass.
Thanks for your patience. Embarrassingly (despite a degree in mathematics) this is the bit I was struggling to get my head around.
It may be something to do with applying two transformations (scaling and rotation). However with some playing around with it, I've managed to achieve what I was looking for. If I'm totally honest, I don't understand why it works... but it does (attached).

If I understand correctly I think that when sx, sy, ox and oy are provided in the function love.graphics.draw...
- The x,y coordinates refer now to the centre of the image.
- ox and oy refer to how far from the centre you would like the top left corner of your image, in the unscaled version. (Positive numbers move the image up and left, negative numbers move the image down and right.)
Attachments
Creature Cards.love
(230.91 KiB) Downloaded 82 times
User avatar
pgimeno
Party member
Posts: 3548
Joined: Sun Oct 18, 2015 2:58 pm

Re: Understanding draw operations and transformations

Post by pgimeno »

randomnovice wrote: Sat Mar 16, 2019 10:29 am Thank you, you guessed correctly. In my (simple) plan I had 80 cards drawn on the screen at the same time (every frame). I could bring that number down however as many of the cards are on top of one another in the stack.
More importantly, I don't think many cards will change between frames, so why not update the canvases of only those that do?

I may be wrong though, perhaps all cards are animated all the time?
randomnovice wrote: Sat Mar 16, 2019 10:29 am If I understand correctly I think that when sx, sy, ox and oy are provided in the function love.graphics.draw...
- The x,y coordinates refer now to the centre of the image.
- ox and oy refer to how far from the centre you would like the top left corner of your image, in the unscaled version. (Positive numbers move the image up and left, negative numbers move the image down and right.)
Close. First, preferably don't call it centre; that may be the cause of the confusion, because it's just an arbitrary point within the image. The LÖVE docs call it "origin offset". I sometimes call it "hook", like in a picture frame. Now, the x, y coordinates *always* refer to that "hook". Simply the default values for sx and sy are 1, which means no scale change, and the default values for ox and oy are 0, which means the top left. Conceptually, they are always applied; I haven't looked up whether LÖVE optimizes the case where no parameters are given, but the concept doesn't change either way.

Let's see if you understand it better this way. Think of the screen and the image as coordinate systems. Then LÖVE performs a linear transform of coordinates between both. When no transformation is performed, the image's origin and the screen's origin coincide, and is located at the screen's upper left corner. If the screen is say 800x600 and the image is 400x300, the image will occupy the upper left 1/4 of the screen.

Now LÖVE performs a series of transformations to the image: first a displacement by -ox,-oy (which will be in image coordinates because it's the first one), then skew, then scale, then rotation and finally a displacement by x,y (the draw coordinates, which will be in screen coordinates because it's the last one).

You can see these steps in the source, here: https://bitbucket.org/rude/love/src/412 ... ix.cpp-239 (note that the matrices are left-multiplied, so the order is right-to-left).

The important points to keep in mind are that ox,oy are in image coordinates, and that wherever you place them, that will be the "hook", and the default "hook" (ox=oy=0) is at the top left of the image. Scaling and rotation will be performed around that "hook". Normally you want the hook either at the top left or at the centre, but in some cases, selecting a different hook can be advantageous. For example, in a "pinch to zoom" library I've been working on, the hook is at the midpoint between the two fingers, so that scaling is centred on that point. In a simple platformer, placing the hook at the bottom left of the player may help making physics easier, as that's the vertical coordinate where contact happens, so you don't have to account for the sprite's height in the calculations. And so on.

As a final example, all of these are equivalent:

Code: Select all

-- Draw an undeformed, unrotated image with origin at ox,oy displaced by ox,oy
love.graphics.draw(img, x + ox, y + oy, 0, 1, 1, ox, oy)
-- Draw the image with origin at 0,0 with no displacement
love.graphics.draw(img, x, y, 0, 1, 1, 0, 0)
-- Same as above (just leave the parameters at their defaults: 0 rotation, 1,1 scale, 0,0 origin offset)
love.graphics.draw(img, x, y)
User avatar
_DaCoolOne_
Prole
Posts: 16
Joined: Sat Mar 09, 2019 3:46 am

Re: Understanding draw operations and transformations [SOLVED]

Post by _DaCoolOne_ »

A little late to the party, but after reading your post I'm thinking that you may be looking for love.graphics.push and love.graphics.pop.

What I would do (although it may not be the most efficient) is something like this:

Code: Select all

-- Push the current state to the transform stack
love.graphics.push()

-- Move the card around (note that the order in which you stack the transforms DOES matter)
love.graphics.translate(100, 50)
love.graphics.rotate(math.pi * 0.4)

-- Draw the components of the card
love.graphics.draw(myImage, 0, 0, 0, 1, 1, myImage:getWidth() * 0.5, myImage:getHeight() * 0.5)
love.graphics.printf('You have activated my trap card!', -200, -100, 400, 'center')

-- Return to previous state
love.graphics.pop()
Idk if a canvas would be more or less efficient in your case (if you're constantly updating then it will definitely be less efficient), but I've found that efficiency doesn't matter too much until you have several hundred/thousand objects. (Or something really crazy in love.update)

Code: Select all

function not(val)
	if val == true then
		return false
	else
		return true
	end
end
randomnovice
Party member
Posts: 126
Joined: Sat May 09, 2015 9:15 pm

Re: Understanding draw operations and transformations [SOLVED]

Post by randomnovice »

Really helpful, thank you for walking me through it pgimeno.

Thanks for the suggestion _DaCoolOne_ but I've managed to find my way through now.
Post Reply

Who is online

Users browsing this forum: No registered users and 26 guests