Quaternion library

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
jdebord
Posts: 547
Joined: May 27, 2005 6:20
Location: Limoges, France
Contact:

Quaternion library

Post by jdebord »

As requested on another thread...

Code: Select all

' ******************************************************************
' Quaternion library
' Adapted from C++ library by Andrew Burbanks
' http://www.lboro.ac.uk/departments/ma/gallery/quat/src.html
' ******************************************************************

TYPE Quaternion
  X AS DOUBLE
  Y AS DOUBLE
  Z AS DOUBLE
  T AS DOUBLE
END TYPE

DIM SHARED ErrCode AS INTEGER  ' Error code

SUB QSet(X AS DOUBLE, Y AS DOUBLE, Z AS DOUBLE, T AS DOUBLE, Q AS Quaternion)
' Set quaternion Q = (X, Y, Z, T)

  Q.X = X
  Q.Y = Y
  Q.Z = Z
  Q.T = T
END SUB

SUB QAdd(Q1 AS Quaternion, Q2 AS Quaternion, Q AS Quaternion)
' Addition: Q = Q1 + Q2

  Q.X = Q1.X + Q2.X
  Q.Y = Q1.Y + Q2.Y
  Q.Z = Q1.Z + Q2.Z
  Q.T = Q1.T + Q2.T
END SUB

SUB QSub(Q1 AS Quaternion, Q2 AS Quaternion, Q AS Quaternion)
' Addition: Q = Q1 - Q2

  Q.X = Q1.X - Q2.X
  Q.Y = Q1.Y - Q2.Y
  Q.Z = Q1.Z - Q2.Z
  Q.T = Q1.T - Q2.T
END SUB

SUB QRMul(R AS DOUBLE, Q1 AS Quaternion, Q AS Quaternion)
' Multiplication by real: Q = R * Q1

  Q.X = R * Q1.X
  Q.Y = R * Q1.Y
  Q.Z = R * Q1.Z
  Q.T = R * Q1.T
END SUB

SUB QMul(Q1 AS Quaternion, Q2 AS Quaternion, Q AS Quaternion)
' Multiplication: Q = Q1 * Q2 (not commutative!)

  DIM AS DOUBLE T0, T1, T2, T3, T4, T5, T6, T7, T8, T9

  T0 = (Q1.T - Q1.Z) * (Q2.Z - Q2.T)
  T1 = (Q1.X + Q1.Y) * (Q2.X + Q2.Y)
  T2 = (Q1.X - Q1.Y) * (Q2.Z + Q2.T)
  T3 = (Q1.T + Q1.Z) * (Q2.X - Q2.Y)
  T4 = (Q1.T - Q1.Y) * (Q2.Y - Q2.Z)
  T5 = (Q1.T + Q1.Y) * (Q2.Y + Q2.Z)
  T6 = (Q1.X + Q1.Z) * (Q2.X - Q2.T)
  T7 = (Q1.X - Q1.Z) * (Q2.X + Q2.T)

  T8 = T5 + T6 + T7
  T9 = 0.5 * (T4 + T8)

  Q.X = T0 - T5 + T9
  Q.Y = T1 - T8 + T9
  Q.Z = T2 - T7 + T9
  Q.T = T3 - T6 + T9
END SUB

SUB QConj(Q1 AS Quaternion, Q AS Quaternion)
' Conjugate: Q = Conj(Q1)

  Q.X = Q1.X
  Q.Y = - Q1.Y
  Q.Z = - Q1.Z
  Q.T = - Q1.T
END SUB

FUNCTION QAbs(Q AS Quaternion) AS DOUBLE
' Absolute value

  QAbs = SQR(Q.X * Q.X + Q.Y * Q.Y + Q.Z * Q.Z + Q.T * Q.T)
END FUNCTION

SUB QInv(Q1 AS Quaternion, Q AS Quaternion)
' Inverse: Q = 1 / Q1

  ErrCode = 0
  IF Q1.X = 0 AND Q1.Y = 0 AND Q1.Z = 0 AND Q1.T = 0 THEN
    PRINT "Undefined inverse"
    ErrCode = -1
    EXIT SUB
  END IF

  DIM R AS DOUBLE

  R = 1 / (Q1.X * Q1.X + Q1.Y * Q1.Y + Q1.Z * Q1.Z + Q1.T * Q1.T)

  Q.X = R * Q1.X
  Q.Y = - R * Q1.Y
  Q.Z = - R * Q1.Z
  Q.T = - R * Q1.T
END SUB

SUB QDiv(Q1 AS Quaternion, Q2 AS Quaternion, Q AS Quaternion)
' Division: Q = Q1 / Q2 = Q1 * Inv(Q2)

  dim as Quaternion InvQ2

  QInv Q2, InvQ2

  if ErrCode then
    print "Undefined division"
    exit sub
  end if

  QMul Q1, InvQ2, Q
