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 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   1404 use 5.004;
  2         7  
27 2     2   12 use strict;
  2         4  
  2         43  
28 2     2   949 use POSIX 'ceil';
  2         14385  
  2         11  
29 2     2   2909 use List::Util 'min'; # 'max'
  2         5  
  2         240  
30             *max = \&Math::PlanePath::_max;
31              
32 2     2   15 use vars '$VERSION', '@ISA';
  2         5  
  2         145  
33             $VERSION = 127;
34 2     2   1524 use Math::PlanePath;
  2         6  
  2         103  
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         3  
42             use Math::PlanePath::Base::Digits
43 2         118 'digit_split_lowtohigh',
44 2     2   1020 'round_up_pow';
  2         6  
45              
46 2     2   1010 use Math::PlanePath::SacksSpiral;
  2         7  
  2         107  
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         150  
54              
55 2         327 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   13 } ];
  2         4  
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         5  
  2         107  
82 2     2   12 use constant dx_maximum => 2;
  2         4  
  2         105  
83 2     2   13 use constant dy_minimum => -1;
  2         12  
  2         115  
84 2     2   14 use constant dy_maximum => 1;
  2         5  
  2         261  
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   13 use constant absdx_minimum => 1;
  2         5  
  2         110  
94 2     2   12 use constant dsumxy_minimum => -2; # diagonals
  2         4  
  2         119  
95 2     2   13 use constant dsumxy_maximum => 2;
  2         5  
  2         116  
96 2     2   13 use constant ddiffxy_minimum => -2;
  2         4  
  2         121  
97 2     2   13 use constant ddiffxy_maximum => 2;
  2         3  
  2         119  
98 2     2   13 use constant dir_maximum_dxdy => (1,-1); # South-East
  2         4  
  2         3964  
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 9736 my $self = shift->SUPER::new(@_);
116 31   100     234 $self->{'arms'} = max(1, min(3, $self->{'arms'} || 1));
117 31         76 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 9488     9488 1 34641 my ($self, $n) = @_;
209             ### FlowsnakeCentres n_to_xy(): $n
210              
211 9488 50       16834 if ($n < 0) { return; }
  0         0  
212 9488 50       18579 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 9488         15974 my $int = int($n);
  9488         13323  
223             ### $int
224             ### $n
225 9488 100       16904 if ($n != $int) {
226 63         129 my ($x1,$y1) = $self->n_to_xy($int);
227 63         167 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
228 63         106 my $frac = $n - $int; # inherit possible BigFloat
229 63         94 my $dx = $x2-$x1;
230 63         89 my $dy = $y2-$y1;
231 63         206 return ($frac*$dx + $x1, $frac*$dy + $y1);
232             }
233 9425         13347 $n = $int; # BigFloat int() gives BigInt, use that
234             }
235              
236             # arm as initial rotation
237 9425         19793 my $rot = _divrem_mutate ($n, $self->{'arms'});
238              
239 9425         18264 my @digits = digit_split_lowtohigh($n,7);
240             ### @digits
241              
242 9425         13919 my $x = 0;
243 9425         12743 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 9425         12613 my $rev = 0;
  9425         12591  
