# XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# $Id: Math.pm,v 1.6 2000/02/19 18:24:46 rich Exp $

package XRacer::Math;

use Exporter;

use Carp qw( confess );

@ISA = qw( Exporter );
@EXPORT = qw( midpoint distance normal unit_normal normalize magnitude
	      multiply_scalar_vector sum_vectors subtract_vectors
	      plane_coefficients distance_point_to_plane
	      distance_point_to_line intersection_of_two_planes
	      dot_product cross_product minimum_distance_between_two_lines
	      minimum_distance_between_line_and_line_segment
	      line_intersects_cylinder matrix_multiply
	      matrix_vector_multiply is_coplanar angle_between
	      magnitude_in_direction split_convex_face_in_plane
	      intersection_line_and_plane bbox );

use strict;

use POSIX qw( acos );

# Compute the midpoint (average) of a set of vertices.
sub midpoint
  {
    my $sum = sum_vectors (@_);
    my $nr_vertices = @_;
    my @r = map { $_ / $nr_vertices } @$sum;

    return \@r;
  }

# Compute the distance from point $a to point $b.
sub distance
  {
    my $a = shift;
    my $b = shift;

    return sqrt (($a->[0] - $b->[0]) * ($a->[0] - $b->[0]) +
		 ($a->[1] - $b->[1]) * ($a->[1] - $b->[1]) +
		 ($a->[2] - $b->[2]) * ($a->[2] - $b->[2]));
  }

# This function returns the normal vector to a plane (not the
# unit length normal vector!).
sub normal
  {
    my $plane = shift;

    return [ $plane->[0], $plane->[1], $plane->[2] ];
  }

# This function returns the unit length normal vector to a plane.
sub unit_normal
  {
    my $plane = shift;
    my $normal = normal ($plane);
    return normalize ($normal);
  }

# Normalize a vector (make it unit length).
sub normalize
  {
    my $vector = shift;
    my $w = magnitude ($vector);
    my @r = map { $_ / $w } @$vector;
    return \@r;
  }

# Return the magnitude of a vector.
sub magnitude
  {
    my $vector = shift;
    return sqrt ($vector->[0] * $vector->[0] +
		 $vector->[1] * $vector->[1] +
		 $vector->[2] * $vector->[2]);
  }

# Return the magnitude of vector v1 in direction vector v2.
sub magnitude_in_direction
  {
    my $v1 = shift;
    my $v2 = shift;

    my $v1n = normalize ($v1);
    my $v2n = normalize ($v2);

    return dot_product ($v1, $v2);
  }

# Return the angle between two vectors.
sub angle_between
  {
    return acos (magnitude_in_direction (@_));
  }

# Multiply a scalar and a vector.
sub multiply_scalar_vector
  {
    my $scalar = shift;
    my $vector = shift;
    my @r = map { $scalar * $_ } @$vector;

    return \@r;
  }

# Multiply two matrices.
sub matrix_multiply
  {
    my $a = shift;
    my $b = shift;

    my $p = [];

   for (my $i = 0; $i < 4; $i++)
     {
       my ($ai0, $ai1, $ai2, $ai3)
	 = ($a->[$i][0], $a->[$i][1], $a->[$i][2], $a->[$i][3]);
       $p->[$i][0] = $ai0 * $b->[0][0] + $ai1 * $b->[1][0] +
	 $ai2 * $b->[2][0] + $ai3 * $b->[3][0];
       $p->[$i][1] = $ai0 * $b->[0][1] + $ai1 * $b->[1][1] +
	 $ai2 * $b->[2][1] + $ai3 * $b->[3][1];
       $p->[$i][2] = $ai0 * $b->[0][2] + $ai1 * $b->[1][2] +
	 $ai2 * $b->[2][2] + $ai3 * $b->[3][2];
       $p->[$i][3] = $ai0 * $b->[0][3] + $ai1 * $b->[1][3] +
	 $ai2 * $b->[2][3] + $ai3 * $b->[3][3];
     }

    return $p;
  }

