File Coverage

blib/lib/Math/Quaternion.pm
Criterion Covered Total %
statement 227 230 98.7
branch 69 76 90.7
condition 16 33 48.4
subroutine 33 33 100.0
pod 26 26 100.0
total 371 398 93.2


line stmt bran cond sub pod time code
1             package Math::Quaternion;
2              
3 2     2   133542 use 5.004;
  2         9  
  2         77  
4 2     2   9 use strict;
  2         6  
  2         52  
5 2     2   11 use warnings;
  2         8  
  2         62  
6 2     2   10 use Carp;
  2         2  
  2         125  
7 2     2   1088 use Math::Trig; # What?!? Where's acos()? You can't have cos and not acos!
  2         30198  
  2         660  
8              
9             require Exporter;
10             use overload
11             '+' => \&plus,
12             '-' => \&minus,
13 9     9   641 'bool' => sub { 1; }, # So we can do if ($foo=Math::Quaternion->new) { .. }
14 2         42 '""' => \&stringify,
15             '*' => \&multiply,
16             '~' => \&conjugate,
17             'abs' => \&modulus,
18             'neg' => \&negate,
19             '**' => \&power,
20             'exp' => \&exp,
21             'log' => \&log,
22 2     2   19 ;
  2         3  
23              
24             our @ISA = qw(Exporter);
25              
26             # Items to export into callers namespace by default. Note: do not export
27             # names by default without a very good reason. Use EXPORT_OK instead.
28             # Do not simply export all your public functions/methods/constants.
29              
30             # This allows declaration use Math::Quaternion ':all';
31             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
32             # will save memory.
33              
34              
35             our %EXPORT_TAGS = ( 'all' => [ qw(
36             unit
37             conjugate
38             inverse
39             normalize
40             modulus
41             isreal
42             multiply
43             dot
44             plus
45             minus
46             power
47             negate
48             squarednorm
49             scale
50             rotation
51             rotation_angle
52             rotation_axis
53             rotate_vector
54             matrix4x4
55             matrix3x3
56             matrix4x4andinverse
57             stringify
58             slerp
59             exp
60             log
61             ) ],
62             );
63              
64             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
65              
66             our @EXPORT = qw(
67            
68             );
69              
70             our $VERSION = '0.07';
71              
72              
73             # Preloaded methods go here.
74              
75              
76             # Below is stub documentation for your module. You'd better edit it!
77              
78             =head1 NAME
79              
80             Math::Quaternion - Perl class to represent quaternions
81              
82             =head1 SYNOPSIS
83              
84             use Math::Quaternion qw(slerp);
85             my $q = Math::Quaternion->new; # Make a new unit quaternion
86            
87             # Make a rotation about the axis (0,1,0)
88             my $q2 = Math::Quaternion->new({axis=>[0,1,0],angle=>0.1});
89             my @v = (1,2,3); # A vector.
90             my @vrotated = $q2->rotate_vector(@v); # Rotate @v about (0,1,0).
91            
92             my $q3 = Math::Quaternion::rotation(0.7,2,1,4); # A different rotation.
93             my $q4 = slerp($q2,$q3,0.5); # Interpolated rotation.
94             my @vinterp = $q4->rotate_vector(@v);
95              
96              
97             =head1 DESCRIPTION
98              
99             This package lets you create and manipulate quaternions. A
100             quaternion is a mathematical object developed as a kind of
101             generalization of complex numbers, usually represented by an array
102             of four real numbers, and is often used to represent rotations in
103             three-dimensional space.
104              
105             See, for example, L for
106             more details on the mathematics of quaternions.
107              
108             Quaternions can be added, subtracted, and scaled just like complex
109             numbers or vectors -- they can also be multiplied, but quaternion
110             multiplication DOES NOT COMMUTE. That is to say, if you have
111             quaternions $q1 and $q2, then in general $q1*$q2 != $q2*$q1. This is
112             related to their use in representing rotations, which also do not
113             commute.
114              
115             If you just want to represent rotations and don't care about the
116             internal mathematical details, this should be all you need to know:
117              
118             All quaternions have a quantity called the "norm", similar to the
119             length of a vector. A quaternion with norm equal to 1 is called a
120             "unit quaternion". All quaternions which represent rotations are
121             unit quaternions.
122              
123             If you call new() without any arguments, it will give you a unit
124             quaternion which represents no rotation:
125            
126             $q = Math::Quaternion->new;
127              
128             You can make a quaternion which represents a rotation of a given
129             angle (in radians) about a given axis:
130              
131             $qrot = Math::Quaternion->new({ angle => 0.1, axis => [ 2,3,4]});
132              
133             Say you have two rotations, $q1 and $q2, and you want to make a
134             quaternion representing a rotation of $q1 followed by $q2. Then, you
135             do:
136              
137             $q3 = $q2 * $q1; # Rotate by $q1, followed by $q2.
138              
139             Remember that this is NOT the same as $q1 * $q2, which will reverse
140             the order of the rotations.
141              
142             If you perform many iterated quaternion operations, the result may
143             not quite be a unit quaternion due to numerical inaccuracies. You
144             can make sure any quaternion has unit length, by doing:
145              
146             $unitquat = $anyquat->normalize;
147              
148             If you have a rotation quaternion, and you want to find the 3x3
149             matrix which represents the corresponding rotation, then:
150              
151             @matrix = $q->matrix3x3;
152              
153             Similarly, you can generate a 4x4 matrix of the sort you'd pass to
154             OpenGL:
155              
156             @glmatrix = $q->matrix4x4;
157              
158             If you have a vector representing a direction, and you want to
159             rotate the vector by a quaternion $q:
160              
161             my @vector = (0,0,1); # Vector pointing in the Z direction.
162            
163             my @newvec = $q->rotate_vector(@vector); # New direction.
164              
165             Say you're using quaternions to represent the orientation of a
166             camera, and you have two quaternions: one to represent a
167             starting orientation, and another to represent a finishing
168             position. If you want to find all the quaternions representing
169             the orientations in between, allowing your camera to move
170             smoothly from start to finish, use the slerp() routine:
171              
172             use Math::Quaternion qw(slerp);
173            
174             my ($qstart, $qend) = ... ;
175            
176             # Set $tween to 9 points between start and end, exclusive.
177            
178             for my $t (1..9) {
179             my $tween = slerp($qstart,$qend,0.1*$t);
180             ...
181             }
182              
183              
184             =head1 METHODS
185              
186             =over 1
187              
188             =item B
189              
190             my $q = Math::Quaternion->new; # Make a new unit quaternion.
191             my $q2 = Math::Quaternion->new(1,2,3,4);# Make a specific quaternion.
192             my $q3 = Math::Quaternion->new($q2); # Copy an existing quaternion.
193             my $q4 = Math::Quaternion->new(5.6); # Make the quaternion (5.6,0,0,0)
194             my $q5 = Math::Quaternion->new(7,8,9); # Make the quaternion (0,7,8,9)
195            
196             my $q6 = Math::Quaternion->new({ # Make a quaternion corresponding
197             axis => [ 1,2,3], # to a rotation of 0.2 radians
198             angle => 0.2, # about the vector (1,2,3).
199             });
200            
201             my $q7 = Math::Quaternion->new({ # Make a quaternion which would
202             'v1' => [ 0,1,2], # rotate the vector (0,1,2) onto
203             'v2' => [ -1,2,0], # the vector (-1,2,0).
204             });
205              
206             If no parameters are given, a unit quaternion is returned. If one
207             non-reference parameter is given, a "scalar" quaternion is returned.
208             If one parameter is given and it is a reference to a quaternion or
209             an array of four numbers, the corresponding quaternion object is
210             returned. If three parameters are given, a "vector" quaternion is
211             returned. If four parameters are given, the corresponding
212             quaternion is returned.
213              
214             Rotation quaternions may also be created by passing a hashref with
215             the axis and angle of rotation, or by specifying two vectors
216             specifying start and finish directions. Bear in mind that the latter
217             method will take the shortest path between the two vectors, ignoring
218             the "roll" angle.
219              
220             =cut
221              
222             sub new {
223 220     220 1 3341 my $class = shift;
224              
225 220         260 my $arr=undef;
226              
227 220 100       844 if (0==@_) {
    100          
    100          
    100          
228             # No arguments, default to unit quaternion.
229 45         101 $arr = [ 1,0,0,0];
230             } elsif (1==@_) {
231             # One argument: if it's not a reference, construct
232             # a "scalar quaternion" (x 0 0 0).
233 32         41 my $arg = $_[0];
234 32         63 my $reftype = ref($arg);
235              
236 32 100       57 if (!$reftype) {
237 12         33 $arr = [ $arg,0,0,0];
238             } else {
239             # We've been passed a reference. If it's an array
240             # ref, then construct a quaternion out of the
241             # corresponding array.
242 20 100       71 if ("ARRAY" eq $reftype) {
    100          
    50          
243 2         17 return Math::Quaternion->new(@$arg);
244             } elsif ("Math::Quaternion" eq $reftype) {
245             # If it's a reference to another quaternion,
246             # copy it.
247 11         46 return Math::Quaternion->new(@$arg);
248             } elsif ("HASH" eq $reftype) {
249             # Hashref.
250 7         30 my %hash = %$arg;
251 7 100       42 if (defined($hash{'axis'})) {
    100          
252             # Construct a rotation.
253 3         9 return rotation(
254             $hash{'angle'},
255 3         5 @{$hash{'axis'}}
256             );
257             } elsif (defined($hash{'v2'})) {
258 3         10 return rotation(
259             $hash{'v1'},$hash{'v2'}
260             );
261             }
262             }
263 1         219 croak("Don't understand arguments to new()");
264              
265             }
266             } elsif (3==@_) {
267             # Three arguments: construct a quaternion to represent
268             # the corresponding vector.
269 6         23 $arr = [ 0, @_[0,1,2] ];
270             } elsif (4==@_) {
271             # Four arguments: just slot the numbers right in.
272 136         421 $arr = [ @_[0,1,2,3] ];
273             } else {
274 1         259 croak("Don't understand arguments passed to new()");
275             }
276            
277              
278 199         858 bless $arr, $class;
279              
280             }
281              
282             =item B
283              
284             Returns a unit quaternion.
285              
286             my $u = Math::Quaternion->unit; # Returns the quaternion (1,0,0,0).
287              
288             =cut
289              
290             sub unit {
291 1     1 1 323 my $class = shift;
292              
293 1         6 bless [ 1,0,0,0 ], $class;
294             }
295              
296             =item B
297              
298             Returns the conjugate of its argument.
299              
300             my $q = Math::Quaternion->new(1,2,3,4);
301             my $p = $q->conjugate; # (1,-2,-3,-4)
302              
303             =cut
304              
305             sub conjugate {
306 12     12 1 307 my $q=shift;
307              
308 12         56 return Math::Quaternion->new(
309             $q->[0],
310             - $q->[1],
311             - $q->[2],
312             - $q->[3],
313             );
314             }
315              
316             =item B
317              
318             Returns the inverse of its argument.
319              
320             my $q = Math::Quaternion->new(1,2,3,4);
321             my $qi = $q->inverse;
322              
323             =cut
324              
325             sub inverse {
326 6     6 1 18 my $q = shift;
327              
328 6         17 return scale(conjugate($q),1.0/squarednorm($q));
329              
330             }
331              
332              
333             =item B
334              
335             Returns its argument, normalized to unit norm.
336              
337             my $q = Math::Quaternion->new(1,2,3,4);
338             my $qn = $q->normalize;
339              
340             =cut
341              
342             sub normalize {
343 5     5 1 261 my $q = shift;
344 5         11 return scale($q,1.0/sqrt(squarednorm($q)));
345             }
346              
347             =item B
348              
349             Returns the modulus of its argument, defined as the
350             square root of the scalar obtained by multiplying the quaternion
351             by its conjugate.
352              
353             my $q = Math::Quaternion->new(1,2,3,4);
354             print $q->modulus;
355              
356             =cut
357              
358             sub modulus {
359 3     3 1 419 my $q = shift;
360 3         9 return sqrt(squarednorm($q));
361             }
362              
363             =item B
364              
365             Returns 1 if the given quaternion is real ,ie has no quaternion
366             part, or else 0.
367              
368             my $q1 = Math::Quaternion->new(1,2,3,4);
369             my $q2 = Math::Quaternion->new(5,0,0,0);
370             print $q1->isreal; # 0;
371             print $q2->isreal; # 1;
372              
373             =cut
374              
375             sub isreal {
376 43     43 1 55 my $q = shift;
377 43         75 my ($q0,$q1,$q2,$q3)=@$q;
378              
379 43 100 66     180 if ( (0.0==$q1) && (0.0==$q2) && (0.0==$q3) ) {
      66        
380 9         31 return 1;
381             } else {
382 34         96 return 0;
383             }
384             }
385              
386             =item B
387              
388             Performs a quaternion multiplication of its two arguments.
389             If one of the arguments is a scalar, then performs a scalar
390             multiplication instead.
391              
392             my $q1 = Math::Quaternion->new(1,2,3,4);
393             my $q2 = Math::Quaternion->new(5,6,7,8);
394             my $q3 = Math::Quaternion::multiply($q1,$q2); # (-60 12 30 24)
395             my $q4 = Math::Quaternion::multiply($q1,$q1->inverse); # (1 0 0 0)
396              
397             =cut
398              
399             sub multiply {
400 48     48 1 2621 my ($a,$b,$reversed) = @_;
401 48 100       105 ($a,$b) = ($b,$a) if $reversed;
402              
403 48 100       114 if (!ref $a) { return scale($b,$a); }
  2         7  
404 46 100       96 if (!ref $b) { return scale($a,$b); }
  2         8  
405              
406 44         217 my $q = new Math::Quaternion;
407              
408 44         224 $q->[0] = $a->[0] * $b->[0]
409             - $a->[1]*$b->[1]
410             - $a->[2]*$b->[2]
411             - $a->[3]*$b->[3];
412            
413 44         198 $q->[1] = $a->[0] * $b->[1]
414             + $b->[0] * $a->[1]
415             + $a->[2] * $b->[3] - $a->[3] * $b->[2];
416              
417 44         148 $q->[2] = $a->[0] * $b->[2]
418             + $b->[0] * $a->[2]
419             + $a->[3] * $b->[1] - $a->[1] * $b->[3];
420              
421 44         114 $q->[3] = $a->[0] * $b->[3]
422             + $b->[0] * $a->[3]
423             + $a->[1] * $b->[2] - $a->[2] * $b->[1];
424 44         140 return $q;
425             }
426              
427             =item B
428              
429             Returns the dot product of two quaternions.
430              
431             my $q1=Math::Quaternion->new(1,2,3,4);
432             my $q2=Math::Quaternion->new(2,4,5,6);
433             my $q3 = Math::Quaternion::dot($q1,$q2);
434              
435             =cut
436              
437             sub dot {
438 4     4 1 9 my ($q1,$q2) = @_;
439 4         7 my ($a0,$a1,$a2,$a3) = @$q1;
440 4         10 my ($b0,$b1,$b2,$b3) = @$q2;
441 4         20 return $a0*$b0 + $a1*$b1 + $a2*$b2 + $a3*$b3 ;
442             }
443              
444             =item B
445              
446             Performs a quaternion addition of its two arguments.
447              
448             my $q1 = Math::Quaternion->new(1,2,3,4);
449             my $q2 = Math::Quaternion->new(5,6,7,8);
450             my $q3 = Math::Quaternion::plus($q1,$q2); # (6 8 10 12)
451              
452             =cut
453              
454              
455             sub plus {
456 18     18 1 1253 my ($a,$b,$reversed)=@_;
457 18         86 my $q = Math::Quaternion->new(
458             $a->[0] + $b->[0],
459             $a->[1] + $b->[1],
460             $a->[2] + $b->[2],
461             $a->[3] + $b->[3],
462             );
463              
464 18         75 return $q;
465              
466             }
467              
468             =item B
469              
470             Performs a quaternion subtraction of its two arguments.
471              
472             my $q1 = Math::Quaternion->new(1,2,3,4);
473             my $q2 = Math::Quaternion->new(5,6,7,8);
474             my $q3 = Math::Quaternion::minus($q1,$q2); # (-4 -4 -4 -4)
475              
476             =cut
477              
478             sub minus {
479 3     3 1 301 my ($a,$b,$reversed)=@_;
480 3 50       13 ($a,$b) = ($b,$a) if $reversed;
481 3         21 my $q = Math::Quaternion->new(
482             $a->[0] - $b->[0],
483             $a->[1] - $b->[1],
484             $a->[2] - $b->[2],
485             $a->[3] - $b->[3],
486             );
487              
488 3         39 return $q;
489              
490             }
491              
492             =item B
493              
494             Raise a quaternion to a scalar or quaternion power.
495              
496             my $q1 = Math::Quaternion->new(1,2,3,4);
497             my $q2 = Math::Quaternion::power($q1,4); # ( 668 -224 -336 -448 )
498             my $q3 = $q1->power(4); # ( 668 -224 -336 -448 )
499             my $q4 = $q1**(-1); # Same as $q1->inverse
500              
501             use Math::Trig;
502             my $q5 = exp(1)**( Math::Quaternion->new(pi,0,0) ); # approx (-1 0 0 0)
503              
504             =cut
505              
506             sub power {
507 14     14 1 2866 my ($a,$b,$reversed)=@_;
508 14 100       38 ($a,$b) = ($b,$a) if $reversed;
509              
510 14 100       46 if (ref $a) {
511 10         25 $a = Math::Quaternion->new($a);
512             }
513              
514 14 100       35 if (ref $b) {
515             # For quaternion^quaternion, use exp and log.
516 7         17 return Math::Quaternion::exp(Math::Quaternion::multiply($b,Math::Quaternion::log($a)));
517             }
518              
519             # For real_quaternion^real_number, use built-in power.
520 7 100       19 if ($a->isreal) {
521 1         6 return Math::Quaternion->new( $a->[0] ** $b, 0, 0, 0 ) ;
522             }
523              
524             # For quat raised to a scalar power, do it manually.
525              
526 6         13 my ($a0,$a1,$a2,$a3) = @$a;
527              
528 6         24 my $s = sqrt($a->squarednorm);
529 6         29 my $theta = Math::Trig::acos($a0/$s);
530 6         212 my $vecmod = sqrt($a1*$a1+$a2*$a2+$a3*$a3);
531 6         44 my $stob = ($s**$b);
532 6         28 my $coeff = $stob/$vecmod*sin($b*$theta);
533            
534 6         9 my $u1 = $a1*$coeff;
535 6         9 my $u2 = $a2*$coeff;
536 6         10 my $u3 = $a3*$coeff;
537              
538              
539 6         31 return Math::Quaternion->new(
540             $stob * cos($b*$theta), $u1,$u2,$u3
541             );
542            
543              
544             }
545              
546             =item B
547              
548             Negates the given quaternion.
549              
550             my $q = Math::Quaternion->new(1,2,3,4);
551             my $q1 = $q->negate; # (-1,-2,-3,-4)
552              
553             =cut
554              
555             sub negate {
556              
557 3     3 1 1165 my $q = shift;
558 3         17 return Math::Quaternion->new(
559             -($q->[0]),
560             -($q->[1]),
561             -($q->[2]),
562             -($q->[3]),
563             );
564              
565             }
566              
567              
568             =item B
569              
570             Returns the squared norm of its argument.
571              
572             my $q1 = Math::Quaternion->new(1,2,3,4);
573             my $sn = $q1->squarednorm; # 30
574              
575             =cut
576              
577             sub squarednorm {
578 24     24 1 33 my $q = shift;
579 24         149 return $q->[0]*$q->[0]
580             + $q->[1]*$q->[1]
581             + $q->[2]*$q->[2]
582             + $q->[3]*$q->[3];
583              
584             }
585              
586             =item B
587              
588             Performs a scalar multiplication of its two arguments.
589              
590             my $q = Math::Quaternion->new(1,2,3,4);
591             my $qq = Math::Quaternion::scale($q,2); # ( 2 4 6 8)
592             my $qqq= $q->scale(3); # ( 3 6 9 12 )
593              
594             =cut
595              
596             sub scale {
597 24     24 1 39 my ($q,$s)=@_;
598 24         176 return Math::Quaternion->new(
599             $q->[0] * $s,
600             $q->[1] * $s,
601             $q->[2] * $s,
602             $q->[3] * $s
603             );
604             }
605              
606             =item B
607              
608              
609             Generates a quaternion corresponding to a rotation.
610              
611             If given three arguments, interprets them as an angle and the
612             three components of an axis vector.
613              
614             use Math::Trig; # Define pi. my $theta = pi/2;
615             # Angle of rotation my $rotquat =
616             Math::Quaternion::rotation($theta,0,0,1);
617            
618             # $rotquat now represents a rotation of 90 degrees about Z axis.
619            
620             my ($x,$y,$z) = (1,0,0); # Unit vector in the X direction.
621             my ($xx,$yy,$zz) = $rotquat->rotate_vector($x,$y,$z);
622            
623             # ($xx,$yy,$zz) is now ( 0, 1, 0), to within floating-point error.
624              
625              
626             rotation() can also be passed a scalar angle and a reference to
627             a vector (in either order), and will generate the corresponding
628             rotation quaternion.
629              
630             my @axis = (0,0,1); # Rotate about Z axis
631             $theta = pi/2;
632             $rotquat = Math::Quaternion::rotation($theta,\@axis);
633              
634              
635             If the arguments to rotation() are both references, they are
636             interpreted as two vectors, and a quaternion is returned which
637             rotates the first vector onto the second.
638              
639             my @startvec = (0,1,0); # Vector pointing north
640             my @endvec = (-1,0,0); # Vector pointing west
641             $rotquat = Math::Quaternion::rotation(\@startvec,\@endvec);
642            
643             my @newvec = $rotquat->rotate_vector(@startvec); # Same as @endvec
644              
645             =cut
646              
647             sub rotation {
648 15     15 1 907 my ($theta,$x,$y,$z);
649 15 100       43 if (2==@_) {
    100          
650 10 100       21 if (ref($_[0])) {
651 5 100       13 if (ref($_[1])) {
652             # Both args references to vectors
653 4         6 my ($ax,$ay,$az)=@{$_[0]};
  4         10  
654 4         6 my ($bx,$by,$bz)=@{$_[1]};
  4         10  
655              
656 4 0 33     43 if ( (($ax == 0) and ($ay == 0) and ($az == 0)) or
      33        
      33        
      33        
      33        
657             (($bx == 0) and ($by == 0) and ($bz == 0)) ) {
658 0         0 croak("Math::Quaternion::rotation() passed zero-length vector");
659             }
660              
661             # Find cross product. This is a vector perpendicular to both
662             # argument vectors, and is therefore the axis of rotation.
663              
664 4         10 $x = $ay*$bz-$az*$by;
665 4         7 $y = $az*$bx-$ax*$bz;
666 4         8 $z = $ax*$by-$ay*$bx;
667              
668             # find the dot product.
669              
670 4         17 my $dotprod = $ax*$bx+$ay*$by+$az*$bz;
671 4         9 my $mod1 = sqrt($ax*$ax+$ay*$ay+$az*$az);
672 4         9 my $mod2 = sqrt($bx*$bx+$by*$by+$bz*$bz);
673              
674             # Find the angle of rotation.
675 4         21 $theta=Math::Trig::acos($dotprod/($mod1*$mod2));
676            
677             # Check for parallel vectors (cross product is zero)
678              
679 4 50 66     493 if (($x == 0) and ($y == 0) and ($z == 0)) {
      66        
680              
681             # Vectors a and b are parallel, such that rotation
682             # vector is the zero-length vector (0,0,0), with
683             # theta either 0 or pi (if vectors are opposite).
684             # To remove round-off errors in theta, explicitly
685             # set it.
686              
687 2 100       7 $theta = $dotprod > 0 ? 0 : pi;
688              
689             # Such a zero-length rotation vector is annoying (e.g.
690             # division by 0 on normalization, and problems combining
691             # rotations). To solve this, select a random rotation
692             # vector that is also perpendicular to both parallel
693             # vectors a and b. This satisfies the rotation requirement,
694             # and helps programs relying on the logic that the rotation
695             # vector has to be perpendicular to both vectors given
696             # (even if there are an infinite amount of rotation vectors
697             # that would satisfy that condition). Algorithm: Find a
698             # random vector b at any non-zero angle to vector a. One of
699             # the main axis will do. To reduce round-off errors, make b
700             # as perpendicular as possible to a by selecting one of the
701             # smallest components of vector a as the main component of
702             # b. This also avoid accidentally selecting a vector
703             # parallel to a
704              
705 2 100 66     29 if ( (abs($ax) <= abs($ay)) and (abs($ax) <= abs($az)) ) {
    50 33        
706 1         4 ($bx,$by,$bz)=(1,0,0);
707             } elsif ( (abs($ay) <= abs($ax)) and (abs($ay) <= abs($az)) ) {
708 1         2 ($bx,$by,$bz)=(0,1,0);
709             } else {
710 0         0 ($bx,$by,$bz)=(0,0,1);
711             }
712              
713             # Then, take the cross product between vector a and the new
714             # vector b, to generate some vector exactly perpendicular
715             # to vector a and hence also perpendicular to the original
716             # vector b (i.e. @{$_[1]})
717              
718 2         5 $x = $ay*$bz-$az*$by;
719 2         4 $y = $az*$bx-$ax*$bz;
720 2         5 $z = $ax*$by-$ay*$bx;
721              
722             # ($x,$y,$z) is now a random yet valid rotation vector
723             # perpendicular to the two original vectors.
724              
725             }
726             } else {
727             # 0 is a ref, 1 is not.
728 1         3 $theta = $_[1]; ($x,$y,$z)=@{$_[0]};
  1         1  
  1         4  
729             }
730             } else {
731 5 100       13 if (ref($_[1])) {
732             # 1 is a ref, 0 is not
733 4         8 $theta = $_[0]; ($x,$y,$z)=@{$_[1]};
  4         4  
  4         11  
734             } else {
735 1         232 croak("Math::Quaternion::rotation() passed 2 nonref args");
736             }
737             }
738             } elsif (4==@_) {
739 4         20 ($theta,$x,$y,$z) = @_;
740             } else {
741 1         144 croak("Math::Quaternion::rotation() passed wrong no of arguments");
742             }
743              
744 13         72 my $modulus = sqrt($x*$x+$y*$y+$z*$z); # Make it a unit vector
745 13 50       39 if ($modulus == 0) {
746 0         0 croak("Math::Quaternion::rotation() passed zero-length rotation vector");
747             }
748 13         16 $x /= $modulus;
749 13         17 $y /= $modulus;
750 13         14 $z /= $modulus;
751              
752 13         26 my $st = sin(0.5 * $theta);
753 13         40 my $ct = cos(0.5 * $theta);
754              
755 13         48 return Math::Quaternion->new(
756             $ct, $x * $st, $y * $st, $z * $st
757             );
758             }
759              
760             =item B
761              
762             Returns the angle of rotation represented by the quaternion
763             argument.
764              
765             my $q = Math::Quaternion::rotation(0.1,2,3,4);
766             my $theta = $q->rotation_angle; # Returns 0.1 .
767              
768             =cut
769              
770             sub rotation_angle {
771 4     4 1 1023 my $q = shift;
772 4         16 return 2.0 * Math::Trig::acos($q->[0]);
773             }
774              
775             =item B
776              
777             Returns the unit vector representing the axis about which
778             rotations will be performed, for the rotation represented by the
779             quaternion argument.
780              
781             my $q = Math::Quaternion::rotation(0.1,1,1,0);
782             my @v = $q->rotation_axis; # Returns (0.5*sqrt(2),0.5*sqrt(2),0)
783              
784             =cut
785              
786             sub rotation_axis {
787 5     5 1 295 my $q = shift;
788 5         18 my $theta = Math::Trig::acos($q->[0]);
789 5         36 my $st = sin($theta);
790 5 100       16 if (0==$st) { return (0,0,1); } # Rotation of angle zero about Z axis
  1         3  
791 4         7 my ($x,$y,$z) = @{$q}[1,2,3];
  4         9  
792              
793 4         26 return ( $x/$st, $y/$st, $z/$st );
794             }
795              
796              
797              
798              
799             =item B
800              
801             When called as a method on a rotation quaternion, uses this
802             quaternion to perform the corresponding rotation on the vector
803             argument.
804              
805             use Math::Trig; # Define pi.
806            
807             my $theta = pi/2; # Rotate 90 degrees
808            
809             my $rotquat = Math::Quaternion::rotation($theta,0,0,1); # about Z axis
810            
811             my ($x,$y,$z) = (1,0,0); # Unit vector in the X direction.
812             my ($xx,$yy,$zz) = $rotquat->rotate_vector($x,$y,$z)
813            
814             # ($xx,$yy,$zz) is now ( 0, 1, 0), to within floating-point error.
815              
816             =cut
817              
818              
819             sub rotate_vector {
820 3     3 1 1027 my ($q,$x,$y,$z) = @_;
821              
822 3         10 my $p = Math::Quaternion->new($x,$y,$z);
823 3         10 my $qq = multiply($q,multiply($p,inverse($q)));
824 3         11 return @{$qq}[1,2,3];
  3         17  
825             }
826              
827              
828             =item B
829              
830             Takes one argument: a rotation quaternion.
831             Returns a 16-element array, equal to the OpenGL
832             matrix which represents the corresponding rotation.
833              
834             my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
835             my @m = $rotquat->matrix4x4;
836              
837             =cut
838              
839             sub matrix4x4 {
840 1     1 1 1059 my $q = shift;
841 1         3 my ($w,$x,$y,$z) = @{$q};
  1         3  
842              
843             return (
844 1         21 1 - 2*$y*$y - 2*$z*$z,
845             2*$x*$y + 2*$w*$z,
846             2*$x*$z - 2*$w*$y,
847             0,
848              
849             2*$x*$y - 2*$w*$z,
850             1 - 2*$x*$x - 2*$z*$z,
851             2*$y*$z + 2*$w*$x,
852             0,
853              
854             2*$x*$z + 2*$w*$y,
855             2*$y*$z - 2*$w*$x,
856             1 - 2*$x*$x - 2*$y*$y,
857             0,
858              
859             0,
860             0,
861             0,
862             1
863             );
864             }
865              
866             =item B
867              
868             Takes one argument: a rotation quaternion.
869             Returns a 9-element array, equal to the 3x3
870             matrix which represents the corresponding rotation.
871              
872             my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
873             my @m = $rotquat->matrix3x3;
874              
875             =cut
876              
877             sub matrix3x3 {
878 1     1 1 680 my $q = shift;
879 1         3 my ($w,$x,$y,$z) = @{$q};
  1         4  
880              
881             return (
882 1         18 1 - 2*$y*$y - 2*$z*$z,
883             2*$x*$y + 2*$w*$z,
884             2*$x*$z - 2*$w*$y,
885              
886             2*$x*$y - 2*$w*$z,
887             1 - 2*$x*$x - 2*$z*$z,
888             2*$y*$z + 2*$w*$x,
889              
890             2*$x*$z + 2*$w*$y,
891             2*$y*$z - 2*$w*$x,
892             1 - 2*$x*$x - 2*$y*$y,
893             );
894             }
895              
896             =item B
897              
898             Similar to matrix4x4, but returnes a list of two array
899             references. The first is a reference to the rotation matrix;
900             the second is a reference to its inverse. This may be useful
901             when rendering sprites, since you can multiply by the rotation
902             matrix for the viewer position, perform some translations, and
903             then multiply by the inverse: any resulting rectangles drawn
904             will always face the viewer.
905              
906              
907             my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
908             my ($matref,$invref) = $rotquat->matrix4x4andinverse;
909              
910             =cut
911              
912              
913             sub matrix4x4andinverse {
914 1     1 1 3 my $q = shift;
915 1         2 my ($w,$x,$y,$z) = @{$q};
  1         3  
916 1         2 my (@m,@mi);
917              
918 1         5 $mi[ 0] = $m[ 0] = 1 - 2*$y*$y - 2*$z*$z;
919 1         4 $mi[ 4] = $m[ 1] = 2*$x*$y + 2*$w*$z;
920 1         5 $mi[ 8] = $m[ 2] = 2*$x*$z - 2*$w*$y;
921 1         23 $mi[12] = $m[ 3] = 0;
922              
923 1         5 $mi[ 1] = $m[ 4] = 2*$x*$y - 2*$w*$z;
924 1         4 $mi[ 5] = $m[ 5] = 1 - 2*$x*$x - 2*$z*$z;
925 1         5 $mi[ 9] = $m[ 6] = 2*$y*$z + 2*$w*$x;
926 1         2 $mi[13] = $m[ 7] = 0;
927              
928 1         4 $mi[ 2] = $m[ 8] = 2*$x*$z + 2*$w*$y;
929 1         3 $mi[ 6] = $m[ 9] = 2*$y*$z - 2*$w*$x;
930 1         4 $mi[10] = $m[10] = 1 - 2*$x*$x - 2*$y*$y;
931 1         2 $mi[14] = $m[11] = 0;
932              
933 1         3 $mi[ 3] = $m[12] = 0;
934 1         2 $mi[ 7] = $m[13] = 0;
935 1         2 $mi[11] = $m[14] = 0;
936 1         2 $mi[15] = $m[15] = 1;
937              
938 1         4 return (\@m,\@mi);
939              
940             }
941              
942             =item B
943              
944             Returns a string representation of the quaternion. This is used
945             to overload the '""' operator, so that quaternions may be
946             freely interpolated in strings.
947              
948             my $q = Math::Quaternion->new(1,2,3,4);
949             print $q->stringify; # "( 1 2 3 4 )"
950             print "$q"; # "( 1 2 3 4 )"
951              
952              
953             =cut
954              
955             sub stringify {
956 2     2 1 12 my $self = shift;
957 2         18 return "( ".join(" ",@$self)." )";
958             }
959              
960             =item B
961              
962             Takes two quaternion arguments and one scalar; performs
963             spherical linear interpolation between the two quaternions. The
964             quaternion arguments are assumed to be unit quaternions, and the
965             scalar is assumed to lie between 0 and 1: a scalar argument of
966             zero will return the first quaternion argument, and a scalar
967             argument of one will return the second.
968              
969             use Math::Trig;
970             my @axis = (0,0,1);
971             my $rq1 = Math::Quaternion::rotation(pi/2,\@axis); # 90 degs about Z
972             my $rq2 = Math::Quaternion::rotation(pi,\@axis); # 180 degs about Z
973            
974             my $interp = Math::Quaternion::slerp($rq1,$rq2,0.5); # 135 degs about Z
975              
976             =cut
977              
978             sub slerp {
979 3     3 1 17 my ($q0,$q1,$t) = @_;
980              
981 3         8 my $dotprod = dot($q0,$q1);
982 3 100       10 if ($dotprod<0) {
983             # Reverse signs so we travel the short way round
984 1         2 $dotprod = -$dotprod;
985 1         4 $q1 = negate($q1);
986             }
987              
988 3         10 my $theta = Math::Trig::acos($dotprod);
989              
990 3 100       27 if (abs($theta) < 1e-5) {
991             # In the limit theta->0 , spherical interpolation is
992             # approximated by linear interpolation, which also
993             # avoids division-by-zero problems.
994              
995 1         5 return plus(scale($q0,(1-$t)) ,scale($q1,$t));
996              
997             }
998              
999 2         5 my $st = sin($theta);
1000 2         4 my $ist = 1.0/$st;
1001              
1002 2         8 my $q = plus(
1003             scale($q0,($ist * sin( (1-$t)*$theta ))),
1004             scale($q1,($ist*sin($t*$theta)))
1005             );
1006              
1007            
1008 2         9 return normalize($q);
1009              
1010             }
1011              
1012              
1013             =item B
1014              
1015             Exponential operator e^q. Any quaternion q can be written as x+uy,
1016             where x is a real number, and u is a unit pure quaternion. Then,
1017             exp(q) == exp(x) * ( cos(y) + u sin(y) ).
1018              
1019             my $q = Math::Quaternion->new(1,2,3,4);
1020             print Math::Quaternion::exp($q);
1021              
1022             =cut
1023              
1024             sub exp {
1025 18     18 1 28 my $q = shift;
1026              
1027 18 100       31 if (isreal($q)) {
1028 1         6 return Math::Quaternion->new(CORE::exp($q->[0]),0,0,0);
1029             }
1030              
1031 17         28 my ($q0,$q1,$q2,$q3)=@$q;
1032              
1033 17         38 my $y = sqrt($q1*$q1+$q2*$q2+$q3*$q3); # Length of pure-quat part.
1034 17         32 my ($ux,$uy,$uz) = ($q1/$y,$q2/$y,$q3/$y); # Unit vector.
1035              
1036 17         64 my $ex = CORE::exp($q0);
1037 17         34 my $exs = $ex*sin($y);
1038              
1039 17         94 return Math::Quaternion->new($ex*cos($y),$exs*$ux,$exs*$uy,$exs*$uz);
1040             }
1041              
1042             =item B
1043              
1044             Returns the logarithm of its argument. The logarithm of a negative
1045             real quaternion can take any value of them form (log(-q0),u*pi) for
1046             any unit vector u. In these cases, u is chosen to be (1,0,0).
1047              
1048             my $q = Math::Quaternion->new(1,2,3,4);
1049             print Math::Quaternion::log($q);
1050              
1051             =cut
1052              
1053             sub log {
1054 17     17 1 807 my $q = shift;
1055              
1056 17 100       34 if (ref $q) {
1057 13 100       46 if ("Math::Quaternion" ne ref $q) {
1058 1         3 $q = Math::Quaternion->new($q);
1059             }
1060             } else {
1061 4         12 $q = Math::Quaternion->new($q);
1062             }
1063              
1064 17 100       29 if (isreal($q)) {
1065 6 100       18 if ($q->[0] > 0) {
1066 5         28 return Math::Quaternion->new(CORE::log($q->[0]));
1067             } else {
1068 1         6 return Math::Quaternion->new(CORE::log(-($q->[0])),pi,0,0);
1069             }
1070             }
1071              
1072 11         32 my ($q0,$q1,$q2,$q3)=@$q;
1073              
1074 11         26 my $modq = sqrt($q0*$q0 + $q1*$q1 + $q2*$q2 + $q3*$q3);
1075              
1076 11         18 my $x = CORE::log($modq);
1077 11         21 my $qquatmod = sqrt($q1*$q1+$q2*$q2+$q3*$q3); # mod of quat part
1078 11         26 my $y = atan2($qquatmod,$q0);
1079 11         15 my $c = $y/$qquatmod;
1080              
1081 11         51 return Math::Quaternion->new($x,$c*$q1,$c*$q2,$c*$q3);
1082            
1083             }
1084              
1085              
1086              
1087             =back
1088              
1089             =head1 AUTHOR
1090              
1091             Jonathan Chin, Ejon-quaternion.pm@earth.liE
1092              
1093             =head1 ACKNOWLEDGEMENTS
1094              
1095             Thanks to Rene Uittenbogaard and Daniel Connelly for useful suggestions, and
1096             Luc Vereecken and Bruce Gray for patches.
1097              
1098             =head1 SEE ALSO
1099              
1100             =over 4
1101              
1102             =item L
1103              
1104             =item L
1105              
1106             =item Acts 12:4
1107              
1108             =back
1109              
1110             =head1 COPYRIGHT AND LICENSE
1111              
1112             Copyright 2003 by Jonathan Chin
1113              
1114             This library is free software; you can redistribute it and/or modify
1115             it under the same terms as Perl itself.
1116              
1117             =cut
1118              
1119             1;
1120             __END__