File Coverage

blib/lib/Math/PlanePath/SierpinskiArrowhead.pm
Criterion Covered Total %
statement 123 173 71.1
branch 41 74 55.4
condition 12 14 85.7
subroutine 22 34 64.7
pod 17 17 100.0
total 215 312 68.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             package Math::PlanePath::SierpinskiArrowhead;
20 5     5   9108 use 5.004;
  5         26  
21 5     5   25 use strict;
  5         10  
  5         148  
22 5     5   38 use Carp 'croak';
  5         9  
  5         348  
23              
24             #use List::Util 'max';
25             *max = \&Math::PlanePath::_max;
26              
27 5     5   35 use vars '$VERSION', '@ISA';
  5         10  
  5         342  
28             $VERSION = 128;
29 5     5   670 use Math::PlanePath;
  5         9  
  5         216  
30             @ISA = ('Math::PlanePath');
31              
32             use Math::PlanePath::Base::Generic
33 5         239 'is_infinite',
34 5     5   32 'round_nearest';
  5         9  
35             use Math::PlanePath::Base::Digits
36 5         462 'round_down_pow',
37             'round_up_pow',
38 5     5   463 'digit_split_lowtohigh';
  5         11  
39              
40             # uncomment this to run the ### lines
41             #use Smart::Comments;
42              
43              
44             # Note: shared by SierpinskiArrowheadCentres
45 5         293 use constant parameter_info_array =>
46             [ { name => 'align',
47             share_key => 'align_trld',
48             display => 'Align',
49             type => 'enum',
50             default => 'triangular',
51             choices => ['triangular','right','left','diagonal'],
52             choices_display => ['Triangular','Right','Left','Diagonal'],
53             },
54 5     5   35 ];
  5         9  
55              
56 5     5   29 use constant n_start => 0;
  5         8  
  5         256  
57 5     5   30 use constant class_y_negative => 0;
  5         10  
  5         800  
58              
59             my %x_negative = (triangular => 1,
60             left => 1,
61             right => 0,
62             diagonal => 0);
63             # Note: shared by SierpinskiArrowheadCentres
64             sub x_negative {
65 2     2 1 116 my ($self) = @_;
66 2         31 return $x_negative{$self->{'align'}};
67             }
68             {
69             my %x_negative_at_n = (triangular => 3,
70             # right => undef,
71             left => 2,
72             # diagonal => undef,
73             );
74             sub x_negative_at_n {
75 0     0 1 0 my ($self) = @_;
76 0         0 return $x_negative_at_n{$self->{'align'}};
77             }
78             }
79              
80 5     5   55 use constant sumxy_minimum => 0; # triangular X>=-Y
  5         11  
  5         260  
81 5     5   1744 use Math::PlanePath::SierpinskiTriangle;
  5         12  
  5         617  
82             *x_maximum = \&Math::PlanePath::SierpinskiTriangle::x_maximum;
83             *diffxy_maximum = \&Math::PlanePath::SierpinskiTriangle::diffxy_maximum;
84              
85             sub dx_minimum {
86 0     0 1 0 my ($self) = @_;
87 0 0       0 return ($self->{'align'} eq 'triangular' ? -2 : -1);
88             }
89             sub dx_maximum {
90 0     0 1 0 my ($self) = @_;
91 0 0       0 return ($self->{'align'} eq 'triangular' ? 2 : 1);
92             }
93 5     5   37 use constant dy_minimum => -1;
  5         9  
  5         271  
94 5     5   32 use constant dy_maximum => 1;
  5         9  
  5         846  
95              
96             {
97             my %_UNDOCUMENTED__dxdy_list
98             = (triangular => [ 2,0, # E N=4 six directions
99             1,1, # NE N=0
100             -1,1, # NW N=2
101             -2,0, # W N=1
102             -1,-1, # SW N=15
103             1,-1, # SE N=6
104             ],
105             right => [ 1,0, # E N=4
106             1,1, # NE N=0
107             0,1, # N N=2
108             -1,0, # W N=1
109             -1,-1, # SW N=15
110             0,-1, # S N=6
111             ],
112             left => [ 1,0, # E N=4
113             0,1, # N N=0
114             -1,1, # NW N=2
115             -1,0, # W N=1
116             0,-1, # S N=15
117             1,-1, # SE N=6
118             ],
119             diagonal => [ 1,0, # E N=0
120             0,1, # N N=2
121             -1,1, # NW N=1
122             -1,0, # W N=15
123             0,-1, # S N=6
124             1,-1, # SE N=4
125             ],
126             );
127             sub _UNDOCUMENTED__dxdy_list {
128 0     0   0 my ($self) = @_;
129 0         0 return @{$_UNDOCUMENTED__dxdy_list{$self->{'align'}}};
  0         0  
130             }
131             }
132 5     5   35 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 15;
  5         70  
  5         1951  
