Log In  

Cart #22757 | 2016-06-12 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
61

A howto about rotating a sprite around its center...
Use left and right arrows to spin the sprite.

P#22756 2016-06-12 10:25 ( Edited 2018-08-11 23:32)

Works great. Mind if I reuse it in my own project?

P#27503 2016-08-27 22:15 ( Edited 2016-08-28 02:15)
1

Very nicely done ! I'm starting to see that FIREFOX is really not a good browser to play these PICO programs. I have one tab open in GOOGLE CHROME just for PICO and it plays there beautifully.

Will have to see the source to this when I get PICO, should be 48-hours from now.

P#27504 2016-08-27 22:54 ( Edited 2016-08-28 02:54)

Nice work! One thing that was confusing me was that when calculating the reverse rotation for xx and yy, the direction of the angle wasn't reversed. The reason it works is because the input angle is specified in a clockwise direction (10 degrees turns the ship right) while increasing angle goes anticlockwise in PICO-8 (10 degrees turns the ship left).

P#41001 2017-05-26 15:19 ( Edited 2017-05-26 19:23)

>Works great. Mind if I reuse it in my own project?

should be ok, the cart has a cc license (see the CC icon)
https://en.wikipedia.org/wiki/Creative_Commons_license

> I'm starting to see that FIREFOX is really not a good browser

running fine here, and it's on on a 5 years old laptop.

P#41003 2017-05-26 15:59 ( Edited 2017-05-26 19:59)

Can it be abstracted as to rotate around arbitrary point? I'm thinking about a game like Asteroids, but where you also have to fight other ships (which would be capable of shooting asteroids as well) and rotating around an arbitrary point would be useful for AI (as I want to make them circle strafe around player from time to time).

P#41004 2017-05-26 16:08 ( Edited 2017-05-26 20:08)

Edit: see post below for the optimized and pixel-perfect version

P#45365 2017-10-20 16:51 ( Edited 2018-05-08 20:35)

Hey @freds72 - I grabbed your code for a project and it seems like it doesn't quite work. When I rotate my sprite 180 degrees the drawing point seems to shift up and right one pixel - so the sprite is moved one pixel to the right and one pixel downwards which cuts off the right most and bottom lines of the sprite. Is this a known issue?

P#51200 2018-04-02 13:21 ( Edited 2018-04-02 17:21)

Edit: See post further below to have the actual fix (and a complete demo as an added bonus!)

P#51831 2018-04-21 02:23 ( Edited 2018-05-08 20:21)

Hah, cool! I only just saw this reply. Thanks for putting in the work for this. I'd fudged it by just not having a left or top line for that sprite. Just plopped in those extra -0.5s and we good!
Thanks again!

P#52298 2018-05-02 16:26 ( Edited 2018-05-02 20:27)

Hello, does anyone mind explaining how this works? I'm adapting the first example @fred72 provided to a Gyruss clone that I'm working on (couldn't get the second to work) and I managed to get it to function semi-decently, but doing so required some pretty nasty hacking since I'm just not familiar enough with the math involved.

Edit: I got the last example working. However when I use this one rather than the previous, the sprites streak at the edges. Does anyone know how to fix this?

P#52520 2018-05-07 22:42 ( Edited 2018-05-08 05:58)
19

My initial sample code misses a 0.5 offset.
Edit: fixed the fix!
Change:

ca*=w
sa*=w

to:

ca*=w-0.5
sa*=w-0.5

(e.g. I needed to adjust the square center with a half pixel)
The sprite can now use the full 8x8 or 16x16 extent!!

The "streaks" you have seens are "normals", sprite "effective image" must fit within a circle (e.g. pixels outside sprite radius will get trimmed).
I also added a clear color (unfortunately needed for pixel outside of the 'safe' zone).
Set the rspr_clear_col to specify a color that matches your sprite palette.

As for the math behind, it comes from:
Fast Bitmap Rotation and Scaling
In short, the code draws the image line by line and calculate the line (in sprite space) that needs to be copied.

The band trick (to stay within sprite boundaries) is from:
https://github.com/morgan3d/misc/tree/master/p8particle

Gyruss is a pseudo-3d game, suggest to use the rspr version I posted (e.g. drawing into spritesheet).
That will give you sspr for free and will allow you to scale sprites based on their distance (actually 1/z).

To get you started :)

