Merge 'remotes/trunk'
[0ad.git] / source / tools / springimport / convert.pl
blob25f3349f8177357c02724d7069e9f5e4a5578236
1 # Copyright (C) 2010 Wildfire Games.
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:
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
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.
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.
25 use strict;
26 use warnings;
27 use Data::Dumper;
29 use TextureAtlas;
30 use SpringPalette;
32 use constant SCALE => 3.0 / 1048576;
34 my $ROOT = "../../../binaries/data/mods/public";
35 my $SOURCE = "../../../../../misc/spring/BA712";
37 sub parse_3do
39 my ($fh) = @_;
41 my $b;
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;
48 die unless $sig == 1;
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;
58 seek $fh, $off_verts, 0;
59 read $fh, $b, 12*$num_verts or die;
60 my @verts = unpack 'l*', $b;
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;
68 seek $fh, $off_idxs, 0;
69 read $fh, $b, 2*$num_idxs or die;
70 my @idxs = unpack 's*', $b;
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;
83 push @prims, {
84 palette => $palette,
85 idxs => \@idxs,
86 texture => $texture,
90 my $ret = {
91 name => $name,
92 verts => \@verts,
93 prims => \@prims,
94 offset => [$x, $y, $z],
97 if ($off_child) {
98 seek $fh, $off_child, 0;
99 $ret->{children} = [ parse_3do($fh) ];
102 if ($off_sib) {
103 seek $fh, $off_sib, 0;
104 return ($ret, parse_3do($fh));
105 } else {
106 return ($ret);
110 sub write_geometries
112 my ($obj, $atlas, $name, $fh) = @_;
114 my $num_verts = @{ $obj->{verts} } / 3;
115 my $num_prims = @{ $obj->{prims} };
117 my @vertex_normals;
118 for (0..$num_verts-1) {
119 push @vertex_normals, [0,0,0];
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];
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];
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 ]}">
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;
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 ]}">
172 for my $n (0..$num_verts-1) {
173 printf $fh "%.6f %.6f %.6f\n", @{$vertex_normals[$n]};
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} };
187 my $num_uvs = @uvs/2;
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"/>
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;
227 ++$i;
228 print $fh "</p>\n";
231 print $fh <<EOF;
232 </polygons>
233 </mesh>
234 </geometry>
235 </library_geometries>
239 sub write_mesh
241 my ($obj, $atlas, $name, $fh) = @_;
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>
252 write_geometries($obj, $atlas, $name, $fh);
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>
269 sub write_joint
271 my ($joint, $fh, $indent) = @_;
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 ");
278 print $fh qq[$indent</node>\n];
281 sub write_skeleton
283 my ($skeleton, $rootobj, $atlas, $name, $fh) = @_;
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>
294 write_geometries($rootobj, $atlas, $name, $fh);
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"/>
304 write_joint($_, $fh, " ") for @$skeleton;
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>
317 sub extract_joint_names
319 my ($joint) = @_;
320 return ($joint->{name}, map { extract_joint_names($_) } @{ $joint->{children} });
323 sub texture_filename
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});
338 sub write_skeleton_actor
340 my ($skeleton, $name, $fh) = @_;
341 print $fh <<EOF;
342 <?xml version="1.0" encoding="utf-8"?>
343 <actor version="1">
345 <castshadow/>
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"/>
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];
360 print $fh <<EOF;
361 </props>
362 </variant>
363 </group>
365 <material>player_trans.xml</material>
367 </actor>
371 sub write_mesh_actor
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">
379 <castshadow/>
381 <group>
382 <variant frequency="100" name="Base">
383 <mesh>spring/$name.dae</mesh>
384 <texture>spring/$prefix.tga</texture>
385 </variant>
386 </group>
388 <material>player_trans.xml</material>
390 </actor>
394 sub write_entity
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>
415 if ($prefix =~ /^(armaas|armbats|corcrus)$/) {
416 print $fh <<EOF;
417 <Position>
418 <Floating>true</Floating>
419 </Position>
423 print $fh <<EOF;
424 </Entity>
428 sub extract_textures
430 my ($obj, $atlas) = @_;
432 my @meshes;
433 my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} };
435 for (@prims) {
436 $atlas->add(texture_filename($_));
439 extract_textures($_, $atlas) for @{ $obj->{children} };
442 sub extract_meshes
444 my ($obj) = @_;
446 my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} };
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;
454 sub extract_skeleton
456 my ($obj) = @_;
457 return { name => $obj->{name}, offset => $obj->{offset}, children => [ map { extract_skeleton($_) } @{ $obj->{children} } ] };
460 sub write_model
462 my ($prefix, $model) = @_;
464 my $atlas = new TextureAtlas(256);
465 extract_textures($model, $atlas);
466 $atlas->finish("$ROOT/art/textures/skins/spring/$prefix.tga");
468 my @meshes = extract_meshes($model);
469 #print Dumper \@meshes;
471 #exit;
473 my @skeleton = extract_skeleton($model);
474 #print Dumper \@skeleton;
477 open my $fhm, '>', "$ROOT/art/meshes/spring/$prefix.dae" or die $!;
478 write_skeleton(\@skeleton, $meshes[0], $atlas, $prefix, $fhm);
480 open my $fha, '>', "$ROOT/art/actors/spring/$prefix.xml" or die $!;
481 write_skeleton_actor(\@skeleton, $prefix, $fha);
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);
489 open my $fha, '>', "$ROOT/art/actors/spring/$name.xml" or die $!;
490 write_mesh_actor($mesh, $prefix, $name, $fha);
494 open my $fhe, '>', "$ROOT/simulation/templates/spring/$prefix.xml" or die $!;
495 write_entity($prefix, $fhe);
499 #for my $n (qw(armpw armcom KrogTaar armlab corak corlab)) {
500 for 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);