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, 2019, 2020 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   9154 use 5.004;
  1         14  
21 1     1   5 use strict;
  1         3  
  1         65  
22             #use List::Util 'max';
23             *max = \&Math::PlanePath::_max;
24              
25 1     1   15 use vars '$VERSION', '@ISA';
  1         3  
  1         76  
26             $VERSION = 128;
27 1     1   668 use Math::PlanePath;
  1         2  
  1         32  
28 1     1   399 use Math::PlanePath::Base::NSEW;
  1         3  
  1         95  
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         56 'is_infinite',
36 1     1   8 'round_nearest';
  1         2  
37             use Math::PlanePath::Base::Digits
38 1         72 'round_up_pow',
39             'round_down_pow',
40             'digit_split_lowtohigh',
41 1     1   434 'digit_join_lowtohigh';
  1         2  
42              
43             # uncomment this to run the ### lines
44             # use Smart::Comments;
45              
46              
47 1     1   7 use constant n_start => 0;
  1         1  
  1         49  
48 1     1   6 use constant class_x_negative => 1;
  1         2  
  1         38  
49 1     1   6 use constant class_y_negative => 1;
  1         2  
  1         86  
50              
51 1         211 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   6 } ];
  1         2  
61              
62              
63             #------------------------------------------------------------------------------
64              
65             sub x_negative {
66 4     4 1 69 my ($self) = @_;
67 4         27 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 8 my ($self) = @_;
73 3         12 return $x_negative_at_n[$self->{'arms'}];
74             }
75             }
76              
77             sub y_negative {
78 4     4 1 10 my ($self) = @_;
79 4         18 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         12 return $y_negative_at_n[$self->{'arms'}];
86             }
87             }
88              
89             #------------------------------------------------------------------------------
90              
91 1     1   457 use Math::PlanePath::DekkingCentres;
  1         3  
  1         37  
92 1     1   7 use vars '@_next_state','@_digit_to_x','@_digit_to_y','@_yx_to_digit';
  1         2  
  1         95  
93             BEGIN {
94 1     1   5 *_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         1353 *_yx_to_digit = \@Math::PlanePath::DekkingCentres::_yx_to_digit;
98             }
99              
100             sub new {
101 12     12 1 2171 my $self = shift->SUPER::new(@_);
102 12   100     70 $self->{'arms'} ||= 1;
103 12         26 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 46536 my ($self, $n) = @_;
127             ### DekkingCurve n_to_xy(): $n
128              
129 22674 50       43257 if ($n < 0) { return; }
  0         0  
130 22674 50       47581 if (is_infinite($n)) { return ($n,$n); }
  0         0  
131              
132 22674         41606 my $int = int($n);
133 22674         31628 $n -= $int; # fraction part
134              
135 22674         35457 my $arms = $self->{'arms'};
136 22674         44731 my $arm = _divrem_mutate ($int, $arms);
137 22674 50       41904 if ($arm) { $int += 1; }
  0         0  
138 22674         44732 my @digits = digit_split_lowtohigh($int,25);
139 22674         33990 my $state = 0;
140 22674         32491 my @x;
141             my @y;
142 22674         46414 foreach my $i (reverse 0 .. $#digits) {
143 107227         143434 $state += $digits[$i];
144 107227         155137 $x[$i] = $_digit_to_x[$state];
145 107227         151031 $y[$i] = $_digit_to_y[$state];
146 107227         153486 $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         36322 my $zero = $int * 0;
156 22674         70182 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         63279 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       45572 if ($arm < 2) {
164 22674 50       38631 if ($arm < 1) { return ($x,$y); } # arm==0
  22674         69347  
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 18460 my ($self, $x, $y) = @_;
173             ### DekkingCurve xy_to_n(): "$x, $y"
174              
175 9405         19769 $x = round_nearest ($x);
176 9405         19033 $y = round_nearest ($y);
177 9405 50       18494 if (is_infinite($x)) {
178 0         0 return $x;
179             }
180 9405 50       20763 if (is_infinite($y)) {
181 0         0 return $y;
182             }
183              
184 9405         17836 my $arms = $self->{'arms'};
185 9405 50 33     42070 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         19048 foreach my $arm (0 .. $arms-1) {
191 9405         14971 foreach my $xoffset (0,-1) {
192 13792         20749 foreach my $yoffset (0,-1) {
193              
194 22565         50995 my @x = digit_split_lowtohigh($x+$xoffset,5);
195 22565         49353 my @y = digit_split_lowtohigh($y+$yoffset,5);
196 22565         34769 my $state = 0;
197 22565         29761 my @n;
198 22565         67804 foreach my $i (reverse 0 .. max($#x,$#y)) {
199 106999   100     324552 my $digit = $n[$i] = $_yx_to_digit[$state + 5*($y[$i]||0) + ($x[$i]||0)];
      100        
200 106999         176755 $state = $_next_state[$state+$digit];
201             }
202 22565         38463 my $zero = $x*0*$y;
203 22565         49879 my $n = digit_join_lowtohigh(\@n, 25, $zero);
204 22565         34743 $n = $n*$arms;
205 22565 50       42013 if (my ($nx,$ny) = $self->n_to_xy($n)) {
206 22565 100 100     83069 if ($nx == $x && $ny == $y) {
207 5019 50       21211 return $n + ($arm ? $arm-$arms : $arm);
208             }
209             }
210             }
211             }
212 4386         9398 ($x,$y) = ($y,-$x); # rotate -90
213             ### rotate to: "$x, $y"
214             }
215 4386         11207 return undef;
216             }
217              
218             # not exact
219             sub rect_to_n_range {
220 8     8 1 755 my ($self, $x1,$y1, $x2,$y2) = @_;
221             ### DekkingCurve rect_to_n_range(): "$x1,$y1, $x2,$y2"
222              
223 8         25 $x1 = round_nearest ($x1);
224 8         18 $x2 = round_nearest ($x2);
225 8         18 $y1 = round_nearest ($y1);
226 8         17 $y2 = round_nearest ($y2);
227              
228 8 50       21 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); }
  0         0  
229 8 50       16 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); }
  0         0  
