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