File Coverage

blib/lib/Math/Vector/Real.pm
Criterion Covered Total %
statement 103 372 27.6
branch 19 124 15.3
condition 4 17 23.5
subroutine 23 60 38.3
pod 27 52 51.9
total 176 625 28.1


line stmt bran cond sub pod time code
1             package Math::Vector::Real;
2              
3             our $VERSION = '0.16';
4              
5 1     1   17535 use strict;
  1         2  
  1         42  
6 1     1   6 use warnings;
  1         2  
  1         37  
7 1     1   5 use Carp;
  1         5  
  1         101  
8 1     1   686 use POSIX ();
  1         5416  
  1         37  
9              
10 1     1   7 use Exporter qw(import);
  1         1  
  1         4026  
11             our @EXPORT = qw(V);
12              
13             our $dont_use_XS;
14             unless ($dont_use_XS) {
15             my $xs_version = do {
16             local ($@, $!, $SIG{__DIE__});
17             eval {
18             require Math::Vector::Real::XS;
19             $Math::Vector::Real::XS::VERSION;
20             }
21             };
22              
23             if (defined $xs_version and $xs_version < 0.07) {
24             croak "Old and buggy version of Math::Vector::Real::XS detected, update it!";
25             }
26             }
27              
28              
29             our %op = (add => '+',
30             neg => 'neg',
31             sub => '-',
32             mul => '*',
33             div => '/',
34             cross => 'x',
35             add_me => '+=',
36             sub_me => '-=',
37             mul_me => '*=',
38             div_me => '/=',
39             abs => 'abs',
40             atan2 => 'atan2',
41             equal => '==',
42             nequal => '!=',
43             clone => '=',
44             as_string => '""');
45              
46             our %ol;
47             $ol{$op{$_}} = \&{${Math::Vector::Real::}{$_}} for keys %op;
48              
49             require overload;
50             overload->import(%ol);
51              
52 6     6 0 23 sub V { bless [@_] }
53              
54             sub new {
55 0     0 1 0 my $class = shift;
56 0         0 bless [@_], $class
57             }
58              
59             sub new_ref {
60 0     0 0 0 my $class = shift;
61 0         0 bless [@{shift()}], $class;
  0         0  
62             }
63              
64             sub zero {
65 0     0 1 0 my ($class, $dim) = @_;
66 0 0       0 $dim >= 0 or croak "negative dimension";
67 0         0 bless [(0) x $dim], $class
68             }
69              
70             sub is_zero {
71 0   0 0 0 0 $_ and return 0 for @$_[0];
72 0         0 return 1
73             }
74              
75             sub cube {
76 0     0 1 0 my ($class, $dim, $size) = @_;
77 0         0 bless [($size) x $dim], $class;
78             }
79              
80             sub axis_versor {
81 0     0 1 0 my ($class, $dim, $ix);
82 0 0       0 if (ref $_[0]) {
83 0         0 my ($self, $ix) = @_;
84 0         0 $class = ref $self;
85 0         0 $dim = @$self;
86             }
87             else {
88 0         0 ($class, $dim, $ix) = @_;
89 0 0       0 $dim >= 0 or croak "negative dimension";
90             }
91 0 0 0     0 ($ix >= 0 and $ix < $dim) or croak "axis index out of range";
92 0         0 my $self = [(0) x $dim];
93 0         0 $self->[$ix] = 1;
94 0         0 bless $self, $class
95             }
96              
97             sub _caller_op {
98 0   0 0   0 my $level = (shift||1) + 1;
99 0         0 my $sub = (caller $level)[3];
100 0         0 $sub =~ s/.*:://;
101 0         0 my $op = $op{$sub};
102 0 0       0 (defined $op ? $op : $sub);
103             }
104              
105             sub _check_dim {
106 78     78   160 local ($@, $SIG{__DIE__});
107 78 50       84 eval { @{$_[0]} == @{$_[1]} } and return;
  78         64  
  78         85  
  78         214  
108 0         0 my $op = _caller_op(1);
109 0 0       0 my $loc = ($_[2] ? 'first' : 'second');
110 0 0       0 UNIVERSAL::isa($_[1], 'ARRAY') or croak "$loc argument to vector operator '$op' is not a vector";
111 0         0 croak "vector dimensions do not match";
112             }
113              
114 0     0 0 0 sub clone { bless [@{$_[0]}] }
  0         0  