END SUB

SUB QPrint(Q AS Quaternion)
' Prints a quaternion

  PRINT "("; Q.X; ","; Q.Y; ","; Q.Z; ","; Q.T; " )"
END SUB

' ******************************************************************
' Demo program
' ******************************************************************

DIM AS Quaternion Q1, Q2, Q

QSet 1, 2, 3, 4, Q1
QSet 5, 6, 7, 8, Q2

PRINT "Q1 = "; : QPrint Q1
PRINT "Q2 = "; : QPrint Q2
PRINT

QAdd Q1, Q2, Q
PRINT "Q1 + Q2 = "; : QPrint Q
PRINT

QSub Q1, Q2, Q
PRINT "Q1 - Q2 = "; : QPrint Q
PRINT

QRMul 5, Q1, Q
PRINT "5 * Q1 = "; : QPrint Q
PRINT

QMul Q1, Q2, Q
PRINT "Q1 * Q2 = "; : QPrint Q
PRINT

QMul Q2, Q1, Q
PRINT "Q2 * Q1 = "; : QPrint Q
PRINT

QConj Q1, Q
PRINT "Q1 conjugate = "; : QPrint Q
PRINT

PRINT "Abs(Q1) = "; QAbs(Q1)
PRINT

QInv Q1, Q
PRINT "Q1 inverse = "; : QPrint Q
PRINT

QDiv Q1, Q2, Q
PRINT "Q1 / Q2 = "; : QPrint Q
PRINT

QDiv Q2, Q1, Q
PRINT "Q2 / Q1 = "; : QPrint Q
PRINT

SLEEP
etko
Posts: 113
Joined: May 27, 2005 7:55
Location: Slovakia
Contact:

Post by etko »

WOW, thanks I'm sure this will help many noth so math savy guys ;).
jdebord
Posts: 547
Joined: May 27, 2005 6:20
Location: Limoges, France
Contact:

Post by jdebord »

Thanks for your interest. I think I will try to add hypercomplex numbers, too. They are more interesting than quaternions to produce fractal graphics.
etko
Posts: 113
Joined: May 27, 2005 7:55
Location: Slovakia
Contact:

Post by etko »

Actually I'm interested in quaternions solely for rotation purposes.
integer
Posts: 408
Joined: Feb 01, 2007 16:54
Location: usa

Post by integer »

Thank you for putting the code into FB.

There are several messy problems with homogenous coordinates.
This will make the task much easier.

Again THANKS.
jdebord
Posts: 547
Joined: May 27, 2005 6:20
Location: Limoges, France
Contact:

Post by jdebord »

Thanks for your encouraging comments :)

With operator overloading it will be possible to write more useful code but I wait for the next stable FB version to do this.
rdc
Posts: 1741
Joined: May 27, 2005 17:22
Location: Texas, USA
Contact:

Post by rdc »

As a little exercise I converted this over to the new extended types available in the CVS version. I am not familiar with Quaternions so you should double-check the calculations to make sure I didn't botch something in the translation. If I made any errors, or if there is a better type construction, please correct.

Code: Select all

' ******************************************************************
' Quaternion library
' Adapted from C++ library by Andrew Burbanks
' http://www.lboro.ac.uk/departments/ma/gallery/quat/src.html
' Converted to Extended Types by RDC
' ******************************************************************

Type Quaternion
    private:
    X_ As Double
    Y_ As Double
    Z_ As Double
    T_ As Double
    ErrCode_ as integer
    ErrStr_ as string 
    public:
    Declare Constructor ()
    Declare Constructor (byval qx as double, byval qy as double, byval qz as double, byval qt as double)
    Declare Destructor ()
    Declare Operator Cast () As String
    declare Property X () as double
    declare Property Y () as double
    declare Property Z () as double
    declare Property T () as double
    declare Property ErrCode () as integer
    declare Property ErrStr () as string
    declare Property ErrCode (byval ec as integer)
    declare Property ErrStr (byval es as string)
    declare function QRMul (byval R As Double) as Quaternion 
    declare function QConj () as Quaternion
    declare Function QAbs () As Double 
    declare function QInv () as Quaternion
End Type

Constructor Quaternion ()
    this.X_ = 0
    this.Y_ = 0
    this.Z_ = 0
    this.T_ = 0
end constructor

Constructor Quaternion (byval qx as double, byval qy as double, byval qz as double, byval qt as double)
    this.X_ = qx
    this.Y_ = qy
    this.Z_ = qz
    this.T_ = qt
end constructor

destructor Quaternion ()
    this.ErrStr_ = ""
end destructor

property Quaternion.X () as double
    return this.X_
end property

property Quaternion.Y () as double
    return this.Y_
