PDA

View Full Version : TBGL - Rotating objects - TBGL_Rotate



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

Petr Schreiber
23-05-2009, 22:33
Thanks for this code,

I will study it deepely for sure.

I must say I am also not very happy with default OpenGL rotations ( wrapped by TBGL_Rotate, TBGL_RotateXYZ ), as they work with global axes, always.

You maybe observed TBGL custom made entity system allows rotation around local axes which is in my opinion very convenient.

But these quaternions always fascinated me, I must admit I don't think I really ever understood them properly, so I am happy you provided code I can study now. Very much appreciated! :eusaclap:


Petr

P.S. Use of TBGL_BindPeriodicFunction, hmm 8)

kryton9
24-05-2009, 04:56
Nice example code, thanks.

dcromley
24-05-2009, 06:18
Thanks,

I forgot about TBGL_RotateXYZ.

I have seen your "entity" examples, but haven't done one.

I don't claim to understand quaternions much at all, but yes, they are fascinating. Way back in 1843 (October 16th) Sir William Rowan Hamilton had an epiphany

http://www.hamilton2005.ie/quaternions.html

I'm still playing with them, so maybe I'll have one (an epiphany) also.

dcromley
24-05-2009, 13:06
A few things:

I forgot to rave about Petr's TBGL module and
psch.thinbasic.com (http://psch.thinbasic.com)
and all the examples and stuff. It's another bottomless pit of good stuff. Another example of you guys being "un-human" (beyond human in a good sense). I can't believe that a couple of months ago I was using QBASIC for my quick and dirty graphics work. Thanks

I don't understand all I know about quaternions, but I do know that, like a rotation matrix, it has to have a "length" of 1. And if, due to even double precision roundoff errors, it starts getting away from 1, it will grow (or shrink) catastrophically. So should one "normalize" the quaternion every once in a while? Every multiplication? If so, one could do single precision.

Along that line, my script should add the following in the QMult routine:


' after this line
if q2.t <> "Quat" then msgbox 0, "TypeErr QM2": stop
' add this line
if abs(iSign) <> 1 then Msgbox 0, "iSign must be -1 or 1": stop

to prevent changing the script incorrectly. QRotX, for example, is built to be a 1 degree rotation. To make a quaternion of "Deg" degrees instead of 1 degree:


QRot = QLoadWXYZ(cos(Deg*pi/360), sin(Deg*pi/360), 0 ,0)

Yes, it's 360 instead of 180 because quaternions use half-angles.

It's 5am here. I love thinking about this stuff.

Petr Schreiber
24-05-2009, 19:40
Thanks for the nice words and for further elaboration regarding the unit length!

When double precision is not enough, extended ( EXT / EXTENDED ) precision can be of use. But your idea with renormalization sounds good to me, and is probably more clean solution.

primo
02-06-2016, 21:25
in the dcromley demo:
IF TBGL_GetWindowKeyState( hWnd, %VK_Numpad Then cy += .05

%VK_Numpad8) changed to %VK_Numpad(smiley).
the example deserve to be inside code tags. i think this demo is more demonstrative than the example inside thinbasic because it is simpler
i am struggling with this topic and downloading all kinds of articles.
hope to have some insight into the topic.
Edit: the above example left thinbasic.exe in memory when exit, changing it like TBGL_Quaternion.tbasic will repair the issue,
dcromley demo again , commenting a few lines so when exit it will not keep thinbasic.exe in memory

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
'End

'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

Billbo
03-06-2016, 14:15
I forget how to copy the code on a page. I had to click and drag. I could not find anything about it in the FAQ.

Bill

primo
03-06-2016, 15:29
Billbo, if the code syntax is colored (JavaScript code syntax highlighter) and the code lines is numbered, then double click (or triple click) on an empty place in the code area you will see that all the code is highlighted, then copy it.
9610

Billbo
04-06-2016, 09:10
primo,

That works. But here's the weird thing. On my laptop, the lines are not highlighted blue. All the code is put together into on big paragraph, no line breaks, and highlighted blue. See attached screenshot. But when I paste into my editor, it comes out okay. Now, this is using IE. It does not do this in firefox; it highlights as your sceenshot.

Bill