File Coverage

blib/lib/Math/PlanePath/CCurve.pm
Criterion Covered Total %
statement 134 188 71.2
branch 29 66 43.9
condition 20 22 90.9
subroutine 20 25 80.0
pod 8 8 100.0
total 211 309 68.2


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
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             package Math::PlanePath::CCurve;
20 1     1   2985 use 5.004;
  1         4  
21 1     1   5 use strict;
  1         3  
  1         38  
22 1     1   7 use List::Util 'min','max','sum';
  1         2  
  1         73  
23              
24 1     1   7 use vars '$VERSION', '@ISA';
  1         2  
  1         86  
25             $VERSION = 127;
26 1     1   887 use Math::PlanePath;
  1         3  
  1         29  
27 1     1   612 use Math::PlanePath::Base::NSEW;
  1         3  
  1         40  
28             @ISA = ('Math::PlanePath::Base::NSEW',
29             'Math::PlanePath');
30              
31             use Math::PlanePath::Base::Generic
32 1         49 'is_infinite',
33 1     1   7 'round_nearest';
  1         1  
34             use Math::PlanePath::Base::Digits
35 1         88 'round_up_pow',
36             'round_down_pow',
37             'bit_split_lowtohigh',
38             'digit_split_lowtohigh',
39 1     1   679 'digit_join_lowtohigh';
  1         2  
40             *_divrem = \&Math::PlanePath::_divrem;
41             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
42              
43 1     1   817 use Math::PlanePath::KochCurve;
  1         3  
  1         48  
44             *_digit_join_hightolow = \&Math::PlanePath::KochCurve::_digit_join_hightolow;
45              
46             # uncomment this to run the ### lines
47             # use Smart::Comments;
48              
49              
50             # Not sure about this yet ... 2 or 4? With mirror images too 8 arms would
51             # fill the plane everywhere 4-visited points double-traversed segments.
52             # use constant parameter_info_array => [ { name => 'arms',
53             # share_key => 'arms_2',
54             # display => 'Arms',
55             # type => 'integer',
56             # minimum => 1,
57             # maximum => 2,
58             # default => 1,
59             # width => 1,
60             # description => 'Arms',
61             # } ];
62              
63 1     1   7 use constant n_start => 0;
  1         2  
  1         52  
64 1     1   6 use constant x_negative_at_n => 6;
  1         2  
  1         41  
65 1     1   5 use constant y_negative_at_n => 22;
  1         3  
  1         40  
66 1     1   5 use constant _UNDOCUMENTED__dxdy_list_at_n => 7;
  1         2  
  1         1606  
