# Copyright 2012 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 <http://www.gnu.org/licenses/>.


# math-image --path=CubicBase --all --output=numbers --size=60x20
# math-image --path=CubicBase --values=Multiples,multiples=27 --output=numbers --size=60x20

# math-image --path=CubicBase --expression='i<128?i:0' --output=numbers --size=132x20
#

package Math::PlanePath::CubicBase;
use 5.004;
use strict;
#use List::Util 'max';
*max = \&Math::PlanePath::_max;

use Math::PlanePath;
*_is_infinite = \&Math::PlanePath::_is_infinite;
*_round_nearest = \&Math::PlanePath::_round_nearest;
*_digit_split_lowtohigh = \&Math::PlanePath::_digit_split_lowtohigh;

use Math::PlanePath::KochCurve 42;
*_round_down_pow = \&Math::PlanePath::KochCurve::_round_down_pow;

use vars '$VERSION', '@ISA';
$VERSION = 77;
@ISA = ('Math::PlanePath');

# uncomment this to run the ### lines
#use Smart::Comments;


use constant n_start => 0;

use constant parameter_info_array =>
  [{ name      => 'radix',
     share_key => 'radix_2',
     type      => 'integer',
     minimum   => 2,
     default   => 2,
     width     => 3,
   },
   { name      => 'skewed',
     type      => 'boolean',
     default   => 0,
   }
];

sub new {
  my $class = shift;
  my $self = $class->SUPER::new(@_);

  my $radix = $self->{'radix'};
  if (! defined $radix || $radix <= 2) { $radix = 2; }
  $self->{'radix'} = $radix;

  return $self;
}

