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