File Coverage

blib/lib/Math/PlanePath/TerdragonMidpoint.pm
Criterion Covered Total %
statement 84 137 61.3
branch 8 30 26.6
condition 7 13 53.8
subroutine 22 33 66.6
pod 11 11 100.0
total 132 224 58.9


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde
2              
3             # This file is part of Math-PlanePath.
4             #
5             # Math-PlanePath is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-PlanePath is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath. If not, see .
17              
18              
19              
20              
21             # math-image --path=TerdragonMidpoint --lines --scale=40
22             #
23             # math-image --path=TerdragonMidpoint --all --output=numbers_dash --size=78x60
24             # math-image --path=TerdragonMidpoint,arms=6 --all --output=numbers_dash --size=78x60
25              
26              
27             package Math::PlanePath::TerdragonMidpoint;
28 3     3   9748 use 5.004;
  3         15  
29 3     3   23 use strict;
  3         6  
  3         97  
30 3     3   20 use List::Util 'min'; # 'max'
  3         5  
  3         299  
31             *max = \&Math::PlanePath::_max;
32              
33 3     3   20 use vars '$VERSION', '@ISA';
  3         6  
  3         201  
34             $VERSION = 129;
35 3     3   740 use Math::PlanePath;
  3         14  
  3         128  
36             @ISA = ('Math::PlanePath');
37              
38             use Math::PlanePath::Base::Generic
39 3         136 'is_infinite',
40 3     3   20 'round_nearest';
  3         5  
41             use Math::PlanePath::Base::Digits
42 3         210 'digit_join_lowtohigh',
43 3     3   476 'round_up_pow';
  3         7  
44             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
45              
46             # uncomment this to run the ### lines
47             # use Smart::Comments;
48              
49              
50 3     3   20 use constant n_start => 0;
  3         6  
  3         228  
51 3         491 use constant parameter_info_array => [ { name => 'arms',
52             share_key => 'arms_6',
53             display => 'Arms',
54             type => 'integer',
55             minimum => 1,
56             maximum => 6,
57             default => 1,
58             width => 1,
59             description => 'Arms',
60 3     3   29 } ];
  3         7  
61              
62             {
63             my @x_negative_at_n = (undef, 12, 5, 2, 2, 2, 2);
64             sub x_negative_at_n {
65 0     0 1 0 my ($self) = @_;
66 0         0 return $x_negative_at_n[$self->{'arms'}];
67             }
68             }
69             {
70             my @y_negative_at_n = (undef, 158, 73, 17, 7, 4, 4);
71             sub y_negative_at_n {
72 0     0 1 0 my ($self) = @_;
73 0         0 return $y_negative_at_n[$self->{'arms'}];
74             }
75             }
76 3     3   30 use constant sumabsxy_minimum => 2; # X=2,Y=0 or X=1,Y=1
  3         6  
  3         310  
77             sub rsquared_minimum {
78 0     0 1 0 my ($self) = @_;
79 0 0       0 return ($self->arms_count < 2
80             ? 4 # 1 arm, minimum X=2,Y=0
81             : 2); # 2 or more arms, minimum X=1,Y=1
82             }
83              
84 3     3   22 use constant dx_minimum => -2;
  3         4  
  3         308  
85             sub dx_maximum {
86 0     0 1 0 my ($self) = @_;
87 0 0       0 return ($self->{'arms'} == 1 ? 1 : 2);
88             }
89 3     3   19 use constant dy_minimum => -1;
  3         5  
  3         162  
90 3     3   19 use constant dy_maximum => 1;
  3         6  
  3         513  
91              
92             sub _UNDOCUMENTED__dxdy_list {
93 0     0   0 my ($self) = @_;
94 0 0       0 return ($self->{'arms'} == 1
95             ? (1,1, # NE
96             -2,0, # W
97             1,-1) # SE
98             : Math::PlanePath::_UNDOCUMENTED__dxdy_list_six());
99             }
100             {
101             my @_UNDOCUMENTED__dxdy_list_at_n = (undef,
102             12, 25, 37,
103             15, 18, 5);
104             sub _UNDOCUMENTED__dxdy_list_at_n {
105 0     0   0 my ($self) = @_;
106 0         0 return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}];
107             }
108             }
109              
110 3     3   31 use constant absdx_minimum => 1;
  3         5  
  3         167  
111 3     3   18 use constant dsumxy_minimum => -2; # diagonals
  3         7  
  3         143  
112 3     3   18 use constant dsumxy_maximum => 2;
  3         6  
  3         147  
113 3     3   19 use constant ddiffxy_minimum => -2;
  3         6  
  3         150  
114 3     3   18 use constant ddiffxy_maximum => 2;
  3         5  
  3         366  
