http://qs321.pair.com?node_id=132457
Category: Math
Author/Contact Info Mark Stratman (count0) mark@sporkstorms.org
Description: This is a module I was using for basic vector operations. I cleaned it up a bit and documented it for (possible) CPAN submission. I'd be most grateful =) if anyone would be so kind as to provide feedback. Some of the areas I was questionable about were:
  • The namespace.. perhaps Physics::Vector might be more appropriage?
  • The extensive operator overloading.. and more specifically, overloading '.' and 'x' with operations that are completely different than the default '.' and 'x' operators.
  • Anything you'd care to comment on! =)
#
#
# An object oriented module for working with vectors.
# Note that 'scalar' refers to the mathmatical definition, not Perl's 
+;)
#

package Math::Vector;
use strict;
use Carp;
use Math::Trig;

use vars qw($VERSION);
$VERSION = '0.01';

use overload
    "="  => \&assign,
    "+=" => \&add_assign,
    "-=" => \&subtract_assign,
    "+"  => \&add,
    "-"  => \&subtract,
    "x"  => \&cross_product,
    "."  => \&dot_product,
    "*"  => \&multiply_scalar,
    "/"  => \&divide_scalar,
    "*=" => \&multiply_scalar_assign,
    "/=" => \&divide_scalar_assign;


sub new {
    my ($package, %args) = @_;

    return bless {
        _i => $args{i} || 0,
        _j => $args{j} || 0,
        _k => $args{k} || 0,
    }, $package;
}


# $v_1 = $v_2;
sub assign {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    $self->set_i($other->i);
    $self->set_j($other->j);
    $self->set_k($other->k);

    return $self;
}


# $v_1 += $v_2;
sub add_assign {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    $self->set_i($other->i + $self->i);
    $self->set_j($other->j + $self->j);
    $self->set_k($other->k + $self->k);

    return $self;
}


# $v_1 -= $v_2;
sub subtract_assign {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    $self->set_i($other->i - $self->i);
    $self->set_j($other->j - $self->j);
    $self->set_k($other->k - $self->k);

    return $self;
}


# $v_new = $v_1 + $v_2;
# $v_new = $v_1->add($v_2);
sub add {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    return new Math::Vector (
        i => $self->i + $other->i,
        j => $self->j + $other->j,
        k => $self->k + $other->k,
    );
}


# $v_new = $v_1 - $v_2;
# $v_new = $v_1->subtract($v_2);
sub subtract {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    return new Math::Vector (
        i => $self->i - $other->i,
        j => $self->j - $other->j,
        k => $self->k - $other->k,
    );
}


# $v *= $scalar;
sub multiply_scalar_assign {
    my ($self, $scalar) = @_;

    $self->set_i($self->i * $scalar);
    $self->set_j($self->j * $scalar);
    $self->set_k($self->k * $scalar);

    return $self;
}


# $v /= $scalar;
sub divide_scalar_assign {
    my ($self, $scalar) = @_;
    carp ('Cannot divide by zero') && return undef unless ($scalar);

    $self->set_i($self->i / $scalar);
    $self->set_j($self->j / $scalar);
    $self->set_k($self->k / $scalar);

    return $self;
}


# $v_new = $v_1 * $scalar;
# $v_new = $v_1->multiply_scalar($scalar);
sub multiply_scalar {
    my ($self, $scalar) = @_;

    return new Math::Vector (
        i => $self->i * $scalar,
        j => $self->j * $scalar,
        k => $self->k * $scalar,
    );
}


# $v_new = $v_1 / $scalar;
# $v_new = $v_1->divide_scalar($scalar);
sub divide_scalar {
    my ($self, $scalar) = @_;
    carp ('Cannot divide by zero') && return undef unless ($scalar);

    return new Math::Vector (
        i => $self->i / $scalar,
        j => $self->j / $scalar,
        k => $self->k / $scalar,
    );
}


# $v_new = $v_1 x $v_2;
# $v_new = $v_1->cross_product($v_2);
sub cross_product {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    return new Math::Vector (
        i => $self->j * $other->k - $self->k * $other->j,
        j => $self->k * $other->i - $self->i * $other->k,
        z => $self->i * $other->j - $self->j * $other->i,
    );
}


# $dot_product = $v_1 . $v_2;
# $v_new = $v_1->dot_product($v_2);
sub dot_product {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    return $self->i * $other->i + $self->j * $other->j + $self->k * $o
+ther->k;
}


# $magnitude = $v->magnitude();
sub magnitude {
    my $self = shift;

    return sqrt($self->i * $self->i + $self->j * $self->j + $self->k *
+ $self->k);
}


# Converts the vector to its normal
# $v->normalize();
sub normalize {
    my $self = shift;

    my $m = $self->magnitude;
    carp('Cannot normalize a zero vector') && return undef unless ($m)
+;

    $self->set_i( $self->i / $m );
    $self->set_j( $self->j / $m );
    $self->set_k( $self->k / $m );
}


# $v_normalized = $v->normal();
sub normal {
    my $self = shift;

    my $normal = new Math::Vector (i=>$self->i, j=>$self->j, k=>$self-
+>k);
    $normal->normalize();

    return $normal;
}


# $angle_in_radians = $v_1->angle($v_2);
sub angle {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    my $self_m  = $self->magnitude;
    my $other_m = $other->magnitude;
    carp('Vectors must be non-zero') && return undef
        unless ($self_m && $other_m);

    return (acos (
        $self->dot_product($other) / ($self_m * $other_m)
    ));
}

