dcromley
23-05-2009, 22:08
Hello,
Ever since my Easter Egg, I haven't been satisfied with rotating an object from the keyboard. I may have missed an example of what I want, but all of the examples seem to get into unnatural movement after being rotated 90 degrees. Including my egg -- it was worse -- it was bizarre.
I could have asked Petr a long time ago, but I wanted to look into it myself. It took a long time. Very enjoyable. Not very efficient, but enjoyable.
So my question is -- don't you agree that rotating the object using the arrow keys is more natural on the right than on the left? Especially after you, for example, rotate down for 90 degrees, and then rotate right-left.
Like I said, I may have missed an example that has this natural rotation, but I didn't see it. The right side certainly takes more code, but works great.
If you agree, I'd suggest simplifying this script (right half only) into a template for somebody wanting to do this. (Petr, I appreciate your tips (e.g. APP_PATH)).
Uses "Console", "TBGL", "Math"
Type Quat ' Quaternion
t as string*4 ' "Quat" typecheck
W as double ' cos of Angle/2
X as double ' x
Y as double ' y
Z as double ' z
End Type
global hWnd as long
global gdx, gdy, gdz as single ' rotation deltas (2 routines)
TBGLMain()
Printl "End": beep
waitkey
Sub TBGLMain()
hWnd = TBGL_CreateWindowEX( _
"Arrow keys, PgUp, PgDn to rotate; Numpad 246789 to move camera; ESC to quit", _
800, 400, 32, %TBGL_WS_WINDOWED or %TBGL_WS_CLOSEBOX)
local hFont AS long = TBGL_FontHandle("Courier New", 9)
TBGL_BuildFont(hFont) ' for print
TBGL_ShowWindow
TBGL_ResetKeyState()
'----This does the loop
TBGL_BindPeriodicFunction( hWnd, "TBGLLoop", 10 )
TBGL_ProcessPeriodicFunction(hWnd)
'----End TBGL loop
TBGL_DestroyWindow
End Sub
Sub TBGLLoop()
static Sw1, cx, cy, cz as single ' camera pos
if Sw1 = 0 then ' set static vars
Sw1 = 1
cx = 0: cy = 0: cz = 6 ' camera pos
end if
tbgl_ClearFrame
' camera can be moved
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad6) Then cx += .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad4) Then cx -= .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad8) Then cy += .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad2) Then cy -= .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad9) Then cz += .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad7) Then cz -= .05
TBGL_Camera cx, cy, cz, cx, cy, 0 ' look in -z direction
' these value are used in both routines, so gdxyz have to be global
gdx = 0: gdy = 0: gdz = 0
IF TBGL_GetWindowKeyState( hWnd, %VK_Down) Then gdx = +1
IF TBGL_GetWindowKeyState( hWnd, %VK_Up) Then gdx = -1
IF TBGL_GetWindowKeyState( hWnd, %VK_Right) Then gdy = +1
IF TBGL_GetWindowKeyState( hWnd, %VK_Left) Then gdy = -1
IF TBGL_GetWindowKeyState( hWnd, %VK_PgUp) Then gdz = +1
IF TBGL_GetWindowKeyState( hWnd, %VK_PgDn) Then gdz = -1
TBGL_PushMatrix
TBGL_Translate -2,0,0 ' left side - simple x, y, z rotation
TBGL_Rotate 18.435,0,1,0 ' face camera
PreRotation() ' fixed items
DoRotationLeft() ' do the rotation
PostRotation() ' rotating items
TBGL_PopMatrix
TBGL_PushMatrix
TBGL_Translate +2,0,0 ' right side - quaternion rotation
TBGL_Rotate -18.435,0,1,0 ' face camera
PreRotation() ' fixed items
DoRotationRight() ' do the rotation
PostRotation() ' rotating items
TBGL_PopMatrix
tbgl_DrawFrame
If TBGL_GetWindowKeyState(hWnd, %VK_ESCAPE) Then TBGL_UnBindPeriodicFunction( hWnd )
End Sub
Sub PreRotation() ' plot the fixed axes
TBGL_Color 255,160,255 ' x-axis cyan
PlotLine(0,0,0, 1.25,0,0)
PlotCylinder(1.25,0,0, 1,0,0, .05,.01,.5)
TBGL_Color 255,255,160 ' y-axis yellow
PlotLine(0,0,0, 0,1.25,0)
PlotCylinder(0,1.25,0, 0,1,0, .05,.01,.5)
TBGL_Color 160,255,255 ' z-axis magenta
PlotLine(0,0,0, 0,0,1.25)
PlotCylinder(0,0,1.25, 0,0,1, .05,.01,.5)
End Sub
Sub PostRotation() ' plot the rotating axes
TBGL_Color 255,0,255 ' x-axis cyan
PlotCylinder(-1,0,0, 1,0,0, .05,.01,2.)
TBGL_Color 255,255,0 ' y-axis yellow
PlotCylinder(0,-1,0, 0,1,0, .05,.01,2.)
TBGL_Color 0,255,255 ' z-axis magenta
PlotCylinder(0,0,-1, 0,0,1, .05,.01,2.)
end Sub
Sub DoRotationLeft() ' simple rotation in x, y, z
Static AngleX, AngleY, AngleZ as integer
AngleX = mod(AngleX + gdx + 360, 360) ' change angles
AngleY = mod(AngleY + gdy + 360, 360)
AngleZ = mod(AngleZ + gdz + 360, 360)
TBGL_Rotate AngleX, 1, 0, 0
TBGL_Rotate AngleY, 0, 1, 0
TBGL_Rotate AngleZ, 0, 0, 1
End Sub
Sub DoRotationRight() ' better? using quaternions
Static Sw1 as integer, QMain, QRotX, QRotY, QRotZ as Quat
local Cosa, Sina, Ang as double
if Sw1 = 0 then ' set up static variables
Sw1 = 1
Cosa = cos(pi/360.): Sina = sin(pi/360.) ' half-angles
QMain = QLoadWXYZ(1, 0, 0, 0)
QRotX = QLoadWXYZ(Cosa, Sina, 0, 0)
QRotY = QLoadWXYZ(Cosa, 0, Sina, 0)
QRotZ = QLoadWXYZ(Cosa, 0, 0, Sina)
end if
if gdx <> 0 then QMain = QMult(QRotX, QMain, gdx) ' sign only
if gdy <> 0 then QMain = QMult(QRotY, QMain, gdy)
if gdz <> 0 then QMain = QMult(QRotZ, QMain, gdz)
Ang = atan2(sqr(1.-QMain.W^2), QMain.W) * 2
TBGL_Rotate Ang, QMain.X, QMain.Y, QMain.Z
End Sub
Function QLoadWXYZ(qW as double, qX as double, qY as double, qZ as double) as string
local q as Quat
q.t = "Quat" ' for typecheck
q.W = qW: q.X = qX: q.Y = qY: q.Z = qZ
Function = q
End Function
Function QMult(sq1 as string, sq2 as string, iSign as integer) as string ' quaternion multiplication
' q1 and q2 had better be type "Quat"s
local q, q1, q2 as Quat
q.t = "Quat": q1 = sq1: q2 = sq2
if q1.t <> "Quat" then msgbox 0, "TypeErr QM1": stop
if q2.t <> "Quat" then msgbox 0, "TypeErr QM2": stop
q.W = q1.W*q2.W - (q1.X*q2.X + q1.Y*q2.Y + q1.Z*q2.Z) * iSign
q.X = q1.W*q2.X + (q1.X*q2.W + q1.Y*q2.Z - q1.Z*q2.Y) * iSign
q.Y = q1.W*q2.Y + (q1.Y*q2.W + q1.Z*q2.X - q1.X*q2.Z) * iSign
q.Z = q1.W*q2.Z + (q1.Z*q2.W + q1.X*q2.Y - q1.Y*q2.X) * iSign
Function = q
End Function
Sub PlotLine(x1 as single, y1 as single, z1 as single, _
optional x2 as single = -999, y2 as single, z2 as single)
static x3, y3, z3 as single
TBGL_Beginpoly %GL_lines
if x2 = -999 then
TBGL_Vertex x3, y3, z3
TBGL_Vertex x1, y1, z1
x3 = x1: y3 = y1: z3 = z1
else
TBGL_Vertex x1, y1, z1
TBGL_Vertex x2, y2, z2
x3 = x2: y3 = y2: z3 = z2
end if
TBGL_Endpoly
End Sub
Sub PlotCylinder(x as double, y as double, z as double, _
vx as double, vy as double, vz as double, _
r1 as double, r2 as double, h as double)
' x, y, z is bottom of cyl
' vx, vy, vz is direction of cyl
' r1 is bottom radius, r2 is top
' h is height
local Ang as double
TBGL_Pushmatrix
TBGL_Translate x, y, z ' to bottom
Ang = atan2(vx, vz) ' rotate (y) into yz plane
TBGL_Rotate Ang, 0, 1, 0
Ang = atan2(sqr(vx^2+vz^2), vy)
TBGL_Rotate Ang, 1, 0, 0 ' rotate (x) into z axis
TBGL_Cylinder r1, r2, h
TBGL_Popmatrix
End Sub
Ever since my Easter Egg, I haven't been satisfied with rotating an object from the keyboard. I may have missed an example of what I want, but all of the examples seem to get into unnatural movement after being rotated 90 degrees. Including my egg -- it was worse -- it was bizarre.
I could have asked Petr a long time ago, but I wanted to look into it myself. It took a long time. Very enjoyable. Not very efficient, but enjoyable.
So my question is -- don't you agree that rotating the object using the arrow keys is more natural on the right than on the left? Especially after you, for example, rotate down for 90 degrees, and then rotate right-left.
Like I said, I may have missed an example that has this natural rotation, but I didn't see it. The right side certainly takes more code, but works great.
If you agree, I'd suggest simplifying this script (right half only) into a template for somebody wanting to do this. (Petr, I appreciate your tips (e.g. APP_PATH)).
Uses "Console", "TBGL", "Math"
Type Quat ' Quaternion
t as string*4 ' "Quat" typecheck
W as double ' cos of Angle/2
X as double ' x
Y as double ' y
Z as double ' z
End Type
global hWnd as long
global gdx, gdy, gdz as single ' rotation deltas (2 routines)
TBGLMain()
Printl "End": beep
waitkey
Sub TBGLMain()
hWnd = TBGL_CreateWindowEX( _
"Arrow keys, PgUp, PgDn to rotate; Numpad 246789 to move camera; ESC to quit", _
800, 400, 32, %TBGL_WS_WINDOWED or %TBGL_WS_CLOSEBOX)
local hFont AS long = TBGL_FontHandle("Courier New", 9)
TBGL_BuildFont(hFont) ' for print
TBGL_ShowWindow
TBGL_ResetKeyState()
'----This does the loop
TBGL_BindPeriodicFunction( hWnd, "TBGLLoop", 10 )
TBGL_ProcessPeriodicFunction(hWnd)
'----End TBGL loop
TBGL_DestroyWindow
End Sub
Sub TBGLLoop()
static Sw1, cx, cy, cz as single ' camera pos
if Sw1 = 0 then ' set static vars
Sw1 = 1
cx = 0: cy = 0: cz = 6 ' camera pos
end if
tbgl_ClearFrame
' camera can be moved
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad6) Then cx += .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad4) Then cx -= .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad8) Then cy += .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad2) Then cy -= .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad9) Then cz += .05
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad7) Then cz -= .05
TBGL_Camera cx, cy, cz, cx, cy, 0 ' look in -z direction
' these value are used in both routines, so gdxyz have to be global
gdx = 0: gdy = 0: gdz = 0
IF TBGL_GetWindowKeyState( hWnd, %VK_Down) Then gdx = +1
IF TBGL_GetWindowKeyState( hWnd, %VK_Up) Then gdx = -1
IF TBGL_GetWindowKeyState( hWnd, %VK_Right) Then gdy = +1
IF TBGL_GetWindowKeyState( hWnd, %VK_Left) Then gdy = -1
IF TBGL_GetWindowKeyState( hWnd, %VK_PgUp) Then gdz = +1
IF TBGL_GetWindowKeyState( hWnd, %VK_PgDn) Then gdz = -1
TBGL_PushMatrix
TBGL_Translate -2,0,0 ' left side - simple x, y, z rotation
TBGL_Rotate 18.435,0,1,0 ' face camera
PreRotation() ' fixed items
DoRotationLeft() ' do the rotation
PostRotation() ' rotating items
TBGL_PopMatrix
TBGL_PushMatrix
TBGL_Translate +2,0,0 ' right side - quaternion rotation
TBGL_Rotate -18.435,0,1,0 ' face camera
PreRotation() ' fixed items
DoRotationRight() ' do the rotation
PostRotation() ' rotating items
TBGL_PopMatrix
tbgl_DrawFrame
If TBGL_GetWindowKeyState(hWnd, %VK_ESCAPE) Then TBGL_UnBindPeriodicFunction( hWnd )
End Sub
Sub PreRotation() ' plot the fixed axes
TBGL_Color 255,160,255 ' x-axis cyan
PlotLine(0,0,0, 1.25,0,0)
PlotCylinder(1.25,0,0, 1,0,0, .05,.01,.5)
TBGL_Color 255,255,160 ' y-axis yellow
PlotLine(0,0,0, 0,1.25,0)
PlotCylinder(0,1.25,0, 0,1,0, .05,.01,.5)
TBGL_Color 160,255,255 ' z-axis magenta
PlotLine(0,0,0, 0,0,1.25)
PlotCylinder(0,0,1.25, 0,0,1, .05,.01,.5)
End Sub
Sub PostRotation() ' plot the rotating axes
TBGL_Color 255,0,255 ' x-axis cyan
PlotCylinder(-1,0,0, 1,0,0, .05,.01,2.)
TBGL_Color 255,255,0 ' y-axis yellow
PlotCylinder(0,-1,0, 0,1,0, .05,.01,2.)
TBGL_Color 0,255,255 ' z-axis magenta
PlotCylinder(0,0,-1, 0,0,1, .05,.01,2.)
end Sub
Sub DoRotationLeft() ' simple rotation in x, y, z
Static AngleX, AngleY, AngleZ as integer
AngleX = mod(AngleX + gdx + 360, 360) ' change angles
AngleY = mod(AngleY + gdy + 360, 360)
AngleZ = mod(AngleZ + gdz + 360, 360)
TBGL_Rotate AngleX, 1, 0, 0
TBGL_Rotate AngleY, 0, 1, 0
TBGL_Rotate AngleZ, 0, 0, 1
End Sub
Sub DoRotationRight() ' better? using quaternions
Static Sw1 as integer, QMain, QRotX, QRotY, QRotZ as Quat
local Cosa, Sina, Ang as double
if Sw1 = 0 then ' set up static variables
Sw1 = 1
Cosa = cos(pi/360.): Sina = sin(pi/360.) ' half-angles
QMain = QLoadWXYZ(1, 0, 0, 0)
QRotX = QLoadWXYZ(Cosa, Sina, 0, 0)
QRotY = QLoadWXYZ(Cosa, 0, Sina, 0)
QRotZ = QLoadWXYZ(Cosa, 0, 0, Sina)
end if
if gdx <> 0 then QMain = QMult(QRotX, QMain, gdx) ' sign only
if gdy <> 0 then QMain = QMult(QRotY, QMain, gdy)
if gdz <> 0 then QMain = QMult(QRotZ, QMain, gdz)
Ang = atan2(sqr(1.-QMain.W^2), QMain.W) * 2
TBGL_Rotate Ang, QMain.X, QMain.Y, QMain.Z
End Sub
Function QLoadWXYZ(qW as double, qX as double, qY as double, qZ as double) as string
local q as Quat
q.t = "Quat" ' for typecheck
q.W = qW: q.X = qX: q.Y = qY: q.Z = qZ
Function = q
End Function
Function QMult(sq1 as string, sq2 as string, iSign as integer) as string ' quaternion multiplication
' q1 and q2 had better be type "Quat"s
local q, q1, q2 as Quat
q.t = "Quat": q1 = sq1: q2 = sq2
if q1.t <> "Quat" then msgbox 0, "TypeErr QM1": stop
if q2.t <> "Quat" then msgbox 0, "TypeErr QM2": stop
q.W = q1.W*q2.W - (q1.X*q2.X + q1.Y*q2.Y + q1.Z*q2.Z) * iSign
q.X = q1.W*q2.X + (q1.X*q2.W + q1.Y*q2.Z - q1.Z*q2.Y) * iSign
q.Y = q1.W*q2.Y + (q1.Y*q2.W + q1.Z*q2.X - q1.X*q2.Z) * iSign
q.Z = q1.W*q2.Z + (q1.Z*q2.W + q1.X*q2.Y - q1.Y*q2.X) * iSign
Function = q
End Function
Sub PlotLine(x1 as single, y1 as single, z1 as single, _
optional x2 as single = -999, y2 as single, z2 as single)
static x3, y3, z3 as single
TBGL_Beginpoly %GL_lines
if x2 = -999 then
TBGL_Vertex x3, y3, z3
TBGL_Vertex x1, y1, z1
x3 = x1: y3 = y1: z3 = z1
else
TBGL_Vertex x1, y1, z1
TBGL_Vertex x2, y2, z2
x3 = x2: y3 = y2: z3 = z2
end if
TBGL_Endpoly
End Sub
Sub PlotCylinder(x as double, y as double, z as double, _
vx as double, vy as double, vz as double, _
r1 as double, r2 as double, h as double)
' x, y, z is bottom of cyl
' vx, vy, vz is direction of cyl
' r1 is bottom radius, r2 is top
' h is height
local Ang as double
TBGL_Pushmatrix
TBGL_Translate x, y, z ' to bottom
Ang = atan2(vx, vz) ' rotate (y) into yz plane
TBGL_Rotate Ang, 0, 1, 0
Ang = atan2(sqr(vx^2+vz^2), vy)
TBGL_Rotate Ang, 1, 0, 0 ' rotate (x) into z axis
TBGL_Cylinder r1, r2, h
TBGL_Popmatrix
End Sub