File Coverage

blib/lib/Math/PlanePath/GosperSide.pm
Criterion Covered Total %
statement 115 131 87.7
branch 9 20 45.0
condition 1 2 50.0
subroutine 30 30 100.0
pod 4 4 100.0
total 159 187 85.0


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 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 it
6             # under the terms of the GNU General Public License as published by the Free
7             # 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             # arms begin at 0,0 or at 1 in ?
20              
21              
22             # math-image --path=GosperSide --lines --scale=10
23             # math-image --path=GosperSide --output=numbers
24              
25              
26             package Math::PlanePath::GosperSide;
27 1     1   9085 use 5.004;
  1         10  
28 1     1   6 use strict;
  1         2  
  1         47  
29 1     1   8 use List::Util 'min','max';
  1         1  
  1         133  
30 1     1   496 use POSIX 'ceil';
  1         8000  
  1         5  
31 1     1   1937 use Math::PlanePath::GosperIslands;
  1         3  
  1         34  
32 1     1   7 use Math::PlanePath::SacksSpiral;
  1         2  
  1         24  
33              
34 1     1   17 use vars '$VERSION', '@ISA', '@_xend','@_yend';
  1         2  
  1         66  
35             $VERSION = 128;
36 1     1   6 use Math::PlanePath;
  1         2  
  1         41  
37             @ISA = ('Math::PlanePath');
38             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
39              
40             use Math::PlanePath::Base::Generic
41 1         47 'is_infinite',
42 1     1   5 'round_nearest';
  1         3  
43             use Math::PlanePath::Base::Digits
44 1     1   6 'digit_split_lowtohigh';
  1         2  
  1         36  
45              
46             # uncomment this to run the ### lines
47             #use Devel::Comments;
48              
49 1     1   5 use constant n_start => 0;
  1         2  
  1         86  
50              
51             # secret experimental as yet ...
52             #
53             # use constant parameter_info_array => [ { name => 'arms',
54             # share_key => 'arms_6',
55             # type => 'integer',
56             # minimum => 1,
57             # maximum => 6,
58             # default => 1,
59             # width => 1,
60             # description => 'Arms',
61             # } ];
62              
63 1     1   8 use constant x_negative_at_n => 113;
  1         2  
  1         49  
64 1     1   6 use constant y_negative_at_n => 11357;
  1         2  
  1         54  
65              
66 1     1   7 use constant dx_minimum => -2;
  1         2  
  1         53  
67 1     1   8 use constant dx_maximum => 2;
  1         2  
  1         46  
68 1     1   6 use constant dy_minimum => -1;
  1         1  
  1         49  
69 1     1   6 use constant dy_maximum => 1;
  1         3  
  1         90  
70              
71             *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six;
72             # 2,0, # E N=0
73             # 1,1, # NE N=1
74             # -1,1, # NW N=4
75             # -2,0, # W N=13
76             # -1,-1, # SW N=40
77             # 1,-1, # SE N=121
78 1     1   7 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 121;
  1         15  
  1         59  
79              
80 1     1   6 use constant absdx_minimum => 1;
  1         2  
  1         64  
81 1     1   7 use constant dsumxy_minimum => -2; # diagonals
  1         2  
  1         42  
82 1     1   5 use constant dsumxy_maximum => 2;
  1         2  
  1         49  
83 1     1   48 use constant ddiffxy_minimum => -2;
  1         2  
  1         80  
84 1     1   9 use constant ddiffxy_maximum => 2;
  1         3  
  1         51  
85 1     1   5 use constant dir_maximum_dxdy => (1,-1); # South-East
  1         2  
  1         63  
86 1     1   7 use constant turn_any_straight => 0; # never straight
  1         2  
  1         647  
