- working on the terrain system. DOES NOT WORK!

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@117 ea6a568a-9f4f-0410-981a-c910a81bb256
actorid
nkorslund 16 years ago
parent c18a1ebd05
commit 75d5ad8882

@ -8,7 +8,7 @@ DMD=gdmd -version=Posix
NIFFLAGS=-debug=warnstd -debug=check -debug=statecheck -debug=strict -debug=verbose
# Linker flags
LFLAGS= -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a -L-lboost_serialization
LFLAGS= -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a
# Compiler settings for Ogre, OIS and MyGUI
# TODO: the -I when we're done
@ -32,9 +32,8 @@ ogre_cpp=ogre framelistener interface bsaarchive
mygui_cpp=mygui console
# Ditto for the landscape engine, in terrain/cpp_X.cpp
terrain_cpp=baseland esm framelistener generator index landdata\
materialgen heightmap palette point2\
quad quaddata terrain terrainmesh
terrain_cpp=baseland esm generator landdata quad terrain terrainmesh \
archive cachewriter
# FFmpeg files, in the form sound/cpp_X.cpp.
avcodec_cpp=avcodec
@ -47,7 +46,7 @@ bullet_cpp=bullet player scale
ogre_cpp_files=\
$(ogre_cpp:%=ogre/cpp_%.cpp) \
$(mygui_cpp:%=gui/cpp_%.cpp) \
$(terrain_cpp:%=terrain/cpp_%.cpp)
$(terrain_cpp:%=terrain/cpp_%.cpp) util/outbuffer.h util/mmfile.h
avcodec_cpp_files=$(avcodec_cpp:%=sound/cpp_%.cpp)
bullet_cpp_files=$(bullet_cpp:%=bullet/cpp_%.cpp)

@ -20,6 +20,7 @@
http://www.gnu.org/licenses/ .
*/
module bsa.bsafile;
//debug=checkHash;
@ -67,7 +68,9 @@ class BSAFile
// equivalent to find this (include the word "granularity" when you
// google for it.) For now I just assume 4K is ok on UNIX, but on
// Windows we need 64K. (Hands up if you agree that MMFile should
// handle this internally!)
// handle this internally!). UPDATE: This is now duplicated in
// util.c_mmfile, if we make it more fancy we should collect it in
// one place.
version(Windows)
static int pageSize = 64*1024;
else

@ -268,9 +268,9 @@ extern(C) void d_superman()
with(*playerData.position)
{
position[0] = 0;
position[1] = 0;
position[2] = 12000;
position[0] = 20000;
position[1] = -70000;
position[2] = 30000;
}
movePlayer();
}

