NAME¶
Math::Quaternion - Perl class to represent quaternions
SYNOPSIS¶
use Math::Quaternion qw(slerp);
my $q = Math::Quaternion->new; # Make a new unit quaternion
# Make a rotation about the axis (0,1,0)
my $q2 = Math::Quaternion->new({axis=>[0,1,0],angle=>0.1});
my @v = (1,2,3); # A vector.
my @vrotated = $q2->rotate_vector(@v); # Rotate @v about (0,1,0).
my $q3 = Math::Quaternion::rotation(0.7,2,1,4); # A different rotation.
my $q4 = slerp($q2,$q3,0.5); # Interpolated rotation.
my @vinterp = $q4->rotate_vector(@v);
DESCRIPTION¶
This package lets you create and manipulate quaternions. A quaternion is a
mathematical object developed as a kind of generalization of complex numbers,
usually represented by an array of four real numbers, and is often used to
represent rotations in three-dimensional space.
See, for example, <
http://mathworld.wolfram.com/Quaternion.html> for more
details on the mathematics of quaternions.
Quaternions can be added, subtracted, and scaled just like complex numbers or
vectors -- they can also be multiplied, but quaternion multiplication DOES NOT
COMMUTE. That is to say, if you have quaternions $q1 and $q2, then in general
$q1*$q2 != $q2*$q1. This is related to their use in representing rotations,
which also do not commute.
If you just want to represent rotations and don't care about the internal
mathematical details, this should be all you need to know:
All quaternions have a quantity called the "norm", similar to the
length of a vector. A quaternion with norm equal to 1 is called a "unit
quaternion". All quaternions which represent rotations are unit
quaternions.
If you call
new() without any arguments, it will give you a unit
quaternion which represents no rotation:
$q = Math::Quaternion->new;
You can make a quaternion which represents a rotation of a given angle (in
radians) about a given axis:
$qrot = Math::Quaternion->new({ angle => 0.1, axis => [ 2,3,4]});
Say you have two rotations, $q1 and $q2, and you want to make a quaternion
representing a rotation of $q1 followed by $q2. Then, you do:
$q3 = $q2 * $q1; # Rotate by $q1, followed by $q2.
Remember that this is NOT the same as $q1 * $q2, which will reverse the order of
the rotations.
If you perform many iterated quaternion operations, the result may not quite be
a unit quaternion due to numerical inaccuracies. You can make sure any
quaternion has unit length, by doing:
$unitquat = $anyquat->normalize;
If you have a rotation quaternion, and you want to find the 3x3 matrix which
represents the corresponding rotation, then:
@matrix = $q->matrix3x3;
Similarly, you can generate a 4x4 matrix of the sort you'd pass to OpenGL:
@glmatrix = $q->matrix4x4;
If you have a vector representing a direction, and you want to rotate the vector
by a quaternion $q:
my @vector = (0,0,1); # Vector pointing in the Z direction.
my @newvec = $q->rotate_vector(@vector); # New direction.
Say you're using quaternions to represent the orientation of a camera, and you
have two quaternions: one to represent a starting orientation, and another to
represent a finishing position. If you want to find all the quaternions
representing the orientations in between, allowing your camera to move
smoothly from start to finish, use the
slerp() routine:
use Math::Quaternion qw(slerp);
my ($qstart, $qend) = ... ;
# Set $tween to 9 points between start and end, exclusive.
for my $t (1..9) {
my $tween = slerp($qstart,$qend,0.1*$t);
...
}
METHODS¶
- new
-
my $q = Math::Quaternion->new; # Make a new unit quaternion.
my $q2 = Math::Quaternion->new(1,2,3,4);# Make a specific quaternion.
my $q3 = Math::Quaternion->new($q2); # Copy an existing quaternion.
my $q4 = Math::Quaternion->new(5.6); # Make the quaternion (5.6,0,0,0)
my $q5 = Math::Quaternion->new(7,8,9); # Make the quaternion (0,7,8,9)
my $q6 = Math::Quaternion->new({ # Make a quaternion corresponding
axis => [ 1,2,3], # to a rotation of 0.2 radians
angle => 0.2, # about the vector (1,2,3).
});
my $q7 = Math::Quaternion->new({ # Make a quaternion which would
'v1' => [ 0,1,2], # rotate the vector (0,1,2) onto
'v2' => [ -1,2,0], # the vector (-1,2,0).
});
If no parameters are given, a unit quaternion is returned. If one
non-reference parameter is given, a "scalar" quaternion is
returned. If one parameter is given and it is a reference to a quaternion
or an array of four numbers, the corresponding quaternion object is
returned. If three parameters are given, a "vector" quaternion
is returned. If four parameters are given, the corresponding quaternion is
returned.
Rotation quaternions may also be created by passing a hashref with the axis
and angle of rotation, or by specifying two vectors specifying start and
finish directions. Bear in mind that the latter method will take the
shortest path between the two vectors, ignoring the "roll"
angle.
- unit
- Returns a unit quaternion.
my $u = Math::Quaternion->unit; # Returns the quaternion (1,0,0,0).
- conjugate
- Returns the conjugate of its argument.
my $q = Math::Quaternion->new(1,2,3,4);
my $p = $q->conjugate; # (1,-2,-3,-4)
- inverse
- Returns the inverse of its argument.
my $q = Math::Quaternion->new(1,2,3,4);
my $qi = $q->inverse;
- normalize
- Returns its argument, normalized to unit norm.
my $q = Math::Quaternion->new(1,2,3,4);
my $qn = $q->normalize;
- modulus
- Returns the modulus of its argument, defined as the square root of the
scalar obtained by multiplying the quaternion by its conjugate.
my $q = Math::Quaternion->new(1,2,3,4);
print $q->modulus;
- isreal
- Returns 1 if the given quaternion is real ,ie has no quaternion part, or
else 0.
my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,0,0,0);
print $q1->isreal; # 0;
print $q2->isreal; # 1;
- multiply
- Performs a quaternion multiplication of its two arguments. If one of the
arguments is a scalar, then performs a scalar multiplication instead.
my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,6,7,8);
my $q3 = Math::Quaternion::multiply($q1,$q2); # (-60 12 30 24)
my $q4 = Math::Quaternion::multiply($q1,$q1->inverse); # (1 0 0 0)
- dot
- Returns the dot product of two quaternions.
my $q1=Math::Quaternion->new(1,2,3,4);
my $q2=Math::Quaternion->new(2,4,5,6);
my $q3 = Math::Quaternion::dot($q1,$q2);
- plus
- Performs a quaternion addition of its two arguments.
my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,6,7,8);
my $q3 = Math::Quaternion::plus($q1,$q2); # (6 8 10 12)
- minus
- Performs a quaternion subtraction of its two arguments.
my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,6,7,8);
my $q3 = Math::Quaternion::minus($q1,$q2); # (-4 -4 -4 -4)
- power
- Raise a quaternion to a scalar or quaternion power.
my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion::power($q1,4); # ( 668 -224 -336 -448 )
my $q3 = $q1->power(4); # ( 668 -224 -336 -448 )
my $q4 = $q1**(-1); # Same as $q1->inverse
use Math::Trig;
my $q5 = exp(1)**( Math::Quaternion->new(pi,0,0) ); # approx (-1 0 0 0)
- negate
- Negates the given quaternion.
my $q = Math::Quaternion->new(1,2,3,4);
my $q1 = $q->negate; # (-1,-2,-3,-4)
- squarednorm
- Returns the squared norm of its argument.
my $q1 = Math::Quaternion->new(1,2,3,4);
my $sn = $q1->squarednorm; # 30
- scale
- Performs a scalar multiplication of its two arguments.
my $q = Math::Quaternion->new(1,2,3,4);
my $qq = Math::Quaternion::scale($q,2); # ( 2 4 6 8)
my $qqq= $q->scale(3); # ( 3 6 9 12 )
- rotation
- Generates a quaternion corresponding to a rotation.
If given three arguments, interprets them as an angle and the three
components of an axis vector.
use Math::Trig; # Define pi. my $theta = pi/2;
# Angle of rotation my $rotquat =
Math::Quaternion::rotation($theta,0,0,1);
# $rotquat now represents a rotation of 90 degrees about Z axis.
my ($x,$y,$z) = (1,0,0); # Unit vector in the X direction.
my ($xx,$yy,$zz) = $rotquat->rotate_vector($x,$y,$z);
# ($xx,$yy,$zz) is now ( 0, 1, 0), to within floating-point error.
rotation() can also be passed a scalar angle and a reference to a
vector (in either order), and will generate the corresponding rotation
quaternion.
my @axis = (0,0,1); # Rotate about Z axis
$theta = pi/2;
$rotquat = Math::Quaternion::rotation($theta,\@axis);
If the arguments to rotation() are both references, they are
interpreted as two vectors, and a quaternion is returned which rotates the
first vector onto the second.
my @startvec = (0,1,0); # Vector pointing north
my @endvec = (-1,0,0); # Vector pointing west
$rotquat = Math::Quaternion::rotation(\@startvec,\@endvec);
my @newvec = $rotquat->rotate_vector(@startvec); # Same as @endvec
- rotation_angle
- Returns the angle of rotation represented by the quaternion argument.
my $q = Math::Quaternion::rotation(0.1,2,3,4);
my $theta = $q->rotation_angle; # Returns 0.1 .
- rotation_axis
- Returns the unit vector representing the axis about which rotations will
be performed, for the rotation represented by the quaternion argument.
my $q = Math::Quaternion::rotation(0.1,1,1,0);
my @v = $q->rotation_axis; # Returns (0.5*sqrt(2),0.5*sqrt(2),0)
- rotate_vector
- When called as a method on a rotation quaternion, uses this quaternion to
perform the corresponding rotation on the vector argument.
use Math::Trig; # Define pi.
my $theta = pi/2; # Rotate 90 degrees
my $rotquat = Math::Quaternion::rotation($theta,0,0,1); # about Z axis
my ($x,$y,$z) = (1,0,0); # Unit vector in the X direction.
my ($xx,$yy,$zz) = $rotquat->rotate_vector($x,$y,$z)
# ($xx,$yy,$zz) is now ( 0, 1, 0), to within floating-point error.
- matrix4x4
- Takes one argument: a rotation quaternion. Returns a 16-element array,
equal to the OpenGL matrix which represents the corresponding rotation.
my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
my @m = $rotquat->matrix4x4;
- matrix3x3
- Takes one argument: a rotation quaternion. Returns a 9-element array,
equal to the 3x3 matrix which represents the corresponding rotation.
my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
my @m = $rotquat->matrix3x3;
- matrix4x4andinverse
- Similar to matrix4x4, but returnes a list of two array references. The
first is a reference to the rotation matrix; the second is a reference to
its inverse. This may be useful when rendering sprites, since you can
multiply by the rotation matrix for the viewer position, perform some
translations, and then multiply by the inverse: any resulting rectangles
drawn will always face the viewer.
my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
my ($matref,$invref) = $rotquat->matrix4x4andinverse;
- stringify
- Returns a string representation of the quaternion. This is used to
overload the '""' operator, so that quaternions may be freely
interpolated in strings.
my $q = Math::Quaternion->new(1,2,3,4);
print $q->stringify; # "( 1 2 3 4 )"
print "$q"; # "( 1 2 3 4 )"
- slerp
- Takes two quaternion arguments and one scalar; performs spherical linear
interpolation between the two quaternions. The quaternion arguments are
assumed to be unit quaternions, and the scalar is assumed to lie between 0
and 1: a scalar argument of zero will return the first quaternion
argument, and a scalar argument of one will return the second.
use Math::Trig;
my @axis = (0,0,1);
my $rq1 = Math::Quaternion::rotation(pi/2,\@axis); # 90 degs about Z
my $rq2 = Math::Quaternion::rotation(pi,\@axis); # 180 degs about Z
my $interp = Math::Quaternion::slerp($rq1,$rq2,0.5); # 135 degs about Z
- exp
- Exponential operator e^q. Any quaternion q can be written as x+uy, where x
is a real number, and u is a unit pure quaternion. Then, exp(q) == exp(x)
* ( cos(y) + u sin(y) ).
my $q = Math::Quaternion->new(1,2,3,4);
print Math::Quaternion::exp($q);
- log
- Returns the logarithm of its argument. The logarithm of a negative real
quaternion can take any value of them form (log(-q0),u*pi) for any unit
vector u. In these cases, u is chosen to be (1,0,0).
my $q = Math::Quaternion->new(1,2,3,4);
print Math::Quaternion::log($q);
AUTHOR¶
Jonathan Chin, <jon-quaternion.pm@earth.li>
ACKNOWLEDGEMENTS¶
Thanks to Rene Uittenbogaard and Daniel Connelly for useful suggestions, and Luc
Vereecken and Bruce Gray for patches.
SEE ALSO¶
- <http://mathworld.wolfram.com/Quaternion.html>
- <http://sjbaker.org/steve/omniv/eulers_are_evil.html>
- Acts 12:4
COPYRIGHT AND LICENSE¶
Copyright 2003 by Jonathan Chin
This library is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.