Rewrote more of the terrain system in D

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@121 ea6a568a-9f4f-0410-981a-c910a81bb256
actorid
nkorslund 16 years ago
parent 2e1f97f3d5
commit 4f5b9d019a

@ -48,7 +48,6 @@ import sound.sfx;
import nif.nif;
import core.filefinder;
//import core.config;
// These are handles for various resources. They may refer to a file
// in the file system, an entry in a BSA archive, or point to an
@ -255,6 +254,9 @@ struct ResourceManager
char[80] texBuffer;
// Checks the BSAs / file system for a given texture name. Tries
// various Morrowind-specific hacks, like changing the extension to
// .dds and adding a 'textures\' prefix. Does not load the texture.
TextureIndex lookupTexture(char[] id)
{
if(dummy) return null;
@ -318,15 +320,19 @@ struct ResourceManager
char[] tmp;
if(id.length < 70)
{
// Avoid memory allocations if possible.
tmp = texBuffer[0..9+id.length];
tmp[9..$] = id;
}
else
tmp = "textures\\" ~ id;
else
{
tmp = "textures\\" ~ id;
writefln("WARNING: Did an allocation on %s", tmp);
}
searchWithDDS(tmp);
// Not found? If so, try without the 'texture\'
// Not found? Try without the 'texture\'
if(ti.bsaIndex == -1)
{
tmp = tmp[9..$];
@ -469,18 +475,9 @@ struct TextureResource
{
return bsaIndex == -1;
}
/*KILLME
// Returns true if resource is loaded
bool isLoaded()
{
return ml != null;
}
*/
}
// OLD STUFF
/+
void initResourceManager()

@ -26,6 +26,11 @@ module esm.loadcell;
import esm.imports;
import esm.loadregn;
import std.math : abs;
int max(int x, int y)
{ return x>=y?x:y; }
/* Cells can be seen as a collection of several records, and holds
* data about objects, creatures, statics and landscape (for exterior
* cells). This data can be huge, and has to be loaded when we need
@ -88,9 +93,9 @@ struct ExteriorCell
Region* region;
// We currently don't load all cell data (these can be huge!),
// anyway it makes sense to only load these when accessed. Use this
// to reopen the file later.
// We currently don't load all cell data (these can be huge!), and
// it makes sense to only load these when accessed. Use this to
// reopen the file later.
TES3FileContext context;
// Landscape and path grid data
@ -99,6 +104,12 @@ struct ExteriorCell
PathGrid paths;
// Return true if we have land data
bool hasLand()
{
return land.state == LoadState.Loaded && land.hasData;
}
void load()
{with(esFile){
this.region = getHNOPtr!(Region)("RGNN", regions);
@ -118,6 +129,10 @@ struct ExteriorCell
// Land can also be here instead. In fact, it can be both
// places. I have to figure out what it means.
if(isNextHRec("LAND")) land.load();
if(land.state == LoadState.Loaded)
if(gridX != land.X || gridY != land.Y)
writefln("WARNING: Grid mismatch at %s,%s!", gridX, gridY);
}}
}
@ -145,6 +160,10 @@ class CellList : ListKeeper
HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells;
HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells;
// Store the maximum x or y coordinate (in absolute value). This is
// used in the landscape pregen process.
int maxXY;
this()
{
in_cells = in_cells.init;
@ -176,6 +195,12 @@ class CellList : ListKeeper
return ex_cells.getPtr(compound(x,y));
}
// Check whether we have a given exterior cell
bool hasExt(int x, int y)
{
return ex_cells.inList(compound(x,y));
}
void *lookup(char[] s)
{ assert(0); }
@ -203,7 +228,6 @@ class CellList : ListKeeper
}
uint length() { return numInt() + numExt(); }
uint numInt() { return in_cells.length; }
uint numExt() { return ex_cells.length; }
@ -223,7 +247,8 @@ class CellList : ListKeeper
{with(esFile){
char[] id = getHNString("NAME");
// Just ignore this, don't know what it does.
// Just ignore this, don't know what it does. I assume it
// deletes the cell, but we can't handle that yet.
if(isNextSub("DELE")) getHInt();
readHNExact(&data, data.sizeof, "DATA");
@ -244,7 +269,7 @@ class CellList : ListKeeper
// Overloading an existing cell
{
if(p.state != LoadState.Previous)
fail("Duplicate internal cell " ~ id);
fail("Duplicate interior cell " ~ id);
assert(id == p.id);
p.load();
@ -266,6 +291,9 @@ class CellList : ListKeeper
p.gridY = data.gridY;
p.load();
p.state = LoadState.Loaded;
int mx = max(abs(p.gridX), abs(p.gridY));
maxXY = max(maxXY, mx);
}
else
{
@ -282,34 +310,46 @@ class CellList : ListKeeper
}
/*
* Landscape data. The landscape is stored as a hight map, and that is
* pretty much all I know at the moment.
* Landscape data.
*/
struct Land
{
LoadState state;
uint flags; // ?? - only first four bits seem to be used?
uint flags; // ?? - only first four bits seem to be used
// Map coordinates.
int X, Y;
TES3FileContext context;
bool hasData = false;
void load()
{with(esFile){
getHNUlong("INTV"); // Grid location. See next line.
//writefln("x=%d, y=%d", *(cast(int*)&grid), *(cast(int*)&grid+1));
// Get the grid location
ulong grid = getHNUlong("INTV");
X = grid & uint.max;
Y = (grid >> 32);
flags = getHNInt("DATA");
// Save file position
getContext(context);
int cnt;
// Skip these here. Load the actual data when the cell is loaded.
if(isNextSub("VNML")) skipHSubSize(12675);
if(isNextSub("VHGT")) skipHSubSize(4232);
if(isNextSub("VNML")) {skipHSubSize(12675);cnt++;}
if(isNextSub("VHGT")) {skipHSubSize(4232);cnt++;}
if(isNextSub("WNAM")) skipHSubSize(81);
if(isNextSub("VCLR")) skipHSubSize(12675);
if(isNextSub("VTEX")) skipHSubSize(512);
if(isNextSub("VTEX")) {skipHSubSize(512);cnt++;}
// We need all three of VNML, VHGT and VTEX in order to use the
// landscape.
hasData = (cnt == 3);
if(state == LoadState.Loaded)
writefln("Duplicate landscape data in " ~ getFilename());

@ -50,7 +50,7 @@ class LandTextureList : ListKeeper
// Contains the list of land textures for each file, indexed by
// file. TODO: Use some handle system here too instead of raw
// filename?
HashTable!(char[], TextureList, ESMRegionAlloc) files;
HashTable!(char[], TextureList, ESMRegionAlloc, CITextHash) files;
// The texture list for the current file
TextureList current;
@ -62,11 +62,7 @@ class LandTextureList : ListKeeper
// The first file (Morrowind.esm) typically needs a little more
// than most others
current = esmRegion.getBuffer!(TextureIndex)(0,120);
// Overkill, just leave it as it is
//files.rehash(resources.esm.length + resources.esp.length);
}
void load()

@ -192,21 +192,6 @@ class CellData
loadReferences();
// TODO: Set up landscape system here.
/*
with(esFile)
{
restoreContext(exCell.land.context, reg);
// TODO: Not all of these will be present at all times
readHNExact(,12675, "VNML");
readHNExact(,4232, "VHGT");
readHNExact(,81, "WNAM");
readHNExact(,12675, "VCLR");
readHNExact(,512, "VTEX");
}
*/
const float cellWidth = 8192;
// TODO/FIXME: This is temporary

@ -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
};

@ -20,83 +20,9 @@
*/
///no texture assigned
const int LAND_LTEX_NONE = 0;
///the default land height that it defaults to in the TESCS
const int LAND_DEFAULT_HEIGHT = -2048;
///how many verts wide (and long) the cell is
const int LAND_VERT_WIDTH = 65;
///Number of verts that make up a cell
const int LAND_NUM_VERTS = LAND_VERT_WIDTH*LAND_VERT_WIDTH;
const int LAND_LTEX_WIDTH = 16;
const int LAND_NUM_LTEX = LAND_LTEX_WIDTH*LAND_LTEX_WIDTH;
const int CELL_WIDTH = 8192;
// Scaling factor to apply to textures on once cell. A factor of 1/16
// gives one repetition per square, since there are 16x16 texture
// 'squares' in acell. For reference, Yacoby's scaling was equivalent
// to having 1.0/10 here, or 10 repititions per cell. TODO: This looks
// a little blocky. Compare with screenshots from TES-CS.
const float TEX_SCALE = 1.0/16;
// Multiplied with the size of the quad. If these are too close, a
// quad might end up splitting/unsplitting continuously, since the
// quad size changes when we split.
const float SPLIT_FACTOR = 0.5;
const float UNSPLIT_FACTOR = 2.0;
//stops it crashing, now it leaks.
#define ENABLED_CRASHING 0
class Quad;
class TerrainMesh;
class BaseLand;
// Cache directory and file
std::string g_cacheDir;
std::string g_cacheFile;
// Enable or disable tracing of runtime functions. Making RTRACE do a
// trace slows down the code significantly even when -debug is off, so
// lets disable it for normal use.
#define RTRACE(x)
//#define RTRACE TRACE
/*
// Prerequisites
#include <vector>
#include <map>
#include <fstream>
#include <string>
#include <list>
#include <algorithm>
// Located in ../util/
#include "mmfile.h"
#include "outbuffer.h"
// Reading and writing the cache files
#include "cpp_archive.cpp"
#include "cpp_cachewriter.cpp"
// For generation
#include "cpp_esm.cpp"
#include "cpp_landdata.cpp"
#include "cpp_generator.cpp"
// For rendering
Quad *g_rootQuad;
BaseLand *g_baseLand;
SceneNode *g_rootTerrainNode;
#include "cpp_baseland.cpp"
#include "cpp_mesh.cpp"
#include "cpp_quad.cpp"
class TerrainFrameListener : public FrameListener
{
@ -112,67 +38,56 @@ protected:
extern "C" void d_superman();
*/
extern "C" void terr_setCacheDir(char *cacheDir)
extern "C"
{
g_cacheDir = cacheDir;
g_cacheFile = g_cacheDir + "terrain.cache";
}
// Set up the rendering system
extern "C" void terr_setupRendering()
{
/*
// Add the terrain directory
ResourceGroupManager::getSingleton().
addResourceLocation(g_cacheDir, "FileSystem", "General");
SceneNode* terr_createChildNode(float relX, float relY,
SceneNode *parent)
{}
// Create a root scene node first. The 'root' node is rotated to
// match the MW coordinate system
g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT");
void terr_destroyNode(SceneNode *node)
{}
// Open the archive file
g_archive.openFile(g_cacheFile);
void *terr_makeBounds(float minHeight, float maxHeight,
float width)
{}
// Create the root quad.
g_rootQuad = new Quad();
float terr_getSqCamDist(void*)
{}
g_baseLand = new BaseLand(g_rootTerrainNode);
void *terr_makeMesh(int segment, SceneNode*)
{}
// Add the frame listener
mRoot->addFrameListener(new TerrainFrameListener);
void terr_killMesh(void*)
{}
// Enter superman mode
mCamera->setFarClipDistance(32*CELL_WIDTH);
//ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
d_superman();
*/
}
// Set up the rendering system
void terr_setupRendering()
{
/*
// Add the terrain directory
ResourceGroupManager::getSingleton().
addResourceLocation(g_cacheDir, "FileSystem", "General");
/*
// Generate all cached data.
extern "C" void terr_genData()
{
Ogre::Root::getSingleton().renderOneFrame();
// Create a root scene node first. The 'root' node is rotated to
// match the MW coordinate system
g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT");
Generator mhm;
{
ESM esm;
// Open the archive file
g_archive.openFile(g_cacheFile);
const std::string fn("data/Morrowind.esm");
// Create the root quad.
g_rootQuad = new Quad();
esm.addRecordType("LAND", "INTV");
esm.addRecordType("LTEX", "INTV");
g_baseLand = new BaseLand(g_rootTerrainNode);
esm.loadFile(fn);
RecordList* land = esm.getRecordsByType("LAND");
for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr )
mhm.addLandData(*itr, fn);
// Add the frame listener
mRoot->addFrameListener(new TerrainFrameListener);
RecordList* ltex = esm.getRecordsByType("LTEX");
for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr )
mhm.addLandTextureData(*itr, fn);
// Enter superman mode
mCamera->setFarClipDistance(32*CELL_WIDTH);
//ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
d_superman();
*/
}
mhm.generate(g_cacheFile);
}
*/

@ -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 &currentLand;
}
LTEXData *getLTEXData(int x, int y)
{
loadCell(x, y);
return &currentLtex;
}
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(&currentLand.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(&currentLtex.vtex, VTEX.sizeof, "VTEX");
}
}

