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

package Math::Vectors2;
require v5.16;
our $VERSION = '20171009';
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 Attributes                                                                   # Attributes that can be set by =

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

#1 Methods                                                                      # Vector msethods.

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 a copy of the first vector and return the result.
 {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 a copy of the first vector and return the result.
 {my ($o, @p) = @_;                                                             # First vector, other vectors
  my $q = $o->clone;
  for(@p)
   {$q->x -= $_->x;
    $q->y -= $_->y;
   }
  $q
 }

sub times($$)                                                                   # Multiply a copy of a vector by a scalar and return the result.
 {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($)                                                                        # Return a normalized 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
 }

sub area($$)                                                                    # Signed area of the parallelogram defined by the two vectors. The area is negative if the second vector appears to the right of the first if they are both placed at the origin and the observer stands against the z-axis in a left handed coordinate system.
 {my ($o, $p) = @_;                                                             # Vector 1, vector 2
  $o->x * $p->y - $o->y * $p->x
 }

sub cos($$)                                                                     # cos(angle between two vectors) in radians.
 {my ($o, $p) = @_;                                                             # Vector 1, vector 2
  $o->dot($p) / $o->l / $p->l
 }

sub sin($$)                                                                     # sin(angle between two vectors) in radians.
 {my ($o, $p) = @_;                                                             # Vector 1, vector 2
  $o->area($p) / $o->l / $p->l
 }

sub r90($)                                                                      # Rotate a vector by 90 degrees anticlockwise.
 {my ($o) = @_;                                                                 # Vector to rotate
  new(-$o->y, $o->x)
 }

sub r180($)                                                                     # Rotate a vector by 180 degrees.
 {my ($o) = @_;                                                                 # Vector to rotate
  new(-$o->x, -$o->y)
 }

sub r270($)                                                                     # Rotate a vector by 270 degrees anticlockwise.
 {my ($o) = @_;                                                                 # Vector to rotate
  new($o->y, -$o->x)
 }

# 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 Attributes

Attributes that can be set by =

=head2 x :lvalue

X component of vector.


=head2 y :lvalue

Y component of vector.


=head1 Methods

Vector msethods.

=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 values($)

Return components of a vector as a list.

  1  $p  Vector

Example:


  my ($x, $y) = Math::Vectors2::new(3, 4)->values;

  ok $x == 3 && $y == 4;


=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 plus($@)

Add zero or more other vectors to a copy of the first vector and return the result.

  1  $o  First vector
  2  @p  Other vectors

Example:


  my $P = $o->plus($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 a copy of the first vector and return the result.

  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 copy of a vector by a scalar and return the result.

  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 l2($)

Length squared of a vector.

  1  $o  Vector

Example:


  ok near1($x->plus($y)->l2, 2);


=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;


=head2 d2($$)

Distance squared 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($x, $y) eq '(0,0), (1,0), (0,1)';

  ok near1($x->d2($y), 2);

  ok near1($x->plus($x)->d2($y), 5);


=head2 n($)

Return a normalized a copy of a vector.

  1  $o  Vector

Example:


  ok $p->l == 5;

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


=head2 dot($$)

Dot product of two vectors.

  1  $o  Vector 1
  2  $p  Vector 2

Example:


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

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


=head2 area($$)

Signed area of the parallelogram defined by the two vectors. The area is negative if the second vector appears to the right of the first if they are both placed at the origin and the observer stands against the z-axis in a left handed coordinate system.

  1  $o  Vector 1
  2  $p  Vector 2

Example:


  ok near1($x->plus($y)->area($x->times(2)), -2);

  ok near1($x->plus($y)->area($y->times(2)), +2);


=head2 cos($$)

cos(angle between two vectors) in radians.

  1  $o  Vector 1
  2  $p  Vector 2

Example:


  ok near1($x->cos($x), +1);

  ok zero1($x->cos($y));


=head2 sin($$)

sin(angle between two vectors) in radians.

  1  $o  Vector 1
  2  $p  Vector 2

Example:


  ok zero1($x->sin($x));

  ok near1($x->sin($y), +1);


=head2 r90($)

Rotate a vector by 90 degrees anticlockwise.

  1  $o  Vector to rotate

Example:


  ok near2($x->r90, $y);

  ok near2($y->r90, $o->minus($x));


=head2 r180($)

Rotate a vector by 180 degrees.

  1  $o  Vector to rotate

Example:


  ok near2($x->r90->r90, $x->r180);


=head2 r270($)

Rotate a vector by 270 degrees anticlockwise.

  1  $o  Vector to rotate

Example:


  ok near2($x->r90->r90->r90, $x->r270);



=head1 Index


1 L<area|/area>

2 L<clone|/clone>

3 L<cos|/cos>

4 L<d|/d>

5 L<d2|/d2>

6 L<dot|/dot>

7 L<l|/l>

8 L<l2|/l2>

9 L<minus|/minus>

10 L<n|/n>

11 L<new|/new>

12 L<plus|/plus>

13 L<print|/print>

14 L<r180|/r180>

15 L<r270|/r270>

16 L<r90|/r90>

17 L<sin|/sin>

18 L<times|/times>

19 L<values|/values>

20 L<x|/x>

21 L<y|/y>

22 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 => 42;

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
 }

ok near2($x->r90, $y);                                                          #Tr90
ok near2($y->r90, $o->minus($x));                                               #Tr90
ok near2($x->r90->r90, $x->r180);                                               #Tr180
ok near2($y->r90->r90, $y->r180);
ok near2($x->r90->r90->r90, $x->r270);                                          #Tr270
ok near2($y->r90->r90->r90, $y->r270);

ok near1($x->cos($x), +1);                                                      #Tcos
ok near1($y->cos($y), +1);
ok zero1($x->cos($y));                                                          #Tcos
ok zero1($y->cos($x));

ok zero1($x->sin($x));                                                          #Tsin
ok zero1($y->sin($y));
ok near1($x->sin($y), +1);                                                      #Tsin
ok near1($y->sin($x), -1);

ok near1($x->plus($y)->sin($x), -sqrt(1/2));
ok near1($x->plus($y)->sin($y), +sqrt(1/2));
ok near1($x->plus($y)->area($x->times(2)), -2);                                 #Tarea
ok near1($x->plus($y)->area($y->times(2)), +2);                                 #Tarea

ok near1($x->plus($y)->r90->area($x->plus($y)),  -2);
ok near1($x->plus($y)->r270->area($x->plus($y)), +2);

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