230              
231 8         17 my $arms = $self->{'arms'};
232 8 50 33     56 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         27 my ($pow) = round_down_pow (max(abs($x1),abs($y1),$x2,$y2) + 1, 5);
238             ### $pow
239 8         27 return (0, 25*$pow*$pow*$arms - 1);
240             }
241              
242             #------------------------------------------------------------------------------
243              
244             sub level_to_n_range {
245 2     2 1 12 my ($self, $level) = @_;
246 2         7 return (0, 25**$level * $self->{'arms'});
247             }
248             sub n_to_level {
249 8     8 1 252 my ($self, $n) = @_;
250             ### n_to_level(): $n
251 8 50       22 if ($n < 0) { return undef; }
  0         0  
252 8 50       22 if (is_infinite($n)) { return $n; }
  0         0  
253 8         24 $n = round_nearest($n);
254 8         17 $n += $self->{'arms'}-1; # division rounding up
255 8         26 _divrem_mutate ($n, $self->{'arms'});
256 8         21 my ($pow, $exp) = round_up_pow ($n, 25);
257 8         31 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   12191 my ($self, $x) = @_;
266 3126 50 33     8689 if ($x < 0 || is_infinite($x)) { return 0; }
  0         0  
267 3126 100       7062 if ($x == 0) { return 1; }
  1         3  
268 3125         6093 my $digit = _divrem_mutate($x, 5);
269 3125 100       6240 if ($digit) {
270 2500         5505 return ($digit == 1);
271             }
272             # find lowest non-zero
273 625   66     1696 while ($x && ! ($digit = _divrem_mutate($x, 5))) { }
274 625   100     2144 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   12551 my ($self, $y) = @_;
280 3126 50 33     9081 if ($y < 0 || is_infinite($y)) { return 0; }
  0         0  
281 3126         7310 my $digit = _divrem_mutate($y, 5);
282 3126 100       6338 if ($digit != 4) {
283 2501         5814 return ($digit == 3);
284             }
285             # find lowest non-4
286 625   100     1853 while ($y && ($digit = _divrem_mutate($y, 5)) == 4) { }
287 625   100     2157 return ($digit == 2 || $digit == 3);
288             }
289              
290             #------------------------------------------------------------------------------
291             1;
292             __END__