# Multiply matrix and vector.
sub matrix_vector_multiply
  {
    my $m = shift;
    my $v = shift;

    my $p = [];

    $p->[0] = $m->[0][0]*$v->[0] + $m->[0][1]*$v->[1] + $m->[0][2]*$v->[2] + $m->[0][3]*$v->[3];
    $p->[1] = $m->[1][0]*$v->[0] + $m->[1][1]*$v->[1] + $m->[1][2]*$v->[2] + $m->[1][3]*$v->[3];
    $p->[2] = $m->[2][0]*$v->[0] + $m->[2][1]*$v->[1] + $m->[2][2]*$v->[2] + $m->[2][3]*$v->[3];
    $p->[3] = $m->[3][0]*$v->[0] + $m->[3][1]*$v->[1] + $m->[3][2]*$v->[2] + $m->[3][3]*$v->[3];

    return $p;
  }

# Add a collection of vectors.
sub sum_vectors
  {
    my @sum = (0, 0, 0);
    foreach (@_)
      {
	$sum[0] += $_->[0];
	$sum[1] += $_->[1];
	$sum[2] += $_->[2];
      }
    return \@sum;
  }

# Compute: $vector0 - $vector1.
sub subtract_vectors
  {
    my $vector0 = shift;
    my $vector1 = shift;

    my @r = ();

    push @r, $vector0->[0] - $vector1->[0];
    push @r, $vector0->[1] - $vector1->[1];
    push @r, $vector0->[2] - $vector1->[2];

    return \@r;
  }

sub bbox
  {
    my ($min_x, $max_x, $min_y, $max_y, $min_z, $max_z)
      = ($_[0]->[0], $_[0]->[0],
	 $_[0]->[1], $_[0]->[1],
	 $_[0]->[2], $_[0]->[2]);

    shift;

    foreach (@_)
      {
	if ($_->[0] < $min_x) {
	  $min_x = $_->[0]
	} elsif ($_->[0] > $max_x) {
	  $max_x = $_->[0]
	}
	if ($_->[1] < $min_y) {
	  $min_y = $_->[1]
	} elsif ($_->[1] > $max_y) {
	  $max_y = $_->[1]
	}
	if ($_->[2] < $min_z) {
	  $min_z = $_->[2]
	} elsif ($_->[2] > $max_z) {
	  $max_z = $_->[2]
	}
      }

    return ($min_x, $max_x, $min_y, $max_y, $min_z, $max_z);
  }

# Compute the four coefficients of a plane which uniquely
# specify that plane, given three points (not colinear) on
# that plane. Most of the variables in this function are
# redundant, but they get it into the same form as I found
# it in Lansdown, p. 178.
sub plane_coefficients
  {
    my $p = shift;
    my $q = shift;
    my $r = shift;

    my $x2 = $p->[0];
    my $y2 = $p->[1];
    my $z2 = $p->[2];
    my $x1 = $q->[0];
    my $y1 = $q->[1];
    my $z1 = $q->[2];
    my $x3 = $r->[0];
    my $y3 = $r->[1];
    my $z3 = $r->[2];

    confess "not a plane (points: ($x1,$y1,$z1), ($x2,$y2,$z2), ($x3,$y3,$z3))"
      if ($x1 == $x2 && $y1 == $y2 && $z1 == $z2) ||
	 ($x1 == $x3 && $y1 == $y3 && $z1 == $z3) ||
	 ($x3 == $x2 && $y3 == $y2 && $z3 == $z2);

    my $xa = $x1 + $x2;
    my $xb = $x2 + $x3;
    my $xc = $x3 + $x1;
    my $ya = $y1 + $y2;
    my $yb = $y2 + $y3;
    my $yc = $y3 + $y1;
    my $za = $z1 + $z2;
    my $zb = $z2 + $z3;
    my $zc = $z3 + $z1;

    my @co = ();
    $co[0] = ($y1-$y2) * $za + ($y2-$y3) * $zb + ($y3-$y1) * $zc;
    $co[1] = ($z1-$z2) * $xa + ($z2-$z3) * $xb + ($z3-$z1) * $xc;
    $co[2] = ($x1-$x2) * $ya + ($x2-$x3) * $yb + ($x3-$x1) * $yc;
    $co[3] = - ($co[0]*$x1 + $co[1]*$y1 + $co[2]*$z1);

    return \@co;
  }

# Return true if the four points given are coplanar, else false.
sub is_coplanar
  {
    my $a = shift;
    my $b = shift;
    my $c = shift;
    my $d = shift;

    my $c1 = plane_coefficients ($a, $b, $c);
    my $c2 = plane_coefficients ($a, $b, $d);

    return $c1->[0] == $c2->[0] && $c1->[1] == $c2->[1] &&
           $c1->[2] == $c2->[2] && $c1->[3] == $c2->[3];
  }

