6 use POSIX qw/floor ceil/;
8 my @lumpname = qw/entities textures planes nodes leafs leaffaces leafbrushes models brushes brushsides vertices triangles effects faces lightmaps lightgrid pvs advertisements/;
9 my %lumpid = map { $lumpname[$_] => $_ } 0..@lumpname-1;
17 or die "invalid input file name (must be a .bsp): $fn";
22 read $fh, my $header, 8;
24 die "Invalid BSP format"
25 if $header ne "IBSP\x2e\x00\x00\x00";
29 read $fh, my $lump, 8;
30 my ($offset, $length) = unpack "VV", $lump;
32 print STDERR "BSP lump $_ ($lumpname[$_]): offset $offset length $length\n";
33 push @bsp, [$offset, $length, undef];
38 my ($offset, $length, $data) = @$_;
40 read $fh, $data, $length;
41 length $data == $length
42 or die "Incomplete BSP lump at $offset\n";
52 my ($lump, @fields) = @_;
64 if(/^(\w*)=(.*?)(\d*)$/)
71 push @decoders, sub { $item->{$f} = $data[$idx++]; };
75 push @decoders, sub { $item->{$f} = [ map { $data[$idx++] } 1..$n ]; };
80 my $itemlen = length pack $spec, ();
81 my $len = length $lump;
83 die "Invalid lump size: $len not divisible by $itemlen"
86 my $items = $len / $itemlen;
89 @data = unpack $spec, substr $lump, $_ * $itemlen, $itemlen;
102 if(/^-d(.+)$/) # delete a lump
104 my $id = $lumpid{$1};
105 die "invalid lump $1 to remove"
109 elsif(/^-m(.*)$/) # change the message
113 elsif(/^-l(jpg|png|tga)(\d+)?$/) # externalize lightmaps (deleting the internal ones)
118 my $faces = $bsp[$lumpid{faces}]->[2];
119 my $lightmaps = $bsp[$lumpid{lightmaps}]->[2];
120 my @values = DecodeLump $faces,
121 qw/texture=V effect=V type=V vertex=V n_vertexes=V meshvert=V n_meshverts=V lm_index=V lm_start=f2 lm_size=f2 lm_origin=f3 lm_vec_0=f3 lm_vec_1=f3 normal=f3 size=V2/;
125 my $l = $_->{lm_index};
126 next if $l >= 2**31; # signed
133 $lightmaps{$_+1} = $lightmaps{$_} for keys %lightmaps;
135 for(sort { $a <=> $b } keys %lightmaps)
137 print STDERR "Lightmap $_ was used $lightmaps{$_} times\n";
139 # export that lightmap
140 my $lmsize = 128 * 128 * 3;
141 next if length $lightmaps < ($_ + 1) * $lmsize;
142 my $lmdata = substr $lightmaps, $_ * $lmsize, $lmsize;
143 my $img = Image::Magick->new(size => '128x128', depth => 8, magick => 'RGB');
144 $img->BlobToImage($lmdata);
145 my $outfn = sprintf "%s/lm_%04d.$ext", $basename, $_;
147 $img->Set(quality => $quality)
149 my $err = $img->Write($outfn);
152 print STDERR "Wrote $outfn\n";
155 # nullify the lightmap lump
156 $bsp[$lumpid{lightmaps}]->[2] = "";
158 elsif(/^-g$/) # decimate light grid
160 my @models = DecodeLump $bsp[$lumpid{models}]->[2],
161 qw/mins=f3 maxs=f3 face=V n_faces=V brush=V n_brushes=V/;
162 my $entities = $bsp[$lumpid{entities}]->[2];
163 my @entitylines = split /\r?\n/, $entities;
164 my $gridsize = "64 64 128";
168 /^\s*"gridsize"\s+"(.*)"$/
171 my @scale = map { 1 / $_ } split / /, $gridsize;
172 my @imins = map { ceil($models[0]{mins}[$_] * $scale[$_]) } 0..2;
173 my @imaxs = map { floor($models[0]{maxs}[$_] * $scale[$_]) } 0..2;
174 my @isize = map { $imaxs[$_] - $imins[$_] + 1 } 0..2;
175 my $isize = $isize[0] * $isize[1] * $isize[2];
176 my @gridcells = DecodeLump $bsp[$lumpid{lightgrid}]->[2],
177 qw/ambient=C3 directional=C3 dir=C2/;
178 die "Cannot decode light grid"
179 unless $isize == @gridcells;
181 # TODO now decimate it and reinsert the lump (and the changed entity lump for the new size)
183 elsif(/^-x(.+)$/) # extract lump to stdout
185 my $id = $lumpid{$1};
186 die "invalid lump $1 to extract"
188 print $bsp[$id]->[2];
190 elsif(/^-o(.+)?$/) # write the final BSP file
194 if not defined $outfile;
195 open my $fh, ">", $outfile
196 or die "$outfile: $!";
198 my $pos = 17 * 8 + tell($fh) + length $msg;
202 $_->[1] = length $_->[2];
204 print $fh pack "VV", $_->[0], $_->[1];
212 print "Wrote $outfile\n";
216 die "Invalid option: $_";
222 # decimate light grid
223 # edit lightmaps/grid