File Coverage

lib/ICC/Support/geo2.pm
Criterion Covered Total %
statement 17 109 15.6
branch 1 48 2.0
condition 1 48 2.0
subroutine 5 11 45.4
pod 1 5 20.0
total 25 221 11.3


line stmt bran cond sub pod time code
1             package ICC::Support::geo2;
2              
3 2     2   97829 use strict;
  2         11  
  2         53  
4 2     2   9 use Carp;
  2         4  
  2         143  
5              
6             our $VERSION = 0.11;
7              
8             # revised 2016-05-17
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 2     2   489 use lib 'lib';
  2         675  
  2         9  
14              
15             # inherit from Shared
16 2     2   636 use parent qw(ICC::Shared);
  2         286  
  2         9  
17              
18             # create new object
19             # hash keys are:
20             # 'points' value is an array reference containing two 3-D point coordinate arrays
21             # parameters: ([ref_to_parameter_hash])
22             # returns: (ref_to_object)
23             sub new {
24              
25             # get object class
26 1     1 0 722 my $class = shift();
27              
28             # create empty geo2 object
29 1         3 my $self = [
30             {}, # object header
31             [] # points array
32             ];
33              
34             # if one parameter, a hash reference
35 1 50 33     5 if (@_ == 1 && ref($_[0]) eq 'HASH') {
36            
37             # create new object from parameter hash
38 0         0 _new_from_hash($self, @_);
39            
40             }
41              
42             # bless object
43 1         3 bless($self, $class);
44              
45             # return object reference
46 1         3 return($self);
47              
48             }
49              
50             # compute distance and offset
51             # optional hash keys:
52             # 'limit0' limits offset to values >= 0
53             # 'limit1' limits offset to values <= 1
54             # parameters: (ref_to_input_array, [hash])
55             # returns: (distance, offset)
56             sub transform {
57              
58             # get parameters
59 0     0 0   my ($self, $in, $hash) = @_;
60              
61             # local variables
62 0           my ($v01, $v21, $vx, $s, $t);
63              
64             # compute (x0 - x1) and (x2 - x1)
65 0           $v01 = [$in->[0] - $self->[1][0][0], $in->[1] - $self->[1][0][1], $in->[2] - $self->[1][0][2]];
66 0           $v21 = [$self->[1][1][0] - $self->[1][0][0], $self->[1][1][1] - $self->[1][0][1], $self->[1][1][2] - $self->[1][0][2]];
67              
68             # compute |(x2 - x1)|^2
69 0 0         if ($s = $v21->[0]**2 + $v21->[1]**2 + $v21->[2]**2) {
70            
71             # compute offset
72 0           $t = ICC::Shared::dotProduct($v01, $v21)/$s;
73            
74             # if offset limited >= 0 and t < 0
75 0 0 0       if ($hash->{'limit0'} && $t < 0) {
    0 0        
76            
77             # return distance and offset
78 0           return(sqrt(($in->[0] - $self->[1][0][0])**2 + ($in->[1] - $self->[1][0][1])**2 + ($in->[2] - $self->[1][0][2])**2), 0);
79            
80             # if offset limited <= 0 and t > 1
81             } elsif ($hash->{'limit1'} && $t > 1) {
82            
83             # return distance and offset
84 0           return(sqrt(($in->[0] - $self->[1][1][0])**2 + ($in->[1] - $self->[1][1][1])**2 + ($in->[2] - $self->[1][1][2])**2), 1);
85            
86             } else {
87            
88             # compute (x0 - x1) x (x2 - x1)
89 0           $vx = ICC::Shared::crossProduct($v01, $v21);
90            
91             # return distance and offset
92 0           return(sqrt(($vx->[0]**2 + $vx->[1]**2 + $vx->[2]**2)/$s), $t);
93            
94             }
95            
96             # identical endpoints
97             } else {
98            
99             # return distance and offset
100 0           return(sqrt(($in->[0] - $self->[1][0][0])**2 + ($in->[1] - $self->[1][0][1])**2 + ($in->[2] - $self->[1][0][2])**2), 0);
101            
102             }
103            
104             }
105              
106             # compute Jacobian matrix
107             # optional hash keys:
108             # 'limit0' limits offset to values >= 0
109             # 'limit1' limits offset to values <= 1
110             # parameters: (ref_to_input_array, [hash])
111             # returns: (Jacobian_matrix, [distance, offset])
112             sub jacobian {
113              
114             # get parameters
115 0     0 0   my ($self, $in, $hash) = @_;
116              
117             # local variables
118 0           my ($v01, $v21, $vx, $vr, $s, $t, $d, $jac, $wx);
119              
120             # compute (x0 - x1) and (x2 - x1)
121 0           $v01 = [$in->[0] - $self->[1][0][0], $in->[1] - $self->[1][0][1], $in->[2] - $self->[1][0][2]];
122 0           $v21 = [$self->[1][1][0] - $self->[1][0][0], $self->[1][1][1] - $self->[1][0][1], $self->[1][1][2] - $self->[1][0][2]];
123              
124             # compute |(x2 - x1)|^2
125 0 0         if ($s = $v21->[0]**2 + $v21->[1]**2 + $v21->[2]**2) {
126            
127             # compute offset
128 0           $t = ICC::Shared::dotProduct($v01, $v21)/$s;
129            
130             # if offset limited >= 0 and t < 0
131 0 0 0       if ($hash->{'limit0'} && $t < 0) {
    0 0        
132            
133             # set offset
134 0           $t = 0;
135            
136             # compute Jacobian vector and radius
137 0           ($jac->[0], $d) = _radjac($self->[1][0], $in);
138            
139             # complete Jacobian
140 0           $jac->[1] = [0, 0, 0];
141            
142             # if offset limited <= 0 and t > 1
143             } elsif ($hash->{'limit1'} && $t > 1) {
144            
145             # set offset
146 0           $t = 1;
147            
148             # compute Jacobian vector and radius
149 0           ($jac->[0], $d) = _radjac($self->[1][1], $in);
150            
151             # complete Jacobian
152 0           $jac->[1] = [0, 0, 0];
153            
154             } else {
155            
156             # compute (x0 - x1) x (x2 - x1)
157 0           $vx = ICC::Shared::crossProduct($v01, $v21);
158            
159             # compute distance
160 0           $d = sqrt(($vx->[0]**2 + $vx->[1]**2 + $vx->[2]**2)/$s);
161            
162             # compute offset partial derivatives
163 0           $jac->[1] = [$v21->[0]/$s, $v21->[1]/$s, $v21->[2]/$s];
164            
165             # compute cross product matrix
166 0           $wx = [
167             [0, $jac->[1][2]/$d, -$jac->[1][1]/$d],
168             [-$jac->[1][2]/$d, 0, $jac->[1][0]/$d],
169             [$jac->[1][1]/$d, -$jac->[1][0]/$d, 0]
170             ];
171            
172             # compute distance partial derivatives
173 0           $jac->[0] = ICC::Support::Lapack::vec_xplus($wx, $vx, {'trans' => 'T'});
174            
175             }
176            
177             # identical endpoints
178             } else {
179            
180             # set offset
181 0           $t = 0;
182            
183             # compute Jacobian vector and radius
184 0           ($jac->[0], $d) = _radjac($self->[1][0], $in);
185            
186             # complete Jacobian
187 0           $jac->[1] = [0, 0, 0];
188            
189             }
190            
191             # bless Jacobian as Math::Matrix object
192 0           bless($jac, 'Math::Matrix');
193              
194             # if array wanted
195 0 0         if (wantarray) {
196            
197             # return Jacobian, distance and offset
198 0           return($jac, $d, $t);
199            
200             } else {
201            
202             # return Jacobian
203 0           return($jac);
204            
205             }
206            
207             }
208              
209             # get/set points array
210             # parameters: ([ref_to_points_array])
211             # returns: (ref_to_points_array)
212             sub points {
213              
214             # get parameters
215 0     0 0   my ($self, $points) = @_;
216              
217             # if parameter supplied
218 0 0         if (defined($points)) {
219            
220             # verify a 2-D array
221 0 0 0       (ref($points) eq 'ARRAY' && @{$points} == grep {ref() eq 'ARRAY'} @{$points}) or croak('\'points\' parameter not a 2-D array');
  0            
  0            
  0            
222            
223             # verify array has 2 rows
224 0 0         (@{$points} == 2) or croak('\'points\' parameter must contain 2 points');
  0            
225            
226             # verify point 0 contains 3 coordinates
227 0 0 0       (@{$points->[0]} == 3 && 3 == grep {Scalar::Util::looks_like_number($_)} @{$points->[0]}) or croak('\'points\' parameter has invalid point 0');
  0            
  0            
  0            
228            
229             # verify point 1 contains 3 coordinates
230 0 0 0       (@{$points->[1]} == 3 && 3 == grep {Scalar::Util::looks_like_number($_)} @{$points->[1]}) or croak('\'points\' parameter has invalid point 1');
  0            
  0            
  0            
231            
232             # verify points are unique
233 0 0 0       ($points->[0][0] != $points->[1][0] || $points->[0][1] != $points->[1][1] || $points->[0][2] != $points->[1][2]) or carp('\'points\' parameter contains identical points');
      0        
234            
235             # copy points array
236 0           $self->[1] = Storable::dclone($points);
237            
238             }
239              
240             # return end point array reference
241 0           return($self->[1]);
242              
243             }
244              
245             # print object contents to string
246             # format is an array structure
247             # parameter: ([format])
248             # returns: (string)
249             sub sdump {
250              
251             # get parameters
252 0     0 1   my ($self, $p) = @_;
253              
254             # local variables
255 0           my ($s, $fmt);
256              
257             # resolve parameter to an array reference
258 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
259              
260             # get format string
261 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
262              
263             # set string to object ID
264 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
265              
266             # return
267 0           return($s);
268              
269             }
270              
271             # compute radial Jacobian
272             # parameters: (point_vector, ref_to_input_vector)
273             # returns: (Jacobian_vector, radius)
274             sub _radjac {
275              
276             # get parameters
277 0     0     my ($point, $in) = @_;
278              
279             # local variables
280 0           my ($jac, $r);
281              
282             # for each dimension
283 0           for my $i (0 .. 2) {
284            
285             # compute Jacobian element
286 0           $jac->[$i] = ($in->[$i] - $point->[$i]);
287            
288             }
289              
290             # compute radius
291 0           $r = sqrt($jac->[0]**2 + $jac->[1]**2 + $jac->[2]**2);
292              
293             # if radius is zero
294 0 0         if ($r == 0) {
295            
296             # set Jacobian to all ones
297 0           $jac = [1, 1, 1];
298            
299             } else {
300            
301             # for each dimension
302 0           for my $i (0 .. 2) {
303            
304             # divide by radius
305 0           $jac->[$i] /= $r;
306            
307             }
308            
309             }
310              
311             # return
312 0           return($jac, $r);
313              
314             }
315              
316             # populate object from parameter hash
317             # parameters: (object_reference, ref_to_parameter_hash)
318             sub _new_from_hash {
319              
320             # get parameters
321 0     0     my ($self, $hash) = @_;
322              
323             # local variables
324 0           my ($points);
325              
326             # get points array
327 0 0         if ($points = $hash->{'points'}) {
328            
329             # verify a 2-D array
330 0 0 0       (ref($points) eq 'ARRAY' && @{$points} == grep {ref() eq 'ARRAY'} @{$points}) or croak('\'points\' parameter not a 2-D array');
  0            
  0            
  0            
331            
332             # verify array has 2 rows
333 0 0         (@{$points} == 2) or croak('\'points\' parameter must contain 2 points');
  0            
334            
335             # verify point 0 contains 3 coordinates
336 0 0 0       (@{$points->[0]} == 3 && 3 == grep {Scalar::Util::looks_like_number($_)} @{$points->[0]}) or croak('\'points\' parameter has invalid point 0');
  0            
  0            
  0            
337            
338             # verify point 1 contains 3 coordinates
339 0 0 0       (@{$points->[1]} == 3 && 3 == grep {Scalar::Util::looks_like_number($_)} @{$points->[1]}) or croak('\'points\' parameter has invalid point 1');
  0            
  0            
  0            
340            
341             # verify points are unique
342 0 0 0       ($points->[0][0] != $points->[1][0] || $points->[0][1] != $points->[1][1] || $points->[0][2] != $points->[1][2]) or carp('\'points\' parameter contains identical points');
      0        
343            
344             # copy points array
345 0           $self->[1] = Storable::dclone($points);
346            
347             }
348              
349             }
350              
351             1;