sub orthogonal {
    my ($self, $other) = @_;
    carp ('Operation only permitted on Math::Vector objects') && retur
+n undef
        unless (ref $other eq 'Math::Vector');

    carp('Vectors must be non-zero') && return undef
        unless ($self->magnitude && $other->magnitude);

    # v1 is perpendicular to v2 if (v1 . v2 == 0)
    return ($self->dot_product($other) ? 0 : 1);
}

sub i { return $_[0]->{_i}; }
sub j { return $_[0]->{_j}; }
sub k { return $_[0]->{_k}; }

sub set_i {
    my $self = shift;
    $self->{_i} = shift || 0;
}

sub set_j {
    my $self = shift;
    $self->{_j} = shift || 0;
}

sub set_k {
    my $self = shift;
    $self->{_k} = shift || 0;
}

1;
__END__

=head1 NAME

Math::Vector - Object oriented vectors

=head1 SYNOPSIS

  use Math::Vector;
  my $vector = new Math::Vector (i => $i, j => $j, k => $k);

=head1 DESCRIPTION

C<Math::Vector> provides an object oriented approach to working with v
+ectors.

Vectors created with this module are three-dimentional, although
two-dimentional vectors are effectively achieved by omitting the 'k' c
+omponent
(which simply sets it to zero).

It is important to note that this module will carp() if operations inv
+olving
division are attempted on zero values.  A basic understanding of vecto
+rs is
expected, and the programmer should check the magnitude of a vector be
+fore
performing operations such as normalization or finding the angle.

=head2 Conventions used in this document

=over 10

The term 'scalar' is used in the mathmatical sense, and is a number re
+presenting
the amount by which a vector is scaled.

Vector objects will always begin with a 'v'. (Example: $v, $v_1, $v_ne
+w)

=back

=head1 OPERATORS

C<Math::Vector> objects override many default operators.

=item '='

$v_1 = $v_2;

=item '+'

$v_new = $v_1 + $v_2;

=item '+='

$v_1 += $v_2

=item '-'

$v_new = $v_1 - $v_2;

=item '-='

$v_1 -= $v_2;

=item 'x'

$v_cross_product = $v_1 x $v_2;

=item '.'

$dot_product = $v_1 . $v_2;

=item '*'

$v_new = $v_1 * $scalar_multiple

=item '*='

$v_1 *= $scalar_multiple; 

=item '/'

$v_new = $v_1 / $scalar_multiple

=item '/='

$v_1 /= $scalar_multiple; 

=head1 METHODS

=head2 Creation

=over 4

=item new Math::Vector (i => $i, j => $j, k => $k)

Creates a new vector object with components ($i, $j, $k).

=back

=head2 Access

=over 4

=item i

Returns the value of the 'i' (first) component of the vector.

=item j

Returns the value of the 'j' (second) component of the vector.

=item k

Returns the value of the 'k' (third) component of the vector.

=item ijk

Returns a list of the three component values.  This is included for co
+nvenience, and is the same as:

    ($v->i, $v->j, $v->k);

=back

=head2 Direct Modifications

=over 4

=item set_i (I)

Sets the value for the 'i' component of the vector.

    $v->set_i($new_i_value)

=item set_j (J)

Sets the value for the 'j' component of the vector.

    $v->set_j($new_j_value)

=item set_k (K)

Sets the value for the 'k' component of the vector.

    $v->set_k($new_k_value)

=back

=head2 Calculations

=over 4

=item dot_product (VECTOR)

Returns the dot product of two vectors.

    $dot_product = $v_1->dot_product( $v_2 );
    # Note this is equivalent:
    $dot_product = $v_1 . $v_2;

=item cross_product (VECTOR)

Returns the cross product of two vectors as a vector object.  Note: yo
+u cannot
take the cross product of 2d vectors.

    $v_cross = $v_1->cross_product( $v_2 );
    # Note this is equivalent:
    $v_cross = $v_1 x $v_2;

=item magnitude

Returns the magnitude of the vector.

    $magnitude = $v->magnitude;

=item normalize

Normalizes the vector.

    $v->normalize;

=item normal

Returns the normal of a vector as a new vector object.

    $v_normalized = $v->normal;
    # Note the following is similar, but does not preserve $v :
    $v->normalize; $v_normalized = $v;

=item angle (VECTOR)

Returns the angle in radians between two vectors.

    $angle = $v_1->angle( $v_2 );

=item orthogonal (VECTOR)

Returns 1 if vectors are orthogonal (or perpendicular), 0 if they are
not, and undef if either is a zero vector.

    $orthogonal = $v_1->orthogonal( $v_2 );
    if ($orthogonal) {
       # $v_1 and $v_2 are perpendicular
    } elsif (defined $orthogonal) {
       # they are not
    } else {
       # you tried to test a zero vector
    }

=back

=head1 SEE ALSO

Math::Trig,
Carp

=head1 BUGS

Please email the author (E<lt>mark@sporkstorms.orgE<gt>) with any.

=head1 FEEDBACK

Feel free to email the author with any questions, comments, or request
+s.
This module was initially written for personal use, and may be lacking
some desired features.  I (Mark) would be glad to add anything to it
that you might need.

=head1 AUTHOR

Mark Stratman, E<lt>mark@sporkstorms.orgE<gt>

=head1 COPYRIGHT

Copyright (c) 2001, Mark Stratman.  All Rights Reserved.
This modules is free software. It may be used, redistributed and/or
modified under the same terms as Perl itself.

=cut
#