File Coverage

blib/lib/Math/PlanePath/Corner.pm
Criterion Covered Total %
statement 96 111 86.4
branch 24 34 70.5
condition 4 8 50.0
subroutine 20 24 83.3
pod 6 6 100.0
total 150 183 81.9


line stmt bran cond sub pod time code
1             # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 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             # cf Matthew Szudzik http://www.szudzik.com/ElegantPairing.pdf going inwardly:
20             #
21             # 5 | 25--...
22             # |
23             # 4 | 16--17--18--19 24
24             # | |
25             # 3 | 9--10--11 15 23
26             # | | |
27             # 2 | 4-- 5 8 14 22
28             # | | | |
29             # 1 | 1 3 7 13 21
30             # | | | | |
31             # Y=0 | 0 2 6 12 20
32             # +---------------------
33             # X=0 1 2 3 4
34             # not in OEIS: 0, 1,2, 4,3,6, 9,5,7,12, 16,10,8,13,20
35             # not in OEIS: 1, 2,3, 5,4,7, 10,6,8,13, 17,11,9,14,21
36             # one-based: 1, 3,2, 7,4,5, 13,8,6,10 = A108644
37             #
38             # cf A185728 where diagonal is last in each gnomon
39             # A185725 gnomon sides alternately starting from ends
40             # A185726 gnomon sides alternately starting from diagonal
41             #
42             # cf A004120 ??
43             #
44             #----------
45             # corners alternating "shell"
46             #
47             # A319514 interleaved x,y
48             # x=OEIS_bfile_func("A319289");
49             # y=OEIS_bfile_func("A319290");
50             # plothraw(vector(3^3,n,n--; x(n)), \
51             # vector(3^3,n,n--; y(n)), 1+8+16+32)
52              
53              
54              
55             package Math::PlanePath::Corner;
56 2     2   2370 use 5.004;
  2         8  
57 2     2   12 use strict;
  2         4  
  2         56  
58 2     2   19 use List::Util 'min';
  2         3  
  2         235  
59              
60 2     2   13 use vars '$VERSION', '@ISA';
  2         4  
  2         226  
61             $VERSION = 129;
62 2     2   857 use Math::PlanePath;
  2         5  
  2         115  
63             *_sqrtint = \&Math::PlanePath::_sqrtint;
64             @ISA = ('Math::PlanePath');
65              
66             use Math::PlanePath::Base::Generic
67 2     2   14 'round_nearest';
  2         4  
  2         111  
68              
69             # uncomment this to run the ### lines
70             # use Smart::Comments;
71              
72              
73 2     2   13 use constant class_x_negative => 0;
  2         4  
  2         132  
74 2     2   12 use constant class_y_negative => 0;
  2         14  
  2         93  
75 2     2   17 use constant n_frac_discontinuity => .5;
  2         3  
  2         125  
76             *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1;
77              
78 2     2   1375 use Math::PlanePath::SquareSpiral;
  2         6  
  2         97  
79             *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array;
80              
81 2     2   13 use constant dx_maximum => 1;
  2         5  
  2         106  
82 2     2   12 use constant dy_minimum => -1;
  2         4  
  2         172  
83              
84             # dSum east dX=1,dY=0 for dSum=+1
85             # south dX=0,dY=-1 for dSum=-1
86             # gnomon up to start of next gnomon is
87             # X=wider+k,Y=0 to X=0,Y=k+1
88             # dSum = 0-(wider+k) + (k+1)-0
89             # = -wider-k + k + 1
90             # = 1-wider
91             sub dsumxy_minimum {
92 0     0 1 0 my ($self) = @_;
93 0         0 return min(-1, 1-$self->{'wider'});
94             }
95 2     2   12 use constant dsumxy_maximum => 1; # East along top
  2         3  
  2         90  
96              
97             # dDiffXY east dX=1,dY=0 for dDiffXY=1-0 = 1
98             # south dX=0,dY=-1 for dDiffXY=0-(-1) = 1
99             # gnomon up to start of next gnomon is
100             # X=wider+k,Y=0 to X=0,Y=k+1
101             # dDiffXY = 0-(wider+k) - ((k+1)-0)
102             # = -wider - 2*k - 1 unbounded negative
103 2     2   10 use constant ddiffxy_maximum => 1; # East along top
  2         4  
  2         88  
104              
105             # use constant dir_minimum_dxdy => (1,0); # East at N=2
106 2     2   22 use constant dir_maximum_dxdy => (0,-1); # South at N=3
  2         5  
  2         1785  
