# Copyright 2011 Kevin Ryde

# This file is part of Math-Image.
#
# Math-Image 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-Image 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-Image.  If not, see <http://www.gnu.org/licenses/>.


package Math::PlanePath::MathImageKochelCurve;
use 5.004;
use strict;

use vars '$VERSION', '@ISA';
$VERSION = 81;
use Math::PlanePath;
@ISA = ('Math::PlanePath');
*_is_infinite = \&Math::PlanePath::_is_infinite;
*_round_nearest = \&Math::PlanePath::_round_nearest;

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

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


use constant n_start => 0;
use constant x_negative => 0;
use constant y_negative => 0;

# tables generated by tools/kochel-curve-table.pl
#
my @next_state = ( 63, 72,  9,  99,  0, 90,  36, 99,  0,    # 0
                   36, 81, 18,  72,  9, 99,  45, 72,  9,    # 9
                   45, 90, 27,  81, 18, 72,  54, 81, 18,    # 18
                   54, 99,  0,  90, 27, 81,  63, 90, 27,    # 27
                   36,135,  0, 126, 36,135,  45,108, 27,    # 36
                   45,108,  9, 135, 45,108,  54,117,  0,    # 45
                   54,117, 18, 108, 54,117,  63,126,  9,    # 54
                   63,126, 27, 117, 63,126,  36,135, 18,    # 63
                   63, 72,  9,  99, 90, 99,  63, 72,  9,    # 72
                   36, 81, 18,  72, 99, 72,  36, 81, 18,    # 81
                   45, 90, 27,  81, 72, 81,  45, 90, 27,    # 90
                   54, 99,  0,  90, 81, 90,  54, 99,  0,    # 99
                   45,108, 27, 135,126,135,  45,108, 27,    # 108
                   54,117,  0, 108,135,108,  54,117,  0,    # 117
                   63,126,  9, 117,108,117,  63,126,  9,    # 126
                   36,135, 18, 126,117,126,  36,135, 18);   # 135
my @digit_to_x = (0,0,0, 1,2,2, 1,1,2,    # 0
                  2,1,0, 0,0,1, 1,2,2,    # 9
                  2,2,2, 1,0,0, 1,1,0,    # 18
                  0,1,2, 2,2,1, 1,0,0,    # 27
                  2,1,1, 2,2,1, 0,0,0,    # 36
                  2,2,1, 1,0,0, 0,1,2,    # 45
                  0,1,1, 0,0,1, 2,2,2,    # 54
                  0,0,1, 1,2,2, 2,1,0,    # 63
                  0,0,0, 1,1,1, 2,2,2,    # 72
                  2,1,0, 0,1,2, 2,1,0,    # 81
                  2,2,2, 1,1,1, 0,0,0,    # 90
                  0,1,2, 2,1,0, 0,1,2,    # 99
                  2,2,2, 1,1,1, 0,0,0,    # 108
                  0,1,2, 2,1,0, 0,1,2,    # 117
                  0,0,0, 1,1,1, 2,2,2,    # 126
                  2,1,0, 0,1,2, 2,1,0);   # 135
my @digit_to_y = (0,1,2, 2,2,1, 1,0,0,    # 0
                  0,0,0, 1,2,2, 1,1,2,    # 9
                  2,1,0, 0,0,1, 1,2,2,    # 18
                  2,2,2, 1,0,0, 1,1,0,    # 27
                  0,0,1, 1,2,2, 2,1,0,    # 36
                  2,1,1, 2,2,1, 0,0,0,    # 45
                  2,2,1, 1,0,0, 0,1,2,    # 54
                  0,1,1, 0,0,1, 2,2,2,    # 63
                  0,1,2, 2,1,0, 0,1,2,    # 72
                  0,0,0, 1,1,1, 2,2,2,    # 81
                  2,1,0, 0,1,2, 2,1,0,    # 90
                  2,2,2, 1,1,1, 0,0,0,    # 99
                  2,1,0, 0,1,2, 2,1,0,    # 108
                  2,2,2, 1,1,1, 0,0,0,    # 117
                  0,1,2, 2,1,0, 0,1,2,    # 126
                  0,0,0, 1,1,1, 2,2,2);   # 135
my @xy_to_digit = (0,1,2, 7,6,3, 8,5,4,    # 0
                   2,3,4, 1,6,5, 0,7,8,    # 9
                   4,5,8, 3,6,7, 2,1,0,    # 18
                   8,7,0, 5,6,1, 4,3,2,    # 27
                   8,7,6, 1,2,5, 0,3,4,    # 36
                   6,5,4, 7,2,3, 8,1,0,    # 45
                   4,3,0, 5,2,1, 6,7,8,    # 54
                   0,1,8, 3,2,7, 4,5,6,    # 63
                   0,1,2, 5,4,3, 6,7,8,    # 72
                   2,3,8, 1,4,7, 0,5,6,    # 81
                   8,7,6, 3,4,5, 2,1,0,    # 90
                   6,5,0, 7,4,1, 8,3,2,    # 99
                   8,7,6, 3,4,5, 2,1,0,    # 108
                   6,5,0, 7,4,1, 8,3,2,    # 117
                   0,1,2, 5,4,3, 6,7,8,    # 126
                   2,3,8, 1,4,7, 0,5,6);   # 135
