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

# This script is an intermediate hack that we will use until
# I have convinced Ton to add the ability to export meshes with
# texture coordinates to Blender. This script takes a simple
# VideoScape-format mesh, adds textures coordinates to it using
# a very simple algorithm, and exports it as a C file which you
# may link in as a scenery object to your track.

# This script is not very clever about how it generates polygons.
# It can only ever generate discrete triangles, quads and polygons,
# and someone should optimize it to generate triangle fans, triangle
# strips and quad strips where appropriate.

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 $coutputfilename;
my $texturefilename;
my $cprefix = "object";
my $texscale = 1.0;
my $rottexture = 0;
my $verbose;
my $help;

GetOptions ("outputc=s" => \$coutputfilename,
	    "texture=s" => \$texturefilename,
	    "texscale=f" => \$texscale,
	    "rottexture=i" => \$rottexture,
	    "cprefix=s" => \$cprefix,
	    "verbose" => \$verbose,
	    "help|?" => \$help);

if ($help)
  {
    print STDERR "$0 [--outputc OUTPUTFILE] --texture TEXTURE [--texscale TEXSCALE] [--rottexture ROTATION] [--cprefix CPREFIX] [--verbose] [INPUTFILE]\n";
    print STDERR "where: OUTPUTFILE is the C file to write\n";
    print STDERR "       TEXTURE is the texture to use\n";
    print STDERR "       TEXSCALE is the texture scale factor (default: 1.0)\n";
    print STDERR "       ROTATION is the amount to rotate the texture (in degrees - currently\n";
    print STDERR "         only 0, 90, 180, 270 are supported)\n";
    print STDERR "       CPREFIX is the name to use before all C identifiers\n";
    print STDERR "       INPUTFILE is the input VideoScape file\n";
    exit 1;
  }

die "texture argument is required" if !defined ($texturefilename);
die "texture rotation must be a multiple of 90 degrees"
  if $rottexture != 0 && $rottexture != 90 && $rottexture != 180 &&
     $rottexture != 270;

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

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 = $_;

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

	$state = "reading faces" if $vcount == 0;
      }
    elsif ($state eq "reading faces")
      {
	my @fs = split /[ \t]+/, $_;
	shift @fs;		# Remove initial number of vertices.
	pop @fs;		# Remove trailing colour.
	push @faces, { 'vertices' => \@fs };
      }
  }

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

# Attach texture coordinates to each vertex. To do this, we use a
# very noddy formula: (1) we find the min and max x, y and z coordinates
# over the whole mesh (2) we work out which pair (eg. (x, y)) of these
# coordinates vary the most (3) we set the texture coordinates to range
# over [0,1] over these two axes.
my ($min_x, $min_y, $min_z,
    $max_x, $max_y, $max_z) = (1000000, 1000000, 1000000,
			       -1000000, -1000000, -1000000);
