#!/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-mktrackscenery.pl,v 1.2 2000/01/16 16:17:01 rich Exp $

# It's rather hard to map textures onto the curving track in
# Blender, so this tool takes the track outline (a VideoScape-
# format) file and writes out code to generate the track
# segments, with the correct textures.
#
# We make some assumptions here that the segments and vertices
# match up.

use strict;

use Getopt::Long;

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

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

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

if ($help)
  {
    print STDERR "$0 --steps STEPS [--outputc OUTPUTFILE] [--verbose] --tubefile TUBEFILE [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 "       TUBEFILE is the tube file generated by mktube prog\n";
    print STDERR "       INPUTFILE is the input VideoScape file\n";
    exit 1;
  }

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

# Read the segments from the tubefile.
my $segmentsref = do $tubefilename
  or die "$tubefilename: $!";
my @segments = @$segmentsref;

print "number of segments in tube file: ", scalar (@segments), "\n"
  if $verbose;

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

	die "number of segments found does not match tube file"
	  if $nr_segments != @segments;

	$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.
if ($verbose)
  {
    print "number of vertices: ", scalar (@vertices), "\n";
    print "number of segments: $nr_segments\n";
  }

die if $nr_segments != @segments;

# Push the vertices into each segment.
for (my $i = 0; $i < @vertices; $i += $nr_steps)
  {
    my @vs = ();

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

    $segments[$i/$nr_steps]->{vertices} = \@vs;
  }

# Iterate over segments, collecting vertices together.
for (my $i = 0; $i < @segments-1; ++$i)
  {
    $segments[$i]->{next_vertices} = $segments[$i+1]->{vertices};
  }
$segments[$nr_segments-1]->{next_vertices} = $segments[0]->{vertices};

# Compute the actual texture coordinates now.
# See figure [vertices.fig] for an old explanation. Things are
# done slightly differently now.
my $segment;
foreach $segment (@segments)
  {
    my @vs0 = @{$segment->{vertices}};
    my @vs1 = @{$segment->{next_vertices}};

    my $v0 = $vs0[0];
    my $v1 = $vs0[@vs0-1];
    my $v2 = $vs1[@vs1-1];
    my $v3 = $vs1[0];

    # Edges of the beestripes.
    my $v4  = track_point (\@vs0, 0.02);
    my $v6  = track_point (\@vs0, 0.04);
    my $v8  = track_point (\@vs0, 0.96);
    my $v10 = track_point (\@vs0, 0.98);
    my $v5  = track_point (\@vs1, 0.02);
    my $v7  = track_point (\@vs1, 0.04);
    my $v9  = track_point (\@vs1, 0.96);
    my $v11 = track_point (\@vs1, 0.98);

    # Divvy up the rest of the track between 4% and 96% into
    # regular sized chunks of approx. 10%.
    my @ts = ();
    for (my $i = 0.04; $i <= 0.96; $i += 0.092)
      {
	push @ts, track_point (\@vs0, $i);
	push @ts, track_point (\@vs1, $i);
      }

    # Create a list of quads to draw and their texture coordinates (and
    # textures).
    my @varray = ();
    my @leftstrip = ();
    my @trackstrip = ();
    my @rightstrip = ();

    # Outer left beestripe.
    push @leftstrip,
    { 'v' => push_vertex (\@varray, $v0), 't' => [ 0, 0 ]},
    { 'v' => push_vertex (\@varray, $v3), 't' => [ 1, 0 ]},
    { 'v' => push_vertex (\@varray, $v4), 't' => [ 0, 1 ]},
    { 'v' => push_vertex (\@varray, $v5), 't' => [ 1, 1 ]};

    # Inner left beestripe.
    push @leftstrip,
    { 'v' => push_vertex (\@varray, $v6), 't' => [ 0, 0 ]},
    { 'v' => push_vertex (\@varray, $v7), 't' => [ 1, 0 ]};

    # Track itself.
    my $y = 0;
    while (@ts)
      {
	my $tv0 = shift @ts;
	my $tv1 = shift @ts;

	push @trackstrip,
	{ 'v' => push_vertex (\@varray, $tv0), 't' => [ 0, $y ]},
	{ 'v' => push_vertex (\@varray, $tv1), 't' => [ 1, $y ]};

	$y += 0.1;
      }

    # Inner right beestripe.
    push @rightstrip,
    { 'v' => push_vertex (\@varray, $v8), 't' => [ 0, 0 ]},
    { 'v' => push_vertex (\@varray, $v9), 't' => [ 1, 0 ]},
    { 'v' => push_vertex (\@varray, $v10), 't' => [ 0, 1 ]},
    { 'v' => push_vertex (\@varray, $v11), 't' => [ 1, 1 ]};

    # Outer right beestripe.
    push @rightstrip,
    { 'v' => push_vertex (\@varray, $v1), 't' => [ 0, 0 ]},
    { 'v' => push_vertex (\@varray, $v2), 't' => [ 1, 0 ]};

    # Save it all.
    $segment->{varray} = \@varray;
    $segment->{leftstrip} = \@leftstrip;
    $segment->{trackstrip} = \@trackstrip;
    $segment->{rightstrip} = \@rightstrip;
  }

