File Coverage

blib/lib/Math/PlanePath/GreekKeySpiral.pm
Criterion Covered Total %
statement 191 206 92.7
branch 67 80 83.7
condition 8 9 88.8
subroutine 13 19 68.4
pod 7 7 100.0
total 286 321 89.1


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 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=GreekKeySpiral --lines --scale=25
20             # http://gwydir.demon.co.uk/jo/greekkey/corners.htm
21              
22              
23             package Math::PlanePath::GreekKeySpiral;
24 1     1   1272 use 5.004;
  1         3  
25 1     1   5 use strict;
  1         4  
  1         25  
26              
27 1     1   4 use vars '$VERSION', '@ISA';
  1         2  
  1         58  
28             $VERSION = 128;
29 1     1   740 use Math::PlanePath;
  1         2  
  1         30  
30 1     1   501 use Math::PlanePath::Base::NSEW;
  1         2  
  1         44  
31             @ISA = ('Math::PlanePath::Base::NSEW',
32             'Math::PlanePath');
33              
34             use Math::PlanePath::Base::Generic
35 1         71 'round_nearest',
36 1     1   6 'floor';
  1         2  
37             *_divrem = \&Math::PlanePath::_divrem;
38             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
39             *_sqrtint = \&Math::PlanePath::_sqrtint;
40              
41             # uncomment this to run the ### lines
42             # use Smart::Comments;
43              
44              
45 1     1   5 use constant xy_is_visited => 1;
  1         2  
  1         74  
46 1         1504 use constant parameter_info_array =>
47             [ { name => 'turns',
48             share_key => 'turns_2',
49             display => 'Turns',
50             type => 'integer',
51             minimum => 0,
52             default => 2,
53             width => 2,
54             },
55 1     1   7 ];
  1         2  