@ -43,6 +43,8 @@ public:
// Start of frame
bool frameStarted(const FrameEvent& evt)
{
TRACE("frameStarted (Input)");
if(mWindow->isClosed())
return false;

@ -50,6 +50,7 @@ extern "C" int32_t ogre_configure(
new LogManager;
Log *log = LogManager::getSingleton().createLog("Ogre.log");
g_isDebug = debugOut;
if(debugOut)
// Full log detail
log->setLogDetail(LL_BOREME);
@ -99,28 +100,29 @@ extern "C" void ogre_initWindow()
windowHndStr << windowHnd;
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
// Non-exclusive mouse and keyboard input
/*
// Non-exclusive mouse and keyboard input in debug mode
if(g_isDebug)
{
#if defined OIS_WIN32_PLATFORM
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_FOREGROUND" )));
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_NONEXCLUSIVE")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_FOREGROUND")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_NONEXCLUSIVE")));
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_FOREGROUND" )));
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_NONEXCLUSIVE")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_FOREGROUND")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_NONEXCLUSIVE")));
#elif defined OIS_LINUX_PLATFORM
pl.insert(std::make_pair(std::string("x11_mouse_grab"),
std::string("true")));
pl.insert(std::make_pair(std::string("x11_mouse_hide"),
std::string("true")));
pl.insert(std::make_pair(std::string("x11_keyboard_grab"),
std::string("true")));
pl.insert(std::make_pair(std::string("XAutoRepeatOn"),
std::string("false")));
pl.insert(std::make_pair(std::string("x11_mouse_grab"),
std::string("false")));
pl.insert(std::make_pair(std::string("x11_mouse_hide"),
std::string("false")));
pl.insert(std::make_pair(std::string("x11_keyboard_grab"),
std::string("false")));
pl.insert(std::make_pair(std::string("XAutoRepeatOn"),
std::string("true")));
#endif
*/
}
mInputManager = InputManager::createInputSystem( pl );

@ -53,6 +53,9 @@ int g_spotOn = 0;
Light *g_light;
*/
// Set to nonzero if debug mode is enabled
int g_isDebug = 0;
OIS::InputManager *mInputManager;
OIS::Mouse *mMouse;
OIS::Keyboard *mKeyboard;

@ -0,0 +1,463 @@
/*
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;
}

@ -28,11 +28,11 @@ public:
}
Ogre::Vector3 p = mCamera->getDerivedPosition();
p.x -= ((int)p.x % 8192);
p.z -= ((int)p.z % 8192);
p.x -= ((int)p.x % CELL_WIDTH);
p.z -= ((int)p.z % CELL_WIDTH);
float h = p.y + 2048;
h = pow(h/8192*2,2);
h = pow(h/CELL_WIDTH*2,2);
if ( h < 0 ) h = 0;
mNode->setPosition(p.x, -32 - h, p.z);
@ -45,7 +45,7 @@ private:
mObject->begin("BaseLandMat", Ogre::RenderOperation::OT_TRIANGLE_LIST);
Ogre::Real vd = mCamera->getFarClipDistance();
vd += 8192 - ((int)vd % 8192);
vd += CELL_WIDTH - ((int)vd % CELL_WIDTH);
mMeshDistance = vd;
@ -84,8 +84,8 @@ private:
void createMaterial()
{
float vd = mCamera->getFarClipDistance();
vd += 8192 - ((int)vd % 8192);
vd = vd/8192 * 2;
vd += CELL_WIDTH - ((int)vd % CELL_WIDTH);
vd = vd/CELL_WIDTH * 2;
mMat = Ogre::MaterialManager::getSingleton().
create(std::string("BaseLandMat"),

@ -0,0 +1,335 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Nicolay Korslund
WWW: http://openmw.sourceforge.net/
This file (cpp_cachewriter.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/ .
*/
// Helper structs
struct AlphaHolder
{
AlphaInfo info;
// Actual pixel buffer
unsigned char *buffer;
// Texture name and alpha material name to use
//std::string texName, alphaName;
};
struct MeshHolder
{
MeshInfo info;
// Actual buffers
char *vertexBuffer;
// Texture name
std::string texName;
// Alpha maps (if any)
std::vector<AlphaHolder> alphas;
};
// A struct that gathers all the relevant quad data in one place.
struct QuadHolder
{
QuadInfo info;
std::vector<MeshHolder> meshes;
};
class CacheWriter
{
public:
// Opens the main archive file for output
void openFile(const std::string &fname)
{
mainFile.open(fname.c_str(), std::ios::binary);
iname = fname + ".index";
fileOffset = 0;
totalStringLength = 0;
}
void setParams(int mxLev, int alphSize)
{
maxLevel = mxLev;
alphaSize = alphSize;
vertBufData.resize(maxLevel);
indexBufData.resize(maxLevel);
vertBufSize.resize(maxLevel);
indexBufSize.resize(maxLevel);
}
// Closes the main archive file and writes the index.
void finish()
{
mainFile.close();
// Write the index file
std::ofstream ofile(iname.c_str(), std::ios::binary);
// Header first
ArchiveHeader head;
head.magic = CACHE_MAGIC;
head.quads = quadList.size();
head.rootLevel = maxLevel;
head.alphaSize = alphaSize;
head.stringNum = stringList.size();
head.stringSize = totalStringLength;
ofile.write((char*)&head, sizeof(head));
// Write the quads
for(QuadList::iterator it = quadList.begin();
it != quadList.end(); it++)
{
QuadInfo qi = *it;
ofile.write((char*)&qi, sizeof(QuadInfo));
}
// String table next. We need to sort it in order of the indices
// first.
std::vector<std::string> strVector;
strVector.resize(head.stringNum);
for(StringList::iterator it = stringList.begin();
it != stringList.end(); it++)
{
strVector[it->second] = it->first;
}
// Next, write the strings to file while we fill inn the offset
// list
std::vector<int> offsets;
offsets.resize(head.stringNum);
size_t curOffs = 0;
for(int i=0; i<head.stringNum; i++)
{
// Add one byte for the zero terminator
int len = strVector[i].length() + 1;
const char *ptr = strVector[i].c_str();
assert(ptr[len-1] == 0);
ofile.write(ptr, len);
// Store the offset
offsets[i] = curOffs;
curOffs += len;
}
// At the end the offset should match the buffer size we set in
// the header.
assert(curOffs == head.stringSize);
// Finally, write the offset table itself
for(int i=0; i<head.stringNum; i++)
{
int offs = offsets[i];
ofile.write((char*)&offs, sizeof(int));
}
for(int i=1;i<maxLevel;i++)
{
int size;
void *ptr;
// Write vertex buffer
size = vertBufSize[i];
ptr = vertBufData[i];
ofile.write((char*)&size, sizeof(int));
ofile.write((char*)ptr, size);
// Then the index buffer
size = indexBufSize[i];
ptr = indexBufData[i];
ofile.write((char*)&size, sizeof(int));
ofile.write((char*)ptr, size);
}
}
// Add a common vertex buffer for a given level
void addVertexBuffer(int level, void *ptr, int size)
{
assert(vertBufData.size() > level);
vertBufData[level] = ptr;
vertBufSize[level] = size;
}
// Add a common vertex buffer for a given level
void addIndexBuffer(int level, void *ptr, int size)
{
assert(indexBufData.size() > level);
indexBufData[level] = ptr;
indexBufSize[level] = size;
}
// Write a finished quad to the archive file. All the offsets and
// numbers in the *Info structs are filled in automatically based on
// the additional data in the Holder structs.
void writeQuad(const QuadHolder &qh)
{
TRACE("writeQuad");
// See util/outbuffer.h
OutBuffer buf;
// Write the MeshInfo's first
int meshNum = qh.meshes.size();
MeshInfo *meshes = buf.write<MeshInfo>(meshNum);
// Then write the mesh data in approximately the order it's read
for(int i=0; i<meshNum; i++)
{
assert(meshes != NULL);
const MeshHolder &mh = qh.meshes[i];
// Copy the basic data first
meshes[i] = mh.info;
// Set everything else except the offsets
int alphaNum = mh.alphas.size();
meshes[i].alphaNum = alphaNum;
//meshes[i].texName = addString(mh.texName);
// Write the vertex buffer
meshes[i].vertBufOffset = buf.size();
writeBuf(buf, mh.vertexBuffer, meshes[i].vertBufSize);
// Next write the alpha maps, if any
meshes[i].alphaOffset = buf.size();
AlphaInfo *ai = buf.write<AlphaInfo>(alphaNum);
// Loop through the alpha maps
for(int k=0; k<alphaNum; k++)
{
AlphaHolder ah = mh.alphas[k];
ai[k] = ah.info;
// Convert the strings
// KILLME
//ai[k].texName = addString(ah.texName);
//ai[k].alphaName = addString(ah.alphaName);
// Write the alpha pixel buffer
ai[k].bufOffset = buf.size();
writeBuf(buf, ah.buffer, ai[k].bufSize);
}
}
// The quad cache is done, write it to file
mainFile << buf;
// Finally set up the QuadInfo itself
QuadInfo qi;
// Basic info
qi = qh.info;
// Derived info
qi.meshNum = meshNum;
qi.size = buf.size();
qi.offset = fileOffset;
// Update the main offset
fileOffset += qi.size;
// Add the quad to the index list
quadList.push_back(qi);
std::cout << "end\n";
}
// Add a texture name as a string. Will convert .tga file names to
// .dds as a convenience
int addTexture(const std::string &orig)
{
size_t d = orig.find_last_of(".") + 1;
return addString(orig.substr(0, d) + "dds");
}
// Convert a string to an index
int addString(const std::string &str)
{
// Do we already have the string?
StringList::iterator it = stringList.find(str);
if(it != stringList.end())
return it->second;
// Nope, insert it
int index = stringList.size();
stringList[str] = index;
stringLookup[index] = str;
// Sum up the string lengths + 1 byte for the zero
totalStringLength += str.length() + 1;
return index;
}
const std::string &getString(int index)
{
const std::string &res = stringLookup[index];
assert(stringList[res] == index);
return res;
}
private:
// Write the given block of memory to 'buf', possibly compressing
// the data.
void writeBuf(OutBuffer &buf, const void *ptr, size_t size)
{
// Reserve the maximum bytes needed.
void *toPtr = buf.reserve(size);
// Store the data
memcpy(toPtr, ptr, size);
// Add the actual number of bytes stored
buf.add(size);
}
std::vector<void*> vertBufData;
std::vector<void*> indexBufData;
std::vector<int> vertBufSize;
std::vector<int> indexBufSize;
// Variables that must be set during the gen phase
int maxLevel;
int alphaSize;
// Contains a unique index for each string
typedef std::map<std::string, int> StringList;
StringList stringList;
std::map<int, std::string> stringLookup;
size_t totalStringLength;
// List of all quads
typedef std::list<QuadInfo> QuadList;
QuadList quadList;
// Output file
std::ofstream mainFile;
size_t fileOffset;
// Index file name
std::string iname;
};

@ -65,12 +65,10 @@ private:
};
typedef boost::shared_ptr<Record> RecordPtr;
typedef std::list<RecordPtr> RecordList;
typedef std::list<Record*> RecordList;
typedef RecordList::iterator RecordListItr;
typedef boost::shared_ptr<RecordList> RecordListPtr;
typedef std::map<std::string, RecordPtr> RecordMap;
typedef std::map<std::string, Record*> RecordMap;
typedef RecordMap::iterator RecordMapItr;
///top level class for loading and saving esp files.
@ -124,7 +122,7 @@ public:
long endPos = recordSize + esp.tellg();
if ( loadType(type) ) {
RecordPtr record = RecordPtr(new Record(type));
Record* record = new Record(type);
//load all subrecords
while ( esp.tellg() < endPos ) {
@ -156,11 +154,11 @@ public:
return true;
}
inline RecordPtr getRecord(const std::string& id){ return mRecords[id]; }
inline Record* getRecord(const std::string& id){ return mRecords[id]; }
RecordListPtr getRecordsByType(const std::string& t)
RecordList* getRecordsByType(const std::string& t)
{
RecordListPtr r = RecordListPtr(new RecordList); //need pointer....
RecordList* r = new RecordList;
for ( RecordMapItr iter = mRecords.begin(); iter != mRecords.end(); ++iter)
if ( t == iter->second->getType() )
r->push_back(iter->second);

@ -1,32 +0,0 @@
class TerrainFrameListener : public FrameListener
{
protected:
bool frameEnded(const FrameEvent& evt)
{
g_rootQuad->update(evt.timeSinceLastFrame);
g_heightMap->update(evt.timeSinceLastFrame);
return true;
}
public:
void setup()
{
// Add the frame listener
mRoot->addFrameListener(this);
// Create a root scene node first
Ogre::SceneNode *node = mSceneMgr->getRootSceneNode()
->createChildSceneNode("TERRAIN_ROOT");
// The main terrain object
g_heightMap = new HeightMap(node);
g_heightMap->load(TERRAIN_OUTPUT);
// Fix settings
g_heightMap->setMorphingEnabled(false);
g_heightMap->setTextureFadingEnabled(false);
// Create the root quad
g_rootQuad = new Quad(Quad::QL_ROOT, 0);
}
};

File diff suppressed because it is too large Load Diff

@ -1,97 +0,0 @@
class HeightMap
{
public:
HeightMap(Ogre::SceneNode* r)
: mTerrainSceneNode(r),
mMorphingEnabled(false),
mTextureFadingEnabled(false),
mBaseLand(r)
{}
/**
* loads the quad data from the disk
*/
QuadData* getData(long x, long y)
{
long offset = mIndex.getOffset(x,y);
if ( offset == -1 ) //check we have xy
assert(0);
mDataIFS.seekg(offset);
//load the quad from the file
QuadData* q = new QuadData();
boost::archive::binary_iarchive oa(mDataIFS);
oa >> *q;
return q;
}
inline bool hasData(long x, long y)
{ return (mIndex.getOffset(x,y) != -1 ); }
inline long getRootSideLength() {
return mIndex.getRootSideLength();
}
inline int getMaxDepth() {
return mIndex.getMaxDepth();
}
/**
* Loads the index and palette
*/
bool load(const std::string& fn)
{
{
std::ifstream ifs(std::string(fn + ".index").c_str(), std::ios::binary);
boost::archive::binary_iarchive oa(ifs);
oa >> mIndex;
}
{
std::ifstream ifs(std::string(fn + ".palette").c_str(), std::ios::binary);
boost::archive::binary_iarchive oa(ifs);
oa >> mPalette;
}
g_materialGen->setTexturePaths(mPalette.getPalette());
mDataIFS.open(std::string(fn + ".data").c_str(), std::ios::binary);
return true;
}
inline Ogre::SceneNode* getTerrainSceneNode(){return mTerrainSceneNode;}
void update(Ogre::Real t)
{
mBaseLand.update();
}
inline Ogre::Real getMorphSpeed(){return 1.0f;}
inline Ogre::Real getTextureFadeSpeed(){ return 2.0f;}
inline void setMorphingEnabled(bool enabled){
mMorphingEnabled = enabled;
}
inline bool isMorphingEnabled() const{
return mMorphingEnabled;
}
inline void setTextureFadingEnabled(bool enabled){
if ( enabled && !mMorphingEnabled )
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "Cannot have fading but not morphing active", "Terrain::setTextureFadingEnabled");
mTextureFadingEnabled = enabled;
}
inline bool isTextureFadingEnabled() const{
return mTextureFadingEnabled;
}
private:
Ogre::SceneNode* mTerrainSceneNode;
bool mMorphingEnabled;
bool mTextureFadingEnabled;
BaseLand mBaseLand;
///ifs for the data file. Opned on load
std::ifstream mDataIFS;
///holds the offsets of the quads
Index mIndex;
TexturePalette mPalette;
};

@ -1,91 +0,0 @@
/**
* Holds index and other data describing the landscape.data file.
*/
class Index
{
public:
typedef std::map<long, std::map<long, long> >::iterator OffsetItr;
typedef std::map<long, std::map<long, long> >::const_iterator OffsetConstItr;
/**
* @brief sets the root quads side length in gu
* @param l the side length
*
* This is used for working out the locations of quad children.
* I am assuming a long is enough...
*/
inline void setRootSideLength(long l) {
mRootSideLength = l;
}
/**
* @return the side length of the root quad.
*/
inline long getRootSideLength() const {
return mRootSideLength;
}
inline void setMaxDepth(int d) {
mMaxDepth = d;
}
inline int getMaxDepth() const {
return mMaxDepth;
}
/**
* @return -1 is returned if there is no offset
* @param x, y the position of the quad in gu
*
* Slightly faster using hasOffset to check if it exists
* Shouldn't be noticable diffrence.
*/
inline long getOffset(long x, long y) const { //inline?
OffsetConstItr itr1 = mQuadOffsets.find(x);
if ( itr1 == mQuadOffsets.end() ) return -1;
std::map<long, long>::const_iterator itr2 = itr1->second.find(y);
if ( itr2 == itr1->second.end() ) return -1;
return itr2->second;
}
/**
* @brief checks if a quad for the given position exists
* @return true/false
* @param x, y the position of the quad in gu
*
* @todo Would it be worth merging this with getOffset?
*/
inline bool hasOffset(long x, long y) const {
OffsetConstItr itr = mQuadOffsets.find(x);
if ( itr == mQuadOffsets.end() ) return false;
return (itr->second.find(y) != itr->second.end());
}
/**
* @brief sets an offset of a quad
* @param x, y the position in gu of the quad
* @param o the offset within the file of the records for this quad
*/
inline void setOffset(long x, long y, long o) {
mQuadOffsets[x][y] = o;
}
protected:
std::map<long, std::map<long, long> > mQuadOffsets;
long mRootSideLength; ///length in gu of the root quad
int mMaxDepth; ///maximum depth assuming root quad depth = 0
friend class boost::serialization::access;
/**
* Saves the data for the max depth, the root side legnth, and the quad offsets
*/
template<class Archive>
inline void serialize(Archive& ar, const unsigned int version){
ar &mMaxDepth;
ar &mRootSideLength;
ar &mQuadOffsets;
}
};
BOOST_CLASS_TRACKING(Index, boost::serialization::track_never);

@ -1,3 +1,11 @@
struct VHGT
{ ///height data
float heightOffset;
char heightData[LAND_NUM_VERTS];
short unknown1;
char unknown2;
};
class MWLand
{
public:
@ -6,7 +14,7 @@ public:
mMaxX = mMaxY = mMinX = mMinY = 0;
}
void addLandTextureData(RecordPtr record, const std::string& source)
void addLandTextureData(Record* record, const std::string& source)
{
LandTexture l;
l.name = record->getSubRecordData("NAME");
@ -15,19 +23,21 @@ public:
mLTEXRecords[source][l.intv] = l;
}
void addLandData(RecordPtr record, const std::string& source)
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();
LAND::VHGT vhgt = *(LAND::VHGT*)record->getSubRecordData("VHGT").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();
//GridPosition gp(intv.x, intv.y);
mLandRecords[intv.x][intv.y].heights = parseHeights(&vhgt); //convert into a format we want
// 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;
@ -47,20 +57,18 @@ public:
///see others
inline int getMinY() const { return mMinY; }
inline std::vector<float>& getHeights(int x, int y)
inline VHGT *getHeights(int x, int y)
{
if ( hasData(x,y) )
return mLandRecords[x][y].heights;
static std::vector<float> e(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT);
return e;
assert(0);
}
inline std::vector<char>& getNormals(int x, int y)
{
if ( hasData(x,y) )
return mLandRecords[x][y].normals;
static std::vector<char> e(LAND_NUM_VERTS*3,0);
return e;
assert(0);
}
inline const std::string& getSource(int x, int y)
@ -126,12 +134,6 @@ private:
};
NORMAL normals[LAND_NUM_VERTS];
};
struct VHGT { ///height data
float heightOffset;
char heightData[LAND_NUM_VERTS];
short unknown1;
char unknown2;
};
struct VTEX { ///splat texture data
short index[LAND_NUM_LTEX];
};
@ -141,22 +143,34 @@ private:
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++) { //code from MGE
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++) {
pos += vhgt->heightData[y*LAND_VERT_WIDTH+x];
ph[y*LAND_VERT_WIDTH+x] = pos*8; //flipped x
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 )
{
@ -193,7 +207,7 @@ private:
struct LandData
{
std::string source; //data file the data is from
std::vector<float> heights;
VHGT *heights;
std::vector<char> normals;
std::vector<short> textures;
};

@ -1,371 +0,0 @@
class TextureSplatter
{
public:
inline TextureSplatter(const std::vector<int>& ltex,
int texSize,
int ltexsize)
: mLTEX(ltex),
mTexSize(texSize), //the root of the size of the texture
mLTEXSize(ltexsize) //the root of the ltex array
{ mSizeDiff = texSize/ltexsize; }
int getAlphaAtPixel(int x, int y, int tid)
{
if ( getTextureAtPixel(x,y) == tid )
return 255;
/*
else if ( mBorder > 0 )
{
//hacky remove fix. perofrmance issues. skips if not ingame gen.
float amount = 0;
for ( int ty = y-1; ty <= y+1; ++ty ) {
for ( int tx = x-1; tx <= x+1; ++tx ) {
if ( ty < -mBorder*mSizeDiff ||
tx < -mBorder*mSizeDiff ||
ty >= mTexSize+mBorder*mSizeDiff ||
tx >= mTexSize+mBorder*mSizeDiff )
continue;
if ( getTextureAtPixel(tx,ty) == tid )
amount += 0.18f;
}
}
if ( amount > 1 ) amount = 1;
assert(amount>=0&&amount<=1);
return amount*255;
}
*/
return 0;
}
private:
int getTextureAtPixel(int x, int y) {
x = (x - x%mSizeDiff)/mSizeDiff;
y = (y - y%mSizeDiff)/mSizeDiff;
return mLTEX[(y)*mLTEXSize+(x)];
}
const std::vector<int>& mLTEX;
const int mTexSize;
const int mLTEXSize;
int mSizeDiff;
};
/**
* Handles the runtime generation of materials
*
*/
class MaterialGenerator
{
public:
/**
* @brief generates a material for a quad using a single
* texture. Only used at runtime, not while generating.
*/
Ogre::MaterialPtr generateSingleTexture
(const std::string& texName,
std::list<Ogre::ResourcePtr> createdResources)
{
const std::string matname("MAT" + Ogre::StringConverter::toString(mCount++));
if ( !Ogre::MaterialManager::getSingleton().resourceExists(matname) )
{
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(matname,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Pass* p = mat->getTechnique(0)->getPass(0);
p->setLightingEnabled(false);
p->createTextureUnitState(texName)->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
createdResources.push_back(mat);
return mat;
}
return Ogre::MaterialPtr();
}
/**
* gets the material for the alpha textures
*/
Ogre::MaterialPtr new_getAlphaMat(std::vector<int>& ltex,
int size,
int border,
float scaleDiv,
std::list<Ogre::ResourcePtr>& createdResources)
{
const std::string materialName("MAT" + Ogre::StringConverter::toString(mCount++));
// We REALLY need to clean these variables up
// Number of textures along one side
const int ltexWidth = Ogre::Math::Sqrt(ltex.size());
// Only true if border = 0, which I guess I've assumed below
assert(ltexWidth == size);
// Side for the quad only, not including the border. Used for the
// assert() below only.
const int rootSideLength = ltexWidth-(border*2);
// Multiply up the number of textures along a side to get the
// resolution of the alpha map. This gives 4*4=16 alpha pixels per
// texture square, or 4*16 = 64 alpha pixels across the entire
// quad.
const int sizeDiff = 4;
size *= sizeDiff;
assert(size%rootSideLength==0);
Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().
create(materialName,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
createdResources.push_back(material);
// But the default texture in the bottom 'layer', so that we don't
// end up seeing through the landscape.
Ogre::Pass* np = material->getTechnique(0)->getPass(0);
np->setLightingEnabled(false);
// FIXME: Why 0.1f?
np->createTextureUnitState("_land_default.dds")->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv);
// Put all the texture indices in a set
typedef std::set<int> SI;
typedef SI::iterator SI_it;
SI textures;
for( int in = 0; in < ltex.size(); in++ )
textures.insert(ltex[in]);
// Allocate the buffers
typedef std::vector<uint8_t> AlphaBuf;
std::map<int,AlphaBuf> alphaMap;
const int bufSize = size*size;
//TextureSplatter ts(ltex, size, ltexWidth, border);
// Peform splatting. Loop over each texture square in this quad.
for(int ty=0; ty < size; ty += sizeDiff)
for(int tx=0; tx < size; tx += sizeDiff)
{
// Get the texture index for this square
const int thisInd = ltex[tx + ltexWidth*ty];
// Offset for this texture
const int offs = ty*size + tx;
AlphaBuf &abuf = alphaMap[thisInd];
abuf.resize(bufSize);
// Set alphas to full for this square
for(int y=0; y<sizeDiff; y++)
for(int x=0; x<sizeDiff; x++)
{
int toffs = offs + y*size + x;
if(toffs >= abuf.size())
std::cout << "tx=" << tx << " ty=" << ty
<< " x=" << x << " y=" << y
<< " offs=" << offs
<< " toffs=" << toffs
<< " abuf.size()=" << abuf.size()
<< "\n";
assert(toffs < abuf.size());
abuf[toffs] = 255;
}
// Get alpha influence from neighbouring cells.
// FIXME: Rewrite TextureSplatter
}
// Create passes for each alpha buffer
for ( SI_it itr = textures.begin(); itr != textures.end(); ++itr )
{
int tid = *itr;
const std::string tn(mTexturePaths[tid]);
if ( tn == "_land_default.dds" )
continue;
//std::cout << " Generating texture " << tn << "\n";
std::string alphaName(materialName + "_A_" + tn);
if ( Ogre::TextureManager::getSingleton().resourceExists(alphaName) )
OGRE_EXCEPT(0, "ALPHA Already Exists", "");
//create alpha map
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
createManual(
alphaName, // Name of texture
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, // Name of resource group in which the texture should be created
Ogre::TEX_TYPE_2D,
size,size, //size ofc
1,0, //depth, mipmaps
Ogre::PF_A8, //we only need one channel to hold the splatting texture
Ogre::TU_STATIC_WRITE_ONLY //best performace, we shopuldn't need the data again
);
createdResources.push_back(texPtr);
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
AlphaBuf *abuf = &alphaMap[tid];
for(AlphaBuf::iterator it = abuf->begin(); it != abuf->end(); it++)
*(pDest++) = *it;
pixelBuffer->unlock();
np = material->getTechnique(0)->createPass();
np->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
np->setLightingEnabled(false);
np->setDepthFunction(Ogre::CMPF_EQUAL);
Ogre::TextureUnitState* tus = np->createTextureUnitState(alphaName);
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
tus->setAlphaOperation( Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
tus = np->createTextureUnitState(tn);
tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv);
}
return material;
}
// The old variant
Ogre::MaterialPtr getAlphaMat(std::vector<int>& ltex,
int size,
float scaleDiv,
std::list<Ogre::ResourcePtr>& createdResources)
{
const std::string materialName("MAT" + Ogre::StringConverter::toString(mCount++));
// Multiply up the number of textures along a side to get the
// resolution of the alpha map. This gives 4*4=16 alpha pixels per
// texture square, or 4*16 = 64 alpha pixels across the entire
// quad.
const int sizeDiff = 4;
size *= sizeDiff;
// Number of textures along one side
const int ltexWidth = Ogre::Math::Sqrt(ltex.size());
assert(size%ltexWidth==0);
Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().
create(materialName,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
createdResources.push_back(material);
// But the default texture in the bottom 'layer', so that we don't
// end up seeing through the landscape.
Ogre::Pass* np = material->getTechnique(0)->getPass(0);
np->setLightingEnabled(false);
np->createTextureUnitState("_land_default.dds")->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv);
// Put all the texture indices in a set
std::set<int> textures;
for ( int y1 = 0; y1 < ltexWidth; y1++ ) {
for ( int x1 = 0; x1 < ltexWidth; x1++ ) {
textures.insert(ltex[(y1)*ltexWidth+x1]);
}
}
for ( std::set<int>::iterator itr = textures.begin(); itr != textures.end(); ++itr )
{
int tid = *itr;
const std::string tn(mTexturePaths[tid]);
if ( tn == "_land_default.dds" )
continue;
//std::cout << " Generating texture " << tn << "\n";
std::string alphaName(materialName + "_A_" + tn);
if ( Ogre::TextureManager::getSingleton().resourceExists(alphaName) )
OGRE_EXCEPT(0, "ALPHA Already Exists", "");
//create alpha map
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
createManual(
alphaName, // Name of texture
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, // Name of resource group in which the texture should be created
Ogre::TEX_TYPE_2D,
size,size, //size ofc
1,0, //depth, mipmaps
Ogre::PF_A8, //we only need one channel to hold the splatting texture
Ogre::TU_STATIC_WRITE_ONLY //best performace, we shopuldn't need the data again
);
createdResources.push_back(texPtr);
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
memset(pDest,0, sizeof(Ogre::uint8)*size*size);
TextureSplatter ts(ltex, size, ltexWidth);
for ( int ty = 0; ty < size; ty++ ) {
for ( int tx = 0; tx < size; tx++ ) {
pDest[ty*size+tx] = ts.getAlphaAtPixel(tx,ty, tid);
}
}
pixelBuffer->unlock();
np = material->getTechnique(0)->createPass();
np->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
np->setLightingEnabled(false);
np->setDepthFunction(Ogre::CMPF_EQUAL);
Ogre::TextureUnitState* tus = np->createTextureUnitState(alphaName);
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
tus->setAlphaOperation( Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
tus = np->createTextureUnitState(tn);
tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv);
}
return material;
}
inline void setTexturePaths( std::map<int, std::string> r) {
mTexturePaths = r;
}
private:
/**
* Merged records accross all mods for LTEX data
*/
std::map<int, std::string> mTexturePaths;
unsigned int mCount;
};

@ -1,75 +0,0 @@
/**
* @brief a class that holds a texture palette (basicly, an index accoicated with a texture)
*
* Unfortunaly, this uses a std::map class, which means that hasIndex is slow.
* A fix would be to use a class that has fast lookups for both key/value (both are keys).
* Or something better than that. Whatever.
*
* Yeah, so this is a bit like a std::map
*/
class TexturePalette {
public:
inline bool hasTexture(int index) {
return (mPalette.find(index) != mPalette.end());
}
/**
* Not a great function. Very slow :(
*/
inline bool hasIndex(const std::string& texture) {
for ( Palette::iterator i = mPalette.begin(); i != mPalette.end(); ++i) {
if ( i->second == texture )
return true;
}
return false;
}
inline int getIndex(const std::string& texture) {
for ( Palette::iterator i = mPalette.begin(); i != mPalette.end(); ++i) {
if ( i->second == texture )
return i->first;
}
return -1;
}
inline int getOrAddIndex(const std::string& texture) {
if ( hasIndex(texture) )
return getIndex(texture);
return addTexture(texture);
}
inline const std::string& getTexture(int index) {
return mPalette[index];
}
inline void setTexture(int index, std::string texture) {
mPalette[index] = texture;
}
/**
* @todo add proper error thing rather than assert(0)
*/
inline int addTexture(const std::string& texture) {
for ( int i = 0; i >= 0; i++ ) { //this loop is not infinate, as it will go to -2^31
if ( mPalette.find(i) != mPalette.end() )
continue;
mPalette[i] = texture;
return i;
}
assert(0); //this should never happen. Seeing as we can assign about 2^31 images
}
inline std::map<int, std::string>& getPalette() {
return mPalette;
}
private:
typedef std::map<int, std::string> Palette;
Palette mPalette;
friend class boost::serialization::access;
/**
* @brief saves the palette
*/
template<class Archive>
inline void serialize(Archive& ar, const unsigned int version){
ar &mPalette;
}
};
BOOST_CLASS_TRACKING(TexturePalette, boost::serialization::track_never);

@ -5,10 +5,6 @@
* 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
*
* needUnsplit and needSplit query the state of the meshes to see if
* it needs spliting or unspliting
*
*/
/* Previously for MeshInterface:
* Interface between the quad and the terrain renderble classes, to the
@ -17,477 +13,290 @@
*
* It also could allow several optimizations (e.g. multiple splits)
*/
class Quad
{
/**
* when each quad is split, the children can be one of 4 places,
* topleft (NW) top right (NE) etc etc. The other position is the
* root which is the top quad. The root quad doesn't have a mesh,
* and should always have 4 children.
*/
typedef std::list<TerrainMesh*> MeshList;
public:
// FIXME: There's probably a better way to do this
enum QuadLocation { QL_NW, QL_NE, QL_SW, QL_SE, QL_ROOT };
/**
* @param l the location of the quad
* @param p the parent quad. Leave 0 if it is root
* @param t the terrain object
*
* Constructor mainly sets up the position variables/depth etc
*/
Quad(QuadLocation l, Quad* p)
: mParent(p), mLocation(l), mQuadData(NULL)
Quad(int cellX=0, int cellY=0, Quad* parent = NULL)
: mCellX(cellX),
mCellY(cellY)
{
TRACE("Quad");
//as mentioned elsewhere, the children should all be null.
RTRACE("Quad");
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
//find the location of the quad
if ( l != Quad::QL_ROOT )
{ //if it isn't the root node
mDepth = p->getDepth() + 1;
mPosition = p->getPosition();
mSideLength = p->getSideLength()/2;
//horrible bit of code
// FIXME
switch (l) {
case Quad::QL_NE:
mPosition.x += mSideLength/2;
mPosition.y += mSideLength/2;
break;
case Quad::QL_NW:
mPosition.x -= mSideLength/2;
mPosition.y += mSideLength/2;
break;
case Quad::QL_SE:
mPosition.x += mSideLength/2;
mPosition.y -= mSideLength/2;
break;
case Quad::QL_SW:
mPosition.x -= mSideLength/2;
mPosition.y -= mSideLength/2;
break;
default:
break;
}
//set after positions have been retrived
mMaxDepth = g_heightMap->getMaxDepth();
mHasData = g_heightMap->hasData(mPosition.x, mPosition.y);
if ( needSplit() ) //need to "semi" build terrain
split();
else if ( mHasData )
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();
justSplit();
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
{ //assume it is root node, get data and position
mDepth = 0; //root
mSideLength = g_heightMap->getRootSideLength();
mPosition = Point2<long>(0,0);
{
// 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;
mHasData = false;
const Ogre::Vector3 pos(cellX * CELL_WIDTH,
cellY * CELL_WIDTH,
0);
mSceneNode = g_rootTerrainNode->
createChildSceneNode(pos);
//always split, as this node never has data
// Split up
split();
// The root can never be unsplit
isStatic = true;
}
}
/**
* Destroyes the terrain mesh OR all children.
* If it needs to do both, there is a bug, as that would lead to a terrain mesh existing
* On more than one level in the same space.
* assert checks that there is only one or the other
*/
~Quad()
{
TRACE("~Quad");
destroyTerrain();
for (size_t i = 0; i < NUM_CHILDREN; i++)
delete mChildren[i];
}
assert(mLevel >= 1);
assert(mSceneNode != NULL);
/**
* @return true if the node needs to be split.
*
* The issue with this is that at present this requires the mesh to
* be built to check. This is fine but it could lead to a lot more
* loading when teleporting
*/
bool needSplit()
{
TRACE("needSplit");
if ( hasChildren() ||
getDepth() == mMaxDepth ||
!hasData() )
return false;
return ( mQuadData && (mSplitState == SS_SPLIT) );
}
// Set up the bounding box. Use MW coordinates all the way
mBounds.setExtents(0,0,mInfo->minHeight,
mInfo->worldWidth,mInfo->worldWidth,
mInfo->maxHeight);
/**
* Deletes the landscape, if there is any
* Creates children, and either splits them, or creates landscape for them
*/
void split()
{
TRACE("split");
destroyTerrain();
// Transform the box to world coordinates, so it can be compared
// with the camera later.
mBounds.transformAffine(mSceneNode->_getFullTransform());
//create a new terrain
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
mChildren[i] = new Quad((QuadLocation)i, this);
const float radius = mInfo->boundingRadius;
assert(!needUnsplit());
}
mSplitDistance = radius * SPLIT_FACTOR;
mUnsplitDistance = radius * UNSPLIT_FACTOR;
/**
* @brief removes all children, and builds terrain on this level
*/
void unsplit()
{
TRACE("unsplit");
//shouldn't unsplit 0 depth
assert(getDepth());
for ( size_t i = 0; i < NUM_CHILDREN; i++ )
delete mChildren[i];
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
if ( mHasData )
{
buildTerrain();
justUnsplit();
}
// Square the distances
mSplitDistance *= mSplitDistance;
mUnsplitDistance *= mUnsplitDistance;
assert(!needSplit());
// Update the terrain. This will create the mesh or children if
// necessary.
update();
}
/**
* @return true if the node needs to delete all its child nodes, and
* rebuild the terrain its level
*/
bool needUnsplit()
~Quad()
{
TRACE("needUnsplit");
if ( hasChildren() && getDepth() )
{
for (size_t i=0;i< NUM_CHILDREN;i++)
{
if ( mChildren[i]->hasData() )
{
if ( !mChildren[i]->hasMesh() )
return false;
else if ( mChildren[i]->getSplitState() != SS_UNSPLIT)
return false;
}
}
return true;
}
//get depth ensures the root doesn't try and unsplit
if ( getDepth() && !hasData() )
return true;
return false;
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);
}
/**
* @brief constructs the terrain on this level. The terrain must on
* exist before hand
*/
void buildTerrain()
// Remove the landscape for this quad, and create children.
void split()
{
TRACE("buildTerrain");
assert(!mQuadData);
assert(hasData());
// This was in MeshInterface().
mMax = 0;
mMin = 0;
mSplitState = SS_NONE;
RTRACE("split");
long qx = mPosition.x;
long qy = mPosition.y;
mQuadData = g_heightMap->getData(qx, qy);
// Never split a static quad or a quad that already has children.
assert(!isStatic);
assert(!hasChildren);
assert(mLevel > 1);
//the mesh is created at zero, so an offset is applied
const Ogre::Vector3 pos(qx - mSideLength/2,
0,qy - mSideLength/2);
if(hasMesh)
destroyTerrain();
mSceneNode = g_heightMap->getTerrainSceneNode()->createChildSceneNode(pos);
// Find the cell width of our children
int cWidth = 1 << (mLevel-2);
// This was in create()
// Create children
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
{
if(!mInfo->hasChild[i])
continue;
if ( mDepth == g_heightMap->getMaxDepth() )
for ( int y = 0; y < 4; ++y )
for ( int x = 0; x < 4; ++x )
{
addNewObject(Ogre::Vector3(x*16*128, 0, y*16*128), //pos
17, //size
false, //skirts
0.25f, float(x)/4.0f, float(y)/4.0f);//quad seg location
}
else
addNewObject(Ogre::Vector3(0,0,0), 65);
// The cell coordinates for this child quad
int x = (i%2)*cWidth + mCellX;
int y = (i/2)*cWidth + mCellY;
getBounds();
mChildren[i] = new Quad(x,y,this);
}
hasChildren = true;
}
/**
* @brief destroys the terrain.
*/
void destroyTerrain()
// Removes children and rebuilds terrain
void unsplit()
{
TRACE("destroyTerrain");
if(!mQuadData)
return;
RTRACE("unsplit");
// From ~MeshInterface()
for ( MeshList::iterator itr =
mMeshList.begin();
itr != mMeshList.end();
++itr )
delete *itr;
mMeshList.clear();
// 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);
mSceneNode->removeAndDestroyAllChildren();
mSceneMgr->destroySceneNode(mSceneNode);
for( size_t i = 0; i < NUM_CHILDREN; i++ )
{
delete mChildren[i];
mChildren[i] = NULL;
}
g_heightMap->getTerrainSceneNode()->detachAllObjects();
buildTerrain();
delete mQuadData;
mQuadData = NULL;
hasChildren = false;
}
/**
* @brief gets the position in relation to the root (always 0,0)
* @return the position as a long in a container holding the .x and .y vals
*
* This is called form the subnodes of this node, and the TerrainRenderable to work out what needs positiong where
* This is a long (as opposed to a float) so it can be used in comparisons
* @todo typedef this, so it can be changed to int or long long (long64)
*
* The roots position is always 0. This is as the roots position is totally independent of the position
* that the terrain is rendered, as you can just move the root terrain node.
* In other words, it makes everything simpler
*
* The position of the quad is always taken from the center of the quad. Therefore, a top left quads location can be
* defined as:
* xpos = parent x pos - sidelength/2
* ypos = parent y pos + sidelength/2
*
* Where the side length is half the parents side length.
* The calcs are all handled in the consturctor (Quad::Quad)
*/
inline Point2<long> getPosition() const{ return mPosition; }
/**
* @return the location of the quad. E.g. North West
*/
inline QuadLocation getLocation() const{ return mLocation; }
/**
* @brief simply the length of one side of the current quad.
* @return the side length of the current quad
*
* As all quads are square, all sides are this length.
* Obviously this has to fit in sizeof(long)
*/
inline long getSideLength() const{ return mSideLength;}
/**
* @brief The depth is how many splits have taken place since the root node.
* @return the depth of the current quad
*
* The root node has a depth 0. As this is the case, all of its children will have
* a depth of one. Simply depth = parent depth + 1
*
* Set in the consturctor (Quad::Quad)
*/
inline int getDepth() const{return mDepth;}
// Determines whether to split or unsplit the quad, and immediately
// does it.
void update()
{
RTRACE("Quad::update");
/**
* @return true if their is a terrain mesh alocated
*/
inline bool hasMesh() const{ return mQuadData; }
// Static quads don't change
if(isStatic)
return;
/**
* @return true if there are any children
*/
inline bool hasChildren() const { return mChildren[0] != 0; }
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();
/**
* @brief checks if the quad has any data (i.e. a mesh avaible for rendering
*/
inline bool hasData() const{ return mHasData; }
return;
}
}
// If we get here, we either had children when we entered, or we
// just performed a split.
assert(!hasMesh);
assert(hasChildren);
/**
* @brief updates all meshes.
* @remarks the camera distance is calculated here so that all terrain has the correct morph levels etc
*/
void update(Ogre::Real time)
{
TRACE("Quad::update");
if ( needSplit() )
{
split();
return;
}
else if ( needUnsplit() )
// If the camera is too far away, kill the children.
if( camDist > mUnsplitDistance )
{
unsplit();
return;
}
//deal with updating the mesh.
if ( !mQuadData )
// We have children and we're happy about it. Update them too.
for (size_t i = 0; i < NUM_CHILDREN; ++i)
{
// We don't have a mesh
if ( hasChildren() )
{
for (size_t i = 0; i < NUM_CHILDREN; ++i) {
assert( mChildren[i] );
mChildren[i]->update(time);
}
}
return;
Quad *q = mChildren[i];
if(q != NULL) q->update();
}
}
// We have a mesh. Update it.
const Ogre::Vector3 cpos = mCamera->getDerivedPosition();
Ogre::Vector3 diff(0, 0, 0);
//copy?
Ogre::AxisAlignedBox worldBounds = mBounds;
worldBounds.transformAffine(mSceneNode->_getFullTransform());
diff.makeFloor(cpos - worldBounds.getMinimum() );
diff.makeCeil(cpos - worldBounds.getMaximum() );
const Ogre::Real camDist = diff.squaredLength();
// Build the terrain for this quad
void buildTerrain()
{
RTRACE("buildTerrain");
assert(!hasMesh);
assert(!isStatic);
mSplitState = SS_NONE;
if ( camDist < mSplitDistance ) mSplitState = SS_SPLIT;
else if ( camDist > mUnsplitDistance ) mSplitState = SS_UNSPLIT;
// Map the terrain data into memory.
g_archive.mapQuad(mInfo);
for ( MeshList::iterator itr = mMeshList.begin();
itr != mMeshList.end();
++itr )
{
assert(*itr);
(*itr)->update(time, camDist, mUnsplitDistance, mMorphDistance);
}
// 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));
}
inline SplitState getSplitState() {
return mSplitState;
}
/**
* @brief propergates the just split through all terrain
*/
inline void justSplit() {
for ( MeshList::iterator itr = mMeshList.begin();
itr != mMeshList.end();
++itr )
(*itr)->justSplit();
}
/**
* @brief propergates the just unsplit through all terrain
* @brief destroys the terrain.
*/
inline void justUnsplit() {
void destroyTerrain()
{
RTRACE("destroyTerrain");
assert(hasMesh);
for ( MeshList::iterator itr = mMeshList.begin();
itr != mMeshList.end();
++itr )
(*itr)->justUnsplit();
itr != mMeshList.end(); ++itr )
delete *itr;
mMeshList.clear();
}
private:
///Must be a ptr, else it destorys before we are ready
// List of meshes, if any
MeshList mMeshList;
Ogre::SceneNode* mSceneNode;
// Scene node. All child quads are added to this.
SceneNode* mSceneNode;
///use for split distances
Ogre::Real mBoundingRadius;
// Bounding box, transformed to world coordinates. Used to calculate
// camera distance.
Ogre::AxisAlignedBox mBounds;
///max and min heights
float mMax, mMin;
Ogre::Real mSplitDistance,mUnsplitDistance,mMorphDistance;
SplitState mSplitState;
QuadData* mQuadData;
/**
* @brief sets the bounds and split radius of the object
*/
void getBounds()
{
mBounds.setExtents( 0,
mMin,
0,
(65 - 1) * mQuadData->getVertexSeperation(),
mMax,
(65 - 1) * mQuadData->getVertexSeperation());
mBoundingRadius = (mBounds.getMaximum() - mBounds.getMinimum()).length() / 2;
mSplitDistance = pow(mBoundingRadius * SPLIT_FACTOR, 2);
mUnsplitDistance = pow(mBoundingRadius * UNSPLIT_FACTOR, 2);
mMorphDistance = pow(mBoundingRadius * 1.5, 2);
}
/**
* @brief Adds a new mesh
* @param pos the position in relation to mSceneNode
* @param terrainSize the size of the terrain in verts. Should be n^2+1
* @param skirts true if the terrain should have skirts
* @param segmentSize the size of the segment. So if splitting terrain into 4*4, it should be 0.25
* @param startX, startY the start position of this segment (0 <= startX < 1)
*/
void addNewObject(const Ogre::Vector3& pos, int terrainSize,
bool skirts = true, float segmentSize = 1,
float startX = 0, float startY = 0 )
{
assert(mQuadData);
TerrainMesh *tm = new TerrainMesh(mQuadData, segmentSize,
startX, startY, pos,
terrainSize, mDepth, skirts,
mSceneNode);
mMax = std::max(tm->getMax(), mMax);
mMin = std::max(tm->getMin(), mMin);
mMeshList.push_back(tm);
}
Ogre::Real mSplitDistance,mUnsplitDistance;
static const size_t NUM_CHILDREN = 4;
Quad* mParent; /// this is the node above this. 0 if this is root
Quad* mChildren[NUM_CHILDREN]; ///optionaly the children. Should be
///0 if not exist
Quad::QuadLocation mLocation; ///the location within the quad (ne, se, nw, sw). See Quad::QuadLocation
Point2<long> mPosition; ///the center of the mesh. this is a long so can be used as comparison. See Quad::getPosition
long mSideLength; ///the length in units of one side of the quad. See Quad::getSideLength
int mDepth; ///depth of the node. See Quad::getDepth for more info
// Contains the 'level' of this node. Level 1 is the closest and
// most detailed level
int mLevel;
int mCellX, mCellY;
QuadInfo *mInfo;
bool mHasData; ///holds if there is terrain data about this quad
int mMaxDepth; ///the maxmium depth. Cached. This is not valid is mDepth == 0
bool hasMesh;
bool hasChildren;
bool isStatic; // Static quads are never split or unsplit
};

@ -1,129 +0,0 @@
/**
* holds data that is passed to the mesh renderer. heights normals etc
*
* This needs a rework, as really the mesh renderer should accept just
* a set of verts Normals and indicies to allow us to pass optimized
* meshes
*/
enum SplitState { SS_NONE, SS_SPLIT, SS_UNSPLIT };
class QuadData
{
typedef std::list<Ogre::ResourcePtr> ResourceList;
typedef std::list<Ogre::ResourcePtr>::const_iterator ResourceListCItr;
public:
virtual ~QuadData()
{
const ResourceListCItr end = mResources.end();
for ( ResourceListCItr itr = mResources.begin(); itr != end; ++itr )
(*itr)->getCreator()->remove((*itr)->getHandle());
}
/**
* How many vertes wide the qd is. Usally 65.
* @todo cache?
*/
inline int getVertexWidth() {
return sqrt(getHeightsRef().size());
}
inline std::vector<float>& getHeightsRef() {
return mHeights;
}
inline float getVertex(int offset){
return getHeightsRef().at(offset);
}
inline std::vector<char>& getNormalsRef() {
return mNormals;
}
inline float getNormal(int offset){
return getNormalsRef().at(offset);
}
inline ResourceList& getUsedResourcesRef()
{ return mResources; }
inline void setTexture(const std::string& t)
{ mTexture = t; }
inline std::string& getTexture()
{ return mTexture; }
inline std::vector<int>& getTextureIndexRef()
{ return mTextureIndex; }
/**
* @brief should be removed when we get around to developing optimized meshes
*/
inline int getVertexSeperation() {
return mVertexSeperation;
}
inline void setVertexSeperation(int vs) {
mVertexSeperation = vs;
}
/**
* @brief lazy get function. Avoids creating material until requested
*/
inline Ogre::MaterialPtr getMaterial() {
if ( mMaterial.isNull() )
createMaterial();
assert(!mMaterial.isNull());
return mMaterial;
}
/**
* @brief gets the texture for the above quad
*/
inline const std::string& getParentTexture() const {
return mParentTexture;
}
inline bool hasParentTexture() const {
return (mParentTexture.length() > 0);
}
inline void setParentTexture(const std::string& c) {
mParentTexture = c;
}
private:
void createMaterial()
{
assert(mTexture.length());
mMaterial = g_materialGen->generateSingleTexture(mTexture, mResources);
}
Ogre::MaterialPtr mMaterial;
std::string mParentTexture;
int mVertexSeperation;
std::vector<float> mHeights;
std::vector<char> mNormals;
///Holds the resources used by the quad
ResourceList mResources;
std::vector<int> mTextureIndex; ///holds index that correspond to the palette
std::string mTexture; ///The land texture. Mutally exclusive with the above
friend class boost::serialization::access;
/**
* Saves the data for the heights, noramals and texture indexies.
* Texture as well
*/
template<class Archive>
inline void serialize(Archive& ar, const unsigned int version){
ar &mVertexSeperation;
ar &mHeights;
ar &mNormals;
ar &mParentTexture;
ar &mTextureIndex;
ar &mTexture;
}
};
BOOST_CLASS_TRACKING(QuadData, boost::serialization::track_never);

@ -20,14 +20,6 @@
*/
// Shader names
#define MORPH_VERTEX_PROGRAM "mw_terrain_VS"
#define FADE_FRAGMENT_PROGRAM "mw_terrain_texfade_FP"
// Directories
#define TEXTURE_OUTPUT "cache/terrain/"
#define TERRAIN_OUTPUT "cache/terrain/landscape"
///no texture assigned
const int LAND_LTEX_NONE = 0;
@ -43,8 +35,14 @@ 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;
// Can be used to turn of landscape data generation
#define GEN_LANDDATA 1
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
@ -56,17 +54,18 @@ const float UNSPLIT_FACTOR = 2.0;
#define ENABLED_CRASHING 0
class Quad;
class QuadData;
class MaterialGenerator;
class HeightMap;
class TerrainMesh;
class BaseLand;
HeightMap *g_heightMap;
Quad *g_rootQuad;
MaterialGenerator *g_materialGen;
// Cache directory and file
std::string g_cacheDir;
std::string g_cacheFile;
#undef TRACE
#define TRACE(x)
// 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>
@ -75,61 +74,82 @@ MaterialGenerator *g_materialGen;
#include <string>
#include <list>
#include <algorithm>
#include <boost/shared_ptr.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
// 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_materialgen.cpp"
#include "cpp_esm.cpp"
#include "cpp_landdata.cpp"
#include "cpp_quaddata.cpp"
#include "cpp_index.cpp"
#include "cpp_palette.cpp"
#include "cpp_point2.cpp"
#include "cpp_generator.cpp"
// For rendering
Quad *g_rootQuad;
BaseLand *g_baseLand;
SceneNode *g_rootTerrainNode;
#include "cpp_baseland.cpp"
#include "cpp_heightmap.cpp"
#include "cpp_terrainmesh.cpp"
#include "cpp_quad.cpp"
#include "cpp_framelistener.cpp"
TerrainFrameListener terrainListener;
class TerrainFrameListener : public FrameListener
{
protected:
bool frameEnded(const FrameEvent& evt)
{
g_rootQuad->update();
g_baseLand->update();
return true;
}
};
extern "C" void d_superman();
extern "C" void terr_setCacheDir(char *cacheDir)
{
g_cacheDir = cacheDir;
g_cacheFile = g_cacheDir + "terrain.cache";
}
// Set up the rendering system
extern "C" void terr_setupRendering()
{
if(!g_materialGen)
g_materialGen = new MaterialGenerator;
// Add the terrain directory
ResourceGroupManager::getSingleton().
addResourceLocation(TEXTURE_OUTPUT, "FileSystem", "General");
addResourceLocation(g_cacheDir, "FileSystem", "General");
// Create a root scene node first. The 'root' node is rotated to
// match the MW coordinate system
g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT");
// Open the archive file
g_archive.openFile(g_cacheFile);
// Set up the terrain frame listener
terrainListener.setup();
// Create the root quad.
g_rootQuad = new Quad();
g_baseLand = new BaseLand(g_rootTerrainNode);
// Add the frame listener
mRoot->addFrameListener(new TerrainFrameListener);
// Enter superman mode
mCamera->setFarClipDistance(32*8192);
mCamera->setFarClipDistance(32*CELL_WIDTH);
//ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
d_superman();
}
// Generate all cached data.
extern "C" void terr_genData()
{
if(!g_materialGen)
g_materialGen = new MaterialGenerator;
Ogre::Root::getSingleton().renderOneFrame();
Generator mhm(TERRAIN_OUTPUT);
Generator mhm;
{
ESM esm;
@ -139,23 +159,14 @@ extern "C" void terr_genData()
esm.addRecordType("LTEX", "INTV");
esm.loadFile(fn);
RecordListPtr land = esm.getRecordsByType("LAND");
RecordList* land = esm.getRecordsByType("LAND");
for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr )
mhm.addLandData(*itr, fn);
RecordListPtr ltex = esm.getRecordsByType("LTEX");
RecordList* ltex = esm.getRecordsByType("LTEX");
for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr )
mhm.addLandTextureData(*itr, fn);
}
mhm.beginGeneration();
mhm.generateLODLevel(6, 1024);
mhm.generateLODLevel(5, 512);
mhm.generateLODLevel(4, 256);
mhm.generateLODLevel(3, 256);
mhm.generateLODLevel(2, 256);
mhm.generateLODLevel(1, 128);
mhm.endGeneration();
mhm.generate(g_cacheFile);
}

