File Coverage

blib/lib/Math/PlanePath/CornerAlternating.pm
Criterion Covered Total %
statement 94 117 80.3
branch 39 58 67.2
condition 4 8 50.0
subroutine 16 19 84.2
pod 4 4 100.0
total 157 206 76.2


line stmt bran cond sub pod time code
1             # Copyright 2021 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::CornerAlternating;
20 1     1   1502 use 5.004;
  1         4  
21 1     1   6 use strict;
  1         2  
  1         25  
22              
23 1     1   5 use vars '$VERSION', '@ISA';
  1         2  
  1         62  
24             $VERSION = 129;
25 1     1   809 use Math::PlanePath;
  1         3  
  1         30  
26 1     1   524 use Math::PlanePath::Base::NSEW;
  1         3  
  1         68  
27             *_sqrtint = \&Math::PlanePath::_sqrtint;
28             @ISA = ('Math::PlanePath::Base::NSEW',
29             'Math::PlanePath');
30              
31             use Math::PlanePath::Base::Generic
32 1     1   7 'round_nearest';
  1         2  
  1         40  
33              
34             # uncomment this to run the ### lines
35             # use Smart::Comments;
36              
37              
38 1     1   6 use constant class_x_negative => 0;
  1         2  
  1         45  
39 1     1   5 use constant class_y_negative => 0;
  1         2  
  1         51  
40             *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1;
41              
42 1     1   640 use Math::PlanePath::SquareSpiral;
  1         3  
  1         49  
43             *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array;
44              
45 1     1   7 use constant dx_maximum => 1;
  1         2  
  1         55  
46 1     1   6 use constant dy_minimum => -1;
  1         2  
  1         928  
