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 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   9940 use 5.004;
  5         24  
21 5     5   29 use strict;
  5         10  
  5         140  
22 5     5   28 use Carp 'croak';
  5         9  
  5         358  
23              
24             #use List::Util 'max';
25             *max = \&Math::PlanePath::_max;
26              
27 5     5   36 use vars '$VERSION', '@ISA';
  5         8  
  5         365  
28             $VERSION = 127;
29 5     5   785 use Math::PlanePath;
  5         11  
  5         220  
30             @ISA = ('Math::PlanePath');
31              
32             use Math::PlanePath::Base::Generic
33 5         268 'is_infinite',
34 5     5   31 'round_nearest';
  5         11  
35             use Math::PlanePath::Base::Digits
36 5         418 'round_down_pow',
37             'round_up_pow',
38 5     5   514 'digit_split_lowtohigh';
  5         10  
39              
40             # uncomment this to run the ### lines
41             #use Smart::Comments;
42              
43              
44             # Note: shared by SierpinskiArrowheadCentres
45 5         323 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   33 ];
  5         10  
55              
56 5     5   33 use constant n_start => 0;
  5         10  
  5         243  
57 5     5   29 use constant class_y_negative => 0;
  5         10  
  5         771  
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 125 my ($self) = @_;
66 2         11 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   36 use constant sumxy_minimum => 0; # triangular X>=-Y
  5         10  
  5         308  
81 5     5   1729 use Math::PlanePath::SierpinskiTriangle;
  5         21  
  5         656  
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         11  
  5         272  
94 5     5   31 use constant dy_maximum => 1;
  5         10  
  5         760  
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   36 use constant _UNDOCUMENTED__dxdy_list_at_n => 15;
  5         10  
  5         1885  
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         10  
  5         5269  
197              
198              
199             #------------------------------------------------------------------------------
200              
201             # Note: shared by SierpinskiArrowheadCentres
202             sub new {
203 17     17 1 5579 my $self = shift->SUPER::new(@_);
204 17   100     101 my $align = ($self->{'align'} ||= 'triangular');
205 17 50       55 if (! exists $x_negative{$align}) {
206 0         0 croak "Unrecognised align option: ", $align;
207             }
208 17         44 return $self;
209             }
210              
211             sub n_to_xy {
212 6189     6189 1 23076 my ($self, $n) = @_;
213             ### SierpinskiArrowhead n_to_xy(): $n
214 6189 50       11493 if ($n < 0) {
215 0         0 return;
216             }
217 6189 50       11654 if (is_infinite($n)) {
218 0         0 return ($n,$n);
219             }
220              
221 6189         11477 my $x = int($n);
222 6189         8814 my $y = $n - $x; # fraction part
223 6189         8569 $n = $x;
224 6189         8365 $x = $y;
225              
226 6189 100       11791 if (my @digits = digit_split_lowtohigh($n,3)) {
227 6185         9230 my $len = 1;
228 6185         8914 for (;;) {
229 18915         27278 my $digit = shift @digits; # low to high
230              
231             ### odd right: "$x,$y len=$len"
232             ### $digit
233 18915 100       34270 if ($digit == 0) {
    100          
234              
235             } elsif ($digit == 1) {
236 7248         9840 $x = $len - $x; # mirror and offset
237 7248         10355 $y += $len;
238              
239             } else {
240 6105         12931 ($x,$y) = (($x+3*$y)/-2, # rotate +120
241             ($x-$y)/2 + 2*$len);
242             }
243              
244 18915 100       33852 @digits || last;
245 16298         22353 $len *= 2;
246 16298         22148 $digit = shift @digits; # low to high
247              
248             ### odd left: "$x,$y len=$len"
249             ### $digit
250 16298 100       28801 if ($digit == 0) {
    100          
251              
252             } elsif ($digit == 1) {
253 6193         9254 $x = - $x - $len; # mirror and offset
254 6193         8409 $y += $len;
255              
256             } else {
257 5118         10701 ($x,$y) = ((3*$y-$x)/2, # rotate -120
258             ($x+$y)/-2 + 2*$len)
259             }
260              
261 16298 100       29734 @digits || last;
262 12730         18114 $len *= 2;
263             }
264             }
265              
266             ### final: "$x,$y"
267 6189 50       17138 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         19324 return ($x,$y);
275             }
276             }
277              
278             sub xy_to_n {
279 565     565 1 4284 my ($self, $x, $y) = @_;
280 565         1015 $x = round_nearest ($x);
281 565         1087 $y = round_nearest ($y);
282             ### SierpinskiArrowhead xy_to_n(): "$x, $y"
283              
284 565 50       1072 if ($y < 0) {
285 0         0 return undef;
286             }
287              
288 565 50       1311 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       1048 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         697 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       576 if (is_infinite($level)) {
325 0         0 return $level;
326             }
327              
328 285         516 my $n = 0;
329 285         505 while ($level-- >= 0) {
330             ### at: "$x,$y level=$level len=$len"
331 636         828 $n *= 3;
332              
333 636 100 66     2330 if ($y < 0 || $x < -$y || $x > $y) {
      100        
334             ### out of range
335 158         342 return undef;
336             }
337 478 100 100     1196 if ($y < $len + !($x==$y||$x==-$y)) {
338             ### digit 0, first triangle, no change
339              
340             } else {
341 387 100       661 if ($level & 1) {
342             ### odd level
343 191 100       284 if ($x > 0) {
344             ### digit 1, right triangle
345 87         121 $n += 1;
346 87         150 $y -= $len;
347 87         130 $x = - ($x-$len);
348             ### shift right and mirror to: "$x,$y"
349             } else {
350             ### digit 2, left triangle
351 104         148 $n += 2;
352 104         155 $y -= 2*$len;
353             ### shift down to: "$x,$y"
354 104         217 ($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       341 if ($x < 0) {
361             ### digit 1, left triangle
362 95         129 $n += 1;
363 95         134 $y -= $len;
364 95         148 $x = - ($x+$len);
365             ### shift right and mirror to: "$x,$y"
366             } else {
367             ### digit 2, right triangle
368 101         136 $n += 2;
369 101         156 $y -= 2*$len;
370             ### shift down to: "$x,$y"
371 101         207 ($x,$y) = (($x+3*$y)/-2, # rotate +120
372             ($x-$y)/2);
373             ### now: "$x,$y"
374             }
375             }
376             }
377              
378 478         907 $len /= 2;
379             }
380              
381 127 100 66     361 if ($x == 0 && $y == 0) {
382 86         309 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 1720 my ($self, $x1,$y1, $x2,$y2) = @_;
391             ### SierpinskiArrowhead rect_to_n_range() ...
392              
393 8         22 $y1 = round_nearest ($y1);
394 8         19 $y2 = round_nearest ($y2);
395 8 50       19 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2
  0         0  
396              
397 8 50       22 if ($self->{'align'} eq 'diagonal') {
398 0         0 $y2 += max (round_nearest ($x1),
399             round_nearest ($x2));
400             }
401              
402 8 50       20 unless ($y2 >= 0) {
403             ### rect all negative, no N ...
404 0         0 return (1, 0);
405             }
406              
407 8         23 my ($pow,$exp) = round_down_pow ($y2-1, 2);
408             ### $y2
409             ### $level
410 8         22 return (0, 3**($exp+1));
411             }
412              
413             #-----------------------------------------------------------------------------
414             # level_to_n_range()
415              
416             sub level_to_n_range {
417 4     4 1 1289 my ($self, $level) = @_;
418 4         14 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__