Cart #52541 | 2018-05-08 | Code ▽ | Embed ▽ | No License
19

P#52525 2018-05-08 02:57 ( Edited 2018-05-08 20:36)

Thank you so much for that link; that's exactly what I was hoping for :D

I did get the second example working after analyzing it for a little while; I was (somehow) under the impression that it required the players actual x and y coordinates to function (it was really late xD). As for the distortion I mentioned: I resolved that by trimming the sprite down a pixel, so the image is now effectively 14x14 rather than 16x16. That's fine, but I liked the original graphic I created more xD

As for the nasty hacks I mentioned, I removed those after I realized that I was calculating the players rotation using a method that didn't work with your function. I can't seem to find a link to where I got the method I was using, but essentially the only real difference between what I was using before, and what I'm using now is that it had me dividing the players angle by pi, which was causing the player object to veer off the origin I wanted it to orbit. After tweaking my function, I managed to get it working properly :D

Distorted Sprite
Editor View

Fixed Sprite
Editor View

Note: I noticed your edit while I was writing this, thanks for the help! As for the code, I'll definitely take a look, since it does a lot more already than what I managed to get working :)

P#52539 2018-05-08 15:28 ( Edited 2018-05-08 19:30)

Bumping up the thread for @Arcturus615 & @Davbo: offset bug is truly fixed (no more pixel shifts or sprite cropping!)

P#52551 2018-05-09 03:11 ( Edited 2018-05-09 07:11)

Hey freds, thanks! When I added the 0.5s last time it appeared to work but then using a sprite which was a circle, so 3 pixels in each corner were transparent, seemed to have an issue where the bottom right corner was the same colour as the rest of the sprite instead of transparent.
I'm going to be working on the game tonight (UK time) so will try again with your latest version since I was still using the non sprite sheet version and let you know how I get on :D

P#52553 2018-05-09 05:06 ( Edited 2018-05-09 09:06)

Awesome, thanks freds! After studying your code, I've pretty much recreated it in a effort to figure it all out in my brain. The Gyruss part, that is. I'm not touching that sprite rotation function for a little bit xD

Have to say, working with your function is extremely easy now that I've worked out roughly how it works.

P#52559 2018-05-09 12:31 ( Edited 2018-05-09 16:31)

tried out the fix and working perfectly for me now - thanks a bunch!

P#52609 2018-05-11 08:12 ( Edited 2018-05-11 12:12)

the first one seems to be broken it doesn't draw the sprite correctly.
it seems to offset the pixels incorrectly

P#54952 2018-08-11 12:09 ( Edited 2018-08-11 16:10)

Is there sprite rotation code that does not use sin() or cos() ?

P#54956 2018-08-11 13:03 ( Edited 2018-08-11 17:03)

@dw817 what do you have against sin/cos?
That’s the usual way to convert an angle to x/y coordinates!

That said, given pico resolution, using a pre-defined cos/sin table by 1/64 increment is usually enough.

P#54962 2018-08-11 14:24 ( Edited 2018-08-11 18:24)
3

If you take a close look at SIN() and COS() for circles, they are really ragged compared to using a simple substitute like balancing comparisons thus:

-- note! requires floating
-- point numbers, messy
function uglycircle(x,y,r)
local i
x=x+r/1.05
for i=1,385 do
  pset(x,y)
  x+=sin(i/(r*6.1))
  y+=cos(i/(r*6.1))
end
end

cls()
for i=7,64,8 do
  uglycircle(64,64,i)
end
-- lovely circle from dots for pico using balancers
-- note! no floating point numbers are needed!
function circle(x,y,r,c)
local xo,yo,ba=0,r,3-2*r
repeat
  pset(x-xo,y-yo)pset(x-xo,y+yo)
  pset(x+xo,y-yo)pset(x+xo,y+yo)
  pset(x-yo,y-xo)pset(x-yo,y+xo)
  pset(x+yo,y-xo)pset(x+yo,y+xo)
  if (ba<0) ba+=6+4*xo else ba+=10+4*(xo-yo) yo-=1
  xo+=1