# Save what we have to the C output file.
if ($coutputfilename)
  {
    open C, ">$coutputfilename"
      or die "$coutputfilename: $!";

    print C "/* This file is used to draw the track.\n * It is automatically generated.\n */\n\n#include \"common.h\"\n\n";

    print C "static int beestripes_tex, track_tex;\n\n";

    # Generate a function the draw each track segment.
    foreach $segment (@segments)
      {
	print C "static void\ndraw_track_seg_", $segment->{n}, " ()\n{\n";

	# Write out the vertex array.
	print C "  static GLfloat va[][3] = ",
	cinitializer (@{$segment->{varray}}),
	";\n";

	print C "  glEnableClientState (GL_VERTEX_ARRAY);\n";
	print C "  glVertexPointer (3, GL_FLOAT, 0, va);\n";

	print C "  glBindTexture (GL_TEXTURE_2D, beestripes_tex);\n";

	print C "  glBegin (GL_QUAD_STRIP);\n";
	print C quad_strip_to_string ($segment->{leftstrip});
	print C "  glEnd ();\n";

	print C "  glBegin (GL_QUAD_STRIP);\n";
	print C quad_strip_to_string ($segment->{rightstrip});
	print C "  glEnd ();\n";

	print C "  glBindTexture (GL_TEXTURE_2D, track_tex);\n";

	print C "  glBegin (GL_QUAD_STRIP);\n";
	print C quad_strip_to_string ($segment->{trackstrip});
	print C "  glEnd ();\n";

	print C "  glDisableClientState (GL_VERTEX_ARRAY);\n";

	print C "}\n\n";
      }

    # Some start-up and cleanup code to get the right textures.
    print C "int\ndraw_track_load ()\n";
    print C "{";
    print C "
  beestripes_tex = xrTextureLoad (\"track-textures/beestripes1.jpg\", 0, 0, 0, 1);
  if (beestripes_tex == 0)
    {
      xrLog (LOG_ERROR, \"cannot load texture: track-textures/beestripes1.jpg\");
      return -1;
    }

  track_tex = xrTextureLoad (\"track-textures/track5.jpg\", 0, 0, 0, 1);
  if (track_tex == 0)
    {
      xrLog (LOG_ERROR, \"cannot load texture: track-textures/track5.jpg\");
      return -1;
    }

  return 0;
";
    print C "}\n\n";

    print C "void\ndraw_track_unload ()\n";
    print C "{\n";
    print C "  xrTextureUnload (beestripes_tex);\n";
    print C "  xrTextureUnload (track_tex);\n";
    print C "}\n\n";

    # Array of segment drawing functions.
    print C "static void (*draw_seg_fn[])() = {\n";
    print C (join ", ", map { "draw_track_seg_" . $_->{n} } @segments ), "\n";
    print C "};\n\n";

    # Function to call the particular drawing function.
    print C "void\ndraw_track_display_seg (int seg)\n";
    print C "{\n";
    print C "  draw_seg_fn[seg] ();\n";
    print C "}\n\n";

    print C "/* EOF */\n";
  }

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 (@$_) : $_ }
			     @_)) . " }";
  }

sub quad_strip_to_string
  {
    my @strip = @{$_[0]};
    my $out = "";

    my $vertex;
    foreach $vertex (@strip)
      {
	my $t = $vertex->{t};
	my $v = $vertex->{v};

	$out .= "  glTexCoord2f (" . $t->[0] . ", " . $t->[1] . ");\n";
	$out .= "  glArrayElement (" . $v . ");\n";
      }

    return $out;
  }

sub track_point
  {
    my @vs = @{$_[0]};
    my $ratio = $_[1];

    # Work out total track length.
    my $total_len = 0;
    my $i;
    for ($i = 1; $i < @vs; ++$i)
      {
	$total_len += distance ($vs[$i-1], $vs[$i]);
      }

    # Work out ratio length.
    my $wanted_len = $ratio * $total_len;

    # Work out which part of the track this falls in.
    my $len = 0;
    for ($i = 1; $i < @vs; ++$i)
      {
	my $len1 = $len;
	$len += distance ($vs[$i-1], $vs[$i]);
	if ($len > $wanted_len)
	  {
	    # Must fall into $vs[$i-1] .. $vs[$i]. Work out
	    # how far along. $t is the parameter [0..1].
	    my $t = ($wanted_len - $len1) / ($len - $len1);

	    die "t = $t (total_len = $total_len, wanted_len = $wanted_len, len1 = $len1, len = $len)"
	      if $t < 0 || $t > 1;

	    # Interpolate.
	    my $p = sum_vectors ($vs[$i-1],
				 multiply_scalar_vector ($t,
				   subtract_vectors ($vs[$i], $vs[$i-1])));

	    return $p;
	  }
      }

    die;
  }

# Push vertex into vertex array, and return index.
sub push_vertex
  {
    my $vertex_array = $_[0];
    my $vertex = $_[1];

    for (my $i = 0; $i < @$vertex_array; ++$i)
      {
	if ($vertex->[0] == $vertex_array->[$i]->[0] &&
	    $vertex->[1] == $vertex_array->[$i]->[1] &&
	    $vertex->[2] == $vertex_array->[$i]->[2])
	  {
	    return $i;
	  }
      }

    push @$vertex_array, $vertex;
    return @$vertex_array-1;
  }
