QB64 Phoenix Edition
Quaternion Rotation - Printable Version

+- QB64 Phoenix Edition (https://staging.qb64phoenix.com)
+-- Forum: QB64 Rising (https://staging.qb64phoenix.com/forumdisplay.php?fid=1)
+--- Forum: Code and Stuff (https://staging.qb64phoenix.com/forumdisplay.php?fid=3)
+---- Forum: Programs (https://staging.qb64phoenix.com/forumdisplay.php?fid=7)
+---- Thread: Quaternion Rotation (/showthread.php?tid=541)



Quaternion Rotation - dcromley - 06-11-2022

[Image: z2.jpg]

[ image changed, minor program improvements ] This comes from a long fascination with quaternions.  I wanted to see them work. 

I need to get into OpenGL/QB64.  I have gone through the great tutorials of Ashish ( https://ashishkingdom.github.io/OpenGL-Tutorials/intro/ ) but he hasn't gotten to rotations.  I was surprised that looking at the QB64 Wiki, that there are 7 _gl statements with "matrix" but none with "quaternion".  I assume that means that OpenGL does not use quaternions?

Lord Kelvin: "Although beautiful and of ingenious origin, they have been a curse on anyone who has come into contact with them in any way."
But I had a good time.

I hope the output is somewhat obvious: there are 5 points in the figure which are rotated using the quaternion.  I added the extraction of the Euler Angles at the bottom. 

The program is intended to work, not to be fast.  Still, I was surprised that the framerate is well above 100/sec.  A credit to QB64.

Code: (Select All)
_Title "Quaternion Rotation" ' dcromley
Option _Explicit
DefSng A-Z: DefLng I-N: DefStr S
Const TRUE = -1, FALSE = 0
Dim Shared mx, my, m1Clk, m1Rpt, m1Dn, m1End, m2Clk, m2Dn ' for MouseCk
Dim Shared Img1, Img2
Img1 = _NewImage(1024, 768, 256)
Img2 = _NewImage(1024, 768, 256)
_Dest Img2: Color 0, 15: Cls
_Dest Img1: Color 0, 15: Cls

' == MAIN start ==

Type type4f ' 4 floats for quaternions, points, triangles
  w As Single ' 0 for points; color for triangles
  x As Single ' pt1 for triangles
  y As Single ' pt2 for triangles
  z As Single ' pt3 for triangles
End Type

Const x0 = 384, y0 = 384, kxy = 200, z0 = 200 ' center, scale
Dim Shared As type4f T, aPts(5), aPts0(5), aTris(4), QMain, QSlew, va, vb
Dim Shared nPts, nTris ' # of Points, Triangles
Dim As type4f Qxp, Qxm, Qyp, Qym, Qzp, Qzm ' +- 1 deg Q's
Dim i, x, y, z, s, p1, p2, p3, icolor, nloop
Dim EuAngX, EuAngY, EuAngZ, fcos, fsin ' Euler angles
Dim az(4), ndx(4), time0, iSlew, xa, ya

' -- Points - x,y,z,/ (# ends)
Data 0,1,0,/,-1,-1,-1,/,-1,-1,1,/,1,-1,1,/,1,-1,-1,#
' -- Triangles - p1,p2,p3,color,/ (# ends)
Data 1,2,3,9,/,1,3,4,10,/,1,4,5,12,/,1,5,2,14,#

Do ' -- load aPoints
  nPts = nPts + 1 ' read point x,y,z
  Read aPts0(nPts).x, aPts0(nPts).y, aPts0(nPts).z, s ' s is / or # to end
Loop Until s = "#"
Do ' -- load aTriangles
  nTris = nTris + 1 ' read triangle p1,p2,p3,color
  Read aTris(nTris).x, aTris(nTris).y, aTris(nTris).z, aTris(nTris).w, s
Loop Until s = "#"
' --  load 1 deg quaternions
fcos = Cos(1 * _Pi / 360): fsin = Sin(1 * _Pi / 360) ' half angle
Qxp.w = fcos: Qxp.x = fsin: Qxm.w = fcos: Qxm.x = -fsin
Qyp.w = fcos: Qyp.y = fsin: Qym.w = fcos: Qym.y = -fsin
Qzp.w = fcos: Qzp.z = fsin: Qzm.w = fcos: Qzm.z = -fsin
QMain.w = 1 ' start with null rotation
QSlew = QMain

time0 = Timer - 1 ' prevent div by 0
Do ' ======== MAIN LOOP ========
  nloop = nloop + 1 ' nloop + 1 and print
  If nloop Mod 2 = 1 Then _Dest Img1: screen img1 _
  Else _Dest Img2: Screen Img2 ' swap screens
  Cls ' simplicity, not performance
  Line (768, 0)-(768, 752), _RGB(192, 192, 192) ' vertical
  MouseCk ' get mouse data
  ' -- check controls
  If iBox(110, 12, " Up") Then Qmult Qxm, QMain, QMain ' nudge orientation
  If iBox(106, 13, "Lft") Then Qmult Qym, QMain, QMain
  If iBox(114, 13, "Rht") Then Qmult Qyp, QMain, QMain
  If iBox(110, 14, " Dn") Then Qmult Qxp, QMain, QMain
  If iBox(106, 15, "CCW") Then Qmult Qzp, QMain, QMain
  If iBox(114, 15, " CW") Then Qmult Qzm, QMain, QMain
  ' -- check for mouse dragging (slewing)
  vb.x = mx - x0: vb.y = y0 - my: vb.z = z0 ' new mouse data
  If m1Dn And isIn(mx, 0, 767) And isIn(my, 0, 767) Then ' yes
    QVtoV va, vb, T ' need to smooth out the mouse data
    QSlew.x = QSlew.x * .9 + T.x * .1: QSlew.y = QSlew.y * .9 + T.y * .1: QSlew.z = QSlew.z * .9 + T.z * .1
    Qnorm QSlew ' this is what slews
  Else
    Const k = .99 ' make the slewing decay
    QSlew.x = QSlew.x * k: QSlew.y = QSlew.y * k: QSlew.z = QSlew.z * k
    QSlew.w = Sqr(1 - QSlew.x * QSlew.x - QSlew.y * QSlew.y - QSlew.z * QSlew.z)
  End If
  Qmult QSlew, QMain, QMain ' add slew to QMain
  va = vb ' new becomes old mouse data
  ' -- quaternion to Euler
  EuAngX = _Atan2(2 * QMain.x * QMain.w - 2 * QMain.y * QMain.z, 1 - 2 * QMain.x * QMain.x - 2 * QMain.z * QMain.z)
  EuAngY = _Atan2(2 * QMain.y * QMain.w - 2 * QMain.x * QMain.z, 1 - 2 * QMain.y * QMain.y - 2 * QMain.z * QMain.z)
  EuAngZ = _Asin(2 * QMain.x * QMain.y + 2 * QMain.z * QMain.w)
  ' -- rotate points
  For i = 1 To nPts
    aPts(i) = aPts0(i) ' reset to original
    T = QMain
    T.x = -T.x: T.y = -T.y: T.z = -T.z: ' << Q' >> conjugate
    Qmult aPts(i), T, T '                 << PQ' >>
    Qmult QMain, T, aPts(i) '             << QPQ' >>
  Next i
  For i = 1 To 4 ' get center Z's into a(4)
    T = aTris(i)
    az(i) = aPts(T.x).z + aPts(T.y).z + aPts(T.z).z ' p1.z+p2.z+p3.z
  Next i
  zSortIndexF az(), ndx() ' getting z-order
  For i = 1 To nTris ' this draws the triangles
    drawTri (ndx(i)) ' in z-order
  Next i
  ' -- print stuff
  Locate 2, 101: Print Using "nloops:#,###,###,###"; nloop
  Locate , 101: Print Using "fps:          ####.#"; nloop / (Timer - time0)
  Locate , 104: Print
  Locate , 104: Print "-- To rotate --"
  Locate , 104: Print "1) Click boxes"
  Locate , 104: Print "2) Press boxes"
  Locate , 104: Print "3) Drag mouse"
  Locate , 104: Print "ESC to end"
  Locate 19, 102: Print " -- Quaternion --"
  Locate , 99: Print Using " ##.#####"; QMain.w
  Locate , 99: Print Using " ##.#####"; QMain.x; QMain.y; QMain.z
  '  Locate , 99: Print Using " ##.#####"; QSlew.w
  '  Locate , 99: Print Using " ##.#####"; QSlew.x; QSlew.y; QSlew.z
  Locate , 100: Print ""
  Locate , 102: Print " -- Points --"
  For i = 1 To nPts
    Locate , 99: Print Using " ##.#####"; aPts(i).x; aPts(i).y; aPts(i).z
  Next i
  Locate , 100: Print ""
  Locate , 102: Print " -- Euler Angles --"
  Locate , 100: Print Using "EuAngX: ###"; (EuAngX * 180 / _Pi + 360) Mod 360
  Locate , 100: Print Using "EuAngY: ###"; (EuAngY * 180 / _Pi + 360) Mod 360
  Locate , 100: Print Using "EuAngZ: ###"; (EuAngZ * 180 / _Pi + 360) Mod 360
  _Display
Loop Until InKey$ = Chr$(27)
System

' == ROUTINES start ==

Function iBox (iCol, iRow, s3) ' simple control
  Dim ix, iy
  Locate iRow, iCol: Color 0, 14: Print s3;: Color 0, 15
  ix = iCol * 8 - 11
  iy = iRow * 16 - 1
  Line (ix, iy)-(ix + 3 * 8 + 4, iy - 16), , B ' rectangle
  If m1Rpt And isIn(mx, ix, ix + 28) And isIn(my, iy - 16, iy) Then iBox = TRUE
End Function

Sub Qmult (qa As type4f, qb As type4f, qab As type4f) ' Q multiplication
  Dim w, x, y, z
  w = qa.w * qb.w - qa.x * qb.x - qa.y * qb.y - qa.z * qb.z
  x = qa.w * qb.x + qa.x * qb.w + qa.y * qb.z - qa.z * qb.y
  y = qa.w * qb.y - qa.x * qb.z + qa.y * qb.w + qa.z * qb.x
  z = qa.w * qb.z + qa.x * qb.y - qa.y * qb.x + qa.z * qb.w
  qab.w = w: qab.x = x: qab.y = y: qab.z = z
End Sub

Sub QVtoV (v1 As type4f, v2 As type4f, Q As type4f) ' get Q from v1 to v2
  Dim v1dv2, v1xv2 As type4f ' dot, cross
  v1dv2 = VdotV(v1, v2) ' dot
  VcrossV v1, v2, Q ' cross
  Q.w = v1dv2 + Sqr(v1dv2 * v1dv2 + VdotV(Q, Q)) ' from the book
  Qnorm Q
End Sub

Function VdotV (v1 As type4f, v2 As type4f) ' dot product
  VdotV = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
End Function

Sub VcrossV (v1 As type4f, v2 As type4f, v As type4f) ' cross product
  v.x = v1.y * v2.z - v1.z * v2.y
  v.y = v1.z * v2.x - v1.x * v2.z
  v.z = v1.x * v2.y - v1.y * v2.x
End Sub

Sub Qnorm (q As type4f) ' normalize
  Dim d
  d = Sqr(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z)
  q.w = q.w / d: q.x = q.x / d: q.y = q.y / d: q.z = q.z / d
End Sub

Sub drawTri (iTri) ' draw Triangle
  Dim ip1, ip2, ip3, icolor
  Dim ixc, iyc, x1, y1, x2, y2, x3, y3
  T = aTris(iTri) ' the triangle
  ip1 = T.x: ip2 = T.y: ip3 = T.z: icolor = T.w ' the points, color
  x1 = 386 + kxy * aPts(ip1).x: y1 = 386 - kxy * aPts(ip1).y
  x2 = 386 + kxy * aPts(ip2).x: y2 = 386 - kxy * aPts(ip2).y
  x3 = 386 + kxy * aPts(ip3).x: y3 = 386 - kxy * aPts(ip3).y
  Line (x1, y1)-(x2, y2), icolor
  Line (x2, y2)-(x3, y3), icolor
  Line (x3, y3)-(x1, y1), icolor
  ' don't paint if points are colinear
  If Abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) < 1000 Then Exit Sub
  ixc = (x1 + x2 + x3) / 3: iyc = (y1 + y2 + y3) / 3 ' center
  Paint (ixc, iyc), icolor ' paint
End Sub

' -- LIBRARY ROUTINES --

' -- need Dim Shared mx,my,m1Clk,m1Rpt,m1Dn,m1End,m2Clk,m2Dn
Sub MouseCk () ' get mouse info
  Static m1Prev, m2Prev, m1Time ' for getting edges (Clk,End) and Repeating
  m1Clk = 0: m1Rpt = 0: m1End = 0: m2Clk = 0
  While _MouseInput: Wend ' bplus
  mx = _MouseX: my = _MouseY: m1Dn = _MouseButton(1): m2Dn = _MouseButton(2)
  If m1Dn Then ' Btn 1 down
    If Not m1Prev Then ' got a Clk (& Rpt), now look for repeats
      m1Clk = TRUE: m1Rpt = TRUE: m1Time = iMsecs + 250 ' delay 1/4 sec for repeats
    Else ' has been down, ck for repeat
      If iMsecs > m1Time Then m1Rpt = TRUE: m1Time = iMsecs + 50 ' repeat 20/sec
    End If
    m1Prev = TRUE
  Else ' Btn 1 up
    If m1Prev Then m1End = TRUE ' end of downtime (upedge)
    m1Prev = FALSE ' for next time
  End If
  If m2Dn Then ' Btn 2 down
    If Not m2Prev Then m2Clk = TRUE ' click (downedge)
    m2Prev = TRUE
  Else
    m2Prev = FALSE
  End If
End Sub

Function isIn (x, a, b) ' ck between
  If x >= a And x <= b Then isIn = TRUE
End Function

Sub zSortIndexF (a(), ndx()) ' make index to a()
  Dim i, j, t
  For i = 1 To UBound(a) ' add one at a time
    t = a(i) ' to be added
    For j = i To 2 Step -1 ' merge in
      If a(ndx(j - 1)) <= t Then Exit For
      ndx(j) = ndx(j - 1)
    Next j
    ndx(j) = i
  Next i
End Sub

Function iMsecs () ' milliseconds since midnight UTC
  iMsecs = Int(Timer(.001) * 1000 + .5)
End Function

___________________________________________________
"I don't understand all I know about quaternions."


RE: Quaternion Rotation - bplus - 06-11-2022

Very nice! A _limit of say 60 in main loop will keep the CPU from getting hot and having fan run.

Your screenshot doesn't show all the great controls you have for moving it.


RE: Quaternion Rotation - dcromley - 06-11-2022

@bplus, Thanks. You are sure good at commenting on the various doings here. I am impressed with much of it also, but I don't comment.

You got me to thinking -- and wondering if you were joking. Is heat really an issue? I have 4-cores and 8 logical processors (what is a logical processor?). As I start concurrent instances, the CPU% goes 22%, 44%, 70%, 87%, 99%. And the framerates go down.

So I notice heat from a laptop even when nothing is running. One wouldn't notice heat in a desktop. So ..?


RE: Quaternion Rotation - DSMan195276 - 06-11-2022

The Quaternion question is very interesting to me, but unfortunately I'm not sure Undecided I haven't really done any OpenGL stuff using QB64 before and most of what I've done used OpenGL shaders which work a bit differently.

That said, while it has been awhile I'm pretty sure in most (or maybe all) cases quaternions were just used in the program and weren't given to OpenGL, it would just receive the resulting model matrix after I had use the quaternions to produce it. So Ex. I would store the orientation of a model as a quaternion, but then when drawing the object I would convert the orientation into a rotation matrix and use the rotation matrix (along with translation, scaling, or other transformations) to produce the model matrix for OpenGL to use. So looking at it I think
glMultMatrix
is want you want, but you have to compute the matrix yourself.

> Is heat really an issue?

It's notable that most modern processors will start throttling themselves if they get too hot, so running the CPU too hot can actually reduce performance until it cools down.


RE: Quaternion Rotation - bplus - 06-11-2022

I work from a laptop and know right away when the CPU is racing around a main loop doing nothing.
_Limit is your friend Smile

Your mouse code is inefficient. Unless you are doing mouse wheel, just poll the Mouse like this:
While _MouseInput: Wend
mx = _MouseX: my = _MouseY: mb (or mb1) = _Mousebutton(1) ' usu. for left side clicking


RE: Quaternion Rotation - euklides - 06-12-2022

Nice. You are ready for writing a flight simulator program !