File Coverage

blib/lib/Math/PlanePath/DekkingCurve.pm
Criterion Covered Total %
statement 137 154 88.9
branch 25 44 56.8
condition 28 45 62.2
subroutine 26 26 100.0
pod 10 10 100.0
total 226 279 81.0


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Kevin Ryde
2              
3             # This file is part of Math-PlanePath.
4             #
5             # Math-PlanePath is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-PlanePath is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath. If not, see .
17              
18              
19             package Math::PlanePath::DekkingCurve;
20 1     1   9065 use 5.004;
  1         10  
21 1     1   4 use strict;
  1         2  
  1         48  
22             #use List::Util 'max';
23             *max = \&Math::PlanePath::_max;
24              
25 1     1   13 use vars '$VERSION', '@ISA';
  1         3  
  1         72  
26             $VERSION = 127;
27 1     1   696 use Math::PlanePath;
  1         3  
  1         29  
28 1     1   408 use Math::PlanePath::Base::NSEW;
  1         2  
  1         60  
29             @ISA = ('Math::PlanePath::Base::NSEW',
30             'Math::PlanePath');
31             *_divrem = \&Math::PlanePath::_divrem;
32             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
33              
34             use Math::PlanePath::Base::Generic
35 1         50 'is_infinite',
36 1     1   6 'round_nearest';
  1         2  
37             use Math::PlanePath::Base::Digits
38 1         71 'round_up_pow',
39             'round_down_pow',
40             'digit_split_lowtohigh',
41 1     1   458 'digit_join_lowtohigh';
  1         3  
42              
43             # uncomment this to run the ### lines
44             # use Smart::Comments;
45              
46              
47 1     1   8 use constant n_start => 0;
  1         2  
  1         47  
48 1     1   6 use constant class_x_negative => 1;
  1         2  
  1         38  
49 1     1   5 use constant class_y_negative => 1;
  1         2  
  1         68  
50              
51 1         255 use constant parameter_info_array => [ { name => 'arms',
52             share_key => 'arms_4',
53             display => 'Arms',
54             type => 'integer',
55             minimum => 1,
56             maximum => 4,
57             default => 1,
58             width => 1,
59             description => 'Arms',
60 1     1   7 } ];
  1         2  
61              
62              
63             #------------------------------------------------------------------------------
64              
65             sub x_negative {
66 4     4 1 77 my ($self) = @_;
67 4         20 return $self->{'arms'} > 1;
68             }
69             {
70             my @x_negative_at_n = (undef, undef, 5, 2, 2);
71             sub x_negative_at_n {
72 3     3 1 6 my ($self) = @_;
73 3         14 return $x_negative_at_n[$self->{'arms'}];
74             }
75             }
76              
77             sub y_negative {
78 4     4 1 16 my ($self) = @_;
79 4         17 return $self->{'arms'} > 2;
80             }
81             {
82             my @y_negative_at_n = (undef, undef, undef, 8, 3);
83             sub y_negative_at_n {
84 3     3 1 7 my ($self) = @_;
85 3         10 return $y_negative_at_n[$self->{'arms'}];
86             }
87             }
88              
89             #------------------------------------------------------------------------------
90              
91 1     1   496 use Math::PlanePath::DekkingCentres;
  1         2  
  1         37  
92 1     1   7 use vars '@_next_state','@_digit_to_x','@_digit_to_y','@_yx_to_digit';
  1         1  
  1         93  
