File Coverage

blib/lib/Math/PlanePath/HIndexing.pm
Criterion Covered Total %
statement 141 168 83.9
branch 38 54 70.3
condition 6 14 42.8
subroutine 15 20 75.0
pod 5 5 100.0
total 205 261 78.5


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             # http://theinf1.informatik.uni-jena.de/~niedermr/publications.html
20             #
21             # Rolf Niedermeier
22             # http://fpt.akt.tu-berlin.de/niedermr/publications.html
23             #
24             #
25             # H second part down per paper
26             # |
27             # | *--* * *-
28             # | | | | |
29             # | * *--* *
30             # | | |
31             # | * *--* *
32             # | | | | |
33             # | O * *--*
34             # |
35             # +------------
36             #
37             # eight similar to AlternatePaper
38             #
39             # |
40             # *--* *--* * *-
41             # | | | | | |
42             # --* * * *--* *--*
43             # | | |
44             # * * *--*--*--*
45             # | | |
46             # *--* * O *--*--*--*
47             # | |
48             # *--*--*--* * * *--*
49             # | | |
50             # *--*--*--* * * *-
51             # | | |
52             # *--* *--* * * *-
53             # | | | | | |
54             # *--* *--*
55             #
56              
57             package Math::PlanePath::HIndexing;
58 1     1   9497 use 5.004;
  1         10  
59 1     1   5 use strict;
  1         2  
  1         39  
60              
61 1     1   5 use vars '$VERSION', '@ISA';
  1         2  
  1         65  
62             $VERSION = 129;
63              
64 1     1   762 use Math::PlanePath;
  1         3  
  1         28  
65 1     1   467 use Math::PlanePath::Base::NSEW;
  1         3  
  1         40  
66             @ISA = ('Math::PlanePath::Base::NSEW',
67             'Math::PlanePath');
68              
69             use Math::PlanePath::Base::Generic
70 1         46 'is_infinite',
71 1     1   6 'round_nearest';
  1         2  
72             use Math::PlanePath::Base::Digits
73 1         79 'round_down_pow',
74             'round_up_pow',
75 1     1   496 'digit_split_lowtohigh';
  1         2  
76             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
77              
78              
79 1     1   6 use constant n_start => 0;
  1         2  
  1         49  
80 1     1   5 use constant class_x_negative => 0;
  1         2  
  1         39  
81 1     1   5 use constant class_y_negative => 0;
  1         2  
  1         36  
82 1     1   5 use constant diffxy_maximum => 0; # upper octant X<=Y so X-Y<=0
  1         2  
  1         72  
83 1     1   6 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 9;
  1         12  
  1         1073  