@ -1,779 +1,275 @@
/**
* @brief Terrain for one cell. Handles building, destroying and LOD morphing
*/
#define QSDEBUG
// The Ogre renderable used to hold and display the terrain meshes.
class TerrainMesh : public Ogre::Renderable, public Ogre::MovableObject
{
public:
TerrainMesh(QuadData* qd, float segSize, float startX, float startY,
const Ogre::Vector3 &pos,
int width, int depth, bool skirts,
Ogre::SceneNode *parent)
TerrainMesh(int segNum, Ogre::SceneNode *parent)
: Ogre::Renderable(),
Ogre::MovableObject(),
mWidth(width),
mUseSkirts(skirts),
mDepth(depth),
mVertexes(0),
mIndices(0),
mLODMorphFactor(0),
mTextureFadeFactor(0),
mMin(30000),
mMax(-30000),
mExtraMorphAmount(0),
mHasFadePass(false),
mQuadData(qd),
mSegmentSize(segSize),
mX(startX),
mY(startY)
Ogre::MovableObject()
{
// From QuadSegment()
assert(qd);
assert(segSize>0&&segSize<=1);
assert(mY>=0&&mY<=1);
assert(mX>=0&&mY<=1);
#ifdef QSDEBUG
{
//check sizes
const float qw = mQuadData->getVertexWidth()-1;
const float fsw = qw*segSize;
const int isw = (int)fsw;
assert(fsw==isw);
}
#endif
using namespace Ogre;
// Get the mesh properties from the archive. The pointer is only
// valid for the duration of this function.
const MeshInfo &info = *g_archive.getMeshInfo(segNum);
// Split all this off into sub-functions again later when you're
// finished.
//precalc offsets, as getVertex/getNormal get called a lot (1000s of times)
computeOffsets();
// Use MW coordinates all the way
mBounds.setExtents(0,0,info.minHeight,
// was (mWidth-1) * vertexSeparation
info.worldWidth, info.worldWidth,
info.maxHeight);
// From Quad
node = parent->createChildSceneNode(pos);
mCenter = mBounds.getCenter();
mBoundingRadius = mBounds.getHalfSize().length();
// From create()
createVertexBuffer();
calculateVertexValues();
calculateIndexValues();
setBounds();
// TODO: VertexData has a clone() function. This probably means we
// can set this up once and then clone it, to get a completely
// unnoticable increase in performance :)
mVertices = new VertexData();
mVertices->vertexStart = 0;
mVertices->vertexCount = info.vertRows*info.vertCols;
node->attachObject(this);
VertexDeclaration* vertexDecl = mVertices->vertexDeclaration;
size_t currOffset = 0;
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
createMaterial();
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(0, currOffset, VET_FLOAT2,
VES_TEXTURE_COORDINATES, 0);
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
assert(vertexDecl->getVertexSize(0) == currOffset);
HardwareVertexBufferSharedPtr mMainBuffer;
mMainBuffer = HardwareBufferManager::getSingleton().createVertexBuffer
(
vertexDecl->getVertexSize(0), // size of one whole vertex
mVertices->vertexCount, // number of vertices
HardwareBuffer::HBU_STATIC_WRITE_ONLY, // usage
false); // no shadow buffer
// Bind the data
mVertices->vertexBufferBinding->setBinding(0, mMainBuffer);
// Fill the buffer
float* verts = static_cast<float*>
(mMainBuffer->lock(HardwareBuffer::HBL_DISCARD));
info.fillVertexBuffer(verts);
mMainBuffer->unlock();
if ( g_heightMap->isMorphingEnabled() &&
mDepth != g_heightMap->getMaxDepth() )
// Create the index data holder
mIndices = new IndexData();
mIndices->indexCount = info.indexCount;
mIndices->indexBuffer =
HardwareBufferManager::getSingleton().createIndexBuffer
( HardwareIndexBuffer::IT_16BIT,
info.indexCount,
HardwareBuffer::HBU_STATIC_WRITE_ONLY,
false);
// Fill the buffer with warm fuzzy archive data
unsigned short* indices = static_cast<unsigned short*>
(mIndices->indexBuffer->lock
(0, mIndices->indexBuffer->getSizeInBytes(),
HardwareBuffer::HBL_DISCARD));
info.fillIndexBuffer(indices);
mIndices->indexBuffer->unlock();
// Finally, create the material
const std::string texName = info.getTexName();
// TODO: A better thing to do here is to keep the material loaded
// and retrieve it if it exists.
assert(!MaterialManager::getSingleton().resourceExists(texName));
mMaterial = MaterialManager::getSingleton().create
(texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Pass* pass = mMaterial->getTechnique(0)->getPass(0);
pass->setLightingEnabled(false);
int lev = info.getLevel();
if(lev != 1)
{
Ogre::Technique* tech = getMaterial()->getTechnique(0);
for ( size_t i = 0; i < tech->getNumPasses(); ++i )
{
assert(g_heightMap->isMorphingEnabled());
tech->getPass(i)->setVertexProgram(MORPH_VERTEX_PROGRAM);
}
// This material just has a normal texture
pass->createTextureUnitState(texName)
//->setTextureAddressingMode(TextureUnitState::TAM_CLAMP)
;
}
else
{
// We have to use alpha splatting
float scale = info.getTexScale();
if ( g_heightMap->isMorphingEnabled() )
calculateDeltaValues();
}
// Get the background texture
const char *bgTex = info.getBackgroundTex();
pass->createTextureUnitState(bgTex)
->setTextureScale(scale,scale);
~TerrainMesh()
{
//deleting null values is fine iirc
delete mIndices;
int alphaSize = info.getAlphaSize();
# if ENABLED_CRASHING == 1
{
delete mVertexes;
}
# else
{
if ( mDepth != g_heightMap->getMaxDepth() ){
delete mVertexes;
// Loop through all the textures in this mesh
for(int tnum=0; tnum<info.alphaNum; tnum++)
{
const AlphaInfo &alpha = *info.getAlphaInfo(tnum);
// Name of the alpha map texture to create
std::string alphaName = alpha.getAlphaName();
// Name of the texture
std::string tname = alpha.getTexName();
// TODO: Need to store the result and either delete it in
// the destructor or fetch it again the next time we run.
Ogre::TexturePtr texPtr = Ogre::TextureManager::
getSingleton().createManual
(alphaName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D,
alphaSize,alphaSize,
1,0, // depth, mipmaps
Ogre::PF_A8, // One-channel alpha
Ogre::TU_STATIC_WRITE_ONLY);
// Get the pointer
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
// Copy alpha data from file
alpha.fillAlphaBuffer(pDest);
// Finish everything up with a lot of Ogre-code
pixelBuffer->unlock();
pass = mMaterial->getTechnique(0)->createPass();
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setLightingEnabled(false);
pass->setDepthFunction(Ogre::CMPF_EQUAL);
Ogre::TextureUnitState* tus = pass->createTextureUnitState(alphaName);
//tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
// Add the actual texture on top of the alpha map.
tus = pass->createTextureUnitState(tname);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(scale, scale);
}
}
}
# endif
assert(node);
node->detachAllObjects();
node->getCreator()->destroySceneNode(node);
// Finally, set up the scene node.
mNode = parent->createChildSceneNode(Vector3(info.x, info.y, 0.0));
mNode->attachObject(this);
}
/**
* @brief Checks if it needs to be split or unsplit and deals with
* the morph factor. time seconds since last frame
*/
void update(Ogre::Real time, Ogre::Real camDist, Ogre::Real usplitDist, Ogre::Real morphDist)
~TerrainMesh()
{
TRACE("TerrainMesh::update");
//if ( USE_MORPH ){
delete mIndices;
//as aprocesh mUnsplitDistance, lower detail
if ( camDist > morphDist && mDepth > 1 ) {
mLODMorphFactor = 1 - (usplitDist - camDist)/(usplitDist-morphDist);
} else
mLODMorphFactor = 0;
mTextureFadeFactor = mLODMorphFactor;
// TODO: This used to crash. See what happens now.
delete mVertices;
assert(mNode);
//on an split, it sets the extra morph amount to 1, we then ensure this ends up at 0... slowly
if ( mExtraMorphAmount > 0 ) {
mLODMorphFactor += mExtraMorphAmount;
mExtraMorphAmount -= (time/g_heightMap->getMorphSpeed()); //decrease slowly
}
if ( mExtraFadeAmount > 0 ) {
mTextureFadeFactor += mExtraFadeAmount;
mExtraFadeAmount -= (time/g_heightMap->getTextureFadeSpeed());
}
//Ensure within valid bounds
if ( mLODMorphFactor > 1 )
mLODMorphFactor = 1;
else if ( mLODMorphFactor < 0 )
mLODMorphFactor = 0;
if ( mTextureFadeFactor > 1 )
mTextureFadeFactor = 1;
else if ( mTextureFadeFactor < 0 )
mTextureFadeFactor = 0;
//}
//remove pass. Keep outside in case terrain fading is removed while it is active
if ( mHasFadePass && mTextureFadeFactor == 0 ) {
removeFadePass();
} else if ( g_heightMap->isTextureFadingEnabled() &&
!mHasFadePass &&
mTextureFadeFactor > 0 &&
hasParentTexture() ) {
addFadePass();
}
// We haven't tried moving this further up - there's an off chance
// it might have something to do with the crash.
mNode->detachAllObjects();
mNode->getCreator()->destroySceneNode(mNode);
}
//-----------------------------------------------------------------------
// These are all Ogre functions that we have to override
//-----------------------------------------------------------------------
/**
* @todo Needs to work out what this does (if it does what it is meant to)
*/
// Internal Ogre function. We should call visitor->visit on all
// Renderables that are part of this object. In our case, this is
// only ourselves.
void visitRenderables(Renderable::Visitor* visitor,
bool debugRenderables = false) {
visitor->visit(this, 0, false);
}
virtual const Ogre::MaterialPtr& getMaterial( void ) const {
return mMaterial;
}
//-----------------------------------------------------------------------
void getRenderOperation( Ogre::RenderOperation& op ) {
op.useIndexes = true;
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
op.vertexData = mVertexes;
op.vertexData = mVertices;
op.indexData = mIndices;
}
//-----------------------------------------------------------------------
void getWorldTransforms( Ogre::Matrix4* xform ) const {
*xform = mParentNode->_getFullTransform();
*xform = mNode->_getFullTransform();
}
//-----------------------------------------------------------------------
const Ogre::Quaternion& getWorldOrientation(void) const {
return mParentNode->_getDerivedOrientation();
return mNode->_getDerivedOrientation();
}
//-----------------------------------------------------------------------
const Ogre::Vector3& getWorldPosition(void) const {
return mParentNode->_getDerivedPosition();
return mNode->_getDerivedPosition();
}
//-----------------------------------------------------------------------
Ogre::Real getSquaredViewDepth(const Ogre::Camera *cam) const {
Ogre::Vector3 diff = mCenter - cam->getDerivedPosition();
// Use squared length to avoid square root
return diff.squaredLength();
}
//-----------------------------------------------------------------------
const Ogre::LightList& getLights(void) const {
if (mLightListDirty) {
getParentSceneNode()->getCreator()->_populateLightList(
mCenter, this->getBoundingRadius(), mLightList);
getParentSceneNode()->getCreator()->_populateLightList
(mCenter, mBoundingRadius, mLightList);
mLightListDirty = false;
}
return mLightList;
}
//-----------------------------------------------------------------------
virtual const Ogre::String& getMovableType( void ) const {
static Ogre::String t = "MW_TERRAIN";
return t;
}
//-----------------------------------------------------------------------
void _updateRenderQueue( Ogre::RenderQueue* queue ) {
mLightListDirty = true;
queue->addRenderable(this, mRenderQueueID);
}
const Ogre::AxisAlignedBox& getBoundingBox( void ) const {
return mBounds;
};
//-----------------------------------------------------------------------
Ogre::Real getBoundingRadius(void) const {
return mBoundingRadius;
}
//-----------------------------------------------------------------------
/**
* @brief passes the morph factor to the custom vertex program
*/
void _updateCustomGpuParameter(const Ogre::GpuProgramParameters::AutoConstantEntry& constantEntry,
Ogre::GpuProgramParameters* params) const
{
using namespace Ogre;
if (constantEntry.data == MORPH_CUSTOM_PARAM_ID)
params->_writeRawConstant(constantEntry.physicalIndex, mLODMorphFactor);
else if ( constantEntry.data == FADE_CUSTOM_PARAM_ID )
params->_writeRawConstant(constantEntry.physicalIndex, mTextureFadeFactor);
else
Renderable::_updateCustomGpuParameter(constantEntry, params);
}
/**
* @brief sets the mExtraMorphAmount so it slowly regains detail from the lowest morph factor
*/
void justSplit()
{
mExtraMorphAmount = 1;
mLODMorphFactor = 1;
mTextureFadeFactor = 1;
mExtraFadeAmount = 1;
if ( g_heightMap->isTextureFadingEnabled() && hasParentTexture() )
addFadePass();
}
virtual const MaterialPtr& getMaterial(void) const
{ return mMaterial; }
/**
* @brief Does nothing
*/
inline void justUnsplit(){}
inline float getMax(){
return mMax;
}
inline float getMin(){
return mMin;
}
/**
* Gets how many vertexes wide this quad segment is. Should always be 2^n+1
* @return the vertex width of this quad segment
*/
int getVertexWidth()
{
return (mQuadData->getVertexWidth()-1)*mSegmentSize+1;
}
/**
* @brief gets a vertex assuming that x = 0, y = 0 addresses the start of the quad
*/
float getVertexHeight(int x, int y)
{
#ifdef QSDEBUG
{
const int vw = getVertexWidth();
assert(x<vw);
}
#endif
return mQuadData->getVertex((mYOffset + y)*mQuadData->getVertexWidth()+(mXOffset+x));
}
float getNormal(int x, int y, int z)
{
assert(z>=0&&z<3);
return mQuadData->getNormal(((mYOffset + y)*mQuadData->getVertexWidth()+(mXOffset+x))*3+z);
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
private:
void createMaterial()
{
assert(mSegmentSize>0);
if ( mSegmentSize == 1 ) //we can use the top level material
{
mMaterial = mQuadData->getMaterial();
return;
}
if ( mQuadData->getTexture().length() )
assert(0&&"NOT IMPLEMENTED");
const std::vector<int>& tref = mQuadData->getTextureIndexRef();
const int indexSize = sqrt(tref.size());
const int cellIndexSize = indexSize - 2;
//plus 1 to take into account border
const int xoff = float(cellIndexSize) * mX;
const int yoff = float(cellIndexSize) * mY;
const int size = float(cellIndexSize) * mSegmentSize;
std::vector<int> ti;
/*
ti.resize((size+2)*(size+2), -1);
for ( int y = 0; y < size+2; ++y ){
for ( int x = 0; x < size+2; ++x ){
ti[(y)*(size+2)+(x)] = tref.at((y+yoff)*(indexSize)+(x+xoff));
}
}
*/
ti.resize(size*size, -1);
for ( int y = 0; y < size; ++y ){
for ( int x = 0; x < size; ++x ){
ti[y*size+x] = tref.at((1+y+yoff)*(indexSize)+(1+x+xoff));
}
}
mMaterial = g_materialGen->getAlphaMat
(ti,size,
1.0f/size,
mQuadData->getUsedResourcesRef());
}
inline bool hasParentTexture() const{
return mQuadData->hasParentTexture();
}
inline const std::string& getParentTexture() const{
return mQuadData->getParentTexture();
}
inline int getVertexSeperation(){
return mQuadData->getVertexSeperation();
}
inline float getSegmentSize(){
return mSegmentSize;
}
inline float getStartX(){
return mX;
}
inline float getStartY(){
return mY;
}
/**
* @brief Adds another pass to the material to fade in/out the material from a higher level
*/
void addFadePass()
{
assert(mHasFadePass==false);
if ( mDepth == g_heightMap->getMaxDepth() ) return;
mHasFadePass = true;
Ogre::MaterialPtr mat = getMaterial();
Ogre::Pass* newPass = mat->getTechnique(0)->createPass();
newPass->setSceneBlending(Ogre::SBF_SOURCE_ALPHA, Ogre::SBF_ONE_MINUS_SOURCE_ALPHA);
//set fragment program
assert(g_heightMap->isTextureFadingEnabled());
newPass->setFragmentProgram(FADE_FRAGMENT_PROGRAM);
if ( g_heightMap->isMorphingEnabled() && mDepth != g_heightMap->getMaxDepth() ) {
assert(g_heightMap->isMorphingEnabled());
newPass->setVertexProgram(MORPH_VERTEX_PROGRAM);
}
//set texture to be used
newPass->createTextureUnitState(getParentTexture(), 1);
}
/**
* @brief removes the last pass from the material. Assumed to be the fade pass
*/
void removeFadePass()
{
assert(mHasFadePass==true);
mHasFadePass = false;
Ogre::MaterialPtr mat = getMaterial();
Ogre::Technique* tech = mat->getTechnique(0);
tech->removePass(tech->getNumPasses()-1);
}
/**
* Inits the vertex stuff
*/
void createVertexBuffer()
{
using namespace Ogre;
size_t vw = mWidth;
if ( mUseSkirts ) vw += 2;
mVertexes = new VertexData();
mVertexes->vertexStart = 0;
mVertexes->vertexCount = vw*vw;// VERTEX_WIDTH;
VertexDeclaration* vertexDecl = mVertexes->vertexDeclaration;
size_t currOffset = 0;
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT3, VES_POSITION);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT3, VES_NORMAL);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
if ( g_heightMap->isTextureFadingEnabled() ) {
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 1);
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
}
mMainBuffer = HardwareBufferManager::getSingleton().createVertexBuffer(
vertexDecl->getVertexSize(0), // size of one whole vertex
mVertexes->vertexCount, // number of vertices
HardwareBuffer::HBU_STATIC_WRITE_ONLY, // usage
false); // no shadow buffer
mVertexes->vertexBufferBinding->setBinding(MAIN_BINDING, mMainBuffer); //bind the data
if ( g_heightMap->isMorphingEnabled() )
vertexDecl->addElement(DELTA_BINDING, 0, VET_FLOAT1, VES_BLEND_WEIGHTS);
}
/**
* @brief fills the vertex buffer with data
* @todo I don't think tex co-ords are right
*/
void calculateVertexValues()
{
//get the texture offsets for the higher uv
float xUVOffset = 0;
float yUVOffset = 0;
if ( g_heightMap->isTextureFadingEnabled() ) {
assert(0);
}
int start = 0;
int end = mWidth;
if ( mUseSkirts )
{
--start;
++end;
}
float* verts = static_cast<float*>(mMainBuffer->lock(HardwareBuffer::HBL_DISCARD));
for ( int y = start; y < end; y++ ) {
for ( int x = start; x < end; x++ ) {
//the skirts
if ( y < 0 || y > (mWidth-1) || x < 0 || x > (mWidth-1) ) {
if ( x < 0 ) *verts++ = 0;
else if ( x > (mWidth-1) ) *verts++ = (mWidth-1)*getVertexSeperation();
else *verts++ = x*getVertexSeperation();
*verts++ = -4096; //2048 below base sea floor height
if ( y < 0 ) *verts++ = 0;
else if ( y > (mWidth-1) ) *verts++ = (mWidth-1)*getVertexSeperation();
else *verts++ = y*getVertexSeperation();
for ( Ogre::uint i = 0; i < 3; i++ )
*verts++ = 0;
float u = (float)(x) / (mWidth-1);
float v = (float)(y) / (mWidth-1);
//clamped, so shouldn't matter if > 1
*verts++ = u;
*verts++ = v;
if ( g_heightMap->isTextureFadingEnabled() ) {
*verts++ = u;
*verts++ = v;
}
} else {
assert(y*mWidth+x>=0&&y*mWidth+x<mWidth*mWidth);
//verts
*verts++ = x*getVertexSeperation();
*verts++ = getVertexHeight(x,y);
*verts++ = y*getVertexSeperation();
mMax = std::max(mMax, getVertexHeight(x,y));
mMin = std::min(mMin, getVertexHeight(x,y));
//normals
for ( Ogre::uint i = 0; i < 3; i++ )
*verts++ = getNormal(x,y,i);
//*verts++ = mInterface->getNormal((y*mWidth+x)*3+i);
const float u = (float)(x) / (mWidth-1);
const float v = (float)(y) / (mWidth-1);
assert(u>=0&&v>=0);
assert(u<=1&&v<=1);
*verts++ = u;
*verts++ = v;
if ( g_heightMap->isTextureFadingEnabled() ) {
*verts++ = u/2.0f + xUVOffset;
*verts++ = v/2.0f + yUVOffset;
}
}
}
}
mMainBuffer->unlock();
}
/**
* @brief returns a a new Vertex Buffer ready for input
* @remarks Unlike other terrain libs, this isn't 0s when it is returend
*/
Ogre::HardwareVertexBufferSharedPtr createDeltaBuffer()
{
size_t vw = mWidth;
if ( mUseSkirts ) vw += 2;
const size_t totalVerts = (vw * vw);
return Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT1),
totalVerts,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,
false); //no shadow buff
}
/**
* @brief DOESN'T WORK FULLY
* @todo fix
*/
#define SET_DELTA_AT(x, y, v) \
if ( mUseSkirts ) pDeltas[( y + 1)*vw+ x + 1] = v; \
else pDeltas[( y)*vw+ x] = v;
void calculateDeltaValues()
{
assert(0);
/*
size_t vw = mWidth;
if ( mUseSkirts ) vw += 2;
//must be using morphing to use this function
assert(g_heightMap->isMorphingEnabled());
const size_t step = 2;
// Create a set of delta values
mDeltaBuffer = createDeltaBuffer();
float* pDeltas = static_cast<float*>(mDeltaBuffer->lock(HardwareBuffer::HBL_DISCARD));
memset(pDeltas, 0, (vw)*(vw) * sizeof(float));
return;
bool flag=false;
for ( size_t y = 0; y < mWidth-step; y += step ) {
for ( size_t x = 0; x < mWidth-step; x += step ) {
//create the diffrence between the full vertex if the vertex wasn't their
float bottom_left = getVertexHeight(x,y);
float bottom_right = getVertexHeight(x+step,y);
float top_left = getVertexHeight(x,y+step);
float top_right = getVertexHeight(x+step,y+step);
//const int vw = mWidth+2;
SET_DELTA_AT(x, y+1, (bottom_left+top_left)/2 - getVertexHeight(x, y+1)) //left
SET_DELTA_AT(x+2, y+1, (bottom_right+top_right)/2 - getVertexHeight(x+2, y+1)) //right
SET_DELTA_AT(x+1, y+2, (top_right+top_left)/2 - getVertexHeight(x+1, y+2)) //top
SET_DELTA_AT(x+1, y, (bottom_right+bottom_left)/2 - getVertexHeight(x+1, y)) //bottom
//this might not be correct
if ( !flag )
SET_DELTA_AT(x+1, y+1, (bottom_left+top_right)/2 - getVertexHeight(x+1, y+1)) //center
else
SET_DELTA_AT(x+1, y+1, (bottom_right+top_left)/2 - getVertexHeight(x+1, y+1)) //center
flag = !flag;
}
flag = !flag; //flip tries for next row
}
mDeltaBuffer->unlock();
mVertexes->vertexBufferBinding->setBinding(DELTA_BINDING,mDeltaBuffer);
*/
}
#undef SET_DELTA_AT
/**
* @brief gets the position of a vertex. It will not interpolate
*/
Ogre::Vector3 getVertexPosition(int x, int y)
{
return Ogre::Vector3(x*getVertexSeperation(), getVertexHeight(x,y) , y*getVertexSeperation());
}
/**
* @brief gets the indicies for the vertex data.
*/
void calculateIndexValues()
{
size_t vw = mWidth-1;
if ( mUseSkirts ) vw += 2;
const size_t indexCount = (vw)*(vw)*6;
//need to manage allocation if not null
assert(mIndices==0);
mIndices = new IndexData();
mIndices->indexCount = indexCount;
mIndices->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(
HardwareIndexBuffer::IT_16BIT,
indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
unsigned short* indices = static_cast<unsigned short*>(mIndices->indexBuffer->lock(0,
mIndices->indexBuffer->getSizeInBytes(),
HardwareBuffer::HBL_DISCARD));
bool flag = false;
Ogre::uint indNum = 0;
for ( Ogre::uint y = 0; y < (vw); y+=1 ) {
for ( Ogre::uint x = 0; x < (vw); x+=1 ) {
const int line1 = y * (vw+1) + x;
const int line2 = (y + 1) * (vw+1) + x;
if ( flag ) {
*indices++ = line1;
*indices++ = line2;
*indices++ = line1 + 1;
*indices++ = line1 + 1;
*indices++ = line2;
*indices++ = line2 + 1;
} else {
*indices++ = line1;
*indices++ = line2;
*indices++ = line2 + 1;
*indices++ = line1;
*indices++ = line2 + 1;
*indices++ = line1 + 1;
}
flag = !flag; //flip tris for next time
indNum+=6;
}
flag = !flag; //flip tries for next row
}
assert(indNum==indexCount);
mIndices->indexBuffer->unlock();
//return mIndices;
}
/*
* Sets the bounds on the renderable. This requires that mMin/mMax
* have been set. They are set in createVertexData. This may look
* as though it is done twice, as it is also done in MeshInterface,
* however, there is no guarentee that the mesh sizes are the same
*/
void setBounds()
{
mBounds.setExtents(0,mMin,0,
(mWidth - 1) * getVertexSeperation(),
mMax,
(mWidth - 1) * getVertexSeperation());
mCenter = Ogre::Vector3( ( (mWidth - 1) * getVertexSeperation() ) / 2,
( mMin + mMax ) / 2,
( (mWidth - 1) * getVertexSeperation() ) / 2);
mBoundingRadius = (mBounds.getMaximum() - mBounds.getMinimum()).length() / 2;
}
Ogre::SceneNode* node;
const int mWidth;
const bool mUseSkirts;
int mDepth;
Ogre::SceneNode* mNode;
Ogre::MaterialPtr mMaterial;
Ogre::ManualObject* mObject;
Ogre::VertexData* mVertexes;
Ogre::VertexData* mVertices;
Ogre::IndexData* mIndices;
Ogre::HardwareVertexBufferSharedPtr mMainBuffer;
Ogre::HardwareVertexBufferSharedPtr mDeltaBuffer;
mutable bool mLightListDirty;
Ogre::Real mBoundingRadius;
Ogre::AxisAlignedBox mBounds;
Ogre::Vector3 mCenter;
Ogre::Real mLODMorphFactor, mTextureFadeFactor;
/** Max and min heights of the mesh */
float mMin, mMax;
int computeOffset(float x)
{
#ifdef QSDEBUG
{
//check it is a valid position
const int start = (mQuadData->getVertexWidth()-1)*x;
const int vw = getVertexWidth()-1;
assert(vw>0);
assert((start%vw)==0);
}
#endif
return float((mQuadData->getVertexWidth()-1))*x;
}
void computeOffsets()
{
mXOffset = computeOffset(mX);
mYOffset = computeOffset(mY);
}
QuadData* mQuadData;
float mSegmentSize;
float mX, mY;
int mXOffset, mYOffset;
/**
* @brief holds the amount to morph by, this decreases to 0 over a periord of time
* It is changed and used in the update() function
*/
Ogre::Real mExtraMorphAmount, mExtraFadeAmount;
/**
* True if the fade pass has been added to the material.
*/
bool mHasFadePass;
//Custom params used in terrian shaders.
static const size_t MORPH_CUSTOM_PARAM_ID = 77;
static const size_t FADE_CUSTOM_PARAM_ID = 78;
//Terrain bindings
static const int MAIN_BINDING = 0;
static const int DELTA_BINDING = 1;
};