133              
134             sub absdx_minimum {
135 0     0 1 0 my ($self) = @_;
136 0 0       0 return ($self->{'align'} eq 'triangular' ? 1 : 0);
137             }
138             sub absdx_maximum {
139 0     0 1 0 my ($self) = @_;
140 0 0       0 return ($self->{'align'} eq 'triangular' ? 2 : 1);
141             }
142              
143             {
144             my %dsumxy_minimum = (triangular => -2,
145             left => -1,
146             right => -2,
147             diagonal => -1,
148             );
149             sub dsumxy_minimum {
150 0     0 1 0 my ($self) = @_;
151 0         0 return $dsumxy_minimum{$self->{'align'}};
152             }
153             }
154             {
155             my %dsumxy_maximum = (triangular => 2,
156             left => 1,
157             right => 2,
158             diagonal => 1,
159             );
160             sub dsumxy_maximum {
161 0     0 1 0 my ($self) = @_;
162 0         0 return $dsumxy_maximum{$self->{'align'}};
163             }
164             }
165              
166             {
167             my %ddiffxy_minimum = (triangular => -2,
168             left => -2,
169             right => -1,
170             diagonal => -2,
171             );
172             sub ddiffxy_minimum {
173 0     0 1 0 my ($self) = @_;
174 0         0 return $ddiffxy_minimum{$self->{'align'}};
175             }
176             }
177             {
178             my %ddiffxy_maximum = (triangular => 2,
179             left => 2,
180             right => 1,
181             diagonal => 2,
182             );
183             sub ddiffxy_maximum {
184 0     0 1 0 my ($self) = @_;
185 0         0 return $ddiffxy_maximum{$self->{'align'}};
186             }
187             }
188              
189             sub dir_maximum_dxdy {
190 0     0 1 0 my ($self) = @_;
191 0 0       0 return ($self->{'align'} eq 'right'
192             ? (0,-1) # South
193             : (1,-1)); # South-East
194             }
195              
196 5     5   37 use constant turn_any_straight => 0; # never straight
  5         25  
  5         5376  