@ -27,16 +27,15 @@ module terrain.generator;
/+
import std.stdio;
import std.string;
import terrain.cachewriter;
import terrain.esmland;
import util.cachefile;
const float TEX_SCALE = 1.0/16;
char[] cacheDir = "cache/terrain/";
// Interface to the ESM landscape data
MWLand mwland;
int mCount;
// Texture sizes for the various levels. For the most detailed level
@ -328,11 +327,11 @@ void genLevel1Meshes(ref GenLevelResult res)
mi.worldWidth = vertSep*intervals;
assert(mi.worldWidth == 8192);
auto land = mwland.getData(cellX, cellY);
auto land = mwland.getLandData(cellX, cellY);
byte[] heightData = land.heights;
byte[] heightData = land.vhgt.heights;
byte[] normals = land.normals;
mi.heightOffset = land.heightOffset;
mi.heightOffset = land.vhgt.heightOffset;
float max=-1000000.0;
float min=1000000.0;

@ -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
}

@ -24,6 +24,7 @@
module terrain.terrain;
import terrain.generator;
import terrain.bindings;
void initTerrain(bool doGen)
{
@ -34,7 +35,3 @@ void initTerrain(bool doGen)
//terr_setupRendering();
}
extern(C):
void terr_genData();
void terr_setupRendering();

Loading…
Cancel
Save