Rotating dynamic sprites... [SOLVED]

Started by DoorKnobHandle, Tue 04/12/2007 21:42:48

Previous topic - Next topic

DoorKnobHandle

For a little project, I need to rotate dynamic sprites. I have one set up for now and when I press the UP cursor-key, it rotates it by calling the DynamicSprite.Rotate ( ) function with "1" passed as parameter for the angle. When trying the game out, nothing happens. When I enter "45" as angle, it works, but will mess up the pixels in the sprite the more it is rotated. When I change the sprite dimensions from even values to uneven ones, it will work with any number, BUT it will mess up the whole sprite a whole lot, reduce its size, if turned enough even make it disappear all while not rotating properly (there are rectangular areas around the sprite rotating somehow, while a little "core" zone in the middle will stay the same).

As I tried this with a few different approaches and even in the older 2.X versions already, I'm pretty sure it's not my mistake - however I was surprised to NOT find anything about it on the forums or the bug-tracker already. I can post screens, but it's really quickly and easily reproduced - plus, I'd say I'd still be pretty surprised if most people who played with AGS and its more advanced rendering options for some time don't know what I'm talking about here already.

Or are AGS' dynamic-sprite-rotating-functions not really made for rotating in "real-time" and small angle-increments?

Hope I made myself clear with this, I'm pretty tired though, so ask if you don't understand something.

Khris

An angle of 1 won't produce visible changes if the sprite is too small (sin(1) and cos(1) are too close to 0/1 to get to the next pixel).
Rotating an already rotated sprite will screw up the pixels.

To solve both problems at once, keep the current angle stored, then
- reset the DynSprite to the unrotated sprite in the slot
- rotate it using the stored angle
- display it

DoorKnobHandle

That rotating an already rotated sprite screws up should be mentioned in the manual. In bold! ;)

I didn't think that the function couldn't be used like I wanted it to use, but rather to only always rotate once. Thanks for the help there.

Gilbert

As the sprites aren't vector images, details MUST be lost after rotating (unless it's angle like 90, 180, 270 degrees), the generated sprite is, well, just a sprite, it wouldn't be so smart to record previous transformation to find its "parent", so when you rotate it again you're just rotating that already rotated sprite, which further mess it up.

I think it's quite obvious behaviour, so I doubt if it's really necessary to make this a big warning. In my opinion, it may help adding a line of notice to the manual, but in bold, probably not.

DoorKnobHandle

Details, babe. Details. :)

Ok, not in bold then. Maybe a more elaborated example of code might be the best?

So, now I have this:

Code: ags

      // store original image temporarily
      this.temp = this.Image;
      
      // rotate
      if ( this.Angle > 0.0 && this.Angle <= 359.0 )
            this.temp.Rotate ( FloatToInt ( this.Angle ), this.temp.Width, this.temp.Height );
      
	// draw the sprite
	Renderer.DrawSprite ( FloatToInt ( this.X ), FloatToInt ( this.Y ), this.temp.Graphic );


And it still doesn't rotate correctly (only few pixels on the border move). Angle is set to 2.0.

Am I really so stupid tonight or is working with this function such a major bitch to everybody? :p

Gilbert

