#!/usr/bin/perl -w

#############################################################################
# Coord - a relative/absolute coordinate
#
# Copyright (C) 1999-2000 by Tels. All rights reserved.
#############################################################################

package CoW::Coord;
use vars qw($VERSION);
$VERSION = 1.00;    # Current version of this package
require  5.005;     # requires this Perl version or later

use base CoW::Size;
use base Exporter;
@EXPORT_OK      = qw( ALIGN_INSIDE ALIGN_OUTSIDE ALIGN_CENTER );
#@EXPORT        = qw( );
use strict 'vars';
use vars qw($AUTOLOAD);

#        '+' => \&add,
#        '-' => \&sub,
use overload
        '*' => \&scale,
        '<=>' => sub { $_[0]->absolute() <=> $_[1]; },
        'cmp' => sub { my $a = $_[0]->absolute(); "$a" cmp "$_[1]"; },
        '+0' => \&absolute,
        '""' => \&absolute;

use constant ALIGN_INSIDE => +1;
use constant ALIGN_CENTER => 0;
use constant ALIGN_OUTSIDE => -1;

use CoW::Misc qw/range/;

#############################################################################
# private, initialize self

sub _init
  {
  my $self = shift;
  my $args = shift;

  $self->SUPER::_init($args);
  $self->SUPER::_default( {
   rel => '',                   # relative to this size and coord
   size => '',                  # my own size
   ofs => 0,                    # an absolute offset
   val => 0, snap => 0,
   align => ALIGN_INSIDE,
   space => 0,
   } );
  die "illegal rel field $self->{rel}"
   unless $self->{rel} eq ''
   || $self->{rel} =~ /^((self|_parent)::|)[a-z]+,?((self|_parent)::|)[a-z]*/;
  die "illegal size field $self->{size}"
   unless $self->{size} eq '' || $self->{size} =~ /^(self::|)[a-z]+/;
  return $self;
  }

#############################################################################
# public

sub absolute
  {
  my $self = shift;

  my $abs = $self->{val};
  if ($self->{rel} ne '')
    {
    # now get the confined space
    my ($r,$c) = split /,/,$self->{rel};
    # in case of one value: base coordinate, in case of two: base size
    $abs = $self->_rel($r);
    if (defined $c)
      {
      # get the size we are based on, then subtract or add our own size
      # if align is not a center
      $abs -= $self->{align} * $self->_rel($self->{size})
       if ($self->{align} != ALIGN_CENTER);
      #print " self rel: ",$self->_rel($self->{size}),"\n";
      #print "abs $abs\n";
      # to leave some space at the walls, calc min/max value
      my $rc = $self->_rel($c); 	# avoid recalc of rel(c)
      my $min = $rc-$abs/2+$self->{space};
      my $max = $rc+$abs/2-$self->{space};
      #print " self space: ",$self->{space},"\n";
      #print " min: ",$min,"\n";
      #print " max: ",$max,"\n";
      #print "abs $abs\n";
      # now multiply the range from -0.5 to 0.5 with that size and add it
      # to base coordinate
      $abs = ($self->{val} - 0.5) * $abs + $rc;
      #print " self val: ",$self->{val}-0.5,"\n";
      #print " self rel c: ",$rc,"\n";
      #print "abs $abs\n";
      # if an offset is defined, add it
      $abs += $self->{ofs};
      # now confine to be min <= abs <= max
      range($min,$max,\$abs);
      }
    else
      {
      $abs += $self->{val};
      }
    }
  # some bounds checking
  $abs = $self->{min} if (defined $self->{min}) && ($abs < $self->{min});
  $abs = $self->{max} if (defined $self->{max}) && ($abs > $self->{max});
  return $abs;
  }
  
1; 

__END__

#############################################################################

=pod

=head1 NAME

CoW::Coord - a coordinate that can be relative to another Coord.