foreach (@vertices)
  {
    my @vs = @{$_->{vertices}};

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

my $range_x = $max_x - $min_x;
my $range_y = $max_y - $min_y;
my $range_z = $max_z - $min_z;

# Which two axes shall we use?
my (@ords, $usub, $uscale, $vsub, $vscale);

if ($range_x >= $range_z && $range_y >= $range_z)
  {
    @ords = (0, 1);		# Use (x, y).
    $usub = $min_x;
    $uscale = $range_x;
    $vsub = $min_y;
    $vscale = $range_y;
  }
elsif ($range_x >= $range_y && $range_z >= $range_y)
  {
    @ords = (0, 2);		# Use (x, z).
    $usub = $min_x;
    $uscale = $range_x;
    $vsub = $min_z;
    $vscale = $range_z;
  }
elsif ($range_y >= $range_x && $range_z >= $range_x)
  {
    @ords = (1, 2);		# Use (y, z).
    $usub = $min_y;
    $uscale = $range_y;
    $vsub = $min_z;
    $vscale = $range_z;
  }
else
  {
    die "oops: ranges = ($range_x, $range_y, $range_z)";
  }

if ($verbose)
  {
    print "ords: ($ords[0],$ords[1])\n";
    print "usub = $usub, uscale = $uscale, vsub = $vsub, vscale = $vscale\n";
    print "x: [$min_x, $max_x] (range: $range_x)\n";
    print "y: [$min_y, $max_y] (range: $range_y)\n";
    print "z: [$min_z, $max_z] (range: $range_z)\n";
  }

# Assign (u, v) texture coordinates to each vertex.
my $vertex;
foreach $vertex (@vertices)
  {
    my @vs = @{$vertex->{vertices}};
    my $u = $texscale * ($vs[$ords[0]] - $usub) / $uscale;
    my $v = $texscale * ($vs[$ords[1]] - $vsub) / $vscale;

    if ($rottexture == 90)
      {
	my $c = $v;
	$v = $u;
	$u = $texscale-$c;
      }
    elsif ($rottexture == 180)
      {
	$u = $texscale-$u;
	$v = $texscale-$v;
      }
    elsif ($rottexture == 270)
      {
	my $c = $v;
	$v = $texscale-$u;
	$u = $c;
      }

    $vertex->{texcoords} = [ $u, $v ];
  }

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

    print C "/* This file describes a scenery object.\n * It is automatically generated.\n */\n\n#include \"common.h\"\n\n";

    print C "static int tex;\n\n";

    # Begin the display function.
    print C "void\n${cprefix}_display ()\n";
    print C "{\n";

    # Write out the vertex array.
    print C "  static GLfloat va[", 0+@vertices, "][3] = {",
    join (", ",
	  map ({ "{" . $_->{vertices}[0] . ", "
		 . $_->{vertices}[1] . ", "
		 . $_->{vertices}[2] . "}" } @vertices)), "};\n";

    # Write out the texture coordinate array.
    print C "  static GLfloat tca[", 0+@vertices, "][2] = {",
    join (", ",
	  map ({ "{" . $_->{texcoords}[0] . ", "
		 . $_->{texcoords}[1] . "}" } @vertices)), "};\n";

    # Drawing prologue.
    print C "  glEnableClientState (GL_VERTEX_ARRAY);\n";
    print C "  glEnableClientState (GL_TEXTURE_COORD_ARRAY);\n";
    print C "  glVertexPointer (3, GL_FLOAT, 0, va);\n";
    print C "  glTexCoordPointer (2, GL_FLOAT, 0, tca);\n";
    print C "  glBindTexture (GL_TEXTURE_2D, tex);\n";

    # Separate vertices into triangles, quads and polygons.
    my @triangles = ();
    my @quads = ();
    my @polygons = ();

    foreach (@faces)
      {
	if (@{$_->{vertices}} == 3)
	  {
	    push @triangles, $_->{vertices};
	  }
	elsif (@{$_->{vertices}} == 4)
	  {
	    push @quads, $_->{vertices};
	  }
	elsif (@{$_->{vertices}} > 4)
	  {
	    push @polygons, $_->{vertices};
	  }
      }

    # Draw triangles.
    if (@triangles)
      {
	print C "  glBegin (GL_TRIANGLES);\n";
	foreach (@triangles)
	  {
	    print C "  glArrayElement (", $_->[0], ");\n";
	    print C "  glArrayElement (", $_->[1], ");\n";
	    print C "  glArrayElement (", $_->[2], ");\n";
	  }
	print C "  glEnd ();\n";
      }

    # Draw quads.
    if (@quads)
      {
	print C "  glBegin (GL_QUADS);\n";
	foreach (@quads)
	  {
	    print C "  glArrayElement (", $_->[0], ");\n";
	    print C "  glArrayElement (", $_->[1], ");\n";
	    print C "  glArrayElement (", $_->[2], ");\n";
	    print C "  glArrayElement (", $_->[3], ");\n";
	  }
	print C "  glEnd ();\n";
      }

    # Draw polygons.
    if (@polygons)
      {
	print C "  glBegin (GL_TRIANGLES);\n";
	my $polygon;
	foreach $polygon (@polygons)
	  {
	    foreach (@$polygon)
	      {
		print C "  glArrayElement (", $_, ");\n";
	      }
	  }
	print C "  glEnd ();\n";
      }

    # Drawing epilogue.
    print C "  glDisableClientState (GL_TEXTURE_COORD_ARRAY);\n";
    print C "  glDisableClientState (GL_VERTEX_ARRAY);\n";

    print C "}\n\n";

    # Generate onload and onunload functions.
    print C "int\n${cprefix}_load ()
{
  tex = xrTextureLoad (\"$texturefilename\", 0, 0, 0, 1);
  if (tex == 0)
    {
      xrLog (LOG_ERROR, \"cannot load texture: $texturefilename\");
      return -1;
    }

  return 0;
}

void
${cprefix}_unload ()
{
  xrTextureUnload (tex);
}
";

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