67              
68              
69             #------------------------------------------------------------------------------
70              
71             sub new {
72 3     3 1 1324 my $self = shift->SUPER::new(@_);
73 3   50     47 $self->{'arms'} = max(1, min(2, $self->{'arms'} || 1));
74 3         8 return $self;
75             }
76              
77              
78             sub n_to_xy {
79 1424     1424 1 29998 my ($self, $n) = @_;
80             ### CCurve n_to_xy(): $n
81              
82 1424 50       2522 if ($n < 0) { return; }
  0         0  
83 1424 50       2584 if (is_infinite($n)) { return ($n, $n); }
  0         0  
84              
85 1424         2450 my $zero = ($n * 0); # inherit bignum 0
86 1424         1778 my $x = $zero;
87 1424         1812 my $y = $zero;
88             {
89 1424         1727 my $int = int($n);
  1424         1860  
90 1424         1774 $x = $n - $int; # inherit possible BigFloat
91 1424         1906 $n = $int; # BigFloat int() gives BigInt, use that
92             }
93              
94             # initial rotation from arm number $n mod $arms
95 1424         2842 my $rot = _divrem_mutate ($n, $self->{'arms'});
96              
97 1424         1967 my $len = $zero+1;
98 1424         2525 foreach my $digit (digit_split_lowtohigh($n,4)) {
99             ### $digit
100              
101 6185 100       10919 if ($digit == 0) {
    100          
    100          
102 1201         1904 ($x,$y) = ($y,-$x); # rotate -90
103             } elsif ($digit == 1) {
104 1665         2096 $y -= $len; # at Y=-len
105             } elsif ($digit == 2) {
106 1654         2096 $x += $len; # at X=len,Y=-len
107 1654         2021 $y -= $len;
108             } else {
109             ### assert: $digit == 3
110 1665         2716 ($x,$y) = (2*$len - $y, # at X=2len,Y=-len and rotate +90
111             $x-$len);
112             }
113 6185         7915 $rot++; # to keep initial direction
114 6185         8195 $len *= 2;
115             }
116              
117 1424 100       2560 if ($rot & 2) {
118 221         308 $x = -$x;
119 221         277 $y = -$y;
120             }
121 1424 100       2228 if ($rot & 1) {
122 955         1516 ($x,$y) = (-$y,$x);
123             }
124              
125             ### final: "$x,$y"
126 1424         2746 return ($x,$y);
127             }
128              
129             # point N=2^(2k) at XorY=+/-2^k radius 2^k
130             # N=2^(2k-1) at X=Y=+/-2^(k-1) radius sqrt(2)*2^(k-1)
131             # radius = sqrt(2^level)
132             # R(l)-R(l-1) = sqrt(2^level) - sqrt(2^(level-1))
133             # = sqrt(2^level) * (1 - 1/sqrt(2))
134             # about 0.29289
135              
136             # len=1 extent of lower level 0
137             # len=4 extent of lower level 2
138             # len=8 extent of lower level 4+1 = 5
139             # len=16 extent of lower level 8+3
140             # len/2 + len/4-1
141              
142             my @digit_to_rot = (-1, 1, 0, 1);
143             my @dir4_to_dsdd = ([1,-1],[1,1],[-1,1],[-1,-1]);
144              
145             sub xy_to_n {
146 61     61 1 15480 return scalar((shift->xy_to_n_list(@_))[0]);
147             }
148             sub xy_to_n_list {
149 113     113 1 10657 my ($self, $x, $y) = @_;
150             ### CCurve xy_to_n(): "$x, $y"
151              
152 113         270 $x = round_nearest($x);
153 113         243 $y = round_nearest($y);
154 113         201 my $zero = $x*0*$y;
155              
156 113         230 ($x,$y) = ($x + $y, $y - $x); # sum and diff
157 113 50       225 if (is_infinite($x)) { return $x; }
  0         0  
158 113 50       251 if (is_infinite($y)) { return $y; }
  0         0  
159              
160 113         192 my @n_list;
161 113         198 foreach my $dsdd (@dir4_to_dsdd) {
162 452         716 my ($ds,$dd) = @$dsdd;
163             ### attempt: "ds=$ds dd=$dd"
164 452         575 my $s = $x; # sum X+Y
165 452         560 my $d = $y; # diff Y-X
166 452         516 my @nbits;
167              
168 452   100     1639 until ($s >= -1 && $s <= 1 && $d >= -1 && $d <= 1) {
      100        
      100        
169             ### at: "s=$s, d=$d nbits=".join('',reverse @nbits)
170 2962         1922 my $bit = $s % 2;
171 2962         1942 push @nbits, $bit;
172 2962 100       2351 if ($bit) {
173 550         648 $s -= $ds;
174 550         677 $d -= $dd;
175 550         849 ($ds,$dd) = ($dd,-$ds); # rotate -90
176             }
177              
178             # divide 1/(1+i) = (1-i)/(1^2 - i^2)
179             # = (1-i)/2
180             # so multiply (s + i*d) * (1-i)/2
181             # s = (s + d)/2
182             # d = (d - s)/2
183             #
184             ### assert: (($s+$d)%2)==0
185              
186             # this form avoids overflow near DBL_MAX
187 2962         1813 my $odd = $s % 2;
188 2962         1795 $s -= $odd;
189 2962         1726 $d -= $odd;
190 2962         1960 $s /= 2;
191 2962         1773 $d /= 2;
192 2962         12811 ($s,$d) = ($s+$d+$odd, $d-$s);
193             }
194              
195             # five final positions
196             # . 0,1 . ds,dd
197             # |
198             # -1,0--0,0--1,0
199             # |
200             # . 0,-1 .
201             #
202             ### end: "s=$s d=$d ds=$ds dd=$dd"
203              
204             # last step must be East dx=1,dy=0
205 452 100 100     1057 unless ($ds == 1 && $dd == -1) { next; }
  283         479  
206              
207 169 100 100     535 if ($s == $ds && $d == $dd) {
    100 66        
208 85         130 push @nbits, 1;
209             } elsif ($s != 0 || $d != 0) {
210 81         161 next;
211             }
212             # ended s=0,d=0 or s=ds,d=dd, found an N
213 88         234 push @n_list, digit_join_lowtohigh(\@nbits, 2, $zero);
214             ### found N: "$n_list[-1]"
215             }
216             ### @n_list
217 113         379 return sort {$a<=>$b} @n_list;
  38         135  
218             }
219              
220             # f = (1 - 1/sqrt(2) = .292
221             # 1/f = 3.41
222             # N = 2^level
223             # Rend = sqrt(2)^level
224             # Rmin = Rend / 2 maybe
225             # Rmin^2 = (2^level)/4
226             # N = 4 * Rmin^2
227             #
228             sub rect_to_n_range {
229 5     5 1 453 my ($self, $x1,$y1, $x2,$y2) = @_;
230             ### CCurve rect_to_n_range(): "$x1,$y1 $x2,$y2"
231              
232 5         17 $x1 = round_nearest ($x1);
233 5         11 $x2 = round_nearest ($x2);
234 5         12 $y1 = round_nearest ($y1);
235 5         14 $y2 = round_nearest ($y2);
236              
237 5 50       13 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
238 5 50       12 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
239              
240 5         13 my ($len,$level) = _rect_to_k ($x1,$y1, $x2,$y2);
241 5 50       16 if (is_infinite($level)) {
242 0         0 return (0, $level);
243             }
244 5         21 return (0, 4*$len*$len*$self->{'arms'} - 1);
245             }
246              
247             # N=16 is Y=4 away k=2
248             # N=64 is Y=-8+1=-7 away k=3
249             # N=256=4^4 is X=2^4=16-3=-7 away k=4
250             # dist = 2^k - (2^(k-2)-1)
251             # = 2^k - 2^(k-2) + 1
252             # = 4*2^(k-2) - 2^(k-2) + 1
253             # = 3*2^(k-2) + 1
254             # k=2 3*2^(2-2)+1=4 len=4^2=16
255             # k=3 3*2^(3-2)+1=7 len=4^3=64
256             # k=4 3*2^(4-2)+1=13
257             # 2^(k-2) = (dist-1)/3
258             # 2^k = (dist-1)*4/3
259             #
260             # up = 3*2^(k-2+1) + 1
261             # 2^(k+1) = (dist-1)*4/3
262             # 2^k = (dist-1)*2/3
263             #
264             # left = 3*2^(k-2+1) + 1
265             # 2^(k+1) = (dist-1)*4/3
266             # 2^k = (dist-1)*2/3
267             #
268             # down = 3*2^(k-2+1) + 1
269             # 2^(k+1) = (dist-1)*4/3
270             # 2^k = (dist-1)*2/3
271             #
272             # m=2 4*(2-1)/3=4/3=1
273             # m=4 4*(4-1)/3=4
274             sub _rect_to_k {
275 5     5   12 my ($x1,$y1, $x2,$y2) = @_;
276             ### _rect_to_k(): $x1,$y1
277              
278             {
279 5         6 my $m = max(abs($x1),abs($y1),abs($x2),abs($y2));
  5         20  
280 5 50       13 if ($m < 2) {
281 0         0 return (2, 1);
282             }
283 5 50       13 if ($m < 4) {
284 0         0 return (4, 2);
285             }
286             ### round_down: 4*($m-1)/3
287 5         18 my ($len, $k) = round_down_pow (4*($m-1)/3, 2);
288 5         13 return ($len, $k);
289             }
290              
291 0         0 my $len;
292 0         0 my $k = 0;
293              
294 0         0 my $offset = -1;
295 0         0 foreach my $m ($x2, $y2, -$x1, -$y1) {
296 0         0 $offset++;
297             ### $offset
298             ### $m
299 0 0       0 next if $m < 0;
300              
301 0         0 my ($len1, $k1);
302             # if ($m < 2) {
303             # $len1 = 1;
304             # $k1 = 0;
305             # } else {
306             # }
307              
308 0         0 ($len1, $k1) = round_down_pow (($m-1)/3, 2);
309 0 0       0 next if $k1 < $offset;
310 0         0 my $sub = ($offset-$k1) % 4;
311 0         0 $k1 -= $sub; # round down to k1 == offset mod 4
312              
313 0 0       0 if ($k1 > $k) {
314 0         0 $k = $k1;
315 0         0 $len = $len1 / 2**$sub;
316             }
317             }
318              
319             ### result: "k=$k len=$len"
320 0         0 return ($len, 2*$k);
321             }
322              
323              
324              
325             my @dir4_to_dx = (1,0,-1,0);
326             my @dir4_to_dy = (0,1,0,-1);
327              
328             sub n_to_dxdy {
329 2525     2525 1 48667 my ($self, $n) = @_;
330             ### n_to_dxdy(): $n
331              
332 2525         3269 my $int = int($n);
333 2525         3201 $n -= $int; # $n fraction part
334              
335 2525         4162 my @digits = bit_split_lowtohigh($int);
336 2525   100     10581 my $dir = (sum(@digits)||0) & 3; # count of 1-bits
337 2525         3936 my $dx = $dir4_to_dx[$dir];
338 2525         3185 my $dy = $dir4_to_dy[$dir];
339              
340 2525 100       4181 if ($n) {
341             # apply fraction part $n
342              
343             # count low 1-bits is right turn of N+1, apply as dir-(turn-1) so decr $dir
344 14         32 while (shift @digits) {
345 18         38 $dir--;
346             }
347              
348             # this with turn=count-1 turn which is dir++ worked into swap and negate
349             # of dir4_to_dy parts
350 14         23 $dir &= 3;
351 14         31 $dx -= $n*($dir4_to_dy[$dir] + $dx); # with rot-90 instead of $dir+1
352 14         28 $dy += $n*($dir4_to_dx[$dir] - $dy);
353              
354             # this the equivalent with explicit dir++ for turn=count-1
355             # $dir++;
356             # $dir &= 3;
357             # $dx += $n*($dir4_to_dx[$dir] - $dx);
358             # $dy += $n*($dir4_to_dy[$dir] - $dy);
359             }
360              
361             ### result: "$dx, $dy"
362 2525         6656 return ($dx,$dy);
363             }
364              
365             #------------------------------------------------------------------------------
366             # k even
367             # S[h]
368             # ---------
369             # / \ Z[h-1]
370             # / \
371             # | | S[h-1]
372             # \ / Z[h-2]
373             # -- --
374             # Hb[k] = S[h] + 2*S[h-1] + S[h] + 2*(Z[h-1]/2 - Z[h-2]/2)
375             # + sqrt(2)*(2*Z[h-1]/2 + 2*Z[h-2]/2)
376             # = 2*S[h] + 2*S[h-1] + Z[h-1]-Z[h-2] + sqrt(2) * (Z[h-1] + Z[h-2])
377             # = 2*2^h + 2*2^(h-1) + 2*2^(h-1)-2 - (2*2^(h-2)-2) + sqrt(2) * (2*2^(h-1)-2 + 2*2^(h-2)-2)
378             # = 3*2^h + 2*2^(h-1)-2 - 2*2^(h-2) + 2 + sqrt(2) * (3*2^(h-1) - 4)
379             # = 3*2^h + 2^(h-1) + sqrt(2) * (3*2^(h-1) - 4)
380             # = 7*2^(h-1) + sqrt(2) * (3*2^(h-1) - 4)
381             # = 7*sqrt(2)^(2h-2) + sqrt(2) * (3*sqrt(2)^(2h-2) - 4)
382             # = 7*sqrt(2)^(k-2) + sqrt(2) * (3*sqrt(2)^(k-2) - 4)
383             # = 7*sqrt(2)^(k-2) + sqrt(2)*3*sqrt(2)^(k-2) - 4*sqrt(2)
384             # = 7*sqrt(2)^(k-2) + 3*sqrt(2)*sqrt(2)^(k-2) - 4*sqrt(2)
385             # = (7 + 3*sqrt(2))*sqrt(2)^(k-2) - 4*sqrt(2)
386             #
387             # S[2]=4
388             # 11--10--7,9--6---5 Z[1]=2 k=4 h=2
389             # | | |
390             # 13--12 8 4---3 4 + 2*2 + 4+(2-0) = 14
391             # | | S[1]=2 (2+0) = 2
392             # 14 2
393             # | |
394             # 15---16 0---1 Z[0] = 0
395             #
396              
397             # k odd
398             # S[h]
399             # ----
400             # Z[h-1] / \ middle Z[h]
401             # S[h-1] | \
402             # \ \
403             # | S[h]
404             # |
405             # \ / Z[h-1]
406             # --
407             # S[h-1]
408             #
409             # Hb[k] = 2*S[h] + 2*S[h-1] + sqrt(2)*( Z[h]/2 + Z[h-1] + Z[h]/2 + S[h]-S[h-1] )
410             # = 2*S[h] + 2*S[h-1] + sqrt(2)*( Z[h] + Z[h-1] + S[h]-S[h-1] )
411             # = 2*2^h + 2*2^(h-1) + sqrt(2)*( 2*2^h-2 + 2*2^(h-1)-2 + 2^h - 2^(h-1) )
412             # = 3*2^h + sqrt(2)*( 3*2^h + 2^(h-1) - 4 )
413             # = 3*2^h + sqrt(2)*( 7*2^(h-1) - 4 )
414              
415             sub _UNDOCUMENTED_level_to_hull_boundary {
416 0     0     my ($self, $level) = @_;
417 0 0         my ($a, $b) = $self->_UNDOCUMENTED_level_to_hull_boundary_sqrt2($level)
418             or return undef;
419 0           return $a + $b*sqrt(2);
420             }
421             sub _UNDOCUMENTED_level_to_hull_boundary_sqrt2 {
422 0     0     my ($self, $level) = @_;
423 0 0         if ($level <= 2) {
424 0 0         if ($level < 0) { return; }
  0            
425 0 0         if ($level == 2) { return (6,0); }
  0            
426 0 0         return (2, ($level == 0 ? 0 : 1));
427             }
428              
429 0           my ($h, $rem) = _divrem($level, 2);
430 0           my $pow = 2**($h-1);
431              
432 0 0         if ($rem) {
433 0           return (6*$pow, 7*$pow-4);
434              
435             # return (2*S_formula($h) + 2*S_formula($h-1),
436             # Z_formula($h)/2 + Z_formula($h-1)
437             # + Z_formula($h)/2 + (S_formula($h)-S_formula($h-1)) );
438              
439             } else {
440 0           return (7*$pow, 3*$pow-4);
441              
442             # return (S_formula($h) + 2*S_formula($h-1) + S_formula($h)+(Z_formula($h-1)-Z_formula($h-2)),
443             # (Z_formula($h-1) + Z_formula($h-2)));
444             }
445             }
446              
447             #------------------------------------------------------------------------------
448             {
449             my @_UNDOCUMENTED_level_to_hull_area = (0, 1/2, 2);
450              
451             sub _UNDOCUMENTED_level_to_hull_area {
452 0     0     my ($self, $level) = @_;
453              
454 0 0         if ($level < 3) {
455 0 0         if ($level < 0) { return undef; }
  0            
456 0           return $_UNDOCUMENTED_level_to_hull_area[$level];
457             }
458 0           my ($h, $rem) = _divrem($level, 2);
459 0 0         return 35*2**($level-4) - ($rem ? 13 : 10)*2**($h-1) + 2;
460              
461             # if ($rem) {
462             # return 35*2**($level-4) - 13*$pow + 2;
463             #
464             # my $width = S_formula($h) + Z_formula($h)/2 + Z_formula($h-1)/2;
465             # my $ul = Z_formula($h-1)/2;
466             # my $ur = Z_formula($h)/2;
467             # my $bl = $width - Z_formula($h-1)/2 - S_formula($h-1);
468             # my $br = Z_formula($h-1)/2;
469             # return $width**2 - $ul**2/2 - $ur**2/2 - $bl**2/2 - $br**2/2;
470             #
471             # } else {
472             # return 35*2**($level-4) - 10*$pow + 2;
473             # return 0;
474             # return 35*2**($level-4) - 5*2**$h + 2;
475             #
476             # # my $width = S_formula($h) + Z_formula($h-1);
477             # # my $upper = Z_formula($h-1)/2;
478             # # my $lower = Z_formula($h-2)/2;
479             # # my $height = S_formula($h-1) + $upper + $lower;
480             # # return $width; # * $height - $upper*$upper - $lower*$lower;
481             # }
482             # }
483             }
484             }
485              
486             #------------------------------------------------------------------------------
487             # levels
488              
489             sub level_to_n_range {
490 0     0 1   my ($self, $level) = @_;
491 0           return (0, 2**$level);
492             }
493             sub n_to_level {
494 0     0 1   my ($self, $n) = @_;
495 0 0         if ($n < 0) { return undef; }
  0            
496 0 0         if (is_infinite($n)) { return $n; }
  0            
497 0           $n = round_nearest($n);
498 0           my ($pow, $exp) = round_up_pow ($n, 2);
499 0           return $exp;
500             }
501              
502             #------------------------------------------------------------------------------
503             1;
504             __END__