115              
116             # arms=1 curve goes at 60,180,300 degrees
117             # arms=2 second +60 to 120,240,0 degrees
118             # so when arms==1 dir minimum is 60 degrees North-East
119             #
120             sub dir_minimum_dxdy {
121 0     0 1 0 my ($self) = @_;
122 0 0       0 return ($self->{'arms'} == 1
123             ? (1,1) # North-East
124             : (1,0)); # East
125             }
126 3     3   20 use constant dir_maximum_dxdy => (1,-1); # South-East
  3         5  
  3         3482  
127              
128             sub _UNDOCUMENTED__turn_any_right_at_n {
129 0     0   0 my ($self) = @_;
130             # N=5 first right, and on multi-arms 10,15,20,25,30
131 0         0 return 5*$self->arms_count;
132             }
133              
134              
135             #------------------------------------------------------------------------------
136              
137             # Not quite.
138             # # all even points when arms==3
139             # use Math::PlanePath::TerdragonCurve;
140             # *xy_is_visited = \&Math::PlanePath::TerdragonCurve::xy_is_visited;
141              
142             sub new {
143 4     4 1 1243 my $self = shift->SUPER::new(@_);
144 4   100     31 $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1));
145 4         9 return $self;
146             }
147              
148             sub n_to_xy {
149 0     0 1 0 my ($self, $n) = @_;
150             ### TerdragonMidpoint n_to_xy(): $n
151              
152 0 0       0 if ($n < 0) { return; }
  0         0  
153 0 0       0 if (is_infinite($n)) { return ($n, $n); }
  0         0  
154              
155             {
156 0         0 my $int = int($n);
  0         0  
157 0 0       0 if ($n != $int) {
158 0         0 my ($x1,$y1) = $self->n_to_xy($int);
159 0         0 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
160 0         0 my $frac = $n - $int; # inherit possible BigFloat
161 0         0 my $dx = $x2-$x1;
162 0         0 my $dy = $y2-$y1;
163 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
164             }
165 0         0 $n = $int; # BigFloat int() gives BigInt, use that
166             }
167              
168             # ENHANCE-ME: own code ...
169             #
170 0         0 require Math::PlanePath::TerdragonCurve;
171 0         0 my ($x1,$y1) = $self->Math::PlanePath::TerdragonCurve::n_to_xy($n);
172 0         0 my ($x2,$y2) = $self->Math::PlanePath::TerdragonCurve::n_to_xy($n+$self->{'arms'});
173              
174             # dx = x2-x1
175             # X = 2 * (x1 + dx/2)
176             # = 2 * (x1 + x2/2 - x1/2)
177             # = 2 * (x1/2 + x2/2)
178             # = x1+x2
179 0         0 return ($x1+$x2,
180             $y1+$y2);
181             }
182              
183             # sub n_to_xy {
184             # my ($self, $n) = @_;
185             # ### TerdragonMidpoint n_to_xy(): $n
186             #
187             # if ($n < 0) { return; }
188             # if (is_infinite($n)) { return ($n, $n); }
189             #
190             # my $frac;
191             # {
192             # my $int = int($n);
193             # $frac = $n - $int; # inherit possible BigFloat
194             # $n = $int; # BigFloat int() gives BigInt, use that
195             # }
196             #
197             # my $zero = ($n * 0); # inherit bignum 0
198             #
199             # ($n, my $rot) = _divrem ($n, $self->{'arms'});
200             #
201             # # ENHANCE-ME: sx,sy just from len,len
202             # my @digits;
203             # my @sx;
204             # my @sy;
205             # {
206             # my $sx = $zero + 1;
207             # my $sy = -$sx;
208             # while ($n) {
209             # push @digits, ($n % 2);
210             # push @sx, $sx;
211             # push @sy, $sy;
212             # $n = int($n/2);
213             #
214             # # (sx,sy) + rot+90(sx,sy)
215             # ($sx,$sy) = ($sx - $sy,
216             # $sy + $sx);
217             # }
218             # }
219             #
220             # ### @digits
221             # my $rev = 0;
222             # my $x = $zero;
223             # my $y = $zero;
224             # my $above_low_zero = 0;
225             #
226             # for (my $i = $#digits; $i >= 0; $i--) { # high to low
227             # my $digit = $digits[$i];
228             # my $sx = $sx[$i];
229             # my $sy = $sy[$i];
230             # ### at: "$x,$y $digit side $sx,$sy"
231             # ### $rot
232             #
233             # if ($rot & 2) {
234             # $sx = -$sx;
235             # $sy = -$sy;
236             # }
237             # if ($rot & 1) {
238             # ($sx,$sy) = (-$sy,$sx);
239             # }
240             # ### rotated side: "$sx,$sy"
241             #
242             # if ($rev) {
243             # if ($digit) {
244             # $x += -$sy;
245             # $y += $sx;
246             # ### rev add to: "$x,$y next is still rev"
247             # } else {
248             # $above_low_zero = $digits[$i+1];
249             # $rot ++;
250             # $rev = 0;
251             # ### rev rot, next is no rev ...
252             # }
253             # } else {
254             # if ($digit) {
255             # $rot ++;
256             # $x += $sx;
257             # $y += $sy;
258             # $rev = 1;
259             # ### plain add to: "$x,$y next is rev"
260             # } else {
261             # $above_low_zero = $digits[$i+1];
262             # }
263             # }
264             # }
265             #
266             # # Digit above the low zero is the direction of the next turn, 0 for left,
267             # # 1 for right.
268             # #
269             # ### final: "$x,$y rot=$rot above_low_zero=".($above_low_zero||0)
270             #
271             # if ($rot & 2) {
272             # $frac = -$frac; # rotate 180
273             # $x -= 1;
274             # }
275             # if (($rot+1) & 2) {
276             # # rot 1 or 2
277             # $y += 1;
278             # }
279             # if (!($rot & 1) && $above_low_zero) {
280             # $frac = -$frac;
281             # }
282             # $above_low_zero ^= ($rot & 1);
283             # if ($above_low_zero) {
284             # $y = $frac + $y;
285             # } else {
286             # $x = $frac + $x;
287             # }
288             #
289             # ### rotated offset: "$x_offset,$y_offset return $x,$y"
290             # return ($x,$y);
291             # }
292              
293              
294             # w^2 = -1+w
295             # c = (X-Y)/2 x=2c+d
296             # d = Y y=d
297             # (c+dw)/(w+1)
298             # = (c+dw)*(2-w)/3
299             # = (2c-cw + 2dw-dw^2) / 3
300             # = (2c-cw + 2dw-d(w-1)) / 3
301             # = (2c-cw + 2dw-dw+d)) / 3
302             # = (2c+d + w(-c + 2d-d)) / 3
303             # = (2c+d + w(d-c)) / 3
304             #
305             # = (x-y+y + w(y - (x-y)/2)) / 3
306             # = (x + w((2y-x+y)/2)) / 3
307             # = (x + w((3y-x)/2)) / 3
308             # then
309             # xq = 2c+d
310             # = (2x + (3y-x)/2 ) / 3
311             # = (4x + 3y-x)/6
312             # = (3x+3y)/6
313             # = (x+y)/2
314             # yq = d = (3y-x)/6
315             #
316             # (-1+5w)(2-w) x=2*-1+5=3,y=5
317             # = -2+w+10w-5w^2
318             # = -2+11w-5(w-1)
319             # = -2+11w-5w+5
320             # = 3+6w -> 1+2w
321             # c=2*-1+5=3 d=-1+5=4
322             # x=2*1+2=4 y=3
323             #
324             # (w+1)*(2-w)
325             # = 2w-w^2+2-w
326             # = 2w-(w-1)+2-w
327             # = 2w-w+1+2-w
328             # = 3 -> 1 x=2
329             #
330             # 3w*(2-w) x=3,y=3 div x=3,y(3+3)/2=3
331             # = 6w-3w^2
332             # = 6w-3(w-1)
333             # = 6w-3w+3
334             # = 3w+3 -> w+1 x=3,y=1
335             #
336             # (w+1)(w+1)
337             # = w^2+2w+1
338             # = w-1+2w+1
339             # = 3w
340             #
341              
342             #
343             # x=3,y=3 (x+y)/2=3
344              
345             # X=-3 -2 -1 0 1 2 3
346             my @yx_to_arm = ([9, 9, 9, 4, 9, 9, 9], # Y=-2
347             [3, 9, 9, 9, 9, 9, 5], # Y=-1
348             [9, 9, 9, 9, 9, 9, 9], # Y=0
349             [2, 9, 9, 9, 9, 9, 0], # Y=1
350             [9, 9, 9, 1, 9, 9, 9], # Y= 2
351             );
352              
353             # my @yx_to_dxdy = (undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
354             # 1,1, 0,0, -1,-1, -2,0, 0,0, 2,0,
355             # undef,undef, 1,-1, undef,undef, -1,1, undef,undef, 0,0,
356             # 0,0, 2,0, 1,1, 0,0, -1,-1, -2,0,
357             # undef,undef, 0,0, undef,undef, 1,-1, undef,undef, -1,1,
358             # -1,-1, -2,0, 0,0, 2,0, 1,1, 0,0,
359             # );
360              
361             my @yx_to_dxdy # 12 each row
362             = (undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef,
363             0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef,
364             undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
365             undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0,
366             0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef,
367             undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef,
368             undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0,
369             undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
370             undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef,
371             0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef,
372             undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
373             undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0,
374             0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef,
375             undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef,
376             undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0,
377             undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
378             undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef,
379             0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef,
380             undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
381             undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0,
382             0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef,
383             undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef,
384             undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0,
385             undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1,
386             );
387              
388             my @x_to_digit = (1, 2, 0); # digit = X+1 mod 3
389              
390             sub xy_to_n {
391 18     18 1 954 my ($self, $x, $y) = @_;
392             ### TerdragonMidpoint xy_to_n(): "$x, $y"
393              
394 18         46 $x = round_nearest($x);
395 18         37 $y = round_nearest($y);
396              
397 18 50       40 if (is_infinite($x)) {
398 0         0 return $x; # infinity
399             }
400 18 50       45 if (is_infinite($y)) {
401 0         0 return $y; # infinity
402             }
403 18         34 my $zero = ($x * 0 * $y); # inherit bignum 0
404 18         28 my @ndigits; # low to high;
405              
406 18         34 for (;;) {
407 18         32 my $digit = $x_to_digit[$x%3];
408              
409 18         32 my $k = 2*(12*($y%12) + ($x%12));
410 18         31 my $dx = $yx_to_dxdy[$k++];
411 18 100       37 if (! defined $dx) {
412             ### not a visited point: "k=$k"
413             ### x mod 12: $x%12
414             ### y mod 12: $y%12
415 12         26 return undef;
416             }
417              
418             ### at: "$x,$y (k=$k) digit=$digit k=$k offset=$yx_to_dxdy[$k-1],$yx_to_dxdy[$k] to ".($x+$yx_to_dxdy[$k-1]).",".($y+$yx_to_dxdy[$k])
419              
420 6         13 push @ndigits, $digit;
421 6         8 $x += $dx;
422 6         12 $y += $yx_to_dxdy[$k];
423              
424 6 50 33     34 last if ($x <= 3 && $x >= -3 && $y <= 2 && $y >= -2);
      33        
      33        
425              
426             ### assert: ($x+$y) % 2 == 0
427             ### assert: $x % 3 == 0
428             ### assert: (3 * $y - $x) % 6 == 0
429 0         0 ($x,$y) = (($x+$y)/2, # divide w+1
430             ($y-$x/3)/2);
431             ### divide down to: "$x,$y"
432             }
433              
434             ### final: "xy=$x,$y"
435              
436 6   100     21 my $arm = $yx_to_arm[$y+2][$x+3] || 0; # 0 to 5
437             ### $arm
438              
439 6         19 my $arms_count = $self->arms_count;
440 6 100       14 if ($arm >= $arms_count) {
441 3         8 return undef;
442             }
443 3 50       7 if ($arm & 1) {
444             ### flip ...
445 0         0 @ndigits = map {2-$_} @ndigits;
  0         0  
446             }
447              
448 3         11 return digit_join_lowtohigh(\@ndigits, 3, $zero) * $arms_count + $arm;
449             }
450              
451             # quarter size of TerdragonCurve
452             #
453             # not exact
454             sub rect_to_n_range {
455 0     0 1 0 my ($self, $x1,$y1, $x2,$y2) = @_;
456             ### TerdragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2"
457 0         0 my $xmax = int(max(abs($x1),abs($x2)));
458 0         0 my $ymax = int(max(abs($y1),abs($y2)));
459             return (0,
460             int (($xmax*$xmax + 3*$ymax*$ymax + 1)
461             / 2)
462 0         0 * $self->{'arms'});
463             }
464              
465             #-----------------------------------------------------------------------------
466             # level_to_n_range()
467              
468             # 3^level segments, one midpoint each
469             # arms*3^level when multi-arm
470             # numbered starting 0
471             #
472             sub level_to_n_range {
473 7     7 1 504 my ($self, $level) = @_;
474 7         23 return (0, 3**$level * $self->{'arms'} - 1);
475             }
476             sub n_to_level {
477 0     0 1   my ($self, $n) = @_;
478 0 0         if ($n < 0) { return undef; }
  0            
479 0 0         if (is_infinite($n)) { return $n; }
  0            
480 0           $n = round_nearest($n);
481 0           _divrem_mutate ($n, $self->{'arms'});
482 0           my ($pow, $exp) = round_up_pow ($n+1, 3);
483 0           return $exp;
484             }
485              
486             #-----------------------------------------------------------------------------
487             1;
488             __END__