until xo>yo
end

cls()
for i=7,64,8 do
  circle(64,64,i)
end
P#54963 2018-08-11 15:37 ( Edited 2018-08-11 20:24)

@freds72 your second post says..
Change:

ca*=w
sa*=w

to:

ca*=w-0.5
sa*=w-0.5

but your first code does not have..

ca*=w
sa*=w

in it but instead has..

 sa=sin(a)
 ca=cos(a)

can u explain?
right now it uses like a 7x7 tile space or something because the higher sprite index I use the worse it looks coming to a point where its not even drawing the correct sprite.

P#54965 2018-08-11 15:54 ( Edited 2018-08-11 20:02)
2

@dw817 hu.. not sure what to do with that. Sprite rotation needs a vector from an angle.

@Shadowblit16 look 2 lines below, I am reusing variables.
Also note that the code assumes the rotated sprite fits within a circle.
(eg if you want a bigger sprite, you need to ‘waste’ space - see the Gyrus example for some sprite examples)
Here is a simpler version that writes to screen:

function rspr(sx,sy,x,y,a,w)
    local ca,sa=cos(a),sin(a)
    local srcx,srcy
    local ddx0,ddy0=ca,sa
    local mask=shl(0xfff8,(w-1))
    w*=4
    ca*=w-0.5
    sa*=w-0.5
    local dx0,dy0=sa-ca+w,-ca-sa+w
    w=2*w-1
    for ix=0,w do
        srcx,srcy=dx0,dy0
        for iy=0,w do
            if band(bor(srcx,srcy),mask)==0 then
                local c=sget(sx+srcx,sy+srcy)
                pset(x+ix,y+iy,c)
            end
            srcx-=ddy0
            srcy+=ddx0
        end
        dx0+=ddx0
        dy0+=ddy0
    end
end
P#54967 2018-08-11 16:37 ( Edited 2018-08-11 20:46)

@freds72 so wait why do u allow a width to be passed but not a height?

is w not have to do with tilesize?

P#54969 2018-08-11 16:50 ( Edited 2018-08-11 20:50)

Hi Fred:

It should be possible to derive rotating coordinates from the circle by marking the pixels into a table. For instance, to stuff the values into an array and make a game like Gyruss.

https://youtu.be/haehLgNNB4c?t=24

P#54972 2018-08-11 17:07 ( Edited 2018-08-11 21:07)

@dw817 the circle thing doesn’t really work as you’ll have hole between circle if increasing radius (in pixel unit) - if I understand what you are proposing...

Note: I already did a Gyruss prototype, see above demo cart ;)

P#54976 2018-08-11 17:55 ( Edited 2018-08-11 21:55)

You could always 1/2 the steps of the plotter. I think my idea would work.

I'm working on a data compressor right now, a powerful one that functions a little like a computer chess player in that it looks ahead for its moves.

I should be able to post it today. Then I'll see if it's possible to make a very smooth rotation engine using the balancers above.

Of course if ZEP would just add rotation plotting to P8, which is standard in most video game programming languages, we wouldn't be here. :)

GrabImage img_stripe,0,0
Repeat
  SetAlpha .008
  Cls
  For i=-256 To 1024+256 Step 2
    SetRotation bgswirl*.0005
    DrawImage img_stripe,i,Sin(bgswirl/2000.0+(i/8.0))*200.0+350.0
    If i Mod 4=0
      bgswirl:+1
    EndIf
  Next
  Flip -1
Until KeyDown(27)
P#54979 2018-08-11 19:32 ( Edited 2018-08-11 23:32)
2