93             BEGIN {
94 1     1   4 *_next_state = \@Math::PlanePath::DekkingCentres::_next_state;
95 1         2 *_digit_to_x = \@Math::PlanePath::DekkingCentres::_digit_to_x;
96 1         2 *_digit_to_y = \@Math::PlanePath::DekkingCentres::_digit_to_y;
97 1         1322 *_yx_to_digit = \@Math::PlanePath::DekkingCentres::_yx_to_digit;
98             }
99              
100             sub new {
101 12     12 1 2280 my $self = shift->SUPER::new(@_);
102 12   100     417 $self->{'arms'} ||= 1;
103 12         31 return $self;
104             }
105              
106             # tables generated by tools/dekking-curve-table.pl
107             #
108             my @edge_dx = (0,0,0,1,1, 0,0,1,1,0, 0,0,0,1,0, 0,0,1,0,1, 0,1,0,1,1,
109             1,1,1,1,1, 1,1,1,0,1, 1,1,0,1,0, 0,0,1,0,0, 0,1,1,0,0,
110             1,1,1,0,0, 1,1,0,0,1, 1,1,1,0,1, 1,1,0,1,0, 1,0,1,0,0,
111             0,0,0,0,0, 0,0,0,1,0, 0,0,1,0,1, 1,1,0,1,1, 1,0,0,1,1,
112             1,1,1,1,1, 1,0,0,0,0, 1,1,1,1,1, 0,0,0,0,1, 1,0,0,1,1,
113             1,1,1,0,0, 1,1,1,1,1, 0,0,0,1,1, 0,0,1,0,1, 0,1,0,1,1,
114             0,0,0,0,0, 0,1,1,1,1, 0,0,0,0,0, 1,1,1,1,0, 0,1,1,0,0,
115             0,0,0,1,1, 0,0,0,0,0, 1,1,1,0,0, 1,1,0,1,0, 1,0,1,0,0);
116             my @edge_dy = (0,0,0,0,0, 0,0,0,1,0, 0,0,1,0,1, 1,1,0,1,1, 1,0,0,1,1,
117             0,0,0,1,1, 0,0,1,1,0, 0,0,0,1,0, 0,0,1,0,1, 0,1,0,1,1,
118             1,1,1,1,1, 1,1,1,0,1, 1,1,0,1,0, 0,0,1,0,0, 0,1,1,0,0,
119             1,1,1,0,0, 1,1,0,0,1, 1,1,1,0,1, 1,1,0,1,0, 1,0,1,0,0,
120             0,0,0,1,1, 0,0,0,0,0, 1,1,1,0,0, 1,1,0,1,0, 1,0,1,0,0,
121             1,1,1,1,1, 1,0,0,0,0, 1,1,1,1,1, 0,0,0,0,1, 1,0,0,1,1,
122             1,1,1,0,0, 1,1,1,1,1, 0,0,0,1,1, 0,0,1,0,1, 0,1,0,1,1,
123             0,0,0,0,0, 0,1,1,1,1, 0,0,0,0,0, 1,1,1,1,0, 0,1,1,0,0);
124              
125             sub n_to_xy {
126 22674     22674 1 44214 my ($self, $n) = @_;
127             ### DekkingCurve n_to_xy(): $n
128              
129 22674 50       43480 if ($n < 0) { return; }
  0         0  
130 22674 50       43893 if (is_infinite($n)) { return ($n,$n); }
  0         0  
131              
132 22674         39582 my $int = int($n);
133 22674         31854 $n -= $int; # fraction part
134              
135 22674         33604 my $arms = $self->{'arms'};
136 22674         41464 my $arm = _divrem_mutate ($int, $arms);
137 22674 50       41712 if ($arm) { $int += 1; }
  0         0  
138 22674         43290 my @digits = digit_split_lowtohigh($int,25);
139 22674         31903 my $state = 0;
140 22674         32426 my @x;
141             my @y;
142 22674         42575 foreach my $i (reverse 0 .. $#digits) {
143 107212         141132 $state += $digits[$i];
144 107212         159825 $x[$i] = $_digit_to_x[$state];
145 107212         149028 $y[$i] = $_digit_to_y[$state];
146 107212         156591 $state = $_next_state[$state];
147             }
148              
149             ### @x
150             ### @y
151             ### $state
152             ### dx: $_digit_to_x[$state+24] - $_digit_to_x[$state]
153             ### dy: $_digit_to_y[$state+24] - $_digit_to_y[$state]
154              
155 22674         35251 my $zero = $int * 0;
156 22674         62451 my $x = ($n * (($_digit_to_x[$state+24] - $_digit_to_x[$state])/4)
157             + digit_join_lowtohigh(\@x, 5, $zero)
158             + $edge_dx[$state]);
159 22674         57589 my $y = ($n * (($_digit_to_y[$state+24] - $_digit_to_y[$state])/4)
160             + digit_join_lowtohigh(\@y, 5, $zero)
161             + $edge_dy[$state]);
162              
163 22674 50       44465 if ($arm < 2) {
164 22674 50       37573 if ($arm < 1) { return ($x,$y); } # arm==0
  22674         66528  
165 0         0 return (-$y,$x); # arm==1 rotate +90
166             }
167 0 0       0 if ($arm < 3) { return (-$x,-$y); } # arm==2
  0         0  
168 0         0 return ($y,-$x); # arm==3 rotate -90
169             }
170              
171             sub xy_to_n {
172 9405     9405 1 17738 my ($self, $x, $y) = @_;
173             ### DekkingCurve xy_to_n(): "$x, $y"
174              
175 9405         18177 $x = round_nearest ($x);
176 9405         17857 $y = round_nearest ($y);
177 9405 50       17792 if (is_infinite($x)) {
178 0         0 return $x;
179             }
180 9405 50       20971 if (is_infinite($y)) {
181 0         0 return $y;
182             }
183              
184 9405         18274 my $arms = $self->{'arms'};
185 9405 50 33     44479 if (($arms < 2 && $x < 0) || ($arms < 3 && $y < 0)) {
      33        
      33        
186             ### X or Y negative, no N value ...
187 0         0 return undef;
188             }
189              
190 9405         19517 foreach my $arm (0 .. $arms-1) {
191 9405         14284 foreach my $xoffset (0,-1) {
192 13792         21785 foreach my $yoffset (0,-1) {
193              
194 22565         47996 my @x = digit_split_lowtohigh($x+$xoffset,5);
195 22565         47340 my @y = digit_split_lowtohigh($y+$yoffset,5);
196 22565         33124 my $state = 0;
197 22565         27795 my @n;
198 22565         63094 foreach my $i (reverse 0 .. max($#x,$#y)) {
199 106999   100     333226 my $digit = $n[$i] = $_yx_to_digit[$state + 5*($y[$i]||0) + ($x[$i]||0)];
      100        
200 106999         171960 $state = $_next_state[$state+$digit];
201             }
202 22565         37973 my $zero = $x*0*$y;
203 22565         47201 my $n = digit_join_lowtohigh(\@n, 25, $zero);
204 22565         32893 $n = $n*$arms;
205 22565 50       43980 if (my ($nx,$ny) = $self->n_to_xy($n)) {
206 22565 100 100     82742 if ($nx == $x && $ny == $y) {
207 5019 50       20319 return $n + ($arm ? $arm-$arms : $arm);
208             }
209             }
210             }
211             }
212 4386         9338 ($x,$y) = ($y,-$x); # rotate -90
213             ### rotate to: "$x, $y"
214             }
215 4386         11408 return undef;
216             }
217              
218             # not exact
219             sub rect_to_n_range {
220 8     8 1 740 my ($self, $x1,$y1, $x2,$y2) = @_;
221             ### DekkingCurve rect_to_n_range(): "$x1,$y1, $x2,$y2"
222              
223 8         19 $x1 = round_nearest ($x1);
224 8         15 $x2 = round_nearest ($x2);
225 8         16 $y1 = round_nearest ($y1);
226 8         18 $y2 = round_nearest ($y2);
227              
228 8 50       21 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); }
  0         0  
