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, 2020 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   1441 use 5.004;
  1         3  
25 1     1   5 use strict;
  1         2  
  1         24  
26              
27 1     1   5 use vars '$VERSION', '@ISA';
  1         2  
  1         62  
28             $VERSION = 129;
29 1     1   837 use Math::PlanePath;
  1         3  
  1         30  
30 1     1   594 use Math::PlanePath::Base::NSEW;
  1         2  
  1         41  
31             @ISA = ('Math::PlanePath::Base::NSEW',
32             'Math::PlanePath');
33              
34             use Math::PlanePath::Base::Generic
35 1         74 'round_nearest',
36 1     1   6 'floor';
  1         4  
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         59  
46 1         1670 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   5 ];
  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 3855 my $self = shift->SUPER::new (@_);
216              
217 26         66 my $turns = $self->{'turns'};
218 26 100       111 if (! defined $turns) {
    50          
219 2         4 $turns = 2;
220             } elsif ($turns < 0) {
221             }
222 26         87 $self->{'turns'} = $turns;
223 26         43 my $t1 = $turns + 1;
224              
225 26 50       69 if (! defined $self->{'n_start'}) {
226 26         152 $self->{'n_start'} = $self->default_n_start;
227             }
228              
229 26         95 $self->{'centre_x'} = int($t1/2) + (($turns%4)==0);
230 26         67 $self->{'centre_y'} = int($turns/2) + (($turns%4)==1);
231              
232 26         70 $self->{'midpoint'} = $turns*$t1/2 + 1;
233 26         52 $self->{'side'} = $t1;
234 26         54 $self->{'squared'} = $t1*$t1;
235              
236             ### turns : $self->{'turns'}
237             ### midpoint: $self->{'midpoint'}
238             ### side : $self->{'side'}
239             ### squared : $self->{'squared'}
240              
241 26         56 return $self;
242             }
243              
244             sub n_to_xy {
245 75807     75807 1 355103 my ($self, $n) = @_;
246             #### GreekKeySpiral n_to_xy: $n
247              
248 75807         109542 $n = $n - $self->{'n_start'};
249             ### n zero based: $n
250 75807 50       134375 if ($n < 0) { return; }
  0         0  
251              
252 75807         107982 my $turns = $self->{'turns'};
253 75807         101754 my $squared = $self->{'squared'};
254 75807         104198 my $side = $turns + 1;
255              
256             ### sqrt of: ($n-1) / $squared
257              
258 75807         157075 my $d = _sqrtint($n / $squared);
259 75807         122710 $n -= $squared*$d*$d - 1;
260 75807         114889 my $dhalf = int($d/2);
261              
262             ### $d
263             ### $dhalf
264             ### n remainder: $n
265              
266 75807         108002 my ($x,$y);
267 75807         100918 my $square_rot = 0;
268 75807         97686 my $frac;
269 75807         99909 { my $int = int($n);
  75807         101977  
270 75807         102882 $frac = $n - int($n);
271 75807         102902 $n = $int;
272             }
273             ### $frac
274             ### $n
275              
276 75807 100       128983 if ($d % 2) {
277             ### odd d, right and top ...
278 35654 100       59605 if ($n >= $squared*($d+1)) {
279             ### top ...
280 15217         21339 $n -= $squared*2*$d;
281 15217         28726 (my $q, $n) = _divrem ($n, $squared);
282 15217         25432 $x = (-$dhalf-$q)*$side + 1;
283 15217         20973 $y = ($dhalf+1)*$side;
284 15217         23903 $square_rot = 2;
285             } else {
286             ### right ...
287 20437         41772 (my $q, $n) = _divrem ($n-$turns-1 + $squared, $squared);
288 20437         31719 $x = ($dhalf+1)*$side;
289 20437         28807 $y = ($q-$dhalf-1)*$side;
290 20437         28896 $square_rot = 1;
291             }
292             } else {
293             ### even d, left and bottom ...
294 40153 100 100     116489 if ($d == 0 || $n >= $squared*($d+1)) {
295             ### bottom ...
296 18725         27114 $n -= $squared*2*$d;
297 18725         35012 (my $q, $n) = _divrem ($n, $squared);
298 18725         30270 $x = ($dhalf+$q)*$side-1;
299 18725         25729 $y = -($dhalf)*$side;
300 18725         26877 $square_rot = 0;
301             } else {
302             ### left ...
303 21428         47604 (my $q, $n) = _divrem ($n-$turns-1 + $squared, $squared);
304 21428         35039 $x = -($dhalf)*$side;
305 21428         31931 $y = -($q-$dhalf-1)*$side;
306 21428         29881 $square_rot = 3;
307             }
308             }
309              
310             ### assert: ! ($n < 0)
311             ### assert: ! ($n >= $squared)
312              
313 75807         112965 my $rot = $turns;
314 75807         102405 my $kx = 0;
315 75807         96068 my $ky = 0;
316 75807         97327 my $before;
317             ### n-midpoint: $n - $self->{'midpoint'}
318              
319 75807 100       152613 if (($n -= $self->{'midpoint'}) >= 0) {
    100          
320             ### after middle ...
321             } elsif ($n += 1) {
322             ### before middle ...
323 33092         46793 $n = -$n;
324 33092 100       49874 if ($frac) {
325             ### fraction ...
326 3510         4843 $frac = 1-$frac;
327 3510         4602 $n -= 1;
328             } else {
329             ### integer ...
330 29582         40594 $n -= 0;
331             }
332 33092         42445 $rot += 2;
333 33092         43407 $before = 1;
334             } else {
335             ### centre segment ...
336 4200         5476 $rot += 1;
337 4200         5447 $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         157581 $d = int( (_sqrtint(8*$n+1) - 1)/4 );
350 75807         133062 $n -= (2*$d+3)*$d + 1;
351             ### $d
352             ### key signed rem: $n
353              
354 75807 100       126917 if ($n < 0) {
355             ### key vertical ...
356 40030         52951 $kx += $d;
357 40030         66017 $ky = -$frac-$n-$d - 1 + $ky;
358 40030 100       71230 if ($d % 2) {
359             ### key right ...
360 17692         25067 $rot += 2;
361 17692         24327 $kx += 1;
362             } else {
363             }
364             } else {
365             ### key horizontal ...
366 35777         54232 $kx = $frac+$n-$d + $kx;
367 35777         50124 $ky += $d + 1;
368 35777         47990 $rot += 2;
369 35777 100       59487 if ($d % 2) {
370             ### key bottom ...
371 18174         23551 $rot += 2;
372 18174         25371 $kx += -1;
373             } else {
374             }
375             }
376             ### kxy raw: "$kx, $ky"
377              
378 75807 100       135205 if ($rot & 2) {
379 35963         51848 $kx = -$kx;
380 35963         47944 $ky = -$ky;
381             }
382 75807 100       127147 if ($rot & 1) {
383 14114         25431 ($kx,$ky) = (-$ky,$kx);
384             }
385             ### kxy rotated: "$kx,$ky"
386              
387 75807 100       130455 if ($before) {
388 37292 100       70054 if (($turns % 4) == 0) {
389 30323         40478 $kx -= 1;
390             }
391 37292 100       62460 if (($turns % 4) == 1) {
392 3935         5132 $ky -= 1;
393             }
394 37292 100       61142 if (($turns % 4) == 2) {
395 1388         1875 $kx += 1;
396             }
397 37292 100       63388 if (($turns % 4) == 3) {
398 1646         2275 $ky += 1;
399             }
400             }
401              
402 75807         109240 $kx += $self->{'centre_x'};
403 75807         104365 $ky += $self->{'centre_y'};
404              
405 75807 100       127316 if ($square_rot & 2) {
406 36645         48630 $kx = $turns-$kx;
407 36645         49743 $ky = $turns-$ky;
408             }
409 75807 100       123143 if ($square_rot & 1) {
410 41865         78627 ($kx,$ky) = ($turns-$ky,$kx);
411             }
412              
413             # kx,ky first to inherit BigRat etc from $frac
414 75807         195345 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 297217 my ($self, $x, $y) = @_;
447              
448 59697         112596 $x = round_nearest ($x);
449 59697         109333 $y = round_nearest ($y);
450             ### xy_to_n: "x=$x, y=$y"
451              
452 59697         92631 my $turns = $self->{'turns'};
453 59697         82312 my $side = $turns + 1;
454 59697         81947 my $squared = $self->{'squared'};
455              
456 59697         119549 my $xs = floor($x/$side);
457 59697         127280 my $ys = floor($y/$side);
458 59697         90110 $x %= $side;
459 59697         80827 $y %= $side;
460 59697         78700 my $n;
461 59697 100       98534 if ($xs > -$ys) {
462             ### top or right
463 28382 100       44763 if ($xs >= $ys) {
464             ### right going upwards
465 16021         25655 $n = $squared*((4*$xs - 3)*$xs + $ys);
466 16021         27495 ($x,$y) = ($y,$turns-$x); # rotate -90
467 16021 100       26592 if ($x == 0) {
468 1777         2535 $x = $turns;
469 1777         2570 $n -= $side*$turns; # +$side modulo
470             } else {
471 14244         19030 $x -= 1;
472 14244         20238 $n += $side;
473             }
474             } else {
475             ### top going leftwards
476 12361         19429 $n = $squared*((4*$ys - 1)*$ys - $xs);
477 12361         17055 $x = $turns-$x; # rotate 180
478 12361         17014 $y = $turns-$y;
479             }
480             } else {
481             ### bottom or left
482 31315 100 66     86358 if ($xs > $ys || ($xs == 0 && $ys == 0)) {
      100        
483             ### bottom going rightwards: "$xs,$ys"
484 13874         23329 $n = $squared*((4*$ys - 3)*$ys + $xs);
485             } else {
486             ### left going downwards
487 17441         29199 $n = $squared*((4*$xs - 1)*$xs - $ys);
488 17441         31259 ($x,$y) = ($turns-$y,$x); # rotate +90
489 17441 100       30773 if ($x == 0) {
490 1159         1542 $x = $turns;
491 1159         1728 $n -= $side*$turns; # +$side modulo
492             } else {
493 16282         22589 $x -= 1;
494 16282         23745 $n += $side;
495             }
496             }
497             }
498              
499 59697 100       97300 if ($x + $y >= $turns) {
500             ### key top or right ...
501 32516 100       52960 if ($x > $y) {
502             ### key right ...
503 15113         20059 $x = $turns-$x;
504 15113 100       24768 if ($x % 2) {
505             ### forward ...
506 6321         10780 $n += (2*$turns-2*$x+2)*$x + $y - $turns;
507             } else {
508             ### backward ...
509 8792         14630 $n += $squared - (2*$turns-2*$x+2)*$x - $y;
510             }
511             } else {
512             ### key top ...
513 17403         23359 $y = $turns-$y;
514 17403 100       27912 if ($y % 2) {
515             ### backward ...
516 7192         12058 $n += (2*$turns-2*$y)*$y + $turns-$x;
517             } else {
518             ### forward ...
519 10211         17267 $n += $squared - (2*$turns - 2*$y)*$y - 2*$turns + $x;
520             }
521             }
522             } else {
523             ### key bottom or left ...
524 27181 100       43628 if ($x >= $y) {
525             ### key bottom ...
526 14516 100       24084 if ($y % 2) {
527             ### backward ...
528 6303         10896 $n += $squared - (2*$turns - 2*$y)*$y - $turns - $x - 1;
529             } else {
530             ### forward ...
531 8213         13811 $n += (2*$turns-2*$y)*$y + $x + 1;
532             }
533             } else {
534             ### key left ...
535 12665 100       21522 if ($x % 2) {
536             ### forward ...
537 5480         9405 $n += (2*$turns-2*$x-2)*$x + 2*$turns - $y;
538             } else {
539             ### backward ...
540 7185         12396 $n += $squared - (2*$turns - 2*$x - 2)*$x - 3*$turns + $y;
541             }
542             }
543             }
544              
545 59697         122971 return $n + $self->{'n_start'}-1;
546             }
547              
548 1     1   616 use Math::PlanePath::SquareArms;
  1         3  
  1         199  
549             *_rect_square_range = \&Math::PlanePath::SquareArms::_rect_square_range;
550              
551             # not exact
552             sub rect_to_n_range {
553 1400     1400 1 4155 my ($self, $x1,$y1, $x2,$y2) = @_;
554             ### rect_to_n_range(): "$x1,$y1 $x2,$y2"
555              
556 1400         3271 $x1 = round_nearest ($x1);
557 1400         2740 $y1 = round_nearest ($y1);
558 1400         2788 $x2 = round_nearest ($x2);
559 1400         2423 $y2 = round_nearest ($y2);
560              
561             # floor divisions to square blocks
562             {
563 1400         2162 my $side = $self->{'turns'} + 1;
  1400         2280  
564 1400         4162 _divrem_mutate($x1,$side);
565 1400         3008 _divrem_mutate($y1,$side);
566 1400         3008 _divrem_mutate($x2,$side);
567 1400         2552 _divrem_mutate($y2,$side);
568             }
569 1400         3218 my ($dlo, $dhi) = _rect_square_range ($x1, $y1,
570             $x2, $y2);
571 1400         2408 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       4563 + $self->{'n_start'}-1);
585             }
586              
587             1;
588             __END__