47              
48             # | 4---5---6 first South at 6 completing all NSEW
49             # | | |
50             # 1 | 3---2 first right turn at 3
51             # | |
52             # Y=0 | 0---1 first left turn at 1
53             # +-----------
54             sub _UNDOCUMENTED__turn_any_left_at_n {
55 0     0   0 my ($self) = @_;
56 0         0 return $self->{'n_start'} + $self->{'wider'} + 1;
57             }
58             sub _UNDOCUMENTED__turn_any_right_at_n {
59 0     0   0 my ($self) = @_;
60 0         0 return $self->{'n_start'} + 2*$self->{'wider'} + 3;
61             }
62             sub _UNDOCUMENTED__dxdy_list_at_n {
63 21     21   97 my ($self) = @_;
64 21         61 return $self->{'n_start'} + 3*$self->{'wider'} + 6;
65             }
66              
67             #------------------------------------------------------------------------------
68              
69             sub new {
70 43     43 1 4921 my $self = shift->SUPER::new (@_);
71 43 100       127 if (! defined $self->{'n_start'}) {
72 22         68 $self->{'n_start'} = $self->default_n_start;
73             }
74 43   100     159 $self->{'wider'} ||= 0; # default
75 43         75 return $self;
76             }
77              
78             sub n_to_xy {
79 294     294 1 464 my ($self, $n) = @_;
80             ### Corner n_to_xy: $n
81              
82             # adjust to N=0 at origin X=0,Y=0
83 294         434 $n = $n - $self->{'n_start'};
84 294 50       526 if ($n < 0) { return; }
  0         0  
85              
86 294         414 my $wider = $self->{'wider'};
87 294         452 my $int = int($n);
88 294         385 $n -= $int; # frac part
89              
90             # wider==0 n_start=0
91             # row start N=0, 1, 4, 9, 16, 25
92             # N = Y^2
93             # Y = floor sqrt(N)
94             #
95             # wider==2 n_start=0
96             # N=0, 3, 8, 15, 24
97             # N = Y^2 + 2*Y
98             # Y = floor (-w + sqrt(w^2 + 4*N))/2
99              
100             # gnomon number d,
101             # starting d=0 for point N=0 at the origin (and more when wider),
102             # with point immediately before each gnomon included in the following one
103             #
104 294         598 my $d = int ((_sqrtint(4*($int+1) + $wider*$wider) - $wider) / 2);
105             ### d frac: (sqrt(int(4*($int+1)) + $wider*$wider) - $wider) / 2
106             ### $d
107              
108             # $r ranges -1 upwards, with -1 being the point immediately before gnomon $d
109 294         486 my $r = $int - $d*($d+$wider);
110             ### subtract start: $d*($d+$wider)
111             ### $r
112 294 100       499 if ($d % 2) {
113 105 100       190 if ($r < 0) {
    100          
114             ### X axis rightward ...
115 21         76 return ($d+$wider+$n-1, 0);
116             } elsif ($r < $d) {
117             ### right upward ...
118 42         113 return ($d+$wider, $r+$n);
119             } else {
120             ### top leftward ...
121 42         120 return ($d+$wider-($r-$d)-$n, $d);
122             }
123             } else {
124 189 100       359 if ($r < 0) {
    100          
125             ### Y axis upward ...
126 42         116 return (0, $d-1+$n);
127             } elsif ($r < $d + $wider) {
128             ### top rightward ...
129 84         210 return ($r+$n, $d);
130             } else {
131             ### right downward ...
132 63         173 return ($d+$wider, $d-($r-$d-$wider) - $n);
133             }
134             }
135             }
136              
137             sub xy_to_n {
138 40     40 1 70 my ($self, $x, $y) = @_;
139             ### Corner xy_to_n(): "$x,$y"
140              
141 40         78 $x = round_nearest ($x);
142 40         73 $y = round_nearest ($y);
143 40 50 33     114 if ($x < 0 || $y < 0) {
144 0         0 return undef;
145             }
146              
147 40         61 my $wider = $self->{'wider'};
148 40         53 my $xw = $x - $wider;
149 40 100       72 if ($y >= $xw) {
150             ### top edge ...
151             return ($y*($y+$wider) + ($y % 2 ? 2*$y+$wider - $x : $x)
152 30 100       91 + $self->{'n_start'});
153             } else {
154             ### right vertical ...
155             return ($x*$xw + ($xw % 2 ? $y : $x+$xw - $y)
156 10 100       33 + $self->{'n_start'});
157             }
158             }
159              
160             # exact
161             sub rect_to_n_range {
162 20     20 1 71 my ($self, $x1,$y1, $x2,$y2) = @_;
163             ### Corner rect_to_n_range(): "$x1,$y1, $x2,$y2"
164              
165 20         51 $x1 = round_nearest ($x1);
166 20         54 $y1 = round_nearest ($y1);
167 20         41 $x2 = round_nearest ($x2);
168 20         41 $y2 = round_nearest ($y2);
169 20 50       43 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); }
  0         0  
170 20 50       38 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); }
  0         0  
171              
172 20 50 33     65 if ($y2 < 0 || $x2 < 0) {
173 0         0 return (1, 0); # rect all negative, no N
174             }
175              
176 20 50       37 if ($x1 < 0) { $x1 *= 0; } # "*=" to preserve bigint x1 or y1
  0         0  
177 20 50       34 if ($y1 < 0) { $y1 *= 0; }
  0         0  
