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, 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 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   1408 use 5.004;
  2         7  
27 2     2   12 use strict;
  2         4  
  2         45  
28 2     2   1042 use POSIX 'ceil';
  2         14980  
  2         11  
29 2     2   2907 use List::Util 'min'; # 'max'
  2         4  
  2         255  
30             *max = \&Math::PlanePath::_max;
31              
32 2     2   15 use vars '$VERSION', '@ISA';
  2         4  
  2         147  
33             $VERSION = 129;
34 2     2   1537 use Math::PlanePath;
  2         5  
  2         116  
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   14 'xy_is_even';
  2         22  
42             use Math::PlanePath::Base::Digits
43 2         127 'digit_split_lowtohigh',
44 2     2   1066 'round_up_pow';
  2         11  
45              
46 2     2   1163 use Math::PlanePath::SacksSpiral;
  2         6  
  2         102  
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         174  
54              
55 2         290 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   14 } ];
  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   14 use constant dx_minimum => -2;
  2         4  
  2         145  
82 2     2   15 use constant dx_maximum => 2;
  2         4  
  2         130  
83 2     2   14 use constant dy_minimum => -1;
  2         4  
  2         118  
84 2     2   14 use constant dy_maximum => 1;
  2         4  
  2         298  
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   16 use constant absdx_minimum => 1;
  2         4  
  2         138  
94 2     2   21 use constant dsumxy_minimum => -2; # diagonals
  2         5  
  2         121  
95 2     2   13 use constant dsumxy_maximum => 2;
  2         4  
  2         110  
96 2     2   13 use constant ddiffxy_minimum => -2;
  2         6  
  2         105  
97 2     2   23 use constant ddiffxy_maximum => 2;
  2         4  
  2         104  
98 2     2   13 use constant dir_maximum_dxdy => (1,-1); # South-East
  2         4  
  2         4053  
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 5609 my $self = shift->SUPER::new(@_);
116 31   100     212 $self->{'arms'} = max(1, min(3, $self->{'arms'} || 1));
117 31         71 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 12139     12139 1 39196 my ($self, $n) = @_;
209             ### FlowsnakeCentres n_to_xy(): $n
210              
211 12139 50       21498 if ($n < 0) { return; }
  0         0  
212 12139 50       25055 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 12139         21298 my $int = int($n);
  12139         17294  
223             ### $int
224             ### $n
225 12139 100       20761 if ($n != $int) {
226 63         146 my ($x1,$y1) = $self->n_to_xy($int);
227 63         150 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
228 63         119 my $frac = $n - $int; # inherit possible BigFloat
229 63         90 my $dx = $x2-$x1;
230 63         105 my $dy = $y2-$y1;
231 63         268 return ($frac*$dx + $x1, $frac*$dy + $y1);
232             }
233 12076         17633 $n = $int; # BigFloat int() gives BigInt, use that
234             }
235              
236             # arm as initial rotation
237 12076         25064 my $rot = _divrem_mutate ($n, $self->{'arms'});
238              
239 12076         23706 my @digits = digit_split_lowtohigh($n,7);
240             ### @digits
241              
242 12076         17574 my $x = 0;
243 12076         16370 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 12076         16430 my $rev = 0;
  12076         15752  
