diff --git a/Makefile b/Makefile index 5cb0b2246..b50119831 100644 --- a/Makefile +++ b/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) diff --git a/bsa/bsafile.d b/bsa/bsafile.d index 8e57a869c..155327bbb 100644 --- a/bsa/bsafile.d +++ b/bsa/bsafile.d @@ -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 diff --git a/input/events.d b/input/events.d index 8f3006ea2..896481c7f 100644 --- a/input/events.d +++ b/input/events.d @@ -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(); } diff --git a/ogre/cpp_framelistener.cpp b/ogre/cpp_framelistener.cpp index ca37bd80c..46427a03b 100644 --- a/ogre/cpp_framelistener.cpp +++ b/ogre/cpp_framelistener.cpp @@ -43,6 +43,8 @@ public: // Start of frame bool frameStarted(const FrameEvent& evt) { + TRACE("frameStarted (Input)"); + if(mWindow->isClosed()) return false; diff --git a/ogre/cpp_interface.cpp b/ogre/cpp_interface.cpp index 8a4bc0aab..143b75550 100644 --- a/ogre/cpp_interface.cpp +++ b/ogre/cpp_interface.cpp @@ -50,6 +50,7 @@ extern "C" int32_t ogre_configure( new LogManager; Log *log = LogManager::getSingleton().createLog("Ogre.log"); + g_isDebug = debugOut; if(debugOut) // Full log detail log->setLogDetail(LL_BOREME); @@ -99,28 +100,29 @@ extern "C" void ogre_initWindow() windowHndStr << windowHnd; pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); - // Non-exclusive mouse and keyboard input - /* + // Non-exclusive mouse and keyboard input in debug mode + if(g_isDebug) + { #if defined OIS_WIN32_PLATFORM - pl.insert(std::make_pair(std::string("w32_mouse"), - std::string("DISCL_FOREGROUND" ))); - pl.insert(std::make_pair(std::string("w32_mouse"), - std::string("DISCL_NONEXCLUSIVE"))); - pl.insert(std::make_pair(std::string("w32_keyboard"), - std::string("DISCL_FOREGROUND"))); - pl.insert(std::make_pair(std::string("w32_keyboard"), - std::string("DISCL_NONEXCLUSIVE"))); + pl.insert(std::make_pair(std::string("w32_mouse"), + std::string("DISCL_FOREGROUND" ))); + pl.insert(std::make_pair(std::string("w32_mouse"), + std::string("DISCL_NONEXCLUSIVE"))); + pl.insert(std::make_pair(std::string("w32_keyboard"), + std::string("DISCL_FOREGROUND"))); + pl.insert(std::make_pair(std::string("w32_keyboard"), + std::string("DISCL_NONEXCLUSIVE"))); #elif defined OIS_LINUX_PLATFORM - pl.insert(std::make_pair(std::string("x11_mouse_grab"), - std::string("true"))); - pl.insert(std::make_pair(std::string("x11_mouse_hide"), - std::string("true"))); - pl.insert(std::make_pair(std::string("x11_keyboard_grab"), - std::string("true"))); - pl.insert(std::make_pair(std::string("XAutoRepeatOn"), - std::string("false"))); + pl.insert(std::make_pair(std::string("x11_mouse_grab"), + std::string("false"))); + pl.insert(std::make_pair(std::string("x11_mouse_hide"), + std::string("false"))); + pl.insert(std::make_pair(std::string("x11_keyboard_grab"), + std::string("false"))); + pl.insert(std::make_pair(std::string("XAutoRepeatOn"), + std::string("true"))); #endif - */ + } mInputManager = InputManager::createInputSystem( pl ); diff --git a/ogre/cpp_ogre.cpp b/ogre/cpp_ogre.cpp index c795c8a4c..7338517b3 100644 --- a/ogre/cpp_ogre.cpp +++ b/ogre/cpp_ogre.cpp @@ -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; diff --git a/terrain/cpp_archive.cpp b/terrain/cpp_archive.cpp new file mode 100644 index 000000000..eaad1a91d --- /dev/null +++ b/terrain/cpp_archive.cpp @@ -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;ioffset, 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=1 && level > > quadMap; + + // These contain pregenerated mesh data that is common for all + // meshes on a given level. + std::vector vertBufData; + std::vector 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; + } + + // 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; +} diff --git a/terrain/cpp_baseland.cpp b/terrain/cpp_baseland.cpp index c7925a875..6ad1dcb9b 100644 --- a/terrain/cpp_baseland.cpp +++ b/terrain/cpp_baseland.cpp @@ -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"), diff --git a/terrain/cpp_cachewriter.cpp b/terrain/cpp_cachewriter.cpp new file mode 100644 index 000000000..514067cd9 --- /dev/null +++ b/terrain/cpp_cachewriter.cpp @@ -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 alphas; +}; + +// A struct that gathers all the relevant quad data in one place. +struct QuadHolder +{ + QuadInfo info; + + std::vector 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 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 offsets; + offsets.resize(head.stringNum); + size_t curOffs = 0; + + for(int i=0; i 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(meshNum); + + // Then write the mesh data in approximately the order it's read + for(int i=0; i(alphaNum); + + // Loop through the alpha maps + for(int k=0; ksecond; + + // 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 vertBufData; + std::vector indexBufData; + std::vector vertBufSize; + std::vector indexBufSize; + + // Variables that must be set during the gen phase + int maxLevel; + int alphaSize; + + // Contains a unique index for each string + typedef std::map StringList; + StringList stringList; + std::map stringLookup; + size_t totalStringLength; + + // List of all quads + typedef std::list QuadList; + QuadList quadList; + + // Output file + std::ofstream mainFile; + size_t fileOffset; + + // Index file name + std::string iname; +}; diff --git a/terrain/cpp_esm.cpp b/terrain/cpp_esm.cpp index 1bb459d30..aef90a8cf 100644 --- a/terrain/cpp_esm.cpp +++ b/terrain/cpp_esm.cpp @@ -65,12 +65,10 @@ private: }; -typedef boost::shared_ptr RecordPtr; -typedef std::list RecordList; +typedef std::list RecordList; typedef RecordList::iterator RecordListItr; -typedef boost::shared_ptr RecordListPtr; -typedef std::map RecordMap; +typedef std::map 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); diff --git a/terrain/cpp_framelistener.cpp b/terrain/cpp_framelistener.cpp deleted file mode 100644 index b55e66488..000000000 --- a/terrain/cpp_framelistener.cpp +++ /dev/null @@ -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); - } -}; diff --git a/terrain/cpp_generator.cpp b/terrain/cpp_generator.cpp index 2bf1cbf8b..ece115574 100644 --- a/terrain/cpp_generator.cpp +++ b/terrain/cpp_generator.cpp @@ -20,25 +20,229 @@ */ -class Generator +typedef uint8_t ubyte; +typedef uint16_t ushort16; + +struct GenLevelResult { +private: + bool isAlpha; + public: - Generator(const std::string& baseFileName) : - mBaseFileName(baseFileName) + QuadHolder quad; + PixelBox image; + bool hasMesh; + + GenLevelResult() { -#if GEN_LANDDATA - mDataO.open(std::string(baseFileName+".data").c_str(), std::ios::binary); -#endif + image.data = NULL; + isAlpha = false; + hasMesh = false; } - inline void addLandData(RecordPtr record, const std::string& source) + ~GenLevelResult() + { + if(!isEmpty()) + { + // This takes care of both normal image data and alpha maps. + free(image.data); + + if(hasMesh) + free(quad.meshes[0].vertexBuffer); + } + } + + ubyte *allocAlphas(int width, int texNum) + { + assert(isEmpty() || hasMesh); + image = PixelBox(width, width, texNum, Ogre::PF_A8); + + image.data = calloc(width*width*texNum, 1); + isAlpha = true; + + // Set up the alpha images. TODO: We have to split these over + // several meshes, but for now pretend that we're using only + // one. This is going to be a bit messy... Perhaps having only + // separate buffers is good enough, without using the pixel box at + // all. + assert(quad.meshes.size() == 1); + quad.meshes[0].alphas.resize(texNum); + + for(int i=0;i0;rows--) + { + memcpy(to, from, rowSize); + to += tskip; + from += fskip; + } +} + +class Generator +{ + // ESM data holder (will disappear soon) + MWLand mMWLand; + + int mCount; + + // Texture sizes for the various levels. For the most detailed level + // (level 1), this give the size of the alpha texture rather than + // the actual final texture (There is no final texture for this + // level, only alpha splatting.) + std::vector texSizes; + + // Default textures + std::vector defaults; + + CacheWriter cache; + +public: + Generator() : + mCount(0) {} + + inline void addLandData(Record* record, const std::string& source) { mMWLand.addLandData(record, source); } - inline void addLandTextureData(RecordPtr record, const std::string& source) + inline void addLandTextureData(Record* record, const std::string& source) { mMWLand.addLandTextureData(record, source); } - void beginGeneration() + void generate(const std::string &filename) { + TRACE("generate"); + + cache.openFile(filename); + // Find the maxiumum distance from (0,0) in any direction int max = 0; max = std::max(mMWLand.getMaxX(), max); @@ -46,364 +250,920 @@ public: max = std::max(-mMWLand.getMinX(), max); max = std::max(-mMWLand.getMinY(), max); - //round up to nearest binary number. makes some stuff easier iirc - //FIXME - for ( int i = 1;;i++ ) { - if ( max < pow((float)2, i) ) - { - max = pow((float)2, i); - break; - } - assert(i<=8); //don't go too high. Used for debug - } - - int maxDepth = 0; //temp var used below - // FIXME: make 8192 a constant - mIndex.setRootSideLength(max*2*8192); //given that 8192 is the length of a cell - - //Keep deviding the root side length by 2 (thereby simulating a - //split) until we reach the width of the base cell (or the - //smallest quad). TODO: We should make sure that the right number - //of levels are actually generated automatically, of course. - for (long i = mIndex.getRootSideLength(); i > 8192; i/=2 ) - maxDepth++; - mIndex.setMaxDepth(maxDepth); - } - - void generateLODLevel(int level, int textureSize) - { - std::cout << "Generating Level " << level << "\n"; - - assert(level <= mIndex.getMaxDepth()); - assert(level > 0 ); - assert(textureSize>2); - assert(textureSize<=4096); //change to gpu max if pos - - const int initialLevel = level; - - // FIXME: Should probably use another name than 'level' here - level = pow((float)2, level); //gap between verts that we want - // Use this instead - assert(level == 1 << initialLevel); - - const int halfLevel = level/2; - assert(halfLevel > 0 ); - - // FIXME. Search for all pow() calls and fix them - int cellDist = pow((float)2, mIndex.getMaxDepth()); - - // Temporary storage - QuadData qd; -#if GEN_LANDDATA - qd.setVertexSeperation(128*halfLevel); //dist between two verts - - std::vector& gh = qd.getHeightsRef(); //ref to the data - //storage in the quad - std::vector& gn = qd.getNormalsRef(); - gh.resize(LAND_NUM_VERTS); //allocate memory for mesh functions - gn.resize(LAND_NUM_VERTS*3); -#endif - - //the 16*16 array used for holding the LTEX records (what texure - //is splatted where) - std::vector& gl = qd.getTextureIndexRef(); - gl.resize((LAND_LTEX_WIDTH+2)*(LAND_LTEX_WIDTH+2)); - - const std::string stringLevel(Ogre::StringConverter::toString(level)); - const std::string defaultTexture(stringLevel + "_default.png"); - bool hasUsedDefault = false; - - // loops over the start of each quad we want to get - for( int y = -(cellDist/2); y < (cellDist/2); y+=halfLevel ) - for( int x = -(cellDist/2); x < (cellDist/2); x+=halfLevel ) - { - qd.setParentTexture(""); - bool usingDefaultTexture = false; - - if ( initialLevel == 1 ) - // For level one (up close), there's no need to generate - // any textures, as we can use the textures from the BSAs - // directly. - generateLTEXData(x, y, gl); - else - { - // Texture file name - std::string name = stringLevel + "_" + - Ogre::StringConverter::toString(x) + "_" + - Ogre::StringConverter::toString(y) + ".png"; - - // Generate the texture - bool hasGen = generateTexture(std::string(TEXTURE_OUTPUT) + name, textureSize, x, y, halfLevel); - - if ( hasGen ) qd.setTexture(name); - else - { - qd.setTexture(defaultTexture); - hasUsedDefault = true; - usingDefaultTexture = true; - } - } - - // Calculate parent texture - if ( initialLevel != mIndex.getMaxDepth() ) - { - // FIXME: This can definitely be improved, although it - // doesn't matter performance wise - - //calcualte the level one higher - // FIXME: pow() again... - int higherLevel = pow((float)2, (initialLevel+1)); - int highHalfLevel = higherLevel/2; - - int higherX = x; - if ( (higherX-halfLevel) % highHalfLevel == 0 ) - higherX -= halfLevel; - - int higherY = y; - if ( (higherY-halfLevel) % highHalfLevel == 0 ) - higherY -= halfLevel; - - std::string higherName = Ogre::StringConverter::toString(higherLevel) + "_" + - Ogre::StringConverter::toString(higherX) + "_" + - Ogre::StringConverter::toString(higherY) + ".png"; - - //check file exists without needing boost filesystem libs - FILE* fp = fopen((std::string(TEXTURE_OUTPUT) + higherName).c_str(), "r"); - if ( fp ) - { - qd.setParentTexture(higherName); - fclose(fp); - } - else - qd.setParentTexture(""); - } - -#if GEN_LANDDATA - generateMesh(gh, gn, x, y, halfLevel ); - - bool isEmptyQuad = true; - if ( usingDefaultTexture ) - { - for ( int i = 0; i < LAND_NUM_VERTS; i++ ){ - if ( gh.at(i) != LAND_DEFAULT_HEIGHT ){ - isEmptyQuad = false; - break; - } - } - } - else isEmptyQuad = false; - - if ( isEmptyQuad ) - continue; - - //save data - //the data is the position of the generated quad - mIndex.setOffset(x*8192+halfLevel*8192/2, - y*8192+halfLevel*8192/2, - mDataO.tellp()); - boost::archive::binary_oarchive oa(mDataO); //using boost fs to write the quad - oa << qd; -#endif - - } - - //check if we have used a default texture - if ( hasUsedDefault ) + // Round up to nearest binary + int depth=1; + while(max) { - std::vector ltex; - ltex.resize(halfLevel*LAND_LTEX_WIDTH*halfLevel*LAND_LTEX_WIDTH, mPalette.getOrAddIndex("_land_default.dds")); - renderTexture(std::string(TEXTURE_OUTPUT) + defaultTexture, ltex, textureSize, halfLevel*LAND_LTEX_WIDTH); + max >>= 1; + depth++; + assert(depth <= 8); } + max = 1 << depth-1; + + // We already know the answers + assert(max == 32); + assert(depth == 6); + + // Set the texture sizes. TODO: These should be config options, + // perhaps - or maybe a result of some higher-level detail setting. + texSizes.resize(depth+1, 0); + texSizes[6] = 1024; + texSizes[5] = 512; + texSizes[4] = 256; + texSizes[3] = 256; + texSizes[2] = 256; + texSizes[1] = 64; + + // Set some general parameters for the runtime + cache.setParams(depth+1, texSizes[1]); + + // Create some common data first + std::cout << "Generating common data\n"; + genDefaults(); + genIndexData(); + + std::cout << "Generating quad data\n"; + // Start at one level above the top, but don't generate a mesh for + // it + GenLevelResult gen; + genLevel(depth+1, -max, -max, gen, false); + std::cout << "Writing index file\n"; + cache.finish(); + std::cout << "Pregeneration done. Results written to " + << filename << "\n"; } - void endGeneration() + // Generates the default texture images "2_default.png" etc + void genDefaults() { - // FIXME: Just write one file? -#if GEN_LANDDATA - mDataO.close(); - std::ofstream ofi(std::string(mBaseFileName + ".index").c_str(), std::ios::binary); - boost::archive::binary_oarchive oai(ofi); - oai << mIndex; -#endif + TRACE("genDefaults"); - std::ofstream ofp(std::string(mBaseFileName + ".palette").c_str(), std::ios::binary); - boost::archive::binary_oarchive oap(ofp); - oap << mPalette; + int size = texSizes.size()-1; + defaults.resize(size); + + for(int i=1; i 2); + genLevel2Map(NULL, defaults[2]); + + for(int i=3; i& index) + // Generates common mesh information that's stored in the .index + // file. This includes the x/y coordinates of meshes on each level, + // the u/v texture coordinates, and the triangle index data. + void genIndexData() { - // All this does is to loop through all the 16x16 'land grid - // points' in the cell (plus the outer border of grid points from - // the surrounding cells) and store the texture names in the - // index. - for ( int texY = 0; texY < LAND_LTEX_WIDTH+2; texY++ ) - for ( int texX = 0; texX < LAND_LTEX_WIDTH+2; texX++ ) + // Generate mesh data for each level. + + /* + TODO: This data is very easy to generate, and we haven't really + tested whether it's worth it to pregenerate it rather than to + just calculate it at runtime. Unlike the index buffer below + (which is just a memcpy at runtime, and definitely worth + pregenerating), we have to loop through all the vertices at + runtime anyway in order to splice this with the height + data. It's possible that the additional memory use, pluss the + slowdown (CPU-cache-wise) of reading from two buffers instead of + one, makes it worthwhile to generate this data at runtime + instead. However I guess the differences will be very small + either way. + */ + for(int lev=1; lev<=6; lev++) + { + // Make a new buffer to store the data + int size = 65*65*4*sizeof(float); + float *vertPtr = (float*)malloc(size); + + // Find the vertex separation for this level. The vertices are + // 128 units apart in each cell, and for each level above that + // we double the distance. This gives 128 * 2^(lev-1) = + // 64*2^lev. + const int vertSep = 64 << lev; + + // Loop over all the vertices in the mesh. + for(int y=0; y<65; y++) + for(int x=0; x<65; x++) + { + // X and Y + *vertPtr++ = x*vertSep; + *vertPtr++ = y*vertSep; + + // U and V (texture coordinates) + const float u = x/64.0; + const float v = y/64.0; + assert(u>=0&&v>=0); + assert(u<=1&&v<=1); + + *vertPtr++ = u; + *vertPtr++ = v; + } + + // Store the buffer + cache.addVertexBuffer(lev,vertPtr,size); + } + + // Next up, triangle indices + int size = 64*64*6*sizeof(ushort16); + ushort16 *indPtr = (ushort16*)malloc(size); + + bool flag = false; + int indNum = 0; + for ( int y = 0; y < 64; y++ ) + { + for ( int x = 0; x < 64; x++ ) + { + const int line1 = y*65 + x; + const int line2 = (y+1)*65 + x; + + if ( flag ) + { + *indPtr++ = line1; + *indPtr++ = line2; + *indPtr++ = line1 + 1; + + *indPtr++ = line1 + 1; + *indPtr++ = line2; + *indPtr++ = line2 + 1; + } + else + { + *indPtr++ = line1; + *indPtr++ = line2; + *indPtr++ = line2 + 1; + + *indPtr++ = line1; + *indPtr++ = line2 + 1; + *indPtr++ = line1 + 1; + } + flag = !flag; //flip tris for next time + indNum+=6; + } + flag = !flag; //flip tries for next row + } + assert(indNum*2==size); + + // The index buffers are the same for all levels + cache.addIndexBuffer(1,indPtr,size); + cache.addIndexBuffer(2,indPtr,size); + cache.addIndexBuffer(3,indPtr,size); + cache.addIndexBuffer(4,indPtr,size); + cache.addIndexBuffer(5,indPtr,size); + cache.addIndexBuffer(6,indPtr,size); + } + + void genLevel(int depth, int X, int Y, GenLevelResult &result, + bool makeData = true) + { + TRACE("genLevel"); + + result.quad.info.cellX = X; + result.quad.info.cellY = Y; + result.quad.info.level = depth; + + assert(result.isEmpty()); + + if(depth == 1) + { + assert(makeData); + + if(!mMWLand.hasData(X,Y)) + // Oops, there's no data for this cell. Skip it. + return; + + // Level 1 (most detailed) is handled differently from the + // other leves. + + // The mesh is generated in pieces rather than as one part. + genLevel1Meshes(result); + + // We also generate alpha maps instead of the actual textures. + genCellAlpha(result); + + if(!result.isEmpty()) + { + // Store the information we just created + assert(result.hasAlpha()); + cache.writeQuad(result.quad); + } + + return; + } + assert(depth > 1); + + // Number of cells in each sub-quad (not in this quad) + int cells = 1 << (depth-2); + + // Call the sub-levels and store the result + GenLevelResult sub[4]; + genLevel(depth-1, X, Y, sub[0]); // NW + genLevel(depth-1, X+cells, Y, sub[1]); // NE + genLevel(depth-1, X, Y+cells, sub[2]); // SW + genLevel(depth-1, X+cells, Y+cells, sub[3]); // SE + + // Mark the sub-quads that have data + bool anyUsed = false; + for(int i=0;i<4;i++) + { + bool used = !sub[i].isEmpty(); + result.quad.info.hasChild[i] = used; + anyUsed = anyUsed || used; + } + + if(!anyUsed) + { + // If our children are empty, then we are also empty. + assert(result.isEmpty()); + return; + } + + if(makeData) + { + // For depth==2, generate a new texture from the alphas. + if(depth == 2) + // Create the texture from the alpha maps + genLevel2Map(sub, result); + else + // Merge the images from the previous levels + mergeMaps(sub, result); + + // Create the landscape mesh + createMesh(sub, result); + } + + // Store the result + cache.writeQuad(result.quad); + } + + // Generate mesh data for one cell + void genLevel1Meshes(GenLevelResult &res) + { + TRACE("genLevel1Meshes"); + + const int intervals = 64; + + // Constants + const int vertNum = intervals+1; + const int vertSep = 128; + + // Allocate the mesh buffer + res.allocMesh(vertNum); + + int cellX = res.quad.info.cellX; + int cellY = res.quad.info.cellY; + assert(res.quad.info.level==1); + + MeshHolder &mh = res.quad.meshes[0]; + MeshInfo &mi = mh.info; + + mi.worldWidth = vertSep*intervals; + assert(mi.worldWidth == 8192); + + const VHGT &verts = *mMWLand.getHeights(cellX,cellY); + const std::vector &normals = mMWLand.getNormals(cellX,cellY); + + mi.heightOffset = verts.heightOffset; + + float max=-1000000.0; + float min=1000000.0; + + char *vertPtr = mh.vertexBuffer; + + // Loop over all the vertices in the mesh. TODO: Mix this with the + // function in MWLand/ESM that decodes the original data, and use + // that directly. + float rowheight = mi.heightOffset; + float height; + for(int y=0; y<65; y++) + for(int x=0; x<65; x++) { - int tempX = x; - int tempY = y; + int offs=y*65+x; - int sourceX = texX - 1; - int sourceY = texY - 1; + // The vertex data from the ESM + char data = verts.heightData[offs]; - if ( sourceX == -1 ) + // Write the height byte + *vertPtr++ = data; + + // Calculate the height here, even though we don't store + // it. We use it to find the min and max values. + if(x == 0) { - tempX--; - sourceX = LAND_LTEX_WIDTH-1; - } - else if ( sourceX == LAND_LTEX_WIDTH) - { - tempX++; - sourceX = 0; - } + // Set the height to the row height + height = rowheight; - if ( sourceY == -1 ) - { - tempY--; - sourceY = LAND_LTEX_WIDTH-1; - } - else if ( sourceY == LAND_LTEX_WIDTH ) - { - tempY++; - sourceY = 0; + // First value in each row adjusts the row height + rowheight += data; } + // Adjust the height from the previous value + height += data; - std::string source; - short texID = 0; + // Calculate the min and max + max = std::max(max, height); + min = std::min(min, height); - if ( mMWLand.hasData(tempX, tempY) ) - { - source = mMWLand.getSource(tempX, tempY); - texID = mMWLand.getLTEXIndex(tempX,tempY, sourceX, sourceY); - } + // Store the normals + for(int k=0; k<3; k++) + *vertPtr++ = normals[offs*3+k]; + } + // Make sure we wrote exactly the right amount of data + assert((vertPtr-mh.vertexBuffer)+1 == + mi.vertBufSize); + + // Store the min/max values + mi.minHeight = min * 8; + mi.maxHeight = max * 8; + } + + // Create the mesh for this level. + void createMesh(GenLevelResult *sub, GenLevelResult &res) + { + TRACE("createMesh"); + + // How much to shift various numbers to the left at this level + // (ie. multiply by 2^shift) + const int shift = res.quad.info.level - 1; + assert(shift >= 1); + + // Constants + const int intervals = 64; + const int vertNum = intervals+1; + const int vertSep = 128 << shift; + + // Allocate the result buffer + res.allocMesh(vertNum); + + MeshHolder &mh = res.quad.meshes[0]; + MeshInfo &mi = mh.info; + + mi.worldWidth = vertSep*intervals; + char *vertPtr = mh.vertexBuffer; + + // Get the height from the first cell + float rowheight; + if(sub[0].isEmpty()) + rowheight = 0.0; + else + rowheight = sub[0].quad.meshes[0].info.heightOffset; + + // This is also the offset for the entire mesh + mi.heightOffset = rowheight; + + // Loop through each 'row' of submeshes + for(int subY=0; subY<2; subY++) + { + // Loop through each row of vertices + for(int row=0; row<65; row++) + { + // Loop through both sub meshes, left and right + for(int subX=0; subX<2; subX++) + { + GenLevelResult *s = &sub[subX+2*subY]; + + // Check if we have any data + if(!s->isEmpty() && 0) + { + const MeshHolder &smh = s->quad.meshes[0]; + char* inPtr = smh.vertexBuffer; + + // Loop through each vertex in this mesh. We skip two + // at a time. + for(int v=0; v<64; v+=2) + { + // Handle the v=0 case + + // Count the height from the two next vertices + int data = *inPtr++; + inPtr++;inPtr++;inPtr++; // Skip the first normal + data += *inPtr++; + + // Divide by two, since the result needs to fit in + // one byte. We compensate for this when we regen + // the mesh at runtime. + data >>= 1; + assert(data < 128 && data >= -128); + + *vertPtr++ = data; + + // Copy over the normal + *vertPtr++ = *inPtr++; + *vertPtr++ = *inPtr++; + *vertPtr++ = *inPtr++; + } + // Store the last one here. It _should_ be the + // same as the first in the next section, if + // present. + } + else + { + // No data in this mesh. Just write zeros. + for(int v=0; v<32; v++) + { + // Height + *vertPtr++ = 0; + + // Normal, pointing straight upwards + *vertPtr++ = 0; + *vertPtr++ = 0; + *vertPtr++ = 0x7f; + } + } + } + } + } + assert(vertPtr == mh.vertexBuffer + mi.vertBufSize); + } + + // About segments: + + /* NOTES for the gen-phase: Was: + // This is pretty messy. Btw: 128*16 == 2048 == + // CELL_WIDTH/4 + // 65 points across one cell means 64 intervals, and 17 points + + // means 16=64/4 intervals. So IOW the number of verts when + // dividing by D is (65-1)/D + 1 = 64/D+1, which means that D + // should divide 64, that is, be a power of two < 64. + + 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 + */ + + /* This was also declared in the original code, you'll need it + when creating the cache data + + size_t vw = mWidth; // mWidth is 17 or 65 + if ( mUseSkirts ) vw += 2; // skirts are used for level 2 and up + vertCount=vw*vw; + */ + + /** + * @brief fills the vertex buffer with data + * @todo I don't think tex co-ords are right + void calculateVertexValues() + { + int start = 0; + int end = mWidth; + + if ( mUseSkirts ) + { + --start; + ++end; + } + + for ( int y = start; y < end; y++ ) + for ( int x = start; x < end; x++ ) + { + if ( y < 0 || y > (mWidth-1) || x < 0 || x > (mWidth-1) ) + { + // These are the skirt vertices. 'Skirts' are simply a + // wall at the edges of the mesh that goes straight down, + // cutting off the posibility that you might see 'gaps' + // between the meshes. Or at least I think that's the + // intention. + + assert(mUseSkirts); + + // 1st coordinate + if ( x < 0 ) + *verts++ = 0; + else if ( x > (mWidth-1) ) + *verts++ = (mWidth-1)*getVertexSeperation(); + else + *verts++ = x*getVertexSeperation(); + + // 2nd coordinate + *verts++ = -4096; //2048 below base sea floor height + + // 3rd coordinate + if ( y < 0 ) + *verts++ = 0; + else if ( y > (mWidth-1) ) + *verts++ = (mWidth-1)*getVertexSeperation(); + else + *verts++ = y*getVertexSeperation(); + + // No normals + for ( Ogre::uint i = 0; i < 3; i++ ) + *verts++ = 0; + + // It shouldn't matter if these go over 1 + float u = (float)(x) / (mWidth-1); + float v = (float)(y) / (mWidth-1); + *verts++ = u; + *verts++ = v; + } + else // Covered already + + 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); + + // buffer was created here + + 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); + //return mIndices; + } + */ + + // Generate the alpha splatting bitmap for one cell. + void genCellAlpha(GenLevelResult &res) + { + TRACE("genCellAlpha"); + + const int cellX = res.quad.info.cellX; + const int cellY = res.quad.info.cellY; + assert(res.quad.info.level == 1); + + // Messy older code. We'll fix this later and read ESM data + // directly from D code. + std::string source = mMWLand.getSource(cellX, cellY); + + // List of texture indices for this cell. A cell has 16x16 texture + // squares. + int ltex[16][16]; + + // A map from the global texture index to the local index for this + // cell. + typedef std::map TSet; + TSet textures; + + int local = 0; // Local index + + // Loop through all the textures in the cell and get the indices + bool isDef = true; + for(int ty = 0; ty < 16; ty++) + for(int tx = 0; tx < 16; tx++) + { + // More messy code, to get the texture file name + short texID = + mMWLand.getLTEXIndex(cellX,cellY, tx, ty); std::string texturePath = "_land_default.dds"; if ( texID != 0 && mMWLand.hasLTEXRecord(source,--texID) ) - texturePath = mMWLand.getLTEXRecord(source,texID); + { + texturePath = mMWLand.getLTEXRecord(source,texID); + isDef = false; + } - // textures given as tga, when they are a dds. I hate that "feature" - // FIXME: do we handle this already? - size_t d = texturePath.find_last_of(".") + 1; - texturePath = texturePath.substr(0, d) + "dds"; - std::transform(texturePath.begin(), texturePath.end(), texturePath.begin(), tolower); + // Store the final index + int index = cache.addTexture(texturePath); + ltex[ty][tx] = index; - index[texY*(LAND_LTEX_WIDTH+2)+texX] = mPalette.getOrAddIndex(texturePath); + // Add the index to the map + if(textures.find(index) == textures.end()) + textures[index] = local++; } - } + assert(local == textures.size()); - // Create a cache texture. - bool generateTexture(const std::string& name, int size, - int blockX, int blockY, int halfLevel) - { - int width = size/(halfLevel*LAND_LTEX_WIDTH); - assert(width>=1); + // If we still only found default textures, exit now. + if(isDef) + return; - // Preallocate a buffer of texture indices - std::vector ltex; - ltex.resize(halfLevel*LAND_LTEX_WIDTH*halfLevel*LAND_LTEX_WIDTH, 0); + const int imageRes = texSizes[1]; + const int dataSize = imageRes*imageRes; + const int texNum = textures.size(); - // This loop collection is just to gather the texture indices for - // this quad. All texture paths are inserted into the - for ( int y = 0; y < halfLevel; y++ ) - for ( int x = 0; x < halfLevel; x++ ) + // Number of alpha pixels per texture square + const int pps = imageRes/16; + + // Make sure there are at least as many alpha pixels as there are + // textures + assert(imageRes >= 16); + assert(imageRes%16 == 0); + assert(pps >= 1); + assert(texNum >= 1); + + // Allocate the alpha images + ubyte *uptr = res.allocAlphas(imageRes, texNum); + + assert(res.hasAlpha() && !res.isEmpty()); + + // Write the indices to the result list + for(TSet::iterator it = textures.begin(); + it != textures.end(); it++) + res.setAlphaTex(it->second, it->first); + + // Loop over all textures again. This time, do alpha splatting. + for(int ty = 0; ty < 16; ty++) + for(int tx = 0; tx < 16; tx++) { - // And loop over all 16x16 textures in each cell - for ( int texY = 0; texY < LAND_LTEX_WIDTH; texY++ ) - for ( int texX = 0; texX < LAND_LTEX_WIDTH; texX++ ) + // Get the local index for this square + int index = textures[ltex[ty][tx]]; + + // Get the offset of this square + long offs = index*dataSize + pps*(ty*imageRes + tx); + + // FIXME: Make real splatting later. This is just + // placeholder code. + + // Set alphas to full for this square + for(int y=0; y::iterator i = ltex.begin(); i != ltex.end(); ++i ) - sum += *i; - - if ( sum == 0 ) //not used any textures - return false; - - renderTexture(name, ltex, size, halfLevel*LAND_LTEX_WIDTH); - - return true; } - // FIXME: renderTexture and getRenderedTexture don't strike me as - // the optimal ways of doing this. For one it's hardware/driver - // dependent, when it should be a simple computational exercise. But - // understand what it actually does, before you change anything. - - // This function (and it's sub calls) is where most of the time is - // spent in the gen process, so this is also where we need to - // optimize. - void renderTexture(const std::string& outputName, std::vector& ltex, - int texSize, int alphaSize) + struct LTEXIndices { - std::cout << " Creating " << outputName << "\n"; + LTEXIndices() + { + for(int i=0;i<4;i++) + inds[i] = -1; + } - assert(Ogre::Math::Sqrt(ltex.size())==alphaSize); + int inds[4]; + }; + + typedef std::map LTexMap; + + // Generate a texture for level 2 from four alpha maps generated in + // level 1. + void genLevel2Map(GenLevelResult *maps, GenLevelResult &res) + { + TRACE("genLevel2Map"); + const int fromSize = texSizes[1]; + const int toSize = texSizes[2]; + + // Create an overview of which texture is used where. The 'key' is + // the global texture index, the 'value' is the corresponding + // local indices in each of the four submaps. + LTexMap lmap; + + if(maps != NULL) // NULL means only render default + for(int mi=0;mi<4;mi++) + { + if(maps[mi].isEmpty()) + continue; + + assert(maps[mi].hasAlpha() && + maps[mi].image.getWidth() == fromSize); + + for(int ltex=0;ltexgetTechnique(0)->getPass(0); + np->setLightingEnabled(false); + np->createTextureUnitState("_land_default.dds") + ->setTextureScale(scale,scale); + + // List of resources created std::list createdResources; - // FIXME: Move out of this function? - MaterialGenerator mg; - mg.setTexturePaths(mPalette.getPalette()); + // Loop through all our textures + if(maps != NULL) + for(LTexMap::iterator it = lmap.begin(); + it != lmap.end(); it++) + { + int gIndex = it->first; + int *inds = it->second.inds; - // Aaand, this is just halfLevel again, which already is a very - // poor name for the number of cells along the side of the quad. - const int scaleDiv = alphaSize/LAND_LTEX_WIDTH; + const std::string tn(cache.getString(gIndex)); + if ( tn == "_land_default.dds" ) + continue; - //genetate material/aplahas - Ogre::MaterialPtr mp = mg.getAlphaMat(ltex, alphaSize, scaleDiv,createdResources); - Ogre::TexturePtr tex1 = getRenderedTexture(mp, "RTT_TEX_1",texSize, Ogre::PF_R8G8B8); - tex1->getBuffer()->getRenderTarget()->writeContentsToFile(outputName); + // Create alpha map for this texture + std::string alphaName(materialName + "_A_" + tn); + Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton(). + createManual(alphaName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + 2*fromSize,2*fromSize, + 1,0, // depth, mipmaps + Ogre::PF_A8, // One-channel alpha + Ogre::TU_STATIC_WRITE_ONLY); + + 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(pixelBox.data); + + // Fill in the alpha values + memset(pDest, 0, 4*fromSize*fromSize); + for(int i=0;i<4;i++) + { + // Does this sub-image have this texture? + if(inds[i] == -1) continue; + + assert(!maps[i].isEmpty()); + + // Find the right sub-texture in the alpha map + ubyte *from = ((ubyte*)maps[i].image.data) + + (fromSize*fromSize)*inds[i]; + + // Find the right destination pointer + int x = i%2; + int y = i/2; + ubyte *to = pDest + x*fromSize + y*fromSize*fromSize*2; + + // Copy the rows one by one + for(int row = 0; row < fromSize; row++) + { + assert(to+fromSize <= pDest + 4*fromSize*fromSize); + memcpy(to, from, fromSize); + to += 2*fromSize; + from += fromSize; + } + } + + // More Ogre-barf + pixelBuffer->unlock(); + np = mp->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(scale, scale); + } + + Ogre::TexturePtr tex1 = getRenderedTexture(mp,materialName + "_T", + toSize,Ogre::PF_R8G8B8); + + // Create the result buffer + res.allocImage(toSize); + + // Blit the texture over + tex1->getBuffer()->blitToMemory(res.image); + + // Clean up Ogre::MaterialManager::getSingleton().remove(mp->getHandle()); - - //remove the texture we have just written to the fs Ogre::TextureManager::getSingleton().remove(tex1->getHandle()); - - //remove all the materials const std::list::const_iterator iend = createdResources.end(); for ( std::list::const_iterator itr = createdResources.begin(); itr != iend; ++itr) { (*itr)->getCreator()->remove((*itr)->getHandle()); } + + // Output file name. TODO: The result structure can do this for us + // now, it knows both the level and the cell coords. Figure out + // what to do in the default case though. + int X = res.quad.info.cellX; + int Y = res.quad.info.cellY; + std::string outname = + "2_" + Ogre::StringConverter::toString(X) + + "_" + Ogre::StringConverter::toString(Y) +".png"; + + // Override for the default image + if(maps == NULL) + outname = "2_default.png"; + + outname = g_cacheDir + outname; + + // Save result + res.save(outname); } - // Does everything in OGRE + void mergeMaps(GenLevelResult *maps, GenLevelResult &res) + { + TRACE("mergeMaps"); + + const int level = res.quad.info.level; + + assert(texSizes.size() > level); + assert(level > 2); + const int fromSize = texSizes[level-1]; + const int toSize = texSizes[level]; + + // Create a new image buffer large enough to hold the four + // sub textures + res.allocImage(fromSize*2); + + // Add the four sub-textures + for(int mi=0;mi<4;mi++) + { + PixelBox src; + + // Use default texture if no source is present + if(maps == NULL || maps[mi].isEmpty()) + src = defaults[level-1].image; + else + src = maps[mi].image; + + // Find the sub-part of the destination buffer to write to + int x = (mi%2) * fromSize; + int y = (mi/2) * fromSize; + PixelBox dst = res.image.getSubVolume(Box(x,y,x+fromSize,y+fromSize)); + + // Copy the image to the box + copyBox(dst, src); + } + + // Resize image if necessary + if(toSize != 2*fromSize) + res.resize(toSize); + + const int X = res.quad.info.cellX; + const int Y = res.quad.info.cellY; + + // Texture file name + std::string outname = + Ogre::StringConverter::toString(level) + "_"; + + if(maps == NULL) + outname += "default.png"; + else + outname += + Ogre::StringConverter::toString(X) + "_" + + Ogre::StringConverter::toString(Y) + ".png"; + + outname = g_cacheDir + outname; + + // Save the image + res.save(outname); + } + + // Renders a material into a texture Ogre::TexturePtr getRenderedTexture(Ogre::MaterialPtr mp, const std::string& name, int texSize, Ogre::PixelFormat tt) { + TRACE("getRenderedTexture"); + Ogre::CompositorPtr cp = Ogre::CompositorManager::getSingleton(). create("Rtt_Comp", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); @@ -441,45 +1201,8 @@ private: Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp"); Ogre::CompositorManager::getSingleton().remove(cp->getHandle()); - renderTexture->removeAllViewports(); //needed? + renderTexture->removeAllViewports(); return texture; } - - void generateMesh(std::vector& gh, std::vector& gn, int blockX, - int blockY, int halfLevel) - { - int gnc = 0; - int ghc = 0; - for ( int y = 0; y < LAND_VERT_WIDTH; y++ ) - for ( int x = 0; x < LAND_VERT_WIDTH; x++ ) - { - //FIXME: Eh, what? - int cellY = floor((float)y/LAND_VERT_WIDTH*halfLevel) + blockY; - int cellX = floor((float)x/LAND_VERT_WIDTH*halfLevel) + blockX; - - std::vector& ch = mMWLand.getHeights(cellX,cellY); - std::vector& cn = mMWLand.getNormals(cellX,cellY); - - int vertY = (((float)y/LAND_VERT_WIDTH*halfLevel) - - (float)floor((float)y/LAND_VERT_WIDTH*halfLevel)) * LAND_VERT_WIDTH; - int vertX = (((float)x/LAND_VERT_WIDTH*halfLevel) - - (float)floor((float)x/LAND_VERT_WIDTH*halfLevel)) * LAND_VERT_WIDTH; - - assert(vertY < LAND_VERT_WIDTH && vertX < LAND_VERT_WIDTH); - - //store data - gh[ghc++] = ch[vertY*LAND_VERT_WIDTH+vertX]; - - for ( int z = 0; z < 3; z++ ) - gn[gnc++] = cn[(vertY*LAND_VERT_WIDTH+vertX)*3+z]; - } - } - - std::string mBaseFileName; ///base file name so we gen mBaseFileName + ".index" etc - std::ofstream mDataO; - - Index mIndex; ///the index of the data. holds offsets and max depth, rsl etc - TexturePalette mPalette; ///all the textures from all mods are merge into a single palette - MWLand mMWLand; ///deals with MW land stuff }; diff --git a/terrain/cpp_heightmap.cpp b/terrain/cpp_heightmap.cpp deleted file mode 100644 index b39f806e4..000000000 --- a/terrain/cpp_heightmap.cpp +++ /dev/null @@ -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; -}; diff --git a/terrain/cpp_index.cpp b/terrain/cpp_index.cpp deleted file mode 100644 index c35fd1673..000000000 --- a/terrain/cpp_index.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Holds index and other data describing the landscape.data file. - */ -class Index -{ -public: - typedef std::map >::iterator OffsetItr; - typedef std::map >::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::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 > 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 - inline void serialize(Archive& ar, const unsigned int version){ - - ar &mMaxDepth; - ar &mRootSideLength; - ar &mQuadOffsets; - - } - -}; - -BOOST_CLASS_TRACKING(Index, boost::serialization::track_never); diff --git a/terrain/cpp_landdata.cpp b/terrain/cpp_landdata.cpp index e62851644..963757a5f 100644 --- a/terrain/cpp_landdata.cpp +++ b/terrain/cpp_landdata.cpp @@ -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& getHeights(int x, int y) + inline VHGT *getHeights(int x, int y) { if ( hasData(x,y) ) return mLandRecords[x][y].heights; - static std::vector e(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT); - return e; + assert(0); } inline std::vector& getNormals(int x, int y) { if ( hasData(x,y) ) return mLandRecords[x][y].normals; - static std::vector 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 parseHeights(LAND::VHGT* vhgt) { std::vector ph; ph.resize(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT); + + // heightOffset offsets the entire cell float offset = vhgt->heightOffset; - for (int y = 0; y < LAND_VERT_WIDTH; y++) { //code from MGE - offset += vhgt->heightData[y*LAND_VERT_WIDTH]; - ph[y*LAND_VERT_WIDTH] =+ (float)offset*8; - float pos = offset; - for (int x = 1; x < LAND_VERT_WIDTH; x++) { - pos += vhgt->heightData[y*LAND_VERT_WIDTH+x]; - ph[y*LAND_VERT_WIDTH+x] = pos*8; //flipped x + + for (int y = 0; y < LAND_VERT_WIDTH; y++) + { + // The first vertex in each row gives the difference relative + // to the last row start + offset += vhgt->heightData[y*LAND_VERT_WIDTH]; + ph[y*LAND_VERT_WIDTH] =+ (float)offset*8; + + float pos = offset; + for (int x = 1; x < LAND_VERT_WIDTH; x++) + { + // And each vertex within a row gives the difference + // relative to the previous one + pos += vhgt->heightData[y*LAND_VERT_WIDTH+x]; + ph[y*LAND_VERT_WIDTH+x] = pos*8; //flipped x + } } - } return ph; } + */ std::vector parseNormals( LAND::VNML* vnml ) { @@ -193,7 +207,7 @@ private: struct LandData { std::string source; //data file the data is from - std::vector heights; + VHGT *heights; std::vector normals; std::vector textures; }; diff --git a/terrain/cpp_materialgen.cpp b/terrain/cpp_materialgen.cpp deleted file mode 100644 index 6743bbb1a..000000000 --- a/terrain/cpp_materialgen.cpp +++ /dev/null @@ -1,371 +0,0 @@ -class TextureSplatter -{ -public: - inline TextureSplatter(const std::vector& 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& 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 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& ltex, - int size, - int border, - float scaleDiv, - std::list& 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 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 AlphaBuf; - std::map 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= 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(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& ltex, - int size, - float scaleDiv, - std::list& 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 textures; - for ( int y1 = 0; y1 < ltexWidth; y1++ ) { - for ( int x1 = 0; x1 < ltexWidth; x1++ ) { - textures.insert(ltex[(y1)*ltexWidth+x1]); - } - } - - for ( std::set::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(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 r) { - mTexturePaths = r; - } -private: - /** - * Merged records accross all mods for LTEX data - */ - std::map mTexturePaths; - - unsigned int mCount; -}; diff --git a/terrain/cpp_palette.cpp b/terrain/cpp_palette.cpp deleted file mode 100644 index dab2b3809..000000000 --- a/terrain/cpp_palette.cpp +++ /dev/null @@ -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& getPalette() { - return mPalette; - } -private: - typedef std::map Palette; - Palette mPalette; - - friend class boost::serialization::access; - /** - * @brief saves the palette - */ - template - inline void serialize(Archive& ar, const unsigned int version){ - ar &mPalette; - } -}; - -BOOST_CLASS_TRACKING(TexturePalette, boost::serialization::track_never); diff --git a/terrain/cpp_quad.cpp b/terrain/cpp_quad.cpp index 7bc514d91..c3d6c1dca 100644 --- a/terrain/cpp_quad.cpp +++ b/terrain/cpp_quad.cpp @@ -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 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; + // Do we have a parent? + if(parent != NULL) + { + mLevel = parent->mLevel-1; - //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 ) + 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(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"); - destroyTerrain(); - for (size_t i = 0; i < NUM_CHILDREN; i++) - delete mChildren[i]; + 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"); - destroyTerrain(); + RTRACE("split"); - //create a new terrain + // Never split a static quad or a quad that already has children. + assert(!isStatic); + assert(!hasChildren); + assert(mLevel > 1); + + if(hasMesh) + destroyTerrain(); + + // Find the cell width of our children + int cWidth = 1 << (mLevel-2); + + // Create children for ( size_t i = 0; i < NUM_CHILDREN; ++i ) - mChildren[i] = new Quad((QuadLocation)i, this); + { + if(!mInfo->hasChild[i]) + continue; - assert(!needUnsplit()); + // 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++ ) - delete mChildren[i]; - memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN); + // 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); - if ( mHasData ) + for( size_t i = 0; i < NUM_CHILDREN; i++ ) { - buildTerrain(); - justUnsplit(); + delete mChildren[i]; + mChildren[i] = NULL; } - assert(!needSplit()); + buildTerrain(); + + hasChildren = false; } - /** - * @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; + { + const Ogre::Vector3 cpos = mCamera->getDerivedPosition(); + Ogre::Vector3 diff(0, 0, 0); + diff.makeFloor(cpos - mBounds.getMinimum() ); + diff.makeCeil(cpos - mBounds.getMaximum() ); + camDist = diff.squaredLength(); + } + + // No children? + if(!hasChildren) { - for (size_t i=0;i< NUM_CHILDREN;i++) + // If we're close, split now. + if(camDist < mSplitDistance) + split(); + else { - if ( mChildren[i]->hasData() ) - { - if ( !mChildren[i]->hasMesh() ) - return false; - else if ( mChildren[i]->getSplitState() != SS_UNSPLIT) - return false; - } + // We're not close, and don't have any children. Should we + // built terrain? + if(!hasMesh) + buildTerrain(); + + return; } - return true; } - //get depth ensures the root doesn't try and unsplit - if ( getDepth() && !hasData() ) - return true; + // If we get here, we either had children when we entered, or we + // just performed a split. + assert(!hasMesh); + assert(hasChildren); - return false; + // 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(); + } } - /** - * @brief constructs the terrain on this level. The terrain must on - * exist before hand - */ + // 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 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 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 }; diff --git a/terrain/cpp_quaddata.cpp b/terrain/cpp_quaddata.cpp deleted file mode 100644 index 15741f060..000000000 --- a/terrain/cpp_quaddata.cpp +++ /dev/null @@ -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 ResourceList; - typedef std::list::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& getHeightsRef() { - return mHeights; - } - inline float getVertex(int offset){ - return getHeightsRef().at(offset); - } - inline std::vector& 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& 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 mHeights; - std::vector mNormals; - - ///Holds the resources used by the quad - ResourceList mResources; - - std::vector 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 - 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); diff --git a/terrain/cpp_terrain.cpp b/terrain/cpp_terrain.cpp index 38fff11bc..a24f4c3e0 100644 --- a/terrain/cpp_terrain.cpp +++ b/terrain/cpp_terrain.cpp @@ -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 @@ -75,61 +74,82 @@ MaterialGenerator *g_materialGen; #include #include #include -#include -#include -#include -#include -#include -#include + +// 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); } diff --git a/terrain/cpp_terrainmesh.cpp b/terrain/cpp_terrainmesh.cpp index 2d1005ade..6c2b091fc 100644 --- a/terrain/cpp_terrainmesh.cpp +++ b/terrain/cpp_terrainmesh.cpp @@ -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 - { - //check sizes - const float qw = mQuadData->getVertexWidth()-1; - const float fsw = qw*segSize; - const int isw = (int)fsw; - assert(fsw==isw); - } -#endif + // 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); - //precalc offsets, as getVertex/getNormal get called a lot (1000s of times) - computeOffsets(); + // Split all this off into sub-functions again later when you're + // finished. - // From Quad - node = parent->createChildSceneNode(pos); + // Use MW coordinates all the way + mBounds.setExtents(0,0,info.minHeight, + // was (mWidth-1) * vertexSeparation + info.worldWidth, info.worldWidth, + info.maxHeight); - // From create() - createVertexBuffer(); - calculateVertexValues(); - calculateIndexValues(); - setBounds(); + mCenter = mBounds.getCenter(); + mBoundingRadius = mBounds.getHalfSize().length(); - node->attachObject(this); + // 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; - createMaterial(); + VertexDeclaration* vertexDecl = mVertices->vertexDeclaration; + size_t currOffset = 0; - if ( g_heightMap->isMorphingEnabled() && - mDepth != g_heightMap->getMaxDepth() ) + 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 + (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 + (mIndices->indexBuffer->lock + (0, mIndices->indexBuffer->getSizeInBytes(), + HardwareBuffer::HBL_DISCARD)); + info.fillIndexBuffer(indices); + mIndices->indexBuffer->unlock(); + + // Finally, create the material + const std::string texName = info.getTexName(); + + // TODO: A better thing to do here is to keep the material loaded + // and retrieve it if it exists. + assert(!MaterialManager::getSingleton().resourceExists(texName)); + mMaterial = MaterialManager::getSingleton().create + (texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + Pass* pass = mMaterial->getTechnique(0)->getPass(0); + pass->setLightingEnabled(false); + + int lev = info.getLevel(); + if(lev != 1) { - Ogre::Technique* tech = getMaterial()->getTechnique(0); - for ( size_t i = 0; i < tech->getNumPasses(); ++i ) + // This material just has a normal texture + pass->createTextureUnitState(texName) + //->setTextureAddressingMode(TextureUnitState::TAM_CLAMP) + ; + } + else + { + // We have to use alpha splatting + float scale = info.getTexScale(); + + // 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; tnumisMorphingEnabled()); - 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(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(xgetVertex((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& 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 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(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+xgetNormal((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(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(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; }; diff --git a/terrain/terrain.d b/terrain/terrain.d index 46d96a1a1..6e366b7bb 100644 --- a/terrain/terrain.d +++ b/terrain/terrain.d @@ -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(); diff --git a/util/c_mmfile.d b/util/c_mmfile.d new file mode 100644 index 000000000..2c69bca05 --- /dev/null +++ b/util/c_mmfile.d @@ -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; +} diff --git a/util/mmfile.h b/util/mmfile.h new file mode 100644 index 000000000..88b631b2f --- /dev/null +++ b/util/mmfile.h @@ -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; +}; diff --git a/util/outbuffer.h b/util/outbuffer.h new file mode 100644 index 000000000..a25659ea7 --- /dev/null +++ b/util/outbuffer.h @@ -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= 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 + T* write(size_t num) + { + return (T*)add(num*sizeof(T)); + } + + void deallocate() + { + for(int i=0;i buffers; + std::vector 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; +} +