package Urb::Math;

use strict;
use warnings;
use 5.010;
use Exporter;

use vars qw /@EXPORT_OK/;
use base qw /Exporter/;
@EXPORT_OK = qw /is_between_2d distance_2d angle_2d subtract_2d add_2d scale_2d normalise_2d
              subtract_3d
              gaussian
              is_angle_between triangle_area
              points_2line angle_2line perpendicular_line line_intersection perpendicular_distance/;

our $PI = atan2 (0,-1);

=head1 NAME

Urb::Math - Misc maths functions

=head1 SYNOPSIS

Some maths functions

=head1 DESCRIPTION

This module contains some exported maths functions.

=head1 USAGE

  use Urb::Math;

=head2 2d vector functions

=over

=item is_between_2d

Is a point on a line between two other points:

  say 'nope!' unless is_between_2d ($coor, $coor_a, $coor_b);

=cut

sub is_between_2d
{
    my ($point, @points) = @_;
    my $length = distance_2d ($points[0], $points[1]);
    my $length_a = distance_2d ($points[0], $point);
    my $length_b = distance_2d ($points[1], $point);
    return 1 if abs ($length - $length_a - $length_b) < 0.000001;
    return 0;
}

=item distance_2d

  $distance = distance_2d ($coor_a, $coor_b);

=cut

sub distance_2d
{
    my ($a, $b) = @_;
    return 0 unless (ref $a && ref $b);
    return sqrt (($a->[0] - $b->[0])**2 + ($a->[1] - $b->[1])**2);
}

=item angle_2d

  $radians = angle_2d ($coor_a, $coor_b);

=cut

sub angle_2d
{
    my ($a, $b) = @_;
    my $vector = subtract_2d ($b, $a);
    return atan2 ($vector->[1], $vector->[0]);
}

=item subtract_2d

  $vector_from_a_to_b = subtract_2d ($b, $a);

=cut

sub subtract_2d
{
    my ($a, $b) = @_;
    return [$a->[0] - $b->[0], $a->[1] - $b->[1]];
}

=item add_2d

  $vector_sum = add_2d ($a, $b);
  $vector_sum = add_2d ($a, $b, $c, $d);

=cut

sub add_2d
{
    my @vectors = @_;
    my $result = [0,0];
    for (@vectors)
    {
        $result->[0] += $_->[0];
        $result->[1] += $_->[1];
    }
    return $result;
}

=item scale_2d

  $vector_scaled = scale_2d (3.2, [2.1,4.3]);
  $vector_scaled = scale_2d ([2.1,4.3], 3.2);

=cut

sub scale_2d
{
    my ($factor, $vector) = @_;
    ($factor, $vector) = ($vector, $factor) if ref $factor;
    return [$vector->[0] * $factor, $vector->[1] * $factor];
}

=item normalise_2d

  $normal_vector = normalise_2d ([3,4]);

$normal_vector is [0.6,0.8]

=cut

sub normalise_2d
{
    my $vector = shift;
    my $length = distance_2d ([0,0], $vector);
    return [1,0] if $length == 0.0;
    return [$vector->[0] / $length, $vector->[1] / $length];
}

=item subtract_3d

  $vector = subtract_3d ([1,2,3], [4,5,6]);

=cut

sub subtract_3d
{
    my ($a, $b) = @_;
    return [$a->[0] - $b->[0], $a->[1] - $b->[1], $a->[2] - $b->[2]];
}

=head2 2d line functions

=over

=item points_2line

  $line = points_2line ([1,2], [3,4]);

$line is {a => 1, b => 1};

=cut

sub points_2line
{
    my ($coor_0, $coor_1) = @_;
    my $vector = subtract_2d ($coor_1, $coor_0);
    $vector->[0] = 0.00000000001 unless $vector->[0];
    my $a = $vector->[1] / $vector->[0];
    my $b = $coor_0->[1] - ($coor_0->[0] * $a);
    return {a => $a, b => $b};
}

=item angle_2line

  $line = angle_2line ($coor, $angle_radians);

=cut

sub angle_2line
{
    my ($coor_0, $angle) = @_;
    my $coor_1 = [$coor_0->[0] + cos $angle, $coor_0->[1] + sin $angle];
    return points_2line ($coor_0, $coor_1);
}

=item perpendicular_line

  $line = perpendicular_line ($line, $coor);

=cut

sub perpendicular_line
{
    my ($line, $coor) = @_;
    $line->{a} = 0.00000000001 unless $line->{a};
    my $a = -1/$line->{a};
    my $b = $coor->[1] - ($coor->[0] * $a);
    return {a => $a, b => $b};
}

=item line_intersection

  $coor = line_intersection ($line_a, $line_b);

=cut

sub line_intersection
{
    my ($line_0, $line_1) = @_;
    return if ($line_0->{a} == $line_1->{a});
    my $x = ($line_1->{b} - $line_0->{b}) / ($line_0->{a} - $line_1->{a});
    my $y = ($line_0->{a} * $x) + $line_0->{b};
    return [$x, $y];
}

=item perpendicular_distance

  $distance = perpendicular_distance ($line, $coor);

=back

=cut

sub perpendicular_distance
{
    my ($line, $coor) = @_;
    my $perpendicular_line = perpendicular_line ($line, $coor);
    my $intersection = line_intersection ($line, $perpendicular_line);
    return distance_2d ($coor, $intersection);
}

=head2 2d angle functions

=over

=item is_angle_between

  say 'nope!' unless is_angle_between (0.4, 0.1, 0.7);

=item triangle_area

  $area = triangle_area ($coor_a, $coor_b, $coor_c);

=back

=cut

sub is_angle_between
{
    my ($angle, $heading_a, $heading_b) = @_;
    $angle -= 2*$PI if $angle > $PI;
    my $angle_a = abs ($heading_a - $angle);
    $angle_a = (2*$PI) - $angle_a if ($angle_a > $PI);
    my $angle_b = abs ($heading_b - $angle);
    $angle_b = (2*$PI) - $angle_b if ($angle_b > $PI);
    my $angle_ab = abs ($heading_a - $heading_b);
    $angle_ab = (2*$PI) - $angle_ab if ($angle_ab > $PI);
    return 0 if ($angle_a + $angle_b - 0.000001 > $angle_ab);
    return 1;
}

sub triangle_area
{
    my ($a, $b, $c) = @_;

    my $A = distance_2d ($b, $c);
    my $B = distance_2d ($a, $c);
    my $C = distance_2d ($a, $b);
    my $S = ($A + $B + $C) / 2;
    return sqrt ($S * ($S - $A) * ($S - $B) * ($S - $C));
}

=head2 Misc functions

=over

=item gaussian

  $result = gaussian ($input_value, 1.0, $centre, $sigma);

=back

=cut

sub gaussian
{
    # $x = input value
    # $a_a = scale (usually 1.0)
    # $b_b = centre of peak
    # $c = sigma
    my ($x, $a_a, $b_b, $c) = @_;
    my $e = 2.718281828;
    return $a_a * ($e **(0- (($x-$b_b)**2/(2*$c*$c))));
}

1;