=head1 SYNOPSIS

        use CoW::Coord qw/ALIGN_INSIDE ALIGN_CENTER ALIGN_OUTSIDE/;

        $coord = new CoW::Coord ( rel => '_parent::x', ofs => 5 );
        print $coord->absolute();       # coords absolute value based on rel

        $coord = new CoW::Coord ( rel => '_parent::l,_parent::x',
          val => 1.0, space => 1, align => ALIGN_INSIDE );
        print $coord->absolute();       # coords absolute value based on rel

=head1 REQUIRES

perl5.005, overload, L<Item|Item>

=head1 EXPORTS

Exports nothing.

=head1 DESCRIPTION

This module defines a coordinate that can be relative to
another value. See L<Angle|Angle> for relative angle coordinates.
C<Coord> overloads the stringify routine, so that you can use

        use CoW::Coord;

        $coord = new CoW::Coord ( rel => '', val => 50 );

        print $coord,"\n";              # prints "50\n";
        print $coord->absolute(),"\n";  # the same
        $simple = $coord->absolute();   # put 50 into simple

It also overloads multiply/add/subtract, so that

        $coord *= 2;
        print $coord,"\n";
        $coord += 20;
        print $coord,"\n";

work as expected. L<Thing|Thing> uses this for moving itself.

This is actually very powerfull, and thus complicated ;o) Here is how it works
in detail:

A coordinate is either:

=over 2

=item Absolute

        my $coord = new CoW::Coord ( val => 50 );

=item Relative by offset

A relative coordinate to another coordinate by having a simple offset:

        my $coord = new CoW::Coord ( rel => '_parent::x', ofs => 5 );

This would set the coord to parent's C<x> and add 5 to it. That is usefull for
things that are positioned with an offset to another thing. Of course it
makes only sense to offset a coordinate in the same axis. Having a coordinate
in X depend on the parents Y coordinate will produce wierd results, to say
the least.

=item Relative by confined to a certain space

In this case the C<val> for the coordinate ranges from 0..1. Whereas 0 means
left/top most, and 1 means right/bottom most.

Just think of a thing that is positioned inside the space of another thing,
f.i. a table that stands in a room, or a door embeded into the wall of a room.

The "space" the value actually is based on is defined by a couple of variables
that are detailed below.

=back

For the confined coordinate, the C<rel> has two values, separated by ','. The
first gives the size and the second the coordinate. In most cases this are
two value from the parent, that are in the same axis. F.i size could be the
parent's width, and coordinate the parent's C<y> field.

Also, the C<size> field holds my own size (mostly a relative value from the
thing the coordinate belongs to). In case of a table, that would be the table's
width. This is needed to correct the confined space so that the coordinate
can be aligned via the C<align> field as detailed below:

=head2 ALIGNMENT

C<align> can have the following value:

=over 2

=item ALIGN_CENTER

                |----------------------|
            |-------|   parents size
             oursize

For doors, windows etc.

=item ALIGN_INSIDE

                |----------------------|
                |-------|  parents size
                 oursize

For tables, stools, etc.

=item ALIGN_OUTSIDE

                |----------------------|
        |-------|   parents size
         oursize

For special purposes ;o)

=back

There is also the C<space> field, that gives an additional space that is
to be left from the wall. In case of ALIGN_CENTER this is ignored. For
ALIGN_OUTSIDE the space is added, for ALIGN_INSIDE subtracted. For instance
a space of one unit would yield:

                  |----------------------|
        |-------|      parents size
         oursize

                  |----------------------|
                    |-------|parents size
                     oursize

You can actually give an C<ofs> to a confined variable, too. This is added
as last step, but wether this makes sense remains unclear.

See also L<Item|Item>.

=head1 FIELDS

=over 2

=item ofs

Specifies an absolute offset, that is added to the coords absolute value.

=item size

For confined coordinates, specifies an absolute or relative value for
my own size.

=item align

Where to align the relative coordinate space.

=item space

How many space to leave from the wall.

=item rel

