[TOC] [Prev] [Next] BM FILES

BM files store textures used in a variety of ways in DF.
They serve as wall textures, as floor and ceiling textures (in which case they must be 64*64), and as 3DO facet textures, as weapons, and as the Heads Up Display.

See Also


Normal BM

Here is the data structure for the BM file header.

BM_Header IS
{
MAGICchar[4]// = 'BM ' + 0x1E
SizeX int16// if = 1 then multiple BM in the file
SizeYint16// EXCEPT if SizeY also = 1,// in which case it is a 1x1 BM
idemX int16//unused by engine
idemYint16 //unused by engine
Transparent byte// 0x36 for normal // 0x3E for transparent // 0x08 for weapons
logSizeYbyte// logSizeY = log2(SizeY)// logSizeY = 0 for weapons
Compressedint16 // 0 = not compressed// 1 = compressed (RLE)// 2 = compressed (RLE0)
DataSize int32// Data size for compressed BM// excluding header and columns starts table// If not compressed,DataSize is unused
pad1byte[12]// 12 times 0x00
}

Pease note that BM must have a height which is a power of 2 (except weapons).
The data follows, encoded by COLUMNS from the bottom to the top.


Transparent BM

You can transform any BM in a transparent BM by changing its Transparent value from 0x36 to 0x3E. The color 0 will 'disappear' and you will be able to see through it if it is a MID texture on an adjoined wall. Note that this isn't the same as DOOM transparent textures (which use something very similar to RLE0).

Note that weapons BM use 0x08 for their transparent value, so maybe the transparent byte is a collection of flags, where the bit 3 means transparent.


Multiple BM

IIf SizeX = 1 (EXCEPT if SizeY = 1 in which case it is a 1*1 BM) the BM file is multiple.

The header of multiple BMs is different from that of a normal BM.

BM_Multiple_Header IS
{
MAGICchar[4]// = 'BM ' + 0x1E
SizeXint16// = 1
SizeYint16// = length of file - 32
idemXint16// = -2
idemYint16// number of 'sub' BMs
Transparentbyte
logSizeYbyte
Compressedint16
DataSizeint32
pad1byte[12]// 12 times 0x00
}

Important notes

Straight after the Multiple BM header are two bytes: - The first is either the frame rate (in frames per second) of an animated texture, or is 0 to designate a switch. You may alter this value if you want. (Note: A switch can only have two textures on or off and anything more will not work.) - The second byte is 2.

Then follows a table of offsets to the 'sub' BM composed of idemY * int32. The simple fact that this table exists tells us that sub BMs of different sizes may be stored. Each 'sub' BM then has its own header, slightly different from the BM_Header:

BM_SUBHeader IS
{
SizeXint16// horizontal size
SizeYint16// vertical size
idemXint16// unused by engine
idemYint16// unused by engine
DataSizeint32// unused (no compression allowed)
logSizeYbyte// logSizeY = log2(SizeY)
pad1byte[3]
u1byte[3]// these are always filled, but they seem // to be unused
pad2byte[5]
Transparentbyte// 0x36 for normal // 0x3E for transparent
pad3byte[3]
}

Important Notes

1) There is no MAGIC field.
2) For a multiple BM to work correctly, it must be made a SIGN, and for switches there MUST also exist a corresponding trigger in the .INF Else, switches will be displayed wrong (as a single column) and the animated will display correctly, but static. This means that you cannot do animated floors and ceilings this way !
3) The multiple BMs are limited to 64K in size because SizeY contains the size of the file - 32 and is an int.
Although it should never be a problem with switches, this means that you must use animated BMs for small textures only.

A solution that allows animated walls of any size AND animated floors and ceilings is to compose a huge texture with your multiple images pasted next to each other. Then use INF elevators to scroll wall or scroll floor/ceiling using the offsets of the images as stops. If you set a speed of 0, the change will be instantaneous, and the effect will be the same. An added bonus is that you'll also have complete control on starting/stopping the animation.


Compressed BM

If Compressed = 1 or 2, the BM is compressed.
These existed in the DEMO (buyit.bm, Compressed = 1; wait.bm, Compressed = 2), but there aren't any in the full game.
The engine still supports them however, so here are their descriptions.
Note that Multiple BMs don't allow compression.
(thanks to Alex Novikov for corrections and improvements on these notions).

The heart of the data is a columns starts table, with the start addresses of each of the columns. It is at the end of the file, at offset DataSize, and has one int32 entry per column containing this column start address. This start address is calculated without the 32 bytes BM header (i.e. read the header in a struct, then the data in a huge buffer at offset 0).

Compressed = 1 (RLE)

The coding of one column follows (in pseudo code format).

while(end of data for this column not reached)
{
 if(buffer[address] <= 127)
   the FOLLOWING n bytes are direct values
 else
   the FOLLOWING byte is a color byte to repeat n-128 times
}

So, for example, the following hex values ...88 02 17 28 82... mean:
write 8 pixels of color 02, then write 17 pixels with colors 28, 82, etc.

This should be the format of choice for non-transparent BMs.

Compressed = 2 (RLE0)

The coding of one column follows (in pseudo code format).

while(end of data for this column not reached)
{
 if(buffer[address] <= 127)
   the FOLLOWING n bytes are direct values
 else
   skip n-128 transparent (background) pixels
}

So, for example, the following hex values ...88 02 17 28 82... mean:
skip 8 background pixels, then write two pixels with colors 17 and 28, then skip 2 background pixels, etc.

This should be the format of choice for transparent BMs.