266 9425         15197 foreach my $digit (reverse @digits) { # high to low
267             ### $digit
268 43149 100       68701 if ($rev) {
269             ### reverse: "$digit to ".(6 - $digit)
270 19583         25283 $digit = 6 - $digit; # mutate the array
271             }
272 43149         62810 $rev ^= $digit_reverse[$digit];
273             ### now rev: $rev
274             }
275             ### reversed n: @n
276             }
277              
278 9425         13936 my ($ox,$oy,$sx,$sy);
279 9425 50       15107 if ($rot == 0) {
    0          
280 9425         12411 $ox = 0;
281 9425         12153 $oy = 0;
282 9425         12124 $sx = 2;
283 9425         12543 $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 9425         17285 while (@digits) {
297 43149         62632 my $digit = shift @digits; # low to high
298             ### digit: "$digit $x,$y side $sx,$sy origin $ox,$oy"
299              
300 43149 100       92801 if ($digit == 0) {
    100          
    100          
    100          
    100          
    100          
    50          
301 16984         28659 $x += (3*$sy - $sx)/2; # at -120
302 16984         25322 $y += ($sx + $sy)/-2;
303              
304             } elsif ($digit == 1) {
305 5543         10134 ($x,$y) = ((3*$y-$x)/2, # rotate -120
306             ($x+$y)/-2);
307 5543         9068 $x += ($sx + 3*$sy)/2; # at -60
308 5543         8097 $y += ($sy - $sx)/2;
309              
310             } elsif ($digit == 2) {
311             # centre
312              
313             } elsif ($digit == 3) {
314 2818         5484 ($x,$y) = (($x+3*$y)/-2, # rotate +120
315             ($x-$y)/2);
316 2818         4006 $x -= $sx; # at -180
317 2818         3744 $y -= $sy;
318              
319             } elsif ($digit == 4) {
320 3185         5731 $x += ($sx + 3*$sy)/-2; # at +120
321 3185         4903 $y += ($sx - $sy)/2;
322              
323             } elsif ($digit == 5) {
324 2861         5207 $x += ($sx - 3*$sy)/2; # at +60
325 2861         4402 $y += ($sx + $sy)/2;
326              
327             } elsif ($digit == 6) {
328 7773         14905 ($x,$y) = (($x+3*$y)/-2, # rotate +120
329             ($x-$y)/2);
330 7773         10722 $x += $sx; # at X axis
331 7773         10427 $y += $sy;
332             }
333              
334 43149         55805 $ox += $sx;
335 43149         55299 $oy += $sy;
336              
337             # 2*(sx,sy) + rot+60(sx,sy)
338 43149         98518 ($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 9425         16330 $x += ($ox-3*$oy)/2; # rotate +60
347 9425         14892 $y += ($ox+$oy)/2;
348              
349             ### final: "$x,$y"
350 9425         20017 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 164     164 1 1319 my ($self, $x, $y) = @_;
390             ### FlowsnakeCentres xy_to_n(): "$x, $y"
391              
392 164         415 $x = round_nearest($x);
393 164         342 $y = round_nearest($y);
394 164 50       519 if (($x ^ $y) & 1) {
395             ### odd x,y ...
396 0         0 return undef;
397             }
398              
399 164         626 my $level_limit = log($x*$x + 3*$y*$y + 1) * 0.835 * 2;
400 164 50       409 if (is_infinite($level_limit)) { return $level_limit; }
  0         0  
401              
402 164         524 my @digits;
403             my $arm;
404 164         0 my $state;
405 164         248 for (;;) {
406 732 50       1486 if ($level_limit-- < 0) {
407             ### oops, level limit ...
408 0         0 return undef;
409             }
410 732 100 100     1721 if ($x == 0 && $y == 0) {
411             ### found first arm 0,0 ...
412 161         254 $arm = 0;
413 161         306 $state = 0;
414 161         273 last;
415             }
416 571 100 100     1205 if ($x == -2 && $y == 0) {
417             ### found second arm -2,0 ...
418 2         4 $arm = 1;
419 2         5 $state = 42;
420 2         4 last;
421             }
422 569 100 100     1091 if ($x == -1 && $y == -1) {
423             ### found third arm -1,-1 ...
424 1         5 $arm = 2;
425 1         2 $state = 70;
426 1         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 568         930 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 568 100       1594 if ($m == 2) { # 2,0 = 2
    100          
    100          
    100          
    100          
    100          
441 126         168 $x -= 2;
442             } elsif ($m == 3) { # 1,1 = 1+2 = 3
443 97         138 $x -= 1;
444 97         140 $y -= 1;
445             } elsif ($m == 1) { # -1,1 = -1+2 = 1
446 74         126 $x += 1;
447 74         115 $y -= 1;
448             } elsif ($m == 4) { # 0,2 = 0+2*2 = 4
449 78         121 $y -= 2;
450             } elsif ($m == 6) { # 2,2 = 2+2*2 = 6
451 61         110 $x -= 2;
452 61         92 $y -= 2;
453             } elsif ($m == 5) { # 3,1 = 3+2*1 = 5
454 70         134 $x -= 3;
455 70         101 $y -= 1;
456             }
457 568         859 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 568         1242 ($x,$y) = ((3*$y + 5*$x) / 14,
466             (5*$y - $x) / 14);
467             }
468              
469             ### @digits
470 164         311 my $arms = $self->{'arms'};
471 164 100       381 if ($arm >= $arms) {
472 1         5 return undef;
473             }
474              
475 163         242 my $n = 0;
476 163         340 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 565         886 $n = 7*$n + $modulus_to_digit[$state + $m];
483 565         1011 $state = $modulus_to_digit[$state+7 + $m];
484             }
485             ### final n along arm: $n
486              
487 163         627 return $n*$arms + $arm;
488             }
489              
490             # exact
491             sub rect_to_n_range {
492 137     137 1 16260 my ($self, $x1,$y1, $x2,$y2) = @_;
493             ### FlowsnakeCentres rect_to_n_range(): "$x1,$y1 $x2,$y2"
494              
495 137         735 my ($r_lo, $r_hi) = _rect_to_radius_range ($x1,$y1*sqrt(3), $x2,$y2*sqrt(3));
496 137         319 $r_hi *= 2;
497 137         381 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         291 my $level_limit = $level_plus_1;
502             ### $level_limit
503 137 50       371 if (is_infinite($level_limit)) { return ($level_limit,$level_limit); }
  0         0  
504              
505 137         416 $x1 = round_nearest ($x1);
506 137         307 $y1 = round_nearest ($y1);
507 137         309 $x2 = round_nearest ($x2);
508 137         300 $y2 = round_nearest ($y2);
509 137 100       328 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
510 137 50       293 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
511             ### sorted range: "$x1,$y1 $x2,$y2"
512              
513             my $rect_dist = sub {
514 19277     19277   30747 my ($x,$y) = @_;
515 19277 100       37479 my $xd = ($x < $x1 ? $x1 - $x
    100          
516             : $x > $x2 ? $x - $x2
517             : 0);
518 19277 100       34412 my $yd = ($y < $y1 ? $y1 - $y
    100          
519             : $y > $y2 ? $y - $y2
520             : 0);
521 19277         33479 return ($xd*$xd + 3*$yd*$yd);
522 137         934 };
523              
524 137         300 my $arms = $self->{'arms'};
525             ### $arms
526 137         260 my $n_lo;
527             {
528 137         195 my @hypot = (6);
  137         283  
529 137         219 my $top = 0;
530 137         231 for (;;) {
531 412         846 ARM_LO: foreach my $arm (0 .. $arms-1) {
532 417         668 my $i = 0;
533 417         548 my @digits;
534 417 100       781 if ($top > 0) {
535 276         605 @digits = ((0)x($top-1), 1);
536             } else {
537 141         266 @digits = (0);
538             }
539              
540 417         669 for (;;) {
541 9695         13642 my $n = 0;
542 9695         15270 foreach my $digit (reverse @digits) { # high to low
543 38225         53836 $n = 7*$n + $digit;
544             }
545 9695         13409 $n = $n*$arms + $arm;
546             ### lo consider: "i=$i digits=".join(',',reverse @digits)." is n=$n"
547              
548 9695         18836 my ($nx,$ny) = $self->n_to_xy($n);
549 9695         18411 my $nh = &$rect_dist ($nx,$ny);
550 9695 100 100     23649 if ($i == 0 && $nh == 0) {
551             ### lo found inside: $n
552 139 100 66     386 if (! defined $n_lo || $n < $n_lo) {
553 137         195 $n_lo = $n;
554             }
555 139         390 next ARM_LO;
556             }
557              
558 9556 100 100     24493 if ($i == 0 || $nh > $hypot[$i]) {
559             ### too far away: "nxy=$nx,$ny nh=$nh vs ".$hypot[$i]
560              
561 8967         18797 while (++$digits[$i] > 6) {
562 1318         1912 $digits[$i] = 0;
563 1318 100       3145 if (++$i <= $top) {
564             ### backtrack up ...
565             } else {
566             ### not found within this top and arm, next arm ...
567 278         701 next ARM_LO;
568             }
569             }
570             } else {
571             ### lo descend ...
572             ### assert: $i > 0
573 589         860 $i--;
574 589         948 $digits[$i] = 0;
575             }
576             }
577             }
578              
579             # if an $n_lo was found on any arm within this $top then done
580 412 100       866 if (defined $n_lo) {
581 137         270 last;
582             }
583              
584             ### lo extend top ...
585 275 50       529 if (++$top > $level_limit) {
586             ### nothing below level limit ...
587 0         0 return (1,0);
588             }
589 275         539 $hypot[$top] = 7 * $hypot[$top-1];
590             }
591             }
592              
593 137         260 my $n_hi = 0;
594 137         416 ARM_HI: foreach my $arm (reverse 0 .. $arms-1) {
595 141         338 my @digits = ((6) x $level_limit);
596 141         251 my $i = $#digits;
597 141         209 for (;;) {
598 9582         12853 my $n = 0;
599 9582         15020 foreach my $digit (reverse @digits) { # high to low
600 57361         79927 $n = 7*$n + $digit;
601             }
602 9582         13612 $n = $n*$arms + $arm;
603             ### hi consider: "arm=$arm i=$i digits=".join(',',reverse @digits)." is n=$n"
604              
605 9582         18777 my ($nx,$ny) = $self->n_to_xy($n);
606 9582         18466 my $nh = &$rect_dist ($nx,$ny);
607 9582 100 100     22284 if ($i == 0 && $nh == 0) {
608             ### hi found inside: $n
609 139 100       304 if ($n > $n_hi) {
610 131         197 $n_hi = $n;
611 131         409 next ARM_HI;
612             }
613             }
614              
615 9451 100 100     26273 if ($i == 0 || $nh > (6 * 7**$i)) {
616             ### too far away: "$nx,$ny nh=$nh vs ".(6 * 7**$i)
617              
618 8063         17567 while (--$digits[$i] < 0) {
619 866         1217 $digits[$i] = 6;
620 866 100       2174 if (++$i < $level_limit) {
621             ### hi backtrack up ...
622             } else {
623             ### hi nothing within level limit for this arm ...
624 10         27 next ARM_HI;
625             }
626             }
627              
628             } else {
629             ### hi descend
630             ### assert: $i > 0
631 1388         1952 $i--;
632 1388         2180 $digits[$i] = 6;
633             }
634             }
635             }
636              
637 137 100       329 if ($n_hi == 0) {
638             ### oops, lo found but hi not found
639 7         13 $n_hi = $n_lo;
640             }
641              
642 137         1234 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 402 my ($self, $level) = @_;
658 6         20 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__