266 12076         18946 foreach my $digit (reverse @digits) { # high to low
267             ### $digit
268 62325 100       98488 if ($rev) {
269             ### reverse: "$digit to ".(6 - $digit)
270 28462         37702 $digit = 6 - $digit; # mutate the array
271             }
272 62325         92776 $rev ^= $digit_reverse[$digit];
273             ### now rev: $rev
274             }
275             ### reversed n: @n
276             }
277              
278 12076         17197 my ($ox,$oy,$sx,$sy);
279 12076 50       19801 if ($rot == 0) {
    0          
280 12076         16097 $ox = 0;
281 12076         15661 $oy = 0;
282 12076         15432 $sx = 2;
283 12076         15625 $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 12076         23075 while (@digits) {
297 62325         90100 my $digit = shift @digits; # low to high
298             ### digit: "$digit $x,$y side $sx,$sy origin $ox,$oy"
299              
300 62325 100       134956 if ($digit == 0) {
    100          
    100          
    100          
    100          
    100          
    50          
301 26003         44439 $x += (3*$sy - $sx)/2; # at -120
302 26003         39487 $y += ($sx + $sy)/-2;
303              
304             } elsif ($digit == 1) {
305 8371         15733 ($x,$y) = ((3*$y-$x)/2, # rotate -120
306             ($x+$y)/-2);
307 8371         14306 $x += ($sx + 3*$sy)/2; # at -60
308 8371         12082 $y += ($sy - $sx)/2;
309              
310             } elsif ($digit == 2) {
311             # centre
312              
313             } elsif ($digit == 3) {
314 4285         8612 ($x,$y) = (($x+3*$y)/-2, # rotate +120
315             ($x-$y)/2);
316 4285         5923 $x -= $sx; # at -180
317 4285         6329 $y -= $sy;
318              
319             } elsif ($digit == 4) {
320 4205         7459 $x += ($sx + 3*$sy)/-2; # at +120
321 4205         6781 $y += ($sx - $sy)/2;
322              
323             } elsif ($digit == 5) {
324 3953         7045 $x += ($sx - 3*$sy)/2; # at +60
325 3953         5926 $y += ($sx + $sy)/2;
326              
327             } elsif ($digit == 6) {
328 9950         19248 ($x,$y) = (($x+3*$y)/-2, # rotate +120
329             ($x-$y)/2);
330 9950         13418 $x += $sx; # at X axis
331 9950         13515 $y += $sy;
332             }
333              
334 62325         82559 $ox += $sx;
335 62325         79905 $oy += $sy;
336              
337             # 2*(sx,sy) + rot+60(sx,sy)
338 62325         148455 ($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 12076         21267 $x += ($ox-3*$oy)/2; # rotate +60
347 12076         18819 $y += ($ox+$oy)/2;
348              
349             ### final: "$x,$y"
350 12076         27080 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 163     163 1 1350 my ($self, $x, $y) = @_;
390             ### FlowsnakeCentres xy_to_n(): "$x, $y"
391              
392 163         397 $x = round_nearest($x);
393 163         356 $y = round_nearest($y);
394 163 50       446 if (($x ^ $y) & 1) {
395             ### odd x,y ...
396 0         0 return undef;
397             }
398              
399 163         636 my $level_limit = log($x*$x + 3*$y*$y + 1) * 0.835 * 2;
400 163 50       380 if (is_infinite($level_limit)) { return $level_limit; }
  0         0  
401              
402 163         513 my @digits;
403             my $arm;
404 163         0 my $state;
405 163         247 for (;;) {
406 725 50       1483 if ($level_limit-- < 0) {
407             ### oops, level limit ...
408 0         0 return undef;
409             }
410 725 100 100     1615 if ($x == 0 && $y == 0) {
411             ### found first arm 0,0 ...
412 159         233 $arm = 0;
413 159         243 $state = 0;
414 159         245 last;
415             }
416 566 100 100     1124 if ($x == -2 && $y == 0) {
417             ### found second arm -2,0 ...
418 2         6 $arm = 1;
419 2         3 $state = 42;
420 2         3 last;
421             }
422 564 100 100     1081 if ($x == -1 && $y == -1) {
423             ### found third arm -1,-1 ...
424 2         3 $arm = 2;
425 2         6 $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 562         891 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 562 100       1472 if ($m == 2) { # 2,0 = 2
    100          
    100          
    100          
    100          
    100          
441 129         175 $x -= 2;
442             } elsif ($m == 3) { # 1,1 = 1+2 = 3
443 81         126 $x -= 1;
444 81         120 $y -= 1;
445             } elsif ($m == 1) { # -1,1 = -1+2 = 1
446 64         95 $x += 1;
447 64         103 $y -= 1;
448             } elsif ($m == 4) { # 0,2 = 0+2*2 = 4
449 73         124 $y -= 2;
450             } elsif ($m == 6) { # 2,2 = 2+2*2 = 6
451 85         139 $x -= 2;
452 85         117 $y -= 2;
453             } elsif ($m == 5) { # 3,1 = 3+2*1 = 5
454 72         114 $x -= 3;
455 72         108 $y -= 1;
456             }
457 562         829 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 562         1183 ($x,$y) = ((3*$y + 5*$x) / 14,
466             (5*$y - $x) / 14);
467             }
468              
469             ### @digits
470 163         288 my $arms = $self->{'arms'};
471 163 100       323 if ($arm >= $arms) {
472 2         14 return undef;
473             }
474              
475 161         253 my $n = 0;
476 161         290 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 551         901 $n = 7*$n + $modulus_to_digit[$state + $m];
483 551         922 $state = $modulus_to_digit[$state+7 + $m];
484             }
485             ### final n along arm: $n
486              
487 161         609 return $n*$arms + $arm;
488             }
489              
490             # exact
491             sub rect_to_n_range {
492 137     137 1 20515 my ($self, $x1,$y1, $x2,$y2) = @_;
493             ### FlowsnakeCentres rect_to_n_range(): "$x1,$y1 $x2,$y2"
494              
495 137         671 my ($r_lo, $r_hi) = _rect_to_radius_range ($x1,$y1*sqrt(3), $x2,$y2*sqrt(3));
496 137         314 $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         249 my $level_limit = $level_plus_1;
502             ### $level_limit
503 137 50       358 if (is_infinite($level_limit)) { return ($level_limit,$level_limit); }
  0         0  
504              
505 137         385 $x1 = round_nearest ($x1);
506 137         292 $y1 = round_nearest ($y1);
507 137         294 $x2 = round_nearest ($x2);
508 137         296 $y2 = round_nearest ($y2);
509 137 100       316 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
510 137 50       306 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
511             ### sorted range: "$x1,$y1 $x2,$y2"
512              
513             my $rect_dist = sub {
514 20775     20775   33547 my ($x,$y) = @_;
515 20775 100       41659 my $xd = ($x < $x1 ? $x1 - $x
    100          
516             : $x > $x2 ? $x - $x2
517             : 0);
518 20775 100       38045 my $yd = ($y < $y1 ? $y1 - $y
    100          
519             : $y > $y2 ? $y - $y2
520             : 0);
521 20775         37381 return ($xd*$xd + 3*$yd*$yd);
522 137         886 };
523              
524 137         326 my $arms = $self->{'arms'};
525             ### $arms
526 137         233 my $n_lo;
527             {
528 137         222 my @hypot = (6);
  137         301  
529 137         213 my $top = 0;
530 137         201 for (;;) {
531 434         863 ARM_LO: foreach my $arm (0 .. $arms-1) {
532 439         682 my $i = 0;
533 439         581 my @digits;
534 439 100       854 if ($top > 0) {
535 298         631 @digits = ((0)x($top-1), 1);
536             } else {
537 141         249 @digits = (0);
538             }
539              
540 439         601 for (;;) {
541 10820         14822 my $n = 0;
542 10820         16912 foreach my $digit (reverse @digits) { # high to low
543 47569         67451 $n = 7*$n + $digit;
544             }
545 10820         15161 $n = $n*$arms + $arm;
546             ### lo consider: "i=$i digits=".join(',',reverse @digits)." is n=$n"
547              
548 10820         21153 my ($nx,$ny) = $self->n_to_xy($n);
549 10820         20684 my $nh = &$rect_dist ($nx,$ny);
550 10820 100 100     25954 if ($i == 0 && $nh == 0) {
551             ### lo found inside: $n
552 139 100 66     367 if (! defined $n_lo || $n < $n_lo) {
553 137         206 $n_lo = $n;
554             }
555 139         396 next ARM_LO;
556             }
557              
558 10681 100 100     28798 if ($i == 0 || $nh > $hypot[$i]) {
559             ### too far away: "nxy=$nx,$ny nh=$nh vs ".$hypot[$i]
560              
561 10050         21692 while (++$digits[$i] > 6) {
562 1493         2102 $digits[$i] = 0;
563 1493 100       3579 if (++$i <= $top) {
564             ### backtrack up ...
565             } else {
566             ### not found within this top and arm, next arm ...
567 300         785 next ARM_LO;
568             }
569             }
570             } else {
571             ### lo descend ...
572             ### assert: $i > 0
573 631         947 $i--;
574 631         1030 $digits[$i] = 0;
575             }
576             }
577             }
578              
579             # if an $n_lo was found on any arm within this $top then done
580 434 100       871 if (defined $n_lo) {
581 137         239 last;
582             }
583              
584             ### lo extend top ...
585 297 50       569 if (++$top > $level_limit) {
586             ### nothing below level limit ...
587 0         0 return (1,0);
588             }
589 297         567 $hypot[$top] = 7 * $hypot[$top-1];
590             }
591             }
592              
593 137         220 my $n_hi = 0;
594 137         414 ARM_HI: foreach my $arm (reverse 0 .. $arms-1) {
595 141         344 my @digits = ((6) x $level_limit);
596 141         225 my $i = $#digits;
597 141         195 for (;;) {
598 9955         13806 my $n = 0;
599 9955         15654 foreach my $digit (reverse @digits) { # high to low
600 63697         88191 $n = 7*$n + $digit;
601             }
602 9955         13891 $n = $n*$arms + $arm;
603             ### hi consider: "arm=$arm i=$i digits=".join(',',reverse @digits)." is n=$n"
604              
605 9955         18927 my ($nx,$ny) = $self->n_to_xy($n);
606 9955         19226 my $nh = &$rect_dist ($nx,$ny);
607 9955 100 100     22592 if ($i == 0 && $nh == 0) {
608             ### hi found inside: $n
609 139 100       370 if ($n > $n_hi) {
610 131         214 $n_hi = $n;
611 131         400 next ARM_HI;
612             }
613             }
614              
615 9824 100 100     27088 if ($i == 0 || $nh > (6 * 7**$i)) {
616             ### too far away: "$nx,$ny nh=$nh vs ".(6 * 7**$i)
617              
618 8378         17893 while (--$digits[$i] < 0) {
619 904         1287 $digits[$i] = 6;
620 904 100       2294 if (++$i < $level_limit) {
621             ### hi backtrack up ...
622             } else {
623             ### hi nothing within level limit for this arm ...
624 10         41 next ARM_HI;
625             }
626             }
627              
628             } else {
629             ### hi descend
630             ### assert: $i > 0
631 1446         2037 $i--;
632 1446         2296 $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         15 $n_hi = $n_lo;
640             }
641              
642 137         1108 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 412 my ($self, $level) = @_;
658 6         21 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__