From fed57d7bee92a0aa5ea9f38e8e8ecc7b40f4f10b Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Thu, 19 Aug 2010 21:59:08 +0000 Subject: [PATCH] Add primitive .3do (TA/Spring) model conversion tool. This was SVN commit r8007. --- source/tools/springimport/SpringPalette.pm | 270 +++++++++++ source/tools/springimport/TextureAtlas.pm | 93 ++++ source/tools/springimport/convert.pl | 507 +++++++++++++++++++++ 3 files changed, 870 insertions(+) create mode 100644 source/tools/springimport/SpringPalette.pm create mode 100644 source/tools/springimport/TextureAtlas.pm create mode 100644 source/tools/springimport/convert.pl diff --git a/source/tools/springimport/SpringPalette.pm b/source/tools/springimport/SpringPalette.pm new file mode 100644 index 0000000000..8f35f143f0 --- /dev/null +++ b/source/tools/springimport/SpringPalette.pm @@ -0,0 +1,270 @@ +package SpringPalette; +use strict; +use warnings; + +my @palette = ( + "000000", + "800000", + "008000", + "808000", + "000080", + "800080", + "008080", + "808080", + "C0DCC0", + "5454FC", + "000000", + "000000", + "000000", + "000000", + "000000", + "000000", + "FFEBF3", + "EBC7D3", + "D7A3B3", + "C38797", + "AF6F7F", + "9B5B63", + "8B474F", + "7B3B47", + "6F333B", + "632B33", + "57232B", + "4B1B27", + "3B171F", + "2F0F17", + "230B0F", + "17070B", + "73FFDF", + "57E7BF", + "43CF9F", + "2FB783", + "1F9F67", + "138B4F", + "0F773F", + "0B6B37", + "075F2F", + "07532B", + "004727", + "003F23", + "00331B", + "002717", + "001B0F", + "00130B", + "E3EFFF", + "C7DFE7", + "AFCFCB", + "93B7A7", + "7F9F83", + "6B8767", + "5F6F53", + "5F6347", + "5B573B", + "534333", + "473B2B", + "3B3323", + "2F2B1B", + "231F13", + "17130F", + "0B0B07", + "FBFBD7", + "DFDFB7", + "C3C39B", + "ABAB83", + "93936F", + "777757", + "636343", + "535333", + "434323", + "333317", + "23230F", + "1B1B07", + "171707", + "131300", + "0F0F00", + "0B0B00", + "FBFBFB", + "EBEBEB", + "DBDBDB", + "CBCBCB", + "BBBBBB", + "ABABAB", + "9B9B9B", + "8B8B8B", + "7B7B7B", + "6B6B6B", + "5B5B5B", + "4B4B4B", + "3B3B3B", + "2B2B2B", + "1F1F1F", + "0F0F0F", + "EBF3FF", + "CBE3FF", + "AFCFFF", + "97B3FF", + "7B97FF", + "677FFF", + "536BEF", + "3F5BE3", + "334BD7", + "233BCB", + "172FAF", + "0F2797", + "071F7B", + "071763", + "000F47", + "000B2F", + "E3F7FF", + "BFDBE7", + "9FBFCF", + "83A7B7", + "6B8FA3", + "53778B", + "3F5F73", + "2F4B5F", + "273F57", + "23374F", + "1F2F47", + "1B273F", + "171F37", + "131B2F", + "0F1327", + "0B0F1F", + "D7EFFF", + "BBE3EF", + "9BCBDF", + "83B7CF", + "6BA3C3", + "538FB3", + "3F7BA3", + "2F6B97", + "235B87", + "1B4B77", + "133F67", + "0B3357", + "072747", + "001B37", + "001327", + "000B1B", + "FFE7FF", + "E7C7EB", + "D3ABD7", + "BB93C3", + "A77BB3", + "8F639F", + "774B8F", + "633B7F", + "4F2B6F", + "431F63", + "371757", + "2B0F47", + "1F073B", + "13002B", + "0B001F", + "070013", + "D7FFA7", + "ABE77F", + "83D35B", + "67BF3F", + "4BAB2B", + "43972B", + "378727", + "2F771B", + "2B6713", + "235B0F", + "1F4F0B", + "1B4307", + "173300", + "0F2700", + "0B1B00", + "070F00", + "FFE39F", + "E3C773", + "CBAF53", + "B3973F", + "9B832F", + "836F23", + "6B5B17", + "53470F", + "4B3B0B", + "433307", + "3B2B07", + "372300", + "2F1B00", + "271300", + "1F0F00", + "1B0B00", + "FFFFA3", + "FBF383", + "F7E367", + "F3D34F", + "EFBB33", + "EFA71B", + "EB8F13", + "E77B0F", + "DF4F07", + "D72300", + "BF1F00", + "A71B00", + "931700", + "7B1300", + "631300", + "4F0F00", + "FFFF00", + "FFBF00", + "FF8300", + "FF4700", + "D32B00", + "AB1700", + "7F0700", + "570000", + "DFCBFF", + "BB9FDF", + "9B77BF", + "7F579F", + "673B7F", + "4B235F", + "33133F", + "1B071F", + "D3DBFF", + "879FF7", + "436FEF", + "1747E7", + "0B2BBB", + "07178F", + "000763", + "000037", + "7BFF77", + "53DF4F", + "33BF2B", + "1B9F13", + "1B7F0B", + "175F07", + "133F00", + "0B1F00", + "000000", + "000000", + "000000", + "000000", + "000000", + "000000", + "FFFBF0", + "A0A0A4", + "808080", + "FF0000", + "00FF00", + "FFFF00", + "0000FF", + "FF00FF", + "00FFFF", + "FFFFFF" +); + +sub get_image +{ + my ($idx) = @_; + return "xc:#" . $palette[$idx] . "[4x4]"; +} + +1; diff --git a/source/tools/springimport/TextureAtlas.pm b/source/tools/springimport/TextureAtlas.pm new file mode 100644 index 0000000000..9b050b37cc --- /dev/null +++ b/source/tools/springimport/TextureAtlas.pm @@ -0,0 +1,93 @@ +package TextureAtlas; + +# Incredibly rubbish texture packer + +use strict; +use warnings; + +use Image::Magick; + +sub new +{ + my ($class, $width) = @_; + my $self = { + width => $width, + x => 0, + y => 0, + rowheight => 0, + images => {}, + }; + bless $self, $class; +} + +sub add +{ + my ($self, $filename) = @_; + return if $self->{images}{$filename}; + + my $img = new Image::Magick; + $img->ReadImage($filename); + my $w = $img->Get("width"); + my $h = $img->Get("height"); + + if ($filename =~ /\.tga$/i) { + for my $y (0..$h-1) { + for my $x (0..$w-1) { + my @p = $img->GetPixel(x => $x, y => $y, channel => "RGBA"); + if ($p[0] == $p[2] and $p[1] == 0) { + my $a = $p[0] * 1.5; + $a = 0.95 if $a > 0.95; # prevent premul ugliness + my $c = $a; + $img->SetPixel(x => $x, y => $y, color => [$c,$c,$c,$a], channel => "RGBA"); + } else { + $img->SetPixel(x => $x, y => $y, color => [$p[0],$p[1],$p[2],0], channel => "RGBA"); + } + } + } + } + + die if $w > $self->{width}; + if ($self->{x} + $w > $self->{width}) { + $self->{x} = 0; + $self->{y} += $self->{rowheight}; + $self->{rowheight} = 0; + } + + $self->{images}{$filename} = { img => $img, w => $w, h => $h, x => $self->{x}, y => $self->{y} }; + + $self->{rowheight} = $h if $h > $self->{rowheight}; + $self->{x} += $w; +} + +sub finish +{ + my ($self, $filename) = @_; + + my $h = $self->{y} + $self->{rowheight}; + my $hlog = log($h)/log(2); + $hlog = int($hlog+1) if $hlog != int($hlog); + $h = 2**$hlog; + $self->{height} = $h; + + my $image = new Image::Magick; + $image->Set(size => $self->{width}."x".$h, depth => 8); + $image->ReadImage("xc:transparent"); + for my $t (values %{ $self->{images} }) { + $image->Composite(image => $t->{img}, x => $t->{x}, y => $t->{y}); + } + $image->Write($filename); +} + +sub get_texcoords +{ + my ($self, $filename) = @_; + my $t = $self->{images}{$filename} or die; + return ( + $t->{x} / $self->{width}, + 1 - ($t->{y} / $self->{height}), + ($t->{x} + $t->{w}) / $self->{width}, + 1 - (($t->{y} + $t->{h}) / $self->{height}), + ); +} + +1; diff --git a/source/tools/springimport/convert.pl b/source/tools/springimport/convert.pl new file mode 100644 index 0000000000..128cef1e29 --- /dev/null +++ b/source/tools/springimport/convert.pl @@ -0,0 +1,507 @@ +# Copyright (c) 2010 Wildfire Games +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# This is a fairly hacky converter from the .3do format used by Spring +# to the Collada + actor XML format used by 0 A.D. + +use strict; +use warnings; +use Data::Dumper; + +use TextureAtlas; +use SpringPalette; + +use constant SCALE => 3.0 / 1048576; + +my $ROOT = "../../../binaries/data/mods/public"; +my $SOURCE = "../../../../../misc/spring/BA712"; + +sub parse_3do +{ + my ($fh) = @_; + + my $b; + + read $fh, $b, 13*4 or die; + my ($sig, $num_verts, $num_prims, $sel_prim, $x, $y, $z, + $off_name, undef, $off_verts, $off_prims, $off_sib, $off_child) = + unpack 'l*', $b; + + die unless $sig == 1; + + seek $fh, $off_name, 0; + my $name = ''; + while (1) { + read $fh, my $c, 1 or die; + last if $c eq "\0"; + $name .= $c; + } + + seek $fh, $off_verts, 0; + read $fh, $b, 12*$num_verts or die; + my @verts = unpack 'l*', $b; + + my @prims; + for my $p (0..$num_prims-1) { + seek $fh, $off_prims + 32*$p, 0; + read $fh, $b, 32 or die; + my ($palette, $num_idxs, undef, $off_idxs, $off_tex) = unpack 'l*', $b; + + seek $fh, $off_idxs, 0; + read $fh, $b, 2*$num_idxs or die; + my @idxs = unpack 's*', $b; + + my $texture; + if ($off_tex) { + $texture = ''; + seek $fh, $off_tex, 0; + while (1) { + read $fh, my $c, 1 or die; + last if $c eq "\0"; + $texture .= $c; + } + } + + push @prims, { + palette => $palette, + idxs => \@idxs, + texture => $texture, + }; + } + + my $ret = { + name => $name, + verts => \@verts, + prims => \@prims, + offset => [$x, $y, $z], + }; + + if ($off_child) { + seek $fh, $off_child, 0; + $ret->{children} = [ parse_3do($fh) ]; + } + + if ($off_sib) { + seek $fh, $off_sib, 0; + return ($ret, parse_3do($fh)); + } else { + return ($ret); + } +} + +sub write_geometries +{ + my ($obj, $atlas, $name, $fh) = @_; + + my $num_verts = @{ $obj->{verts} } / 3; + my $num_prims = @{ $obj->{prims} }; + + my @vertex_normals; + for (0..$num_verts-1) { + push @vertex_normals, [0,0,0]; + } + + my @prim_normals; + for my $prim (@{ $obj->{prims} }) { + my @v0 = ($obj->{verts}[$prim->{idxs}[0]*3], $obj->{verts}[$prim->{idxs}[0]*3+1], $obj->{verts}[$prim->{idxs}[0]*3+2]); + my @v1 = ($obj->{verts}[$prim->{idxs}[1]*3], $obj->{verts}[$prim->{idxs}[1]*3+1], $obj->{verts}[$prim->{idxs}[1]*3+2]); + my @v2 = ($obj->{verts}[$prim->{idxs}[2]*3], $obj->{verts}[$prim->{idxs}[2]*3+1], $obj->{verts}[$prim->{idxs}[2]*3+2]); + my @d10 = ($v1[0] - $v0[0], $v1[1] - $v0[1], $v1[2] - $v0[2]); + my @d20 = ($v2[0] - $v0[0], $v2[1] - $v0[1], $v2[2] - $v0[2]); + my @x = ($d10[1]*$d20[2] - $d10[2]*$d20[1], $d10[2]*$d20[0] - $d10[0]*$d20[2], $d10[0]*$d20[1] - $d10[1]*$d20[0]); + my $d = sqrt($x[0]*$x[0] + $x[1]*$x[1] + $x[2]*$x[2]); + my @n = $d ? ($x[0]/$d, $x[1]/$d, $x[2]/$d) : (0,1,0); + push @prim_normals, \@n; + for my $v (@{ $prim->{idxs} }) { + $vertex_normals[$v][0] += $n[0]; + $vertex_normals[$v][1] += $n[1]; + $vertex_normals[$v][2] += $n[2]; + } + } + + for (0..$num_verts-1) { + my @n = @{ $vertex_normals[$_] }; + my $d = sqrt($n[0]*$n[0] + $n[1]*$n[1] + $n[2]*$n[2]); + $vertex_normals[$_] = $d ? [$n[0]/$d, $n[1]/$d, $n[2]/$d] : [0,0,0]; + } + + print $fh < + + + + +EOF + + for my $n (0..$num_verts-1) { + printf $fh "%.6f %.6f %.6f\n", $obj->{verts}[$n*3]*SCALE, $obj->{verts}[$n*3+1]*SCALE, $obj->{verts}[$n*3+2]*SCALE; + } + + print $fh < + + + + + + + + + + +EOF + + for my $n (0..$num_verts-1) { + printf $fh "%.6f %.6f %.6f\n", @{$vertex_normals[$n]}; + } + + my @uvs; + for my $prim (@{ $obj->{prims} }) { + my ($u0,$v0, $u1,$v1) = $atlas->get_texcoords(texture_filename($prim)); + if (@{ $prim->{idxs} } == 3) { + push @uvs, $u0,$v0, $u0,$v1, $u1,$v1; + } elsif (@{ $prim->{idxs} } == 4) { + push @uvs, $u0,$v0, $u0,$v1, $u1,$v1, $u1,$v0; + } else { + push @uvs, ($u1,$v0) x @{ $prim->{idxs} }; + } + } + my $num_uvs = @uvs/2; + + print $fh < + + + + + + + + + + +@uvs + + + + + + + + + + + + + + + +EOF + + my $i = 0; + my $j = 0; + for my $prim (@{ $obj->{prims} }) { + print $fh "

