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, 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             # http://www.woollythoughts.com/afghans/peano.html
20             # Knitting
21             #
22             # http://www.geom.uiuc.edu/docs/reference/CRC-formulas/node36.html
23             # Closed path, curved parts
24             #
25             # http://www.wolframalpha.com/entities/calculators/Peano_curve/jh/4o/im/
26             # Curved corners tilted to a diamond, or is it an 8-step pattern?
27             #
28             # http://www.davidsalomon.name/DC2advertis/AppendC.pdf
29             #
30             # http://www.cut-the-knot.org/do_you_know/hilbert.shtml
31             # Java applet
32             #
33              
34             package Math::PlanePath::HilbertCurve;
35 5     5   1552 use 5.004;
  5         19  
36 5     5   28 use strict;
  5         8  
  5         220  
37             #use List::Util 'max','min';
38             *max = \&Math::PlanePath::_max;
39              
40 5     5   45 use vars '$VERSION', '@ISA';
  5         8  
  5         327  
41             $VERSION = 128;
42 5     5   1138 use Math::PlanePath;
  5         8  
  5         117  
43 5     5   1215 use Math::PlanePath::Base::NSEW;
  5         12  
  5         175  
44             @ISA = ('Math::PlanePath::Base::NSEW',
45             'Math::PlanePath');
46              
47             use Math::PlanePath::Base::Generic
48 5         240 'is_infinite',
49 5     5   26 'round_nearest';
  5         9  
50             use Math::PlanePath::Base::Digits
51 5         294 'round_down_pow',
52             'round_up_pow',
53             'bit_split_lowtohigh',
54             'digit_split_lowtohigh',
55 5     5   851 '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         7  
  5         231  
62 5     5   26 use constant class_x_negative => 0;
  5         7  
  5         185  
63 5     5   23 use constant class_y_negative => 0;
  5         8  
  5         4932  
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 9586 my ($self, $n) = @_;
99             ### HilbertCurve n_to_xy(): $n
100             ### hex: sprintf "%#X", $n
101              
102 128 50       244 if ($n < 0) { return; }
  0         0  
103 128 50       230 if (is_infinite($n)) { return ($n,$n); }
  0         0  
104              
105 128         209 my $int = int($n);
106 128         182 $n -= $int; # fraction part
107              
108 128         230 my @ndigits = digit_split_lowtohigh($int,4);
109 128 100       228 my $state = ($#ndigits & 1 ? 4 : 0);
110 128 100       184 my $dirstate = ($#ndigits & 1 ? 0 : 4); # default if all $ndigit==3
111              
112 128         166 my (@xbits, @ybits);
113 128         235 foreach my $i (reverse 0 .. $#ndigits) { # digits high to low
114 515         573 my $ndigit = $ndigits[$i];
115 515         530 $state += $ndigit;
116 515 100       691 if ($ndigit != 3) {
117 381         395 $dirstate = $state; # lowest non-3 digit
118             }
119              
120 515         590 $xbits[$i] = $digit_to_x[$state];
121 515         578 $ybits[$i] = $digit_to_y[$state];
122 515         642 $state = $next_state[$state];
123             }
124              
125 128         166 my $zero = ($int * 0); # inherit bigint 0
126 128         299 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 259     259 1 7438 my ($self, $x, $y) = @_;
135             ### HilbertCurve xy_to_n(): "$x, $y"
136              
137 259         447 $x = round_nearest ($x);
138 259 50       428 if (is_infinite($x)) { return $x; }
  0         0  
139 259         421 $y = round_nearest ($y);
140 259 50       432 if (is_infinite($y)) { return $y; }
  0         0  
141              
142 259 50 33     650 if ($x < 0 || $y < 0) {
143 0         0 return undef;
144             }
145              
146 259         413 my @xbits = bit_split_lowtohigh($x);
147 259         463 my @ybits = bit_split_lowtohigh($y);
148 259         607 my $numbits = max($#xbits,$#ybits);
149              
150 259         304 my @ndigits;
151 259 100       406 my $state = ($numbits & 1 ? 4 : 0);
152 259         392 foreach my $i (reverse 0 .. $numbits) { # high to low
153             ### at: "state=$state xbit=".($xbits[$i]||0)." ybit=".($ybits[$i]||0)
154 1738   100     4214 my $ndigit = $yx_to_digit[$state + 2*($ybits[$i]||0) + ($xbits[$i]||0)];
      100        
155 1738         2095 $ndigits[$i] = $ndigit;
156 1738         2129 $state = $next_state[$state+$ndigit];
157             }
158             ### @ndigits
159 259         571 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 1018 my ($self, $x1,$y1, $x2,$y2) = @_;
180             ### HilbertCurve rect_to_n_range(): "$x1,$y1, $x2,$y2"
181              
182 78         120 $x1 = round_nearest ($x1);
183 78         126 $y1 = round_nearest ($y1);
184 78         125 $x2 = round_nearest ($x2);
185 78         127 $y2 = round_nearest ($y2);
186 78 100       144 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
187 78 100       132 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
188              
189 78 50 33     201 if ($x2 < 0 || $y2 < 0) {
190 0         0 return (1, 0); # rectangle outside first quadrant
191             }
192              
193 78         117 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       163 my ($len, $level) = round_down_pow (($x2 > $y2 ? $x2 : $y2),
199             2);
200             ### $len
201             ### $level
202 78 50       149 if (is_infinite($level)) {
203 0         0 return (0, $level);
204             }
205 78 100       145 my $min_state = my $max_state = ($level & 1 ? 4 : 0);
206              
207 78         118 while ($level >= 0) {
208             {
209 404         755 my $x_cmp = $x_min + $len;
210 404         442 my $y_cmp = $y_min + $len;
211 404 100       755 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 404         455 $n_min = 4*$n_min + $digit;
216 404         427 $min_state += $digit;
217 404 100       530 if ($digit_to_x[$min_state]) { $x_min += $len; }
  176         187  
218 404 100       543 if ($digit_to_y[$min_state]) { $y_min += $len; }
  169         176  
219 404         467 $min_state = $next_state[$min_state];
220             }
221             {
222 404         404 my $x_cmp = $x_max + $len;
  404         418  
  404         459  
223 404         457 my $y_cmp = $y_max + $len;
224 404 100       739 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 404         448 $n_max = 4*$n_max + $digit;
229 404         414 $max_state += $digit;
230 404 100       542 if ($digit_to_x[$max_state]) { $x_max += $len; }
  196         201  
231 404 100       567 if ($digit_to_y[$max_state]) { $y_max += $len; }
  191         198  
232 404         460 $max_state = $next_state[$max_state];
233             }
234              
235 404         462 $len = int($len/2);
236 404         559 $level--;
237             }
238              
239 78         158 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__