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   10048 use 5.004;
  5         25  
21 5     5   26 use strict;
  5         11  
  5         131  
22 5     5   29 use Carp 'croak';
  5         11  
  5         376  
23              
24             #use List::Util 'max';
25             *max = \&Math::PlanePath::_max;
26              
27 5     5   34 use vars '$VERSION', '@ISA';
  5         19  
  5         350  
28             $VERSION = 129;
29 5     5   738 use Math::PlanePath;
  5         11  
  5         247  
30             @ISA = ('Math::PlanePath');
31              
32             use Math::PlanePath::Base::Generic
33 5         261 'is_infinite',
34 5     5   35 'round_nearest';
  5         11  
35             use Math::PlanePath::Base::Digits
36 5         487 'round_down_pow',
37             'round_up_pow',
38 5     5   574 'digit_split_lowtohigh';
  5         12  
39              
40             # uncomment this to run the ### lines
41             #use Smart::Comments;
42              
43              
44             # Note: shared by SierpinskiArrowheadCentres
45 5         363 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   36 use constant n_start => 0;
  5         10  
  5         278  
57 5     5   44 use constant class_y_negative => 0;
  5         11  
  5         858  
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 120 my ($self) = @_;
66 2         28 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   45 use constant sumxy_minimum => 0; # triangular X>=-Y
  5         11  
  5         302  
81 5     5   1926 use Math::PlanePath::SierpinskiTriangle;
  5         11  
  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   66 use constant dy_minimum => -1;
  5         14  
  5         270  
94 5     5   29 use constant dy_maximum => 1;
  5         11  
  5         830  
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   38 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 15;
  5         71  
  5         2057  
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   41 use constant turn_any_straight => 0; # never straight
  5         19  
  5         5425  
197              
198              
199             #------------------------------------------------------------------------------
200              
201             # Note: shared by SierpinskiArrowheadCentres
202             sub new {
203 17     17 1 4354 my $self = shift->SUPER::new(@_);
204 17   100     97 my $align = ($self->{'align'} ||= 'triangular');
205 17 50       56 if (! exists $x_negative{$align}) {
206 0         0 croak "Unrecognised align option: ", $align;
207             }
208 17         42 return $self;
209             }
210              
211             sub n_to_xy {
212 6189     6189 1 19285 my ($self, $n) = @_;
213             ### SierpinskiArrowhead n_to_xy(): $n
214 6189 50       12536 if ($n < 0) {
215 0         0 return;
216             }
217 6189 50       12311 if (is_infinite($n)) {
218 0         0 return ($n,$n);
219             }
220              
221 6189         11450 my $x = int($n);
222 6189         8944 my $y = $n - $x; # fraction part
223 6189         9593 $n = $x;
224 6189         8881 $x = $y;
225              
226 6189 100       12508 if (my @digits = digit_split_lowtohigh($n,3)) {
227 6185         9432 my $len = 1;
228 6185         8935 for (;;) {
229 18861         27070 my $digit = shift @digits; # low to high
230              
231             ### odd right: "$x,$y len=$len"
232             ### $digit
233 18861 100       33396 if ($digit == 0) {
    100          
234              
235             } elsif ($digit == 1) {
236 7242         10369 $x = $len - $x; # mirror and offset
237 7242         9642 $y += $len;
238              
239             } else {
240 6059         12885 ($x,$y) = (($x+3*$y)/-2, # rotate +120
241             ($x-$y)/2 + 2*$len);
242             }
243              
244 18861 100       33934 @digits || last;
245 16242         22842 $len *= 2;
246 16242         22971 $digit = shift @digits; # low to high
247              
248             ### odd left: "$x,$y len=$len"
249             ### $digit
250 16242 100       28454 if ($digit == 0) {
    100          
251              
252             } elsif ($digit == 1) {
253 6154         9188 $x = - $x - $len; # mirror and offset
254 6154         8293 $y += $len;
255              
256             } else {
257 5150         10818 ($x,$y) = ((3*$y-$x)/2, # rotate -120
258             ($x+$y)/-2 + 2*$len)
259             }
260              
261 16242 100       28846 @digits || last;
262 12676         18230 $len *= 2;
263             }
264             }
265              
266             ### final: "$x,$y"
267 6189 50       16922 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         20646 return ($x,$y);
275             }
276             }
277              
278             sub xy_to_n {
279 565     565 1 3569 my ($self, $x, $y) = @_;
280 565         1006 $x = round_nearest ($x);
281 565         1064 $y = round_nearest ($y);
282             ### SierpinskiArrowhead xy_to_n(): "$x, $y"
283              
284 565 50       1120 if ($y < 0) {
285 0         0 return undef;
286             }
287              
288 565 50       1171 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       1043 if (($x%2) != ($y%2)) {
296 280         515 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         615 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       568 if (is_infinite($level)) {
325 0         0 return $level;
326             }
327              
328 285         489 my $n = 0;
329 285         535 while ($level-- >= 0) {
330             ### at: "$x,$y level=$level len=$len"
331 636         839 $n *= 3;
332              
333 636 100 66     2312 if ($y < 0 || $x < -$y || $x > $y) {
      100        
334             ### out of range
335 158         340 return undef;
336             }
337 478 100 100     1223 if ($y < $len + !($x==$y||$x==-$y)) {
338             ### digit 0, first triangle, no change
339              
340             } else {
341 387 100       669 if ($level & 1) {
342             ### odd level
343 191 100       298 if ($x > 0) {
344             ### digit 1, right triangle
345 87         114 $n += 1;
346 87         155 $y -= $len;
347 87         128 $x = - ($x-$len);
348             ### shift right and mirror to: "$x,$y"
349             } else {
350             ### digit 2, left triangle
351 104         146 $n += 2;
352 104         161 $y -= 2*$len;
353             ### shift down to: "$x,$y"
354 104         207 ($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       358 if ($x < 0) {
361             ### digit 1, left triangle
362 95         125 $n += 1;
363 95         125 $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         135 $n += 2;
369 101         142 $y -= 2*$len;
370             ### shift down to: "$x,$y"
371 101         214 ($x,$y) = (($x+3*$y)/-2, # rotate +120
372             ($x-$y)/2);
373             ### now: "$x,$y"
374             }
375             }
376             }
377              
378 478         904 $len /= 2;
379             }
380              
381 127 100 66     364 if ($x == 0 && $y == 0) {
382 86         196 return $n;
383             } else {
384 41         94 return undef;
385             }
386             }
387              
388             # not exact
389             sub rect_to_n_range {
390 8     8 1 503 my ($self, $x1,$y1, $x2,$y2) = @_;
391             ### SierpinskiArrowhead rect_to_n_range() ...
392              
393 8         21 $y1 = round_nearest ($y1);
394 8         15 $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       19 if ($self->{'align'} eq 'diagonal') {
398 0         0 $y2 += max (round_nearest ($x1),
399             round_nearest ($x2));
400             }
401              
402 8 50       17 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         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 332 my ($self, $level) = @_;
418 4         11 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__