sub n_to_xy {
  my ($self, $n) = @_;
  ### CubicBase n_to_xy(): "$n"

  if ($n < 0) { return; }
  if (_is_infinite($n)) { return ($n,$n); }

  # is this sort of midpoint worthwhile? not documented yet
  {
    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 = 0;
  my $y = 0;

  my $radix = $self->{'radix'};
  if (my @digits = _digit_split_lowtohigh($n,$radix)) {
    my $dx = 2;
    my $dy = 0;
    my $len = ($n * 0) + 1;  # inherit bignum 1
    my $ext = 1;
    for (;;) {
      { # 0 degrees
        $x += (2*(shift @digits)) * $len;    # low to high
      }
      @digits || last;

      if ($ext ^= 1) {
        $len *= $radix;
      }

      { # +120 degrees
        my $dlen = (shift @digits) * $len;   # low to high
        $x -= $dlen;
        $y += $dlen;
      }
      @digits || last;

      if ($ext ^= 1) {
        $len *= $radix;
      }

      { # +240 degrees
        my $dlen = (shift @digits) * $len;   # low to high
        $x -= $dlen;
        $y -= $dlen;
      }
      @digits || last;

      if ($ext ^= 1) {
        $len *= $radix;
      }
    }

    if ($self->{'skewed'}) {
      $x = ($x + $y) / 2;
    }
  }

  ### result: "$x,$y"
  return ($x,$y);
}

sub xy_to_n {
  my ($self, $x, $y) = @_;
  ### CubicBase xy_to_n(): "$x, $y"

  $x = _round_nearest ($x);
  $y = _round_nearest ($y);
  if (_is_infinite($x)) { return ($x); }
  if (_is_infinite($y)) { return ($y); }

  if ($self->{'skewed'}) {
    $x = 2*$x - $y;
  } else {
    if (($x + $y) % 2) {
      # nothing on odd squares, only A2 even squares
      return undef;
    }
  }
  # $x = ($x-$y)/2;  # into i,j coordinates

  my $radix = $self->{'radix'};
  my $n = ($x * 0 * $y);  # inherit bignum 0


  # my ($len, $level) = _round_down_pow(abs($x)+abs($y), $radix);
  # $len *= $radix;
  # $level++;
  # $len *= $radix;
  # ### $level
  # ### $len
  #
  # for (;;) {
  #   ### at: "x=$x y=$y"
  #
  #   {
  #     my $k = -$y;
  #     my $digit = ($k >= 0
  #                  ? int($k/$len)
  #                  : -int(-$k/$len));
  #     $n = $n*$radix + $digit;
  #     $x += $digit*$len;
  #     $y += $digit*$len;
  #
  #     ### 240deg digit: $digit
  #     ### add to: "x=$x y=$y"
  #   }
  #
  #   $len = int($len/$radix);
  #   ### $len
  #
  #   {
  #     my $k = $y;
  #     my $digit = int($k/$len) % $radix;
  #     $n = $n*$radix + $digit;
  #     $x += $digit*$len;
  #     $y -= $digit*$len;
  #
  #     ### 120deg digit: $digit
  #     ### subtract to: "x=$x y=$y"
  #   }
  #
  #   {
  #     my $digit = ($x >= 0
  #                  ? int($x/(2*$len))
  #                  : -int(-$x/(2*$len)));
  #     $n = $n*$radix + $digit;
  #     $x -= 2*$digit;
  #
  #     ### 0deg digit: $digit
  #     ### subtract to: "x=$x y=$y"
  #   }
  #
  #   last unless $level-- > 0;
  #
  # }



  my $power = $n + 1;     # inherit bignum 1
  my $rot = 0;

  if ($x || $y) {
    my $ext = 1;

    for (;;) {
      ### at: "x=$x y=$y"

      {
        my $digit = (($x+$y)/2) % $radix;
        $n += $digit * $power;    # low to high
        $x -= 2*$digit;

        ### 0deg digit: $digit
        ### subtract to: "x=$x y=$y"
      }

      last unless $x || $y;
      $power *= $radix;
      if ($ext ^= 1) {
        ### assert: ($x % $radix) == 0
        ### assert: ($y % $radix) == 0
        $x = int($x/$radix);
        $y = int($y/$radix);
        ### divide out to: "x=$x y=$y"
      }

      {
        my $digit = (($y-$x)/2) % $radix;
        $n += $digit * $power;    # low to high
        $x += $digit;
        $y -= $digit;

        ### 120deg digit: $digit
        ### subtract to: "x=$x y=$y"
      }

      last unless $x || $y;
      $power *= $radix;
      if ($ext ^= 1) {
        ### assert: ($x % $radix) == 0
        ### assert: ($y % $radix) == 0
        $x = int($x/$radix);
        $y = int($y/$radix);
        ### divide out to: "x=$x y=$y"
      }

      {
        my $digit = (-$y) % $radix;
        $n += $digit * $power;    # digit low to high stored into $n
        $x += $digit;
        $y += $digit;

        ### 240deg digit: $digit
        ### subtract to: "x=$x y=$y"
      }

      last unless $x || $y;
      $power *= $radix;
      if ($ext ^= 1) {
        ### assert: ($x % $radix) == 0
        ### assert: ($y % $radix) == 0
        $x = int($x/$radix);
        $y = int($y/$radix);
        ### divide out to: "x=$x y=$y"
      }
    }
  }

  ### result: $n
  return $n;
}

# ENHANCE-ME: Can probably do better by measuring extents in 3 directions
# for a hexagonal boundary.
#
# not exact
sub rect_to_n_range {
  my ($self, $x1,$y1, $x2,$y2) = @_;
  ### CubicBase rect_to_n_range(): "$x1,$y1  $x2,$y2"

  my $radix = $self->{'radix'} ;
  my $xm = max(abs($x1),abs($x2)) * $radix*$radix*$radix;
  my $ym = max(abs($y1),abs($y2)) * $radix*$radix*$radix;

  return (0,
          int($xm*$xm+$ym*$ym));
}

1;
__END__

=for stopwords eg Ryde Math-PlanePath ZOrderCurve Radix ie

=head1 NAME

Math::PlanePath::CubicBase -- replications in three directions

=head1 SYNOPSIS

 use Math::PlanePath::CubicBase;
 my $path = Math::PlanePath::CubicBase->new (radix => 4);
 my ($x, $y) = $path->n_to_xy (123);

=head1 DESCRIPTION

This is a pattern of replications in three directions 0, 120 and 240 degrees.

=cut

# these numbers generated with
#   math-image --path=CubicBase --expression='i<64?i:0' --output=numbers --size=132x20

=pod

                       18    19    26    27                      5
                          16    17    24    25                   4
                 22    23    30    31                            3
                    20    21    28    29                         2
           50    51    58    59     2     3    10    11          1
              48    49    56    57     0     1     8     9   <- Y=0
     54    55    62    63     6     7    14    15               -1
        52    53    60    61     4     5    12    13            -2
                       34    35    42    43                     -3
                          32    33    40    41                  -4
                 38    39    46    47                           -5
                    36    37    44    45                        -6

                                       ^
    -11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1  2  3  4  5  6

The points are on a triangular grid by using every second integer X,Y, as
per L<Math::PlanePath/Triangular Lattice>.  All points on that triangular
grid are visited.

The initial N=0,N=1 is replicated at +120 degrees.  Then that trapezoid at
+240 degrees

    +-----------+                       +-----------+       
     \  2     3  \                       \  2     3  \      
      +-----------+                       \           \     
        \  0     1  \                       \  0     1  \   
         +-----------+             ---------  -----------+  
                                   \  6     7  \            
      replicate +120deg              \          \    rep +240deg
                                      \  4     5 \          
                                       +----------+         

Then that bow-tie N=0to7 is replicated at 0 degrees again.  Each replication
is 1/3 of the circle around, 0, 120, 240 degrees repeating.  The relative
layout within a replication is unchanged.

                      -----------------------
                      \ 18    19    26    27 \
                       \                       \
                        \  16    17    24    25 \
               ----------              ----------
                \ 22    23    30    31 \
                  \                      \
                   \ 20    21    28    29  \
          --------- ------------ +----------- -----------
          \ 50    51    58    59  \  2     3  \ 10    11 \
            \                      +-----------+           \
             \ 48    49    56    57  \  0     1  \  8     9 \
    ----------              --------- +-----------  ---------+
    \ 54    55    62    63  \  6     7  \ 14    15  \
     \                        \          \            \
       \ 52    53    60    61  \  4     5 \  12    13  \
        --------------          +----------+------------
                      \ 34    35    42    43 \
                       \                       \
                        \  32    33    40    41 \
                ---------+            -----------
                \ 38    39    46    47 \
                 \                       \
                   \ 36    37    44    45 \
                    -----------------------

The radial distance doubles on every second replication, so N=1 and N=2 are
at 1 unit from the origin, then N=4 and N=8 at 2 units, then N=16 and N=32
at 4 units.  N=64 is not shown but is then at 8 units away (X=8,Y=0).

This is similar to the ImaginaryBase, but where it repeats in 4 directions
based on i=squareroot(-1), here it's 3 directions based on w=cuberoot(1) =
-1/2+i*sqrt(3)/2.

=head2 Radix

The C<radix> parameter controls the "r" used to break N into X,Y.  For
example C<radix =E<gt> 4> gives 4x4 blocks, with r-1 replications of the
preceding level at each stage.

=cut

# math-image --path=CubicBase,radix=4 --expression='i<64?i:0' --output=numbers --size=150x30

=pod

       3                                 12    13    14    15
       2                                     8     9    10    11
       1                                        4     5     6     7
     Y=0 ->                                        0     1     2     3
      -1                     28    29    30    31
      -2                        24    25    26    27
      -3                           20    21    22    23
      -4                              16    17    18    19
      -5         44    45    46    47
      ...           40    41    42    43
                       36    37    38    39
                          32    33    34    35
     60    61    62    63
        56    57    58    59
           52    53    54    55
              48    49    50    51

                                                   ^
    -15-14-13-12-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1  2  3  4  5  6

Notice the parts always replicate away from the origin, so the block N=16 to
N=31 is near the origin at X=-4, then N=32,48,64 are further away.

In this layout the replications still mesh together perfectly and all points
on the triangular grid are visited.

=head1 FUNCTIONS

See L<Math::PlanePath/FUNCTIONS> for behaviour common to all path classes.

=over 4

=item C<$path = Math::PlanePath::CubicBase-E<gt>new ()>

=item C<$path = Math::PlanePath::CubicBase-E<gt>new (radix =E<gt> $r)>

Create and return a new path object.

=item C<($x,$y) = $path-E<gt>n_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<lt> 0> then the return is an empty list.

=back

=head1 SEE ALSO

L<Math::PlanePath>,
L<Math::PlanePath::ImaginaryBase>,
L<Math::PlanePath::ImaginaryHalf>

=head1 HOME PAGE

http://user42.tuxfamily.org/math-planepath/index.html

=head1 LICENSE

Copyright 2012 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 <http://www.gnu.org/licenses/>.

=cut
