#!/bin/perl -w
#####################################################################
# map2obj.pl
#	radiant .map file parser and .obj (wavefront) converter
#	2007-04-05 Copyright Werner 'hoehrer' Höhrer bill_spam2 [AT) yahoo (DOT] de
#
# Licence:
#	The content of this file is under the GNU/GPL license v2.
#	http://www.gnu.org/licenses/gpl.txt
#
# Links:
#	OBJ Specs
#	http://www.fileformat.info/format/wavefrontobj/egff.htm
#	Quake MAP Specs
#	http://www.gamers.org/dEngine/quake/QDP/qmapspec.html
#
# Changelog:
#	2007-04-06	0.0.4	Basic pasing+export of verts and faces now works. Still some bugs to kill. -- Werner 'hoehrer' Höhrer
#	2007-04-06	0.0.3	Parsing of UV data seems to work (->check if it is correct)	-- Werner 'hoehrer' Höhrer
#	2007-04-06	0.0.2	Export of vertex- and face- data. UV data to come.	-- Werner 'hoehrer' Höhrer
#	2007-04-05	0.0.1	Basic parser	-- Werner 'hoehrer' Höhrer
#####################################################################
# usage: map2obj.pl <file.map>
#####################################################################

use strict;
use warnings;

my $version = "0.0.4";

############################
# Initialize the map data.
############################
sub map_init ($) {
	my ($map) = @_;

	# Init the material list
	$map->{materials} = {};
	$map->{materialcount} = 0;

	$map->{classes} = {};
	$map->{classcount} = 0;

	$map->{entities} = [];
	$map->{entitycount} = 0;

	# Init the brush-array
	$map->{brushes} = [];
	$map->{brushcount} = 0;

	$map->{vertices} = [];
	$map->{vertexcount} = 0;

	$map->{uv} = [];
	$map->{uvcount} = 0;


	# Init map filename (just for info)
	$map->{map_filename} = '';
	return $map;
}

sub brush_init ($) {
	my ($brush) = @_;

	$brush->{polygons} = [];
	$brush->{textures} = [];
	$brush->{vertices} = [];
	$brush->{uv} = [];
	$brush->{polycount} = 0;	# Used to count polygons/vertices and textures (it's the same for both)

	$brush->{data} = {};
	return $brush;
}

sub entity_init ($) {
	my ($entity) = @_;

	# init entity
	$entity = {};
	$entity->{brushes} = [];
	$entity->{brushcount} = 0;
	$entity->{data} = {};
	return $entity;
}

sub str2array ($) {
	my ($string) = @_;

	$string =~ s/^\s+//;
	my @array = split(/\s+/, $string);

	return @array;
}

sub obj_write($$) {
	my ($map, $filename) = @_;

	open(OBJ, "> ".$filename)
		|| die "failed to open obj file for writing: $filename\n";

	printf OBJ "# Generated by the map2obj.pl script (v%s) \n", $version;
	printf OBJ "\n";
	printf OBJ "# Debug\n";
	#printf OBJ "o Dummy\n"; #Debug

	# Write vertex list.
	my $counter =0;
	foreach my $vertex (@{$map->{vertices}}) {
		#print $vertex->[0]; # Debug
		printf OBJ "v %f %f %f\n",
			$vertex->[0],
			$vertex->[1],
			$vertex->[2];
		$counter++;
	}
	printf OBJ "# %i vertices written\n", $counter;

	# Write uv list
	$counter =0;
	foreach my $uv (@{$map->{uv}}) {
		#print $uv->[0]; # Debug
		printf OBJ "vt %f %f\n",
			$uv->[0],
			$uv->[1];
		$counter++;
	}
	printf OBJ "# %i uv coords written\n", $counter;

	my $current_material = '';
	my $temp_string;
	my $brushcount;
	my $entitycount = 0;
	foreach my $entity  (@{$map->{entities}}) {
		if ((exists $entity->{data}->{classname}) && $entity->{data}->{classname} eq "worldspawn") {
			# Write object/face data.
			$brushcount=0;
			printf OBJ "\n";
			printf OBJ "# Entity_%i\n", $entitycount;
			foreach my $brush (@{$entity->{brushes}}) {
				# Write Object start-data.
				printf OBJ "\n";
				printf OBJ "o Brush_%i\n", $brushcount;

				#print $vertex->[0]; # Debug

				for (my $polycount=0; $polycount < $brush->{polycount}; $polycount++) {
					# Write material data if it (material/texture) changed.
					if ($current_material ne $brush->{textures}->[$polycount]) {
						$temp_string = '';
						# New material-usage found.
						$current_material = $brush->{textures}->[$polycount];
						$temp_string = $map->{materials}->{$current_material};
						# Write material link.
						printf OBJ "usemtl %s\n", $temp_string;
					}

					# Write face/polygon data.
#					print Dumper($polygon);
					my $polygon = $brush->{vertices}->[$polycount];
					my $uv = $brush->{uv}->[$polycount];

					printf OBJ "f ";
					my $vert_count = 0;
					foreach my $vertex_link (@{$polygon}) {
						printf OBJ " %i/%i", $vertex_link+1, $uv->[$vert_count]+1;
						$vert_count++;
					}
					printf OBJ "\n";

				}
				$brushcount++;
			}
		}
		$entitycount++;
	}
	printf OBJ "\n";
	printf OBJ "# EOF\n\n";
	close(OBJ);
}