56              
57             sub x_negative_at_n {
58 0     0 1 0 my ($self) = @_;
59 0         0 return $self->n_start + 4*($self->{'turns'}+1)**2;
60             }
61             sub y_negative_at_n {
62 0     0 1 0 my ($self) = @_;
63 0         0 return $self->n_start + 6*($self->{'turns'}+1)**2;
64             }
65              
66             # 17-- 18--19--20--21
67             # |
68             # 16 3t-2 -- 8 -- 2t
69             # | | |
70             # 15 4t-5 ---11 6
71             # | | |
72             # 14-- 13-----12 5
73             # |
74             # 1---- 2----- 3-- t
75             #
76             sub _UNDOCUMENTED__dxdy_list_at_n {
77 0     0   0 my ($self) = @_;
78 0         0 my $turns = $self->{'turns'};
79 0 0       0 return $self->n_start + ($turns == 0 ? 4 # turns=0
    0          
80             : $turns <= 2 ? 6 # turns=1,2
81             : 3*$turns - 4);
82             }
83              
84             sub turn_any_right {
85 0     0 1 0 my ($self) = @_;
86 0         0 return ($self->{'turns'} != 0); # SquareSpiral is left or straight only
87             }
88             sub _UNDOCUMENTED__turn_any_right_at_n {
89 0     0   0 my ($self) = @_;
90             # turns=1 2,4,7,11,22,29
91             return ($self->{'turns'} == 0 ? undef # SquareSpiral left or straight only
92 0 0       0 : $self->n_start + $self->{'midpoint'}-1);
93             }
94             sub _UNDOCUMENTED__turn_any_left_at_n {
95 0     0   0 my ($self) = @_;
96 0         0 my $turns = $self->{'turns'};
97             # turns=1 2,4,7,11,22,29
98 0 0       0 return $self->n_start + ($turns==0 ? 1
    0          
99             : $turns==1 ? 3
100             : $turns-1);
101             }
102              
103              
104             #------------------------------------------------------------------------------
105              
106             # turns=1
107             # 2---3
108             # | |
109             # 0---1 4
110             #
111             # turns=2 |
112             # 5---6---7 18 15--14
113             # | | | | |
114             # 4---3 8 17--16 13 x=1,y=1
115             # | | |
116             # 0---1---2 9 10--11--12
117             #
118             # turns=3
119             # 10--11--12--13
120             # | |
121             # 9 6---5 14 x=2,y=1
122             # | | | |
123             # 8---7 4 15
124             # | |
125             # 0---1---2---3 16
126             #
127             # turns=4
128             # 17--18--19--20--21 50 37--36--35--34
129             # | | | | | 3,3,2,1,1,1,2,3,4,down4
130             # 16 9---8---7 22 49 38 41--42 33
131             # | | | | | | | | |
132             # 15 10--11 6 23 48 39--40 43 32 x=3,y=2
133             # | | | | | | |
134             # 14--13--12 5 24 47--46--45--44 31
135             # | | |
136             # 0---1---2---3---4 25--26--27--28--29--30 5,4,3,2,1,1,1,2,3,up3
137             #
138             # turns=5
139             # 26--27--28--29--30--31
140             # | | 4,4,3,2,1,1,1,2,3,4,5,5
141             # 25 12--11--10---9 32
142             # | | | |
143             # 24 13 16--17 8 33 5,4,3,2,1,1,1,2,3,4,5,rem
144             # | | | | | | |
145             # 35 23 14--15 18 7 34
146             # | | | | | x=3,y=3
147             # 36 22--21--20--19 6 35
148             # | |
149             # 0---1---2---3---4---5 36-
150             #
151             # turns=6
152             # 37--38--39--40--41--42--43
153             # | |
154             # 36 15--14--13--12--11 44 x=3,y=3
155             # | | | |
156             # 35 16 23--24--25 10 45
157             # | | | | | |
158             # 34 17 22--21 26 9 46 6,5,4,3,2,1,1,1,2,3,4,5,rem
159             # | | | | | |
160             # 33 18--19--20 27 8 47
161             # | | | |
162             # 32--31--30--29--28 7 48
163             # | |
164             # 0---1---2---3---4---5---6 49-
165             #
166             # turns=7
167             # 50--51--52--53--54--55--56--57
168             # | |
169             # 49 18--17--16--15--14--13 58
170             # | | | |
171             # 48 19 32--33--34--35 12 59 x=4,y=3
172             # | | | | | |
173             # 47 20 31 28--27 36 11 60
174             # | | | | | | | |
175             # 46 21 30--29 26 37 10 61 6,5,4,3,2,1,1,1,2,3,4,5,rem
176             # | | | | | |
177             # 45 22--23--24--25 38 9 62
178             # | | | |
179             # 44--43--42--41--40--39 8 63
180             # | |
181             # 0---1---2---3---4---5---6---7 64
182             #
183             # turns=8 x=5,y=4
184              
185              
186             # centre
187             # 2 1 1
188             # 3 2 1
189              
190             # 4 3 2
191             # 5 3 3
192             # 6 3 3
193             # 7 4 3
194              
195             # 8 5 4
196             # 9 5 5
197             # 10 5 5
198             # 11 6 5
199              
200             # 12 7 6
201             # 13 7 7
202             # 14 7 7
203             # 15 8 7
204             #
205             # turns 2, 3, 4, 5
206             # midp 4 6, 10, 15, 21 N = (1/2 d^2 + 1/2 d)
207             #
208             # 63, 189, 387, 657
209             # 9*7 9*21, 9*43, 9*73
210             #
211             # 82 226 442
212             # 9*9+1 9*25+1 9*49+1
213              
214             sub new {
215 26     26 1 3360 my $self = shift->SUPER::new (@_);
216              
217 26         67 my $turns = $self->{'turns'};
218 26 100       111 if (! defined $turns) {
    50          
219 2         4 $turns = 2;
220             } elsif ($turns < 0) {
221             }
222 26         59 $self->{'turns'} = $turns;
223 26         61 my $t1 = $turns + 1;
224              
225 26 50       68 if (! defined $self->{'n_start'}) {
226 26         97 $self->{'n_start'} = $self->default_n_start;
227             }
228              
229 26         92 $self->{'centre_x'} = int($t1/2) + (($turns%4)==0);
230 26         73 $self->{'centre_y'} = int($turns/2) + (($turns%4)==1);
231              
232 26         78 $self->{'midpoint'} = $turns*$t1/2 + 1;
233 26         51 $self->{'side'} = $t1;
234 26         65 $self->{'squared'} = $t1*$t1;
235              
236             ### turns : $self->{'turns'}
237             ### midpoint: $self->{'midpoint'}
238             ### side : $self->{'side'}
239             ### squared : $self->{'squared'}
240              
241 26         57 return $self;
242             }
243              
244             sub n_to_xy {
245 75807     75807 1 324314 my ($self, $n) = @_;
246             #### GreekKeySpiral n_to_xy: $n
247              
248 75807         97610 $n = $n - $self->{'n_start'};
249             ### n zero based: $n
250 75807 50       124341 if ($n < 0) { return; }
  0         0  
251              
252 75807         97010 my $turns = $self->{'turns'};
253 75807         93931 my $squared = $self->{'squared'};
254 75807         92884 my $side = $turns + 1;
255              
256             ### sqrt of: ($n-1) / $squared
257              
258 75807         141081 my $d = _sqrtint($n / $squared);
259 75807         110237 $n -= $squared*$d*$d - 1;
260 75807         104796 my $dhalf = int($d/2);
261              
262             ### $d
263             ### $dhalf
264             ### n remainder: $n
265              
266 75807         94170 my ($x,$y);
267 75807         87522 my $square_rot = 0;
268 75807         85849 my $frac;
269 75807         89328 { my $int = int($n);
  75807         94496  
270 75807         91401 $frac = $n - int($n);
271 75807         93348 $n = $int;
272             }
273             ### $frac
274             ### $n
275              
276 75807 100       116847 if ($d % 2) {
277             ### odd d, right and top ...
278 35654 100       52246 if ($n >= $squared*($d+1)) {
279             ### top ...
280 15217         18605 $n -= $squared*2*$d;
281 15217         24069 (my $q, $n) = _divrem ($n, $squared);
282 15217         21867 $x = (-$dhalf-$q)*$side + 1;
283 15217         18566 $y = ($dhalf+1)*$side;
284 15217         18091 $square_rot = 2;
285             } else {
286             ### right ...
287 20437         37214 (my $q, $n) = _divrem ($n-$turns-1 + $squared, $squared);
288 20437         28917 $x = ($dhalf+1)*$side;
289 20437         25800 $y = ($q-$dhalf-1)*$side;
290 20437         25989 $square_rot = 1;
291             }
292             } else {
293             ### even d, left and bottom ...
294 40153 100 100     101486 if ($d == 0 || $n >= $squared*($d+1)) {
295             ### bottom ...
296 18725         24441 $n -= $squared*2*$d;
297 18725         31591 (my $q, $n) = _divrem ($n, $squared);
298 18725         28484 $x = ($dhalf+$q)*$side-1;
299 18725         23123 $y = -($dhalf)*$side;
300 18725         23711 $square_rot = 0;
301             } else {
302             ### left ...
303 21428         40947 (my $q, $n) = _divrem ($n-$turns-1 + $squared, $squared);
304 21428         29649 $x = -($dhalf)*$side;
305 21428         27074 $y = -($q-$dhalf-1)*$side;
306 21428         26292 $square_rot = 3;
307             }
308             }
309              
310             ### assert: ! ($n < 0)
311             ### assert: ! ($n >= $squared)
312              
313 75807         94929 my $rot = $turns;
314 75807         91225 my $kx = 0;
315 75807         86486 my $ky = 0;
316 75807         86760 my $before;
317             ### n-midpoint: $n - $self->{'midpoint'}
318              
319 75807 100       136644 if (($n -= $self->{'midpoint'}) >= 0) {
    100          
320             ### after middle ...
321             } elsif ($n += 1) {
322             ### before middle ...
323 33092         40062 $n = -$n;
324 33092 100       46064 if ($frac) {
325             ### fraction ...
326 3510         4590 $frac = 1-$frac;
327 3510         4889 $n -= 1;
328             } else {
329             ### integer ...
330 29582         34837 $n -= 0;
331             }
332 33092         39384 $rot += 2;
333 33092         40228 $before = 1;
334             } else {
335             ### centre segment ...
336 4200         5503 $rot += 1;
337 4200         5341 $before = 1;
338             }
339             ### key n: $n
340              
341             # d: [ 0, 1, 2 ]
342             # n: [ 0, 3, 10 ]
343             # d = -1/4 + sqrt(1/2 * $n + 1/16)
344             # = (-1 + sqrt(8*$n + 1)) / 4
345             # N = (2*$d + 1)*$d
346             # rel = (2*$d + 1)*$d + 2*$d+1
347             # = (2*$d + 3)*$d + 1
348             #
349 75807         133968 $d = int( (_sqrtint(8*$n+1) - 1)/4 );
350 75807         109716 $n -= (2*$d+3)*$d + 1;
351             ### $d
352             ### key signed rem: $n
353              
354 75807 100       112879 if ($n < 0) {
355             ### key vertical ...
356 40030         47039 $kx += $d;
357 40030         57857 $ky = -$frac-$n-$d - 1 + $ky;
358 40030 100       59109 if ($d % 2) {
359             ### key right ...
360 17692         21706 $rot += 2;
361 17692         21352 $kx += 1;
362             } else {
363             }
364             } else {
365             ### key horizontal ...
366 35777         48881 $kx = $frac+$n-$d + $kx;
367 35777         43198 $ky += $d + 1;
368 35777         42864 $rot += 2;
369 35777 100       53370 if ($d % 2) {
370             ### key bottom ...
371 18174         21824 $rot += 2;
372 18174         22238 $kx += -1;
373             } else {
374             }
375             }
376             ### kxy raw: "$kx, $ky"
377              
378 75807 100       121823 if ($rot & 2) {
379 35963         45437 $kx = -$kx;
380 35963         43776 $ky = -$ky;
381             }
382 75807 100       113774 if ($rot & 1) {
383 14114         25183 ($kx,$ky) = (-$ky,$kx);
384             }
385             ### kxy rotated: "$kx,$ky"
386              
387 75807 100       112263 if ($before) {
388 37292 100       62059 if (($turns % 4) == 0) {
389 30323         35078 $kx -= 1;
390             }
391 37292 100       55110 if (($turns % 4) == 1) {
392 3935         5182 $ky -= 1;
393             }
394 37292 100       55739 if (($turns % 4) == 2) {
395 1388         1856 $kx += 1;
396             }
397 37292 100       57026 if (($turns % 4) == 3) {
398 1646         2211 $ky += 1;
399             }
400             }
401              
402 75807         97288 $kx += $self->{'centre_x'};
403 75807         91757 $ky += $self->{'centre_y'};
404              
405 75807 100       114750 if ($square_rot & 2) {
406 36645         44491 $kx = $turns-$kx;
407 36645         43970 $ky = $turns-$ky;
408             }
409 75807 100       112328 if ($square_rot & 1) {
410 41865         68248 ($kx,$ky) = ($turns-$ky,$kx);
411             }
412              
413             # kx,ky first to inherit BigRat etc from $frac
414 75807         173174 return ($kx + $x,
415             $ky + $y);
416             }
417              
418              
419             # t+(t-1)+(t-2)+(t-3) = 4t-6
420              
421             # y=0 0
422             # y=2 0+1+2+3 total 6
423             # y=4 4+5+6+7 total 28
424             # (2 d^2 - d)
425             # N=4*t*y/2 - (2y-1)*y
426             # =(2t - 2y + 1)*y
427              
428             # x=1 0+1+2 total 3
429             # x=3 3+4+5+6 total 21
430             # x=5 7+8+9+10 total 55
431             # (2 d^2 + d)
432             # N = 4*t*(x-1)/2 + 3t-3 - (2x+1)*x
433             # = 2*t*(x-1) + 3t-3 - (2x+1)*x
434             # = 2tx-2t + 3t-3 - (2x+1)*x
435             # = (2t-2x-1)x - 2t + 3t-3
436             # = (2t-2x-1)x + t-3
437              
438             # y=0 squared-t-t total 0
439             # y=2 - (t-1)-(t-2)-(t-3)-(t-4) total 10
440             # y=4 - 5+6+7+8 total 36
441             # (2 d^2 + d)
442             # N = squared - 4*t*y/2 - 2t - (2y+1)*y +(x-y)
443             # = squared - (2t+2y+1)*y - 2t + x
444              
445             sub xy_to_n {
446 59697     59697 1 249457 my ($self, $x, $y) = @_;
447              
448 59697         97321 $x = round_nearest ($x);
449 59697         92051 $y = round_nearest ($y);
450             ### xy_to_n: "x=$x, y=$y"
451              
452 59697         85048 my $turns = $self->{'turns'};
453 59697         71723 my $side = $turns + 1;
454 59697         70707 my $squared = $self->{'squared'};
455              
456 59697         106925 my $xs = floor($x/$side);
457 59697         107922 my $ys = floor($y/$side);
458 59697         78264 $x %= $side;
459 59697         70807 $y %= $side;
460 59697         66528 my $n;
461 59697 100       86924 if ($xs > -$ys) {
462             ### top or right
463 28382 100       39275 if ($xs >= $ys) {
464             ### right going upwards
465 16021         21515 $n = $squared*((4*$xs - 3)*$xs + $ys);
466 16021         23539 ($x,$y) = ($y,$turns-$x); # rotate -90
467 16021 100       21948 if ($x == 0) {
468 1777         2005 $x = $turns;
469 1777         2275 $n -= $side*$turns; # +$side modulo
470             } else {
471 14244         16462 $x -= 1;
472 14244         17596 $n += $side;
473             }
474             } else {
475             ### top going leftwards
476 12361         16603 $n = $squared*((4*$ys - 1)*$ys - $xs);
477 12361         13604 $x = $turns-$x; # rotate 180
478 12361         14516 $y = $turns-$y;
479             }
480             } else {
481             ### bottom or left
482 31315 100 66     73541 if ($xs > $ys || ($xs == 0 && $ys == 0)) {
      100        
483             ### bottom going rightwards: "$xs,$ys"
484 13874         19551 $n = $squared*((4*$ys - 3)*$ys + $xs);
485             } else {
486             ### left going downwards
487 17441         24572 $n = $squared*((4*$xs - 1)*$xs - $ys);
488 17441         27171 ($x,$y) = ($turns-$y,$x); # rotate +90
489 17441 100       25800 if ($x == 0) {
490 1159         1431 $x = $turns;
491 1159         1454 $n -= $side*$turns; # +$side modulo
492             } else {
493 16282         18941 $x -= 1;
494 16282         20201 $n += $side;
495             }
496             }
497             }
498              
499 59697 100       86504 if ($x + $y >= $turns) {
500             ### key top or right ...
501 32516 100       45265 if ($x > $y) {
502             ### key right ...
503 15113         17311 $x = $turns-$x;
504 15113 100       21813 if ($x % 2) {
505             ### forward ...
506 6321         9412 $n += (2*$turns-2*$x+2)*$x + $y - $turns;
507             } else {
508             ### backward ...
509 8792         12878 $n += $squared - (2*$turns-2*$x+2)*$x - $y;
510             }
511             } else {
512             ### key top ...
513 17403         20434 $y = $turns-$y;
514 17403 100       24169 if ($y % 2) {
515             ### backward ...
516 7192         10763 $n += (2*$turns-2*$y)*$y + $turns-$x;
517             } else {
518             ### forward ...
519 10211         15503 $n += $squared - (2*$turns - 2*$y)*$y - 2*$turns + $x;
520             }
521             }
522             } else {
523             ### key bottom or left ...
524 27181 100       37120 if ($x >= $y) {
525             ### key bottom ...
526 14516 100       20593 if ($y % 2) {
527             ### backward ...
528 6303         9142 $n += $squared - (2*$turns - 2*$y)*$y - $turns - $x - 1;
529             } else {
530             ### forward ...
531 8213         11736 $n += (2*$turns-2*$y)*$y + $x + 1;
532             }
533             } else {
534             ### key left ...
535 12665 100       18198 if ($x % 2) {
536             ### forward ...
537 5480         8485 $n += (2*$turns-2*$x-2)*$x + 2*$turns - $y;
538             } else {
539             ### backward ...
540 7185         11585 $n += $squared - (2*$turns - 2*$x - 2)*$x - 3*$turns + $y;
541             }
542             }
543             }
544              
545 59697         106735 return $n + $self->{'n_start'}-1;
546             }
547              
548 1     1   541 use Math::PlanePath::SquareArms;
  1         3  
  1         191  