# Construct a line segment object.
sub line_segment
  {
    my $point0 = shift;
    my $point1 = shift;

    my %lineseg = ( 'p' => $point0,
		    'v' => subtract_vectors ($point1, $point0),
		    'a' => 0,
		    'b' => 1 );

    return \%lineseg;
  }

# Distance from a point to a plane.
sub distance_point_to_plane
  {
    my $plane = shift;
    my $point = shift;

    my $a = $plane->[0];
    my $b = $plane->[1];
    my $c = $plane->[2];
    my $d = $plane->[3];
    my $x = $point->[0];
    my $y = $point->[1];
    my $z = $point->[2];
    my $denom = $a*$a + $b*$b + $c*$c;
    confess "denom = $denom, plane = [$a, $b, $c, $d]" if $denom == 0;
    my $t = ($a*$x + $b*$y + $c*$z + $d) / -$denom;
    my $t2 = $t*$t;
    my $dist = sqrt ($t2*$a*$a + $t2*$b*$b + $t2*$c*$c);
    # Don't lose the sign of t.
    if ($t < 0) { return $dist; } else { return -$dist; }
  }

# Distance from a point to a line.
sub distance_point_to_line
  {
    my $line = shift;
    my $point = shift;

    # Normalize the vector u along the line.
    my $u = normalize ($line->{v});

    # The distance is given by: | (p-a) x u |
    # where p is the point, a is a point on the line, and x is cross product.
    my $dist = magnitude (cross_product (subtract_vectors ($point, $line->{p}),
					 $u));

    return $dist;
  }

# This function calculates the equation of the line which forms
# the intersection of two planes. If the two planes are coplanar
# or parallel, then undef is returned.
sub intersection_of_two_planes
  {
    my $plane0 = shift;
    my $plane1 = shift;

    my $normal0 = normal ($plane0);
    my $normal1 = normal ($plane1);

    my $n00 = dot_product ($normal0, $normal0);
    my $n01 = dot_product ($normal0, $normal1);
    my $n11 = dot_product ($normal1, $normal1);
    my $det = $n00 * $n11 - $n01 * $n01;

    # Coplanar or parallel planes.
    return undef if abs ($det) < 1e-6;

    my $invdet = 1 / $det;

    my $c0 = ($n11 * $plane0->[3] - $n01 * $plane1->[3]) * $invdet;
    my $c1 = ($n00 * $plane1->[3] - $n01 * $plane0->[3]) * $invdet;

    return { v => cross_product ($normal0, $normal1),
	     p => sum_vectors (multiply_scalar_vector ($c0, $normal0),
			       multiply_scalar_vector ($c1, $normal1)) };
  }

# Calculate the point where a line intersects a plane.
sub intersection_line_and_plane
  {
    my $plane = shift;
    my $line = shift;

    my $denom = ($plane->[0] * $line->{v}->[0] +
		 $plane->[1] * $line->{v}->[1] +
		 $plane->[2] * $line->{v}->[2]);

    return undef if $denom == 0; # Parallel.

    my $t = - ($plane->[0] * $line->{p}->[0] +
	       $plane->[1] * $line->{p}->[1] +
	       $plane->[2] * $line->{p}->[2] +
	       $plane->[3]) / $denom;
    my $point = [ $line->{p}->[0] + $t * $line->{v}->[0],
		  $line->{p}->[1] + $t * $line->{v}->[1],
		  $line->{p}->[2] + $t * $line->{v}->[2] ];

    return $point;
  }