# state length 144 in each of 4 tables

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

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

  {
    # ENHANCE-ME: determine dx/dy direction from N bits, not full
    # calculation of N+1
    my $int = int($n);
    if ($n != $int) {
      my $frac = $n - $int;  # inherit possible BigFloat/BigRat
      my ($x1,$y1) = $self->n_to_xy($int);
      my ($x2,$y2) = $self->n_to_xy($int+1);
      my $dx = $x2-$x1;
      my $dy = $y2-$y1;
      return ($frac*$dx + $x1, $frac*$dy + $y1);
    }
    $n = $int;
  }

  my @digits;
  my $len = $n*0 + 1;   # inherit bignum 1
  while ($n) {
    push @digits, $n % 9;
    $n = int($n/9);
    $len *= 3;
  }
  ### digits: join(', ',@digits)."   count ".scalar(@digits)
  ### $len

  my $state = ($#digits & 1 ? 0 : 63);
  $state = 63;
  my $x = 0;
  my $y = 0;

  while (@digits) {
    $len /= 3;
    $state += pop @digits;

    ### $len
    ### $state
    ### digit_to_x: $digit_to_x[$state]
    ### digit_to_y: $digit_to_y[$state]
    ### next_state: $next_state[$state]

    $x += $len * $digit_to_x[$state];
    $y += $len * $digit_to_y[$state];
    $state = $next_state[$state];
  }

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

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

  $x = _round_nearest ($x);
  $y = _round_nearest ($y);
  if ($x < 0 || $y < 0) {
    return undef;
  }
  if (_is_infinite($x)) {
    return $x;
  }
  if (_is_infinite($y)) {
    return $y;
  }

  my ($len, $level) = _round_down_pow (($x > $y ? $x : $y),
                                       3);
  ### $len
  ### $level

  my $n = ($x * 0 * $y);
  my $state = ($level & 1 ? 0 : 63);
  $state = 63;
  while ($level-- >= 0) {
    ### at: "$x,$y  len=$len level=$level"
    ### assert: $x >= 0
    ### assert: $y >= 0
    ### assert: $x < 3*$len
    ### assert: $y < 3*$len

    my $xo = int ($x / $len);
    my $yo = int ($y / $len);
    ### assert: $xo >= 0
    ### assert: $xo <= 2
    ### assert: $yo >= 0
    ### assert: $yo <= 2

    $x %= $len;
    $y %= $len;
    ### xy bits: "$xo, $yo"

    my $digit = $xy_to_digit[$state + 3*$xo + $yo];
    $state = $next_state[$state+$digit];
    $n = 9*$n + $digit;
    $len /= 3;
  }

  ### assert: $x == 0
  ### assert: $y == 0

  return $n;
}

# not exact
sub rect_to_n_range {
  my ($self, $x1,$y1, $x2,$y2) = @_;
  ### KochelCurve rect_to_n_range(): "$x1,$y1, $x2,$y2"

  $x1 = _round_nearest ($x1);
  $x2 = _round_nearest ($x2);
  ($x1,$x2) = ($x2,$x1) if $x1 > $x2;

  $y1 = _round_nearest ($y1);
  $y2 = _round_nearest ($y2);
  ($y1,$y2) = ($y2,$y1) if $y1 > $y2;

  if ($x2 < 0 || $y2 < 0) {
    return (1, 0);
  }

  my ($len, $level) = _round_down_pow (($x2 > $y2 ? $x2 : $y2),
                                       3);
  ### $len
  ### $level
  return (0, 9*$len*$len-1);
}

1;
__END__

=for stopwords eg Ryde ie KochelCurve Math-PlanePath Haverkort Tilings

=head1 NAME

Math::PlanePath::MathImageKochelCurve -- 3x3 self-similar S and R

=head1 SYNOPSIS

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

=head1 DESCRIPTION

I<In progress ...>

This is an integer version of a 3x3 self-similar curve by Herman Haverkort,

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

The base pattern is the N=0 to N=8 section.  It then repeats with rotations
or reversals that make the ends join.

=head1 FUNCTIONS

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

=over 4

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

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::PeanoCurve>

Herman Haverkort, "Recursive Tilings and Space-Filling Curves with Little
Fragmentation"

    http://arxiv.org/abs/1002.1843

=cut


# Local variables:
# compile-command: "math-image --path=MathImageKochelCurve --lines --scale=20"
# End:
#
# math-image --path=MathImageKochelCurve --all --output=numbers_dash