107              
108             sub turn_any_left {
109 0     0 1 0 my ($self) = @_;
110 0         0 return ($self->{'wider'} != 0); # wider=0 no left, right or straight always
111             }
112             sub _UNDOCUMENTED__turn_any_left_at_n {
113 0     0   0 my ($self) = @_;
114 0 0       0 return ($self->{'wider'} ? $self->n_start + $self->{'wider'}
115             : undef); # wider=0 no left
116             }
117             sub _UNDOCUMENTED__turn_any_right_at_n {
118 0     0   0 my ($self) = @_;
119 0         0 return $self->n_start + $self->{'wider'} + 1;
120             }
121              
122              
123             #------------------------------------------------------------------------------
124              
125             # same as PyramidSides, just 45 degress around
126              
127             sub new {
128 57     57 1 7538 my $self = shift->SUPER::new (@_);
129 57 50       165 if (! defined $self->{'n_start'}) {
130 57         144 $self->{'n_start'} = $self->default_n_start;
131             }
132 57   100     119 $self->{'wider'} ||= 0; # default
133 57         107 return $self;
134             }
135              
136             sub n_to_xy {
137 101     101 1 7143 my ($self, $n) = @_;
138             ### Corner n_to_xy: $n
139              
140             # adjust to N=1 at origin X=0,Y=0
141 101         196 $n = $n - $self->{'n_start'} + 1;
142              
143             # comparing $n<0.5, but done in integers for the benefit of Math::BigInt
144 101 100       1241 if (2*$n < 1) {
145 1         247 return;
146             }
147              
148 100         699 my $wider = $self->{'wider'};
149              
150             # wider==0
151             # vertical at X=0 has N=1, 2, 5, 10, 17, 26
152             # but start 0.5 back so at X=-0.5 have N=0.5, 1.5, 4.5, 9.5, 16.5, 25.5
153             # N = (Y^2 + 1/2)
154             # Y = floor sqrt(N - 1/2)
155             # = floor sqrt(4*N - 2)/2 staying in integers for the sqrt()
156             #
157             # wider==1
158             # 0.5 back so at X=-0.5 have N=0.5, 2.5, 6.5, 12.5
159             # N = (Y^2 + Y + 1/2)
160             # Y = floor -1/2 + sqrt(N - 1/4)
161             # = floor (-1 + sqrt(4*N - 1))/2
162             #
163             # wider==2
164             # 0.5 back so at X=-0.5 have N=0.5, 3.5, 8.5, 15.5
165             # N = (Y^2 + 2 Y + 1/2)
166             # Y = floor -1 + sqrt(N + 1/2)
167             # = floor (-2 + sqrt(4*N + 2))/2
168             #
169             # wider==3
170             # 0.5 back so at X=-0.5 have N=0.5, 4.5, 10.5, 18.5
171             # N = (Y^2 + 3 Y + 1/2)
172             # Y = floor -3/2 + sqrt(N + 7/4)
173             # = floor (-3 + sqrt(4*N + 7))/2
174             #
175             # 0,1,4,9
176             # my $y = int((sqrt(4*$n + -1) - $wider) / 2);
177             # ### y frac: (sqrt(4*$n + -1) - $wider) / 2
178              
179 100         260 my $y = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2);
180             ### y frac: (sqrt(int(4*$n) + $wider*$wider - 2) - $wider) / 2
181             ### $y
182              
183             # diagonal at X=Y has N=1, 3, 7, 13, 21
184             # N = ((Y + 1)*Y + (Y+1)*wider + 1)
185             # = ((Y + 1 + wider)*Y + wider + 1)
186             # so subtract that leaving N negative on the horizontal part, or positive
187             # for the downward vertical part
188             #
189 100         5418 $n -= $y*($y+1+$wider) + $wider + 1;
190             ### corner n: $y*($y+1+$wider) + $wider + 1
191             ### rem: $n
192             ### assert: $n!=$n || $n >= -($y+$wider+0.5)
193             # ### assert: $n <= ($y+0.5)
194              
195 100 100       2213 if ($n < 0) {
196             # top horizontal
197 63         354 return ($n + $y+$wider,
198             $y);
199             } else {
200             # right vertical
201 37         256 return ($y+$wider,
202             -$n + $y);
203             }
204             }
205              
206             sub xy_to_n {
207 392     392 1 6990 my ($self, $x, $y) = @_;
208             ### Corner xy_to_n(): "$x,$y"
209              
210 392         741 $x = round_nearest ($x);
211 392         746 $y = round_nearest ($y);
212 392 50 33     1189 if ($x < 0 || $y < 0) {
213 0         0 return undef;
214             }
215              
216 392         1124 my $wider = $self->{'wider'};
217 392         508 my $xw = $x - $wider;
218 392 100       835 if ($y >= $xw) {
219             ### top edge, N left is: $y*$y + $wider*$y + 1
220             return ($y*$y + $wider*$y # Y axis N value
221             + $x # plus X offset across
222 298         912 + $self->{'n_start'});
223             } else {
224             ### right vertical, N diag is: $xw*$xw + $xw*$wider
225             ### $xw
226             # Ndiag = Nleft + Y+w
227             # N = xw*xw + w*xw + 1 + xw+w + (xw - y)
228             # = xw*xw + w*xw + 1 + xw+w + xw - y
229             # = xw*xw + xw*(w+2) + 1 + w - y
230             # = xw*(xw + w+2) + w+1 - y
231             return ($xw*($xw+$wider+2) + $wider
232             - $y
233 94         240 + $self->{'n_start'});
234             }
235             }
236              
237             # exact
238             sub rect_to_n_range {
239 146     146 1 10385 my ($self, $x1,$y1, $x2,$y2) = @_;
240             ### Corner rect_to_n_range(): "$x1,$y1, $x2,$y2"
241              
242 146         353 $x1 = round_nearest ($x1);
243 146         293 $y1 = round_nearest ($y1);
244 146         302 $x2 = round_nearest ($x2);
245 146         279 $y2 = round_nearest ($y2);
246 146 50       300 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); }
  0         0  