An angle of 2 degrees is still VERY small (especially for small sprites, I don't know about the size of your sprites though), it's not surprising that only some pixels in the border have moved. Say for example you paint two dots on a disc one near the perimeter, one near the centre, when you spin it you'll notice that the dot on the perimeter moves faster, so when you rotate a sprite in a very small angle, chances are the displacement of the pixels near the centre are too small to have any changes, while those near the border may move one or two pixels.

Try testing in 5 degree increment (5 degree may stiill be small, so try also 10 degrees, 15 degrees, etc.) and see if it's working properly.

DoorKnobHandle

Thanks for the answer. I tried higher numbers before, though. They mess the pixels up even when I'm doing it with this "new" way. At 15, for example, the sprite slowly gets "rounded", at 10, the core of the sprite (middle 10px or sth.) stay put, the rest flies around.

Any other suggestions?

Gilbert

Is this.Image the original sprite?

Then this line is the problem:
this.temp = this.Image;

When you use a '=' to equate (dynamic) sprites you're actually just copying the pointer of teh structure, i.e. this.temp points to the same sprite as this.Image, it's not making a new copy.
So, if you rotate this.temp, this.Image would be changed accordingly.

Instead, do the following to make a copy:
this.temp=DynamicSprite.CreateFromExistingSprite(this.Image.Graphic);

DoorKnobHandle

Ah, dumb mistake!

That fixes it, however, now it cuts the edges of the rotated sprite off. When I get rid of the additional width, height parameters, it rotates correctly without cutting edges off, but the sprite starts to move forth and back on the x- and y-axis a bit. What to do against that? Manual correction? I hope not.

To my humble opinion, this thread now already shows that a simpler version of the rotate-function or a different approach altogether should really be considered, if possible. Anybody agree?

Gilbert

Try to place it relative to its centre then.

Like putting it at (x - (this.temp.Width/2), y - (this.temp.Height/2) )

Snarky

Quote from: dkh on Wed 05/12/2007 01:50:37
To my humble opinion, this thread now already shows that a simpler version of the rotate-function or a different approach altogether should really be considered, if possible. Anybody agree?

Coding is hard, and doing geometry in code is extra-hard. The problems you've faced are classic beginner's mistakes. I remember making them myself the first time I tried to do 2D sprites. If there is a simpler method that is as powerful as the current system, that'd be nice, but I can't think of any.

DoorKnobHandle

#11
Typical beginner mistakes? In C++ and SDL (and the SDL_image library) there are Sprite.Rotate (angles) functions that allow to be used exactly as I wanted it in the first place. I just tried to convert my functions from there to here.

For a newcomer to AGS, rotating a sprite sounds simple enough, but at the moment, it's actually one of the hardest things you can do with the program.

What I'd like to see in the rotate-function then, would be:

(1) a function that can be called multiple times without having to manually reset the sprite each frame and rotating it only once. The function should, if possible, do that for the user, so that I can draw a sprite to the screen in the rep_ex () for example and when a button is pressed, I call sprite.rotate (5) and while that button is down, the sprite rotates correctly.

(2) when no optional parameters are given, the function should rotate sprites correctly around their center. To me, that is expected behavior, without making them slide away and forcing the user to add mathematical corrections in form of formulas to keep the sprite in place.

(3) as an idea: how about giving the user the chance to give the coordinates of a new center to rotate around instead of the new width, height ones? That sounds a lot more simple to me, but I haven't played around with the parameters as they are enough to judge whether this is actually a good idea or whether there's some kind of downside to be found.

(4) more freedom when specifying angles? The game crashes when an angle < 0 or > 359 is given. That means the user needs even more workarounds. When the user wants to draw a sprite to the screen and rotate it forever, he should be able to call sprite.rotate (1) in the rep_ex () and not write loops to make sure the sprite rotates correctly. It must be very easy to do this for the user in the function already, so that one can simply supply any number, including negative ones and zero (in which case the function DOESN'T crash, just doesn't do anything) and positive ones > 359.


Anyways, thanks for the help so far.

Gilbot: That rotates correctly, but it jumps across the screen (~20-30px down and right). I tried modifying the parameters (taking the /2 away or taking the whole part after x/y away), but it still jumps. See what I mean with heavy over-complication of a very basic and generally simple function? Even I with the help of three "AGS experts" can't get the function to do the one most obvious basic task without errors: rotating a simple sprite around itself. For a newbie to AGS, this function would be unusable to say the least.

Gilbert

#12
Actually the dynamic sprite manipulating functions are considered advanced functions, so I think it's no surprising that even an expert makes mistakes in the initial codes.

Hmmm, can you show the line for drawing the sprite? Maybe there's some problem.

Ashen

Probably too obvious to be the answer, but:
I think Gilbot's suggestion would center the rotated sprite around (x, y), whatever you supplied for that (this.X/Y?), so there might be jumping from 'positioned at (x,y)' to 'centered at (x,y)'. Actually, wouldn't that jump 'up and left', not 'down and right'? So it might not be the problem after all... But if it is, you may need to work out the centerpoint of the original sprite first, then rotate the image and base the new coordinates on that.
[psuedocode]
cenx = x + (width/2);
ceny = y + (height/2);
Rotate();
DrawAt(cenx - (newwidth/2), ceny-(newheight/2));
[/psuedocode]

While this may seem overcomplicated for a newbie, is there really that much demand for sprite rotation? There's only one thread asking about it that I remember, from March '06. Oddly, you suggested looking up DynamicSprite.Rotate - if it's taken you this long to notice the 'glitch', it can't be that serious :) A lot of that thread was taken up with resolving much the same things you mention here, to the eventual satisfaction of the poster (not a newbie, I admit).

