mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 00:56:39 +00:00 
			
		
		
		
	git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@138 ea6a568a-9f4f-0410-981a-c910a81bb256
		
			
				
	
	
		
			461 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			D
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			D
		
	
	
	
	
	
/*
 | 
						|
  OpenMW - The completely unofficial reimplementation of Morrowind
 | 
						|
  Copyright (C) 2009  Nicolay Korslund
 | 
						|
  WWW: http://openmw.sourceforge.net/
 | 
						|
 | 
						|
  This file (archive.d) is part of the OpenMW package.
 | 
						|
 | 
						|
  OpenMW is distributed as free software: you can redistribute it
 | 
						|
  and/or modify it under the terms of the GNU General Public License
 | 
						|
  version 3, as published by the Free Software Foundation.
 | 
						|
 | 
						|
  This program is distributed in the hope that it will be useful, but
 | 
						|
  WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
  General Public License for more details.
 | 
						|
 | 
						|
  You should have received a copy of the GNU General Public License
 | 
						|
  version 3 along with this program. If not, see
 | 
						|
  http://www.gnu.org/licenses/ .
 | 
						|
 | 
						|
*/
 | 
						|
 | 
						|
module terrain.archive;
 | 
						|
 | 
						|
const float TEX_SCALE = 1.0/16;
 | 
						|
 | 
						|
// This should be part of the generic cache system.
 | 
						|
const int CACHE_MAGIC = 0x345AF815;
 | 
						|
 | 
						|
import std.mmfile;
 | 
						|
import std.string;
 | 
						|
import std.stdio;
 | 
						|
import terrain.myfile;
 | 
						|
 | 
						|
version(Windows)
 | 
						|
  static int pageSize = 64*1024;
 | 
						|
else
 | 
						|
  static int pageSize = 4*1024;
 | 
						|
 | 
						|
extern(C)
 | 
						|
{
 | 
						|
  // Convert a texture index to string
 | 
						|
  char *d_terr_getTexName(int index)
 | 
						|
    { return g_archive.getString(index).ptr; }
 | 
						|
 | 
						|
  // Fill various hardware buffers from cache
 | 
						|
  void d_terr_fillVertexBuffer(MeshInfo *mi, float *buffer, ulong size)
 | 
						|
    { mi.fillVertexBuffer(buffer[0..size]); }
 | 
						|
 | 
						|
  void d_terr_fillIndexBuffer(MeshInfo *mi, ushort *buffer, ulong size)
 | 
						|
    { mi.fillIndexBuffer(buffer[0..size]); }
 | 
						|
 | 
						|
  void d_terr_fillAlphaBuffer(AlphaInfo *mi, ubyte *buffer, ulong size)
 | 
						|
    { mi.fillAlphaBuffer(buffer[0..size]); }
 | 
						|
 | 
						|
  // Get a given alpha map struct belonging to a mesh
 | 
						|
  AlphaInfo *d_terr_getAlphaInfo(MeshInfo *mi, int index)
 | 
						|
    { return mi.getAlphaInfo(index); }
 | 
						|
 | 
						|
  int d_terr_getAlphaSize() { return g_archive.alphaSize; }
 | 
						|
}
 | 
						|
 | 
						|
// Info about the entire quad. TODO: Some of this (such as the texture
 | 
						|
// scale and probably the width and radius) can be generated at
 | 
						|
// loadtime and is common for all quads on the same level. We could
 | 
						|
// just make a QuadLevelInfo struct.
 | 
						|
struct QuadInfo
 | 
						|
{
 | 
						|
  // Basic info
 | 
						|
  int cellX, cellY;
 | 
						|
  int level;
 | 
						|
 | 
						|
  // Bounding box info
 | 
						|
  float minHeight, maxHeight;
 | 
						|
  float worldWidth;
 | 
						|
  float boundingRadius;
 | 
						|
 | 
						|
  // True if we should make the given child
 | 
						|
  bool hasChild[4];
 | 
						|
 | 
						|
  // Number of mesh segments in this quad
 | 
						|
  int meshNum;
 | 
						|
 | 
						|
  // Location of this quad in the main archive file. The size includes
 | 
						|
  // everything related to this quad, including mesh data, alpha maps,
 | 
						|
  // etc.
 | 
						|
  size_t offset, size;
 | 
						|
}
 | 
						|
 | 
						|