sub mtl_write($$) {
	my ($map, $filename) = @_;


	foreach my $mat  (keys %{$map->{materials}}) {
		# TODO: Write material data
	}
}

############################
# Parse the text from the map file.
############################
sub map_parse ($$) {
	my ($map, $filename) = @_;
	my $entity_open = 0;
	my $brush_open = 0;

	open(MAP_INPUT, "< ".$filename) ||
		die "Failed to open mapfile '", $filename, "' .\n";

	my $entity;
	my $brush;

	$map->{map_filename} = $filename;
	my $line;

	while(<MAP_INPUT>) {
		next if /^\s*\/\//;	# Skip comments
		next if /^$/;		# Skip newlines
		next if /^\s*$/;	# Skip line with only whitespaces

		chomp;	# Remove trailing whitespaces

		if (!$brush_open) {	# just in case
			$brush = {};
		}
		if (!$entity_open) {	# just in case
			$entity = {};
		}

		$line = $_;

		if ($line =~ m/^\s*{\s*$/) {
			# Opening curly bracket found.
			if ($entity_open) {
				if (!$brush_open) {
					# Brush opening curly-bracket found.

					# init brush (TODO: extra function?)
					$brush_open = 1;
					$brush = brush_init($brush);
				} else {
					print "Bad opening bracked found: '",$line,"'\n";
					print "Syntax of file possibly corrupted. Abort.\n";
					return;
				}
			} else {
				#print "New enity.\n"; # Debug
				# Entity opening curly-bracket found.
				$entity_open = 1;
				$entity = entity_init($entity);

				# Add brush to map-tree
				push (@{$map->{entities}}, $entity);
				$map->{entitycount}++;
			}
			next;
		}

		# (1) (2) (3) 4 5
		if (($line =~ m/^\s*\(([^)]*)\)\s*\(([^)]*)\)\s*\(([^)]*)\)\s*([^\s]*)\s*([-\d.\s]*)\s*$/) && $brush_open) {
			# Found a polygon with texture.
			#print "Parsing brush:", $map->{brushcount}-1," Polygon:", $brush->{polycount},"\n";
			my ($v1, $v2, $v3) = ($1, $2, $3);
			my $tex = $4;
			my $the_others = $5;

			$tex =~ s/^\s+//;
			my @vert1 = str2array($v1);
			my @vert2 = str2array($v2);
			my @vert3 = str2array($v3);
			my @uv_info = str2array($the_others);
			#print Dumper([@uv_info]); # Debug
			my @uv1 = ($uv_info[0], $uv_info[1]);
			my @uv2 = ($uv_info[2], $uv_info[3]);
			my @uv3 = ($uv_info[4], $uv_info[5]);
			my @uv_unknown = ($uv_info[6], $uv_info[7]);

			#print Dumper(@uv1); # Debug
			#print Dumper(@vert1); # Debug
			my $polygon = [[@vert1],[@vert2], [@vert3]];

			# TODO: correctly transform face+uv info here .... dammit, these are _plane_ coordinates :(

#{ Brush Begin
#( 128 0 0 ) ( 128 1 0 ) ( 128 0 1 ) GROUND1_6 0 0 0 1.0 1.0
#( PLANE1 ) ( PLANE2 ) ( PLANE3 ) SHADER TEXOFFSETX TEXOFFSETY TEXROTATE TEXSCALEX TEXSCALEY
#} Brush End
# TEXOFFSETX	- Texture x-offset (must be multiple of 16)
# TEXOFFSETY	- Texture y-offset (must be multiple of 16)
# TEXROTATE	- floating point value indicating texture rotation
# TEXSCALEX	- scales x-dimension of texture (negative value to flip)
# TEXSCALEY	- scales y-dimension of texture (negative value to flip)


			push (@{$brush->{polygons}}, $polygon);	# Store polygon. ... better always use the "vertices" later on (as soon as it works)
			push (@{$brush->{textures}}, $tex);		# Store texture path.

			push (@{$brush->{vertices}}, [$map->{vertexcount}, $map->{vertexcount}+1, $map->{vertexcount}+2]);	# Store pointer to vertex data
			push (@{$map->{vertices}}, [@vert1], [@vert2], [@vert3]);				# Store vertex data
			$map->{vertexcount} += 3;

			push (@{$brush->{uv}}, [$map->{uvcount}, $map->{uvcount}+1, $map->{uvcount}+2]);	# Store pointer to uv data
			push (@{$map->{uv}}, [@uv1], [@uv2], [@uv2]);						# Store uv data
			$map->{uvcount} += 3;

			if (!exists($map->{materials}->{$tex})) {
				$map->{materials}->{$tex} = "mat".$map->{materialcount};		# Store info about all used textures (info only).
				$map->{materialcount}++;
			}

			$brush->{polycount}++;
			next;
		}
		if ($line =~ m/^\s*"([^"]*)"\s*"([^"]*)"\s*$/) {
			if ($brush_open) {
				# Found brush data
				# example: "origin" "416 -620 76"
				# $1 should be: origin
				# $2 should be: 416 -620 76
				$brush->{data}->{$1} = $2;
				#TODO: parse special cases for later export (like position and rotation).
				next;
			}
			if ($entity_open) {
				# Found brush data
				# example: "origin" "416 -620 76"
				# $1 should be: origin
				# $2 should be: 416 -620 76
				$entity->{data}->{$1} = $2;
				#TODO: parse special cases for later export (like position and rotation).
				if ($1 eq  'classname') {
					# print "Entityclass: ",$entity->{data}->{$1}, "\n"; Debug
					if (!exists($map->{classes}->{$entity->{data}->{$1} })) {
						$map->{classes}->{$entity->{data}->{$1} } = 1;
						$map->{classcount}++;
					}
				} elsif  (($1 eq  'origin') || ($1 eq  'color') || ($1 eq  '_color') || ($1 eq  'angles')){
					my @threes = str2array($2);
					$entity->{data}->{$1} = @threes;

				}
				next;
			}
		}

		if ($line =~ m/^\s*}\s*$/) {
			# Closing curly bracket found
			if ($entity_open) {
				if ($brush_open) {
					# We are closing this brush
					$brush_open = 0;
					# Add brush to map-tree
					push (@{$map->{brushes}}, $brush);
					$map->{brushcount}++;
					push (@{$entity->{brushes}}, $brush);
					$entity->{brushcount}++;
				} else {
					# We are closing this enity.
					$entity_open = 0;
				}
			} else {
				print "Badclosing bracked found, we haven't even met an opening one: '",$line,"'\n";
				print "Syntax of file possibly corrupted. Abort.\n";
				return;
			}
			next;
		}

	}
	close(MAP_INPUT);
} # parse