"; + for (@{ $prim->{idxs} }) { + print $fh "$_ $_ $j "; + ++$j; + } + ++$i; + print $fh "

\n"; + } + + print $fh < +
+
+ +EOF +} + +sub write_mesh +{ + my ($obj, $atlas, $name, $fh) = @_; + + print $fh < + + + + Y_UP + +EOF + + write_geometries($obj, $atlas, $name, $fh); + + print $fh < + + + + + + + + + + +EOF +} + +sub write_joint +{ + my ($joint, $fh, $indent) = @_; + + print $fh qq[$indent\n]; + print $fh qq[$indent ] . (sprintf "%.6f %.6f %.6f", map $_*SCALE, $joint->{offset}[0], $joint->{offset}[1], -$joint->{offset}[2]) . qq[\n]; + for my $c (@{ $joint->{children} }) { + write_joint($c, $fh, "$indent "); + } + print $fh qq[$indent\n]; +} + +sub write_skeleton +{ + my ($skeleton, $rootobj, $atlas, $name, $fh) = @_; + + print $fh < + + + + Y_UP + +EOF + + write_geometries($rootobj, $atlas, $name, $fh); + + print $fh < + + + 0 1.0 0 180 + +EOF + + write_joint($_, $fh, " ") for @$skeleton; + + print $fh < + + + + + + +EOF +} + +sub extract_joint_names +{ + my ($joint) = @_; + return ($joint->{name}, map { extract_joint_names($_) } @{ $joint->{children} }); +} + +sub texture_filename +{ + my ($prim) = @_; + if ($prim->{texture}) { + my $base = "$prim->{texture}"; + my @files = ("${base}.tga", "${base}00.tga", "${base}00.bmp", "${base}00.BMP"); + push @files, map { lc $_ } @files; + @files = grep { -e "$SOURCE/unittextures/tatex/$_" } @files; + die "can't find $prim->{texture}" unless @files; + return "$SOURCE/unittextures/tatex/$files[0]"; + } else { + return SpringPalette::get_image($prim->{palette}); + } +} + +sub write_skeleton_actor +{ + my ($skeleton, $name, $fh) = @_; + print $fh < + + + + + + + spring/$name.dae + spring/$name.tga + + +EOF + + my @joints = map { extract_joint_names($_) } @$skeleton; + for my $n (@joints[1..$#joints]) { + print $fh qq[ \n]; + } + + print $fh < + + + + player_trans.xml + + +EOF +} + +sub write_mesh_actor +{ + my ($mesh, $prefix, $name, $fh) = @_; + #warn Dumper $mesh; + print $fh < + + + + + + + spring/$name.dae + spring/$prefix.tga + + + + player_trans.xml + + +EOF +} + +sub write_entity +{ + my ($prefix, $fh) = @_; + print $fh < + + + hele + $prefix + + + ??? + + + 9.0 + + + spring/$prefix.xml + +EOF + + if ($prefix =~ /^(armaas|armbats|corcrus)$/) { + print $fh < + true + +EOF + } + + print $fh < +EOF +} + +sub extract_textures +{ + my ($obj, $atlas) = @_; + + my @meshes; + my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} }; + + for (@prims) { + $atlas->add(texture_filename($_)); + } + + extract_textures($_, $atlas) for @{ $obj->{children} }; +} + +sub extract_meshes +{ + my ($obj) = @_; + + my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} }; + + my @meshes; + push @meshes, { name => $obj->{name}, verts => $obj->{verts}, prims => \@prims }; + push @meshes, map { extract_meshes($_) } @{ $obj->{children} }; + return @meshes; +} + +sub extract_skeleton +{ + my ($obj) = @_; + return { name => $obj->{name}, offset => $obj->{offset}, children => [ map { extract_skeleton($_) } @{ $obj->{children} } ] }; +} + +sub write_model +{ + my ($prefix, $model) = @_; + + my $atlas = new TextureAtlas(256); + extract_textures($model, $atlas); + $atlas->finish("$ROOT/art/textures/skins/spring/$prefix.tga"); + + my @meshes = extract_meshes($model); + #print Dumper \@meshes; + + #exit; + + my @skeleton = extract_skeleton($model); + #print Dumper \@skeleton; + + { + open my $fhm, '>', "$ROOT/art/meshes/spring/$prefix.dae" or die $!; + write_skeleton(\@skeleton, $meshes[0], $atlas, $prefix, $fhm); + + open my $fha, '>', "$ROOT/art/actors/spring/$prefix.xml" or die $!; + write_skeleton_actor(\@skeleton, $prefix, $fha); + } + + for my $mesh (@meshes[1..$#meshes]) { + my $name = $prefix.'-'.$mesh->{name}; + open my $fhm, '>', "$ROOT/art/meshes/spring/$name.dae" or die $!; + write_mesh($mesh, $atlas, $name, $fhm); + + open my $fha, '>', "$ROOT/art/actors/spring/$name.xml" or die $!; + write_mesh_actor($mesh, $prefix, $name, $fha); + } + + { + open my $fhe, '>', "$ROOT/simulation/templates/spring/$prefix.xml" or die $!; + write_entity($prefix, $fhe); + } +} + +#for my $n (qw(armpw armcom KrogTaar armlab corak corlab)) { +for my $n (map { (/.*\/(.*)\.3do/)[0] } grep /\/(cor|arm)[a-z]+\./, <$SOURCE/Objects3d/*.3do>) { + print "$n\n"; + open my $t, "$SOURCE/Objects3d/$n.3do" or die "$n: $!"; + binmode $t; + my $model = parse_3do($t); + #print Dumper $model; + write_model($n, $model); +}