File Coverage

blib/lib/Math/PlanePath/FlowsnakeCentres.pm
Criterion Covered Total %
statement 240 274 87.5
branch 78 96 81.2
condition 25 26 96.1
subroutine 27 32 84.3
pod 9 9 100.0
total 379 437 86.7


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 it
6             # under the terms of the GNU General Public License as published by the Free
7             # 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=FlowsnakeCentres --lines --scale=10
20             #
21             # http://80386.nl/projects/flowsnake/
22             #
23              
24              
25             package Math::PlanePath::FlowsnakeCentres;
26 2     2   1330 use 5.004;
  2         9  
27 2     2   10 use strict;
  2         4  
  2         45  
28 2     2   946 use POSIX 'ceil';
  2         14038  
  2         11  
29 2     2   2815 use List::Util 'min'; # 'max'
  2         4  
  2         248  
30             *max = \&Math::PlanePath::_max;
31              
32 2     2   14 use vars '$VERSION', '@ISA';
  2         5  
  2         144  
33             $VERSION = 128;
34 2     2   1443 use Math::PlanePath;
  2         6  
  2         104  
35             @ISA = ('Math::PlanePath');
36             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
37              
38             use Math::PlanePath::Base::Generic
39 2         100 'is_infinite',
40             'round_nearest',
41 2     2   13 'xy_is_even';
  2         21  
42             use Math::PlanePath::Base::Digits
43 2         120 'digit_split_lowtohigh',
44 2     2   990 'round_up_pow';
  2         5  
45              
46 2     2   1028 use Math::PlanePath::SacksSpiral;
  2         7  
  2         114  
47             *_rect_to_radius_range = \&Math::PlanePath::SacksSpiral::_rect_to_radius_range;
48              
49             # uncomment this to run the ### lines
50             #use Devel::Comments;
51              
52              
53 2     2   14 use constant n_start => 0;
  2         5  
  2         153  
54              
55 2         288 use constant parameter_info_array => [ { name => 'arms',
56             share_key => 'arms_3',
57             display => 'Arms',
58             type => 'integer',
59             minimum => 1,
60             maximum => 3,
61             default => 1,
62             width => 1,
63             description => 'Arms',
64 2     2   12 } ];
  2         5  
65              
66             {
67             my @x_negative_at_n = (undef, 3, 1, 1);
68             sub x_negative_at_n {
69 0     0 1 0 my ($self) = @_;
70 0         0 return $x_negative_at_n[$self->{'arms'}];
71             }
72             }
73             {
74             my @y_negative_at_n = (undef, 8597, 7, 2);
75             sub y_negative_at_n {
76 0     0 1 0 my ($self) = @_;
77 0         0 return $y_negative_at_n[$self->{'arms'}];
78             }
79             }
80              
81 2     2   15 use constant dx_minimum => -2;
  2         4  
  2         135  
82 2     2   14 use constant dx_maximum => 2;
  2         4  
  2         95  
83 2     2   13 use constant dy_minimum => -1;
  2         4  
  2         121  
84 2     2   15 use constant dy_maximum => 1;
  2         3  
  2         273  
85             *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six;
86             {
87             my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 10, 6, 8);
88             sub _UNDOCUMENTED__dxdy_list_at_n {
89 0     0   0 my ($self) = @_;
90 0         0 return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}];
91             }
92             }
93 2     2   15 use constant absdx_minimum => 1;
  2         4  
  2         129  
94 2     2   13 use constant dsumxy_minimum => -2; # diagonals
  2         4  
  2         130  
95 2     2   13 use constant dsumxy_maximum => 2;
  2         3  
  2         112  
96 2     2   12 use constant ddiffxy_minimum => -2;
  2         4  
  2         108  
97 2     2   13 use constant ddiffxy_maximum => 2;
  2         11  
  2         104  
98 2     2   13 use constant dir_maximum_dxdy => (1,-1); # South-East
  2         4  
  2         3876  
