#!/usr/bin/perl
#-------------------------------------------------------------------------------
# Vectors in two dimensions
# Philip R Brenan at gmail dot com, Appa Apps Ltd Inc., 2017
#-------------------------------------------------------------------------------

package Math::Vectors2;
our $VERSION = '20171008';
require v5.16;
use warnings FATAL => qw(all);
use strict;
use Carp qw(confess);
use Data::Table::Text qw(:all);

my $nearness = 1e-6;                                                            # Definition of near

sub zero1($)                                                                    # Check a scalar is near zero
 {my ($o) = @_;
  near1($o, 0)
 }

sub zero2($)                                                                    # Check a vectors is nearly the 0 vector
 {my ($o) = @_;
  near1($o->x, 0) && near1($o->y, 0)
 }

sub near1($$)                                                                   # Check two scalars are near each other
 {my ($o, $p) = @_;
  abs($p-$o) < $nearness
 }

sub near2($$)                                                                   # Check two vectors are near each other
 {my ($o, $p) = @_;
  $o->d($p) < $nearness
 }

#1 Methods                                                                      # Methods

genLValueScalarMethods(qw(x));                                                  # X component of vector
genLValueScalarMethods(qw(y));                                                  # Y component of vector

sub new($$)                                                                     # Create new vector from components
 {my ($x, $y) = @_;                                                             # X component, Y component
  bless {x=>$x, y=>$y}
 }

sub zeroAndUnits()                                                              # Create the useful vectors: o=(0,0), x=(1,0), y=(0,1)
 {map {&new(@$_)} ([0, 0], [1, 0], [0, 1])
 }

sub print($@)                                                                   # Print one or more vectors
 {my ($p, @p) = @_;                                                             # Vector to print, more vectors to print
  join ', ', map {'('.$_->x.','.$_->y.')'} @_
 }

sub values($)                                                                   # Return components of a vector as a list
 {my ($p) = @_;                                                                 # Vector
  ($p->x, $p->y)
 }

sub clone($)                                                                    # Clone a vector
 {my ($o) = @_;                                                                 # Vector to clone
  new($o->x, $o->y)
 }

sub plus($@)                                                                    # Add zero or more other vectors to the first vector
 {my ($o, @p) = @_;                                                             # First vector, other vectors
  my $q = $o->clone;
  for(@p)
   {$q->x += $_->x;
    $q->y += $_->y;
   }
  $q
 }

sub minus($@)                                                                   # Subtract zero or more vectors from the first vector
 {my ($o, @p) = @_;                                                             # First vector, other vectors
  my $q = $o->clone;
  for(@p)
   {$q->x -= $_->x;
    $q->y -= $_->y;
   }
  $q
 }

sub times($$)                                                                   # Multiply a vector by a scalar
 {my ($o, $m) = @_;                                                             # Vector, scalar to multiply by
  new($o->x * $m, $o->y * $m)
 }

sub l($)                                                                        # Length of a vector
 {my ($o) = @_;                                                                 # Vector
  sqrt($o->x**2 + $o->y**2)
 }

sub l2($)                                                                       # Length squared of a vector
 {my ($o) = @_;                                                                 # Vector
  $o->x**2 + $o->y**2
 }

sub d($$)                                                                       # Distance between the points identified by two vectors when placed on the same point
 {my ($o, $p) = @_;                                                             # Vector 1, vector 2
  sqrt(($o->x-$p->x)**2 + ($o->y-$p->y)**2)
 }

sub d2($$)                                                                      # Distance squared between the points identified by two vectors when placed on the same point
 {my ($o, $p) = @_;                                                             # Vector 1, vector 2
  ($o->x-$p->x)**2 + ($o->y-$p->y)**2
 }

sub n($)                                                                        # Normalize a copy of a vector
 {my ($o) = @_;                                                                 # Vector
  my $l = $o->l;
  $l == 0 and confess;
  new($o->x / $l, $o->y / $l)
 }

sub dot($$)                                                                     # Dot product of two vectors
 {my ($o, $p) = @_;                                                             # Vector 1, vector 2
  $o->x * $p->x + $o->y * $p->y
 }

# podDocumentation

=encoding utf-8

=head1 Name

Math::Vectors2 - Vectors in two dimensions

=head1 Synopsis

 use Math::Vectors2;

 my ($o, $x, $y) = Math::Vectors2::zeroAndUnits;
 ok $o->print($x, $y) eq '(0,0), (1,0), (0,1)';

 my $p1 = $x->times(3);
 my $p2 = $y->times(4);
 my $p  = $o->plus($p1, $p2);

 ok $p->print($p1, $p2) eq '(3,4), (3,0), (0,4)';
 ok $o->d($p) == 5;

Or more briefly:

 use Math::Vectors2;

 *v = *Math::Vectors2::new;

 ok v(3,4)->l == 5;

=head1 Description

The following sections describe the methods in each functional area of this
module.  For an alphabetic listing of all methods by name see L<Index|/Index>.



=head1 Methods

Methods

=head2 x :lvalue

X component of vector


=head2 y :lvalue

Y component of vector


=head2 new($$)

Create new vector from components

  1  $x  X component
  2  $y  Y component

Example:


  ok $o->print($x, $y) eq '(0,0), (1,0), (0,1)';

  ok near2(Math::Vectors2::new(0, 0), $o);


=head2 zeroAndUnits()

Create the useful vectors: o=(0,0), x=(1,0), y=(0,1)