229 8 50       20 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); }
  0         0  
230              
231 8         15 my $arms = $self->{'arms'};
232 8 50 33     55 if (($arms < 2 && $x2 < 0) || ($arms < 3 && $y2 < 0)) {
      33        
      33        
233             ### rectangle all negative, no N values ...
234 0         0 return (1, 0);
235             }
236              
237 8         23 my ($pow) = round_down_pow (max(abs($x1),abs($y1),$x2,$y2) + 1, 5);
238             ### $pow
239 8         26 return (0, 25*$pow*$pow*$arms - 1);
240             }
241              
242             #------------------------------------------------------------------------------
243              
244             sub level_to_n_range {
245 2     2 1 23 my ($self, $level) = @_;
246 2         22 return (0, 25**$level * $self->{'arms'});
247             }
248             sub n_to_level {
249 8     8 1 258 my ($self, $n) = @_;
250             ### n_to_level(): $n
251 8 50       24 if ($n < 0) { return undef; }
  0         0  
252 8 50       23 if (is_infinite($n)) { return $n; }
  0         0  
253 8         23 $n = round_nearest($n);
254 8         20 $n += $self->{'arms'}-1; # division rounding up
255 8         22 _divrem_mutate ($n, $self->{'arms'});
256 8         21 my ($pow, $exp) = round_up_pow ($n, 25);
257 8         30 return $exp;
258             }
259              
260             #------------------------------------------------------------------------------
261             # Not taking into account multiple arms ...
262              
263             # Return true if X axis segment $x to $x+1 is traversed
264             sub _UNDOCUMENTED__xseg_is_traversed {
265 3126     3126   11229 my ($self, $x) = @_;
266 3126 50 33     8426 if ($x < 0 || is_infinite($x)) { return 0; }
  0         0  
267 3126 100       6646 if ($x == 0) { return 1; }
  1         3  
268 3125         6009 my $digit = _divrem_mutate($x, 5);
269 3125 100       6285 if ($digit) {
270 2500         5291 return ($digit == 1);
271             }
272             # find lowest non-zero
273 625   66     1620 while ($x && ! ($digit = _divrem_mutate($x, 5))) { }
274 625   100     2024 return ($digit == 1 || $digit == 2);
275             }
276              
277             # Return true if Y axis segment $y to $y+1 is traversed
278             sub _UNDOCUMENTED__yseg_is_traversed {
279 3126     3126   11773 my ($self, $y) = @_;
280 3126 50 33     8823 if ($y < 0 || is_infinite($y)) { return 0; }
  0         0  
281 3126         6958 my $digit = _divrem_mutate($y, 5);
282 3126 100       6234 if ($digit != 4) {
283 2501         5463 return ($digit == 3);
284             }
285             # find lowest non-4
286 625   100     1677 while ($y && ($digit = _divrem_mutate($y, 5)) == 4) { }
287 625   100     2152 return ($digit == 2 || $digit == 3);
288             }
289              
290             #------------------------------------------------------------------------------
291             1;
292             __END__