197              
198              
199             #------------------------------------------------------------------------------
200              
201             # Note: shared by SierpinskiArrowheadCentres
202             sub new {
203 17     17 1 4070 my $self = shift->SUPER::new(@_);
204 17   100     95 my $align = ($self->{'align'} ||= 'triangular');
205 17 50       51 if (! exists $x_negative{$align}) {
206 0         0 croak "Unrecognised align option: ", $align;
207             }
208 17         46 return $self;
209             }
210              
211             sub n_to_xy {
212 6189     6189 1 19211 my ($self, $n) = @_;
213             ### SierpinskiArrowhead n_to_xy(): $n
214 6189 50       11862 if ($n < 0) {
215 0         0 return;
216             }
217 6189 50       11803 if (is_infinite($n)) {
218 0         0 return ($n,$n);
219             }
220              
221 6189         11478 my $x = int($n);
222 6189         8875 my $y = $n - $x; # fraction part
223 6189         8654 $n = $x;
224 6189         8387 $x = $y;
225              
226 6189 100       12810 if (my @digits = digit_split_lowtohigh($n,3)) {
227 6185         9315 my $len = 1;
228 6185         8161 for (;;) {
229 18830         26736 my $digit = shift @digits; # low to high
230              
231             ### odd right: "$x,$y len=$len"
232             ### $digit
233 18830 100       33144 if ($digit == 0) {
    100          
234              
235             } elsif ($digit == 1) {
236 7216         9968 $x = $len - $x; # mirror and offset
237 7216         9277 $y += $len;
238              
239             } else {
240 6049         12967 ($x,$y) = (($x+3*$y)/-2, # rotate +120
241             ($x-$y)/2 + 2*$len);
242             }
243              
244 18830 100       32868 @digits || last;
245 16193         22326 $len *= 2;
246 16193         21716 $digit = shift @digits; # low to high
247              
248             ### odd left: "$x,$y len=$len"
249             ### $digit
250 16193 100       27797 if ($digit == 0) {
    100          
251              
252             } elsif ($digit == 1) {
253 6145         8752 $x = - $x - $len; # mirror and offset
254 6145         8421 $y += $len;
255              
256             } else {
257 5070         10501 ($x,$y) = ((3*$y-$x)/2, # rotate -120
258             ($x+$y)/-2 + 2*$len)
259             }
260              
261 16193 100       27421 @digits || last;
262 12645         18020 $len *= 2;
263             }
264             }
265              
266             ### final: "$x,$y"
267 6189 50       16581 if ($self->{'align'} eq 'right') {
    50          
    50          
268 0         0 return (($x+$y)/2, $y);
269             } elsif ($self->{'align'} eq 'left') {
270 0         0 return (($x-$y)/2, $y);
271             } elsif ($self->{'align'} eq 'diagonal') {
272 0         0 return (($x+$y)/2, ($y-$x)/2);
273             } else { # triangular
274 6189         19231 return ($x,$y);
275             }
276             }
277              
278             sub xy_to_n {
279 565     565 1 3680 my ($self, $x, $y) = @_;
280 565         1064 $x = round_nearest ($x);
281 565         1128 $y = round_nearest ($y);
282             ### SierpinskiArrowhead xy_to_n(): "$x, $y"
283              
284 565 50       1139 if ($y < 0) {
285 0         0 return undef;
286             }
287              
288 565 50       1201 if ($self->{'align'} eq 'left') {
    50          
289 0 0       0 if ($x > 0) {
290 0         0 return undef;
291             }
292 0         0 $x = 2*$x + $y; # adjust to triangular style
293              
294             } elsif ($self->{'align'} eq 'triangular') {
295 565 100       1104 if (($x%2) != ($y%2)) {
296 280         518 return undef;
297             }
298              
299             } else {
300             # right or diagonal
301 0 0       0 if ($x < 0) {
302 0         0 return undef;
303             }
304 0 0       0 if ($self->{'align'} eq 'right') {
305 0         0 $x = 2*$x - $y;
306             } else { # diagonal
307 0         0 ($x,$y) = ($x-$y, $x+$y);
308             }
309             }
310             ### adjusted xy: "$x,$y"
311              
312             # On row Y=2^k the points belong to belong in the level below except for
313             # the endmost X=Y or X=-Y. For example Y=4 has N=6 which is in the level
314             # below, but at the end has N=9 belongs to the level above. So $y-1 puts
315             # Y=2^k into the level below and +($y==abs($x)) pushes the end back up to
316             # the next.
317             #
318 285         663 my ($len, $level) = round_down_pow ($y-1 + ($y==abs($x)),
319             2);
320             ### pow2 round down: $y-1+($y==abs($x))
321             ### $len
322             ### $level
323              
324 285 50       612 if (is_infinite($level)) {
325 0         0 return $level;
326             }
327              
328 285         507 my $n = 0;
329 285         545 while ($level-- >= 0) {
330             ### at: "$x,$y level=$level len=$len"
331 636         916 $n *= 3;
332              
333 636 100 66     2283 if ($y < 0 || $x < -$y || $x > $y) {
      100        
334             ### out of range
335 158         326 return undef;
336             }
337 478 100 100     1205 if ($y < $len + !($x==$y||$x==-$y)) {
338             ### digit 0, first triangle, no change
339              
340             } else {
341 387 100       630 if ($level & 1) {
342             ### odd level
343 191 100       302 if ($x > 0) {
344             ### digit 1, right triangle
345 87         112 $n += 1;
346 87         121 $y -= $len;
347 87         137 $x = - ($x-$len);
348             ### shift right and mirror to: "$x,$y"
349             } else {
350             ### digit 2, left triangle
351 104         150 $n += 2;
352 104         140 $y -= 2*$len;
353             ### shift down to: "$x,$y"
354 104         211 ($x,$y) = ((3*$y-$x)/2, # rotate -120
355             ($x+$y)/-2);
356             ### rotate to: "$x,$y"
357             }
358             } else {
359             ### even level
360 196 100       323 if ($x < 0) {
361             ### digit 1, left triangle
362 95         136 $n += 1;
363 95         123 $y -= $len;
364 95         155 $x = - ($x+$len);
365             ### shift right and mirror to: "$x,$y"
366             } else {
367             ### digit 2, right triangle
368 101         137 $n += 2;
369 101         155 $y -= 2*$len;
370             ### shift down to: "$x,$y"
371 101         215 ($x,$y) = (($x+3*$y)/-2, # rotate +120
372             ($x-$y)/2);
373             ### now: "$x,$y"
374             }
375             }
376             }
377              
378 478         896 $len /= 2;
379             }
380              
381 127 100 66     345 if ($x == 0 && $y == 0) {
382 86         191 return $n;
383             } else {
384 41         90 return undef;
385             }
386             }
387              
388             # not exact
389             sub rect_to_n_range {
390 8     8 1 531 my ($self, $x1,$y1, $x2,$y2) = @_;
391             ### SierpinskiArrowhead rect_to_n_range() ...
392              
393 8         19 $y1 = round_nearest ($y1);
394 8         18 $y2 = round_nearest ($y2);
395 8 50       20 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2
  0         0  
396              
397 8 50       21 if ($self->{'align'} eq 'diagonal') {
398 0         0 $y2 += max (round_nearest ($x1),
399             round_nearest ($x2));
400             }
401              
402 8 50       19 unless ($y2 >= 0) {
403             ### rect all negative, no N ...
404 0         0 return (1, 0);
405             }
406              
407 8         21 my ($pow,$exp) = round_down_pow ($y2-1, 2);
408             ### $y2
409             ### $level
410 8         21 return (0, 3**($exp+1));
411             }
412              
413             #-----------------------------------------------------------------------------
414             # level_to_n_range()
415              
416             sub level_to_n_range {
417 4     4 1 315 my ($self, $level) = @_;
418 4         10 return (0, 3**$level);
419             }
420             sub n_to_level {
421 0     0 1   my ($self, $n) = @_;
422 0 0         if ($n < 0) { return undef; }
  0            
423 0 0         if (is_infinite($n)) { return $n; }
  0            
424 0           $n = round_nearest($n);
425 0           my ($pow, $exp) = round_up_pow ($n, 3);
426 0           return $exp;
427             }
428              
429             #-----------------------------------------------------------------------------
430             1;
431             __END__