Example:


  my ($o, $x, $y) = Math::Vectors2::zeroAndUnits;

  ok $o->print($x, $y) eq '(0,0), (1,0), (0,1)';


=head2 print($@)

Print one or more vectors

  1  $p  Vector to print
  2  @p  More vectors to print

Example:


  ok $o->print($x, $y) eq '(0,0), (1,0), (0,1)';


=head2 clone($)

Clone a vector

  1  $o  Vector to clone

Example:


  ok $o->print($P, $p1, $p2) eq '(0,0), (3,4), (3,0), (0,4)';

  my $p = $P->clone;

  ok $p->print($P) eq '(3,4), (3,4)';


=head2 add($@)

Add zero or more other vectors to the first vector

  1  $o  First vector
  2  @p  Other vectors

Example:


  my $P = $o->add($p1, $p2);

  ok $o->print($P, $p1, $p2) eq '(0,0), (3,4), (3,0), (0,4)';


=head2 minus($@)

Subtract zero or more vectors from the first vector

  1  $o  First vector
  2  @p  Other vectors

Example:


  ok $o->print($p, $p1, $p2) eq '(0,0), (3,4), (3,0), (0,4)';

  ok near2($o, $p->minus($p1, $p2));


=head2 times($$)

Multiply a vector by a scalar

  1  $o  Vector
  2  $m  Scalar to multiply by

Example:


  ok $o->print($x, $y) eq '(0,0), (1,0), (0,1)';

  my $p1 = $x->times(3);

  my $p2 = $y->times(4);

  ok $o->print($P, $p1, $p2) eq '(0,0), (3,4), (3,0), (0,4)';


=head2 l($)

Length of a vector

  1  $o  Vector

Example:


  ok $p->print($P) eq '(3,4), (3,4)';

  ok $p->l == 5;


=head2 d($$)

Distance between the points identified by two vectors when placed on the same point

  1  $o  Vector 1
  2  $p  Vector 2

Example:


  ok $o->print($p) eq '(0,0), (3,4)';

  ok $o->d($p) == 5;



=head1 Index


1 L<add|/add>

2 L<clone|/clone>

3 L<d|/d>

4 L<l|/l>

5 L<minus|/minus>

6 L<new|/new>

7 L<print|/print>

8 L<times|/times>

9 L<x|/x>

10 L<y|/y>

11 L<zeroAndUnits|/zeroAndUnits>

=head1 Installation

This module is written in 100% Pure Perl and, thus, it is easy to read, use,
modify and install.

Standard L<Module::Build> process for building and installing modules:

  perl Build.PL
  ./Build
  ./Build test
  ./Build install

=head1 Author

L<philiprbrenan@gmail.com|mailto:philiprbrenan@gmail.com>

L<http://www.appaapps.com|http://www.appaapps.com>

=head1 Copyright

Copyright (c) 2016-2017 Philip R Brenan.

This module is free software. It may be used, redistributed and/or modified
under the same terms as Perl itself.

=cut



# Tests and documentation

sub test
 {my $p = __PACKAGE__;
  binmode($_, ":utf8") for *STDOUT, *STDERR;
  return if eval "eof(${p}::DATA)";
  my $s = eval "join('', <${p}::DATA>)";
  $@ and die $@;
  eval $s;
  $@ and die $@;
 }

test unless caller;

1;
# podDocumentation
__DATA__
use Test::More tests => 22;

my ($o, $x, $y) = Math::Vectors2::zeroAndUnits;                                 #TzeroAndUnits
ok $o->print($x, $y) eq '(0,0), (1,0), (0,1)';                                  #TzeroAndUnits #Tnew #Tprint #Ttimes #Tdot #Td2

my $p1 = $x->times(3);                                                          #Ttimes
my $p2 = $y->times(4);                                                          #Ttimes
my $P = $o->plus($p1, $p2);                                                     #Tplus
ok $o->print($P, $p1, $p2) eq '(0,0), (3,4), (3,0), (0,4)';                     #Tclone #Tplus #Ttimes
my $p = $P->clone;                                                              #Tclone
ok $p->print($P) eq '(3,4), (3,4)';                                             #Tclone #Tl
ok $p->l == 5;                                                                  #Tl #Tn
ok $P->l == 5;

ok near1($p->x, 3);
ok near1($p->y, 4);
ok near2($p, $p);
ok $o->print($p) eq '(0,0), (3,4)';                                             #Td
ok $o->d($p) == 5;                                                              #Td
ok $p->d($o) == 5;

ok $o->print($p, $p1, $p2) eq '(0,0), (3,4), (3,0), (0,4)';                     #Tminus
ok near2($o, $p->minus($p1, $p2));                                              #Tminus

ok near2(Math::Vectors2::new(0, 0), $o);                                        #Tnew

ok near1($p->n->l, 1);                                                          #Tn

ok near1($x->dot($y), 0);                                                       #Tdot

if (1)
 {ok near1($x->d2($y), 2);                                                      #Td2
  ok near1($x->plus($x)->d2($y), 5);                                            #Td2
 }

if (1)
 {ok near1($x->plus($y)->l2, 2);                                                #Tl2
 }

if (1)
 {my ($x, $y) = Math::Vectors2::new(3, 4)->values;                              #Tvalues
  ok $x == 3 && $y == 4;                                                       #Tvalues
 }

if (1)
 {*v = *Math::Vectors2::new;
  ok v(3,4)->l         == 5;
  ok v(3,4)->d(v(0,0)) == 5;
 }
