General

The vmp files contains information about how to put together the 8x8 blocks defined in the corresponding vcn files, into proper walls (including the background).



File format (PC version, EOB I)


In Lands of Lore VMP files are compressed like any other CPS file. In Eye of the Beholder the file
struct VMP
{
    unsigned short nbrOfBlocks;
    unsigned short codes[nbrOfBlocks];
};
For codes meaning look at tiles section below.



File format (Amiga version, EOB I)

The visible viewport is 22x15 blocks big (8x8 pixel sized blocks).
struct VMP
{
   unsigned short header;
   unsigned short backgroundTiles[22][15];
   unsigned short padding[101];
   unsigned short wallTiles[nbrWallTypes][431];
};

header

Not investigated, but most probably contains number of wallTypes defined.

backgroundTiles

Tile indices defining the background graphics. The background is assumed to be x-flipped when party.x&party.y&party.direction = 1. I.e. all kind of moves and rotations from the current position will result in the background being x-flipped.

padding

Each wall type consists of 431 tile incides. The padding allows the backgroundTiles+padding to be 431 tiles in total for simplicity (22*15+101=431).

wallTiles

Tile indices defining the wall graphics per wall type

nbrWallTypes

Probably defined in the header, but can otherwize be obtained from (filesize-2)/(431*2)-1;


Tiles

Each tile is a 16-bit value in the following format (most significant bits first):
struct Tile
{
   unsigned limit      :1;
   unsigned flipped    :1;
   unsigned blockIndex :14;
};

limit

The bit is used for drawing the blocks where the walls meet the ceilings/floors.

flipped

1 if the block graphics should be x-flipped, otherwise 0.

blockIndex

A 14-bit block index selecting a block in the vcn file.


How to render the wall tiles

To simplify rendering, the following structure can be used:
struct WallRenderData
{
   int baseOffset;
   int offsetInViewPort;
   int visibleWidthInBlocks;
   int visibleHeightInBlocks;
   int skipValue;
   int flipFlag;
} wallRenderData[25]= /* 25 different wall positions exists */
{
   /* Side-Walls left back */
   {   3, 66, 5, 1, 2, 0},             /* A-east */
   {   1, 68, 5, 3, 0, 0},             /* B-east */
   {  -4, 74, 5, 1, 0, 0},             /* C-east */
 
   /* Side-Walls right back */
   {  -4, 79, 5, 1, 0, 1},              /* E-west */
   {   1, 83, 5, 3, 0, 1},              /* F-west */
   {   3, 87, 5, 1, 2, 1},              /* G-west */
 
   /* Frontwalls back */
   {  32, 66, 5, 2, 4, 0},              /* B-south */
   {  28, 68, 5, 6, 0, 0},              /* C-south */
   {  28, 74, 5, 6, 0, 0},              /* D-south */
   {  28, 80, 5, 6, 0, 0},              /* E-south */
   {  28, 86, 5, 2, 4, 0},              /* F-south */
 
   /* Side walls middle back left */
   {  16, 66, 6, 2, 0, 0},              /* H-east */
   { -20, 50, 8, 2, 0, 0},              /* I-east */
 
   /* Side walls middle back right */
   { -20, 58, 8, 2, 0, 1},              /* K-west */
   {  16, 86, 6, 2, 0, 1},              /* L-west */
 
   /* Frontwalls middle back */
   {  62, 44, 8, 6, 4, 0},              /* I-south */
   {  58, 50, 8,10, 0, 0},              /* J-south */
   {  58, 60, 8, 6, 4, 0},              /* K-south */
 
   /* Side walls middle front left */
   { -56, 25,12, 3, 0, 0},              /* M-east */
 
   /* Side walls middle front right */
   { -56, 38,12, 3, 0, 1},              /* O-west */
 
   /* Frontwalls middle front */
   { 151, 22,12, 3,13, 0},              /* M-south */
   { 138, 41,12, 3,13, 0},              /* O-south */
   { 138, 25,12,16, 0, 0},              /* N-south */
 
   /* Side wall front left */
   {-101,  0,15, 3, 0, 0},              /* P-east */
 
   /* Side wall front right */
   {-101, 19,15, 3, 0, 1},              /* Q-west */
};