99              
100              
101              
102             #------------------------------------------------------------------------------
103             # *
104             # / \
105             # / \
106             # *-----*
107             #
108             # (b/2)^2 + h^2 = s
109             # (1/2)^2 + h^2 = 1
110             # h^2 = 1 - 1/4
111             # h = sqrt(3)/2 = 0.866
112             #
113              
114             sub new {
115 31     31 1 5294 my $self = shift->SUPER::new(@_);
116 31   100     201 $self->{'arms'} = max(1, min(3, $self->{'arms'} || 1));
117 31         70 return $self;
118             }
119              
120              
121             # # next_state length 84
122             # my @next_state = (0, 35,49,14, 0,70, 7, 0,21, 7,21,42,28, 7, # 0,7
123             # 14,49,63,28,14, 0,21, 14,35,21,35,56,42,21, # 14,21
124             # 28,63,77,42,28,14,35, 28,49,35,49,70,56,35, # 28,35
125             # 42,77, 7,56,42,28,49, 42,63,49,63, 0,70,49, # 42,49
126             # 56, 7,21,70,56,42,63, 56,77,63,77,14, 0,63, # 56,63
127             # 70,21,35, 0,70,56,77, 70, 7,77, 7,28,14,77); # 70,77
128             # my @digit_to_i = (0, 1, 0,-1,-1, 0, 1, 0, 1, 2, 3, 3, 2, 1, # 0,7
129             # 0, 0,-1,-1,-2,-2,-1, 0, 0, 1, 1, 0, 0,-1, # 14,21
130             # 0, -1,-1, 0,-1,-2,-2, 0,-1,-1,-2,-3,-2,-2, # 28,35
131             # 0, -1, 0, 1, 1, 0,-1, 0,-1,-2,-3,-3,-2,-1, # 42,49
132             # 0, 0, 1, 1, 2, 2, 1, 0, 0,-1,-1, 0, 0, 1, # 56,63
133             # 0, 1, 1, 0, 1, 2, 2, 0, 1, 1, 2, 3, 2,2); # 70,77
134             # my @digit_to_j = (0, 0, 1, 1, 2, 2, 1, 0, 0,-1,-1, 0, 0, 1, # 0,7
135             # 0, 1, 1, 0, 1, 2, 2, 0, 1, 1, 2, 3, 2, 2, # 14,21
136             # 0, 1, 0,-1,-1, 0, 1, 0, 1, 2, 3, 3, 2, 1, # 28,35
137             # 0, 0,-1,-1,-2,-2,-1, 0, 0, 1, 1, 0, 0,-1, # 42,49
138             # 0, -1,-1, 0,-1,-2,-2, 0,-1,-1,-2,-3,-2,-2, # 56,63
139             # 0, -1, 0, 1, 1, 0,-1, 0,-1,-2,-3,-3,-2,-1); # 70,77
140             # my @state_to_di = ( 1, 1, 0, 0,-1,-1, -1,-1, 0, 0, 1,1);
141             # my @state_to_dj = ( 0, 0, 1, 1, 1, 1, 0, 0,-1,-1,-1,-1);
142             #
143             #
144             # sub n_to_xy {
145             # my ($self, $n) = @_;
146             # ### Flowsnake n_to_xy(): $n
147             #
148             # if ($n < 0) { return; }
149             # if (is_infinite($n)) { return ($n,$n); }
150             #
151             # my $int = int($n);
152             # $n -= $int; # fraction part
153             # ### $int
154             # ### frac: $n
155             #
156             # my $state;
157             # {
158             # my $arm = _divrem_mutate ($int, $self->{'arms'});
159             # $state = 28 * $arm; # initial rotation
160             #
161             # # adjust so that for arms=2 point N=1 has $int==1
162             # # or for arms=3 then points N=1 and N=2 have $int==1
163             # if ($arm) { $int += 1; }
164             # }
165             # ### initial state: $state
166             #
167             # my $i = my $j = $int*0; # bignum zero
168             #
169             # foreach my $digit (reverse digit_split_lowtohigh($int,7)) { # high to low
170             # ### at: "state=$state digit=$digit i=$i,j=$j di=".$digit_to_i[$state+$digit]." dj=".$digit_to_j[$state+$digit]
171             #
172             # # i,j * (2+w), being 2*(i,j)+rot60(i,j)
173             # # then add low digit position
174             # #
175             # $state += $digit;
176             # ($i, $j) = (2*$i - $j + $digit_to_i[$state],
177             # 3*$j + $i + $digit_to_j[$state]);
178             # $state = $next_state[$state];
179             # }
180             # ### integer: "i=$i, j=$j"
181             #
182             # # fraction in final $state direction
183             # if ($n) {
184             # ### apply: "frac=$n state=$state"
185             # $state /= 7;
186             # $i = $n * $state_to_di[$state] + $i;
187             # $j = $n * $state_to_dj[$state] + $j;
188             # }
189             #
190             # ### ret: "$i, $j x=".(2*$i+$j)." y=$j"
191             # return (2*$i+$j,
192             # $j);
193             #
194             # }
195              
196             # 4-->5
197             # ^ ^
198             # / \
199             # 3--- 2 6--
200             # \
201             # v
202             # 0-->1
203             #
204              
205             my @digit_reverse = (0,1,1,0,0,0,1); # 1,2,6
206              
207             sub n_to_xy {
208 11805     11805 1 38065 my ($self, $n) = @_;
209             ### FlowsnakeCentres n_to_xy(): $n
210              
211 11805 50       21017 if ($n < 0) { return; }
  0         0  
212 11805 50       24068 if (is_infinite($n)) { return ($n,$n); }
  0         0  
213              
214             # ENHANCE-ME: work $frac into initial $x,$y somehow
215             # my $frac;
216             # {
217             # my $int = int($n);
218             # $frac = $n - $int; # inherit possible BigFloat/BigRat
219             # $n = $int; # BigInt instead of BigFloat
220             # }
221             {
222 11805         20134 my $int = int($n);
  11805         16663  
223             ### $int
224             ### $n
225 11805 100       20378 if ($n != $int) {
226 63         133 my ($x1,$y1) = $self->n_to_xy($int);
227 63         170 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
228 63         113 my $frac = $n - $int; # inherit possible BigFloat
229 63         87 my $dx = $x2-$x1;
230 63         95 my $dy = $y2-$y1;
231 63         216 return ($frac*$dx + $x1, $frac*$dy + $y1);
232             }
233 11742         17097 $n = $int; # BigFloat int() gives BigInt, use that
234             }
235              
236             # arm as initial rotation
237 11742         23952 my $rot = _divrem_mutate ($n, $self->{'arms'});
238              
239 11742         23231 my @digits = digit_split_lowtohigh($n,7);
240             ### @digits
241              
242 11742         17791 my $x = 0;
243 11742         15803 my $y = 0;
244             {
245             # if (! @n || $digits[0] == 0) {
246             # $x = 2*$frac;
247             # } elsif ($digits[0] == 1) {
248             # $x = $frac;
249             # $y = -$frac;
250             # } elsif ($digits[0] == 2) {
251             # $x = -2*$frac;
252             # } elsif ($digits[0] == 3) {
253             # $x = $frac;
254             # $y = -$frac;
255             # } elsif ($digits[0] == 4) {
256             # $x = 2*$frac;
257             # } elsif ($digits[0] == 5) {
258             # $x = $frac;
259             # $y = -$frac;
260             # } elsif ($digits[0] == 6) {
261             # $x = -$frac;
262             # $y = -$frac;
263             # }
264              
265 11742         16111 my $rev = 0;
  11742         15870  
266 11742         18561 foreach my $digit (reverse @digits) { # high to low
267             ### $digit
268 59771 100       96040 if ($rev) {
269             ### reverse: "$digit to ".(6 - $digit)
270 26367         34447 $digit = 6 - $digit; # mutate the array
271             }
272 59771         89904 $rev ^= $digit_reverse[$digit];
273             ### now rev: $rev
274             }
275             ### reversed n: @n
276             }
277              
278 11742         17582 my ($ox,$oy,$sx,$sy);
279 11742 50       19653 if ($rot == 0) {
    0          
280 11742         15980 $ox = 0;
281 11742         15208 $oy = 0;
282 11742         15778 $sx = 2;
283 11742         16112 $sy = 0;
284             } elsif ($rot == 1) {
285 0         0 $ox = -1; # at +120
286 0         0 $oy = 1;
287 0         0 $sx = -1; # rot to +120
288 0         0 $sy = 1;
289             } else {
290 0         0 $ox = -2; # at 180
291 0         0 $oy = 0;
292 0         0 $sx = -1; # rot to +240
293 0         0 $sy = -1;
294             }
295              
296 11742         22160 while (@digits) {
297 59771         86347 my $digit = shift @digits; # low to high
298             ### digit: "$digit $x,$y side $sx,$sy origin $ox,$oy"
299              
300 59771 100       128742 if ($digit == 0) {
    100          
    100          
    100          
    100          
    100          
    50          
301 25240         44517 $x += (3*$sy - $sx)/2; # at -120
302 25240         38698 $y += ($sx + $sy)/-2;
303              
304             } elsif ($digit == 1) {
305 7309         13640 ($x,$y) = ((3*$y-$x)/2, # rotate -120
306             ($x+$y)/-2);
307 7309         12466 $x += ($sx + 3*$sy)/2; # at -60
308 7309         10860 $y += ($sy - $sx)/2;
309              
310             } elsif ($digit == 2) {
311             # centre
312              
313             } elsif ($digit == 3) {
314 4112         7965 ($x,$y) = (($x+3*$y)/-2, # rotate +120
315             ($x-$y)/2);
316 4112         5755 $x -= $sx; # at -180
317 4112         5424 $y -= $sy;
318              
319             } elsif ($digit == 4) {
320 4240         7638 $x += ($sx + 3*$sy)/-2; # at +120
321 4240         6688 $y += ($sx - $sy)/2;
322              
323             } elsif ($digit == 5) {
324 3689         6844 $x += ($sx - 3*$sy)/2; # at +60
325 3689         5721 $y += ($sx + $sy)/2;
326              
327             } elsif ($digit == 6) {
328 9312         18416 ($x,$y) = (($x+3*$y)/-2, # rotate +120
329             ($x-$y)/2);
330 9312         12993 $x += $sx; # at X axis
331 9312         12782 $y += $sy;
332             }
333              
334 59771         77965 $ox += $sx;
335 59771         74600 $oy += $sy;
336              
337             # 2*(sx,sy) + rot+60(sx,sy)
338 59771         138789 ($sx,$sy) = ((5*$sx - 3*$sy) / 2,
339             ($sx + 5*$sy) / 2);
340             }
341              
342              
343             ### digits to: "$x,$y"
344             ### origin sum: "$ox,$oy"
345             ### origin rotated: (($ox-3*$oy)/2).','.(($ox+$oy)/2)
346 11742         21203 $x += ($ox-3*$oy)/2; # rotate +60
347 11742         18242 $y += ($ox+$oy)/2;
348              
349             ### final: "$x,$y"
350 11742         26059 return ($x,$y);
351             }
352              
353             # all even points when arms==3
354             sub xy_is_visited {
355 0     0 1 0 my ($self, $x, $y) = @_;
356 0 0       0 if ($self->{'arms'} == 3) {
357 0         0 return xy_is_even($self,$x,$y);
358             } else {
359 0         0 return defined($self->xy_to_n($x,$y));
360             }
361             }
362              
363             # 4-->5
364             # ^ ^ forw
365             # / \
366             # 3--- 2 6---
367             # \
368             # v
369             # 0-->1
370             #
371             # 5 3
372             # \ rev
373             # / \ / v
374             # --6 4 2
375             # /
376             # v
377             # 0-->1
378             #
379              
380             my @modulus_to_digit
381             = (0,3,1,2,4,6,5, 0,42,14,28, 0,56, 0, # 0 right forw 0
382             0,5,1,4,6,2,3, 0,42,14,70,14,14,28, # 14 +120 rev 1
383             6,3,5,4,2,0,1, 28,56,70, 0,28,42,28, # 28 left rev 2
384             4,5,3,2,6,0,1, 42,42,70,56,14,42,28, # 42 +60 forw 3
385             2,1,3,4,0,6,5, 56,56,14,42,70,56, 0, # 56 -60 rev 6
386             6,1,5,2,0,4,3, 28,56,70,14,70,70, 0, # 70 forw
387             );
388             sub xy_to_n {
389 168     168 1 1317 my ($self, $x, $y) = @_;
390             ### FlowsnakeCentres xy_to_n(): "$x, $y"
391              
392 168         404 $x = round_nearest($x);
393 168         347 $y = round_nearest($y);
394 168 50       459 if (($x ^ $y) & 1) {
395             ### odd x,y ...
396 0         0 return undef;
397             }
398              
399 168         614 my $level_limit = log($x*$x + 3*$y*$y + 1) * 0.835 * 2;
400 168 50       399 if (is_infinite($level_limit)) { return $level_limit; }
  0         0  
401              
402 168         473 my @digits;
403             my $arm;
404 168         0 my $state;
405 168         266 for (;;) {
406 727 50       1519 if ($level_limit-- < 0) {
407             ### oops, level limit ...
408 0         0 return undef;
409             }
410 727 100 100     1744 if ($x == 0 && $y == 0) {
411             ### found first arm 0,0 ...
412 164         284 $arm = 0;
413 164         239 $state = 0;
414 164         262 last;
415             }
416 563 100 100     1130 if ($x == -2 && $y == 0) {
417             ### found second arm -2,0 ...
418 2         5 $arm = 1;
419 2         4 $state = 42;
420 2         4 last;
421             }
422 561 100 100     1140 if ($x == -1 && $y == -1) {
423             ### found third arm -1,-1 ...
424 2         6 $arm = 2;
425 2         4 $state = 70;
426 2         4 last;
427             }
428              
429             # if ((($x == -1 || $x == 1) && $y == -1)
430             # || ($x == 0 && $y == -2)) {
431             # ### below island ...
432             # return undef;
433             # }
434              
435 559         888 my $m = ($x + 2*$y) % 7;
436             ### at: "$x,$y digits=".join(',',@digits)
437             ### mod remainder: $m
438              
439             # 0,0 is m=0
440 559 100       1540 if ($m == 2) { # 2,0 = 2
    100          
    100          
    100          
    100          
    100          
441 107         150 $x -= 2;
442             } elsif ($m == 3) { # 1,1 = 1+2 = 3
443 101         146 $x -= 1;
444 101         141 $y -= 1;
445             } elsif ($m == 1) { # -1,1 = -1+2 = 1
446 81         137 $x += 1;
447 81         125 $y -= 1;
448             } elsif ($m == 4) { # 0,2 = 0+2*2 = 4
449 64         108 $y -= 2;
450             } elsif ($m == 6) { # 2,2 = 2+2*2 = 6
451 76         123 $x -= 2;
452 76         122 $y -= 2;
453             } elsif ($m == 5) { # 3,1 = 3+2*1 = 5
454 68         107 $x -= 3;
455 68         108 $y -= 1;
456             }
457 559         818 push @digits, $m;
458              
459             ### digit: "$m to $x,$y"
460             ### shrink to: ((3*$y + 5*$x) / 14).','.((5*$y - $x) / 14)
461             ### assert: (3*$y + 5*$x) % 14 == 0
462             ### assert: (5*$y - $x) % 14 == 0
463              
464             # shrink
465 559         1174 ($x,$y) = ((3*$y + 5*$x) / 14,
466             (5*$y - $x) / 14);
467             }
468              
469             ### @digits
470 168         318 my $arms = $self->{'arms'};
471 168 100       379 if ($arm >= $arms) {
472 2         9 return undef;
473             }
474              
475 166         251 my $n = 0;
476 166         300 foreach my $m (reverse @digits) { # high to low
477             ### $m
478             ### digit: $modulus_to_digit[$state + $m]
479             ### state: $state
480             ### next state: $modulus_to_digit[$state+7 + $m]
481              
482 552         928 $n = 7*$n + $modulus_to_digit[$state + $m];
483 552         922 $state = $modulus_to_digit[$state+7 + $m];
484             }
485             ### final n along arm: $n
486              
487 166         604 return $n*$arms + $arm;
488             }
489              
490             # exact
491             sub rect_to_n_range {
492 137     137 1 12184 my ($self, $x1,$y1, $x2,$y2) = @_;
493             ### FlowsnakeCentres rect_to_n_range(): "$x1,$y1 $x2,$y2"
494              
495 137         657 my ($r_lo, $r_hi) = _rect_to_radius_range ($x1,$y1*sqrt(3), $x2,$y2*sqrt(3));
496 137         282 $r_hi *= 2;
497 137         417 my $level_plus_1 = ceil( log(max(1,$r_hi/4)) / log(sqrt(7)) ) + 2;
498             # return (0, 7**$level_plus_1);
499              
500              
501 137         263 my $level_limit = $level_plus_1;
502             ### $level_limit
503 137 50       373 if (is_infinite($level_limit)) { return ($level_limit,$level_limit); }
  0         0  
504              
505 137         378 $x1 = round_nearest ($x1);
506 137         288 $y1 = round_nearest ($y1);
507 137         296 $x2 = round_nearest ($x2);
508 137         312 $y2 = round_nearest ($y2);
509 137 100       369 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
510 137 50       268 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
511             ### sorted range: "$x1,$y1 $x2,$y2"
512              
513             my $rect_dist = sub {
514 20163     20163   32312 my ($x,$y) = @_;
515 20163 100       40174 my $xd = ($x < $x1 ? $x1 - $x
    100          
516             : $x > $x2 ? $x - $x2
517             : 0);
518 20163 100       37140 my $yd = ($y < $y1 ? $y1 - $y
    100          
519             : $y > $y2 ? $y - $y2
520             : 0);
521 20163         35330 return ($xd*$xd + 3*$yd*$yd);
522 137         877 };
523              
524 137         314 my $arms = $self->{'arms'};
525             ### $arms
526 137         209 my $n_lo;
527             {
528 137         231 my @hypot = (6);
  137         270  
529 137         230 my $top = 0;
530 137         194 for (;;) {
531 414         853 ARM_LO: foreach my $arm (0 .. $arms-1) {
532 419         594 my $i = 0;
533 419         551 my @digits;
534 419 100       773 if ($top > 0) {
535 278         575 @digits = ((0)x($top-1), 1);
536             } else {
537 141         261 @digits = (0);
538             }
539              
540 419         569 for (;;) {
541 10567         14498 my $n = 0;
542 10567         16564 foreach my $digit (reverse @digits) { # high to low
543 45646         63846 $n = 7*$n + $digit;
544             }
545 10567         14516 $n = $n*$arms + $arm;
546             ### lo consider: "i=$i digits=".join(',',reverse @digits)." is n=$n"
547              
548 10567         20318 my ($nx,$ny) = $self->n_to_xy($n);
549 10567         20780 my $nh = &$rect_dist ($nx,$ny);
550 10567 100 100     25861 if ($i == 0 && $nh == 0) {
551             ### lo found inside: $n
552 139 100 66     340 if (! defined $n_lo || $n < $n_lo) {
553 137         199 $n_lo = $n;
554             }
555 139         404 next ARM_LO;
556             }
557              
558 10428 100 100     27891 if ($i == 0 || $nh > $hypot[$i]) {
559             ### too far away: "nxy=$nx,$ny nh=$nh vs ".$hypot[$i]
560              
561 9761         20617 while (++$digits[$i] > 6) {
562 1441         1975 $digits[$i] = 0;
563 1441 100       3345 if (++$i <= $top) {
564             ### backtrack up ...
565             } else {
566             ### not found within this top and arm, next arm ...
567 280         701 next ARM_LO;
568             }
569             }
570             } else {
571             ### lo descend ...
572             ### assert: $i > 0
573 667         980 $i--;
574 667         1130 $digits[$i] = 0;
575             }
576             }
577             }
578              
579             # if an $n_lo was found on any arm within this $top then done
580 414 100       791 if (defined $n_lo) {
581 137         279 last;
582             }
583              
584             ### lo extend top ...
585 277 50       519 if (++$top > $level_limit) {
586             ### nothing below level limit ...
587 0         0 return (1,0);
588             }
589 277         542 $hypot[$top] = 7 * $hypot[$top-1];
590             }
591             }
592              
593 137         209 my $n_hi = 0;
594 137         353 ARM_HI: foreach my $arm (reverse 0 .. $arms-1) {
595 141         363 my @digits = ((6) x $level_limit);
596 141         223 my $i = $#digits;
597 141         219 for (;;) {
598 9596         13167 my $n = 0;
599 9596         14873 foreach my $digit (reverse @digits) { # high to low
600 59195         82571 $n = 7*$n + $digit;
601             }
602 9596         13130 $n = $n*$arms + $arm;
603             ### hi consider: "arm=$arm i=$i digits=".join(',',reverse @digits)." is n=$n"
604              
605 9596         18685 my ($nx,$ny) = $self->n_to_xy($n);
606 9596         18441 my $nh = &$rect_dist ($nx,$ny);
607 9596 100 100     22172 if ($i == 0 && $nh == 0) {
608             ### hi found inside: $n
609 139 100       309 if ($n > $n_hi) {
610 131         212 $n_hi = $n;
611 131         388 next ARM_HI;
612             }
613             }
614              
615 9465 100 100     26694 if ($i == 0 || $nh > (6 * 7**$i)) {
616             ### too far away: "$nx,$ny nh=$nh vs ".(6 * 7**$i)
617              
618 8067         17454 while (--$digits[$i] < 0) {
619 873         1182 $digits[$i] = 6;
620 873 100       2511 if (++$i < $level_limit) {
621             ### hi backtrack up ...
622             } else {
623             ### hi nothing within level limit for this arm ...
624 10         26 next ARM_HI;
625             }
626             }
627              
628             } else {
629             ### hi descend
630             ### assert: $i > 0
631 1398         2098 $i--;
632 1398         2190 $digits[$i] = 6;
633             }
634             }
635             }
636              
637 137 100       323 if ($n_hi == 0) {
638             ### oops, lo found but hi not found
639 7         13 $n_hi = $n_lo;
640             }
641              
642 137         1107 return ($n_lo, $n_hi);
643             }
644              
645             #------------------------------------------------------------------------------
646             # levels
647              
648             # arms=1 arms=2
649             # level 1 0..6 = 7 0..13 = 14
650             # level 2 0..48 = 49 0..97 = 98
651             # 7^k-1 2*7^k-1
652              
653             # level 7^k points
654             # or arms*7^k
655             # counting from 0
656             sub level_to_n_range {
657 6     6 1 393 my ($self, $level) = @_;
658 6         18 return (0, 7**$level * $self->{'arms'} - 1);
659             }
660             sub n_to_level {
661 0     0 1   my ($self, $n) = @_;
662 0 0         if ($n < 0) { return undef; }
  0            
663 0 0         if (is_infinite($n)) { return $n; }
  0            
664 0           $n = round_nearest($n);
665 0           _divrem_mutate ($n, $self->{'arms'});
666 0           my ($pow, $exp) = round_up_pow ($n+1, 7);
667 0           return $exp;
668             }
669              
670             #------------------------------------------------------------------------------
671             1;
672             __END__