# Copyright 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, or (at your option) any later # version. # # Math-PlanePath is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # with Math-PlanePath. If not, see . # math-image --path=PyramidReplicate --lines --scale=10 # math-image --path=PyramidReplicate --all --output=numbers_dash --size=80x50 package Math::PlanePath::PyramidReplicate; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 100; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; # uncomment this to run the ### lines #use Devel::Comments; use constant n_start => 0; # 4 3 2 # 5 0 1 # 6 7 8 # my @digit_to_x = (0,1,0,-1, -2,-3,-2,-1, 0,-1, 0, 1, 2,1,2,3); my @digit_to_y = (0,0,1, 0, 1, 1, 0, 1, -1,-1,-2,-1, 1,1,0,1); sub n_to_xy { my ($self, $n) = @_; ### PyramidReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = ($n * 0); # inherit bignum 0 my $len = ($x + 1); # inherit bignum 1 my $bx = 1; my $by = 1; while ($n) { my $digit = $n % 16; $n = int($n/16); ### at: "$x,$y" ### $digit $x += $digit_to_x[$digit] * $bx; $y += $digit_to_y[$digit] * $by; $bx *= 6; $by *= 4; } ### final: "$x,$y" return ($x,$y); } # mod digit # 5 3 4 4 3 2 (x mod 3) + 3*(y mod 3) # 2 0 1 5 0 1 # 8 6 7 6 7 8 # my @mod_to_digit = (0,1,5, 3,2,4, 7,8,6); sub xy_to_n { my ($self, $x, $y) = @_; ### PyramidReplicate xy_to_n(): "$x, $y" return undef; $x = round_nearest ($x); $y = round_nearest ($y); my ($len,$level_limit); { my $xa = abs($x); my $ya = abs($y); ($len,$level_limit) = round_down_pow (2*($xa > $ya ? $xa : $ya) || 1, 3); ### $level_limit ### $len } $level_limit += 2; if (is_infinite($level_limit)) { return $level_limit; } my $n = ($x * 0 * $y); # inherit bignum 0 my $power = ($n + 1); # inherit bignum 1 while ($x || $y) { if ($level_limit-- < 0) { ### oops, level limit reached ... return undef; } my $m = ($x % 3) + 3*($y % 3); my $digit = $mod_to_digit[$m]; ### at: "$x,$y m=$m digit=$digit" $x -= $digit_to_x[$digit]; $y -= $digit_to_y[$digit]; ### subtract: "$digit_to_x[$digit],$digit_to_y[$digit] to $x,$y" ### assert: $x % 3 == 0 ### assert: $y % 3 == 0 $x /= 3; $y /= 3; $n += $digit * $power; $power *= 9; } return $n; } # level N Xmax # 1 9^1-1 1 # 2 9^2-1 1+3 # 3 9^3-1 1+3+9 # X <= 3^0+3^1+...+3^(level-1) # X <= 1 + 3^0+3^1+...+3^(level-1) # X <= (3^level - 1)/2 # 2*X+1 <= 3^level # level >= log3(2*X+1) # # X < 1 + 3^0+3^1+...+3^(level-1) # X < 1 + (3^level - 1)/2 # (3^level - 1)/2 > X-1 # 3^level - 1 > 2*X-2 # 3^level > 2*X-1 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PyramidReplicate rect_to_n_range(): "$x1,$y1 $x2,$y2" my $max = abs(round_nearest($x1)); foreach ($y1, $x2, $y2) { my $m = abs(round_nearest($_)); if ($m > $max) { $max = $m } } my ($len,$level) = round_down_pow (2*($max||1)-1, 3); return (0, 9*$len*$len - 1); # 9^level-1 } 1; __END__ =for stopwords eg Ryde Math-PlanePath aabbccdd =head1 NAME Math::PlanePath::PyramidReplicate -- replicating squares =head1 SYNOPSIS use Math::PlanePath::PyramidReplicate; my $path = Math::PlanePath::PyramidReplicate->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a self-similar replicating pyramid shape made from 4 points each, 4 3 2 1 <- Y=0 -1 -2 -3 -4 ^ -4 -3 -2 -1 X=0 1 2 3 4 The base shape is the initial N=0 to N=8 section, +---+ | 2 | +---+---+---+ | 3 | 0 | 1 | +---+---+---+ It then repeats inverted to make a similar shape but upside-down, +---+---+---+---+---+---+---+ | 5 4 7 | 2 |13 12 15 | +---+ +---+ +---+ +---+ | 6 | 3 0 1 |14 | +---+---+---+---+---+ | 9 8 11 | +---+ +---+ |10 | +---+ =head2 Level Ranges A given replication extends to ... Nlevel = 4^level - 1 - ... <= X <= ... - ... <= Y <= ... =head2 Complex Base This pattern corresponds to expressing a complex integer X+i*Y in base b=... X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] using complex digits a[i] encoded in N in integer base 4 ... a[i] digit N digit ---------- ------- 0 0 1 1 i 2 -1 3 =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PyramidReplicate-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE http://user42.tuxfamily.org/math-planepath/index.html =head1 LICENSE Copyright 2011, 2012, 2013 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Math-PlanePath. If not, see . =cut