The One Lab

The Utility of Perl

Mon Mar 15, 2010 05:05:05 -0600

I've been hacking on a fairly recent app of mine -- one that my brother had the idea for first, and I decided to take up recently. He and I like to play Magic: The Gathering fairly often, but for obvious reasons we can't find a way to play it over the 'net. In fact, we looked into the Magic: The Gathering Online, but realized that we would end up spending nearly double our original investment just to buy and put together the same decks we liked to play with in the first place.

So my brother managed to come up with an idea for a 3D card game program -- one that basically just shows the cards, a deck, and handles sending the positions and such to the other end. Pretty simple, but a slight bit difficult.

Anyway, in the process of building this little proggie, I decided to use PhysicsFS to make adding texture, script, and model packs to the game easy (and its cross-platform FS API is a plus). All of my model files are simply designed -- just a pair of texture filenames (one for the front and back of the quad) and a set of four 3D floating-point vectors, repeated for each quad in the model. Unfortunately, PhysicsFS doesn't seem to have any equivalent of a sscanf or fgets -- only number reading functions and read -- so parsing in floating point numbers in string form is a pain. I could roll my own fgets or equivalent, but that's a also a bit of a PITA to get it right. Instead, I chose the lazy route. I chose Perl.

I've found that Perl's pack function is perfect to create binary files of structured information, so I wrote a quick script to load in a model in text form, and write it out to a binary version of it -- complete with null-terminated strings! In all, using this method is the simplest I've found for what I want to do, and so far it seems to work very well! So here's the source:

#!/usr/bin/perl -w

package Quad;

use strict;
use warnings;

use POSIX;

sub new {
    my $self = {
        vertices => [],
        textures => []
    };

    bless($self);
    return $self;
}

sub addVertex {
    my $self = shift;
    my $line = shift;
    my @vertex;

    @vertex = split(/, ?/, $line);
    return undef if (@vertex != 3);

    push @{$self->{vertices}}, @vertex
}

sub setTexture {
    my $self = shift;
    my $line = shift;

    foreach my $tex (split(/ /, $line)) {
        push @{$self->{textures}}, $tex;
    }

    return undef if (@{$self->{textures}} != 2);
}

sub toBinary {
    my $self = shift;
    my $result = "";

    foreach my $texture (@{$self->{textures}}) {
        print $texture;
        print "\000";
    }

    foreach my $vertex (@{$self->{vertices}}) {
        my $whole = fmod($vertex, 1);
        my $fract = $vertex - fmod($vertex, 1);
        $fract =~ s/^0.//;

        print pack "l<", $whole;
        print pack "L<", $fract;
    }
}

1;


package txt2mdl;

use strict;
use warnings;

use Data::Dumper;

my @quads;
my $quad;

while (my $line = <>) {
    chomp($line);

    # Skip comments and empty lines
    $line =~ s/#.*//;
    next if ($line =~ /^$/);

    if ($line =~ /^(-?\d+\.\d+( *, *)?)+$/) { # -0.00, -0.00, 0.00
        $quad->addVertex($line);
    } else {
        $quad = new Quad();
        $quad->setTexture($line);
        push @quads, $quad;
    }
}

print "MDL\000";
print pack("L<", scalar(@quads));
foreach my $quad (@quads) {
    $quad->toBinary();
}

1;

=pod

File format is thus:

  Offset 0,      len 4: 'M' 'D' 'L' '\0'
  Offset 4,      len 4: 32-bit unsigned long -- count of quads for this model

  -- start of a quad --
  Offset 8,      len n: Null-terminated string for the front texture
  Offset 8+n,    len m: Null-terminated string for the back texture

  -- start of a 3D vector --
  Offset 8+n+m,  len 4: 32-bit signed long -- whole of the x of the vector number
  Offset 12+n+m, len 4: 32-bit unsigned long -- frac part of the x of the vector number
  Offset 16+n+m, len 4: 32-bit signed long -- whole of the y of the vector number
  Offset 20+n+m, len 4: 32-bit unsigned long -- frac part of the y of the vector number
  Offset 24+n+m, len 4: 32-bit signed long -- whole of the z of the vector number
  Offset 28+n+m, len 4: 32-bit unsigned long -- frac part of the z of the vector number

  Repeat the above vector layout three more times (once for each
  vector in the quad), then repeat the whole quad block described at
  offset 8 the number of times listed at offset 4. The start of each
  quad is deliniated by the two texture strings, and there are always
  four vectors per quad.

  Note: all numbers are written in little-endian byte-order to the
  file.

=cut

Post a comment

blog comments powered by Disqus