What am I missing here? - TerryRitchie - 09-08-2022
Over the past week I've been on a trigonometry quest to better understand the math needed for degree, radian, and vector math for my tutorial. The previous tutorial was lacking in this area and I'm changing that.
I've been using a routine written by Galleon back in 2009 to rotate a sprite using trig and _MAPTRIANGLE. I thought I would see if I could rewrite it using new commands that have come out since then like _HYPOT, _ATAN2, _D2R, etc..
The code below is my attempt at this, and it works. But still not nearly as efficient as Galleon's. I have marked Galleon's lines of code and my lines of code with REM statements, ' ******* RITCHIE and ' ******* GALLEON.
I've stared and stared at his lines 59, 60, 70, and 71 (Galleon's calculations) and I'll be damned if I can make sense of them. Could someone please explain to me how his lines of code achieve the same thing I'm doing? What am I missing here?
Code: (Select All) '** SPRITE ROTATION
DIM Img AS LONG
DIM RotImg AS LONG
Img = _NEWIMAGE(50, 100, 32)
_DEST Img
CLS
LINE (0, 0)-(49, 99), , BF
_DEST 0
SCREEN _NEWIMAGE(640, 480, 32)
CLS
_PUTIMAGE (100, 100), Img ' original image
RotateImage 30, Img, RotImg
_PUTIMAGE (200, 100), RotImg ' rotated image
SUB RotateImage (Degree AS SINGLE, InImg AS LONG, OutImg AS LONG)
DIM px(3) AS INTEGER
DIM py(3) AS INTEGER
DIM Left AS INTEGER
DIM Right AS INTEGER
DIM Top AS INTEGER
DIM Bottom AS INTEGER
DIM v AS INTEGER
DIM RotWidth AS INTEGER
DIM RotHeight AS INTEGER
DIM Xoffset AS INTEGER
DIM Yoffset AS INTEGER
DIM Rotate AS SINGLE ' ******* RITCHIE
DIM NewRadian AS SINGLE ' ******* RITCHIE
DIM Distance AS SINGLE ' ******* RITCHIE
DIM COSr AS SINGLE ' ******* GALLEON
DIM SINr AS SINGLE ' ******* GALLEON
DIM x AS SINGLE ' ******* GALLEON
DIM y AS SINGLE ' ******* GALLEON
IF OutImg THEN _FREEIMAGE OutImg
px(0) = -_WIDTH(InImg) / 2 ' -x,-y ------------------- x,-y
py(0) = -_HEIGHT(InImg) / 2 ' Create points around (0,0) p(0) | | p(3)
px(1) = px(0) ' that macth the size of the | |
py(1) = _HEIGHT(InImg) / 2 ' original image. This creates | . |
px(2) = _WIDTH(InImg) / 2 ' four vector quantities to | 0,0 |
py(2) = py(1) ' work with. | |
px(3) = px(2) ' p(1) | | p(2)
py(3) = py(0) ' -x,y ------------------- x,y
'Rotate = _D2R(Degree) ' ******* RITCHIE convert to radian rotation
SINr = SIN(-Degree / 57.2957795131) ' ******* GALLEON
COSr = COS(-Degree / 57.2957795131) ' ******* GALLEON
DO ' cycle through vectors
'Distance = _HYPOT(px(v), py(v)) ' ******* RITCHIE get distance to vector
'NewRadian = _ATAN2(py(v), px(v)) + Rotate ' ******* RITCHIE convert vector to radian then add rotation
'px(v) = COS(NewRadian) * Distance ' ******* RITCHIE convert radian to vector with correct distance
'py(v) = SIN(NewRadian) * Distance ' ******* RITCHIE
x = px(v) * COSr + SINr * py(v) ' ******* GALLEON
y = py(v) * COSr - px(v) * SINr ' ******* GALLEON
px(v) = x ' ******* GALLEON
py(v) = y ' ******* GALLEON
IF px(v) < Left THEN Left = px(v) ' keep track of new image size
IF px(v) > Right THEN Right = px(v)
IF py(v) < Top THEN Top = py(v)
IF py(v) > Bottom THEN Bottom = py(v)
v = v + 1 ' increment vector counter
LOOP UNTIL v = 4 ' leave when all vectors processed
RotWidth = Right - Left + 1 ' calculate width of rotated image
RotHeight = Bottom - Top + 1 ' calculate height of rotated image
Xoffset = RotWidth / 2 ' place (0,0) in upper left corner of rotated image
Yoffset = RotHeight / 2
v = 0 ' reset corner counter
DO ' cycle through rotated image coordinates
px(v) = px(v) + Xoffset ' move image coordinates so (0,0) in upper left corner
py(v) = py(v) + Yoffset
v = v + 1 ' increment corner counter
LOOP UNTIL v = 4 ' leave when all four corners of image moved
OutImg = _NEWIMAGE(RotWidth, RotHeight, 32) ' create rotated image canvas
_MAPTRIANGLE (0, 0)-(0, _HEIGHT(InImg) - 1)-(_WIDTH(InImg) - 1, _HEIGHT(InImg) - 1), InImg TO(px(0), py(0))-(px(1), py(1))-(px(2), py(2)), OutImg
_MAPTRIANGLE (0, 0)-(_WIDTH(InImg) - 1, 0)-(_WIDTH(InImg) - 1, _HEIGHT(InImg) - 1), InImg TO(px(0), py(0))-(px(3), py(3))-(px(2), py(2)), OutImg
END SUB
RE: What am I missing here? - luke - 09-08-2022
The 57.3 division factor is 180 / pi, a change from degrees to radians.
Rob's code first defines four characteristic points, the corners. If we can work out how to transform them, we can just redraw the triangles that connect them and the whole image will be rotated.
Each of the four points is a 2D (x, y) vector. It is a well known result from linear algebra that a vector can be rotated through an angle a by multiplying it by the rotation matrix:
Code: (Select All) .- - - - - -
| cos(a) -sin(a) | |x| = |x cos(a) - y sin(a) | = |x'|
| sin(a) cos(a) | |y| |x sin(a) + y cos(a) | |y'|
.- - - - - -
So we have x' = x cos(a) - y sin(a), y' = x sin(a) + y cos(a).
Rob's SINr and COSr are sin(a) and cos(a) respectively. These lines:
Code: (Select All) x = px(v) * COSr + SINr * py(v) ' ******* GALLEON
y = py(v) * COSr - px(v) * SINr ' ******* GALLEON
then apply the formulas above with the caveat that x & y are swapped. This is just a matter of convention, the original matrix could have been written differently (I think it's just a matter of which way your rotation is going).
RE: What am I missing here? - TerryRitchie - 09-08-2022
(09-08-2022, 01:40 PM)luke Wrote: The 57.3 division factor is 180 / pi, a change from degrees to radians.
Rob's code first defines four characteristic points, the corners. If we can work out how to transform them, we can just redraw the triangles that connect them and the whole image will be rotated.
Each of the four points is a 2D (x, y) vector. It is a well known result from linear algebra that a vector can be rotated through an angle a by multiplying it by the rotation matrix:
Code: (Select All) .- - - - - -
| cos(a) -sin(a) | |x| = |x cos(a) - y sin(a) | = |x'|
| sin(a) cos(a) | |y| |x sin(a) + y cos(a) | |y'|
.- - - - - -
So we have x' = x cos(a) - y sin(a), y' = x sin(a) + y cos(a).
Rob's SINr and COSr are sin(a) and cos(a) respectively. These lines:
Code: (Select All) x = px(v) * COSr + SINr * py(v) ' ******* GALLEON
y = py(v) * COSr - px(v) * SINr ' ******* GALLEON
then apply the formulas above with the caveat that x & y are swapped. This is just a matter of convention, the original matrix could have been written differently (I think it's just a matter of which way your rotation is going).
It's a matrix then. Thank you for pointing that out. I just read the Wikipedia page on the 2D rotation matrix and now it makes a bit more sense. Yes, you're right about rotation direction. Use a positive angle for counter-clockwise rotation and a negative angle (as Galleon did) for clockwise rotation according to the Wikipedia article.
So it would appear I need to read up on matrix math now and play around with this matrix to get a better understanding.
Thanks for the explanation Luke.
RE: What am I missing here? - bplus - 09-08-2022
Here image rotation from the standpoint of point rotations, might be easier to understand?
Code: (Select All) _Title "Rotate Point" 'b+ 2022-09-08
Screen _NewImage(800, 600, 32)
rotateCenterX = _Width / 2
rotateCenterY = _Height / 2
' test rotate point about the middle of the screen start point at 300, 100 and rotate 1/12 circle = 30 degree or pi/12 radians
x = 300: y = 100
Circle (x, y), 1, &HFF0000FF
For a = _Pi(2 / 12) To _Pi(2 - 1 / 6) Step _Pi(1 / 6)
Cls
Print "rotated"; _R2D(a); " degrees, press any to continue demo"
Circle (x, y), 1, &HFF0000FF
drawRotate x, y, rotateCenterX, rotateCenterY, a
Sleep
Next
' OK make a rectangle image 50x100
Line (0, 0)-(49, 99), &HFFFFFF00, BF
rect& = _NewImage(50, 100, 32)
_PutImage , 0, rect&, (0, 0)-(49, 99)
_PutImage (_Width - _Width(rect&), 0), rect&, 0
_PutImage (0, _Height - _Height(rect&)), rect&, 0
_PutImage (_Width - _Width(rect&), _Height - _Height(rect&)), rect&, 0
For a = 0 To _Pi(2) - .001 Step _Pi(1 / 6)
Cls
rotateImage rotateCenterX, rotateCenterY, rect&, a, 0
Print "Rotated"; _R2D(a)
Sleep
Next
Sub rotateImage (aboutX, aboutY, Img&, radianRot, dest&)
Dim px(1 To 4), py(1 To 4), rotx(1 To 4), roty(1 To 4)
w = _Width(Img&) / 2 ' these are 1/2 widths and heights for faster calc
h = _Height(Img&) / 2
px(1) = aboutX - w: py(1) = aboutY - h
px(2) = aboutX - w: py(2) = aboutY + h
px(3) = aboutX + w: py(3) = aboutY + h
px(4) = aboutX + w: py(4) = aboutY - h
For i = 1 To 4
Rotate px(i), py(i), aboutX, aboutY, radianRot, rotx(i), roty(i)
Next
w = _Width(Img&) - 1 'for source coordinates
h = _Height(Img&) - 1
_MapTriangle (0, 0)-(0, h)-(w, h), Img& To(rotx(1), roty(1))-(rotx(2), roty(2))-(rotx(3), roty(3)), dest&
_MapTriangle (0, 0)-(w, 0)-(w, h), Img& To(rotx(1), roty(1))-(rotx(4), roty(4))-(rotx(3), roty(3)), dest&
End Sub
Sub drawRotate (x, y, aboutX, aboutY, rotRA)
radius = _Hypot(y - aboutY, x - aboutX)
angle = _Atan2(y - aboutY, x - aboutX)
rotA = angle + rotRA
'rotated x, y point
rotX = aboutX + radius * Cos(rotA)
rotY = aboutY + radius * Sin(rotA)
Circle (rotX, rotY), 1, &HFFFFFF00
End Sub
' rotate point x,y about point aboutX, aboutY, Radian Angles, (output) rotX, (output) rotY
Sub Rotate (x, y, aboutX, aboutY, rotRA, rotX, rotY)
radius = _Hypot(y - aboutY, x - aboutX)
angle = _Atan2(y - aboutY, x - aboutX)
rotA = angle + rotRA
'rotated x, y point
rotX = aboutX + radius * Cos(rotA)
rotY = aboutY + radius * Sin(rotA)
End Sub
Edit: fixed error in the 2 _MapTriangle lines.
RE: What am I missing here? - bplus - 09-08-2022
OK fixed.
We were just 2 steps away from Classic RotoZoom. Incorporate the RotatePoint code into the RotateAndZoomImage sub and add a scale for the Destination points to grow or shrink the radius:
Code: (Select All) _Title "Rotate and Zoom from Point perspective" 'b+ 2022-09-08
Screen _NewImage(800, 600, 32)
rotateCenterX = _Width / 2
rotateCenterY = _Height / 2
' OK make a rectangle image 50x100 and rotate it around the center of the screen in 30 degree increments
Line (0, 0)-(49, 99), &HFFFFFF00, BF
rect& = _NewImage(50, 100, 32)
_PutImage , 0, rect&, (0, 0)-(49, 99)
scale = .5
For a = 0 To _Pi(2) - .001 Step _Pi(1 / 12)
Cls
RotateAndZoomImage rotateCenterX, rotateCenterY, rect&, scale, a, 0
Print "Rotated"; _R2D(a); " scaled at:"; scale
scale = scale + .1
Sleep
Next
Sub RotateAndZoomImage (aboutX As Long, aboutY As Long, Img&, xyScale, radianRot, dest&)
Dim px(1 To 4), py(1 To 4), rotx(1 To 4), roty(1 To 4)
w = _Width(Img&) / 2 ' these are 1/2 widths and heights for faster calc of destination coordinates
h = _Height(Img&) / 2
px(1) = aboutX - w: py(1) = aboutY - h
px(2) = aboutX - w: py(2) = aboutY + h
px(3) = aboutX + w: py(3) = aboutY + h
px(4) = aboutX + w: py(4) = aboutY - h
radius = _Hypot(py(1) - aboutY, px(1) - aboutX) ' radius is all the same
For i = 1 To 4
angle = _Atan2(py(i) - aboutY, px(i) - aboutX)
rotA = angle + radianRot
'rotated x, y point
rotx(i) = aboutX + xyScale * radius * Cos(rotA)
roty(i) = aboutY + xyScale * radius * Sin(rotA)
Next
w = _Width(Img&) - 1 'for source coordinates
h = _Height(Img&) - 1
_MapTriangle (0, 0)-(0, h)-(w, h), Img& To(rotx(1), roty(1))-(rotx(2), roty(2))-(rotx(3), roty(3)), dest&
_MapTriangle (0, 0)-(w, 0)-(w, h), Img& To(rotx(1), roty(1))-(rotx(4), roty(4))-(rotx(3), roty(3)), dest&
End Sub
RE: What am I missing here? - bplus - 09-09-2022
Here I write up the Alternate RotateAndZoomImage sub with more comments and test it out with demo:
Feature Sub:
Code: (Select All) Sub RotateAndZoomImage (aboutX, aboutY, Img&, xyScale, radianRot, dest&)
Dim px(1 To 4), py(1 To 4), rotx(1 To 4), roty(1 To 4)
' aboutX, aboutY is the centerPoint for the destination image
' Img& is image handle from drawn _newImage or _LoadImage
' xyScale is multiplier to shrink or grow image projection from Map Triangle
' RadianRot is the angle in radians to turn the image from 0 rotation clockwise when positive
' dest& is where you want the projected image drawn 0 is the screen
' This part sets up our Destination points for _MapTriangle, 4 points around a Center X, Y point
w = _Width(Img&) / 2 ' these are 1/2 widths and heights for faster calc of destination coordinates
h = _Height(Img&) / 2
' the 4 points are the central point +- half the width and or height
px(1) = aboutX - w: py(1) = aboutY - h
px(2) = aboutX - w: py(2) = aboutY + h
px(3) = aboutX + w: py(3) = aboutY + h
px(4) = aboutX + w: py(4) = aboutY - h
' the "radius" of 4 points from the center will be same for square or rectangle image
radius = _Hypot(py(1) - aboutY, px(1) - aboutX) ' radius is all the same
' the 4 projection points needs to be rotated
For i = 1 To 4
angle = _Atan2(py(i) - aboutY, px(i) - aboutX) ' the angle the point is before rotation
rotA = angle + radianRot ' add the rotation to angle
'rotated x, y point
rotx(i) = aboutX + xyScale * radius * Cos(rotA)
roty(i) = aboutY + xyScale * radius * Sin(rotA)
Next
' this w, h concerns the Triangle coordinates for the Source _MapTriangle points
w = _Width(Img&) - 1 'for source coordinates
h = _Height(Img&) - 1
_MapTriangle (0, 0)-(0, h)-(w, h), Img& To(rotx(1), roty(1))-(rotx(2), roty(2))-(rotx(3), roty(3)), dest&
_MapTriangle (0, 0)-(w, 0)-(w, h), Img& To(rotx(1), roty(1))-(rotx(4), roty(4))-(rotx(3), roty(3)), dest&
End Sub
Code: (Select All) _Title "Test Alternate RotoZoom Derived From Point Rotation" ' b+ 2022-09-09
Screen _NewImage(600, 350, 32)
' from demo code for Lander
' =========================================== make background snapshot
Color , _RGB32(30, 30, 60)
snapBack& = _NewImage(_Width, _Height, 32)
Cls
DrawTerrain 100, 25, &HFF332211
DrawTerrain 150, 20, &HFF443322
DrawTerrain 200, 15, &HFF554433
DrawTerrain 250, 10, &HFF665544
DrawTerrain 300, 5, &HFF776655
_PutImage , 0, snapBack&
' ========================================== make a spaceship sprite
ship& = _NewImage(61, 31, 32) ' ship is 60 x 30 drawn in top left hand corner
' need black backgrounf for ship
Color , &HFF000000 '= black background
Cls
drawShip 30, 15, &HFF00FF88
_PutImage , 0, ship&, (0, 0)-(61, 31) ' <<<< upper left corner of screen!!!
_ClearColor &HFF000000, ship& ' <<< make the background black of ship transparent
' ============================================= now for test of Alternate RotoZoom
sx = 0 ' from left edge to right and back
dx = 5 ' 270 / 5 = 54 loops to go from start to mid screen with max tilt there at pi(.25)
tilt = 0
dt = _Pi(.25 / 54)
scale = 1
ds = 1 / 54 ' want to double scale at 54 loops
Do
_PutImage , snapBack&, 0 ' back to screen
'rotozoom workes from image center, add 30 for middle of ship 15 add to y keeps ship lower
RotateAndZoomImage sx + 30, 175 + 15, ship&, scale, tilt, 0 ' ship to screen at destination x, y
' update x, scale and tilt
sx = sx + dx
If sx > _Width - 60 Then
sx = _Width - 60: dx = -dx
scale = 1: tilt = 0
ElseIf sx < 0 Then
sx = 0: dx = -dx
scale = 1: tilt = 0
ElseIf Abs(sx - 270) < .01 And dx > 0 Then
dt = -dt: ds = -ds
ElseIf Abs(sx - 270) < .01 And dx < 0 Then
dt = -dt: ds = -ds
End If
scale = scale + ds
tilt = tilt + dt
Locate 1, 1
Print "Press escape to quit..."
_Display 'no flicker
_Limit 20 ' max 20 loops a second
Loop Until _KeyDown(27)
Sub drawShip (x, y, colr As _Unsigned Long) 'shipType collisions same as circle x, y radius = 30
Static ls
Dim light As Long, r As Long, g As Long, b As Long
r = _Red32(colr): g = _Green32(colr): b = _Blue32(colr)
fellipse x, y, 6, 15, _RGB32(r, g - 120, b - 100)
fellipse x, y, 18, 11, _RGB32(r, g - 60, b - 50)
fellipse x, y, 30, 7, _RGB32(r, g, b)
For light = 0 To 5
fcirc x - 30 + 11 * light + ls, y, 1, _RGB32(ls * 50, ls * 50, ls * 50)
Next
ls = ls + 1
If ls > 5 Then ls = 0
End Sub
' ======== helper subs for drawShip that you can use for other things specially fcirc = fill_circle x, y, radius, color
Sub fellipse (CX As Long, CY As Long, xr As Long, yr As Long, C As _Unsigned Long)
If xr = 0 Or yr = 0 Then Exit Sub
Dim h2 As _Integer64, w2 As _Integer64, h2w2 As _Integer64
Dim x As Long, y As Long
w2 = xr * xr: h2 = yr * yr: h2w2 = h2 * w2
Line (CX - xr, CY)-(CX + xr, CY), C, BF
Do While y < yr
y = y + 1
x = Sqr((h2w2 - y * y * w2) \ h2)
Line (CX - x, CY + y)-(CX + x, CY + y), C, BF
Line (CX - x, CY - y)-(CX + x, CY - y), C, BF
Loop
End Sub
Sub fcirc (x As Long, y As Long, R As Long, C As _Unsigned Long) 'vince version fill circle x, y, radius, color
Dim x0 As Long, y0 As Long, e As Long
x0 = R: y0 = 0: e = 0
Do While y0 < x0
If e <= 0 Then
y0 = y0 + 1
Line (x - x0, y + y0)-(x + x0, y + y0), C, BF
Line (x - x0, y - y0)-(x + x0, y - y0), C, BF
e = e + 2 * y0
Else
Line (x - y0, y - x0)-(x + y0, y - x0), C, BF
Line (x - y0, y + x0)-(x + y0, y + x0), C, BF
x0 = x0 - 1: e = e - 2 * x0
End If
Loop
Line (x - R, y)-(x + R, y), C, BF
End Sub
Sub DrawTerrain (h, modN, c As _Unsigned Long) ' modN for ruggedness the higher the less smooth
For x = 0 To _Width
If x Mod modN = 0 Then ' adjust mod number for ruggedness the higher the number the more jagged
If h < 350 - modN And h > 50 + modN Then
dy = Rnd * 20 - 10
ElseIf h >= 350 - modN Then
dy = Rnd * -10
ElseIf h <= 50 + modN Then
dy = Rnd * 10
End If
End If
h = h + .1 * dy
Line (x, _Height)-(x, h), c
Next
End Sub
Sub RotateAndZoomImage (aboutX, aboutY, Img&, xyScale, radianRot, dest&)
Dim px(1 To 4), py(1 To 4), rotx(1 To 4), roty(1 To 4)
' aboutX, aboutY is the centerPoint for the destination image
' Img& is image handle from drawn _newImage or _LoadImage
' xyScale is multiplier to shrink or grow image projection from Map Triangle
' RadianRot is the angle in radians to turn the image from 0 rotation clockwise when positive
' dest& is where you want the projected image drawn 0 is the screen
' This part sets up our Destination points for _MapTriangle, 4 points around a Center X, Y point
w = _Width(Img&) / 2 ' these are 1/2 widths and heights for faster calc of destination coordinates
h = _Height(Img&) / 2
' the 4 points are the central point +- half the width and or height
px(1) = aboutX - w: py(1) = aboutY - h
px(2) = aboutX - w: py(2) = aboutY + h
px(3) = aboutX + w: py(3) = aboutY + h
px(4) = aboutX + w: py(4) = aboutY - h
' the "radius" of 4 points from the center will be same for square or rectangle image
radius = _Hypot(py(1) - aboutY, px(1) - aboutX) ' radius is all the same
' the 4 projection points needs to be rotated
For i = 1 To 4
angle = _Atan2(py(i) - aboutY, px(i) - aboutX) ' the angle the point is before rotation
rotA = angle + radianRot ' add the rotation to angle
'rotated x, y point
rotx(i) = aboutX + xyScale * radius * Cos(rotA)
roty(i) = aboutY + xyScale * radius * Sin(rotA)
Next
' this w, h concerns the Triangle coordinates for the Source _MapTriangle Points
w = _Width(Img&) - 1 'for source coordinates
h = _Height(Img&) - 1
_MapTriangle (0, 0)-(0, h)-(w, h), Img& To(rotx(1), roty(1))-(rotx(2), roty(2))-(rotx(3), roty(3)), dest&
_MapTriangle (0, 0)-(w, 0)-(w, h), Img& To(rotx(1), roty(1))-(rotx(4), roty(4))-(rotx(3), roty(3)), dest&
End Sub
So important to understand Rotozoom, so really cool to be able to derive it on your own with a little guidance from Galleon's example.
EDIT to fix spelling
|