# Split a convex face in a plane.
sub split_convex_face_in_plane
  {
    my $face = shift;
    my $plane = shift;

    my $i;

    my @distance = map { distance_point_to_plane ($plane, $_) } @$face;

    # Compare adjacent vertices to find out which ones cross the plane.
    my @splits = ();
    my @split_point = ();

    for ($i = 0; $i < @$face; ++$i)
      {
	my $j = $i+1;
	if ($j >= @$face) { $j -= @$face }

	if (($distance[$i] >= 0 && $distance[$j] <  0) ||
	    ($distance[$i] <  0 && $distance[$j] >= 0))
	  {
	    # Record the split location (eg. [0,1] here indicates that
	    # we are splitting the edge from vertex 0 to vertex 1.
	    push @splits, [ $i, $j ];

	    # Find the point along this edge where we will split. However,
	    # if the point is actually coincident with one of the end
	    # vertices, then we have to fudge it slightly by splitting
	    # close to that end vertex.
	    my $splitting_point;
	    my $point_i = $face->[$i];
	    my $point_j = $face->[$j];

	    if (abs ($distance[$i]) > 0.001 && abs ($distance[$j]) > 0.001)
	      {
		# Normal case: split somewhere in the middle.
		my $lineseg = line_segment ($point_i, $point_j);

		$splitting_point
		  = intersection_line_and_plane ($plane, $lineseg)
		  or confess "strange: line and plane are parallel";
	      }
	    elsif (abs ($distance[$i]) <= 0.001)
	      {
		# Split near to vertex $i.
		$splitting_point
		  = sum_vectors (multiply_scalar_vector (0.99, $point_i),
				 multiply_scalar_vector (0.01, $point_j));
	      }
	    else # abs ($distance[$j]) <= 0.001
	      {
		# Split near to vertex $j.
		$splitting_point
		  = sum_vectors (multiply_scalar_vector (0.01, $point_i),
				 multiply_scalar_vector (0.99, $point_j));
	      }

	    push @split_point, $splitting_point;
	  }
      }

    if (@splits != 2)
      {
	print STDERR "\@splits = (", join (", ", @splits), ")\n";
	print STDERR "faces = (", join (", ", @$face), ")\n";
	confess;
      }

    die if @splits != @split_point;

    # Construct the first face.
    my @face0 = ();

    for ($i = 0; $i <= $splits[0][0]; ++$i)
      {
	push @face0, $face->[$i];
      }
    push @face0, $split_point[0];
    push @face0, $split_point[1];
    if ($splits[1][1] != 0)
      {
	for ($i = $splits[1][1]; $i <= @$face-1; ++$i)
	  {
	    push @face0, $face->[$i];
	  }
      }

    foreach (@face0) { confess "splits = ",
			 $splits[0][0], ",",
			 $splits[0][1], ":",
			 $splits[1][0], ",",
			 $splits[1][1], ", nr faces = ",
			 0+@$face, ", \@face0 = ",
			 0+@face0
			 if !defined $_ }

    # Construct the second face.
    my @face1 = ();

    for ($i = $splits[0][1]; $i <= $splits[1][0]; ++$i)
      {
	push @face1, $face->[$i];
      }
    push @face1, $split_point[1];
    push @face1, $split_point[0];

    foreach (@face1) { confess if !defined $_ }

    # Which one is which?
    my ($inside_face, $outside_face);

    if ($distance[0] >= 0)
      {
	$inside_face = \@face0;
	$outside_face = \@face1;
      }
    else
      {
	$outside_face = \@face0;
	$inside_face = \@face1;
      }

    return ($inside_face, $outside_face);
  }

# Compute the dot product of two vectors.
sub dot_product
  {
    my $vector0 = shift;
    my $vector1 = shift;

    return $vector0->[0] * $vector1->[0] +
           $vector0->[1] * $vector1->[1] +
           $vector0->[2] * $vector1->[2];
  }

# Compute the cross product of two vectors.
sub cross_product
{
  my $v = shift;
  my $w = shift;
  my @r = ();

  push @r, $v->[1]*$w->[2] - $v->[2]*$w->[1];
  push @r, $v->[2]*$w->[0] - $v->[0]*$w->[2];
  push @r, $v->[0]*$w->[1] - $v->[1]*$w->[0];

  return \@r;
}

# Compute the minimum distance between two lines.
sub minimum_distance_between_two_lines
  {
    my $line0 = shift;
    my $line1 = shift;

    my $diff = subtract_vectors ($line0->{p}, $line1->{p});
    my $a =   dot_product ($line0->{v}, $line0->{v});
    my $b = - dot_product ($line0->{v}, $line1->{v});
    my $c =   dot_product ($line1->{v}, $line1->{v});
    my $d =   dot_product ($line0->{v}, $diff);

    my $f =   dot_product ($diff, $diff);

    my $det = abs ($a*$c - $b*$b);

    if ($det >= 1e-6)		# Not parallel.
      {
	my $e = - dot_product ($line1->{v}, $diff);
	my $invdet = 1 / $det;
	my $s = ($b*$e - $c*$d) * $invdet;
	my $t = ($b*$d - $a*$e) * $invdet;

	return sqrt (abs ($s * ($a*$s + $b*$t + 2*$d) +
			  $t * ($b*$s + $c*$t + 2*$e) + $f));
      }
    else			# Parallel.
      {
	my $s = -$d / $a;
	my $t = 0;
	return sqrt (abs ($d * $s + $f));
      }
  }