@ -23,48 +23,15 @@
module terrain.terrain;
import std.stdio;
import std.file;
import monster.util.string;
void fail(char[] msg)
{
throw new Exception(msg);
}
// Move elsewhere, make part of the general cache system later
void makeDir(char[] pt)
{
if(exists(pt))
{
if(!isdir(pt))
fail(pt ~ " is not a directory");
}
else
mkdir(pt);
}
void makePath(char[] pt)
{
assert(!pt.begins("/"));
foreach(int i, char c; pt)
if(c == '/')
makeDir(pt[0..i]);
if(!pt.ends("/"))
makeDir(pt);
}
import terrain.generator;
void initTerrain(bool doGen)
{
makePath("cache/terrain");
if(doGen)
terr_genData();
generate();
terr_setupRendering();
}
extern(C):
void terr_genData();
void terr_setupRendering();

@ -0,0 +1,69 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (c_mmfile.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
/*
This file provides a simple interface to memory mapped files
through C functions. Since D's MmFile is superior in terms of
usability and platform independence, we use it even for C++ code.
*/
module util.c_mmfile;
import std.mmfile;
import std.string;
version(Windows)
static int pageSize = 64*1024;
else
static int pageSize = 4*1024;
// List of all MMFs in existence, to keep the GC from killing them
int[MmFile] mf_list;
extern(C):
// Open a new memory mapped file
MmFile mmf_open(char *fileName)
{
auto mmf = new MmFile(toString(fileName),
MmFile.Mode.Read,
0, null, pageSize);
mf_list[mmf] = 1;
return mmf;
}
// Close a file. Do not use the handle after calling this function, as
// the object gets deleted
void mmf_close(MmFile mmf)
{
mf_list.remove(mmf);
delete mmf;
}
// Map a region of the file. Do NOT attempt to access several regions
// at once. Map will almost always unmap the current mapping (thus
// making all current pointers invalid) when a new map is requested.
void* mmf_map(MmFile mmf, ulong offset, ulong size)
{
return mmf[offset..offset+size].ptr;
}

@ -0,0 +1,44 @@
typedef void* D_MmFile;
// These functions are implemented in util/c_mmfile.d
extern "C"
{
// Open a new memory mapped file
D_MmFile mmf_open(const char *fileName);
// Close a file. Do not use the handle after calling this function,
// as the object gets deleted
void mmf_close(D_MmFile mmf);
// Map a region of the file. Do NOT attempt to access several
// regions at once. Map will almost always unmap the current mapping
// (thus making all current pointers invalid) when a new map is
// requested.
void* mmf_map(D_MmFile mmf, int64_t offset, int64_t size);
}
// This struct allows you to open, read and close a memory mapped
// file. It uses the D MmFile class to achieve platform independence
// and an abstract interface.
struct MmFile
{
MmFile(const std::string &file)
{
mmf = mmf_open(file.c_str());
}
~MmFile()
{
mmf_close(mmf);
}
void *map(int64_t offset, int64_t size)
{
return mmf_map(mmf, offset, size);
}
private:
D_MmFile mmf;
};

@ -0,0 +1,129 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (outbuffer.h) 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/ .
*/
/*
This files provides a simple buffer class used for writing the cache
files. It lets you 'write' data to a growing memory buffer and
allows you to change the written data after the fact (since it's
retained in memory.) When you're done, you can write the entire
buffer to a stream in one operation.
*/
// This is sort of like a mini-version of the Region class in
// D. FIXME: And it doesn't need to be. Rewrite this to add buffers of
// the exact size requested instead of filling a buffer of predefined
// size.
class OutBuffer
{
public:
OutBuffer() : used(0), left(0), buffers(), sizes()
{}
~OutBuffer()
{
deallocate();
}
// Write everyting to a stream as one buffer
void writeTo(std::ostream &str)
{
for(int i=0;i<buffers.size();i++)
str.write((char*)buffers[i], sizes[i]);
}
// Get a pointer to a new block at least 'bytes' large. Allocate a
// new buffer if necessary.
void *reserve(size_t bytes)
{
assert(bytes <= bufSize);
if(left >= bytes)
return curPtr;
// Not enough space left. Allocate a new buffer.
curPtr = (char*)malloc(bufSize);
left = bufSize;
// Store the new buffer in the lists
buffers.push_back(curPtr);
sizes.push_back(0);
return curPtr;
}
// Get a new block which is 'bytes' size large. The block will be
// marked as 'used'.
void *add(size_t bytes)
{
void *res = reserve(bytes);
if(bytes == 0)
return res;
assert(left >= bytes);
curPtr += bytes;
left -= bytes;
// We keep a count of the total number of bytes used
used += bytes;
// Keep a count for each buffer as well
sizes[sizes.size()-1] += bytes;
return res;
}
template <class T>
T* write(size_t num)
{
return (T*)add(num*sizeof(T));
}
void deallocate()
{
for(int i=0;i<buffers.size();i++)
free(buffers[i]);
buffers.clear();
sizes.clear();
left = 0;
used = 0;
}
size_t size() { return used; }
private:
std::vector<void*> buffers;
std::vector<int> sizes;
size_t used, left;
char *curPtr;
static const size_t bufSize = 200*1024;
};
std::ostream& operator<<(std::ostream& os, OutBuffer& buf)
{
buf.writeTo(os);
return os;
}
Loading…
Cancel
Save