File Coverage

blib/lib/Math/PlanePath/HilbertCurve.pm
Criterion Covered Total %
statement 102 119 85.7
branch 47 58 81.0
condition 6 10 60.0
subroutine 13 15 86.6
pod 5 5 100.0
total 173 207 83.5


line stmt bran cond sub pod time code
1             # Copyright 2010, 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             # http://www.cut-the-knot.org/do_you_know/hilbert.shtml
20             # Java applet
21             #
22             # http://www.woollythoughts.com/afghans/peano.html
23             # Knitting
24             #
25             # http://www.geom.uiuc.edu/docs/reference/CRC-formulas/node36.html
26             # Closed path, curved parts
27             #
28             # http://www.wolframalpha.com/entities/calculators/Peano_curve/jh/4o/im/
29             # Curved corners tilted to a diamond, or is it an 8-step pattern?
30             #
31             # http://www.davidsalomon.name/DC2advertis/AppendC.pdf
32             #
33              
34             package Math::PlanePath::HilbertCurve;
35 5     5   1736 use 5.004;
  5         24  
36 5     5   31 use strict;
  5         8  
  5         214  
37             #use List::Util 'max','min';
38             *max = \&Math::PlanePath::_max;
39              
40 5     5   26 use vars '$VERSION', '@ISA';
  5         9  
  5         322  
41             $VERSION = 127;
42 5     5   1235 use Math::PlanePath;
  5         11  
  5         160  
43 5     5   1263 use Math::PlanePath::Base::NSEW;
  5         8  
  5         174  
44             @ISA = ('Math::PlanePath::Base::NSEW',
45             'Math::PlanePath');
46              
47             use Math::PlanePath::Base::Generic
48 5         240 'is_infinite',
49 5     5   25 'round_nearest';
  5         8  
50             use Math::PlanePath::Base::Digits
51 5         281 'round_down_pow',
52             'round_up_pow',
53             'bit_split_lowtohigh',
54             'digit_split_lowtohigh',
55 5     5   888 'digit_join_lowtohigh';
  5         10  
56              
57             # uncomment this to run the ### lines
58             #use Smart::Comments;
59              
60              
61 5     5   28 use constant n_start => 0;
  5         8  
  5         226  
62 5     5   24 use constant class_x_negative => 0;
  5         8  
  5         200  
63 5     5   23 use constant class_y_negative => 0;
  5         18  
  5         4639  