end property

property Quaternion.Z () as double
    return this.Z_
end property

property Quaternion.T () as double
    return this.T_
end property

property Quaternion.ErrCode () as integer
    return this.ErrCode_
end property

property Quaternion.ErrStr () as string
    return this.ErrStr_
end property

property Quaternion.ErrCode (byval ec as integer)
    this.ErrCode_ = ec
end property

property Quaternion.ErrStr (byval es as string)
    this.ErrStr_ = es
end property

operator Quaternion.Cast () as string
    return "(" & this.X_ & "," & this.Y_ & "," & this.Z_ & "," & this.T_ & ")"
end operator

function Quaternion.QRMul(byval R As Double) as Quaternion
    ' Multiplication by real: Q = R * Q1

    return Type<Quaternion>(R * this.X_, R * this.Y_, R * this.Z_, R * this.T_)
    
End function

function Quaternion.QConj() as Quaternion
    ' Conjugate: Q = Conj(Q1)

    return Type<Quaternion>(this.X_, - this.Y_, - this.Z_, - this.T_)
    
End function

Function Quaternion.QAbs() As Double
    ' Absolute value
    
    return Sqr(this.X_ * this.X_ + this.Y_ * this.Y_ + this.Z_ * this.Z_ + this.T_ * this.T_)
End Function

function Quaternion.QInv() as Quaternion 
    ' Inverse: Q = 1 / Q1

    this.ErrCode_ = 0
    If this.X_ = 0 And this.Y_ = 0 And this.Z_ = 0 And this.T_ = 0 Then
        this.ErrStr_ = "Undefined inverse"
        this.ErrCode_ = -1
        Exit function
    End If

    Dim R As Double

    R = 1 / (this.X_ * this.X_ + this.Y_ * this.Y_ + this.Z_ * this.Z_ + this.T_ * this.T_)

    return Type<Quaternion>(R * this.X_, R * this.Y_, R * this.Z_, R * this.T_)
End function


operator + (byval lhs As Quaternion, byval rhs As Quaternion ) As Quaternion
    ' Addition: Q = Q1 + Q2
    return Type<Quaternion>(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z, lhs.T + rhs.T)
end operator

operator - (byval lhs As Quaternion, byval rhs As Quaternion ) As Quaternion
    ' Subtraction: Q = Q1 - Q2
    return Type<Quaternion>(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z, lhs.T - rhs.T)
end operator

operator * (byval lhs As Quaternion, byval rhs As Quaternion ) As Quaternion
    ' Multiplication: Q = Q1 * Q2 (not commutative!)
    Dim As Double T0, T1, T2, T3, T4, T5, T6, T7, T8, T9

    T0 = (lhs.T - lhs.Z) * (rhs.Z - rhs.T)
    T1 = (lhs.X + lhs.Y) * (rhs.X + rhs.Y)
    T2 = (lhs.X - lhs.Y) * (rhs.Z + rhs.T)
    T3 = (lhs.T + lhs.Z) * (rhs.X - rhs.Y)
    T4 = (lhs.T - lhs.Y) * (rhs.Y - rhs.Z)
    T5 = (lhs.T + lhs.Y) * (rhs.Y + rhs.Z)
    T6 = (lhs.X + lhs.Z) * (rhs.X - rhs.T)
    T7 = (lhs.X - lhs.Z) * (rhs.X + rhs.T)
    
    T8 = T5 + T6 + T7
    T9 = 0.5 * (T4 + T8)
    
    return Type<Quaternion>(T0 - T5 + T9, T1 - T8 + T9, T2 - T7 + T9, T3 - T6 + T9)
end operator

operator / (byval lhs As Quaternion, byval rhs As Quaternion ) As Quaternion
    ' Division: Q = Q1 / Q2 = Q1 * Inv(Q2)

    Dim As Quaternion InvQ2, Q  

    InvQ2 = rhs.QInv

    If rhs.ErrCode <> 0 Then
        Q.ErrCode = -1
        Q.ErrStr = "Undefined division"
    else
        Q = lhs * InvQ2 
    End If

    return Q
  
End operator


' ******************************************************************
' Demo program
' ******************************************************************

Dim As Quaternion Q1 = Quaternion(1, 2, 3, 4), Q2 = Quaternion(5, 6, 7, 8), Q

Print "Q1 = "; Q1
Print "Q2 = "; Q2
Print "---------------------------------------"

Print "Q1 + Q2 = "; Q1 + Q2
Print "---------------------------------------"

Print "Q1 - Q2 = "; Q1 - Q2
Print "---------------------------------------"

Print "5 * Q1 = "; Q1.QRMul(5) 
Print "---------------------------------------"

Print "Q1 * Q2 = "; Q1 * Q2
Print "---------------------------------------"