Also, adding a pivot wouldn't just be a DynamicSprite function, would it? The sprite output won't be any different with a different pivot, just the amount it needs to be offset by - which would be different for RawDraw-n sprites than for Objects, or Characters, etc. I'd say it's easy enough to module-ify the code once it works, and even neater with 3.0s Extender functions, so changing the way the engine works for such a specialised request is unnecessary. (Of course, that's just my opinion now - I'll probably regret saying that if I ever need it...) 
Clarifying that further rotations will be done to the already rotated sprite, screwing up the pixels, sounds fair enough. Although, honestly, that seems like a more logical behaviour than it going back to the original sprite.
I know what you're thinking ... Don't think that.

SSH

For the walkcycle gereator, which rotates sprites on the fly all the time, I always use the centrepoint of the "bones" and the rotated sprites to do the positioning, as it avoided the problems you have discovered.
12

DoorKnobHandle

Okay, thanks for the patience and continued response, everybody. I appreciate it.

So, this thread has two basic aspects. In this post, I'll answer them one by one.

Aspect #1: my specific problem of rotating a sprite

Gilbot:

Code: ags

function CRenderer::DrawSprite ( int x, int y, int sprite_slot )
// draws a sprite
{
      // draw the sprite
      this.Surface.DrawImage ( x, y, sprite_slot );
}


Don't think anything's wrong there.

Ashen:

But the newwidth and newheight are supposed to be the same as width and height in my example, so your parameters would cancel out to x, y and that's what I have or had when testing. Am I missing something?


Aspect #2: my suggestion to possibly simplify the rotate-function for sprites

Ok, then. Maybe CJ also has an opinion regarding the function and its use. I still stick to my 4 points of improving the function, because I think it would be a lot more usable and easier usable to the general audience, but I see your points, and since - as Ashen pointed out - people don't seem to use the function very often anyways, I don't think it's all so important either. I just wanted to mention. :)

Ashen