549             *_rect_square_range = \&Math::PlanePath::SquareArms::_rect_square_range;
550              
551             # not exact
552             sub rect_to_n_range {
553 1400     1400 1 3436 my ($self, $x1,$y1, $x2,$y2) = @_;
554             ### rect_to_n_range(): "$x1,$y1 $x2,$y2"
555              
556 1400         2442 $x1 = round_nearest ($x1);
557 1400         2147 $y1 = round_nearest ($y1);
558 1400         2139 $x2 = round_nearest ($x2);
559 1400         2023 $y2 = round_nearest ($y2);
560              
561             # floor divisions to square blocks
562             {
563 1400         1682 my $side = $self->{'turns'} + 1;
  1400         1858  
564 1400         3231 _divrem_mutate($x1,$side);
565 1400         2518 _divrem_mutate($y1,$side);
566 1400         2492 _divrem_mutate($x2,$side);
567 1400         1956 _divrem_mutate($y2,$side);
568             }
569 1400         2680 my ($dlo, $dhi) = _rect_square_range ($x1, $y1,
570             $x2, $y2);
571 1400         2110 my $squared = $self->{'squared'};
572              
573             ### d range sides: "$dlo, $dhi"
574             ### right start: ((4*$squared*$dlo - 4*$squared)*$dlo + 10)
575              
576             return (($dlo == 0 ? 0 # special case Nlo=1 for innermost square
577             # Nlo at right vertical start
578             : ((4*$squared*$dlo - 4*$squared)*$dlo + $squared))
579             + $self->{'n_start'},
580              
581             # Nhi at bottom horizontal end
582             (4*$squared*$dhi + 4*$squared)*$dhi
583             + $squared
584 1400 100       3678 + $self->{'n_start'}-1);
585             }
586              
587             1;
588             __END__