1# Copyright (C) 2010 Wildfire Games. 2# 3# Permission is hereby granted, free of charge, to any person obtaining 4# a copy of this software and associated documentation files (the 5# "Software"), to deal in the Software without restriction, including 6# without limitation the rights to use, copy, modify, merge, publish, 7# distribute, sublicense, and/or sell copies of the Software, and to 8# permit persons to whom the Software is furnished to do so, subject to 9# the following conditions: 10# 11# The above copyright notice and this permission notice shall be included 12# in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22# This is a fairly hacky converter from the .3do format used by Spring 23# to the Collada + actor XML format used by 0 A.D. 24 25use strict; 26use warnings; 27use Data::Dumper; 28 29use TextureAtlas; 30use SpringPalette; 31 32use constant SCALE => 3.0 / 1048576; 33 34my $ROOT = "../../../binaries/data/mods/public"; 35my $SOURCE = "../../../../../misc/spring/BA712"; 36 37sub parse_3do 38{ 39 my ($fh) = @_; 40 41 my $b; 42 43 read $fh, $b, 13*4 or die; 44 my ($sig, $num_verts, $num_prims, $sel_prim, $x, $y, $z, 45 $off_name, undef, $off_verts, $off_prims, $off_sib, $off_child) = 46 unpack 'l*', $b; 47 48 die unless $sig == 1; 49 50 seek $fh, $off_name, 0; 51 my $name = ''; 52 while (1) { 53 read $fh, my $c, 1 or die; 54 last if $c eq "\0"; 55 $name .= $c; 56 } 57 58 seek $fh, $off_verts, 0; 59 read $fh, $b, 12*$num_verts or die; 60 my @verts = unpack 'l*', $b; 61 62 my @prims; 63 for my $p (0..$num_prims-1) { 64 seek $fh, $off_prims + 32*$p, 0; 65 read $fh, $b, 32 or die; 66 my ($palette, $num_idxs, undef, $off_idxs, $off_tex) = unpack 'l*', $b; 67 68 seek $fh, $off_idxs, 0; 69 read $fh, $b, 2*$num_idxs or die; 70 my @idxs = unpack 's*', $b; 71 72 my $texture; 73 if ($off_tex) { 74 $texture = ''; 75 seek $fh, $off_tex, 0; 76 while (1) { 77 read $fh, my $c, 1 or die; 78 last if $c eq "\0"; 79 $texture .= $c; 80 } 81 } 82 83 push @prims, { 84 palette => $palette, 85 idxs => \@idxs, 86 texture => $texture, 87 }; 88 } 89 90 my $ret = { 91 name => $name, 92 verts => \@verts, 93 prims => \@prims, 94 offset => [$x, $y, $z], 95 }; 96 97 if ($off_child) { 98 seek $fh, $off_child, 0; 99 $ret->{children} = [ parse_3do($fh) ]; 100 } 101 102 if ($off_sib) { 103 seek $fh, $off_sib, 0; 104 return ($ret, parse_3do($fh)); 105 } else { 106 return ($ret); 107 } 108} 109 110sub write_geometries 111{ 112 my ($obj, $atlas, $name, $fh) = @_; 113 114 my $num_verts = @{ $obj->{verts} } / 3; 115 my $num_prims = @{ $obj->{prims} }; 116 117 my @vertex_normals; 118 for (0..$num_verts-1) { 119 push @vertex_normals, [0,0,0]; 120 } 121 122 my @prim_normals; 123 for my $prim (@{ $obj->{prims} }) { 124 my @v0 = ($obj->{verts}[$prim->{idxs}[0]*3], $obj->{verts}[$prim->{idxs}[0]*3+1], $obj->{verts}[$prim->{idxs}[0]*3+2]); 125 my @v1 = ($obj->{verts}[$prim->{idxs}[1]*3], $obj->{verts}[$prim->{idxs}[1]*3+1], $obj->{verts}[$prim->{idxs}[1]*3+2]); 126 my @v2 = ($obj->{verts}[$prim->{idxs}[2]*3], $obj->{verts}[$prim->{idxs}[2]*3+1], $obj->{verts}[$prim->{idxs}[2]*3+2]); 127 my @d10 = ($v1[0] - $v0[0], $v1[1] - $v0[1], $v1[2] - $v0[2]); 128 my @d20 = ($v2[0] - $v0[0], $v2[1] - $v0[1], $v2[2] - $v0[2]); 129 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]); 130 my $d = sqrt($x[0]*$x[0] + $x[1]*$x[1] + $x[2]*$x[2]); 131 my @n = $d ? ($x[0]/$d, $x[1]/$d, $x[2]/$d) : (0,1,0); 132 push @prim_normals, \@n; 133 for my $v (@{ $prim->{idxs} }) { 134 $vertex_normals[$v][0] += $n[0]; 135 $vertex_normals[$v][1] += $n[1]; 136 $vertex_normals[$v][2] += $n[2]; 137 } 138 } 139 140 for (0..$num_verts-1) { 141 my @n = @{ $vertex_normals[$_] }; 142 my $d = sqrt($n[0]*$n[0] + $n[1]*$n[1] + $n[2]*$n[2]); 143 $vertex_normals[$_] = $d ? [$n[0]/$d, $n[1]/$d, $n[2]/$d] : [0,0,0]; 144 } 145 146 print $fh <<EOF; 147 <library_geometries> 148 <geometry id="$name"> 149 <mesh> 150 <source id="$name-Position"> 151 <float_array id="$name-Position-array" count="@{[ $num_verts*3 ]}"> 152EOF 153 154 for my $n (0..$num_verts-1) { 155 printf $fh "%.6f %.6f %.6f\n", $obj->{verts}[$n*3]*SCALE, $obj->{verts}[$n*3+1]*SCALE, $obj->{verts}[$n*3+2]*SCALE; 156 } 157 158 print $fh <<EOF; 159</float_array> 160 <technique_common> 161 <accessor source="#$name-Position-array" count="$num_verts" stride="3"> 162 <param name="X" type="float"/> 163 <param name="Y" type="float"/> 164 <param name="Z" type="float"/> 165 </accessor> 166 </technique_common> 167 </source> 168 <source id="$name-Normal0"> 169 <float_array id="$name-Normal0-array" count="@{[ $num_verts*3 ]}"> 170EOF 171 172 for my $n (0..$num_verts-1) { 173 printf $fh "%.6f %.6f %.6f\n", @{$vertex_normals[$n]}; 174 } 175 176 my @uvs; 177 for my $prim (@{ $obj->{prims} }) { 178 my ($u0,$v0, $u1,$v1) = $atlas->get_texcoords(texture_filename($prim)); 179 if (@{ $prim->{idxs} } == 3) { 180 push @uvs, $u0,$v0, $u0,$v1, $u1,$v1; 181 } elsif (@{ $prim->{idxs} } == 4) { 182 push @uvs, $u0,$v0, $u0,$v1, $u1,$v1, $u1,$v0; 183 } else { 184 push @uvs, ($u1,$v0) x @{ $prim->{idxs} }; 185 } 186 } 187 my $num_uvs = @uvs/2; 188 189 print $fh <<EOF; 190</float_array> 191 <technique_common> 192 <accessor source="#$name-Normal0-array" count="$num_verts" stride="3"> 193 <param name="X" type="float"/> 194 <param name="Y" type="float"/> 195 <param name="Z" type="float"/> 196 </accessor> 197 </technique_common> 198 </source> 199 <source id="$name-UV0"> 200 <float_array id="$name-UV0-array" count="@{[ $num_uvs*2 ]}"> 201@uvs 202</float_array> 203 <technique_common> 204 <accessor source="#$name-UV0-array" count="$num_uvs" stride="2"> 205 <param name="S" type="float"/> 206 <param name="T" type="float"/> 207 </accessor> 208 </technique_common> 209 </source> 210 <vertices id="$name-Vertex"> 211 <input semantic="POSITION" source="#$name-Position"/> 212 </vertices> 213 <polygons count="$num_prims"> 214 <input semantic="VERTEX" offset="0" source="#$name-Vertex"/> 215 <input semantic="NORMAL" offset="1" source="#$name-Normal0"/> 216 <input semantic="TEXCOORD" offset="2" set="0" source="#$name-UV0"/> 217EOF 218 219 my $i = 0; 220 my $j = 0; 221 for my $prim (@{ $obj->{prims} }) { 222 print $fh "<p>"; 223 for (@{ $prim->{idxs} }) { 224 print $fh "$_ $_ $j "; 225 ++$j; 226 } 227 ++$i; 228 print $fh "</p>\n"; 229 } 230 231 print $fh <<EOF; 232 </polygons> 233 </mesh> 234 </geometry> 235 </library_geometries> 236EOF 237} 238 239sub write_mesh 240{ 241 my ($obj, $atlas, $name, $fh) = @_; 242 243 print $fh <<EOF; 244<?xml version="1.0" encoding="utf-8"?> 245<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.0"> 246 <asset> 247 <unit meter="0.025400"/> 248 <up_axis>Y_UP</up_axis> 249 </asset> 250EOF 251 252 write_geometries($obj, $atlas, $name, $fh); 253 254 print $fh <<EOF; 255 <library_visual_scenes> 256 <visual_scene id="RootNode" name="RootNode"> 257 <node name="$name\_node"> 258 <instance_geometry url="#$name"/> 259 </node> 260 </visual_scene> 261 </library_visual_scenes> 262 <scene> 263 <instance_visual_scene url="#RootNode"/> 264 </scene> 265</COLLADA> 266EOF 267} 268 269sub write_joint 270{ 271 my ($joint, $fh, $indent) = @_; 272 273 print $fh qq[$indent<node id="prop-$joint->{name}" name="prop-$joint->{name}" type="JOINT">\n]; 274 print $fh qq[$indent <translate>] . (sprintf "%.6f %.6f %.6f", map $_*SCALE, $joint->{offset}[0], $joint->{offset}[1], -$joint->{offset}[2]) . qq[</translate>\n]; 275 for my $c (@{ $joint->{children} }) { 276 write_joint($c, $fh, "$indent "); 277 } 278 print $fh qq[$indent</node>\n]; 279} 280 281sub write_skeleton 282{ 283 my ($skeleton, $rootobj, $atlas, $name, $fh) = @_; 284 285 print $fh <<EOF; 286<?xml version="1.0" encoding="utf-8"?> 287<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.0"> 288 <asset> 289 <unit meter="0.025400"/> 290 <up_axis>Y_UP</up_axis> 291 </asset> 292EOF 293 294 write_geometries($rootobj, $atlas, $name, $fh); 295 296 print $fh <<EOF; 297 <library_visual_scenes> 298 <visual_scene id="RootNode"> 299 <node type="NODE" name="$name\_node"> 300 <rotate>0 1.0 0 180</rotate> 301 <instance_geometry url="#$name"/> 302EOF 303 304 write_joint($_, $fh, " ") for @$skeleton; 305 306 print $fh <<EOF; 307 </node> 308 </visual_scene> 309 </library_visual_scenes> 310 <scene> 311 <instance_visual_scene url="#RootNode"/> 312 </scene> 313</COLLADA> 314EOF 315} 316 317sub extract_joint_names 318{ 319 my ($joint) = @_; 320 return ($joint->{name}, map { extract_joint_names($_) } @{ $joint->{children} }); 321} 322 323sub texture_filename 324{ 325 my ($prim) = @_; 326 if ($prim->{texture}) { 327 my $base = "$prim->{texture}"; 328 my @files = ("${base}.tga", "${base}00.tga", "${base}00.bmp", "${base}00.BMP"); 329 push @files, map { lc $_ } @files; 330 @files = grep { -e "$SOURCE/unittextures/tatex/$_" } @files; 331 die "can't find $prim->{texture}" unless @files; 332 return "$SOURCE/unittextures/tatex/$files[0]"; 333 } else { 334 return SpringPalette::get_image($prim->{palette}); 335 } 336} 337 338sub write_skeleton_actor 339{ 340 my ($skeleton, $name, $fh) = @_; 341 print $fh <<EOF; 342<?xml version="1.0" encoding="utf-8"?> 343<actor version="1"> 344 345 <castshadow/> 346 347 <group> 348 <variant frequency="100" name="Base"> 349 <mesh>spring/$name.dae</mesh> 350 <texture>spring/$name.tga</texture> 351 <props> 352 <prop actor="props/units/weapons/spear_gold.xml" attachpoint="projectile"/> 353EOF 354 355 my @joints = map { extract_joint_names($_) } @$skeleton; 356 for my $n (@joints[1..$#joints]) { 357 print $fh qq[ <prop actor="spring/$name-$n.xml" attachpoint="$n"/>\n]; 358 } 359 360 print $fh <<EOF; 361 </props> 362 </variant> 363 </group> 364 365 <material>player_trans.xml</material> 366 367</actor> 368EOF 369} 370 371sub write_mesh_actor 372{ 373 my ($mesh, $prefix, $name, $fh) = @_; 374 #warn Dumper $mesh; 375 print $fh <<EOF; 376<?xml version="1.0" encoding="utf-8"?> 377<actor version="1"> 378 379 <castshadow/> 380 381 <group> 382 <variant frequency="100" name="Base"> 383 <mesh>spring/$name.dae</mesh> 384 <texture>spring/$prefix.tga</texture> 385 </variant> 386 </group> 387 388 <material>player_trans.xml</material> 389 390</actor> 391EOF 392} 393 394sub write_entity 395{ 396 my ($prefix, $fh) = @_; 397 print $fh <<EOF; 398<?xml version="1.0" encoding="utf-8"?> 399<Entity parent="template_unit_infantry_ranged"> 400 <Identity> 401 <Civ>hele</Civ> 402 <SpecificName>$prefix</SpecificName> 403 </Identity> 404 <Promotion> 405 <Entity>???</Entity> 406 </Promotion> 407 <UnitMotion> 408 <WalkSpeed>9.0</WalkSpeed> 409 </UnitMotion> 410 <VisualActor> 411 <Actor>spring/$prefix.xml</Actor> 412 </VisualActor> 413EOF 414 415 if ($prefix =~ /^(armaas|armbats|corcrus)$/) { 416 print $fh <<EOF; 417 <Position> 418 <Floating>true</Floating> 419 </Position> 420EOF 421 } 422 423 print $fh <<EOF; 424</Entity> 425EOF 426} 427 428sub extract_textures 429{ 430 my ($obj, $atlas) = @_; 431 432 my @meshes; 433 my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} }; 434 435 for (@prims) { 436 $atlas->add(texture_filename($_)); 437 } 438 439 extract_textures($_, $atlas) for @{ $obj->{children} }; 440} 441 442sub extract_meshes 443{ 444 my ($obj) = @_; 445 446 my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} }; 447 448 my @meshes; 449 push @meshes, { name => $obj->{name}, verts => $obj->{verts}, prims => \@prims }; 450 push @meshes, map { extract_meshes($_) } @{ $obj->{children} }; 451 return @meshes; 452} 453 454sub extract_skeleton 455{ 456 my ($obj) = @_; 457 return { name => $obj->{name}, offset => $obj->{offset}, children => [ map { extract_skeleton($_) } @{ $obj->{children} } ] }; 458} 459 460sub write_model 461{ 462 my ($prefix, $model) = @_; 463 464 my $atlas = new TextureAtlas(256); 465 extract_textures($model, $atlas); 466 $atlas->finish("$ROOT/art/textures/skins/spring/$prefix.tga"); 467 468 my @meshes = extract_meshes($model); 469 #print Dumper \@meshes; 470 471 #exit; 472 473 my @skeleton = extract_skeleton($model); 474 #print Dumper \@skeleton; 475 476 { 477 open my $fhm, '>', "$ROOT/art/meshes/spring/$prefix.dae" or die $!; 478 write_skeleton(\@skeleton, $meshes[0], $atlas, $prefix, $fhm); 479 480 open my $fha, '>', "$ROOT/art/actors/spring/$prefix.xml" or die $!; 481 write_skeleton_actor(\@skeleton, $prefix, $fha); 482 } 483 484 for my $mesh (@meshes[1..$#meshes]) { 485 my $name = $prefix.'-'.$mesh->{name}; 486 open my $fhm, '>', "$ROOT/art/meshes/spring/$name.dae" or die $!; 487 write_mesh($mesh, $atlas, $name, $fhm); 488 489 open my $fha, '>', "$ROOT/art/actors/spring/$name.xml" or die $!; 490 write_mesh_actor($mesh, $prefix, $name, $fha); 491 } 492 493 { 494 open my $fhe, '>', "$ROOT/simulation/templates/spring/$prefix.xml" or die $!; 495 write_entity($prefix, $fhe); 496 } 497} 498 499#for my $n (qw(armpw armcom KrogTaar armlab corak corlab)) { 500for my $n (map { (/.*\/(.*)\.3do/)[0] } grep /\/(cor|arm)[a-z]+\./, <$SOURCE/Objects3d/*.3do>) { 501 print "$n\n"; 502 open my $t, "$SOURCE/Objects3d/$n.3do" or die "$n: $!"; 503 binmode $t; 504 my $model = parse_3do($t); 505 #print Dumper $model; 506 write_model($n, $model); 507} 508