#16
Someone's missing something, but it's just as likely to be me... That psuedocode was a bit vague, let me phrase it better:
height/width are the dimensions of the currently rotated sprite. I guess that'd be this.temp before you reload this.Image (the original, unrotated graphic) and rotate it to the new angle. newheight/newwidth are the dimensions of this.temp after the latest rotation - so unless you haven't actually changed the angle they should be at least slightly different. (And if you haven't changed the angle, the existing center is still good, and the coords should cancel down to (x, y).)
x/y are the last coordnates you drew to. Again, I'd guess that that's this.X/Y - although you might need to update them when you draw the re-rotated sprite:
Code: ags

  this.Surface.DrawImage ( x, y, sprite_slot );
  this.X = x;
  this.Y = y;


EDIT:
Wait, are this.X/Y the center of the rotation, or the initial coords use in DrawImage? If they're the pivot don't update them, but you shouldn't be calling them directly in DrawImage. If they're coords for DrawImage you should update them, and can use them directly...
EDIT2:
Just seen the optional height/width parameters you use. Try leaving them out. Since you're starting the rotation from the original each time, the sprite won't just keep expanding as it would if you rotated an already rotated sprite, and it might be that it's resizing there that causes your problems (as SSH says, cutting bits off the sprite you might actually want). It should also mean that Gilbot's centering code will actually work. I swear, if it was that I'm going to scream...
I know what you're thinking ... Don't think that.

SSH

The basic underlying problem is that if you have a square that is, say, 10 pixels wide and rotate it 45 degrees you now have a diamond that is 14 and a bit pixels wide. Since we specify drawing from the edge co-ordinates in AGS, this causes the jumping around. The only way to keep the sprite from growing like this is to cut bits off, but then you lose the corners of the square...
12

DoorKnobHandle

#18
Okay, I have it like this now:

Code: ags

      // store original image temporarily
      this.temp=DynamicSprite.CreateFromExistingSprite ( this.Image.Graphic );
      
      int cenx = FloatToInt ( this.X ) + ( this.Image.Width / 2 );
      int ceny = FloatToInt ( this.Y ) + ( this.Image.Height / 2 );
      
      // rotate
      if ( this.Angle > 0.0 && this.Angle <= 359.0 )
            this.temp.Rotate ( FloatToInt ( this.Angle ), cenx - ( this.temp.Width / 2 ), ceny - ( this.temp.Height / 2 ) );
      
      // draw the sprite
      Renderer.DrawSprite ( FloatToInt ( this.X ), FloatToInt ( this.Y ), this.temp.Graphic );


this.X/Y is the top-left corner of the sprite. this.temp is the rotated sprite and this.Image is the original. Is the above what you meant, Ashen? I don't really get the part about updating this.X/Y though. What you have put in your post there, doesn't work because that part is in Renderer::DrawSprite ( ) and a Renderer doesn't have this.X/Y - I don't think I have access to the Sprite.X/Y from the renderer. Plus, I wouldn't know why, either. Sprite.X/Y isn't ever changed by any function, right?

Edit: By the way, it doesn't move smoothly up/down/left/right anymore like it used to when the difference in size was there because of the rotation, it jumps there once I rotate it at all! How does that make sense? I'm really confused with this, and again, I don't ever get tired of saying this, a thread with almost 20 replies now and several experts at least somewhat confused really speaks for the point I made earlier, rotating sprites in AGS is currently (imho unnecessarily) hard and confusing... :p

Ashen

OK, so many thiss floatng round, that I got confused about what this actually is ... However, I don't think you need to update this.X/Y after all. I think I see the confusion here - the cenx/y stuff, and AFAIK Gilbot's original suggestion, should be fed into DrawSprite, not used as dimentions for the rotated sprite. So try:
Code: ags

      this.temp=DynamicSprite.CreateFromExistingSprite ( this.Image.Graphic );
      
      int cenx = FloatToInt ( this.X ) + ( this.Image.Width / 2 );
      int ceny = FloatToInt ( this.Y ) + ( this.Image.Height / 2 );
      // Get the original center, based on the original sprite size and location
      
      // rotate
      if ( this.Angle > 0.0 && this.Angle <= 359.0 )
            this.temp.Rotate ( FloatToInt ( this.Angle ) ); // Rotate sprite, no resizing
      
      // draw the sprite
      Renderer.DrawSprite ( cenx - (this.temp.Width/2), ceny - (this.temp.Height/2), this.temp.Graphic );
      // Ges the new top-left coords, based on original center and current dimensions.


And Renderer.DrawSprite can stay as it is.

If this sorts it, the problem isn't that rotating sprites in AGS is unnecessarily confusing, it's that YOU'RE unnecessarily confused when describing it :P
I know what you're thinking ... Don't think that.

SMF spam blocked by CleanTalk