File Coverage

blib/lib/Math/Vector/Real.pm
Criterion Covered Total %
statement 103 444 23.2
branch 19 164 11.5
condition 4 20 20.0
subroutine 23 67 34.3
pod 33 59 55.9
total 182 754 24.1


line stmt bran cond sub pod time code
1             package Math::Vector::Real;
2              
3             our $VERSION = '0.18';
4              
5 1     1   176239 use strict;
  1         3  
  1         33  
6 1     1   6 use warnings;
  1         2  
  1         31  
7 1     1   6 use Carp;
  1         6  
  1         55  
8 1     1   343 use POSIX ();
  1         5937  
  1         34  
9              
10 1     1   10 use Exporter qw(import);
  1         2  
  1         5148  
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 151 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   319 local ($@, $SIG{__DIE__});
107 78 50       209 eval { @{$_[0]} == @{$_[1]} } and return;
  78         137  
  78         181  
  78         435  
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 757 &_check_dim;
124 13         36 my ($v0, $v1) = @_;
125 13         123 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 7 sub neg { bless [map -$_, @{$_[0]}] }
  2         18  
136              
137             sub sub {
138 16     16 0 53 &_check_dim;
139 16 100       63 my ($v0, $v1) = ($_[2] ? @_[1, 0] : @_);
140 16         131 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 137 if (ref $_[1]) {
152 17         63 &_check_dim;
153 17         48 my ($v0, $v1) = @_;
154 17         34 my $acu = 0;
155 17         85 $acu += $v0->[$_] * $v1->[$_] for 0..$#$v0;
156 17         75 $acu;
157             }
158             else {
159 29         63 my ($v, $s) = @_;
160 29         184 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 44 croak "can't use vector as dividend"
173             if ($_[2] or ref $_[1]);
174 6         16 my ($v, $div) = @_;
175 6 50       24 $div == 0 and croak "illegal division by zero";
176 6         18 my $i = 1 / $div;
177 6         49 bless [map $i * $_, @$v]
178             }
179              
180             sub div_me {
181 1 50   1 0 11 croak "can't use vector as dividend" if ref $_[1];
182 1         3 my $v = shift;
183 1         4 my $i = 1.0 / shift;
184 1         6 $_ *= $i for @$v;
185 1         4 $v;
186             }
187              
188             sub equal {
189 16     16 0 61 &_check_dim;
190 16         51 my ($v0, $v1) = @_;
191 16   50     125 $v0->[$_] == $v1->[$_] || return 0 for 0..$#$v0;
192 16         130 1;
193             }
194              
195             sub nequal {
196 1     1 0 6 &_check_dim;
197 1         4 my ($v0, $v1) = @_;
198 1   100     13 $v0->[$_] == $v1->[$_] || return 1 for 0..$#$v0;
199 0         0 0;
200             }
201              
202             sub cross {
203 15     15 0 65 &_check_dim;
204 15 100       68 my ($v0, $v1) = ($_[2] ? @_[1, 0] : @_);
205 15         37 my $dim = @$v0;
206 15 50       47 if ($dim == 3) {
207 15         134 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 16101 my $acu = 0;
223 11         23 $acu += $_ * $_ for @{$_[0]};
  11         58  
224 11         127 sqrt $acu;
225             }
226              
227             sub abs2 {
228 4     4 0 11 my $acu = 0;
229 4         10 $acu += $_ * $_ for @{$_[0]};
  4         16  
230 4         15 $acu;
231             }
232              
233             sub dist {
234 0     0 1 0 &_check_dim;
235 0         0 my ($v0, $v1) = @_;
236 0         0 my $d2 = 0;
237 0         0 for (0..$#$v0) {
238 0         0 my $d = $v0->[$_] - $v1->[$_];
239 0         0 $d2 += $d * $d;
240             }
241 0         0 sqrt($d2);
242             }
243              
244             sub dist2 {
245 0     0 1 0 &_check_dim;
246 0         0 my ($v0, $v1) = @_;
247 0         0 my $d2 = 0;
248 0         0 for (0..$#$v0) {
249 0         0 my $d = $v0->[$_] - $v1->[$_];
250 0         0 $d2 += $d * $d;
251             }
252 0         0 $d2;
253             }
254              
255             sub max_component {
256 0     0 1 0 my $max = 0;
257 0         0 for (@{shift()}) {
  0         0  
258 0         0 my $abs = CORE::abs($_);
259 0 0       0 $abs > $max and $max = $abs;
260             }
261             $max
262 0         0 }
263              
264             sub min_component {
265 0     0 1 0 my $self = shift;
266 0         0 my $min = CORE::abs($self->[0]);
267 0         0 for (@$self) {
268 0         0 my $abs = CORE::abs($_);
269 0 0       0 $abs < $min and $min = $abs;
270             }
271             $min
272 0         0 }
273              
274             sub manhattan_norm {
275 0     0 1 0 my $n = 0;
276 0         0 $n += CORE::abs($_) for @{$_[0]};
  0         0  
277 0         0 return $n;
278             }
279              
280             sub manhattan_dist {
281 0     0 1 0 &_check_dim;
282 0         0 my ($v0, $v1) = @_;
283 0         0 my $d = 0;
284 0         0 $d += CORE::abs($v0->[$_] - $v1->[$_]) for 0..$#$v0;
285 0         0 return $d;
286             }
287              
288             sub chebyshev_dist {
289 0     0 1 0 &_check_dim;
290 0         0 my ($v0, $v1) = @_;
291 0         0 my $max = 0;
292 0         0 for (0..$#$v0) {
293 0         0 my $d = CORE::abs($v0->[$_] - $v1->[$_]);
294 0 0       0 $max = $d if $d > $max;
295             }
296 0         0 $max;
297             }
298              
299             sub _upgrade {
300 0     0   0 my $dim;
301             map {
302 0         0 my $d = eval { @{$_} };
  0         0  
  0         0  
  0         0  
303 0 0       0 defined $d or croak "argument is not a vector or array";
304 0 0       0 if (defined $dim) {
305 0 0       0 $d == $dim or croak "dimensions do not match";
306             }
307             else {
308 0         0 $dim = $d;
309             }
310 0 0       0 UNIVERSAL::isa($_, __PACKAGE__) ? $_ : clone($_);
311             } @_;
312             }
313              
314             sub atan2 {
315 1     1 0 6 my ($v0, $v1) = @_;
316 1 50       7 if (@$v0 == 2) {
317 0         0 my $dot = $v0->[0] * $v1->[0] + $v0->[1] * $v1->[1];
318 0         0 my $cross = $v0->[0] * $v1->[1] - $v0->[1] * $v1->[0];
319 0         0 return CORE::atan2($cross, $dot);
320             }
321             else {
322 1         4 my $a0 = &abs($v0);
323 1 50       5 return 0 unless $a0;
324 1         6 my $u0 = $v0 / $a0;
325 1         5 my $p = $v1 * $u0;
326 1         6 CORE::atan2(&abs($v1 - $p * $u0), $p);
327             }
328             }
329              
330             sub versor {
331 8     8 1 18 my $self = shift;
332 8         19 my $f = 0;
333 8         32 $f += $_ * $_ for @$self;
334 8 50       23 $f == 0 and croak "Illegal division by zero";
335 8         21 $f = 1/sqrt $f;
336 8         50 bless [map $f * $_, @$self]
337             }
338              
339             sub wrap {
340 0     0 1 0 my ($self, $v) = @_;
341 0         0 &_check_dim;
342              
343 0         0 bless [map { my $s = $self->[$_];
  0         0  
344 0         0 my $c = $v->[$_];
345 0         0 $c - $s * POSIX::floor($c/$s) } (0..$#$self)];
346             }
347              
348             sub first_orthant_reflection {
349 0     0 1 0 my $self = shift;
350 0         0 bless [map CORE::abs, @$self];
351             }
352              
353             sub sum {
354 0 0   0 1 0 ref $_[0] or shift; # works both as a class and as an instance method
355 0         0 my $sum;
356 0 0       0 if (@_) {
357 0         0 $sum = V(@{shift()});
  0         0  
358 0         0 $sum += $_ for @_;
359             }
360 0         0 return $sum;
361             }
362              
363             sub box {
364 0     0 1 0 shift;
365 0 0       0 return unless @_;
366 0         0 my $min = clone(shift);
367 0         0 my $max = clone($min);
368 0         0 my $dim = $#$min;
369 0         0 for (@_) {
370 0         0 for my $ix (0..$dim) {
371 0         0 my $c = $_->[$ix];
372 0 0       0 if ($max->[$ix] < $c) {
    0          
373 0         0 $max->[$ix] = $c;
374             }
375             elsif ($min->[$ix] > $c) {
376 0         0 $min->[$ix] = $c
377             }
378             }
379             }
380 0 0       0 wantarray ? ($min, $max) : $max - $min;
381             }
382              
383             sub nearest_in_box {
384 0     0 1 0 my $p = shift->clone;
385 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
386 0         0 for (0..$#$p) {
387 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
388 0         0 $p->[$_] = $min->[$_];
389             }
390             elsif ($p->[$_] > $max->[$_]) {
391 0         0 $p->[$_] = $max->[$_];
392             }
393             }
394             $p
395 0         0 }
396              
397             sub dist2_to_box {
398 0 0   0 1 0 @_ > 1 or croak 'Usage: $v->dist2_to_box($w0, ...)';
399 0         0 my $p = shift;
400 0         0 my $d2 = 0;
401 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
402 0         0 for (0..$#$p) {
403 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
404 0         0 my $d = $p->[$_] - $min->[$_];
405 0         0 $d2 += $d * $d;
406             }
407             elsif ($p->[$_] > $max->[$_]) {
408 0         0 my $d = $p->[$_] - $max->[$_];
409 0         0 $d2 += $d * $d;
410             }
411             }
412 0         0 $d2;
413             }
414              
415             sub chebyshev_dist_to_box {
416 0 0   0 1 0 @_ > 1 or croak 'Usage $v->chebyshev_dist_to_box($w0, ...)';
417 0         0 my $p = shift;
418 0         0 my $d = 0;
419 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
420 0         0 for (0..$#$p) {
421 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
422 0         0 my $delta = CORE::abs($p->[$_] - $min->[$_]);
423 0 0       0 $d = $delta if $delta > $d;
424             }
425             elsif ($p->[$_] > $max->[$_]) {
426 0         0 my $delta = CORE::abs($p->[$_] - $min->[$_]);
427 0 0       0 $d = $delta if $delta > $d;
428             }
429             }
430 0         0 $d;
431             }
432              
433             sub chebyshev_cut_box {
434 0 0   0 0 0 @_ > 2 or croak 'Usage $v->chebyshev_cut_box($cd, $w0, ...)';
435 0         0 my $p = shift;
436 0         0 my $cd = shift;
437 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
438 0         0 for (0..$#$p) {
439 0         0 my $a = $p->[$_];
440 0         0 my $a_min = $a - $cd;
441 0         0 my $a_max = $a + $cd;
442 0         0 my $b_min = $min->[$_];
443 0         0 my $b_max = $max->[$_];
444 0 0 0     0 return if $b_min > $a_max or $b_max < $a_min;
445 0 0       0 $min->[$_] = $a_min if $b_min < $a_min;
446 0 0       0 $max->[$_] = $a_max if $b_min > $a_max;
447             }
448 0         0 ($min, $max);
449             }
450              
451             sub nearest_in_box_border {
452             # TODO: this method can be optimized
453 0     0 0 0 my $p = shift->clone;
454 0         0 my ($b0, $b1) = Math::Vector::Real->box(@_);
455 0         0 my $in = 0;
456 0         0 for (0..$#$p) {
457 0 0       0 if ($p->[$_] < $b0->[$_]) {
    0          
458 0         0 $p->[$_] = $b0->[$_];
459             }
460             elsif ($p->[$_] > $b1->[$_]) {
461 0         0 $p->[$_] = $b1->[$_];
462             }
463             else {
464 0         0 $in++;
465             }
466             }
467 0 0       0 if ($in == @$p) {
468             # vector was inside the box
469 0         0 my $min_d = 'inf';
470 0         0 my ($comp, $comp_ix);
471 0         0 for my $q ($b0, $b1) {
472 0         0 for (0..$#$p) {
473 0         0 my $d = CORE::abs($p->[$_] - $q->[$_]);
474 0 0       0 if ($min_d > $d) {
475 0         0 $min_d = $d;
476 0         0 $comp = $q->[$_];
477 0         0 $comp_ix = $_;
478             }
479             }
480             }
481 0         0 $p->[$comp_ix] = $comp;
482             }
483 0         0 $p;
484             }
485              
486             sub max_dist2_to_box {
487 0 0   0 1 0 @_ > 1 or croak 'Usage: $v->max_dist2_to_box($w0, ...)';
488 0         0 my $p = shift;
489 0         0 my ($c0, $c1) = Math::Vector::Real->box(@_);
490 0         0 my $d2 = 0;
491 0         0 for (0..$#$p) {
492 0         0 my $d0 = CORE::abs($c0->[$_] - $p->[$_]);
493 0         0 my $d1 = CORE::abs($c1->[$_] - $p->[$_]);
494 0 0       0 $d2 += ($d0 >= $d1 ? $d0 * $d0 : $d1 * $d1);
495             }
496 0         0 return $d2;
497             }
498              
499             sub dist2_between_boxes {
500 0     0 1 0 my ($class, $a0, $a1, $b0, $b1) = @_;
501 0         0 my ($c0, $c1) = $class->box($a0, $a1);
502 0         0 my ($d0, $d1) = $class->box($b0, $b1);
503 0         0 my $d2 = 0;
504 0         0 for (0..$#$c0) {
505 0         0 my $e0 = $d0->[$_] - $c1->[$_];
506 0 0       0 if ($e0 >= 0) {
507 0         0 $d2 += $e0 * $e0;
508             }
509             else {
510 0         0 my $e1 = $c0->[$_] - $d1->[$_];
511 0 0       0 if ($e1 > 0) {
512 0         0 $d2 += $e1 * $e1;
513             }
514             }
515             }
516 0         0 $d2;
517             }
518              
519             *min_dist2_between_boxes = \&dist2_between_boxes;
520              
521             sub max_dist2_between_boxes {
522 0     0 1 0 my ($class, $a0, $a1, $b0, $b1) = @_;
523 0         0 my ($c0, $c1) = $class->box($a0, $a1);
524 0         0 my ($d0, $d1) = $class->box($b0, $b1);
525 0         0 my $d2 = 0;
526 0         0 for (0..$#$c0) {
527 0         0 my $e0 = $d1->[$_] - $c0->[$_];
528 0         0 my $e1 = $d0->[$_] - $c1->[$_];
529 0         0 $e0 *= $e0;
530 0         0 $e1 *= $e1;
531 0 0       0 $d2 += ($e0 > $e1 ? $e0 : $e1);
532             }
533 0         0 $d2;
534             }
535              
536             sub max_component_index {
537 0     0 1 0 my $self = shift;
538 0 0       0 return unless @$self;
539 0         0 my $max = 0;
540 0         0 my $max_ix = 0;
541 0         0 for my $ix (0..$#$self) {
542 0         0 my $c = CORE::abs($self->[$ix]);
543 0 0       0 if ($c > $max) {
544 0         0 $max = $c;
545 0         0 $max_ix = $ix;
546             }
547             }
548 0         0 $max_ix;
549             }
550              
551             sub min_component_index {
552 0     0 0 0 my $self = shift;
553 0 0       0 return unless @$self;
554 0         0 my $min = CORE::abs($self->[0]);
555 0         0 my $min_ix = 0;
556 0         0 for my $ix (1..$#$self) {
557 0         0 my $c = CORE::abs($self->[$ix]);
558 0 0       0 if ($c < $min) {
559 0         0 $min = $c;
560 0         0 $min_ix = $ix
561             }
562             }
563 0         0 $min_ix;
564             }
565              
566             sub decompose {
567 4     4 1 14 my ($u, $v) = @_;
568 4         13 my $p = $u * ($u * $v)/abs2($u);
569 4         17 my $n = $v - $p;
570 4 50       25 wantarray ? ($p, $n) : $n;
571             }
572              
573             sub canonical_base {
574 0     0 1 0 my ($class, $dim) = @_;
575 0         0 my @base = map { bless [(0) x $dim], $class } 1..$dim;
  0         0  
576 0         0 $base[$_][$_] = 1 for 0..$#base;
577 0         0 return @base;
578             }
579              
580             sub rotation_base_3d {
581 4     4 1 11 my $v = shift;
582 4 50       20 @$v == 3 or croak "rotation_base_3d requires a vector with three dimensions";
583 4         14 $v = $v->versor;
584 4         12 my $n = [0, 0, 0];
585 4         14 for (0..2) {
586 7 100       27 if (CORE::abs($v->[$_]) > 0.57) {
587 4         14 $n->[($_ + 1) % 3] = 1;
588 4         14 $n = $v->decompose($n)->versor;
589 4         18 return ($v, $n, $v x $n);
590             }
591             }
592 0         0 die "internal error, all the components where smaller than 0.57!";
593             }
594              
595             sub rotate_3d {
596 3     3 1 11 my $v = shift;
597 3         9 my $angle = shift;
598 3         18 my $c = cos($angle); my $s = sin($angle);
  3         9  
599 3         12 my ($i, $j, $k) = $v->rotation_base_3d;
600 3         12 my $rj = $c * $j + $s * $k;
601 3         13 my $rk = $c * $k - $s * $j;
602 3 50       16 if (wantarray) {
603 0         0 return map { ($_ * $i) * $i + ($_ * $j) * $rj + ($_ * $k) * $rk } @_;
  0         0  
604             }
605             else {
606 3         7 my $a = shift;
607 3         66 return (($a * $i) * $i + ($a * $j) * $rj + ($a * $k) * $rk);
608             }
609             }
610              
611 0     0 1   sub normal_base { __PACKAGE__->complementary_base(@_) }
612              
613             sub complementary_base {
614 0     0 1   shift;
615 0 0         @_ or croak "complementaty_base requires at least one argument in order to determine the dimension";
616 0           my $dim = @{$_[0]};
  0            
617 0 0 0       if ($dim == 2 and @_ == 1) {
618 0           my $u = versor($_[0]);
619 0           @$u = ($u->[1], -$u->[0]);
620 0           return $u;
621             }
622              
623 0           my @v = map clone($_), @_;
624 0           my @base = Math::Vector::Real->canonical_base($dim);
625 0           for my $i (0..$#v) {
626 0           my $u = versor($v[$i]);
627 0           $_ = decompose($u, $_) for @v[$i+1 .. $#v];
628 0           $_ = decompose($u, $_) for @base;
629             }
630              
631 0           my $last = $#base - @v;
632 0 0         return if $last < 0;
633 0           for my $i (0 .. $last) {
634 0           my $max = abs2($base[$i]);
635 0 0         if ($max < 0.3) {
636 0           for my $j ($i+1 .. $#base) {
637 0           my $d2 = abs2($base[$j]);
638 0 0         if ($d2 > $max) {
639 0           @base[$i, $j] = @base[$j, $i];
640 0 0         last unless $d2 < 0.3;
641 0           $max = $d2;
642             }
643             }
644             }
645 0           my $versor = $base[$i] = versor($base[$i]);
646 0           $_ = decompose($versor, $_) for @base[$i+1..$#base];
647             }
648 0 0         wantarray ? @base[0..$last] : $base[0];
649             }
650              
651             sub select_in_ball {
652 0     0 1   my $v = shift;
653 0           my $r = shift;
654 0           my $r2 = $r * $r;
655 0           grep $v->dist2($_) <= $r2, @_;
656             }
657              
658             sub select_in_ball_ref2bitmap {
659 0     0 0   my $v = shift;
660 0           my $r = shift;
661 0           my $p = shift;
662 0           my $r2 = $r * $r;
663 0           my $bm = "\0" x int((@$p + 7) / 8);
664 0           for my $ix (0..$#$p) {
665 0 0         vec($bm, $ix, 1) = 1 if $v->dist2($p->[$ix]) <= $r2;
666             }
667 0           return $bm;
668             }
669              
670             sub dist2_to_segment {
671 0     0 1   my ($p, $a, $b) = @_;
672 0           my $ab = $a - $b;
673 0           my $ap = $a - $p;
674 0           my $ap_ab = $ap * $ab;
675 0 0         return norm2($ap) if $ap_ab <= 0;
676 0           my $x = $ap * $ab / ($ab * $ab);
677 0 0         return dist2($ap, $ab) if $x >= 1;
678 0           return dist2($ap, $x * $ab);
679             }
680              
681 0     0 0   sub dist_to_segment { sqrt(&dist_to_segment) }
682              
683             sub dist2_between_segments {
684 0     0 1   my ($class, $a, $b, $c, $d) = @_;
685              
686 0           my $ab = $a - $b;
687 0           my $cd = $c - $d;
688 0           my $bd = $b - $d;
689              
690 0 0         if (@$a > 2) {
691 0           my $ab_ab = $ab * $ab;
692 0           my $ab_cd = $ab * $cd;
693 0           my $cd_cd = $cd * $cd;
694              
695 0 0         if (CORE::abs(1.0 - ($ab_cd * $ab_cd) / ($ab_ab * $cd_cd)) > 1e-10) {
696             # This method works for non-parallel segments
697 0           my $ab_bd = $ab * $bd;
698 0           my $bd_cd = $bd * $cd;
699              
700 0           my $D01 = $ab_cd * $ab_cd - $ab_ab * $cd_cd;
701 0           my $D21 = $cd_cd * $ab_bd - $bd_cd * $ab_cd;
702 0           my $x = $D21 / $D01;
703 0 0         return dist2_to_segment($b, $c, $d) if $x < 0;
704 0 0         return dist2_to_segment($a, $c, $d) if $x > 1;
705              
706 0           my $D02 = $ab_cd * $ab_bd - $bd_cd * $ab_ab;
707 0           my $y = $D02 / $D01;
708 0 0         return dist2_to_segment($d, $a, $b) if $y < 0;
709 0 0         return dist2_to_segment($c, $a, $b) if $y > 1;
710              
711 0           my $p = $b + $ab * $x;
712 0           my $q = $d + $cd * $y;
713              
714 0           return $p->dist2($q);
715             }
716             }
717              
718             # We are in 2D or lines are parallel, we consider the distance
719             # between one segment to the vertices of the other one and
720             # viceverse and return the minimum.
721 0           my $min_d2 = dist2_to_segment($a, $c, $d);
722 0           my $d2 = dist2_to_segment($b, $c, $d);
723 0           $d2 = dist2_to_segment($c, $a, $b);
724 0 0         $min_d2 = $d2 if $d2 < $min_d2;
725 0           $d2 = dist2_to_segment($d, $a, $b);
726 0 0         $min_d2 = $d2 if $d2 < $min_d2;
727 0           return $min_d2;
728             }
729              
730 0     0 0   sub dist_between_segments { sqrt(&dist2_between_segments) }
731              
732             # This is run *after* Math::Vector::Real::XS is loaded!
733             *norm = \&abs;
734             *norm2 = \&abs2;
735             *max = \&max_component;
736             *min = \&min_component;
737             *chebyshev_norm = \&max_component;
738              
739             1;
740             __END__