# Return the minimum distance between a line and a line segment.
sub minimum_distance_between_line_and_line_segment
  {
    my $line;
    my $lineseg;

    # XXXXX






  }

# Return true if line intersects cylinder.
sub line_intersects_cylinder
  {
    my $line = shift;
    my $cylinder = shift;

    return minimum_distance_between_line_and_line_segment ($line, $cylinder)
      <= $cylinder->{radius};
  }

1;
__END__

=head1 NAME

XRacer::Math - Library of miscellaneous 3D math functions used by XRacer tools.

=head1 SYNOPSIS

  use XRacer::Math;

  Basic mathematics:

  $midpoint = midpoint (@vertices);
  $normalize_vector = normalize ($vector);
  $magnitude = magnitude ($vector);
  $magnitude = magnitude_in_direction ($v1, $v2);
  $angle = angle_between ($v1, $v2);
  $vector = multiply_scalar_vector ($scalar, $vector);
  $matrix = matrix_multiply ($a, $b);
  $vector = matrix_vector_multiply ($m, $v);
  $sum = sum_vectors (@vectors);
  $vector = subtract_vectors ($vector1, $vector2);
  ($min_x, $max_x, $min_y, $max_y, $min_z, $max_z) = bbox (@vertices);

  Dot products and cross products:

  $product = dot_product ($vector1, $vector2);
  $vector = cross_product ($vector1, $vector2);

  Distances:

  $distance = distance ($point1, $point2);
  $distance = distance_point_to_line ($line, $point);
  $distance = distance_point_to_plane ($plane, $point);
  $distance = minimum_distance_between_two_lines ($line1, $line2);

  Planes:

  $plane = plane_coefficients ($point1, $point2, $point3);
  $boolean_result = is_coplanar ($point1, $point2, $point3, $point4);
  $normal = normal ($plane);
  $unit_normal = unit_normal ($plane);
  $line = intersection_of_two_planes ($plane1, $plane2);
  $point = intersection_line_and_plane ($plane, $line);
  ($inside_face, $outside_face) = split_convex_face_in_place ($face, $plane);

  Line segments:

  $lineseg = line_segment ($point1, $point2);

  Cylinders:

  $boolean_result = line_intersects_cylinder ($line, $cylinder);

=head1 DESCRIPTION

This library contains miscellaneous functions which are used
by the XRacer track building tools. The functions perform
various 3D mathematical operations on points (vertices),
vectors, lines and planes.

Points or vertices are represented by a reference to an array
containing three elements. That is to say, to initialize a
point, do this:

  $point = [ $x, $y, $z ];

Vectors are represented by a reference to an array containing
three elements:

  $vector = [ $dx, $dy, $dz ];

Lines are represented by the formula:

  p + t.v

where p is a point on the line and v is a vector parallel
to the line. Therefore a line is simply a reference to a
hash containing the point and vector:

  $line = {'p' => $point, 'v' => $vector};

A line segment is represented by the formula:

  p + t.v where a <= t <= b

implying the following representation:

  $lineseg = { 'p' => $point, 'v' => $vector, 'a' => $a, 'b' => $b };

Notice that you can use a line segment wherever you
would use a line.

Cylinders are represented by the hash reference:

  $cylinder = { 'radius' => $radius,
                'p' => $point, 'v' => $vector, 'a' => $a, 'b' => $b };

where C<$radius> is the radius of the cylinder, and
the other fields are a line segment running from the midpoint
of one end of the cylinder to the midpoint of the other
end of the cylinder. In other words, a cylinder is simply
a line segment with added radius field.

Planes are represented by the formula:

  ax + by + cz + d = 0

where a, b, c and d are coefficients which uniquely define
the plane. A plane is a reference to an array containing
these four constants:

  $plane = [ $a, $b, $c, $d ];

