package Urb::Field;

use 5.010;
use strict;
use warnings;
use Urb::Math qw /subtract_3d/;
use YAML;

our $VERSION = 0.01;


=head1 NAME

Urb::Field - scalar/vector/tensor/ntuple Field in 3D space

=head1 SYNOPSIS

  use Urb::Field;

=head1 DESCRIPTION

Stub documentation for Urb::Field.

=over

=item new

Create a new Urb::Field object:

  $field1 = new Urb::Field;

..or add an overlay field:

  $field2 = new Urb::Field ($field1);

=cut

sub new
{
    my $class = shift;
    $class = ref $class || $class;
    my $self;

    my $overlay = shift || undef;
    $self = {_data => [], origin => [0.0,0.0,0.0], overlay => $overlay, nocache => undef};

    bless $self, $class;
    return $self;
}

=item Serialise

Dump to a YAML stream:

  $text_yaml = $field1->Serialise;

=cut

sub Serialise
{
    my $self = shift;
    my $data = {_data => $self->{_data}, origin => $self->{origin}};
    return YAML::Dump $data;
}

=item Deserialise

Initialise from a YAML stream:

  $field1->Deserialise ($text_yaml);

=cut

sub Deserialise
{
    my $self = shift;
    my $data = shift;
    my $temp = YAML::Load $data or return 0;
    $self->{_data} = $temp->{_data};
    $self->{origin} = $temp->{origin};
    return 1;
}

=item Get_int

Retrieve the value for an integer xyz point, expects integer coordinates:

  $float = $field->Get_int ([$int_x,$int_y,$int_z]);

=cut

sub Get_int
{
    my $self = shift;
    my $coor = shift;
    my ($x,$y,$z) = @{$coor};
    return if ($x<0 or $y<0 or $z<0);
    return unless defined $self->{_data}->[$x];
    return unless defined $self->{_data}->[$x][$y];
    return unless defined $self->{_data}->[$x][$y][$z];
    return $self->{_data}->[$x][$y][$z];
}

=item Get_weighted

Retrieve zero or more values with weights:

  @results = $field->Get_weighted ([$x,$y,$z]);

expects float coordinates, returns zero or more matches with weights

=cut

sub Get_weighted
{
    my $self = shift;
    my $coor = shift;
    $coor = subtract_3d ($coor, $self->{origin});
    my ($x_0,$y_0,$z_0) = map {int $_} (@{$coor});
    my @results;
    for my $x_int ($x_0, $x_0 +1)
    {
        for my $y_int ($y_0, $y_0 +1)
        {
            for my $z_int ($z_0, $z_0 +1)
            {
                my $item = $self->Get_int ([$x_int,$y_int,$z_int]);
                my $distance = _max_xyz ([$x_int,$y_int,$z_int], $coor);
                next if $distance > 1;
                push @results, [$item, 1-$distance] if defined $item;
            }
        }
    }

    my $total = 0;
    $total += $_->[1] for @results;
    return () unless $total;
    $_->[1] /= $total for @results;

    return @results;
}

=item Get_interpolated

Retrieve the value for an xyz point (fractional point coordinates will be
interpolated):

  $float = $field->Get_interpolated ([$x,$y,$z]);

=cut

sub Get_interpolated
{
    my $self = shift;
    my $coor = shift;
    my @results = $self->Get_weighted ($coor);
    my $result = undef;
    $result += $_->[0] * $_->[1] for @results;
    return $result;
}

sub _max_xyz
{
    my ($a, $b) = @_;
    my $max = 0;
    my $x = abs ($a->[0] - $b->[0]);
    my $y = abs ($a->[1] - $b->[1]);
    my $z = abs ($a->[2] - $b->[2]);
    $max = $x if $x > $max;
    $max = $y if $y > $max;
    $max = $z if $z > $max;
    return $max;
}

=back

=cut

1;

__END__

=head1 SEE ALSO

L<Urb>

L<Urb::Field::Occlusion>

=head1 AUTHOR

Bruno Postle <bruno@postle.net>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2013 by Bruno Postle

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.1 or,
at your option, any later version of Perl 5 you may have available.

