File Coverage

blib/lib/Math/PlanePath/AlternatePaperMidpoint.pm
Criterion Covered Total %
statement 133 148 89.8
branch 41 54 75.9
condition 10 17 58.8
subroutine 18 22 81.8
pod 10 10 100.0
total 212 251 84.4


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             # math-image --path=AlternatePaperMidpoint,arms=8 --all --output=numbers_dash
20             # math-image --path=AlternatePaperMidpoint --lines --scale=20
21              
22              
23             package Math::PlanePath::AlternatePaperMidpoint;
24 1     1   9292 use 5.004;
  1         12  
25 1     1   42 use strict;
  1         4  
  1         42  
26 1     1   6 use List::Util 'min'; # 'max'
  1         2  
  1         148  
27             *max = \&Math::PlanePath::_max;
28              
29 1     1   6 use vars '$VERSION', '@ISA';
  1         3  
  1         68  
30             $VERSION = 127;
31 1     1   663 use Math::PlanePath;
  1         4  
  1         33  
32 1     1   401 use Math::PlanePath::Base::NSEW;
  1         3  
  1         40  
33             @ISA = ('Math::PlanePath::Base::NSEW',
34             'Math::PlanePath');
35              
36             use Math::PlanePath::Base::Generic
37 1         48 'is_infinite',
38 1     1   6 'round_nearest';
  1         2  
39             use Math::PlanePath::Base::Digits
40 1         79 'round_down_pow',
41             'digit_split_lowtohigh',
42 1     1   455 'digit_join_lowtohigh';
  1         3  
43             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
44              
45 1     1   611 use Math::PlanePath::AlternatePaper;
  1         3  
  1         58  
46              
47             # uncomment this to run the ### lines
48             #use Smart::Comments;
49              
50              
51 1         63 use constant parameter_info_array => [ { name => 'arms',
52             share_key => 'arms_8',
53             display => 'Arms',
54             type => 'integer',
55             minimum => 1,
56             maximum => 8,
57             default => 1,
58             width => 1,
59             description => 'Arms',
60 1     1   8 } ];
  1         2  
61              
62 1     1   7 use constant n_start => 0;
  1         2  
  1         1211  