64             *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1;
65              
66              
67             #------------------------------------------------------------------------------
68              
69             # state=0 3--2 plain
70             # |
71             # 0--1
72             #
73             # state=4 1--2 transpose
74             # | |
75             # 0 3
76             #
77             # state=8
78             #
79             # state=12 3 0 rot180 + transpose
80             # | |
81             # 2--1
82             #
83             # generated by tools/hilbert-curve-table.pl
84             my @next_state = (4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0);
85             my @digit_to_x = (0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0);
86             my @digit_to_y = (0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1);
87             my @yx_to_digit = (0,1,3,2, 0,3,1,2, 2,3,1,0, 2,1,3,0);
88             my @min_digit = (0,0,1,0, 0,1,3,2, 2,undef,undef,undef,
89             0,0,3,0, 0,2,1,1, 2,undef,undef,undef,
90             2,2,3,1, 0,0,1,0, 0,undef,undef,undef,
91             2,1,1,2, 0,0,3,0, 0);
92             my @max_digit = (0,1,1,3, 3,2,3,3, 2,undef,undef,undef,
93             0,3,3,1, 3,3,1,2, 2,undef,undef,undef,
94             2,3,3,2, 3,3,1,1, 0,undef,undef,undef,
95             2,2,1,3, 3,1,3,3, 0);
96              
97             sub n_to_xy {
98 128     128 1 10149 my ($self, $n) = @_;
99             ### HilbertCurve n_to_xy(): $n
100             ### hex: sprintf "%#X", $n
101              
102 128 50       271 if ($n < 0) { return; }
  0         0  
103 128 50       255 if (is_infinite($n)) { return ($n,$n); }
  0         0  
104              
105 128         220 my $int = int($n);
106 128         167 $n -= $int; # fraction part
107              
108 128         268 my @ndigits = digit_split_lowtohigh($int,4);
109 128 100       230 my $state = ($#ndigits & 1 ? 4 : 0);
110 128 100       210 my $dirstate = ($#ndigits & 1 ? 0 : 4); # default if all $ndigit==3
111              
112 128         171 my (@xbits, @ybits);
113 128         219 foreach my $i (reverse 0 .. $#ndigits) { # digits high to low
114 485         568 my $ndigit = $ndigits[$i];
115 485         546 $state += $ndigit;
116 485 100       676 if ($ndigit != 3) {
117 343         393 $dirstate = $state; # lowest non-3 digit
118             }
119              
120 485         626 $xbits[$i] = $digit_to_x[$state];
121 485         579 $ybits[$i] = $digit_to_y[$state];
122 485         614 $state = $next_state[$state];
123             }
124              
125 128         176 my $zero = ($int * 0); # inherit bigint 0
126 128         308 return ($n * ($digit_to_x[$dirstate+1] - $digit_to_x[$dirstate]) # frac
127             + digit_join_lowtohigh (\@xbits, 2, $zero),
128              
129             $n * ($digit_to_y[$dirstate+1] - $digit_to_y[$dirstate]) # frac
130             + digit_join_lowtohigh (\@ybits, 2, $zero));
131             }
132              
133             sub xy_to_n {
134 288     288 1 8339 my ($self, $x, $y) = @_;
135             ### HilbertCurve xy_to_n(): "$x, $y"
136              
137 288         497 $x = round_nearest ($x);
138 288 50       494 if (is_infinite($x)) { return $x; }
  0         0  
139 288         522 $y = round_nearest ($y);
140 288 50       468 if (is_infinite($y)) { return $y; }
  0         0  
141              
142 288 50 33     742 if ($x < 0 || $y < 0) {
143 0         0 return undef;
144             }
145              
146 288         501 my @xbits = bit_split_lowtohigh($x);
147 288         489 my @ybits = bit_split_lowtohigh($y);
148 288         755 my $numbits = max($#xbits,$#ybits);
149              
150 288         367 my @ndigits;
151 288 100       434 my $state = ($numbits & 1 ? 4 : 0);
152 288         478 foreach my $i (reverse 0 .. $numbits) { # high to low
153             ### at: "state=$state xbit=".($xbits[$i]||0)." ybit=".($ybits[$i]||0)
154 1674   100     4274 my $ndigit = $yx_to_digit[$state + 2*($ybits[$i]||0) + ($xbits[$i]||0)];
      100        
155 1674         2105 $ndigits[$i] = $ndigit;
156 1674         2306 $state = $next_state[$state+$ndigit];
157             }
158             ### @ndigits
159 288         669 return digit_join_lowtohigh(\@ndigits, 4,
160             $x * 0 * $y); # inherit bignum 0
161             }
162              
163              
164             # rect_to_n_range() finds the exact minimum/maximum N in the given rectangle.
165             #
166             # The strategy is similar to xy_to_n(), except that at each bit position
167             # instead of taking a bit of x,y from the input instead those bits are
168             # chosen from among the 4 sub-parts according to which has the maximum N and
169             # is within the given target rectangle. The final result is both an $n_max
170             # and a $x_max,$y_max which is its position, but only the $n_max is
171             # returned.
172             #
173             # At a given sub-part the comparisons ask whether x1 is above or below the
174             # midpoint, and likewise x2,y1,y2. Since x2>=x1 and y2>=y1 there's only 3
175             # combinations of x1>=cmp,x2>=cmp, not 4.
176              
177             # exact
178             sub rect_to_n_range {
179 78     78 1 1096 my ($self, $x1,$y1, $x2,$y2) = @_;
180             ### HilbertCurve rect_to_n_range(): "$x1,$y1, $x2,$y2"
181              
182 78         125 $x1 = round_nearest ($x1);
183 78         149 $y1 = round_nearest ($y1);
184 78         167 $x2 = round_nearest ($x2);
185 78         157 $y2 = round_nearest ($y2);
186 78 100       156 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
187 78 100       188 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
188              
189 78 50 33     215 if ($x2 < 0 || $y2 < 0) {
190 0         0 return (1, 0); # rectangle outside first quadrant
191             }
192              
193 78         118 my $n_min = my $n_max
194             = my $x_min = my $y_min
195             = my $x_max = my $y_max
196             = ($x1 * 0 * $x2 * $y1 * $y2); # inherit bignum 0
197              
198 78 100       189 my ($len, $level) = round_down_pow (($x2 > $y2 ? $x2 : $y2),
199             2);
200             ### $len
201             ### $level
202 78 50       140 if (is_infinite($level)) {
203 0         0 return (0, $level);
204             }
205 78 100       162 my $min_state = my $max_state = ($level & 1 ? 4 : 0);
206              
207 78         139 while ($level >= 0) {
208             {
209 347         425 my $x_cmp = $x_min + $len;
210 347         411 my $y_cmp = $y_min + $len;
211 347 100       720 my $digit = $min_digit[3*$min_state
    100          
    100          
    100          
212             + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0)
213             + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)];
214              
215 347         463 $n_min = 4*$n_min + $digit;
216 347         370 $min_state += $digit;
217 347 100       480 if ($digit_to_x[$min_state]) { $x_min += $len; }
  146         163  
218 347 100       483 if ($digit_to_y[$min_state]) { $y_min += $len; }
  156         186  
219 347         426 $min_state = $next_state[$min_state];
220             }
221             {
222 347         354 my $x_cmp = $x_max + $len;
  347         364  
  347         404  
223 347         399 my $y_cmp = $y_max + $len;
224 347 100       730 my $digit = $max_digit[3*$max_state
    100          
    100          
    100          
225             + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0)
226             + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)];
227              
228 347         422 $n_max = 4*$n_max + $digit;
229 347         386 $max_state += $digit;
230 347 100       492 if ($digit_to_x[$max_state]) { $x_max += $len; }
  177         206  
231 347 100       469 if ($digit_to_y[$max_state]) { $y_max += $len; }
  170         187  
232 347         380 $max_state = $next_state[$max_state];
233             }
234              
235 347         466 $len = int($len/2);
236 347         515 $level--;
237             }
238              
239 78         173 return ($n_min, $n_max);
240             }
241              
242             #------------------------------------------------------------------------------
243              
244             # shared by Math::PlanePath::AR2W2Curve and others
245             sub level_to_n_range {
246 0     0 1   my ($self, $level) = @_;
247 0           return (0, 4**$level - 1);
248             }
249             sub n_to_level {
250 0     0 1   my ($self, $n) = @_;
251 0 0         if ($n < 0) { return undef; }
  0            
252 0 0         if (is_infinite($n)) { return $n; }
  0            
253 0           $n = round_nearest($n);
254 0           my ($pow, $exp) = round_up_pow ($n+1, 4);
255 0           return $exp;
256             }
257              
258             #------------------------------------------------------------------------------
259             1;
260             __END__