If you're using the function spr_r defined at the top in jihem's code, I found a fix for a bug where the wrong spritesheet pixels would be taken (unless your sprite id is 0, pixels from a sprite too far will be taken. Sometimes this will go beyond the spritesheet, resulting in black pixels.

sx=(s%8)*8
sy=flr(s/8)*8

should be

sx=(s%16)*8
sy=flr(s/16)*8

since there are 16 sprites per row in the spritesheet.

P#80643 2020-08-11 19:55
5

I continued working on the function and added flipping and custom pivot. I also refactored the code a bit to work directly with delta coords dx and dy and reduce extra operations.
I also now pass the sprite location (i, j) directly but if your prefer working with sprite id n, just compute yourself i = n % 16 and j = flr(n / 16).

Note that I'm not flooring x and y but if like me you observe jittering when applying rotation to your moving character while following it with the camera, this is due to pixel fractions bumping rotated pixels to the neighbor pixels. I suggest you floor x and y before passing them to spr_r in that case.

My code is a bit more expanded and has comments, I don't mind because I minify it, but if you don't, feel free to make it compact again as in the original version.

Adapted from jihem's spr_r

Changes:

  • w and h don't default to 1 (you can restore that easily)
  • angle is passed directly as PICO-8 angle between 0 and 1 (no division by 360, counter-clockwise sign convention)
  • support flipping
  • support custom pivot
  • support transparent_color
  • draw pixels even the farthest from the pivot (e.g. square corner to opposite corner) by identifying target disc
  • fixed yy <= sh -> yy < sh to avoid drawing an extra line from neighbor sprite
function spr_r(i, j, x, y, w, h, flip_x, flip_y, pivot_x, pivot_y, angle, transparent_color)
  -- precompute pixel values from tile indices: sprite source top-left, sprite size
  local sx = 8 * i
  local sy = 8 * j
  local sw = 8 * w
  local sh = 8 * h

  -- precompute angle trigonometry
  local sa = sin(angle)
  local ca = cos(angle)

  -- in the operations below, 0.5 offsets represent pixel "inside"
  -- we let PICO-8 functions floor coordinates at the last moment for more symmetrical results

  -- precompute "target disc": where we must draw pixels of the rotated sprite (relative to (x, y))
  -- the target disc ratio is the distance between the pivot the farthest corner of the sprite rectangle
  local max_dx = max(pivot_x, sw - pivot_x) - 0.5 
  local max_dy = max(pivot_y, sh - pivot_y) - 0.5
  local max_sqr_dist = max_dx * max_dx + max_dy * max_dy
  local max_dist_minus_half = ceil(sqrt(max_sqr_dist)) - 0.5

  -- iterate over disc's bounding box, then check if pixel is really in disc
  for dx = - max_dist_minus_half, max_dist_minus_half do
    for dy = - max_dist_minus_half, max_dist_minus_half do
      if dx * dx + dy * dy <= max_sqr_dist then
        -- prepare flip factors
        local sign_x = flip_x and -1 or 1
        local sign_y = flip_y and -1 or 1

        -- if you don't use luamin (which has a bracket-related bug),
        -- you don't need those intermediate vars, you can just inline them if you want
        local rotated_dx = sign_x * ( ca * dx + sa * dy)
        local rotated_dy = sign_y * (-sa * dx + ca * dy)

        local xx = pivot_x + rotated_dx
        local yy = pivot_y + rotated_dy

        -- make sure to never draw pixels from the spritesheet
        --  that are outside the source sprite
        if xx >= 0 and xx < sw and yy >= 0 and yy < sh then
          -- get source pixel
          local c = sget(sx + xx, sy + yy)
          -- ignore if transparent color
          if c ~= transparent_color then
            -- set target pixel color to source pixel color
            pset(x + dx, y + dy, c)
          end
        end
      end
    end
  end
end

Check the source if you need more comments, but note the actual file I'm using relies on some constant tile_size = 8 defined elsewhere.

Source (v1.0): sprite.lua
Latest (may contain patches, but link may not work if I move or rename the file): sprite.lua

P#81404 2020-08-31 17:43

@huulong Thank you so much for providing such a powerful and well-commented function! This is exactly what I needed!

A few things that confused me at first:

  • "Tile" here means an 8px by 8px cell of the sprite sheet. At first I thought i and j were referring to x and y coordinate of the sprite on the spritesheet, but actually they're the row and column of the tile in the spritesheet.
  • It took me a while to figure out what pivot_x and pivot_y were. They are the point that the sprite will rotate around. If you set pivot_x=0 and pivot_y=0, then the sprite will rotate around its top left corner. If you set pivot_x=0 and pivot_y=6, the sprite will pivot around the pixel that is 6 pixels below the top left corner.

Once I figured those things out, this worked like a charm. Thank you so much for sharing it!

P#83536 2020-10-31 06:20
5

I made a version that doesn't cut off the edges of the sprite:

function rspr(s,x,y,a,w,h)
 sw=(w or 1)*8
 sh=(h or 1)*8
 sx=(s%8)*8
 sy=flr(s/8)*8
 x0=flr(0.5*sw)
 y0=flr(0.5*sh)
 a=a/360
 sa=sin(a)
 ca=cos(a)
 for ix=sw*-1,sw+4 do
  for iy=sh*-1,sh+4 do
   dx=ix-x0
   dy=iy-y0
   xx=flr(dx*ca-dy*sa+x0)
   yy=flr(dx*sa+dy*ca+y0)
   if (xx>=0 and xx<sw and yy>=0 and yy<=sh-1) then
    pset(x+ix,y+iy,sget(sx+xx,sy+yy))
   end
  end
 end
end

Here's an example

P#92940 2021-06-03 00:02 ( Edited 2021-06-03 00:38)
2

For anyone needing transparency:

replacepset(x+ix,y+iy,sget(sx+xx,sy+yy))

by

local col = sget(sx+xx,sy+yy)
if col != 0 then
   pset(x+ix,y+iy,col)
end
P#94406 2021-07-03 11:58 ( Edited 2021-07-03 12:06)

@Gabe_8_bit I wanted to have a look at this example in Pico8 but it wouldn't download.
Is it my end or has it been removed?

Thanks

AH. I realised it's just a post number and not a cart number... Sorry

P#98051 2021-09-30 18:34 ( Edited 2021-09-30 18:36)

Hello forum

In addition to this rotate function can anyone give some insight into how to apply a dx/dy value relative to the direction the sprite is facing?

I am trying to create a top down driving game that uses the sprite rotation function presented in this post. I took this cart of Asteroid by @mccolgst: https://www.lexaloffle.com/bbs/?tid=29903 and am trying to apply the sprite rotation to the player (My edit of this cart is attached to the post).

I'm very new to use of angles and game programming in general, but i've been stuck on this problem for quite a while and just reaching out if anybody can explain how this could be achieved; am I missing any other functions that could solve it etc...

Cart #rotate1-1 | 2022-01-15 | Code ▽ | Embed ▽ | No License

Thank you in advance!

P#105149 2022-01-15 18:32

rotation seems fine in your cart. what is the problem?

P#105150 2022-01-15 19:05

Hi - yes, sorry I don't think my post was that clear.

Simply, I was just curious how you could take the cart at the beginning of this thread (#22757) and make the ship move forwards in the direction the sprite is facing?

The Asteroid cart I posted has the right movement, but I cannot figure out how to apply the sprite rotate function from the original post...

Thanks!

P#105196 2022-01-16 20:14

can you make another thread for that, or come on discord in the #help channel? you have direction and rotation in two separate carts, now it’s a matter of combining the functions!

P#105299 2022-01-18 04:13
P#105336 2022-01-18 18:27

@freds72 I want to thank you for your example (your Gyruss-like post). Basically I faced the sprite rotation problem working on a new game and I spent almost a week on doing linear geometry stuff but due to integer approximation I was having a lot of "holes" in my rotated sprites. Then I searched and searched for a solution and found your example. Applying your function gave me the result I was searching for, and so thank you. So, for future people searching how to rotate a sprite in Pico-8, definetly check out the Gyruss cart!

P#111099 2022-04-30 12:18 ( Edited 2022-04-30 12:19)

thanks but do consider switching to: https://www.lexaloffle.com/bbs/?tid=38548
must faster and bettet integration with sprite flags

P#111102 2022-04-30 14:01

How do I add force to the direction where the sprite is pointing? Thanks in advance! (my brain is running kinda slow right now)

P#134583 2023-09-19 13:05

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-19 06:22:34 | 0.150s | Q:94