#
# Street Fighter 4 EMO2SMD Converter
# by magnum29
#

use Math::Trig;

print "Enter an EMO file:\n";
chomp( my $emo = <STDIN> );
$emo =~ s/^"//;
$emo =~ s/"$//;
open $INPUT, "<$emo" or die "Couldn't open $emo: $!\n";
binmode($INPUT);

open O, ">${emo}_data.txt";

seek $INPUT, 0x10, 0;
my $skeleton_address = readu( $INPUT, "I", 4 );

seek $INPUT, $skeleton_address, 0;

my $total_nodes = readu( $INPUT, "I", 4 );

seek $INPUT, 4, 1; # unknown, small number

my $skeleton_start_offset = readu( $INPUT, "I", 4 );
my $skeleton_name_addresses_offset = readu( $INPUT, "I", 4 );

seek $INPUT, 8, 1; # 0 0

seek $INPUT, 4, 1; # address for bunch of FF's

my $matrix_offset = readu( $INPUT, "I", 4 );

seek $INPUT, $skeleton_address + $skeleton_start_offset, 0;

open SMD, ">${emo}.smd";
print SMD "version 1\nnodes\n";

my @node_parent;
my @node_number;
my @node_matrix;
for( 0..$total_nodes-1 ) {
	my $parent = readu( $INPUT, "s", 2 );
	my $node_number = readu( $INPUT, "s", 2 );

	seek $INPUT, 12, 1; #skip unknowns (short + FF + FF + zeros)

	my @transformation_matrix = readu( $INPUT, "f16", 64 );
	
	push( @node_parent, $parent );
	push( @node_number, $node_number );
	push( @node_matrix, \@transformation_matrix );

}
print O "\n";

seek $INPUT, $skeleton_address + $skeleton_name_addresses_offset, 0;
my @skeleton_name_addresses = readu( $INPUT, "I$total_nodes", 4 * $total_nodes );

for( 0..$total_nodes-1 ) {
	seek $INPUT, $skeleton_address + $skeleton_name_addresses[$_], 0;

	my $node_name = "";
	my $char;
	read( $INPUT, $char, 1 );
	while( $char ne "\0" ) {
		$node_name .= $char;
		read( $INPUT, $char, 1 );
	}
	print SMD "$_ \"$node_name\" $node_parent[$_]\n";
	printf O "%32s %3d - %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f\n", $node_name, $node_parent[$_], @{$node_matrix[$_]};
}
print O "\n";

seek $INPUT, $skeleton_address + $matrix_offset, 0;
print O "\n\nMATRIX DATA\n";
for( 0..$total_nodes-1 ) {
	my @matrix = readu( $INPUT, "f16", 64 );
	printf O "%3d - %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f %9f\n", $_, @matrix;
}


print SMD "end\nskeleton\ntime 0\n";

# [m00 m01 m02]
# [m10 m11 m12]
# [m20 m21 m22]
# heading = Math.atan2(-m.m20,m.m00);
# bank = Math.atan2(-m.m12,m.m11);
# attitude = Math.asin(m.m10);

for( 0..$total_nodes-1 ) {
	my @translation = (-${$node_matrix[$_]}[12],${$node_matrix[$_]}[14],${$node_matrix[$_]}[13]);

	my $ry = atan2( -${$node_matrix[$_]}[2], ${$node_matrix[$_]}[0] );
	my $rx = atan2( -${$node_matrix[$_]}[9], ${$node_matrix[$_]}[5] );
	my $rz = asin_real( ${$node_matrix[$_]}[1] );

	printf SMD "%d  %f %f %f %f %f %f\n", $_, @translation, -$rx, $rz, $ry;
}

print SMD "end\ntriangles\n";

seek $INPUT, 0x20, 0;
my $num_emg_subfiles = readu( $INPUT, "S", 2 );

seek $INPUT, 0x28, 0;
my @emg_file_addresses = readu( $INPUT, "I$num_emg_subfiles", 4 * $num_emg_subfiles );


$emo =~ s/^(.+?)([^\\\/]+)$/\2/;
$emo =~ /^(......)/;
my $texture_prefix = $1 . "_";

