The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright 2014 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/>.


# much overlap


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

use Math::PlanePath;
*_divrem_mutate = \&Math::PlanePath::_divrem_mutate;

use Math::PlanePath::Base::Generic
  'is_infinite',
  'round_nearest',
  'xy_is_even';
use Math::PlanePath::Base::Digits
  'digit_split_lowtohigh';

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

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


use constant n_start => 0;

#------------------------------------------------------------------------------
#
#        .
#        h
#        .
#        .........
#                .
#            ....g...
#            .   .
#        .   .   .
#        .   .
#     .. f..10---d--11
#        .   |
#        7...|....
#        |   |   .
#    8---c---9   e
#        |       .
#        6-------5                 3
#                |
#            2---b---3             2
#            |   |
#            |   4                 1
#            |
#    0---a---1                     0
# 
#    0   1   2   3   4       

#           10---*--11
#            |
#        7   |
#        |   |
#    8---*---9
#        |
#        6-------5
#           \ /  |  \
#            2--/*---3
#           /|\  |/   \
#            |   4
#   \     / \|/ /
#    0---*---1
#     \ /   / \

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

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

  my $zero = ($n * 0);  # inherit bignum 0

  {
    # high to low

    my $x = 0;
    my $y = 0;
    my $dx = 1;
    my $dy = 0;
    # return if $n >=9;

    my $lowdigit = _divrem_mutate($n, 4);
    my @digits = digit_split_lowtohigh($n,3);

    foreach my $digit (reverse(@digits), $lowdigit) {
      ### at: "$x,$y  digit=$digit"
      ($x,$y) = ($x-$y,$x+$y); # rotate +45
      $x += 1;
      ### rotate to: "$x,$y"

      if ($digit == 0) {
        $x -= $dx;
        $y -= $dy;

      } elsif ($digit == 1) {
        $x += $dx;
        $y += $dy;
        ($dx,$dy) = (-$dy,$dx); # rotate +90

      } elsif ($digit == 2) {
        $x += $dx - 2*$dy;   # across then at +90
        $y += $dy + 2*$dx;

      } elsif ($digit == 3) {
        $x += 3*$dx - 2*$dy;   # across then at +90, for $lowdigit
        $y += 3*$dy + 2*$dx;
      }
    }

    ### return: "$x,$y"
    return ($x,$y);
  }
  {
    # low to high

    my $x = 0;
    my $y = 0;
    my $dx = 1 + $zero;
    my $dy = $zero;
    return if $n >=16;
    my $lowdigit = _divrem_mutate($n, 3);
    if ($lowdigit == 0) {

    } elsif ($lowdigit == 1) {
      $x = 2;

    } elsif ($lowdigit == 2) {
      $x = 2;
      $y = 2;

    } elsif ($lowdigit == 3) {
      $x = 4;
      $y = 2;
    }

    foreach my $digit (digit_split_lowtohigh($n,3)) {
      # $dx *= 2;
      # $dy *= 2;
      ($dx,$dy) = ($dx+$dy,$dy-$dx); # rotate 45
      # ($dx,$dy) = (-$dy,$dx); # rotate +90

      if ($digit == 0) {

      } elsif ($digit == 1) {
        ($x,$y) = (-$y,$x); # rotate +90
        $x += 3/2*$dx;
        $y += 3/2*$dy;
        ($dx,$dy) = (-$dy,$dx); # rotate +90
        $x += 1/2*$dx;
        $y += 1/2*$dy;

      } elsif ($digit == 2) {
        $x -= 4/2*$dy;
        $y += 4/2*$dx;
      }
    }

    return ($x,$y);
  }
}


sub xy_to_n {
  my ($self, $x, $y) = @_;
  return undef;
}


# minimum  -- no, not quite right
#
#                *----------*
#                 \
#                  \   *
#               *   \
#                    \
#          *----------*
#
# width = side/2
# minimum = side*sqrt(3)/2 - width
#         = side*(sqrt(3)/2 - 1)
#
# minimum 4/9 * 2.9^level roughly
# h = 4/9 * 2.9^level
# 2.9^level = h*9/4
# level = log(h*9/4)/log(2.9)
# 3^level = 3^(log(h*9/4)/log(2.9))
#         = h*9/4, but big bigger for log
#
# not exact
sub rect_to_n_range {
  my ($self, $x1,$y1, $x2,$y2) = @_;
  ### Z2DragonCurve rect_to_n_range(): "$x1,$y1  $x2,$y2"
  my $xmax = int(max(abs($x1),abs($x2)));
  my $ymax = int(max(abs($y1),abs($y2)));
  return (0,
          ($xmax*$xmax + $ymax*$ymax + 1));
}

1;
__END__