84              
85              
86             #------------------------------------------------------------------------------
87              
88             sub n_to_xy {
89 278     278 1 14349 my ($self, $n) = @_;
90             ### HIndexing n_to_xy(): $n
91              
92 278 50       568 if ($n < 0) { # negative
93 0         0 return;
94             }
95 278 50       594 if (is_infinite($n)) {
96 0         0 return ($n,$n);
97             }
98              
99             {
100             # ENHANCE-ME: get direction without full N+1 calculation
101 278         494 my $int = int($n);
  278         399  
102             ### $int
103             ### $n
104 278 100       514 if ($n != $int) {
105 60         128 my ($x1,$y1) = $self->n_to_xy($int);
106 60         140 my ($x2,$y2) = $self->n_to_xy($int+1);
107 60         94 my $frac = $n - $int; # inherit possible BigFloat
108 60         92 my $dx = $x2-$x1;
109 60         75 my $dy = $y2-$y1;
110 60         203 return ($frac*$dx + $x1, $frac*$dy + $y1);
111             }
112 218         337 $n = $int; # BigFloat int() gives BigInt, use that
113             }
114              
115 218         490 my $low = _divrem_mutate ($n, 2);
116             ### $low
117             ### $n
118              
119 218         469 my @digits = digit_split_lowtohigh($n,4);
120 218         428 my $len = ($n*0 + 2) ** scalar(@digits); # inherit bignum 2
121              
122 218         286 my $x = 0;
123 218         295 my $y = 0;
124 218         281 my $rev = 0;
125 218         257 my $xinvert = 0;
126 218         280 my $yinvert = 0;
127 218         414 while (@digits) {
128 828         1152 my $digit = pop @digits;
129              
130             ### $len
131             ### $rev
132             ### $digit
133              
134 828         1075 my $new_xinvert = $xinvert;
135 828         1008 my $new_yinvert = $yinvert;
136 828         991 my $xo = 0;
137 828         1006 my $yo = 0;
138 828 100       1308 if ($rev) {
139 371 100       709 if ($digit == 1) {
    100          
    100          
140 100         130 $xo = $len-1;
141 100         128 $yo = $len-1;
142 100         126 $rev ^= 1;
143 100         148 $new_yinvert = $yinvert ^ 1;
144             } elsif ($digit == 2) {
145 95         136 $xo = 2*$len-2;
146 95         117 $yo = 0;
147 95         127 $rev ^= 1;
148 95         122 $new_xinvert = $xinvert ^ 1;
149             } elsif ($digit == 3) {
150 72         93 $xo = $len;
151 72         106 $yo = $len;
152             }
153              
154             } else {
155 457 100       828 if ($digit == 1) {
    100          
    100          
156 191         280 $xo = $len-2;
157 191         244 $yo = $len;
158 191         279 $rev ^= 1;
159 191         289 $new_xinvert = $xinvert ^ 1;
160             } elsif ($digit == 2) {
161 120         153 $xo = 1;
162 120         170 $yo = 2*$len-1;
163 120         169 $rev ^= 1;
164 120         162 $new_yinvert = $yinvert ^ 1;
165             } elsif ($digit == 3) {
166 96         168 $xo = $len;
167 96         137 $yo = $len;
168             }
169             }
170              
171             ### $xo
172             ### $yo
173              
174 828 100       1206 if ($xinvert) {
175 348         439 $x -= $xo;
176             } else {
177 480         651 $x += $xo;
178             }
179 828 100       1194 if ($yinvert) {
180 221         267 $y -= $yo;
181             } else {
182 607         731 $y += $yo;
183             }
184              
185 828         991 $xinvert = $new_xinvert;
186 828         1022 $yinvert = $new_yinvert;
187 828         1543 $len /= 2;
188             }
189              
190             ### final: "$x,$y"
191              
192 218 100       362 if ($yinvert) {
193 92         123 $y -= $low;
194             } else {
195 126         172 $y += $low;
196             }
197              
198             ### is: "$x,$y"
199 218         455 return ($x, $y);
200             }
201              
202             # uncomment this to run the ### lines
203             #use Smart::Comments;
204              
205             sub xy_to_n {
206 186     186 1 14517 my ($self, $x, $y) = @_;
207             ### HIndexing xy_to_n(): "$x, $y"
208              
209 186         480 $x = round_nearest ($x);
210 186         341 $y = round_nearest ($y);
211              
212 186 50 33     913 if ($x < 0 || $y < 0 || $x > $y - ($y&1)) {
      33        
213 0         0 return undef;
214             }
215 186 50       399 if (is_infinite($x)) {
216 0         0 return $x;
217             }
218 186         631 my ($len, $level) = round_down_pow (int($y/1), 2);
219             ### $len
220             ### $level
221 186 50       372 if (is_infinite($level)) {
222 0         0 return $level;
223             }
224              
225 186         304 my $n = 0;
226 186         302 my $npower = $len*$len/2;
227 186         262 my $rev = 0;
228 186         368 while (--$level >= 0) {
229             ### at: "$x,$y rev=$rev len=$len n=$n"
230 965         1152 my $digit;
231 965         1227 my $new_rev = $rev;
232 965 100       1477 if ($y >= $len) {
233 610         775 $y -= $len;
234 610 100       960 if ($x >= $len) {
235             ### digit 3 ...
236 504         614 $digit = 3;
237 504         629 $x -= $len;
238             } else {
239 106         141 my $yinv = $len-1-$y;
240             ### digit 1 or 2: "y reduce to $y, x cmp ".($yinv-($yinv&1))
241 106 100       167 if ($x > $yinv-($yinv&1)) {
242             ### digit 2, x invert to: $len-1-$x
243 36         46 $digit = 2;
244 36         49 $x = $len-1-$x;
245             } else {
246             ### digit 1, y invert to: $yinv
247 70         92 $digit = 1;
248 70         93 $y = $yinv;
249             }
250 106         145 $new_rev ^= 1;
251             }
252             } else {
253             ### digit 0 ...
254 355         422 $digit = 0;
255             }
256              
257 965 100       1524 if ($rev) {
258 79         104 $digit = 3 - $digit;
259             ### reversed digit: $digit
260             }
261 965         1202 $rev = $new_rev;
262              
263             ### add n: $npower*$digit
264 965         1244 $n += $npower*$digit;
265 965         1256 $len /= 2;
266 965         1638 $npower /= 4;
267             }
268              
269             ### end at: "$x,$y n=$n rev=$rev"
270             ### assert: $x == 0
271             ### assert: $y == 0 || $y == 1
272              
273 186         388 return $n + $y^$rev;
274             }
275              
276             # not exact
277             sub rect_to_n_range {
278 67     67 1 5793 my ($self, $x1,$y1, $x2,$y2) = @_;
279              
280 67         176 $x1 = round_nearest ($x1);
281 67         127 $y1 = round_nearest ($y1);
282 67         126 $x2 = round_nearest ($x2);
283 67         122 $y2 = round_nearest ($y2);
284 67 50       137 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
285 67 50       118 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
286             ### HIndexing rect_to_n_range(): "$x1,$y1 to $x2,$y2"
287              
288             # y2 & 1 excluding the X=Y diagonal on odd Y rows
289 67 50 33     350 if ($x2 < 0 || $y2 < 0 || $x1 > $y2 - ($y2&1)) {
      33        
290 0         0 return (1, 0);
291             }
292              
293 67   100     203 my ($len, $level) = round_down_pow (($y2||1), 2);
294 67         174 return (0, 2*$len*$len-1);
295             }
296              
297              
298             #------------------------------------------------------------------------------
299              
300             sub level_to_n_range {
301 0     0 1   my ($self, $level) = @_;
302 0           return (0, 2*4**$level - 1);
303             }
304             sub n_to_level {
305 0     0 1   my ($self, $n) = @_;
306 0 0         if ($n < 0) { return undef; }
  0            
307 0 0         if (is_infinite($n)) { return $n; }
  0            
308 0           $n = round_nearest($n);
309 0           _divrem_mutate ($n, 2);
310 0           my ($pow,$exp) = round_up_pow ($n+1, 4);
311 0           return $exp;
312             }
313              
314             sub _UNDOCUMENTED__level_to_area {
315 0     0     my ($self, $level) = @_;
316 0           return (2**$level - 1)**2;
317             }
318             sub _UNDOCUMENTED__level_to_area_Y {
319 0     0     my ($self, $level) = @_;
320 0 0         if ($level == 0) { return 0; }
  0            
321 0           return 2**(2*$level-1) - 2**$level;
322             }
323             sub _UNDOCUMENTED__level_to_area_up {
324 0     0     my ($self, $level) = @_;
325 0 0         if ($level == 0) { return 0; }
  0            
326 0           return 2**(2*$level-1) - 2**$level + 1;
327             }
328              
329             #------------------------------------------------------------------------------
330             1;
331             __END__