Use the C<plane_coefficients> function to
compute the four coefficients of a plane, given any
three points (not colinear) on the plane.

The following page was enormously useful in working
out these formulae: C<http://www.magic-software.com/ComputerGraphics.html>

=head1 PACKAGE FUNCTIONS

=over 4

=item $midpoint = midpoint (@vertices);

Compute the midpoint (average) of a list of vertices.

=item $distance = distance ($point1, $point2);

Compute the distance between two points.

=item $plane = plane_coefficients ($point1, $point2, $point3);

Compute the four coefficients of a plane. The three points
given as parameters lie on the plane, but must not be
colinear. Returns a reference to the array of four coefficients.

=item $boolean_result = is_coplanar ($point1, $point2, $point3, $point4);

Return true if the four points given are coplanar.

=item $distance = distance_point_to_line ($line, $point);

Compute the shortest distance from a point to a line.

=item $distance = distance_point_to_plane ($plane, $point);

Compute the shortest distance from a point to a plane. This function
preserves the sign of the distance, so C<$distance> will be
I<positive> if the point is above/inside the plane and
I<negative> if the point is below/outside the plane.

=item $normal = normal ($plane);

Compute the normal vector to the plane. This vector is
not unit length (see the C<unit_normal> function for that).

=item $unit_normal = unit_normal ($plane);

Compute the unit length normal vector to a plane.

=item $normalize_vector = normalize ($vector);

Take a vector and return a normalized vector, of unit length.

=item $magnitude = magnitude ($vector);

Return the magnitude of a vector.

=item $magnitude = magnitude_in_direction ($v1, $v2);

Compute the magnitude of vector v1 in direction vector v2.
If v1 == v2, then this returns 1. If v1 = -v2, this returns
-1. If v1 is perpendicular to v2, this returns 0.

=item $angle = angle_between ($v1, $v2);

Return the angle (in radians) between two vectors.

=item $vector = multiply_scalar_vector ($scalar, $vector);

Multiply a scalar and a vector and return the result.

=item $matrix = matrix_multiply ($a, $b);

Multiply two matrices 4x4 together. The resulting matrix is returned.

=item $vector = matrix_vector_multiply ($m, $v);

Multiply a 4x1 vector and a 4x4 matrix together. The resulting
4x1 vector is returned.

=item $sum = sum_vectors (@vectors);

Sum a collection of vectors and return the result.

=item $line = intersection_of_two_planes ($plane1, $plane2);

Compute the intersection (a line) of two planes. If the
two planes are coplanar or parallel, then this function
will return undef.

=item $point = intersection_line_and_plane ($plane, $line);

Compute the intersection of a line with a plane. This returns
a point. If the line and plane are parallel, it returns undef.

=item ($inside_face, $outside_face) = split_convex_face_in_place ($face, $plane);

Given a convex face, specified as follows:

  $face = [ $vertex0, $vertex1, ... ];

we split the face into two faces. The first face returned is
inside the plane. The second face returned is outside the
plane.

=item $vector = subtract_vectors ($vector1, $vector2);

Compute: C<$vector1 - $vector2>, and return the resulting vector.

=item ($min_x, $max_x, $min_y, $max_y, $min_z, $max_z) = bbox (@vertices);

Construct the bounding box (minima and maxima of each
coordinate) of a list of vertices.

=item $product = dot_product ($vector1, $vector2);

Compute the dot product (a scalar number) of the two vector arguments.

=item $vector = cross_product ($vector1, $vector2);

Compute the cross product (a vector) of the two vector arguments.

=item $distance = minimum_distance_between_two_lines ($line1, $line2);

Compute the minimum distance between two lines.

=item $lineseg = line_segment ($point1, $point2);

Construct a line segment from C<$point1> to C<$point2>.

=item $boolean_result = line_intersects_cylinder ($line, $cylinder);

Returns true if the C<$line> intersects the C<$cylinder> at any point.

=back

=head1 AUTHOR

  Richard W.M. Jones, <rich@annexia.org>

=head1 COPYRIGHT

XRacer is copyright (C) 1999-2000 Richard W.M. Jones (rich@annexia.org)
and other contributors listed in the AUTHORS file.

=head1 SEE ALSO

L<perl(1)>, L<xracer(6)>.

=cut