87              
88              
89             #------------------------------------------------------------------------------
90              
91             sub new {
92 7     7 1 2436 my $self = shift->SUPER::new(@_);
93 7   50     60 $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1));
94 7         21 return $self;
95             }
96              
97             sub n_to_xy {
98 2514     2514 1 114837 my ($self, $n) = @_;
99             ### GosperSide n_to_xy(): $n
100 2514 50       4827 if ($n < 0) {
101 0         0 return;
102             }
103 2514 50       5217 if (is_infinite($n)) {
104 0         0 return ($n,$n);
105             }
106              
107 2514         4150 my $x;
108 2514         3711 my $y = my $yend = ($n * 0); # inherit bignum 0
109 2514         3443 my $xend = $y + 2; # inherit bignum 2
110             {
111 2514         3256 my $int = int($n);
  2514         3239  
112 2514         3581 $x = 2 * ($n - $int);
113 2514         3670 $n = $int;
114             }
115              
116              
117 2514 50       5222 if ((my $arms = $self->{'arms'}) > 1) {
118 0         0 my $rot = _divrem_mutate ($n, $arms);
119 0 0       0 if ($rot >= 3) {
120 0         0 $rot -= 3;
121 0         0 $x = -$x; # rotate 180, knowing y=0,yend=0
122 0         0 $xend = -2;
123             }
124 0 0       0 if ($rot == 1) {
    0          
125 0         0 $x = $y = $x/2; # rotate +60, knowing y=0,yend=0
126 0         0 $xend = $yend = $xend/2;
127             } elsif ($rot == 2) {
128 0         0 $y = $x/2; # rotate +120, knowing y=0,yend=0
129 0         0 $x = -$y;
130 0         0 $yend = $xend/2;
131 0         0 $xend = -$yend;
132             }
133             }
134              
135 2514         5305 foreach my $digit (digit_split_lowtohigh($n,3)) {
136 15492         22343 my $xend_offset = 3*($xend-$yend)/2; # end and end +60
137 15492         21650 my $yend_offset = ($xend+3*$yend)/2;
138              
139             ### at: "$x,$y"
140             ### $digit
141             ### $xend
142             ### $yend
143             ### $xend_offset
144             ### $yend_offset
145              
146 15492 100       26972 if ($digit == 1) {
    100          
147 5788         11987 ($x,$y) = (($x-3*$y)/2 + $xend, # rotate +60
148             ($x+$y)/2 + $yend);
149             } elsif ($digit == 2) {
150 4948         6717 $x += $xend_offset; # offset and offset +60
151 4948         6421 $y += $yend_offset;
152             }
153 15492         20684 $xend += $xend_offset; # offset and offset +60
154 15492         22672 $yend += $yend_offset;
155             }
156              
157             ### final: "$x,$y"
158 2514         6179 return ($x, $y);
159             }
160              
161             # level = (log(hypot) + log(2*.99)) * 1/log(sqrt(7))
162             # = (log(hypot^2)/2 + log(2*.99)) * 1/log(sqrt(7))
163             # = (log(hypot^2) + 2*log(2*.99)) * 1/(2*log(sqrt(7)))
164             #
165             sub xy_to_n {
166 510     510 1 2741 my ($self, $x, $y) = @_;
167 510         1078 $x = round_nearest ($x);
168 510         1015 $y = round_nearest ($y);
169             ### GosperSide xy_to_n(): "$x, $y"
170              
171 510 50       1318 if (($x ^ $y) & 1) {
172 0         0 return undef;
173             }
174              
175 510         889 my $h2 = $x*$x + $y*$y*3 + 1;
176 510         2280 my $level = max (0,
177             ceil ((log($h2) + 2*log(2*.99)) * (1/2*log(sqrt(7)))));
178 510 50       1063 if (is_infinite($level)) {
179 0         0 return $level;
180             }
181 510         1732 return Math::PlanePath::GosperIslands::_xy_to_n_in_level($x,$y,$level);
182             }
183              
184              
185             # Points beyond N=3^level only go a small distance back before that N
186             # hypotenuse.
187             # hypot = .99 * 2 * sqrt(7)^level
188             # sqrt(7)^level = hypot / (2*.99)
189             # sqrt(7)^level = hypot / (2*.99)
190             # level = log(hypot / (2*.99)) / log(sqrt(7))
191             # = (log(hypot) + log(2*.99)) * 1/log(sqrt(7))
192             #
193             # not exact
194             sub rect_to_n_range {
195 514     514 1 46224 my ($self, $x1,$y1, $x2,$y2) = @_;
196 514         1030 $y1 *= sqrt(3);
197 514         791 $y2 *= sqrt(3);
198 514         1519 my ($r_lo, $r_hi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range
199             ($x1,$y1, $x2,$y2);
200 514         2448 my $level = max (0,
201             ceil ((log($r_hi+.1) + log(2*.99)) * (1/log(sqrt(7)))));
202             return (0,
203 514         1675 $self->{'arms'} * 3 ** $level - 1);
204             }
205              
206             #------------------------------------------------------------------------------
207             # levels
208              
209 1     1   519 use Math::PlanePath::SierpinskiArrowhead;
  1         3  
  1         60  
210             *level_to_n_range = \&Math::PlanePath::SierpinskiArrowhead::level_to_n_range;
211             *n_to_level = \&Math::PlanePath::SierpinskiArrowhead::n_to_level;
212              
213             #------------------------------------------------------------------------------
214             1;
215             __END__