baseOffset

Base offset into wallTiles[wallType-1]. This can be negative, but don't be deceived, this is just a base. The other values will add to this base making sure the wallTiles[wallType-1] array is accessed within bounds.

offsetInViewPort

Block index in the viewport where to start render.
xpos = offsetInViewPort%22;
ypos = offsetInViewPort/22;

visibleWidthInBlocks

How many visible blocks wide the wall is in the viewport.

visibleHeightInBlocks

How many visible blocks hight the wall is in the viewport.

skipValue

Number of tiles to the next row in the wallTiles[wallType-1] array.

flipFlag

1 if the wall is to be x-flipped in the viewport. Generally all right-walls are x-flipped.

Field of vision

A|B|C|D|E|F|G
  ¯ ¯ ¯ ¯ ¯
  H|I|J|K|L
    ¯ ¯ ¯
    M|N|O
    ¯ ¯ ¯
    P|^|Q
^ in the picture is the party position, facing north.
| and - in the above picture resembled walls. There are a total of 25 different wall positions. To render all the walls correctly 17 maze positions must be read (A-Q).

Pseudo code for rendering the background:
   void drawBackground(bool xflip)
   {
      for (int y=0; y<15; y++)
      {
         for (int x=0; x<22; x++)
         {
            int tile;
 
            if (xflip)
               tile=vmp.backgroundTiles[21-x][y];
            else
               tile=vmp.backgroundTiles[x][y];
 
            /* xor with xflip to make block flip and background flip cancel each other out. */
            int blockFlip = (tile&0x4000)^flipX;
            int blockIndex= tile&0x3fff;
 
            if (blockFlip)
               drawBlockXFlip(x*8, y*8, vcn.blocks[blockIndex]);
            else
               drawBlock(x*8, y*8, vcn.blocks[blockIndex]);
         }
      }
   }

Pseudo code for rendering a wall:
   /* wallType = wallType index aquired from the wallMapping[mazeByte].wallType. See the .inf file.
    * wallPosition = index to wall position in the viewport.
    */
   void drawWall(int wallType, int wallPosition)
   {
      /* 0x4000 because when x-ored with this flip flag in the tile data,
         the resulting flip state will be correct */
      int flipX=wallRenderData[wallPosition].flipFlag ? 0:0x4000;
      int offset=wallRenderData[wallPosition].baseOffset;
 
      for (int y=0; y<wallRenderData[wallPosition].visibleHeightInBlocks; y++)
      {
         for (int x=0; x<wallRenderData[wallPosition].visibleWidthInBlocks; x++)
         {
            int blockIndex;
            if (flipX==0)
            {
               blockIndex=x+y*22+wallRenderData[wallPosition].offsetInViewPort;
            }
            else
            {
               blockIndex=wallRenderData[wallPosition].offsetInViewPort+
                          wallRenderData[wallPosition].visibleWidthInBlocks-
                          1-
                          x+y*22;
            }
 
            int xpos=blockIndex%22;
            int ypos=blockIndex/22;
 
            int tile=vmp.wallTiles[wallType][offset];
 
            /* xor with wall flip-x to make block flip and wall flip cancel each other out. */
            int blockFlip = (tile&0x4000)^flipX;
            int blockIndex= tile&0x3fff;
 
            if (blockFlip)
               drawBlockXFlip(xpos*8, ypos*8, vcn.blocks[blockIndex]);
            else
               drawBlock(xpos*8, ypos*8, vcn.blocks[blockIndex]);
 
            offset++;
         }
        offset+=wallRenderData[wallPosition].skipValue;
      }
 
   }

Note: drawBlock and drawBlockXFlip draws an 8x8 block but with color 0 as transparent. Thus before rendering anything make sure to clear the viewport with color 0.