########################
# MAIN
########################
my $map_filename = 'condor04.map'; # Dummy, never used
my $obj_filename;
my $map = {};

# parse commandline paarameters (md2-filenames)
if ( $#ARGV < 0 ) {
	die "Usage:\tmap2obj.pl <file.map>\n";
} elsif ( $#ARGV == 0 ) {
	$map_filename = $ARGV[0];
	print "Mapfile= \"". $map_filename, "\"\n";
}

# Generate output filename
if ($map_filename =~ m/.*\.map$/) {
	($obj_filename = $map_filename) =~ s/\.map$/\.obj/;
} else {
	$obj_filename = $map_filename.".obj";
}
print "OBJfile= \"". $obj_filename, "\"\n";

$map = map_init($map);

# Open + parse map file
map_parse($map, $map_filename);

#Debug
use Data::Dumper;
#print Dumper($map);

# Print Info about parsed file
print "materials:\n";
foreach my $mat  (keys %{$map->{materials}}) {
	print "\t",$mat," \t generated name:", $map->{materials}->{$mat},"\n";
}

print "classes:\n";
foreach my $class  (keys %{$map->{classes}}) {
	print "\t",$class,"\n";
}

# write obj (only types of "classname" that are supported by obj)
obj_write($map, $obj_filename);

# TODO: write mtl
