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   9435 use 5.004;
  1         12  
21 1     1   5 use strict;
  1         2  
  1         48  
22             #use List::Util 'max';
23             *max = \&Math::PlanePath::_max;
24              
25 1     1   8 use vars '$VERSION', '@ISA';
  1         2  
  1         76  
26             $VERSION = 129;
27 1     1   717 use Math::PlanePath;
  1         2  
  1         32  
28 1     1   479 use Math::PlanePath::Base::NSEW;
  1         3  
  1         59  
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         69 'round_up_pow',
39             'round_down_pow',
40             'digit_split_lowtohigh',
41 1     1   495 'digit_join_lowtohigh';
  1         2  
42              
43             # uncomment this to run the ### lines
44             # use Smart::Comments;
45              
46              
47 1     1   8 use constant n_start => 0;
  1         1  
  1         48  
48 1     1   6 use constant class_x_negative => 1;
  1         1  
  1         52  
49 1     1   8 use constant class_y_negative => 1;
  1         1  
  1         75  
50              
51 1         207 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 76 my ($self) = @_;
67 4         40 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         13 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 8 my ($self) = @_;
85 3         12 return $y_negative_at_n[$self->{'arms'}];
86             }
87             }
88              
89             #------------------------------------------------------------------------------
90              
91 1     1   523 use Math::PlanePath::DekkingCentres;
  1         3  
  1         36  
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   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         1402 *_yx_to_digit = \@Math::PlanePath::DekkingCentres::_yx_to_digit;
98             }
99              
100             sub new {
101 12     12 1 2403 my $self = shift->SUPER::new(@_);
102 12   100     67 $self->{'arms'} ||= 1;
103 12         30 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 46468 my ($self, $n) = @_;
127             ### DekkingCurve n_to_xy(): $n
128              
129 22674 50       40594 if ($n < 0) { return; }
  0         0  
130 22674 50       42961 if (is_infinite($n)) { return ($n,$n); }
  0         0  
131              
132 22674         38190 my $int = int($n);
133 22674         31500 $n -= $int; # fraction part
134              
135 22674         34822 my $arms = $self->{'arms'};
136 22674         42599 my $arm = _divrem_mutate ($int, $arms);
137 22674 50       41171 if ($arm) { $int += 1; }
  0         0  
138 22674         42765 my @digits = digit_split_lowtohigh($int,25);
139 22674         32071 my $state = 0;
140 22674         31973 my @x;
141             my @y;
142 22674         43197 foreach my $i (reverse 0 .. $#digits) {
143 107232         140563 $state += $digits[$i];
144 107232         155077 $x[$i] = $_digit_to_x[$state];
145 107232         150040 $y[$i] = $_digit_to_y[$state];
146 107232         153780 $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         35769 my $zero = $int * 0;
156 22674         62758 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         58180 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       43788 if ($arm < 2) {
164 22674 50       37102 if ($arm < 1) { return ($x,$y); } # arm==0
  22674         65314  
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 17512 my ($self, $x, $y) = @_;
173             ### DekkingCurve xy_to_n(): "$x, $y"
174              
175 9405         18856 $x = round_nearest ($x);
176 9405         17832 $y = round_nearest ($y);
177 9405 50       18121 if (is_infinite($x)) {
178 0         0 return $x;
179             }
180 9405 50       19676 if (is_infinite($y)) {
181 0         0 return $y;
182             }
183              
184 9405         17711 my $arms = $self->{'arms'};
185 9405 50 33     42764 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         19134 foreach my $arm (0 .. $arms-1) {
191 9405         14138 foreach my $xoffset (0,-1) {
192 13792         19978 foreach my $yoffset (0,-1) {
193              
194 22565         48381 my @x = digit_split_lowtohigh($x+$xoffset,5);
195 22565         44912 my @y = digit_split_lowtohigh($y+$yoffset,5);
196 22565         32686 my $state = 0;
197 22565         29273 my @n;
198 22565         61860 foreach my $i (reverse 0 .. max($#x,$#y)) {
199 106999   100     322423 my $digit = $n[$i] = $_yx_to_digit[$state + 5*($y[$i]||0) + ($x[$i]||0)];
      100        
200 106999         175438 $state = $_next_state[$state+$digit];
201             }
202 22565         36882 my $zero = $x*0*$y;
203 22565         48662 my $n = digit_join_lowtohigh(\@n, 25, $zero);
204 22565         33648 $n = $n*$arms;
205 22565 50       43242 if (my ($nx,$ny) = $self->n_to_xy($n)) {
206 22565 100 100     81390 if ($nx == $x && $ny == $y) {
207 5019 50       20984 return $n + ($arm ? $arm-$arms : $arm);
208             }
209             }
210             }
211             }
212 4386         9191 ($x,$y) = ($y,-$x); # rotate -90
213             ### rotate to: "$x, $y"
214             }
215 4386         11143 return undef;
216             }
217              
218             # not exact
219             sub rect_to_n_range {
220 8     8 1 764 my ($self, $x1,$y1, $x2,$y2) = @_;
221             ### DekkingCurve rect_to_n_range(): "$x1,$y1, $x2,$y2"
222              
223 8         22 $x1 = round_nearest ($x1);
224 8         21 $x2 = round_nearest ($x2);
225 8         18 $y1 = round_nearest ($y1);
226 8         17 $y2 = round_nearest ($y2);
227              
228 8 50       20 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         16 my $arms = $self->{'arms'};
232 8 50 33     57 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         25 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 14 my ($self, $level) = @_;
246 2         10 return (0, 25**$level * $self->{'arms'});
247             }
248             sub n_to_level {
249 8     8 1 218 my ($self, $n) = @_;
250             ### n_to_level(): $n
251 8 50       23 if ($n < 0) { return undef; }
  0         0  
252 8 50       21 if (is_infinite($n)) { return $n; }
  0         0  
253 8         21 $n = round_nearest($n);
254 8         19 $n += $self->{'arms'}-1; # division rounding up
255 8         25 _divrem_mutate ($n, $self->{'arms'});
256 8         23 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   12061 my ($self, $x) = @_;
266 3126 50 33     8203 if ($x < 0 || is_infinite($x)) { return 0; }
  0         0  
267 3126 100       6924 if ($x == 0) { return 1; }
  1         3  
268 3125         6076 my $digit = _divrem_mutate($x, 5);
269 3125 100       6168 if ($digit) {
270 2500         5343 return ($digit == 1);
271             }
272             # find lowest non-zero
273 625   66     1560 while ($x && ! ($digit = _divrem_mutate($x, 5))) { }
274 625   100     2038 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   11622 my ($self, $y) = @_;
280 3126 50 33     8486 if ($y < 0 || is_infinite($y)) { return 0; }
  0         0  
281 3126         7240 my $digit = _divrem_mutate($y, 5);
282 3126 100       6038 if ($digit != 4) {
283 2501         5599 return ($digit == 3);
284             }
285             # find lowest non-4
286 625   100     1628 while ($y && ($digit = _divrem_mutate($y, 5)) == 4) { }
287 625   100     2081 return ($digit == 2 || $digit == 3);
288             }
289              
290             #------------------------------------------------------------------------------
291             1;
292             __END__