115              
116             sub set {
117 0     0 1 0 &_check_dim;
118 0         0 my ($v0, $v1) = @_;
119 0         0 $v0->[$_] = $v1->[$_] for 0..$#$v1;
120             }
121              
122             sub add {
123 13     13 0 324 &_check_dim;
124 13         12 my ($v0, $v1) = @_;
125 13         69 bless [map $v0->[$_] + $v1->[$_], 0..$#$v0]
126             }
127              
128             sub add_me {
129 0     0 0 0 &_check_dim;
130 0         0 my ($v0, $v1) = @_;
131 0         0 $v0->[$_] += $v1->[$_] for 0..$#$v0;
132 0         0 $v0;
133             }
134              
135 2     2 0 2 sub neg { bless [map -$_, @{$_[0]}] }
  2         9  
136              
137             sub sub {
138 16     16 0 26 &_check_dim;
139 16 100       30 my ($v0, $v1) = ($_[2] ? @_[1, 0] : @_);
140 16         70 bless [map $v0->[$_] - $v1->[$_], 0..$#$v0]
141             }
142              
143             sub sub_me {
144 0     0 0 0 &_check_dim;
145 0         0 my ($v0, $v1) = @_;
146 0         0 $v0->[$_] -= $v1->[$_] for 0..$#$v0;
147 0         0 $v0;
148             }
149              
150             sub mul {
151 46 100   46 0 62 if (ref $_[1]) {
152 17         18 &_check_dim;
153 17         20 my ($v0, $v1) = @_;
154 17         10 my $acu = 0;
155 17         55 $acu += $v0->[$_] * $v1->[$_] for 0..$#$v0;
156 17         38 $acu;
157             }
158             else {
159 29         26 my ($v, $s) = @_;
160 29         85 bless [map $s * $_, @$v];
161             }
162             }
163              
164             sub mul_me {
165 0 0   0 0 0 ref $_[1] and croak "can not multiply by a vector in place as the result is not a vector";
166 0         0 my ($v, $s) = @_;
167 0         0 $_ *= $s for @$v;
168 0         0 $v
169             }
170              
171             sub div {
172 6 50 33 6 0 27 croak "can't use vector as dividend"
173             if ($_[2] or ref $_[1]);
174 6         9 my ($v, $div) = @_;
175 6 50       10 $div == 0 and croak "illegal division by zero";
176 6         7 my $i = 1 / $div;
177 6         23 bless [map $i * $_, @$v]
178             }
179              
180             sub div_me {
181 1 50   1 0 6 croak "can't use vector as dividend" if ref $_[1];
182 1         2 my $v = shift;
183 1         3 my $i = 1.0 / shift;
184 1         6 $_ *= $i for @$v;
185 1         3 $v;
186             }
187              
188             sub equal {
189 16     16 0 24 &_check_dim;
190 16         38 my ($v0, $v1) = @_;
191 16   50     91 $v0->[$_] == $v1->[$_] || return 0 for 0..$#$v0;
192 16         53 1;
193             }
194              
195             sub nequal {
196 1     1 0 3 &_check_dim;
197 1         2 my ($v0, $v1) = @_;
198 1   100     12 $v0->[$_] == $v1->[$_] || return 1 for 0..$#$v0;
199 0         0 0;
200             }
201              
202             sub cross {
203 15     15 0 27 &_check_dim;
204 15 100       32 my ($v0, $v1) = ($_[2] ? @_[1, 0] : @_);
205 15         14 my $dim = @$v0;
206 15 50       25 if ($dim == 3) {
207 15         81 return bless [$v0->[1] * $v1->[2] - $v0->[2] * $v1->[1],
208             $v0->[2] * $v1->[0] - $v0->[0] * $v1->[2],
209             $v0->[0] * $v1->[1] - $v0->[1] * $v1->[0]]
210             }
211 0 0       0 if ($dim == 7) {
212 0         0 croak "cross product for dimension 7 not implemented yet, patches welcome!";
213             }
214             else {
215 0         0 croak "cross product not defined for dimension $dim"
216             }
217             }
218              
219 0     0 0 0 sub as_string { "{" . join(", ", @{$_[0]}). "}" }
  0         0  
220              
221             sub abs {
222 11     11 0 790 my $acu = 0;
223 11         14 $acu += $_ * $_ for @{$_[0]};
  11         37  
224 11         76 sqrt $acu;
225             }
226              
227             *norm = \&abs;
228              
229             sub abs2 {
230 4     4 0 4 my $acu = 0;
231 4         4 $acu += $_ * $_ for @{$_[0]};
  4         12  
232 4         7 $acu;
233             }
234              
235             *norm2 = \&abs2;
236              
237             sub dist {
238 0     0 1 0 &_check_dim;
239 0         0 my ($v0, $v1) = @_;
240 0         0 my $d2 = 0;
241 0         0 for (0..$#$v0) {
242 0         0 my $d = $v0->[$_] - $v1->[$_];
243 0         0 $d2 += $d * $d;
244             }
245 0         0 sqrt($d2);
246             }
247              
248             sub dist2 {
249 0     0 1 0 &_check_dim;
250 0         0 my ($v0, $v1) = @_;
251 0         0 my $d2 = 0;
252 0         0 for (0..$#$v0) {
253 0         0 my $d = $v0->[$_] - $v1->[$_];
254 0         0 $d2 += $d * $d;
255             }
256 0         0 $d2;
257             }
258              
259             sub manhattan_norm {
260 0     0 0 0 my $n = 0;
261 0         0 $n += CORE::abs($_) for @{$_[0]};
  0         0  
262 0         0 return $n;
263             }
264              
265             sub manhattan_dist {
266 0     0 0 0 &_check_dim;
267 0         0 my ($v0, $v1) = @_;
268 0         0 my $d = 0;
269 0         0 $d += CORE::abs($v0->[$_] - $v1->[$_]) for 0..$#$v0;
270 0         0 return $d;
271             }
272              
273             sub _upgrade {
274 0     0   0 my $dim;
275             map {
276 0         0 my $d = eval { @{$_} };
  0         0  
  0         0  
  0         0  
277 0 0       0 defined $d or croak "argument is not a vector or array";
278 0 0       0 if (defined $dim) {
279 0 0       0 $d == $dim or croak "dimensions do not match";
280             }
281             else {
282 0         0 $dim = $d;
283             }
284 0 0       0 UNIVERSAL::isa($_, __PACKAGE__) ? $_ : clone($_);
285             } @_;
286             }
287              
288             sub atan2 {
289 1     1 0 2 my ($v0, $v1) = @_;
290 1 50       4 if (@$v0 == 2) {
291 0         0 my $dot = $v0->[0] * $v1->[0] + $v0->[1] * $v1->[1];
292 0         0 my $cross = $v0->[0] * $v1->[1] - $v0->[1] * $v1->[0];
293 0         0 return CORE::atan2($cross, $dot);
294             }
295             else {
296 1         3 my $a0 = &abs($v0);
297 1 50       4 return 0 unless $a0;
298 1         3 my $u0 = $v0 / $a0;
299 1         3 my $p = $v1 * $u0;
300 1         3 CORE::atan2(&abs($v1 - $p * $u0), $p);
301             }
302             }
303              
304             sub versor {
305 8     8 1 7 my $self = shift;
306 8         10 my $f = 0;
307 8         23 $f += $_ * $_ for @$self;
308 8 50       16 $f == 0 and croak "Illegal division by zero";
309 8         9 $f = 1/sqrt $f;
310 8         27 bless [map $f * $_, @$self]
311             }
312              
313             sub wrap {
314 0     0 1 0 my ($self, $v) = @_;
315 0         0 &_check_dim;
316              
317 0         0 bless [map { my $s = $self->[$_];
  0         0  
318 0         0 my $c = $v->[$_];
319 0         0 $c - $s * POSIX::floor($c/$s) } (0..$#$self)];
320             }
321              
322             sub max_component {
323 0     0 1 0 my $max = 0;
324 0         0 for (@{shift()}) {
  0         0  
325 0         0 my $abs = CORE::abs($_);
326 0 0       0 $abs > $max and $max = $abs;
327             }
328             $max
329 0         0 }
330              
331             sub min_component {
332 0     0 1 0 my $self = shift;
333 0         0 my $min = CORE::abs($self->[0]);
334 0         0 for (@$self) {
335 0         0 my $abs = CORE::abs($_);
336 0 0       0 $abs < $min and $min = $abs;
337             }
338             $min
339 0         0 }
340              
341             *max = \&max_component;
342             *min = \&min_component;
343              
344             sub first_orthant_reflection {
345 0     0 1 0 my $self = shift;
346 0         0 bless [map CORE::abs, @$self];
347             }
348              
349             sub sum {
350 0 0   0 1 0 ref $_[0] or shift; # works both as a class and as an instance method
351 0         0 my $sum;
352 0 0       0 if (@_) {
353 0         0 $sum = V(@{shift()});
  0         0  
354 0         0 $sum += $_ for @_;
355             }
356 0         0 return $sum;
357             }
358              
359             sub box {
360 0     0 1 0 shift;
361 0 0       0 return unless @_;
362 0         0 my $min = clone(shift);
363 0         0 my $max = clone($min);
364 0         0 my $dim = $#$min;
365 0         0 for (@_) {
366 0         0 for my $ix (0..$dim) {
367 0         0 my $c = $_->[$ix];
368 0 0       0 if ($max->[$ix] < $c) {
    0          
369 0         0 $max->[$ix] = $c;
370             }
371             elsif ($min->[$ix] > $c) {
372 0         0 $min->[$ix] = $c
373             }
374             }
375             }
376 0 0       0 wantarray ? ($min, $max) : $max - $min;
377             }
378              
379             sub nearest_in_box {
380 0     0 1 0 my $p = shift->clone;
381 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
382 0         0 for (0..$#$p) {
383 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
384 0         0 $p->[$_] = $min->[$_];
385             }
386             elsif ($p->[$_] > $max->[$_]) {
387 0         0 $p->[$_] = $max->[$_];
388             }
389             }
390             $p
391 0         0 }
392              
393             sub dist2_to_box {
394 0 0   0 1 0 @_ > 1 or croak 'Usage: $v->dist2_to_box($w0, ...)';
395 0         0 my $p = shift;
396 0         0 my $d2 = 0;
397 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
398 0         0 for (0..$#$p) {
399 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
400 0         0 my $d = $p->[$_] - $min->[$_];
401 0         0 $d2 += $d * $d;
402             }
403             elsif ($p->[$_] > $max->[$_]) {
404 0         0 my $d = $p->[$_] - $max->[$_];
405 0         0 $d2 += $d * $d;
406             }
407             }
408 0         0 $d2;
409             }
410              
411             sub nearest_in_box_border {
412             # TODO: this method can be optimized
413 0     0 0 0 my $p = shift->clone;
414 0         0 my ($b0, $b1) = Math::Vector::Real->box(@_);
415 0         0 my $in = 0;
416 0         0 for (0..$#$p) {
417 0 0       0 if ($p->[$_] < $b0->[$_]) {
    0          
418 0         0 $p->[$_] = $b0->[$_];
419             }
420             elsif ($p->[$_] > $b1->[$_]) {
421 0         0 $p->[$_] = $b1->[$_];
422             }
423             else {
424 0         0 $in++;
425             }
426             }
427 0 0       0 if ($in == @$p) {
428             # vector was inside the box
429 0         0 my $min_d = 'inf';
430 0         0 my ($comp, $comp_ix);
431 0         0 for my $q ($b0, $b1) {
432 0         0 for (0..$#$p) {
433 0         0 my $d = CORE::abs($p->[$_] - $q->[$_]);
434 0 0       0 if ($min_d > $d) {
435 0         0 $min_d = $d;
436 0         0 $comp = $q->[$_];
437 0         0 $comp_ix = $_;
438             }
439             }
440             }
441 0         0 $p->[$comp_ix] = $comp;
442             }
443 0         0 $p;
444             }
445              
446             sub max_dist2_to_box {
447 0 0   0 1 0 @_ > 1 or croak 'Usage: $v->max_dist2_to_box($w0, ...)';
448 0         0 my $p = shift;
449 0         0 my ($c0, $c1) = Math::Vector::Real->box(@_);
450 0         0 my $d2 = 0;
451 0         0 for (0..$#$p) {
452 0         0 my $d0 = CORE::abs($c0->[$_] - $p->[$_]);
453 0         0 my $d1 = CORE::abs($c1->[$_] - $p->[$_]);
454 0 0       0 $d2 += ($d0 >= $d1 ? $d0 * $d0 : $d1 * $d1);
455             }
456 0         0 return $d2;
457             }
458              
459             sub dist2_between_boxes {
460 0     0 1 0 my ($class, $a0, $a1, $b0, $b1) = @_;
461 0         0 my ($c0, $c1) = $class->box($a0, $a1);
462 0         0 my ($d0, $d1) = $class->box($b0, $b1);
463 0         0 my $d2 = 0;
464 0         0 for (0..$#$c0) {
465 0         0 my $e0 = $d0->[$_] - $c1->[$_];
466 0 0       0 if ($e0 >= 0) {
467 0         0 $d2 += $e0 * $e0;
468             }
469             else {
470 0         0 my $e1 = $c0->[$_] - $d1->[$_];
471 0 0       0 if ($e1 > 0) {
472 0         0 $d2 += $e1 * $e1;
473             }
474             }
475             }
476 0         0 $d2;
477             }
478              
479             *min_dist2_between_boxes = \&dist2_between_boxes;
480              
481             sub max_dist2_between_boxes {
482 0     0 1 0 my ($class, $a0, $a1, $b0, $b1) = @_;
483 0         0 my ($c0, $c1) = $class->box($a0, $a1);
484 0         0 my ($d0, $d1) = $class->box($b0, $b1);
485 0         0 my $d2 = 0;
486 0         0 for (0..$#$c0) {
487 0         0 my $e0 = $d1->[$_] - $c0->[$_];
488 0         0 my $e1 = $d0->[$_] - $c1->[$_];
489 0         0 $e0 *= $e0;
490 0         0 $e1 *= $e1;
491 0 0       0 $d2 += ($e0 > $e1 ? $e0 : $e1);
492             }
493 0         0 $d2;
494             }
495              
496             sub max_component_index {
497 0     0 1 0 my $self = shift;
498 0 0       0 return unless @$self;
499 0         0 my $max = 0;
500 0         0 my $max_ix = 0;
501 0         0 for my $ix (0..$#$self) {
502 0         0 my $c = CORE::abs($self->[$ix]);
503 0 0       0 if ($c > $max) {
504 0         0 $max = $c;
505 0         0 $max_ix = $ix;
506             }
507             }
508 0         0 $max_ix;
509             }
510              
511             sub min_component_index {
512 0     0 0 0 my $self = shift;
513 0 0       0 return unless @$self;
514 0         0 my $min = CORE::abs($self->[0]);
515 0         0 my $min_ix = 0;
516 0         0 for my $ix (1..$#$self) {
517 0         0 my $c = CORE::abs($self->[$ix]);
518 0 0       0 if ($c < $min) {
519 0         0 $min = $c;
520 0         0 $min_ix = $ix
521             }
522             }
523 0         0 $min_ix;
524             }
525              
526             sub decompose {
527 4     4 1 13 my ($u, $v) = @_;
528 4         9 my $p = $u * ($u * $v)/abs2($u);
529 4         9 my $n = $v - $p;
530 4 50       12 wantarray ? ($p, $n) : $n;
531             }
532              
533             sub canonical_base {
534 0     0 1 0 my ($class, $dim) = @_;
535 0         0 my @base = map { bless [(0) x $dim], $class } 1..$dim;
  0         0  
536 0         0 $base[$_][$_] = 1 for 0..$#base;
537 0         0 return @base;
538             }
539              
540             sub rotation_base_3d {
541 4     4 1 5 my $v = shift;
542 4 50       12 @$v == 3 or croak "rotation_base_3d requires a vector with three dimensions";
543 4         8 $v = $v->versor;
544 4         6 my $n = [0, 0, 0];
545 4         5 for (0..2) {
546 7 100       14 if (CORE::abs($v->[$_]) > 0.57) {
547 4         8 $n->[($_ + 1) % 3] = 1;
548 4         8 $n = $v->decompose($n)->versor;
549 4         9 return ($v, $n, $v x $n);
550             }
551             }
552 0         0 die "internal error, all the components where smaller than 0.57!";
553             }
554              
555             sub rotate_3d {
556 3     3 1 5 my $v = shift;
557 3         14 my $angle = shift;
558 3         10 my $c = cos($angle); my $s = sin($angle);
  3         5  
559 3         8 my ($i, $j, $k) = $v->rotation_base_3d;
560 3         4 my $rj = $c * $j + $s * $k;
561 3         6 my $rk = $c * $k - $s * $j;
562 3 50       7 if (wantarray) {
563 0         0 return map { ($_ * $i) * $i + ($_ * $j) * $rj + ($_ * $k) * $rk } @_;
  0         0  
564             }
565             else {
566 3         3 my $a = shift;
567 3         4 return (($a * $i) * $i + ($a * $j) * $rj + ($a * $k) * $rk);
568             }
569             }
570              
571 0     0 1   sub normal_base { __PACKAGE__->complementary_base(@_) }
572              
573             sub complementary_base {
574 0     0 1   shift;
575 0 0         @_ or croak "complementaty_base requires at least one argument in order to determine the dimension";
576 0           my $dim = @{$_[0]};
  0            
577 0 0 0       if ($dim == 2 and @_ == 1) {
578 0           my $u = versor($_[0]);
579 0           @$u = ($u->[1], -$u->[0]);
580 0           return $u;
581             }
582              
583 0           my @v = map clone($_), @_;
584 0           my @base = Math::Vector::Real->canonical_base($dim);
585 0           for my $i (0..$#v) {
586 0           my $u = versor($v[$i]);
587 0           $_ = decompose($u, $_) for @v[$i+1 .. $#v];
588 0           $_ = decompose($u, $_) for @base;
589             }
590              
591 0           my $last = $#base - @v;
592 0 0         return if $last < 0;
593 0           for my $i (0 .. $last) {
594 0           my $max = abs2($base[$i]);
595 0 0         if ($max < 0.3) {
596 0           for my $j ($i+1 .. $#base) {
597 0           my $d2 = abs2($base[$j]);
598 0 0         if ($d2 > $max) {
599 0           @base[$i, $j] = @base[$j, $i];
600 0 0         last unless $d2 < 0.3;
601 0           $max = $d2;
602             }
603             }
604             }
605 0           my $versor = $base[$i] = versor($base[$i]);
606 0           $_ = decompose($versor, $_) for @base[$i+1..$#base];
607             }
608 0 0         wantarray ? @base[0..$last] : $base[0];
609             }
610              
611             sub select_in_ball {
612 0     0 1   my $v = shift;
613 0           my $r = shift;
614 0           my $r2 = $r * $r;
615 0           grep $v->dist2($_) <= $r2, @_;
616             }
617              
618             sub select_in_ball_ref2bitmap {
619 0     0 0   my $v = shift;
620 0           my $r = shift;
621 0           my $p = shift;
622 0           my $r2 = $r * $r;
623 0           my $bm = "\0" x int((@$p + 7) / 8);
624 0           for my $ix (0..$#$p) {
625 0 0         vec($bm, $ix, 1) = 1 if $v->dist2($p->[$ix]) <= $r2;
626             }
627 0           return $bm;
628             }
629              
630             1;
631             __END__