for my $emg_address_i ( 0..$#emg_file_addresses ) {
	print O "EMG #$emg_address_i\n";
	print "\nEMG $emg_address_i/$#emg_file_addresses\n";
	my $emg_address = $emg_file_addresses[$emg_address_i];
	$emg_address += 0x20;
	seek $INPUT, $emg_address, 0;
	warn "Thing != 1" if readu( $INPUT, "I", 4 ) != 1; # 1
	warn "Thing != 0x10" if readu( $INPUT, "I", 4 ) != 0x10; # 0x10
	$emg_address += 0x10;
	seek $INPUT, $emg_address + 6, 0;
	warn "Thing2 != 1" if readu( $INPUT, "S", 2 ) != 1; # 1
	warn "Thing2 != 0x10" if readu( $INPUT, "I", 4 ) != 0x10; # 0x10
	$emg_address += 0x10;

	seek $INPUT, $emg_address, 0;

	readu( $INPUT, "I", 4 ); # 711
	my $a_blocks = readu( $INPUT, "I", 4 ); # number of subblocks in BLOCK A
	readu( $INPUT, "I", 4 ); # 0
	my $a_block_address = readu( $INPUT, "I", 4 ); # BLOCK A address

	my $vertexblockcount = readu( $INPUT, "S", 2 );
	my $vertexblocksize = readu( $INPUT, "S", 2 );
	my $vertexblockaddress = readu( $INPUT, "I", 4 );

	my $b_blocks = readu( $INPUT, "S", 2 ); # should be 1, or else warn
	my $b_subblocks = readu( $INPUT, "S", 2 );
	my $b_block_address = readu( $INPUT, "S", 2 );

	seek $INPUT, $emg_address + 0x20, 0;
	my @mesh_matrix = readu( $INPUT, "f12", 48 );
	printf O "%10f %10f %10f %10f\n%10f %10f %10f %10f\n%10f %10f %10f %10f\n\n", @mesh_matrix;

	seek $INPUT, $emg_address + $a_block_address, 0;
	my @a_block_addresses = readu( $INPUT, "I$a_blocks", 4 * $a_blocks );
	my @texture_block;
	for( 0..$a_blocks-1 ) {
		seek $INPUT, $emg_address + $a_block_addresses[$_], 0;
		my $num_textures = readu( $INPUT, "I", 4 );

		my $texture_num = readu( $INPUT, "n", 2 ); # Big Endian short
		push( @texture_block, $texture_num );
	}

	my @mesh_faces;
	my @mesh_name;
	my @mesh_nodes;
	my @mesh_ids;
	my @mesh_vector;
	seek $INPUT, $emg_address + $b_block_address, 0;
	my @b_subblock_addresses = readu( $INPUT, "I$b_subblocks", 4 * $b_subblocks );
	print "Face addresses - @b_subblock_addresses\n";
	for my $b_subblock_address( @b_subblock_addresses ) {
		seek $INPUT, $emg_address + $b_subblock_address, 0;

		my @floats = readu( $INPUT, "f4", 16 );
		push( @mesh_vector, \@floats );

		my $mesh_id = readu( $INPUT, "S", 2 );
		my $num_faces = readu( $INPUT, "S", 2 );
		my $num_shorts = readu( $INPUT, "S", 2 );
		my $mesh_name = readu( $INPUT, "a32", 32 );
		$mesh_name =~ s/\0+$//;

		push( @mesh_name, $mesh_name );
		push( @mesh_ids, $mesh_id );

		print O "[$mesh_id] - $mesh_name : $num_faces Face Indices, $num_shorts Bones\n@floats\n\n";

		my @allfaces = readu( $INPUT, "S$num_faces", 2 * $num_faces );
		print O "@allfaces\n\n";

		my @triangle_sets;
		my $backface = 1;
		for( 0..$#allfaces-2 ) {
			my ($a, $b, $c) = ($allfaces[$_],$allfaces[$_+1],$allfaces[$_+2]);

			$backface = ($backface + 1) % 2;

			if( $a != $b && $a != $c && $b != $c ) {
				my @faceset;
				if( $backface ) {
					@faceset = ($c, $b, $a);
				} else {
					@faceset = ($a, $b, $c);
				}
				push( @triangle_sets, \@faceset );
			}
		}

		push( @mesh_faces, \@triangle_sets );

		my @allshorts = readu( $INPUT, "S$num_shorts", 2 * $num_shorts );

		push( @mesh_nodes, \@allshorts );
		print O "@allshorts\n\n";
		print O "----------------------------------------------------------\n";
	}

	seek $INPUT, $emg_address + $vertexblockaddress, 0;
	print O "Vertices : $vertexblockcount\n";
	print O "        x         y         z        nx        ny        nz        tu        tv  unknown1  unknown2  unknown3   unknowns          bi0 bi1 bi2 bi3           w0        w1        w2\n";

	my @xyz;
	my @normal;
	my @uv;
	my @nodes;
	my @node_weights;
	for( 0..$vertexblockcount-1 ) {

		my @floats;
		if( $vertexblocksize == 64 ) {
			@floats = readu( $INPUT, "f11", 44 );

			my @v_uc = ($floats[6],-$floats[7]);

			my @v_xyz = (-$floats[0],$floats[2],$floats[1]);
			my @v_normal = (-$floats[3],$floats[5],$floats[4]);

			push( @uv, \@v_uc );
			push( @xyz, \@v_xyz );
			push( @normal, \@v_normal );

		} elsif( $vertexblocksize == 32 ) {	# used in skeleton EMO files
			@floats = readu( $INPUT, "f3", 12 );

			my @v_xyz = ($floats[0],-$floats[2],$floats[1]);
			my @v_uc = (0,0);
			my @v_normal = (0,0,0);

			push( @uv, \@v_uc );
			push( @xyz, \@v_xyz );
			push( @normal, \@v_normal );

		} elsif( $vertexblocksize == 40 ) {	# used in shadow EMO files
			@floats = readu( $INPUT, "f6", 24 );

			my @v_xyz = ($floats[0],-$floats[2],$floats[1]);
			my @v_uc = (0,0);
			my @v_normal = ($floats[3],-$floats[5],$floats[4]);

			push( @uv, \@v_uc );
			push( @xyz, \@v_xyz );
			push( @normal, \@v_normal );
		}
		printf O "%9f "x($#floats+1), @floats;

		if( $vertexblocksize != 40 ) {
			my @unknownchars = readu( $INPUT, "C4", 4 );
			printf O "| %3d %3d %3d %3d | ", @unknownchars;
		}

		my @chars = readu( $INPUT, "C4", 4 );
		printf O "%3d %3d %3d %3d | ", @chars;

		my @weight_floats = readu( $INPUT, "f3", 12 );
		my $remaining_weight = 0;
		my $total_weight = 0;
		$total_weight += $_ for @weight_floats;
		if( (1-$total_weight) > 0.001 ) {
			$remaining_weight = 1-$total_weight;
		}
		push( @weight_floats, $remaining_weight );
		printf O "%9f %9f %9f\n", @weight_floats;

		my %bone;
		my @revised_nodes;
		my @revised_weights;
		for( 0..3 ) {
			if( defined($bone{$chars[$_]}) ) {
				if( $weight_floats[$_] != 0 ) {
					print "Double Weight!\n";
					$revised_weights[$bone{$chars[$_]}] += $weight_floats[$_];
				}
			} else {
				if( $weight_floats[$_] > 0 ) {
					push( @revised_nodes, $chars[$_] );
					push( @revised_weights, $weight_floats[$_] );
					$bone{$chars[$_]} = $#revised_nodes;
				} elsif( $weight_floats[$_] < 0 ) {
					print "NEGATIVE WEIGHT: $weight_floats[$_]\n";
				}
				
			}
		}
		push( @nodes, \@revised_nodes );
		push( @node_weights, \@revised_weights );
	}

	for my $m( 0..$#mesh_faces ) {

		my @triangle_sets = @{$mesh_faces[$m]};

		print "Dumping mesh $m... ", $#triangle_sets+1, " triangle sets\n";

		for my $f( 0..$#triangle_sets ) {
			my @face_vertices = @{$triangle_sets[$f]};

			print SMD "$mesh_name[$m]\\..\\$texture_prefix$texture_block[$mesh_ids[$m]].dds\n";
			printf SMD "0 %10f %10f %10f %10f %10f %10f %10f %10f ", @{$xyz[$face_vertices[0]]}, @{$normal[$face_vertices[0]]}, @{$uv[$face_vertices[0]]};
			print SMD ($#{$nodes[$face_vertices[0]]}+1);
			for( 0..$#{$nodes[$face_vertices[0]]} ) {
				printf SMD " %3d %10f", $mesh_nodes[$m][${$nodes[$face_vertices[0]]}[$_]], ${$node_weights[$face_vertices[0]]}[$_];
			}
			print SMD "\n";

			printf SMD "0 %10f %10f %10f %10f %10f %10f %10f %10f ", @{$xyz[$face_vertices[1]]}, @{$normal[$face_vertices[1]]}, @{$uv[$face_vertices[1]]};
			print SMD ($#{$nodes[$face_vertices[1]]}+1);
			for( 0..$#{$nodes[$face_vertices[1]]} ) {
				printf SMD " %3d %10f", $mesh_nodes[$m][${$nodes[$face_vertices[1]]}[$_]], ${$node_weights[$face_vertices[1]]}[$_];
			}
			print SMD "\n";

			printf SMD "0 %10f %10f %10f %10f %10f %10f %10f %10f ", @{$xyz[$face_vertices[2]]}, @{$normal[$face_vertices[2]]}, @{$uv[$face_vertices[2]]};
			print SMD ($#{$nodes[$face_vertices[2]]}+1);
			for( 0..$#{$nodes[$face_vertices[2]]} ) {
				printf SMD " %3d %10f", $mesh_nodes[$m][${$nodes[$face_vertices[2]]}[$_]], ${$node_weights[$face_vertices[2]]}[$_];
			}
			print SMD "\n";
		}
	}

	print O "\n\nEMG FILE ENDED\n\n";
}

print SMD "end\n";
print "\nDone.\n\n";
system("pause");




sub readu {
	my $temp;
	read( $_[0], $temp, $_[2] );
	return unpack( $_[1], $temp );
}