178              
179 20         31 my $wider = $self->{'wider'};
180 20         74 my $xmin = $x1;
181 20         28 my $ymin = $y1;
182              
183 20         29 my $t = $wider + $y1; # x where diagonal goes through row y1
184 20 100       40 if ($x1 <= $t) {
185             ### for min, x1,y1 at or before diagonal ...
186             # | +-------+ / y2
187             # | | |/ |
188             # | | / | /
189             # | | /| | +------+ / y2
190             # | | / | | | | /
191             # | @----@--+ y1 | @------@ / y1
192             # | x1 / x2 | x1 x2 /
193             # +------------------ +--------------------
194             # ..wider
195 18 100       39 if ($y1 % 2) {
196             ### leftward row y1, min at smaller of x2 or diagonal ...
197 4 50       10 $xmin = ($x2 < $t ? $x2 : $t);
198             }
199              
200             } else {
201             ### for min, x1,y1 after diagonal ...
202             # /
203             # | +------+ y2 |
204             # | | / | | /
205             # | |/ | | /
206             # | @ | | / @------+ y2
207             # | /| | | / | |
208             # | / @------+ y1 | / @------+ y1
209             # | / x1 x2 | / x1 x2
210             # +------------------ +------------------
211             # ^...^xw
212             # wider
213              
214 2         4 $t = $x1 - $wider;
215 2 50       8 unless ($t % 2) {
216             ### column x1 even, downward ...
217 0 0       0 $ymin = ($y2 < $t ? $y2 : $t);
218             }
219             }
220              
221             #-----
222 20         27 my $xmax = $x2;
223 20         27 my $ymax = $y2;
224              
225             # | /
226             # | @------/ y2 x2,y2 on the diagonal
227             # | | /| executes both "on or before"
228             # | | / | and "on or after"
229             # | | / | selecting one or other of
230             # | | / | the opposite points
231             # | +-/----@ y1 according as direction of
232             # | x1/ x2 the gnomon
233             # +---------------
234              
235 20         31 $t = $x2 - $wider; # y where diagonal passes column x2
236 20 100       38 if ($y2 >= $t) {
237             ### for max, x2,y2 on or before diagonal ...
238             # max is x1 in an odd row (leftward)
239             #
240             # | /
241             # | @------@ /y2
242             # | | |/
243             # | | /
244             # | | /|
245             # | | / |
246             # | +---/--+ y1
247             # | x1 / x2
248             # +----------------
249 14 100       37 if ($y2 % 2) {
250             ### top row odd, max at leftward x1 ...
251 6         10 $xmax = $x1;
252             }
253             }
254 20 100       38 if ($y2 <= $t) {
255             ### for max, x2,y2 on or after of diagonal ...
256             # max is y1 in a downward column ...
257             #
258             # | /
259             # | +--/---@ y2
260             # | | / |
261             # | |/ |
262             # | / |
263             # | /| |
264             # | / +------@ y1
265             # | / x2
266             # +-----------------
267             # ^
268             # wider
269             #
270 8 100       23 unless ($t % 2) {
271             ### x2 column even, downward ...
272 4         7 $ymax = $y1;
273             }
274             }
275              
276             ### min xy: "$xmin,$ymin"
277             ### max xy: "$xmax,$ymax"
278 20         44 return ($self->xy_to_n ($xmin,$ymin),
279             $self->xy_to_n ($xmax,$ymax));
280             }
281              
282             #------------------------------------------------------------------------------
283              
284             sub _NOTDOCUMENTED_n_to_figure_boundary {
285 0     0     my ($self, $n) = @_;
286             ### _NOTDOCUMENTED_n_to_figure_boundary(): $n
287              
288             # adjust to N=1 at origin X=0,Y=0
289 0           $n = $n - $self->{'n_start'} + 1;
290              
291 0 0         if ($n < 1) {
292 0           return undef;
293             }
294              
295 0           my $wider = $self->{'wider'};
296 0 0         if ($n <= $wider) {
297             # single block row, nothing special at diagonal
298             # +---+-----+----+
299             # | 1 | ... | $n | boundary = 2*N + 2
300             # +---+-----+----+
301 0           return 2*$n + 2;
302             }
303              
304 0           my $d = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2);
305             ### $d
306             ### $wider
307              
308 0 0         if ($n > $d*($d+1+$wider) + ($d%2 ? 0 : $wider)) {
    0          
309 0           $wider++;
310             ### increment for +2 after turn on diagonal ...
311             }
312 0           return 4*$d + 2*$wider + 2;
313             }
314              
315             #------------------------------------------------------------------------------
316             1;
317             __END__