forked from mirror/openmw-tes3mp
Rewrote more of the terrain system in D
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@121 ea6a568a-9f4f-0410-981a-c910a81bb256actorid
parent
2e1f97f3d5
commit
4f5b9d019a
@ -0,0 +1,17 @@
|
||||
module terrain.bindings;
|
||||
|
||||
alias void *SceneNode;
|
||||
alias void *Bounds;
|
||||
alias void *MeshObj;
|
||||
|
||||
extern(C):
|
||||
|
||||
SceneNode terr_createChildNode(float relX, float relY, SceneNode);
|
||||
void terr_destroyNode(SceneNode);
|
||||
Bounds terr_makeBounds(float minHeight, float maxHeight, float width);
|
||||
float terr_getSqCamDist(Bounds);
|
||||
MeshObj terr_makeMesh(int segment, SceneNode);
|
||||
void terr_killMesh(MeshObj);
|
||||
|
||||
void terr_genData();
|
||||
void terr_setupRendering();
|
@ -1,463 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2009 Nicolay Korslund
|
||||
WWW: http://openmw.sourceforge.net/
|
||||
|
||||
This file (cpp_archive.cpp) 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/ .
|
||||
|
||||
*/
|
||||
|
||||
// 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.
|
||||
struct QuadInfo
|
||||
{
|
||||
// Basic info
|
||||
int cellX, cellY;
|
||||
int level;
|
||||
|
||||
// Bounding box info
|
||||
float minHeight, maxHeight;
|
||||
float worldWidth;
|
||||
float boundingRadius;
|
||||
|
||||
// Texture scale for this quad
|
||||
float texScale;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
struct MeshInfo;
|
||||
|
||||
const static int CACHE_MAGIC = 0x345AF815;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// This class handles the cached terrain data.
|
||||
class TerrainArchive
|
||||
{
|
||||
public:
|
||||
MeshInfo *curMesh;
|
||||
QuadInfo *curQuad;
|
||||
|
||||
QuadInfo *rootQuad;
|
||||
|
||||
TerrainArchive()
|
||||
: mappedPtr(0),
|
||||
mappedSize(0),
|
||||
mmf(0),
|
||||
curMesh(0),
|
||||
curQuad(0),
|
||||
rootQuad(0) {}
|
||||
|
||||
void openFile(const std::string &name)
|
||||
{
|
||||
mmf = new MmFile(name);
|
||||
|
||||
// Read the index file first
|
||||
std::ifstream ifile((name+".index").c_str(), std::ios::binary);
|
||||
|
||||
ArchiveHeader head;
|
||||
ifile.read((char*)&head, sizeof(head));
|
||||
|
||||
// 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.read((char*)quadList, sizeof(QuadInfo)*head.quads);
|
||||
|
||||
// Create an index of all the quads
|
||||
for(int qn = 0; qn < head.quads; qn++)
|
||||
{
|
||||
int x = quadList[qn].cellX;
|
||||
int y = quadList[qn].cellY;
|
||||
int l = quadList[qn].level;
|
||||
|
||||
assert(l >= 1);
|
||||
|
||||
quadMap[l][x][y] = qn;
|
||||
|
||||
// Store the root quad
|
||||
if(l == head.rootLevel)
|
||||
{
|
||||
assert(rootQuad == NULL);
|
||||
rootQuad = &quadList[qn];
|
||||
}
|
||||
else
|
||||
assert(l < head.rootLevel);
|
||||
}
|
||||
|
||||
// Make sure the root was set
|
||||
assert(rootQuad != NULL);
|
||||
|
||||
// Next read the string table
|
||||
stringBuf = new char[head.stringSize];
|
||||
stringOffsets = new int[head.stringNum];
|
||||
stringNum = head.stringNum;
|
||||
|
||||
// First read the main string buffer
|
||||
ifile.read(stringBuf, head.stringSize);
|
||||
|
||||
// Then read the string offsets
|
||||
ifile.read((char*)stringOffsets, stringNum*sizeof(int));
|
||||
|
||||
// Read the vertex buffer data
|
||||
int bufNum = head.rootLevel;
|
||||
assert(bufNum == 7);
|
||||
vertBufData.resize(bufNum);
|
||||
indexBufData.resize(bufNum);
|
||||
|
||||
// Fill the buffers. Start at level 1.
|
||||
for(int i=1;i<bufNum;i++)
|
||||
{
|
||||
int size;
|
||||
void *ptr;
|
||||
|
||||
// Vertex buffer
|
||||
ifile.read((char*)size, sizeof(int));
|
||||
ptr = malloc(size);
|
||||
vertBufData[i] = ptr;
|
||||
ifile.read((char*)ptr, size);
|
||||
|
||||
// Index buffer
|
||||
ifile.read((char*)size, sizeof(int));
|
||||
ptr = malloc(size);
|
||||
indexBufData[i] = ptr;
|
||||
ifile.read((char*)ptr, size);
|
||||
}
|
||||
}
|
||||
|
||||
// Get info about a given quad from the index.
|
||||
QuadInfo *getQuad(int X, int Y, int level)
|
||||
{
|
||||
// FIXME: First check if the quad exists. This function should
|
||||
// FAIL if that is not the case.
|
||||
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);
|
||||
|
||||
float *getVertexBuffer(int level)
|
||||
{
|
||||
assert(level>=1 && level<vertBufData.size());
|
||||
return (float*)vertBufData[level];
|
||||
}
|
||||
|
||||
unsigned short *getIndexBuffer(int level, int &size)
|
||||
{
|
||||
assert(level>=1 && level<indexBufData.size());
|
||||
return (unsigned short*)indexBufData[level];
|
||||
}
|
||||
|
||||
private:
|
||||
// All quad headers (from the index) are stored in this list
|
||||
QuadInfo *quadList;
|
||||
|
||||
// A map of all quads. Contain indices to the above array. Indexed
|
||||
// by [level][X][Y].
|
||||
std::map<int,std::map<int,std::map<int,int> > > quadMap;
|
||||
|
||||
// These contain pregenerated mesh data that is common for all
|
||||
// meshes on a given level.
|
||||
std::vector<void*> vertBufData;
|
||||
std::vector<void*> indexBufData;
|
||||
|
||||
// Used for the mmapped file
|
||||
MmFile *mmf;
|
||||
uint8_t *mappedPtr;
|
||||
size_t mappedSize;
|
||||
|
||||
// Stores the string table
|
||||
char *stringBuf;
|
||||
int *stringOffsets;
|
||||
int stringNum;
|
||||
|
||||
// Texture size of the alpha maps
|
||||
int alphaSize;
|
||||
|
||||
const char *getString(int index)
|
||||
{
|
||||
assert(index >= 0);
|
||||
assert(index < stringNum);
|
||||
|
||||
return stringBuf + stringOffsets[index];
|
||||
}
|
||||
|
||||
void doMap(size_t offset, size_t size)
|
||||
{
|
||||
assert(mmf != NULL);
|
||||
mappedPtr = (uint8_t*)mmf->map(offset, size);
|
||||
mappedSize = size;
|
||||
assert(mappedPtr != NULL);
|
||||
}
|
||||
|
||||
// Get the pointer to a given buffer in the mapped window. The
|
||||
// offset is relative to the start of the window, and the size must
|
||||
// fit inside the window.
|
||||
void *getRelPtr(size_t offset, size_t size)
|
||||
{
|
||||
assert(mappedPtr != NULL);
|
||||
|
||||
// Don't overstep the mapped data
|
||||
assert(offset + size <= mappedSize);
|
||||
|
||||
return mappedPtr + offset;
|
||||
}
|
||||
|
||||
// Copy a given buffer from the file. The buffer might be a
|
||||
// compressed stream, so it's important that the buffers are written
|
||||
// the same 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 *dest, size_t offset, size_t inSize)
|
||||
{
|
||||
const void *source = getRelPtr(offset, inSize);
|
||||
|
||||
// Just copy it for now
|
||||
memcpy(dest, source, inSize);
|
||||
}
|
||||
|
||||
friend struct MeshInfo;
|
||||
friend struct AlphaInfo;
|
||||
} g_archive;
|
||||
|
||||
// Info about an alpha map belonging to a mesh
|
||||
struct AlphaInfo
|
||||
{
|
||||
size_t bufSize, bufOffset;
|
||||
|
||||
// The texture name for this layer. The actual string is stored in
|
||||
// the archive's string buffer.
|
||||
int texName;
|
||||
int alphaName;
|
||||
|
||||
// Fill the alpha texture buffer
|
||||
void fillAlphaBuffer(uint8_t *abuf) const
|
||||
{
|
||||
g_archive.copy(abuf, bufOffset, bufSize);
|
||||
}
|
||||
|
||||
// Get the texture for this alpha layer
|
||||
const char *getTexName() const
|
||||
{
|
||||
return g_archive.getString(texName);
|
||||
}
|
||||
|
||||
// Get the material name to give the alpha texture
|
||||
const char *getAlphaName() const
|
||||
{
|
||||
return g_archive.getString(alphaName);
|
||||
}
|
||||
};
|
||||
|
||||
// Info about each submesh
|
||||
struct MeshInfo
|
||||
{
|
||||
// Bounding box info
|
||||
float minHeight, maxHeight;
|
||||
float worldWidth;
|
||||
|
||||
// Vertex and index numbers
|
||||
int vertRows, vertCols;
|
||||
int indexCount;
|
||||
|
||||
// Scene node position (relative to the parent node)
|
||||
float x, y;
|
||||
|
||||
// Height offset to apply to all vertices
|
||||
float heightOffset;
|
||||
|
||||
// Size and offset of the vertex buffer
|
||||
size_t vertBufSize, vertBufOffset;
|
||||
|
||||
// Number and offset of AlphaInfo blocks
|
||||
int alphaNum;
|
||||
size_t alphaOffset;
|
||||
|
||||
// Texture name. Index to the string table.
|
||||
int texName;
|
||||
|
||||
// Fill the given vertex buffer
|
||||
void fillVertexBuffer(float *vbuf) const
|
||||
{
|
||||
//g_archive.copy(vbuf, vertBufOffset, vertBufSize);
|
||||
|
||||
// The height map and normals from the archive
|
||||
char *hmap = (char*)g_archive.getRelPtr(vertBufOffset, vertBufSize);
|
||||
|
||||
int level = getLevel();
|
||||
|
||||
// The generic part, containing the x,y coordinates and the uv
|
||||
// maps.
|
||||
float *gmap = g_archive.getVertexBuffer(level);
|
||||
|
||||
// Calculate the factor to multiply each height value with. The
|
||||
// heights are very limited in range as they are stored in a
|
||||
// single byte. Normal MW data uses a factor of 8, but we have to
|
||||
// double this for each successive level since we're splicing
|
||||
// several vertices together and need to allow larger differences
|
||||
// for each vertex. The formula is 8*2^(level-1).
|
||||
float scale = 4.0 * (1<<level);
|
||||
|
||||
// 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 += *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++; // Skip the byte we just read
|
||||
|
||||
// X and Y from the pregenerated buffer
|
||||
*vbuf++ = *gmap++;
|
||||
*vbuf++ = *gmap++;
|
||||
|
||||
// The height is calculated from the current offset
|
||||
*vbuf++ = rowofs * scale;
|
||||
|
||||
// Normal vector.
|
||||
// TODO: Normalize?
|
||||
*vbuf++ = *hmap++;
|
||||
*vbuf++ = *hmap++;
|
||||
*vbuf++ = *hmap++;
|
||||
|
||||
// UV
|
||||
*vbuf++ = *gmap++;
|
||||
*vbuf++ = *gmap++;
|
||||
|
||||
// Adjust the offset for the next vertex. On the last
|
||||
// iteration this will read past the current row, but
|
||||
// that's OK since rowofs is discarded afterwards.
|
||||
rowofs += *hmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the index buffer
|
||||
void fillIndexBuffer(unsigned short *ibuf) const
|
||||
{
|
||||
// The index buffer is pregenerated. It is identical for all
|
||||
// meshes on the same level, so just copy it over.
|
||||
int size;
|
||||
unsigned short *generic = g_archive.getIndexBuffer(getLevel(), size);
|
||||
memcpy(ibuf, generic, size);
|
||||
}
|
||||
|
||||
int getLevel() const
|
||||
{
|
||||
assert(g_archive.curQuad);
|
||||
return g_archive.curQuad->level;
|
||||
}
|
||||
|
||||
// Get an alpha map belonging to this mesh
|
||||
AlphaInfo *getAlphaInfo(int num) const
|
||||
{
|
||||
assert(num < alphaNum && num >= 0);
|
||||
assert(getLevel() == 1);
|
||||
AlphaInfo *res = (AlphaInfo*)g_archive.getRelPtr
|
||||
(alphaOffset, alphaNum*sizeof(AlphaInfo));
|
||||
res += num;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get the size of the alpha textures (in pixels).
|
||||
int getAlphaSize() const
|
||||
{ return g_archive.alphaSize; }
|
||||
|
||||
// Get the texture and material name to use for this mesh.
|
||||
const char *getTexName() const
|
||||
{ return g_archive.getString(texName); }
|
||||
|
||||
float getTexScale() const
|
||||
{ return g_archive.curQuad->texScale; }
|
||||
|
||||
const char *getBackgroundTex() const
|
||||
{ return "_land_default.dds"; }
|
||||
};
|
||||
|
||||
MeshInfo *TerrainArchive::getMeshInfo(int segNum)
|
||||
{
|
||||
assert(curQuad);
|
||||
assert(segNum < curQuad->meshNum);
|
||||
|
||||
// The mesh headers are at the beginning of the mapped segment.
|
||||
curMesh = (MeshInfo*) getRelPtr(0, sizeof(MeshInfo)*curQuad->meshNum);
|
||||
curMesh += segNum;
|
||||
|
||||
return curMesh;
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Jacob Essex 2009
|
||||
|
||||
This file is part of MWLand.
|
||||
|
||||
MWLand is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
MWLand 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
|
||||
along with MWLand. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
///generic subrecord
|
||||
struct SubRecord
|
||||
{
|
||||
SubRecord(){}
|
||||
SubRecord(const std::string& n, const std::string& d) : subName(n), subData(d){}
|
||||
std::string subName;
|
||||
std::string subData;
|
||||
};
|
||||
|
||||
///generic record
|
||||
class Record {
|
||||
public:
|
||||
Record(const std::string& type)
|
||||
: mType(type) {}
|
||||
|
||||
inline void addSubRecord(const std::string& id,
|
||||
const SubRecord& subRecord)
|
||||
{mSubRecords[id] = subRecord; }
|
||||
|
||||
std::string getSubRecordData(const std::string& recordName)
|
||||
{
|
||||
SubRecordMapItr itr = mSubRecords.find(recordName );
|
||||
if ( itr != mSubRecords.end() )
|
||||
return itr->second.subData;
|
||||
return std::string("");
|
||||
}
|
||||
|
||||
bool hasSubRecord(const std::string& recordName)
|
||||
{
|
||||
if ( mSubRecords.find(recordName ) != mSubRecords.end() )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const std::string& getID() { return mId; }
|
||||
inline void setID( const std::string& id) { mId = id;}
|
||||
const std::string& getType(){return mType;}
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, SubRecord> SubRecordMap;
|
||||
typedef SubRecordMap::iterator SubRecordMapItr;
|
||||
SubRecordMap mSubRecords;
|
||||
|
||||
std::string mType;
|
||||
std::string mId;
|
||||
|
||||
};
|
||||
|
||||
typedef std::list<Record*> RecordList;
|
||||
typedef RecordList::iterator RecordListItr;
|
||||
|
||||
typedef std::map<std::string, Record*> RecordMap;
|
||||
typedef RecordMap::iterator RecordMapItr;
|
||||
|
||||
///top level class for loading and saving esp files.
|
||||
class ESM
|
||||
{
|
||||
private:
|
||||
/// types of records to load
|
||||
std::map<std::string, std::string> mLoadTypes;
|
||||
|
||||
/// map<id, record> of the record
|
||||
RecordMap mRecords;
|
||||
|
||||
///checks if the given type should be loaded
|
||||
inline bool loadType(const std::string& t)
|
||||
{
|
||||
return ( mLoadTypes.find(t) != mLoadTypes.end() );
|
||||
}
|
||||
|
||||
public:
|
||||
inline void addRecordType(const std::string& t,
|
||||
const std::string& i = "NAME")
|
||||
{ mLoadTypes[t] = i; }
|
||||
|
||||
bool loadFile(const std::string& file)
|
||||
{
|
||||
std::ifstream esp (file.c_str(), std::ios::in | std::ios::binary);
|
||||
|
||||
if ( !esp.is_open() ) return false; //check open
|
||||
|
||||
esp.seekg(4);
|
||||
|
||||
long hdrSize; //get offset for start of data
|
||||
esp.read ((char *)&hdrSize, sizeof(long));
|
||||
|
||||
//get num records
|
||||
esp.seekg(16 + 8 + 296);
|
||||
long numRecords;
|
||||
esp.read ((char *)&numRecords, sizeof(long));
|
||||
|
||||
esp.seekg(hdrSize + 16); //go to end of header
|
||||
|
||||
for ( long i = 0; i < numRecords; i++ ){
|
||||
|
||||
char type[5];
|
||||
esp.get(type, 5);
|
||||
type[4] = '\0';
|
||||
|
||||
long recordSize;
|
||||
esp.read ((char *)&recordSize, 4);
|
||||
esp.seekg(8, std::ofstream::cur);
|
||||
long endPos = recordSize + esp.tellg();
|
||||
|
||||
if ( loadType(type) ) {
|
||||
Record* record = new Record(type);
|
||||
|
||||
//load all subrecords
|
||||
while ( esp.tellg() < endPos ) {
|
||||
char subType[5];
|
||||
esp.get(subType, 5);
|
||||
|
||||
long subRecLength;
|
||||
esp.read ((char *)&subRecLength, 4);
|
||||
|
||||
long subRecEnd = subRecLength + esp.tellg();
|
||||
char* subRecData = new char[subRecLength];
|
||||
esp.read(subRecData, subRecLength);
|
||||
|
||||
record->addSubRecord(subType, SubRecord(subType,std::string(subRecData, subRecLength)));
|
||||
delete [] subRecData;
|
||||
|
||||
assert(subRecEnd==esp.tellg());
|
||||
}
|
||||
record->setID(record->getSubRecordData(mLoadTypes[type]));
|
||||
mRecords[record->getSubRecordData(mLoadTypes[type])] = record;
|
||||
}else{
|
||||
esp.seekg(endPos);
|
||||
}
|
||||
assert(endPos==esp.tellg());
|
||||
|
||||
}
|
||||
esp.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Record* getRecord(const std::string& id){ return mRecords[id]; }
|
||||
|
||||
RecordList* getRecordsByType(const std::string& t)
|
||||
{
|
||||
RecordList* r = new RecordList;
|
||||
for ( RecordMapItr iter = mRecords.begin(); iter != mRecords.end(); ++iter)
|
||||
if ( t == iter->second->getType() )
|
||||
r->push_back(iter->second);
|
||||
return r;
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,223 +0,0 @@
|
||||
struct VHGT
|
||||
{ ///height data
|
||||
float heightOffset;
|
||||
char heightData[LAND_NUM_VERTS];
|
||||
short unknown1;
|
||||
char unknown2;
|
||||
};
|
||||
|
||||
class MWLand
|
||||
{
|
||||
public:
|
||||
MWLand()
|
||||
{
|
||||
mMaxX = mMaxY = mMinX = mMinY = 0;
|
||||
}
|
||||
|
||||
void addLandTextureData(Record* record, const std::string& source)
|
||||
{
|
||||
LandTexture l;
|
||||
l.name = record->getSubRecordData("NAME");
|
||||
l.data = record->getSubRecordData("DATA");
|
||||
l.intv = *((short*) record->getSubRecordData("INTV").c_str());
|
||||
mLTEXRecords[source][l.intv] = l;
|
||||
}
|
||||
|
||||
void addLandData(Record* record, const std::string& source)
|
||||
{
|
||||
if ( !record->hasSubRecord("VHGT") || !record->hasSubRecord("VTEX") ) //ensure all records exist
|
||||
return;
|
||||
|
||||
//copy these, else we end up with invliad data
|
||||
LAND::INTV intv = *(LAND::INTV*)record->getSubRecordData("INTV").c_str();
|
||||
VHGT *vhgt = (VHGT*) record->getSubRecordData("VHGT").c_str();
|
||||
LAND::VNML vnml = *(LAND::VNML*)record->getSubRecordData("VNML").c_str();
|
||||
LAND::VTEX vtex = *(LAND::VTEX*)record->getSubRecordData("VTEX").c_str();
|
||||
|
||||
// FIXME: What happens to the memory allocation of vhgt here?
|
||||
// Doesn't matter much, we're killing this entire file soon
|
||||
// anyway.
|
||||
mLandRecords[intv.x][intv.y].heights = vhgt;
|
||||
mLandRecords[intv.x][intv.y].normals = parseNormals(&vnml);
|
||||
mLandRecords[intv.x][intv.y].textures = parseTextures(&vtex);
|
||||
mLandRecords[intv.x][intv.y].source = source;
|
||||
|
||||
mMaxX = std::max<int>(mMaxX, intv.x);
|
||||
mMaxY = std::max<int>(mMaxY, intv.y);
|
||||
mMinX = std::min<int>(mMinX, intv.x);
|
||||
mMinY = std::min<int>(mMinY, intv.y);
|
||||
}
|
||||
|
||||
///Maximum distance of a cell on the X plane from grid pos 0 in the positive direction
|
||||
inline int getMaxX() const { return mMaxX; }
|
||||
///Maximum distance of a cell on the Y plane from grid pos 0 in the positvie direction
|
||||
inline int getMaxY() const { return mMaxY; }
|
||||
///Maximum distance of a cell on the X plane from grid pos 0 in the negative direction
|
||||
inline int getMinX() const { return mMinX; }
|
||||
///see others
|
||||
inline int getMinY() const { return mMinY; }
|
||||
|
||||
inline VHGT *getHeights(int x, int y)
|
||||
{
|
||||
if ( hasData(x,y) )
|
||||
return mLandRecords[x][y].heights;
|
||||
assert(0);
|
||||
}
|
||||
|
||||
inline std::vector<char>& getNormals(int x, int y)
|
||||
{
|
||||
if ( hasData(x,y) )
|
||||
return mLandRecords[x][y].normals;
|
||||
assert(0);
|
||||
}
|
||||
|
||||
inline const std::string& getSource(int x, int y)
|
||||
{
|
||||
assert(hasData(x,y));
|
||||
return mLandRecords[x][y].source;
|
||||
}
|
||||
|
||||
inline bool hasData(int x, int y) const
|
||||
{
|
||||
std::map<int , std::map<int, LandData> >::const_iterator itr = mLandRecords.find(x);
|
||||
if ( itr == mLandRecords.end() )
|
||||
return false;
|
||||
if ( itr->second.find(y) == itr->second.end() )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline std::string& getLTEXRecord(const std::string& source, short i)
|
||||
{
|
||||
return mLTEXRecords[source][i].data;
|
||||
}
|
||||
|
||||
inline bool hasLTEXRecord(const std::string& source, short index) const
|
||||
{
|
||||
std::map<std::string, std::map<short, LandTexture> >::const_iterator itr = mLTEXRecords.find(source);
|
||||
if ( itr == mLTEXRecords.end() )
|
||||
return false;
|
||||
if ( itr->second.find(index) == itr->second.end() )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline short getLTEXIndex(int x, int y, int pos)
|
||||
{
|
||||
assert(hasData(x,y));
|
||||
return mLandRecords[x][y].textures[pos];
|
||||
}
|
||||
|
||||
inline short getLTEXIndex(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
return getLTEXIndex(x1, y1, y2*LAND_LTEX_WIDTH+x2);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
///the min/max size of cells
|
||||
int mMaxY, mMinY;
|
||||
int mMaxX, mMinX;
|
||||
|
||||
// Land structure as held in the ESM
|
||||
struct LAND {
|
||||
struct INTV { /// x, y grid pos of the cell
|
||||
long x;
|
||||
long y;
|
||||
};
|
||||
struct VNML { ///vertex normal data
|
||||
struct NORMAL {
|
||||
char x;
|
||||
char y;
|
||||
char z;
|
||||
};
|
||||
NORMAL normals[LAND_NUM_VERTS];
|
||||
};
|
||||
struct VTEX { ///splat texture data
|
||||
short index[LAND_NUM_LTEX];
|
||||
};
|
||||
INTV* intv;
|
||||
VNML* vnml;
|
||||
VHGT* vhgt;
|
||||
VTEX* vtex;
|
||||
};
|
||||
|
||||
/*
|
||||
std::vector<float> parseHeights(LAND::VHGT* vhgt)
|
||||
{
|
||||
std::vector<float> ph;
|
||||
ph.resize(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT);
|
||||
|
||||
// heightOffset offsets the entire cell
|
||||
float offset = vhgt->heightOffset;
|
||||
|
||||
for (int y = 0; y < LAND_VERT_WIDTH; y++)
|
||||
{
|
||||
// The first vertex in each row gives the difference relative
|
||||
// to the last row start
|
||||
offset += vhgt->heightData[y*LAND_VERT_WIDTH];
|
||||
ph[y*LAND_VERT_WIDTH] =+ (float)offset*8;
|
||||
|
||||
float pos = offset;
|
||||
for (int x = 1; x < LAND_VERT_WIDTH; x++)
|
||||
{
|
||||
// And each vertex within a row gives the difference
|
||||
// relative to the previous one
|
||||
pos += vhgt->heightData[y*LAND_VERT_WIDTH+x];
|
||||
ph[y*LAND_VERT_WIDTH+x] = pos*8; //flipped x
|
||||
}
|
||||
}
|
||||
return ph;
|
||||
}
|
||||
*/
|
||||
|
||||
std::vector<char> parseNormals( LAND::VNML* vnml )
|
||||
{
|
||||
std::vector<char> n;
|
||||
n.resize(LAND_NUM_VERTS*3,0);
|
||||
for ( int y = 0; y < LAND_VERT_WIDTH; y++ ) { //this could just be cast.
|
||||
for ( int x = 0; x < LAND_VERT_WIDTH; x++ ) { //as a vector is a continus segment of mem...
|
||||
n[(y*LAND_VERT_WIDTH+x)*3] = vnml->normals[y*LAND_VERT_WIDTH+x].x;
|
||||
n[(y*LAND_VERT_WIDTH+x)*3+1] = vnml->normals[y*LAND_VERT_WIDTH+x].y;
|
||||
n[(y*LAND_VERT_WIDTH+x)*3+2] = vnml->normals[y*LAND_VERT_WIDTH+x].z;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
std::vector<short> parseTextures( LAND::VTEX* vtex )
|
||||
{
|
||||
std::vector<short> t;
|
||||
t.resize(LAND_NUM_LTEX,0);
|
||||
|
||||
//thanks to timeslip (MGE) for the code
|
||||
int rpos = 0; //bit ugly, but it works
|
||||
for ( int y1 = 0; y1 < 4; y1++ )
|
||||
for ( int x1 = 0; x1 < 4; x1++ )
|
||||
for ( int y2 = 0; y2 < 4; y2++)
|
||||
for ( int x2 = 0; x2 < 4; x2++ )
|
||||
t[(y1*4+y2)*16+(x1*4+x2)]=vtex->index[rpos++];
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the representation of a cell in the way that is most usefull to me
|
||||
*/
|
||||
struct LandData
|
||||
{
|
||||
std::string source; //data file the data is from
|
||||
VHGT *heights;
|
||||
std::vector<char> normals;
|
||||
std::vector<short> textures;
|
||||
};
|
||||
|
||||
struct LandTexture
|
||||
{
|
||||
std::string name, data;
|
||||
short intv;
|
||||
};
|
||||
|
||||
std::map<std::string, std::map<short, LandTexture> > mLTEXRecords;
|
||||
std::map<int, std::map<int,LandData> > mLandRecords;
|
||||
};
|
@ -1,302 +0,0 @@
|
||||
/**
|
||||
* defines an area of Landscape
|
||||
*
|
||||
* A quad can either hold a mesh, or 4 other sub quads The functions
|
||||
* split and unsplit either break the current quad into smaller quads,
|
||||
* or alternatively remove the lower quads and create the terrain mesh
|
||||
* on the the current (now the lowest) level
|
||||
*/
|
||||
/* Previously for MeshInterface:
|
||||
* Interface between the quad and the terrain renderble classes, to the
|
||||
* quad it looks like this rendereds a single mesh for the quad. This
|
||||
* may not be the case.
|
||||
*
|
||||
* It also could allow several optimizations (e.g. multiple splits)
|
||||
*/
|
||||
|
||||
class Quad
|
||||
{
|
||||
typedef std::list<TerrainMesh*> MeshList;
|
||||
|
||||
public:
|
||||
|
||||
Quad(int cellX=0, int cellY=0, Quad* parent = NULL)
|
||||
: mCellX(cellX),
|
||||
mCellY(cellY)
|
||||
{
|
||||
RTRACE("Quad");
|
||||
|
||||
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
|
||||
|
||||
hasMesh = false;
|
||||
hasChildren = false;
|
||||
isStatic = false;
|
||||
|
||||
// Do we have a parent?
|
||||
if(parent != NULL)
|
||||
{
|
||||
mLevel = parent->mLevel-1;
|
||||
|
||||
if(mLevel == 1)
|
||||
{
|
||||
// Create the terrain and leave it there.
|
||||
buildTerrain();
|
||||
isStatic = true;
|
||||
}
|
||||
|
||||
// Coordinates relative to our parent
|
||||
const int relX = cellX - parent->mCellX;
|
||||
const int relY = cellY - parent->mCellY;
|
||||
|
||||
// The coordinates give the top left corner of the quad, or our
|
||||
// relative coordinates within that should always be positive.
|
||||
assert(relX >= 0);
|
||||
assert(relY >= 0);
|
||||
|
||||
// Create a child scene node. The scene node position is given in
|
||||
// world units, ie. CELL_WIDTH units per cell.
|
||||
const Ogre::Vector3 pos(relX * CELL_WIDTH,
|
||||
relY * CELL_WIDTH,
|
||||
0);
|
||||
mSceneNode = parent->mSceneNode->createChildSceneNode(pos);
|
||||
|
||||
// Get the archive data for this quad.
|
||||
mInfo = g_archive.getQuad(mCellX,mCellY,mLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No parent, this is the top-most quad. Get all the info from
|
||||
// the archive.
|
||||
mInfo = g_archive.rootQuad;
|
||||
|
||||
mLevel = mInfo->level;
|
||||
cellX = mCellX = mInfo->cellX;
|
||||
cellY = mCellY = mInfo->cellY;
|
||||
|
||||
const Ogre::Vector3 pos(cellX * CELL_WIDTH,
|
||||
cellY * CELL_WIDTH,
|
||||
0);
|
||||
mSceneNode = g_rootTerrainNode->
|
||||
createChildSceneNode(pos);
|
||||
|
||||
// Split up
|
||||
split();
|
||||
|
||||
// The root can never be unsplit
|
||||
isStatic = true;
|
||||
}
|
||||
|
||||
assert(mLevel >= 1);
|
||||
assert(mSceneNode != NULL);
|
||||
|
||||
// Set up the bounding box. Use MW coordinates all the way
|
||||
mBounds.setExtents(0,0,mInfo->minHeight,
|
||||
mInfo->worldWidth,mInfo->worldWidth,
|
||||
mInfo->maxHeight);
|
||||
|
||||
// Transform the box to world coordinates, so it can be compared
|
||||
// with the camera later.
|
||||
mBounds.transformAffine(mSceneNode->_getFullTransform());
|
||||
|
||||
const float radius = mInfo->boundingRadius;
|
||||
|
||||
mSplitDistance = radius * SPLIT_FACTOR;
|
||||
mUnsplitDistance = radius * UNSPLIT_FACTOR;
|
||||
|
||||
// Square the distances
|
||||
mSplitDistance *= mSplitDistance;
|
||||
mUnsplitDistance *= mUnsplitDistance;
|
||||
|
||||
// Update the terrain. This will create the mesh or children if
|
||||
// necessary.
|
||||
update();
|
||||
}
|
||||
|
||||
~Quad()
|
||||
{
|
||||
RTRACE("~Quad");
|
||||
if(hasMesh)
|
||||
destroyTerrain();
|
||||
else if(hasChildren)
|
||||
for (size_t i = 0; i < NUM_CHILDREN; i++)
|
||||
delete mChildren[i];
|
||||
|
||||
mSceneNode->removeAndDestroyAllChildren();
|
||||
mSceneMgr->destroySceneNode(mSceneNode);
|
||||
}
|
||||
|
||||
// Remove the landscape for this quad, and create children.
|
||||
void split()
|
||||
{
|
||||
RTRACE("split");
|
||||
|
||||
// Never split a static quad or a quad that already has children.
|
||||
assert(!isStatic);
|
||||
assert(!hasChildren);
|
||||
assert(mLevel > 1);
|
||||
|
||||
if(hasMesh)
|
||||
destroyTerrain();
|
||||
|
||||
// Find the cell width of our children
|
||||
int cWidth = 1 << (mLevel-2);
|
||||
|
||||
// Create children
|
||||
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
|
||||
{
|
||||
if(!mInfo->hasChild[i])
|
||||
continue;
|
||||
|
||||
// The cell coordinates for this child quad
|
||||
int x = (i%2)*cWidth + mCellX;
|
||||
int y = (i/2)*cWidth + mCellY;
|
||||
|
||||
mChildren[i] = new Quad(x,y,this);
|
||||
}
|
||||
hasChildren = true;
|
||||
}
|
||||
|
||||
// Removes children and rebuilds terrain
|
||||
void unsplit()
|
||||
{
|
||||
RTRACE("unsplit");
|
||||
|
||||
// Never unsplit the root quad
|
||||
assert(mLevel < g_archive.rootQuad->level);
|
||||
// Never unsplit a static or quad that isn't split.
|
||||
assert(!isStatic);
|
||||
assert(hasChildren);
|
||||
assert(!hasMesh);
|
||||
|
||||
for( size_t i = 0; i < NUM_CHILDREN; i++ )
|
||||
{
|
||||
delete mChildren[i];
|
||||
mChildren[i] = NULL;
|
||||
}
|
||||
|
||||
buildTerrain();
|
||||
|
||||
hasChildren = false;
|
||||
}
|
||||
|
||||
// Determines whether to split or unsplit the quad, and immediately
|
||||
// does it.
|
||||
void update()
|
||||
{
|
||||
RTRACE("Quad::update");
|
||||
|
||||
// Static quads don't change
|
||||
if(isStatic)
|
||||
return;
|
||||
|
||||
assert(mUnsplitDistance > mSplitDistance);
|
||||
|
||||
// Get (squared) camera distance. TODO: shouldn't this just be a
|
||||
// simple vector difference from the mesh center?
|
||||
float camDist;
|
||||
{
|
||||
const Ogre::Vector3 cpos = mCamera->getDerivedPosition();
|
||||
Ogre::Vector3 diff(0, 0, 0);
|
||||
diff.makeFloor(cpos - mBounds.getMinimum() );
|
||||
diff.makeCeil(cpos - mBounds.getMaximum() );
|
||||
camDist = diff.squaredLength();
|
||||
}
|
||||
|
||||
// No children?
|
||||
if(!hasChildren)
|
||||
{
|
||||
// If we're close, split now.
|
||||
if(camDist < mSplitDistance)
|
||||
split();
|
||||
else
|
||||
{
|
||||
// We're not close, and don't have any children. Should we
|
||||
// built terrain?
|
||||
if(!hasMesh)
|
||||
buildTerrain();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we either had children when we entered, or we
|
||||
// just performed a split.
|
||||
assert(!hasMesh);
|
||||
assert(hasChildren);
|
||||
|
||||
// If the camera is too far away, kill the children.
|
||||
if( camDist > mUnsplitDistance )
|
||||
{
|
||||
unsplit();
|
||||
return;
|
||||
}
|
||||
|
||||
// We have children and we're happy about it. Update them too.
|
||||
for (size_t i = 0; i < NUM_CHILDREN; ++i)
|
||||
{
|
||||
Quad *q = mChildren[i];
|
||||
if(q != NULL) q->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Build the terrain for this quad
|
||||
void buildTerrain()
|
||||
{
|
||||
RTRACE("buildTerrain");
|
||||
assert(!hasMesh);
|
||||
assert(!isStatic);
|
||||
|
||||
// Map the terrain data into memory.
|
||||
g_archive.mapQuad(mInfo);
|
||||
|
||||
// Create one mesh for each segment in the quad. TerrainMesh takes
|
||||
// care of the loading.
|
||||
for(int i=0; i < mInfo->meshNum; i++)
|
||||
mMeshList.push_back(new TerrainMesh(i, mSceneNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief destroys the terrain.
|
||||
*/
|
||||
void destroyTerrain()
|
||||
{
|
||||
RTRACE("destroyTerrain");
|
||||
assert(hasMesh);
|
||||
|
||||
for ( MeshList::iterator itr = mMeshList.begin();
|
||||
itr != mMeshList.end(); ++itr )
|
||||
delete *itr;
|
||||
|
||||
mMeshList.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// List of meshes, if any
|
||||
MeshList mMeshList;
|
||||
|
||||
// Scene node. All child quads are added to this.
|
||||
SceneNode* mSceneNode;
|
||||
|
||||
// Bounding box, transformed to world coordinates. Used to calculate
|
||||
// camera distance.
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
|
||||
Ogre::Real mSplitDistance,mUnsplitDistance;
|
||||
|
||||
static const size_t NUM_CHILDREN = 4;
|
||||
|
||||
Quad* mChildren[NUM_CHILDREN]; ///optionaly the children. Should be
|
||||
///0 if not exist
|
||||
|
||||
// Contains the 'level' of this node. Level 1 is the closest and
|
||||
// most detailed level
|
||||
int mLevel;
|
||||
int mCellX, mCellY;
|
||||
|
||||
QuadInfo *mInfo;
|
||||
|
||||
bool hasMesh;
|
||||
bool hasChildren;
|
||||
bool isStatic; // Static quads are never split or unsplit
|
||||
};
|
@ -0,0 +1,149 @@
|
||||
module terrain.esmland;
|
||||
|
||||
import esm.loadltex;
|
||||
import esm.loadcell;
|
||||
import util.regions;
|
||||
import esm.filereader;
|
||||
|
||||
const int LAND_NUM_VERTS = 65*65;
|
||||
|
||||
MWLand mwland;
|
||||
|
||||
// Interface to the ESM landscape data
|
||||
struct MWLand
|
||||
{
|
||||
RegionManager reg;
|
||||
|
||||
// These structs/types represent the way data is actually stored in
|
||||
// the ESM files.
|
||||
|
||||
// Heightmap
|
||||
align(1)
|
||||
struct VHGT
|
||||
{
|
||||
float heightOffset;
|
||||
byte heightData[LAND_NUM_VERTS];
|
||||
short unknown1;
|
||||
char unknown2;
|
||||
}
|
||||
|
||||
// Normals
|
||||
typedef byte[LAND_NUM_VERTS*3] VNML;
|
||||
|
||||
// Land textures. This is organized in 4x4 buffers of 4x4 squares
|
||||
// each. This is how the original engine splits up the cell meshes,
|
||||
// and it's probably a good idea for us to do the same.
|
||||
typedef short[4][4][4][4] VTEX;
|
||||
|
||||
static assert(VHGT.sizeof == 4232);
|
||||
static assert(VNML.sizeof == 12675);
|
||||
static assert(VTEX.sizeof == 512);
|
||||
|
||||
// Landscape data for one cell
|
||||
struct LandData
|
||||
{
|
||||
VHGT vhgt;
|
||||
VNML normals;
|
||||
}
|
||||
|
||||
// Texture data for one cell
|
||||
struct LTEXData
|
||||
{
|
||||
// TODO: Store the source file here too, so we can get the list
|
||||
// from the right file. The source file is the same as the one we
|
||||
// load the landscape from in loadCell().
|
||||
VTEX vtex;
|
||||
|
||||
// Get the texture x2,y2 from the sub map x1,x2
|
||||
char[] getTexture(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
// Get the texture index relative to the current esm/esp file
|
||||
short texID = vtex[y1][x1][y2][x2];
|
||||
|
||||
// Hack, will only work for Morrowind.esm. Fix this later.
|
||||
auto tl = landTextures.files["Morrowind.esm"];
|
||||
|
||||
// Return the 'new' texture name. This name has automatically
|
||||
// been converted to .dds if the .tga file was not found.
|
||||
return tl[texID].getNewName();
|
||||
}
|
||||
|
||||
// Get a texture from the 16x16 grid in one cell
|
||||
char[] getTexture(int x, int y)
|
||||
{
|
||||
return getTexture(x/4,y/4,x%4,y%4);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the maximum absolute coordinate value in any direction
|
||||
int getMaxCoord()
|
||||
{ return cells.maxXY; }
|
||||
|
||||
// Does the given cell exist and does it have land data?
|
||||
bool hasData(int x, int y)
|
||||
{
|
||||
// Does the cell exist?
|
||||
if(!cells.hasExt(x,y))
|
||||
return false;
|
||||
|
||||
// And does it have terrain data?
|
||||
auto ex = cells.getExt(x,y);
|
||||
return ex.hasLand();
|
||||
}
|
||||
|
||||
LandData *getLandData(int x, int y)
|
||||
{
|
||||
loadCell(x, y);
|
||||
return ¤tLand;
|
||||
}
|
||||
|
||||
LTEXData *getLTEXData(int x, int y)
|
||||
{
|
||||
loadCell(x, y);
|
||||
return ¤tLtex;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int currentX = -1234;
|
||||
int currentY = 4321;
|
||||
|
||||
LandData currentLand;
|
||||
LTEXData currentLtex;
|
||||
|
||||
// Make sure the given cell is loaded
|
||||
void loadCell(int x, int y)
|
||||
{
|
||||
// If the right cell is already loaded, don't do anything
|
||||
if(x == currentX && y == currentY)
|
||||
return;
|
||||
|
||||
assert(hasData(x,y));
|
||||
|
||||
currentX = x;
|
||||
currentY = y;
|
||||
|
||||
// Get the file context for the terrain data. This can be used to
|
||||
// skip to the right part of the ESM file.
|
||||
auto cont = cells.getExt(x,y).land.context;
|
||||
|
||||
// We should use an existing region later, or at least delete this
|
||||
// once we're done with the gen process.
|
||||
if(reg is null)
|
||||
reg = new RegionManager();
|
||||
|
||||
// Open the ESM at this cell
|
||||
esFile.restoreContext(cont, reg);
|
||||
|
||||
// Store the data
|
||||
esFile.readHNExact(currentLand.normals.ptr,
|
||||
currentLand.normals.length, "VNML");
|
||||
esFile.readHNExact(¤tLand.vhgt, VHGT.sizeof, "VHGT");
|
||||
|
||||
// These aren't used yet
|
||||
if(esFile.isNextSub("WNAM")) esFile.skipHSubSize(81);
|
||||
if(esFile.isNextSub("VCLR")) esFile.skipHSubSize(12675);
|
||||
|
||||
esFile.readHNExact(¤tLtex.vtex, VTEX.sizeof, "VTEX");
|
||||
}
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
module terrain.quad;
|
||||
|
||||
import terrain.archive;
|
||||
import terrain.bindings;
|
||||
|
||||
const int CELL_WIDTH = 8192;
|
||||
const float SPLIT_FACTOR = 0.5;
|
||||
const float UNSPLIT_FACTOR = 2.0;
|
||||
|
||||
class Quad
|
||||
{
|
||||
this(int cellX=0, int cellY=0, Quad parent = null)
|
||||
{
|
||||
mCellX = cellX;
|
||||
mCellY = cellY;
|
||||
|
||||
// Do we have a parent?
|
||||
if(parent !is null)
|
||||
{
|
||||
mLevel = parent.mLevel-1;
|
||||
|
||||
if(mLevel == 1)
|
||||
{
|
||||
// Create the terrain and leave it there.
|
||||
buildTerrain();
|
||||
isStatic = true;
|
||||
}
|
||||
|
||||
// Coordinates relative to our parent
|
||||
int relX = cellX - parent.mCellX;
|
||||
int relY = cellY - parent.mCellY;
|
||||
|
||||
// The coordinates give the top left corner of the quad, or our
|
||||
// relative coordinates within that should always be positive.
|
||||
assert(relX >= 0);
|
||||
assert(relY >= 0);
|
||||
|
||||
// Create a child scene node. The scene node position is given in
|
||||
// world units, ie. CELL_WIDTH units per cell.
|
||||
mNode = terr_createChildNode(relX*CELL_WIDTH,
|
||||
relY*CELL_WIDTH,
|
||||
parent.mNode);
|
||||
/*
|
||||
Ogre::Vector3 pos(relX * CELL_WIDTH,
|
||||
relY * CELL_WIDTH,
|
||||
0);
|
||||
mNode = parent.mNode.createChildSceneNode(pos);
|
||||
*/
|
||||
|
||||
// Get the archive data for this quad.
|
||||
mInfo = g_archive.getQuad(mCellX,mCellY,mLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No parent, this is the top-most quad. Get all the info from
|
||||
// the archive.
|
||||
mInfo = g_archive.rootQuad;
|
||||
|
||||
mLevel = mInfo.level;
|
||||
cellX = mCellX = mInfo.cellX;
|
||||
cellY = mCellY = mInfo.cellY;
|
||||
|
||||
mNode = terr_createChildNode(cellX*CELL_WIDTH,
|
||||
cellY*CELL_WIDTH,
|
||||
null);
|
||||
/*
|
||||
mNode = g_rootTerrainNode.
|
||||
createChildSceneNode(pos);
|
||||
*/
|
||||
|
||||
// Split up
|
||||
split();
|
||||
|
||||
// The root can never be unsplit
|
||||
isStatic = true;
|
||||
}
|
||||
|
||||
assert(mLevel >= 1);
|
||||
assert(mNode !is null);
|
||||
|
||||
// TODO: How do we store the C++ bounding box?
|
||||
mBounds = terr_makeBounds(mInfo.minHeight,
|
||||
mInfo.maxHeight,
|
||||
mInfo.worldWidth);
|
||||
/*
|
||||
// Set up the bounding box. Use MW coordinates all the way
|
||||
mBounds.setExtents(0,0,mInfo.minHeight,
|
||||
mInfo.worldWidth,mInfo.worldWidth,
|
||||
mInfo.maxHeight);
|
||||
|
||||
// Transform the box to world coordinates, so it can be compared
|
||||
// with the camera later.
|
||||
mBounds.transformAffine(mNode._getFullTransform());
|
||||
*/
|
||||
float radius = mInfo.boundingRadius;
|
||||
|
||||
mSplitDistance = radius * SPLIT_FACTOR;
|
||||
mUnsplitDistance = radius * UNSPLIT_FACTOR;
|
||||
|
||||
// Square the distances
|
||||
mSplitDistance *= mSplitDistance;
|
||||
mUnsplitDistance *= mUnsplitDistance;
|
||||
|
||||
// Update the terrain. This will create the mesh or children if
|
||||
// necessary.
|
||||
update();
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
// TODO: We might rewrite the code so that the quads are never
|
||||
// actually destroyed, just 'inactivated' by hiding their scene
|
||||
// node. We only call update on our children if we don't have a
|
||||
// mesh ourselves.
|
||||
if(hasMesh)
|
||||
destroyTerrain();
|
||||
else if(hasChildren)
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
delete mChildren[i];
|
||||
|
||||
terr_destroyNode(mNode);
|
||||
/*
|
||||
mNode.removeAndDestroyAllChildren();
|
||||
mSceneMgr.destroySceneNode(mNode);
|
||||
*/
|
||||
}
|
||||
|
||||
// Remove the landscape for this quad, and create children.
|
||||
void split()
|
||||
{
|
||||
// Never split a static quad or a quad that already has children.
|
||||
assert(!isStatic);
|
||||
assert(!hasChildren);
|
||||
assert(mLevel > 1);
|
||||
|
||||
if(hasMesh)
|
||||
destroyTerrain();
|
||||
|
||||
// Find the cell width of our children
|
||||
int cWidth = 1 << (mLevel-2);
|
||||
|
||||
// Create children
|
||||
for ( size_t i = 0; i < 4; ++i )
|
||||
{
|
||||
if(!mInfo.hasChild[i])
|
||||
continue;
|
||||
|
||||
// The cell coordinates for this child quad
|
||||
int x = (i%2)*cWidth + mCellX;
|
||||
int y = (i/2)*cWidth + mCellY;
|
||||
|
||||
mChildren[i] = new Quad(x,y,this);
|
||||
}
|
||||
hasChildren = true;
|
||||
}
|
||||
|
||||
// Removes children and rebuilds terrain
|
||||
void unsplit()
|
||||
{
|
||||
// Never unsplit the root quad
|
||||
assert(mLevel < g_archive.rootQuad.level);
|
||||
// Never unsplit a static or quad that isn't split.
|
||||
assert(!isStatic);
|
||||
assert(hasChildren);
|
||||
assert(!hasMesh);
|
||||
|
||||
for( size_t i = 0; i < 4; i++ )
|
||||
{
|
||||
delete mChildren[i];
|
||||
mChildren[i] = null;
|
||||
}
|
||||
|
||||
buildTerrain();
|
||||
|
||||
hasChildren = false;
|
||||
}
|
||||
|
||||
// Determines whether to split or unsplit the quad, and immediately
|
||||
// does it.
|
||||
void update()
|
||||
{
|
||||
// Static quads don't change
|
||||
if(!isStatic)
|
||||
{
|
||||
assert(mUnsplitDistance > mSplitDistance);
|
||||
|
||||
// Get (squared) camera distance. TODO: shouldn't this just
|
||||
// be a simple vector difference from the mesh center?
|
||||
float camDist = terr_getSqCamDist(mBounds);
|
||||
/*
|
||||
{
|
||||
Ogre::Vector3 cpos = mCamera.getDerivedPosition();
|
||||
Ogre::Vector3 diff(0, 0, 0);
|
||||
diff.makeFloor(cpos - mBounds.getMinimum() );
|
||||
diff.makeCeil(cpos - mBounds.getMaximum() );
|
||||
camDist = diff.squaredLength();
|
||||
}
|
||||
*/
|
||||
|
||||
// No children?
|
||||
if(!hasChildren)
|
||||
{
|
||||
// If we're close, split now.
|
||||
if(camDist < mSplitDistance)
|
||||
split();
|
||||
else
|
||||
{
|
||||
// We're not close, and don't have any children. Should we
|
||||
// built terrain?
|
||||
if(!hasMesh)
|
||||
buildTerrain();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we either had children when we entered,
|
||||
// or we just performed a split.
|
||||
assert(!hasMesh);
|
||||
assert(hasChildren);
|
||||
|
||||
// If the camera is too far away, kill the children.
|
||||
if(camDist > mUnsplitDistance)
|
||||
{
|
||||
unsplit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(!hasChildren)
|
||||
return;
|
||||
|
||||
// We have children and we're happy about it. Update them too.
|
||||
for(int i; i < 4; ++i)
|
||||
{
|
||||
Quad q = mChildren[i];
|
||||
if(q !is null) q.update();
|
||||
}
|
||||
}
|
||||
|
||||
// Build the terrain for this quad
|
||||
void buildTerrain()
|
||||
{
|
||||
assert(!hasMesh);
|
||||
assert(!isStatic);
|
||||
|
||||
// Map the terrain data into memory.
|
||||
g_archive.mapQuad(mInfo);
|
||||
|
||||
// Create one mesh for each segment in the quad. TerrainMesh takes
|
||||
// care of the loading.
|
||||
meshList.length = mInfo.meshNum;
|
||||
foreach(i, ref m; meshList)
|
||||
m = terr_makeMesh(i, mNode);
|
||||
|
||||
hasMesh = true;
|
||||
}
|
||||
|
||||
void destroyTerrain()
|
||||
{
|
||||
assert(hasMesh);
|
||||
|
||||
foreach(m; meshList)
|
||||
terr_killMesh(m);
|
||||
|
||||
meshList[] = null;
|
||||
hasMesh = false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// List of meshes, if any. The meshes are C++ objects.
|
||||
MeshObj meshList[];
|
||||
|
||||
// Scene node. All child quads are added to this.
|
||||
SceneNode mNode;
|
||||
|
||||
// Bounding box, transformed to world coordinates. Used to calculate
|
||||
// camera distance.
|
||||
//Ogre::AxisAlignedBox mBounds;
|
||||
Bounds mBounds;
|
||||
|
||||
float mSplitDistance,mUnsplitDistance;
|
||||
|
||||
Quad mChildren[4];
|
||||
|
||||
// Contains the 'level' of this node. Level 1 is the closest and
|
||||
// most detailed level
|
||||
int mLevel;
|
||||
int mCellX, mCellY;
|
||||
|
||||
QuadInfo *mInfo;
|
||||
|
||||
bool hasMesh;
|
||||
bool hasChildren;
|
||||
bool isStatic; // Static quads are never split or unsplit
|
||||
}
|
Loading…
Reference in New Issue