Specifies wether the L<value|val> is relative or absolut. If set to '',
the value is absolute, otherwise rel specifies the field where the value
is relativ to. Use '_parent:: to make it relative to the parent. Examples:

        $coord->rel('_parent::x');      # relative to parents width (aka to
                                        # $coord->{_parent}->{_parent}->{x}
        $coord->rel('_self::y);         # relative to own lenght (self is the
                                        # object the coord belongs to, aka
                                        # $coord->{_parent}->{y})
        $coord->rel('y');               # dito as last example

In most cases it does not make much sense to base a coordinate upon another
from 'self'.  Just think of what would happen if your table's Y is always
X+1.

For confined coordinates, C<rel> actually has two values, separated by ','.
The first is the size and the second the coordinate. In this case also
C<align>, C<space> and C<size> will be used:

        $coord->rel('_parent::w,_parent::y');

=item val

The actual value. Only for absolute and relative-by-offset coordinates.

=item size

My own size (given as rel). Only for confined coordinates.

=item space

My space from the wall. Only for confined coordinates.

=item snap

The actual value will always be snapped to the gridsize given by C<snap>. If
snap is 1, then C<absolute()> will always give you -1,0,1,2 etc, but never
0.5. The actual value will be rounded to the nearest snap value. This
implements the grid/gridsize in Dromed, think of it as a rounding factor.

A value of 0 (the default) means no snapping takes place.

Currently unimplemented.

=item min

The minimum absolute value. If the value after conversion to absolute is
smaller than C<min>, it will be set to C<max>.

=item max

The maximum absolute value. If the value after conversion to absolute is
greater than C<max>, it will be set to C<max>.

=back

=head1 METHODS

=head2 absolute

Return the absolute value of this coordinate based on it's rel value (this
might traverse the entire tree upwards up to the root).

        print $coord->absolute();

=head2 rel

Sets/gets the L<rel|rel> field, and does check wether the new C<rel> field
is valid.

=head2 val

Sets/gets the L<val|val> field and returns the new value.

=head2 scale

Multiply the coordinate by the given factor. Works for absolute as well as for
relative coordinates.

=head2 add & sub

Add/subtract the given offset to/from self. Works for absolute as well as for
relative coordinates. Note that for relative coodinates, you can only add
relative values (although the actual value you can add/subtract is unlimited):

        use CoW::Coord;
        use CoW::Thing;

        $c = new Cow::Coord ( rel => 'l', val => 0.5 );
        $t = new CoW::Thing ( l => 32, w => $c );
        print "$c\n";                                   # 16
        $c->add(1.5);                                   # set val to 1.5
        print "$c\n";                                   # 48

It is not possible to add f.i. absolut 16 to C<val>, since it is not known
which value has except by using C<absolute();> and this depends on the current
value of C<l>.

=head1 EXAMPLES

Since this is probably very confusing for you, here are some common examples:

        use CoW::Coord;

        $cell => new CoW::Cell ( x => 0, y => 0, z => 0, w => 128,
          l => 64, h => 16 );

The center of this cell is at 0,0,0. The left (east) wall is at -64,
the right at 64.  The ceiling at +8, the floor at -8, the north wall is at
-32, the south at 32.

You want a thing to stand at the center, it should be exactly 8x8x8 units big.
X,Y and Z will have the proper defaults:

	$thing = new CoW::Thing ( w => 8, l => 8, h => 8, );
	$cell->insert($thing);

You want the thing to be placed 5 units from the center to the north, and 2
units to the west. This will make the things center offset from the parents
center. Z has the usual default:
	
	$thing = new CoW::Thing ( w => 8, l => 8, h => 8, 
          x => CoW::Coord->new( rel => 'x', val => -5 ),
          y => CoW::Coord->new( rel => 'y', val => 2 ),
          );
	$cell->insert($thing);


=head1 BUGS

None known yet.

=head1 AUTHOR

Tels http://bloodgate.com in late 2000.

=cut