// Info about an alpha map belonging to a mesh
 | 
						|
struct AlphaInfo
 | 
						|
{
 | 
						|
  // Position of the actual image data
 | 
						|
  ulong bufSize, bufOffset;
 | 
						|
 | 
						|
  // The texture name for this layer. The actual string is stored in
 | 
						|
  // the archive's string buffer.
 | 
						|
  int texName = -1;
 | 
						|
  int alphaName = -1;
 | 
						|
 | 
						|
  // Fill the alpha texture buffer
 | 
						|
  void fillAlphaBuffer(ubyte abuf[])
 | 
						|
  {
 | 
						|
    assert(abuf.length == bufSize);
 | 
						|
    g_archive.copy(abuf.ptr, bufOffset, bufSize);
 | 
						|
  }
 | 
						|
}
 | 
						|
static assert(AlphaInfo.sizeof == 6*4);
 | 
						|
 | 
						|
// Info about each submesh
 | 
						|
// If you change this struct please check whether align(1) still fits.
 | 
						|
align(1)
 | 
						|
struct MeshInfo
 | 
						|
{
 | 
						|
  // Bounding box info
 | 
						|
  float minHeight, maxHeight;
 | 
						|
  float worldWidth;
 | 
						|
 | 
						|
  // Vertex and index numbers
 | 
						|
  int vertRows, vertCols;
 | 
						|
 | 
						|
  // Height offset to apply to all vertices
 | 
						|
  float heightOffset;
 | 
						|
 | 
						|
  // Size and offset of the vertex buffer
 | 
						|
  ulong vertBufSize, vertBufOffset;
 | 
						|
 | 
						|
  // Texture name. Index to the string table.
 | 
						|
  int texName = -1;
 | 
						|
 | 
						|
  // Number and offset of AlphaInfo blocks
 | 
						|
  int alphaNum;
 | 
						|
  ulong alphaOffset;
 | 
						|
 | 
						|
  // Fill the given vertex buffer
 | 
						|
  void fillVertexBuffer(float vdest[])
 | 
						|
  {
 | 
						|
    // The height map and normals from the archive
 | 
						|
    byte *hmap = cast(byte*)g_archive.getRelSlice(vertBufOffset, vertBufSize).ptr;
 | 
						|
    // The generic part, containing the x,y coordinates and the uv
 | 
						|
    // maps.
 | 
						|
    float *gmap = g_archive.getVertexBuffer(getLevel()).ptr;
 | 
						|
 | 
						|
    // Destination pointer
 | 
						|
    float *vbuf = vdest.ptr;
 | 
						|
    assert(vdest.length == vertRows*vertCols*8);
 | 
						|
 | 
						|
    // Merge the two data sets together into the output buffer.
 | 
						|
    float offset = heightOffset;
 | 
						|
    for(int y=0; y<vertRows; y++)
 | 
						|
      {
 | 
						|
        // The offset for the entire row is determined by the first
 | 
						|
        // height value. All the values in a row gives the height
 | 
						|
        // relative to the previous value, and the first value in each
 | 
						|
        // row is relative to the first value in the previous row.
 | 
						|
        offset += *cast(short*)hmap;
 | 
						|
 | 
						|
        // This is the 'sliding offset' for this row. It's adjusted
 | 
						|
        // for each vertex that's added, but only affects this row.
 | 
						|
        float rowofs = offset;
 | 
						|
        for(int x=0; x<vertCols; x++)
 | 
						|
          {
 | 
						|
            hmap+=2; // Skip the height we just read
 | 
						|
 | 
						|
            // X and Y from the pregenerated buffer
 | 
						|
            *vbuf++ = *gmap++;
 | 
						|
            *vbuf++ = *gmap++;
 | 
						|
 | 
						|
            // The height is calculated from the current offset
 | 
						|
            *vbuf++ = rowofs * 8;
 | 
						|
 | 
						|
            // Normal vector.
 | 
						|
            *vbuf++ = *hmap++;
 | 
						|
            *vbuf++ = *hmap++;
 | 
						|
            *vbuf++ = *hmap++;
 | 
						|
 | 
						|
            // UV
 | 
						|
            *vbuf++ = *gmap++;
 | 
						|
            *vbuf++ = *gmap++;
 | 
						|
 | 
						|
            // Adjust the offset for the next vertex.
 | 
						|
            if(x < vertCols-1)
 | 
						|
              rowofs += *cast(short*)hmap;
 | 
						|
          }
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  // Fill the index buffer
 | 
						|
  void fillIndexBuffer(ushort ibuf[])
 | 
						|
  {
 | 
						|
    // The index buffer is pregenerated. It is identical for all
 | 
						|
    // meshes on the same level, so just copy it over.
 | 
						|
    ushort generic[] = g_archive.getIndexBuffer();
 | 
						|
    assert(ibuf.length == generic.length);
 | 
						|
    ibuf[] = generic[];
 | 
						|
  }
 | 
						|
 | 
						|
  int getLevel()
 | 
						|
  {
 | 
						|
    assert(g_archive.curQuad);
 | 
						|
    return g_archive.curQuad.level;
 | 
						|
  }
 | 
						|
 | 
						|
  // Get an alpha map belonging to this mesh
 | 
						|
  AlphaInfo *getAlphaInfo(int num)
 | 
						|
  {
 | 
						|
    assert(num < alphaNum && num >= 0);
 | 
						|
    assert(getLevel() == 1);
 | 
						|
    AlphaInfo *res = cast(AlphaInfo*)g_archive.getRelSlice
 | 
						|
      (alphaOffset, alphaNum*AlphaInfo.sizeof);
 | 
						|
    res += num;
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
}
 | 
						|
static assert(MeshInfo.sizeof == 14*4);
 | 
						|
 | 
						|
// The first part of the .index file
 | 
						|
struct ArchiveHeader
 | 
						|
{
 | 
						|
  // "Magic" number to make sure we're actually reading an archive
 | 
						|
  // file
 | 
						|
  int magic;
 | 
						|
 | 
						|
  // Total number of quads in the archive
 | 
						|
  int quads;
 | 
						|
 | 
						|
  // Level of the 'root' quad. There will only be one quad on this
 | 
						|
  // level.
 | 
						|
  int rootLevel;
 | 
						|
 | 
						|
  // Size of the alpha maps, in pixels along one side.
 | 
						|
  int alphaSize;
 | 
						|
 | 
						|
  // Number of strings in the string table
 | 
						|
  int stringNum;
 | 
						|
 | 
						|
  // Size of the string buffer
 | 
						|
  size_t stringSize;
 | 
						|
}
 | 
						|
 | 
						|
TerrainArchive g_archive;
 | 
						|
 | 
						|
// This class handles the cached terrain data.
 | 
						|
struct TerrainArchive
 | 
						|
{
 | 
						|
  MeshInfo *curMesh;
 | 
						|
  QuadInfo *curQuad;
 | 
						|
  QuadInfo *rootQuad;
 | 
						|
 | 
						|
  void openFile(char[] name)
 | 
						|
  {
 | 
						|
    mmf = new MmFile(name,
 | 
						|
                     MmFile.Mode.Read,
 | 
						|
                     0, null, pageSize);
 | 
						|
 | 
						|
    // Read the index file first
 | 
						|
    MyFile ifile = new MyFile(name ~ ".index");
 | 
						|
 | 
						|
    ArchiveHeader head;
 | 
						|
    ifile.fill(head);
 | 
						|
 | 
						|
    // Reads data into an array. Would be better if this was part of
 | 
						|
    // the stream.
 | 
						|
 | 
						|
    // Sanity check
 | 
						|
    assert(head.magic == CACHE_MAGIC);
 | 
						|
    assert(head.quads > 0 && head.quads < 8192);
 | 
						|
 | 
						|
    // Store header info
 | 
						|
    alphaSize = head.alphaSize;
 | 
						|
 | 
						|
    // Read all the quads
 | 
						|
    quadList = new QuadInfo[head.quads];
 | 
						|
    ifile.fillArray(quadList);
 | 
						|
 | 
						|
    // Create an index of all the quads
 | 
						|
    foreach(int index, qn; quadList)
 | 
						|
      {
 | 
						|
        int x = qn.cellX;
 | 
						|
        int y = qn.cellY;
 | 
						|
        int l = qn.level;
 | 
						|
 | 
						|
        assert(l >= 1);
 | 
						|
 | 
						|
        quadMap[l][x][y] = index;
 | 
						|
        assert(index == quadMap[l][x][y]);
 | 
						|
 | 
						|
        // Store the root quad
 | 
						|
        if(l == head.rootLevel)
 | 
						|
          {
 | 
						|
            assert(rootQuad == null);
 | 
						|
            rootQuad = &quadList[index];
 | 
						|
          }
 | 
						|
        else
 | 
						|
          assert(l < head.rootLevel);
 | 
						|
      }
 | 
						|
 | 
						|
    // Make sure the root was set
 | 
						|
    assert(rootQuad !is null);
 | 
						|
 | 
						|
    // Next read the string table. First read the main string buffer.
 | 
						|
    stringBuf = new char[head.stringSize];
 | 
						|
    ifile.fillArray(stringBuf);
 | 
						|
 | 
						|
    // Then read the string offsets
 | 
						|
    int[] offsets = new int[head.stringNum];
 | 
						|
    ifile.fillArray(offsets);
 | 
						|
 | 
						|
    // Set up the string table
 | 
						|
    char *strptr = stringBuf.ptr;
 | 
						|
    strings.length = head.stringNum;
 | 
						|
    foreach(int i, ref str; strings)
 | 
						|
      {
 | 
						|
        // toString(char*) returns the string up to the zero
 | 
						|
        // terminator byte
 | 
						|
        str = toString(strptr + offsets[i]);
 | 
						|
        assert(str.ptr + str.length <=
 | 
						|
               stringBuf.ptr + stringBuf.length);
 | 
						|
      }
 | 
						|
    delete offsets;
 | 
						|
 | 
						|
    // Read the vertex buffer data
 | 
						|
    int bufNum = head.rootLevel;
 | 
						|
    assert(bufNum == 7);
 | 
						|
    vertBufData.length = bufNum;
 | 
						|
 | 
						|
    // Fill the vertex buffers. Start at level 1.
 | 
						|
    for(int i=1;i<bufNum;i++)
 | 
						|
      {
 | 
						|
        // Vertex buffer
 | 
						|
        ifile.readArray(vertBufData[i]);
 | 
						|
      }
 | 
						|
 | 
						|
    // Index buffer
 | 
						|
    ifile.readArray(indexBufData);
 | 
						|
  }
 | 
						|
 | 
						|
  bool hasQuad(int X, int Y, int level)
 | 
						|
  {
 | 
						|
    if((level in quadMap) is null ||
 | 
						|
       (X in quadMap[level]) is null ||
 | 
						|
       (Y in quadMap[level][X]) is null)
 | 
						|
      return false;
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Get info about a given quad from the index.
 | 
						|
  QuadInfo *getQuad(int X, int Y, int level)
 | 
						|
  {
 | 
						|
    assert(hasQuad(X,Y,level), format("Cannot find quad %s %s level %s",
 | 
						|
                                      X, Y, level));
 | 
						|
    int ind = quadMap[level][X][Y];
 | 
						|
    QuadInfo *res = &quadList[ind];
 | 
						|
    assert(res);
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
 | 
						|
  // Maps the terrain and material info for a given quad into
 | 
						|
  // memory. This is typically called right before the meshes are
 | 
						|
  // created.
 | 
						|
  void mapQuad(QuadInfo *info)
 | 
						|
  {
 | 
						|
    assert(info);
 | 
						|
 | 
						|
    // Store the quad for later
 | 
						|
    curQuad = info;
 | 
						|
 | 
						|
    doMap(info.offset, info.size);
 | 
						|
  }
 | 
						|
 | 
						|
  // Get the info struct for a given segment. Remembers the MeshInfo
 | 
						|
  // for all later calls.
 | 
						|
  MeshInfo *getMeshInfo(int segNum)
 | 
						|
    {
 | 
						|
      assert(curQuad);
 | 
						|
      assert(segNum < curQuad.meshNum);
 | 
						|
 | 
						|
      // The mesh headers are at the beginning of the mapped segment.
 | 
						|
      curMesh = cast(MeshInfo*) getRelSlice(0, MeshInfo.sizeof*curQuad.meshNum);
 | 
						|
      curMesh += segNum;
 | 
						|
 | 
						|
      return curMesh;
 | 
						|
    }
 | 
						|
 | 
						|
  float[] getVertexBuffer(int level)
 | 
						|
  {
 | 
						|
    assert(level>=1 && level<vertBufData.length);
 | 
						|
    return vertBufData[level];
 | 
						|
  }
 | 
						|
 | 
						|
  ushort[] getIndexBuffer()
 | 
						|
  {
 | 
						|
    return indexBufData;
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  // All quad headers (from the index) are stored in this array
 | 
						|
  QuadInfo quadList[];
 | 
						|
 | 
						|
  // A map of all quads. Contain indices to the above array. Indexed
 | 
						|
  // by [level][X][Y].
 | 
						|
  int[int][int][int] quadMap;
 | 
						|
 | 
						|
  // These contain pregenerated mesh data that is common for all
 | 
						|
  // meshes on a given level.
 | 
						|
  float[][] vertBufData;
 | 
						|
  ushort[] indexBufData;
 | 
						|
 | 
						|
  // Used for the mmapped file
 | 
						|
  MmFile mmf;
 | 
						|
 | 
						|
  ubyte mapped[];
 | 
						|
 | 
						|
  // Stores the string table
 | 
						|
  char[] stringBuf;
 | 
						|
  char[][] strings;
 | 
						|
 | 
						|
  // Texture size of the alpha maps.
 | 
						|
  int alphaSize;
 | 
						|
 | 
						|
  char[] getString(int index)
 | 
						|
  {
 | 
						|
    assert(index >= 0);
 | 
						|
    assert(index < strings.length);
 | 
						|
 | 
						|
    return strings[index];
 | 
						|
  }
 | 
						|
 | 
						|
  void doMap(size_t offset, size_t size)
 | 
						|
  {
 | 
						|
    assert(mmf !is null);
 | 
						|
    assert(size);
 | 
						|
    mapped = cast(ubyte[])mmf[offset..offset+size];
 | 
						|
    assert(mapped.length == size);
 | 
						|
  }
 | 
						|
 | 
						|
  // Get a slice of a given buffer within the mapped window. The
 | 
						|
  // offset is relative to the start of the window, and the size must
 | 
						|
  // fit inside the window.
 | 
						|
  ubyte[] getRelSlice(size_t offset, size_t size)
 | 
						|
  {
 | 
						|
    assert(mapped.length);
 | 
						|
 | 
						|
    return mapped[offset..offset+size];
 | 
						|
  }
 | 
						|
 | 
						|
  // Copy a given buffer from the file. The buffer might be a
 | 
						|
  // compressed stream, so it's important that the buffers are written
 | 
						|
  // in the same block sizes as they are read. (Ie. you can't write a
 | 
						|
  // buffer as one operation and read it as two, or vice versa. Also,
 | 
						|
  // buffers cannot overlap.) The offset is relative to the current
 | 
						|
  // mapped file window.
 | 
						|
  void copy(void *dst, size_t offset, size_t inSize)
 | 
						|
  {
 | 
						|
    ubyte source[] = getRelSlice(offset, inSize);
 | 
						|
 | 
						|
    // Just copy it for now
 | 
						|
    ubyte* dest = cast(ubyte*)dst;
 | 
						|
    dest[0..source.length] = source[];
 | 
						|
  }
 | 
						|
}
 |