247 146 50       251 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); }
  0         0  
248              
249 146 50 33     475 if ($y2 < 0 || $x2 < 0) {
250 0         0 return (1, 0); # rect all negative, no N
251             }
252              
253 146 50       237 if ($x1 < 0) { $x1 *= 0; } # "*=" to preserve bigint x1 or y1
  0         0  
254 146 50       276 if ($y1 < 0) { $y1 *= 0; }
  0         0  
255              
256 146         230 my $wider = $self->{'wider'};
257 146         200 my $ylo = $y1;
258 146         203 my $xw = $x1 - $wider;
259              
260 146 100       264 if ($y1 <= $xw) {
261             # left column is partly or wholly below X=Y diagonal
262             #
263             # make x1,y1 the min pos
264 52 100       99 $y1 = ($y2 < $xw
265              
266             # wholly below diag, min "*" is at top y2 of the x1 column
267             #
268             # | /
269             # | /
270             # | / *------+ y2
271             # | / | |
272             # | / +------+ y1
273             # | / x1 x2
274             # +------------------
275             # ^.....^
276             # wider xw
277             #
278             ? $y2
279              
280             # only partly below diag, min "*" is the X=Y+wider diagonal at x1
281             #
282             # /
283             # | +------+ y2
284             # | | / |
285             # | |/ |
286             # | * |
287             # | /| |
288             # | / +------+ y1
289             # | / x1 x2
290             # +------------------
291             # ^...^xw
292             # wider
293             #
294             : $xw);
295             }
296              
297 146 100       242 if ($y2 <= $x2 - $wider) {
298             # right column entirely at or below X=Y+wider diagonal so max is at the
299             # ylo bottom end of the column
300             #
301             # | /
302             # | --/---+ y2
303             # | | / |
304             # | |/ |
305             # | / |
306             # | /| |
307             # | / +------+ ylo
308             # | / x2
309             # +------------------
310             # ^
311             # wider
312             #
313 59         85 $y2 = $ylo; # x2,y2 now the max pos
314             }
315              
316             ### min xy: "$x1,$y1"
317             ### max xy: "$x2,$y2"
318 146         299 return ($self->xy_to_n ($x1,$y1),
319             $self->xy_to_n ($x2,$y2));
320             }
321              
322             #------------------------------------------------------------------------------
323              
324             sub _NOTDOCUMENTED_n_to_figure_boundary {
325 36     36   3790 my ($self, $n) = @_;
326             ### _NOTDOCUMENTED_n_to_figure_boundary(): $n
327              
328             # adjust to N=1 at origin X=0,Y=0
329 36         83 $n = $n - $self->{'n_start'} + 1;
330              
331 36 50       79 if ($n < 1) {
332 0         0 return undef;
333             }
334              
335 36         55 my $wider = $self->{'wider'};
336 36 100       67 if ($n <= $wider) {
337             # single block row
338             # +---+-----+----+
339             # | 1 | ... | $n | boundary = 2*N + 2
340             # +---+-----+----+
341 4         11 return 2*$n + 2;
342             }
343              
344 32         91 my $d = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2);
345             ### $d
346             ### $wider
347              
348 32 100       75 if ($n > $d*($d+1+$wider) + $wider) {
349 17         25 $wider++;
350             ### increment for +2 after turn ...
351             }
352 32         64 return 4*$d + 2*$wider + 2;
353             }
354              
355             #------------------------------------------------------------------------------
356             1;
357             __END__