mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 16:15:31 +00:00
- 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
This commit is contained in:
parent
c18a1ebd05
commit
75d5ad8882
25 changed files with 2665 additions and 2393 deletions
9
Makefile
9
Makefile
|
@ -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,8 +100,9 @@ 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" )));
|
||||
|
@ -112,15 +114,15 @@ extern "C" void ogre_initWindow()
|
|||
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_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;
|
||||
|
|
463
terrain/cpp_archive.cpp
Normal file
463
terrain/cpp_archive.cpp
Normal file
|
@ -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"),
|
||||
|
|
335
terrain/cpp_cachewriter.cpp
Normal file
335
terrain/cpp_cachewriter.cpp
Normal file
|
@ -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
|
||||
|
||||
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++) {
|
||||
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,231 +13,246 @@
|
|||
*
|
||||
* 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;
|
||||
hasMesh = false;
|
||||
hasChildren = false;
|
||||
isStatic = false;
|
||||
|
||||
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 )
|
||||
// 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;
|
||||
|
||||
mHasData = false;
|
||||
mLevel = mInfo->level;
|
||||
cellX = mCellX = mInfo->cellX;
|
||||
cellY = mCellY = mInfo->cellY;
|
||||
|
||||
//always split, as this node never has data
|
||||
const Ogre::Vector3 pos(cellX * CELL_WIDTH,
|
||||
cellY * CELL_WIDTH,
|
||||
0);
|
||||
mSceneNode = g_rootTerrainNode->
|
||||
createChildSceneNode(pos);
|
||||
|
||||
// Split up
|
||||
split();
|
||||
}
|
||||
|
||||
// The root can never be unsplit
|
||||
isStatic = true;
|
||||
}
|
||||
|
||||
assert(mLevel >= 1);
|
||||
assert(mSceneNode != NULL);
|
||||
|
||||
// Set up the bounding box. Use MW coordinates all the way
|
||||
mBounds.setExtents(0,0,mInfo->minHeight,
|
||||
mInfo->worldWidth,mInfo->worldWidth,
|
||||
mInfo->maxHeight);
|
||||
|
||||
// Transform the box to world coordinates, so it can be compared
|
||||
// with the camera later.
|
||||
mBounds.transformAffine(mSceneNode->_getFullTransform());
|
||||
|
||||
const float radius = mInfo->boundingRadius;
|
||||
|
||||
mSplitDistance = radius * SPLIT_FACTOR;
|
||||
mUnsplitDistance = radius * UNSPLIT_FACTOR;
|
||||
|
||||
// Square the distances
|
||||
mSplitDistance *= mSplitDistance;
|
||||
mUnsplitDistance *= mUnsplitDistance;
|
||||
|
||||
// Update the terrain. This will create the mesh or children if
|
||||
// necessary.
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the landscape, if there is any
|
||||
* Creates children, and either splits them, or creates landscape for them
|
||||
*/
|
||||
// Remove the landscape for this quad, and create children.
|
||||
void split()
|
||||
{
|
||||
TRACE("split");
|
||||
RTRACE("split");
|
||||
|
||||
// Never split a static quad or a quad that already has children.
|
||||
assert(!isStatic);
|
||||
assert(!hasChildren);
|
||||
assert(mLevel > 1);
|
||||
|
||||
if(hasMesh)
|
||||
destroyTerrain();
|
||||
|
||||
//create a new terrain
|
||||
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
|
||||
mChildren[i] = new Quad((QuadLocation)i, this);
|
||||
// Find the cell width of our children
|
||||
int cWidth = 1 << (mLevel-2);
|
||||
|
||||
assert(!needUnsplit());
|
||||
// Create children
|
||||
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
|
||||
{
|
||||
if(!mInfo->hasChild[i])
|
||||
continue;
|
||||
|
||||
// The cell coordinates for this child quad
|
||||
int x = (i%2)*cWidth + mCellX;
|
||||
int y = (i/2)*cWidth + mCellY;
|
||||
|
||||
mChildren[i] = new Quad(x,y,this);
|
||||
}
|
||||
hasChildren = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief removes all children, and builds terrain on this level
|
||||
*/
|
||||
// Removes children and rebuilds terrain
|
||||
void unsplit()
|
||||
{
|
||||
TRACE("unsplit");
|
||||
//shouldn't unsplit 0 depth
|
||||
assert(getDepth());
|
||||
RTRACE("unsplit");
|
||||
|
||||
for ( size_t i = 0; i < NUM_CHILDREN; i++ )
|
||||
// Never unsplit the root quad
|
||||
assert(mLevel < g_archive.rootQuad->level);
|
||||
// Never unsplit a static or quad that isn't split.
|
||||
assert(!isStatic);
|
||||
assert(hasChildren);
|
||||
assert(!hasMesh);
|
||||
|
||||
for( size_t i = 0; i < NUM_CHILDREN; i++ )
|
||||
{
|
||||
delete mChildren[i];
|
||||
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
|
||||
mChildren[i] = NULL;
|
||||
}
|
||||
|
||||
if ( mHasData )
|
||||
{
|
||||
buildTerrain();
|
||||
justUnsplit();
|
||||
|
||||
hasChildren = false;
|
||||
}
|
||||
|
||||
assert(!needSplit());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the node needs to delete all its child nodes, and
|
||||
* rebuild the terrain its level
|
||||
*/
|
||||
bool needUnsplit()
|
||||
// Determines whether to split or unsplit the quad, and immediately
|
||||
// does it.
|
||||
void update()
|
||||
{
|
||||
TRACE("needUnsplit");
|
||||
if ( hasChildren() && getDepth() )
|
||||
RTRACE("Quad::update");
|
||||
|
||||
// Static quads don't change
|
||||
if(isStatic)
|
||||
return;
|
||||
|
||||
assert(mUnsplitDistance > mSplitDistance);
|
||||
|
||||
// Get (squared) camera distance. TODO: shouldn't this just be a
|
||||
// simple vector difference from the mesh center?
|
||||
float camDist;
|
||||
{
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
//get depth ensures the root doesn't try and unsplit
|
||||
if ( getDepth() && !hasData() )
|
||||
return true;
|
||||
// No children?
|
||||
if(!hasChildren)
|
||||
{
|
||||
// If we're close, split now.
|
||||
if(camDist < mSplitDistance)
|
||||
split();
|
||||
else
|
||||
{
|
||||
// We're not close, and don't have any children. Should we
|
||||
// built terrain?
|
||||
if(!hasMesh)
|
||||
buildTerrain();
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief constructs the terrain on this level. The terrain must on
|
||||
* exist before hand
|
||||
*/
|
||||
// If we get here, we either had children when we entered, or we
|
||||
// just performed a split.
|
||||
assert(!hasMesh);
|
||||
assert(hasChildren);
|
||||
|
||||
// If the camera is too far away, kill the children.
|
||||
if( camDist > mUnsplitDistance )
|
||||
{
|
||||
unsplit();
|
||||
return;
|
||||
}
|
||||
|
||||
// We have children and we're happy about it. Update them too.
|
||||
for (size_t i = 0; i < NUM_CHILDREN; ++i)
|
||||
{
|
||||
Quad *q = mChildren[i];
|
||||
if(q != NULL) q->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Build the terrain for this quad
|
||||
void buildTerrain()
|
||||
{
|
||||
TRACE("buildTerrain");
|
||||
assert(!mQuadData);
|
||||
assert(hasData());
|
||||
RTRACE("buildTerrain");
|
||||
assert(!hasMesh);
|
||||
assert(!isStatic);
|
||||
|
||||
// This was in MeshInterface().
|
||||
// Map the terrain data into memory.
|
||||
g_archive.mapQuad(mInfo);
|
||||
|
||||
mMax = 0;
|
||||
mMin = 0;
|
||||
mSplitState = SS_NONE;
|
||||
|
||||
long qx = mPosition.x;
|
||||
long qy = mPosition.y;
|
||||
mQuadData = g_heightMap->getData(qx, qy);
|
||||
|
||||
//the mesh is created at zero, so an offset is applied
|
||||
const Ogre::Vector3 pos(qx - mSideLength/2,
|
||||
0,qy - mSideLength/2);
|
||||
|
||||
mSceneNode = g_heightMap->getTerrainSceneNode()->createChildSceneNode(pos);
|
||||
|
||||
// This was in create()
|
||||
|
||||
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);
|
||||
|
||||
getBounds();
|
||||
// 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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,245 +260,43 @@ public:
|
|||
*/
|
||||
void destroyTerrain()
|
||||
{
|
||||
TRACE("destroyTerrain");
|
||||
if(!mQuadData)
|
||||
return;
|
||||
RTRACE("destroyTerrain");
|
||||
assert(hasMesh);
|
||||
|
||||
// From ~MeshInterface()
|
||||
for ( MeshList::iterator itr =
|
||||
mMeshList.begin();
|
||||
itr != mMeshList.end();
|
||||
++itr )
|
||||
for ( MeshList::iterator itr = mMeshList.begin();
|
||||
itr != mMeshList.end(); ++itr )
|
||||
delete *itr;
|
||||
|
||||
mMeshList.clear();
|
||||
|
||||
mSceneNode->removeAndDestroyAllChildren();
|
||||
mSceneMgr->destroySceneNode(mSceneNode);
|
||||
|
||||
g_heightMap->getTerrainSceneNode()->detachAllObjects();
|
||||
|
||||
delete mQuadData;
|
||||
mQuadData = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;}
|
||||
|
||||
/**
|
||||
* @return true if their is a terrain mesh alocated
|
||||
*/
|
||||
inline bool hasMesh() const{ return mQuadData; }
|
||||
|
||||
/**
|
||||
* @return true if there are any children
|
||||
*/
|
||||
inline bool hasChildren() const { return mChildren[0] != 0; }
|
||||
|
||||
/**
|
||||
* @brief checks if the quad has any data (i.e. a mesh avaible for rendering
|
||||
*/
|
||||
inline bool hasData() const{ return mHasData; }
|
||||
|
||||
|
||||
/**
|
||||
* @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() )
|
||||
{
|
||||
unsplit();
|
||||
return;
|
||||
}
|
||||
|
||||
//deal with updating the mesh.
|
||||
if ( !mQuadData )
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
mSplitState = SS_NONE;
|
||||
if ( camDist < mSplitDistance ) mSplitState = SS_SPLIT;
|
||||
else if ( camDist > mUnsplitDistance ) mSplitState = SS_UNSPLIT;
|
||||
|
||||
for ( MeshList::iterator itr = mMeshList.begin();
|
||||
itr != mMeshList.end();
|
||||
++itr )
|
||||
{
|
||||
assert(*itr);
|
||||
(*itr)->update(time, camDist, mUnsplitDistance, mMorphDistance);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
inline void justUnsplit() {
|
||||
for ( MeshList::iterator itr = mMeshList.begin();
|
||||
itr != mMeshList.end();
|
||||
++itr )
|
||||
(*itr)->justUnsplit();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
bool mHasData; ///holds if there is terrain data about this quad
|
||||
int mMaxDepth; ///the maxmium depth. Cached. This is not valid is mDepth == 0
|
||||
QuadInfo *mInfo;
|
||||
|
||||
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");
|
||||
|
||||
// Set up the terrain frame listener
|
||||
terrainListener.setup();
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
using namespace Ogre;
|
||||
|
||||
#ifdef QSDEBUG
|
||||
// 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.
|
||||
|
||||
// Use MW coordinates all the way
|
||||
mBounds.setExtents(0,0,info.minHeight,
|
||||
// was (mWidth-1) * vertexSeparation
|
||||
info.worldWidth, info.worldWidth,
|
||||
info.maxHeight);
|
||||
|
||||
mCenter = mBounds.getCenter();
|
||||
mBoundingRadius = mBounds.getHalfSize().length();
|
||||
|
||||
// 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;
|
||||
|
||||
VertexDeclaration* vertexDecl = mVertices->vertexDeclaration;
|
||||
size_t currOffset = 0;
|
||||
|
||||
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
|
||||
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
|
||||
|
||||
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();
|
||||
|
||||
// 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)
|
||||
{
|
||||
//check sizes
|
||||
const float qw = mQuadData->getVertexWidth()-1;
|
||||
const float fsw = qw*segSize;
|
||||
const int isw = (int)fsw;
|
||||
assert(fsw==isw);
|
||||
// This material just has a normal texture
|
||||
pass->createTextureUnitState(texName)
|
||||
//->setTextureAddressingMode(TextureUnitState::TAM_CLAMP)
|
||||
;
|
||||
}
|
||||
#endif
|
||||
|
||||
//precalc offsets, as getVertex/getNormal get called a lot (1000s of times)
|
||||
computeOffsets();
|
||||
|
||||
// From Quad
|
||||
node = parent->createChildSceneNode(pos);
|
||||
|
||||
// From create()
|
||||
createVertexBuffer();
|
||||
calculateVertexValues();
|
||||
calculateIndexValues();
|
||||
setBounds();
|
||||
|
||||
node->attachObject(this);
|
||||
|
||||
createMaterial();
|
||||
|
||||
if ( g_heightMap->isMorphingEnabled() &&
|
||||
mDepth != g_heightMap->getMaxDepth() )
|
||||
else
|
||||
{
|
||||
Ogre::Technique* tech = getMaterial()->getTechnique(0);
|
||||
for ( size_t i = 0; i < tech->getNumPasses(); ++i )
|
||||
// We have to use alpha splatting
|
||||
float scale = info.getTexScale();
|
||||
|
||||
// Get the background texture
|
||||
const char *bgTex = info.getBackgroundTex();
|
||||
pass->createTextureUnitState(bgTex)
|
||||
->setTextureScale(scale,scale);
|
||||
|
||||
int alphaSize = info.getAlphaSize();
|
||||
|
||||
// Loop through all the textures in this mesh
|
||||
for(int tnum=0; tnum<info.alphaNum; tnum++)
|
||||
{
|
||||
assert(g_heightMap->isMorphingEnabled());
|
||||
tech->getPass(i)->setVertexProgram(MORPH_VERTEX_PROGRAM);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if ( g_heightMap->isMorphingEnabled() )
|
||||
calculateDeltaValues();
|
||||
// Finally, set up the scene node.
|
||||
mNode = parent->createChildSceneNode(Vector3(info.x, info.y, 0.0));
|
||||
mNode->attachObject(this);
|
||||
}
|
||||
|
||||
~TerrainMesh()
|
||||
{
|
||||
//deleting null values is fine iirc
|
||||
delete mIndices;
|
||||
|
||||
# if ENABLED_CRASHING == 1
|
||||
{
|
||||
delete mVertexes;
|
||||
}
|
||||
# else
|
||||
{
|
||||
if ( mDepth != g_heightMap->getMaxDepth() ){
|
||||
delete mVertexes;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
// TODO: This used to crash. See what happens now.
|
||||
delete mVertices;
|
||||
|
||||
assert(node);
|
||||
assert(mNode);
|
||||
|
||||
node->detachAllObjects();
|
||||
node->getCreator()->destroySceneNode(node);
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
TRACE("TerrainMesh::update");
|
||||
//if ( USE_MORPH ){
|
||||
//-----------------------------------------------------------------------
|
||||
// These are all Ogre functions that we have to override
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
//as aprocesh mUnsplitDistance, lower detail
|
||||
if ( camDist > morphDist && mDepth > 1 ) {
|
||||
mLODMorphFactor = 1 - (usplitDist - camDist)/(usplitDist-morphDist);
|
||||
} else
|
||||
mLODMorphFactor = 0;
|
||||
mTextureFadeFactor = mLODMorphFactor;
|
||||
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
virtual const MaterialPtr& getMaterial(void) const
|
||||
{ return mMaterial; }
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
|
|
69
util/c_mmfile.d
Normal file
69
util/c_mmfile.d
Normal file
|
@ -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;
|
||||
}
|
44
util/mmfile.h
Normal file
44
util/mmfile.h
Normal file
|
@ -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;
|
||||
};
|
129
util/outbuffer.h
Normal file
129
util/outbuffer.h
Normal file
|
@ -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…
Reference in a new issue