#!/usr/bin/perl -w
# 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: xracer-mktube.pl,v 1.6 2000/01/02 14:13:32 rich Exp $

# You have made the basic tubular outline of the track in Blender,
# converted it to a mesh and saved it as a VideoScape file. This
# script takes that file and converts it to a list of roughly
# cylindrical segments. The result is saved as a suitable C
# file for the track, and also as an intermediate .tube file which
# is used by the other track processing scripts.

use strict;

use Getopt::Long;
use Data::Dumper;

use lib '../../XRacer/blib/lib'; # So you can run this without installing it.
use XRacer::Math;

# Read command line arguments.
my $nr_steps;
my $coutputfilename;
my $tubeoutputfilename;
my $verbose;
my $help;

GetOptions ("steps=i" => \$nr_steps,
	    "outputc=s" => \$coutputfilename,
	    "outputtube=s" => \$tubeoutputfilename,
	    "verbose" => \$verbose,
	    "help|?" => \$help);

if ($help)
  {
    print STDERR "$0 --steps STEPS [--outputc OUTPUTFILE] [--outputtube TUBEOUTPUTFILE] [--verbose] [INPUTFILE]\n";
    print STDERR "where: STEPS is the number of vertices in each segment\n";
    print STDERR "       OUTPUTFILE is the C file to write\n";
    print STDERR "       TUBEOUTPUTFILE is the .tube file to write\n";
    print STDERR "       INPUTFILE is the input VideoScape file\n";
    exit 1;
  }

die "--steps argument is required" if !$nr_steps;

# Read input lines.
my $state = "expect 3DG1";
my $vcount;
my $nr_segments;
my @vertices = ();

while (<>)
  {
    s/[\n\r]+$//g;		# Removes trailing CR, LF.

    if ($state eq "expect 3DG1")
      {
	die "expecting first line to be 3DG1" if $_ ne "3DG1";
	$state = "expect vcount";
      }
    elsif ($state eq "expect vcount")
      {
	die "expecting vertex count" if $_ !~ m/^[1-9][0-9]*$/;

	$vcount = $_;

	# Check that steps divides number of vertices.
	die "number of steps must divide number of vertices ($vcount)"
	  if ($vcount / $nr_steps != int ($vcount / $nr_steps));

	$nr_segments = $vcount / $nr_steps;

	$state = "reading vertices";
      }
    elsif ($state eq "reading vertices")
      {
	my @vs = split /[ \t]+/, $_;
	push @vertices, \@vs;
	$vcount--;
      }

    last if $state eq "reading vertices" && $vcount == 0;
  }

# Print a summary of the file.
print "number of vertices: ", scalar (@vertices), "\n" if $verbose;
print "number of segments: $nr_segments\n" if $verbose;

# Group the vertices by segment.
my @segments = ();

for (my $i = 0; $i < @vertices; $i += $nr_steps)
  {
    my @vs = ();

    for (my $j = $i; $j < $i + $nr_steps; $j++)
      {
	push @vs, $vertices[$j];
      }

    my %th = ('vertices' => \@vs,
	      'n' => $i / $nr_steps);

    push @segments, \%th;
  }

# Now process the vertices in each segment to fill in the rest
# of the information for each segment in the @segments array.
my $segment;
foreach $segment (@segments)
  {
    my $n = $segment->{n};
    my @vs = @{$segment->{vertices}};

    print "segment: $n, number of vertices: ", scalar (@vs), "\n" if $verbose;

    print "vertices: ", cinitializer (@vs), "\n" if $verbose;

    # Compute the midpoint of the enter plane.
    my $midpoint = XRacer::Math::midpoint (@vs);

    print "midpoint: $midpoint->[0], $midpoint->[1], $midpoint->[2]\n"
      if $verbose;

    # Compute the distance from each vertex to the midpoint. It
    # should be rougly the same. If not, then probably we are
    # not grouping the vertices correctly into segments (ie.
    # $nr_steps is wrong). Anyway, we should get the radius of
    # the tube this way.
    my $radius = XRacer::Math::distance ($vs[0], $midpoint);

    foreach (@vs)
      {
	my $d = XRacer::Math::distance ($_, $midpoint);

	if (abs ($radius - $d) > 0.01)
	  {
	    print STDERR "segment: $n: points do not form a circle\n";
	    print STDERR "  This probably means that the --steps argument is wrong.\n";
	    print STDERR "  Radius = $radius. Distance = $d.\n";
	    print STDERR "  Point = ",
	    $_->[0], ", ", $_->[1], ", ", $_->[2], "\n";
	    exit 1;
	  }
      }

    print "radius: $radius\n" if $verbose;

    # Calculate the enter plane. Assuming the points are coplanar,
    # we can just take ANY three points and compute the plane. However,
    # for more accuracy, take the midpoint and two points on the
    # edge which form a right angle.
    my $point1 = $vs[0];
    my $point2 = $vs[@vs/4];

    my $enterplane = XRacer::Math::plane_coefficients ($point1, $midpoint, $point2);

    print "enter plane coefficients: ",
    "$enterplane->[0], $enterplane->[1], $enterplane->[2], $enterplane->[3]\n"
      if $verbose;

    # Save all this information in the segment hash.
    $segment->{midpoint} = $midpoint;
    $segment->{radius} = $radius;
    $segment->{enterplane} = $enterplane;

    # Delete the vertices - don't need these any more, and we don't
    # particularly want them dumped out into the tube file.
    delete $segment->{vertices};
  }

# Write out the tube file.
if ($tubeoutputfilename)
  {
    open TUBE, ">$tubeoutputfilename"
      or die "$tubeoutputfilename: $!";

    print TUBE "# This is a TUBE file for XRacer. It is automatically generated.\n";

    print TUBE Dumper (\@segments);

    print TUBE "# End of file.\n";

    close TUBE;
  }

# Write the C file.
if ($coutputfilename)
  {
    open C, ">$coutputfilename"
      or die "$coutputfilename: $!";

    print C "/* This file describes the outline tube around the track.\n * It is automatically generated.\n */\n\n#include \"common.h\"\n\n";

    print C "int nr_segments = ", $nr_segments, ";\n";

    print C "GLfloat midpoints[][3] = ",
    cinitializer (map { $_->{midpoint} } @segments), ";\n";
    print C "GLfloat radii[] = ",
    cinitializer (map { $_->{radius} } @segments), ";\n";
    print C "GLfloat enterplanes[][4] = ",
    cinitializer (map { $_->{enterplane} } @segments), ";\n";

    print C "/* End of file. */\n";

    close C;

  }

exit 0;

#----------------------------------------------------------------------

# This small helper function takes a list of either numbers of
# array refs, and returns an equivalent C string for initializing
# a C multi-dimensional array or structure.
sub cinitializer
  {
    return "{ " . join (", ",
			map ({ ref ($_) eq 'ARRAY' ? cinitializer (@$_) : $_ }
			     @_)) . " }";
  }