Print "Q2 * Q1 = "; Q2 * Q1
Print "---------------------------------------"

Print "Q1 conjugate = "; Q1.QConj
Print "---------------------------------------"

Print "Abs(Q1) = "; Q1.QAbs
Print "---------------------------------------"

Print "Q1 inverse = "; Q1.QInv
Print "---------------------------------------"

Q = Q1 / Q2
if Q.ErrCode <> 0 then
    Print Q.ErrStr
else
    Print "Q1 / Q2 = "; Q
end if
Print "---------------------------------------"

Q = Q2 / Q1
if Q.ErrCode <> 0 then
    Print Q.ErrStr
else
    Print "Q2 / Q1 = "; Q
end if

Sleep

And here is the output for those without the CVS version.

Code: Select all

Q1 = (1,2,3,4)
Q2 = (5,6,7,8)
---------------------------------------
Q1 + Q2 = (6,8,10,12)
---------------------------------------
Q1 - Q2 = (-4,-4,-4,-4)
---------------------------------------
5 * Q1 = (5,10,15,20)
---------------------------------------
Q1 * Q2 = (-60,12,30,24)
---------------------------------------
Q2 * Q1 = (-60,20,14,32)
---------------------------------------
Q1 conjugate = (1,-2,-3,-4)
---------------------------------------
Abs(Q1) =  5.477225575051661
---------------------------------------
Q1 inverse = (3.333333333333333e-002,6.666666666666667e-002,0.1,0.1333333333333333)
---------------------------------------
Q1 / Q2 = (-0.3448275862068966,6.896551724137928e-002,0.1724137931034483,0.1379310344827586)
---------------------------------------
Q2 / Q1 = (-2,0.6666666666666666,0.4666666666666668,1.066666666666667)
acetoline
Posts: 228
Joined: Oct 27, 2006 6:50
Contact:

Post by acetoline »

Nice work, jdebord.

I've been wanting to do quaternions for some time but been too lazy... good job!
owen
Posts: 555
Joined: Apr 19, 2006 10:55
Location: Kissimmee, FL
Contact:

Post by owen »

if somebody feels generous, could they post in this topic an example of quats in action. i'm thinking about their application for 3d rotation as a good example. and in order to demonstrate this in it's simplest form (cuz visualizing 3d in itself is difficult), i'm thinking that a spinning cube and movable / rotatable camera is the best example to use for learning purposes.

the idea is to demonstrate how using quats solves gimbal lock issues
thanx in advance.
owen
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Post by rolliebollocks »

@Owen

The specific rotation you're asking for is XZ which is side to side rotation, and as long as you're on the ground, is the only rotation you need ( ie, if you're head stays parallel to the XZ plane, you only need XZ rotation, you can't look up (YZ) or cock your head, XY.)

FUDGE!

My rotations only work for 2d images in 3d (because half of it drops out)...

Eh.

Take the 2d rotation formula:

x = centerx + cos X - sin Y
y = centery + sin X + cos Y

And turn it the same way on the other plane like this:

x = centerx + cosX - sin Z
z = centerz + sinX + cosZ

Imagine Linda Blair in the Exorcist, and her head spinning around in circles. That's what it's like when you over complicate the process.

All the rules that apply in 2d, will still apply in three.

Try spinning a line first.
relsoft
Posts: 1767
Joined: May 27, 2005 10:34
Location: Philippines
Contact:

Post by relsoft »

owen wrote:if somebody feels generous, could they post in this topic an example of quats in action. i'm thinking about their application for 3d rotation as a good example. and in order to demonstrate this in it's simplest form (cuz visualizing 3d in itself is difficult), i'm thinking that a spinning cube and movable / rotatable camera is the best example to use for learning purposes.

the idea is to demonstrate how using quats solves gimbal lock issues
thanx in advance.
owen
http://rel.betterwebber.com/junk.php?id=43

BTW, quats are not immune to gimbal lock.
owen
Posts: 555
Joined: Apr 19, 2006 10:55
Location: Kissimmee, FL
Contact:

Post by owen »

This is not a question. Just making a statement which will hopefully lead someone to post a (tips and tricks) which provides a solution or a work around in the event of gimbal lock.

Gimbal lock is unavoidable. Quats or no quats.

One idea i have been experimenting with is switching between the 6 Euler rotations orders as a possible means to continue rotating on the (lost axis)

So if this is the trick then I'll post it in a Euler Rotation Order topic.

Other wise, the solution will have to be a method that others use, which is an adjustment (or correction) of 2 of the 3 axes. How to calculate this correction using Quats and or Euler is the real question in this long winded statement.
http://vimeo.com/2824431
watching that short video (about 3/4 the way thru) you see mention of this correction / adjustment that i refer to with out any detail as to how it might have been derived.
Post Reply