63              
64             sub x_negative {
65 6     6 1 82 my ($self) = @_;
66 6         16 return ($self->{'arms'} >= 3);
67             }
68             sub y_negative {
69 6     6 1 301 my ($self) = @_;
70 6         16 return ($self->{'arms'} >= 5);
71             }
72             {
73             my @x_negative_at_n = (undef,
74             undef,undef,11,3,
75             3,3,3,3);
76             sub x_negative_at_n {
77 0     0 1 0 my ($self) = @_;
78 0         0 return $x_negative_at_n[$self->{'arms'}];
79             }
80             }
81             {
82             my @y_negative_at_n = (undef,
83             undef,undef,undef,undef,
84             24,11,12,7);
85             sub y_negative_at_n {
86 0     0 1 0 my ($self) = @_;
87 0         0 return $y_negative_at_n[$self->{'arms'}];
88             }
89             }
90              
91             sub sumxy_minimum {
92 0     0 1 0 my ($self) = @_;
93 0 0       0 return ($self->arms_count <= 3
94             ? 0 # 1,2,3 arms above X=-Y diagonal
95             : undef);
96             }
97             sub diffxy_minimum {
98 0     0 1 0 my ($self) = @_;
99 0 0       0 return ($self->arms_count == 1
100             ? 0 # 1 arms right of X=Y diagonal
101             : undef);
102             }
103              
104              
105             #------------------------------------------------------------------------------
106              
107             sub new {
108 25     25 1 2479 my $self = shift->SUPER::new(@_);
109 25   100     142 $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1));
110 25         52 return $self;
111             }
112              
113             # +-----------+ states
114             # |\ -------/|
115             # | \ \ 4 / |
116             # |^ \ \ / |
117             # || \ v / /||
118             # || \ / / ||
119             # ||8 / * /12||
120             # || / / \ ||
121             # ||/ / ^ \ ||
122             # | / \ \ v|
123             # | / 0 \ \ |
124             # |/ ------ \|
125             # +-----------+
126             #
127             # + state=0 digits
128             # /|\
129             # / | \
130             # / | \
131             # /\ 1|3 /\
132             # / \ | / \
133             # / 0 \|/ 2 \
134             # +------+------+
135              
136             my @next_state = (0, 12, 0, 8, # 0 forward
137             4, 8, 4, 12, # 4 forward NW
138             4, 8, 0, 8, # 8 reverse
139             0, 12, 4, 12, # 12 reverse NE
140             );
141             my @digit_to_x = (0,0,1,1,
142             1,1,0,0,
143             0,0,0,0,
144             1,1,1,1,
145             );
146             my @digit_to_y = (0,0,0,0,
147             1,1,1,1,
148             0,0,1,1,
149             1,1,0,0,
150             );
151              
152             sub n_to_xy {
153 2659     2659 1 14069 my ($self, $n) = @_;
154             ### AlternatePaperMidpoint n_to_xy(): $n
155              
156 2659 50       5079 if ($n < 0) { return; }
  0         0  
157 2659 50       5078 if (is_infinite($n)) { return ($n, $n); }
  0         0  
158              
159             {
160 2659         4696 my $int = int($n);
  2659         4073  
161 2659 100       5027 if ($n != $int) {
162 6         17 my ($x1,$y1) = $self->n_to_xy($int);
163 6         25 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
164 6         14 my $frac = $n - $int; # inherit possible BigFloat
165 6         8 my $dx = $x2-$x1;
166 6         10 my $dy = $y2-$y1;
167 6         23 return ($frac*$dx + $x1, $frac*$dy + $y1);
168             }
169 2653         3907 $n = $int; # BigFloat int() gives BigInt, use that
170             }
171              
172 2653         3928 my $zero = ($n * 0); # inherit bignum 0
173 2653         5785 my $arm = _divrem_mutate ($n, $self->{'arms'});
174             ### $arm
175             ### $n
176              
177 2653         5608 my @digits = digit_split_lowtohigh($n,4);
178 2653         4356 my $state = my $dirstate = 0;
179              
180 2653         4046 my @x;
181             my @y;
182 2653         5268 foreach my $i (reverse 1 .. scalar(@digits)) {
183 7123         10541 my $digit = $digits[$i-1]; # high to low, all digits
184 7123         9265 $state += $digit;
185 7123 100       12546 if ($digit != 3) {
186 5210         7144 $dirstate = $state;
187             }
188 7123         10198 $x[$i] = $digit_to_x[$state]; # high to low, leaving one lowest
189 7123         10157 $y[$i] = $digit_to_y[$state];
190 7123         11501 $state = $next_state[$state];
191             }
192              
193 2653         4480 $x[0] = $digit_to_x[$state]; # state=4,12 increment
194 2653         4186 $y[0] = $digit_to_y[$state + 3]; # state=4,8 increment
195              
196 2653         5816 my $x = digit_join_lowtohigh(\@x,2,$zero);
197 2653         5499 my $y = digit_join_lowtohigh(\@y,2,$zero);
198              
199             ### final: "x=$x,y=$y state=$state"
200              
201 2653 100       5358 if ($arm & 1) {
202 1445         2563 ($x,$y) = ($y+1,$x+1); # transpose and offset
203             }
204 2653 100       4792 if ($arm & 2) {
205 1310         2347 ($x,$y) = (-$y,$x+1); # rotate +90 and offset
206             }
207 2653 100       4808 if ($arm & 4) {
208 1050         1465 $x = -1 - $x; # rotate 180 and offset
209 1050         1420 $y = 1 - $y;
210             }
211              
212             # ### rotated return: "$x,$y"
213 2653         6272 return ($x,$y);
214             }
215              
216             # | |
217             # 64-65-66 71-72-73-74 95
218             # | |
219             # 63 98-97-96
220             # | |
221             # 20-21 62 99
222             # | | |
223             # 19 22 61-60-59
224             # | | |
225             # 16-17-18 23 56-57-58
226             # | | |
227             # 15 26-25-24 55 50-49-48-47
228             # | | | | |
229             # 4--5 14 27-28-29 54 51 36-37 46
230             # | | | | | | | | |
231             # 3 6 13-12-11 30 53-52 35 38 45-44-43
232             # | | | | | | |
233             # 0--1--2 7--8--9-10 31-32-33-34 39-40-41-42
234             #
235             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
236              
237             # 43-35 42-50-58 57-49-41
238             # | | | |
239             # 91-99 51 27 34-26-18 17-25-33
240             # | | | | |
241             # 83-75-67-59 19-11--3 10 9 32-40
242             # | | | |
243             # 84-76-68-60 20-12--4 2 1 24 48 96-88
244             # | | | | | |
245             # 92 52 28 5 6 0--8-16 56-64-72-80
246             # | | | |
247             # 44-36 13 14 7-15-23 63-71-79-87
248             # | | | | |
249             # 37-29-21 22-30-38 31 55 95
250             # | | | |
251             # 45-53-61 62-54-46 39-47
252             # | |
253             # 69 70
254              
255             sub xy_to_n {
256 44     44 1 523 my ($self, $x, $y) = @_;
257             ### AlternatePaperMidpoint xy_to_n(): "$x, $y"
258              
259 44         107 $x = round_nearest($x);
260 44         98 $y = round_nearest($y);
261              
262 44 50       92 if (is_infinite($x)) {
263 0         0 return $x; # infinity
264             }
265 44 50       105 if (is_infinite($y)) {
266 0         0 return $y; # infinity
267             }
268              
269             # arm in various octants, rotate/transpose to first
270 44         75 my $arm;
271 44 100       101 if ($y >= ($x>=0?0:2)) { # Y>=0 when X positive, Y>=2 when X negative
    100          
272 33         49 $arm = 0;
273             } else {
274             # lower arms 4,5,6,7 ...
275 11         14 $arm = 4;
276 11         21 $x = -1 - $x; # rotate 180, offset
277 11         13 $y = 1 - $y;
278             }
279 44 100       107 if ($x < ($y>0?1:0)) {
    100          
280             ### second quad arms 2,3 ...
281 11         21 ($x,$y) = ($y-1,-$x); # rotate -90, offset
282 11         17 $arm += 2;
283             }
284 44 100       93 if ($y > $x-($x%2)) {
285             ### above diagonal, arm 1 ...
286 16         34 ($x,$y) = ($y-1,$x-1); # offset and transpose
287 16         26 $arm++;
288             }
289             ### assert: $x >= 0
290             ### assert: $y >= 0
291             ### assert: $y <= $x - ($x%2)
292              
293 44 50       93 if ($arm >= $self->{'arms'}) {
294 0         0 return undef;
295             }
296              
297 44         105 my ($len, $level) = round_down_pow ($x, 2);
298 44 50       97 if (is_infinite($level)) {
299 0         0 return ($level);
300             }
301              
302             # + state=0 digits
303             # /|\
304             # / | \
305             # / | \
306             # /\ 1|3 /\
307             # / \ | / \
308             # / 0 \|/ 2 \
309             # +------+------+
310              
311             # + state=0 digits
312             # /|\
313             # / | \
314             # / | \
315             # /\ 2|0 /\
316             # / \ | / \
317             # / 3 \|/ 1 \
318             # +------+------+
319              
320 44         114 my $n = ($x * 0 * $y); # inherit bignum 0
321 44         67 my $rev = 0;
322              
323 44         63 $len *= 2;
324 44         88 while ($level-- >= 0) {
325             ### at: "xy=$x,$y rev=$rev len=$len n=".sprintf('%#x',$n)
326              
327             ### assert: $x >= 0
328             ### assert: $y >= 0
329             ### assert: $y <= $x - ($x%2)
330             ### assert: $x+$y+($x%2) < 2*$len
331              
332 236         304 my $digit;
333 236 100       393 if ($x < $len) {
334             ### diagonal: $x+$y+($x%2), $len
335 126 100       234 if ($x+$y+($x%2) < $len) {
336             ### part 0 ...
337 75         113 $digit = 0;
338             } else {
339             ### part 1 ...
340 51         93 ($x,$y) = ($y,$len-1-$x); # shift, rotate -90
341 51         73 $rev ^= 3;
342 51         69 $digit = 2; # becoming digit=1 with reverse
343             }
344             } else {
345 110         146 $x -= $len;
346             ### 2,3 ycmp: $y, $x-($x%2)
347 110 100       189 if ($y <= $x-($x%2)) {
348             ### part 2 ...
349 61         94 $digit = 2;
350             } else {
351             ### part 3 ...
352 49         93 ($x,$y) = ($len-1-$y,$x); # shift, rotate +90
353 49         68 $rev ^= 3;
354 49         70 $digit = 0; # becoming digit=3 with reverse
355             }
356             }
357             ### $digit
358              
359 236         318 $digit ^= $rev; # $digit = 3-$digit if reverse
360             ### reversed digit: $digit
361              
362 236         322 $n *= 4;
363 236         316 $n += $digit;
364 236         430 $len /= 2;
365             }
366             ### final: "xy=$x,$y rev=$rev"
367              
368             ### assert: $x == 0
369             ### assert: $y == 0
370              
371 44         153 return $n*$self->{'arms'} + $arm;
372             }
373              
374              
375             # not exact
376             sub rect_to_n_range {
377 40     40 1 3296 my ($self, $x1,$y1, $x2,$y2) = @_;
378             ### AlternatePaperMidpoint rect_to_n_range(): "$x1,$y1 $x2,$y2 arms=$self->{'arms'}"
379              
380 40         110 $x1 = round_nearest($x1);
381 40         82 $x2 = round_nearest($x2);
382 40         80 $y1 = round_nearest($y1);
383 40         83 $y2 = round_nearest($y2);
384              
385 40 50       88 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
386 40 50       77 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
387              
388 40         86 my $arms = $self->{'arms'};
389 40 50 66     263 if (($arms == 1 && $y1 > $x2) # x2,y1 bottom right corner
      66        
      33        
      66        
      33        
390             || ($arms <= 2 && $x2 < 0)
391             || ($arms <= 4 && $y2 < 0)) {
392             ### outside ...
393 0         0 return (1,0);
394             }
395              
396 40 100       174 my ($len) = round_down_pow (max ($x2,
    100          
    100          
397             ($arms >= 2 ? $y2-1 : ()),
398             ($arms >= 4 ? -1-$x1 : ()),
399             ($arms >= 6 ? -$y1 : ())),
400             2);
401 40         113 return (0, 2*$arms*$len*$len-1);
402             }
403              
404             #------------------------------------------------------------------------------
405             # levels
406              
407 1     1   9 use Math::PlanePath::DragonMidpoint;
  1         3  
  1         83  
408             *level_to_n_range = \&Math::PlanePath::DragonMidpoint::level_to_n_range;
409             *n_to_level = \&Math::PlanePath::DragonMidpoint::n_to_level;
410              
411             #------------------------------------------------------------------------------
412             1;
413             __END__