From 4f5b9d019a89d4dfaf30a089ae10776351c63fae Mon Sep 17 00:00:00 2001 From: nkorslund Date: Sat, 6 Jun 2009 12:02:18 +0000 Subject: [PATCH] Rewrote more of the terrain system in D git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@121 ea6a568a-9f4f-0410-981a-c910a81bb256 --- core/resource.d | 23 +- esm/loadcell.d | 68 ++- esm/loadltex.d | 6 +- scene/celldata.d | 15 - terrain/bindings.d | 17 + terrain/cpp_archive.cpp | 463 -------------- terrain/cpp_esm.cpp | 167 ----- terrain/cpp_generator.cpp | 1208 ------------------------------------- terrain/cpp_landdata.cpp | 223 ------- terrain/cpp_quad.cpp | 302 ---------- terrain/cpp_terrain.cpp | 161 ++--- terrain/esmland.d | 149 +++++ terrain/generator.d | 11 +- terrain/quad.d | 295 +++++++++ terrain/terrain.d | 5 +- 15 files changed, 570 insertions(+), 2543 deletions(-) create mode 100644 terrain/bindings.d delete mode 100644 terrain/cpp_archive.cpp delete mode 100644 terrain/cpp_esm.cpp delete mode 100644 terrain/cpp_generator.cpp delete mode 100644 terrain/cpp_landdata.cpp delete mode 100644 terrain/cpp_quad.cpp create mode 100644 terrain/esmland.d create mode 100644 terrain/quad.d diff --git a/core/resource.d b/core/resource.d index 62a541040..4747c16d3 100644 --- a/core/resource.d +++ b/core/resource.d @@ -48,7 +48,6 @@ import sound.sfx; import nif.nif; import core.filefinder; -//import core.config; // These are handles for various resources. They may refer to a file // in the file system, an entry in a BSA archive, or point to an @@ -255,6 +254,9 @@ struct ResourceManager char[80] texBuffer; + // Checks the BSAs / file system for a given texture name. Tries + // various Morrowind-specific hacks, like changing the extension to + // .dds and adding a 'textures\' prefix. Does not load the texture. TextureIndex lookupTexture(char[] id) { if(dummy) return null; @@ -318,15 +320,19 @@ struct ResourceManager char[] tmp; if(id.length < 70) { + // Avoid memory allocations if possible. tmp = texBuffer[0..9+id.length]; tmp[9..$] = id; } - else - tmp = "textures\\" ~ id; + else + { + tmp = "textures\\" ~ id; + writefln("WARNING: Did an allocation on %s", tmp); + } searchWithDDS(tmp); - // Not found? If so, try without the 'texture\' + // Not found? Try without the 'texture\' if(ti.bsaIndex == -1) { tmp = tmp[9..$]; @@ -469,18 +475,9 @@ struct TextureResource { return bsaIndex == -1; } - - /*KILLME - // Returns true if resource is loaded - bool isLoaded() - { - return ml != null; - } - */ } // OLD STUFF - /+ void initResourceManager() diff --git a/esm/loadcell.d b/esm/loadcell.d index fafc9ef2d..b774f1e6d 100644 --- a/esm/loadcell.d +++ b/esm/loadcell.d @@ -26,6 +26,11 @@ module esm.loadcell; import esm.imports; import esm.loadregn; +import std.math : abs; + +int max(int x, int y) +{ return x>=y?x:y; } + /* Cells can be seen as a collection of several records, and holds * data about objects, creatures, statics and landscape (for exterior * cells). This data can be huge, and has to be loaded when we need @@ -88,9 +93,9 @@ struct ExteriorCell Region* region; - // We currently don't load all cell data (these can be huge!), - // anyway it makes sense to only load these when accessed. Use this - // to reopen the file later. + // We currently don't load all cell data (these can be huge!), and + // it makes sense to only load these when accessed. Use this to + // reopen the file later. TES3FileContext context; // Landscape and path grid data @@ -99,6 +104,12 @@ struct ExteriorCell PathGrid paths; + // Return true if we have land data + bool hasLand() + { + return land.state == LoadState.Loaded && land.hasData; + } + void load() {with(esFile){ this.region = getHNOPtr!(Region)("RGNN", regions); @@ -118,6 +129,10 @@ struct ExteriorCell // Land can also be here instead. In fact, it can be both // places. I have to figure out what it means. if(isNextHRec("LAND")) land.load(); + + if(land.state == LoadState.Loaded) + if(gridX != land.X || gridY != land.Y) + writefln("WARNING: Grid mismatch at %s,%s!", gridX, gridY); }} } @@ -145,6 +160,10 @@ class CellList : ListKeeper HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells; HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells; + // Store the maximum x or y coordinate (in absolute value). This is + // used in the landscape pregen process. + int maxXY; + this() { in_cells = in_cells.init; @@ -176,6 +195,12 @@ class CellList : ListKeeper return ex_cells.getPtr(compound(x,y)); } + // Check whether we have a given exterior cell + bool hasExt(int x, int y) + { + return ex_cells.inList(compound(x,y)); + } + void *lookup(char[] s) { assert(0); } @@ -203,7 +228,6 @@ class CellList : ListKeeper } uint length() { return numInt() + numExt(); } - uint numInt() { return in_cells.length; } uint numExt() { return ex_cells.length; } @@ -223,7 +247,8 @@ class CellList : ListKeeper {with(esFile){ char[] id = getHNString("NAME"); - // Just ignore this, don't know what it does. + // Just ignore this, don't know what it does. I assume it + // deletes the cell, but we can't handle that yet. if(isNextSub("DELE")) getHInt(); readHNExact(&data, data.sizeof, "DATA"); @@ -244,7 +269,7 @@ class CellList : ListKeeper // Overloading an existing cell { if(p.state != LoadState.Previous) - fail("Duplicate internal cell " ~ id); + fail("Duplicate interior cell " ~ id); assert(id == p.id); p.load(); @@ -266,6 +291,9 @@ class CellList : ListKeeper p.gridY = data.gridY; p.load(); p.state = LoadState.Loaded; + + int mx = max(abs(p.gridX), abs(p.gridY)); + maxXY = max(maxXY, mx); } else { @@ -282,34 +310,46 @@ class CellList : ListKeeper } /* - * Landscape data. The landscape is stored as a hight map, and that is - * pretty much all I know at the moment. + * Landscape data. */ struct Land { LoadState state; - uint flags; // ?? - only first four bits seem to be used? + uint flags; // ?? - only first four bits seem to be used + + // Map coordinates. + int X, Y; TES3FileContext context; + bool hasData = false; + void load() {with(esFile){ - getHNUlong("INTV"); // Grid location. See next line. - //writefln("x=%d, y=%d", *(cast(int*)&grid), *(cast(int*)&grid+1)); + // Get the grid location + ulong grid = getHNUlong("INTV"); + X = grid & uint.max; + Y = (grid >> 32); flags = getHNInt("DATA"); // Save file position getContext(context); + int cnt; + // Skip these here. Load the actual data when the cell is loaded. - if(isNextSub("VNML")) skipHSubSize(12675); - if(isNextSub("VHGT")) skipHSubSize(4232); + if(isNextSub("VNML")) {skipHSubSize(12675);cnt++;} + if(isNextSub("VHGT")) {skipHSubSize(4232);cnt++;} if(isNextSub("WNAM")) skipHSubSize(81); if(isNextSub("VCLR")) skipHSubSize(12675); - if(isNextSub("VTEX")) skipHSubSize(512); + if(isNextSub("VTEX")) {skipHSubSize(512);cnt++;} + + // We need all three of VNML, VHGT and VTEX in order to use the + // landscape. + hasData = (cnt == 3); if(state == LoadState.Loaded) writefln("Duplicate landscape data in " ~ getFilename()); diff --git a/esm/loadltex.d b/esm/loadltex.d index 9e8dba209..8028730f7 100644 --- a/esm/loadltex.d +++ b/esm/loadltex.d @@ -50,7 +50,7 @@ class LandTextureList : ListKeeper // Contains the list of land textures for each file, indexed by // file. TODO: Use some handle system here too instead of raw // filename? - HashTable!(char[], TextureList, ESMRegionAlloc) files; + HashTable!(char[], TextureList, ESMRegionAlloc, CITextHash) files; // The texture list for the current file TextureList current; @@ -62,11 +62,7 @@ class LandTextureList : ListKeeper // The first file (Morrowind.esm) typically needs a little more // than most others - current = esmRegion.getBuffer!(TextureIndex)(0,120); - - // Overkill, just leave it as it is - //files.rehash(resources.esm.length + resources.esp.length); } void load() diff --git a/scene/celldata.d b/scene/celldata.d index 97324cb5a..06d516f4f 100644 --- a/scene/celldata.d +++ b/scene/celldata.d @@ -192,21 +192,6 @@ class CellData loadReferences(); - // TODO: Set up landscape system here. - /* - with(esFile) - { - restoreContext(exCell.land.context, reg); - - // TODO: Not all of these will be present at all times - readHNExact(,12675, "VNML"); - readHNExact(,4232, "VHGT"); - readHNExact(,81, "WNAM"); - readHNExact(,12675, "VCLR"); - readHNExact(,512, "VTEX"); - } - */ - const float cellWidth = 8192; // TODO/FIXME: This is temporary diff --git a/terrain/bindings.d b/terrain/bindings.d new file mode 100644 index 000000000..4da9aef8d --- /dev/null +++ b/terrain/bindings.d @@ -0,0 +1,17 @@ +module terrain.bindings; + +alias void *SceneNode; +alias void *Bounds; +alias void *MeshObj; + +extern(C): + +SceneNode terr_createChildNode(float relX, float relY, SceneNode); +void terr_destroyNode(SceneNode); +Bounds terr_makeBounds(float minHeight, float maxHeight, float width); +float terr_getSqCamDist(Bounds); +MeshObj terr_makeMesh(int segment, SceneNode); +void terr_killMesh(MeshObj); + +void terr_genData(); +void terr_setupRendering(); diff --git a/terrain/cpp_archive.cpp b/terrain/cpp_archive.cpp deleted file mode 100644 index eaad1a91d..000000000 --- a/terrain/cpp_archive.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2009 Nicolay Korslund - WWW: http://openmw.sourceforge.net/ - - This file (cpp_archive.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - -*/ - -// Info about the entire quad. TODO: Some of this (such as the texture -// scale and probably the width and radius) can be generated at -// loadtime and is common for all quads on the same level. -struct QuadInfo -{ - // Basic info - int cellX, cellY; - int level; - - // Bounding box info - float minHeight, maxHeight; - float worldWidth; - float boundingRadius; - - // Texture scale for this quad - float texScale; - - // True if we should make the given child - bool hasChild[4]; - - // Number of mesh segments in this quad - int meshNum; - - // Location of this quad in the main archive file. The size includes - // everything related to this quad, including mesh data, alpha maps, - // etc. - size_t offset, size; -}; - -struct MeshInfo; - -const static int CACHE_MAGIC = 0x345AF815; - -struct ArchiveHeader -{ - // "Magic" number to make sure we're actually reading an archive - // file - int magic; - - // Total number of quads in the archive - int quads; - - // Level of the 'root' quad. There will only be one quad on this - // level. - int rootLevel; - - // Size of the alpha maps, in pixels along one side. - int alphaSize; - - // Number of strings in the string table - int stringNum; - - // Size of the string buffer - size_t stringSize; -}; - -// This class handles the cached terrain data. -class TerrainArchive -{ -public: - MeshInfo *curMesh; - QuadInfo *curQuad; - - QuadInfo *rootQuad; - - TerrainArchive() - : mappedPtr(0), - mappedSize(0), - mmf(0), - curMesh(0), - curQuad(0), - rootQuad(0) {} - - void openFile(const std::string &name) - { - mmf = new MmFile(name); - - // Read the index file first - std::ifstream ifile((name+".index").c_str(), std::ios::binary); - - ArchiveHeader head; - ifile.read((char*)&head, sizeof(head)); - - // Sanity check - assert(head.magic == CACHE_MAGIC); - assert(head.quads > 0 && head.quads < 8192); - - // Store header info - alphaSize = head.alphaSize; - - // Read all the quads - quadList = new QuadInfo[head.quads]; - ifile.read((char*)quadList, sizeof(QuadInfo)*head.quads); - - // Create an index of all the quads - for(int qn = 0; qn < head.quads; qn++) - { - int x = quadList[qn].cellX; - int y = quadList[qn].cellY; - int l = quadList[qn].level; - - assert(l >= 1); - - quadMap[l][x][y] = qn; - - // Store the root quad - if(l == head.rootLevel) - { - assert(rootQuad == NULL); - rootQuad = &quadList[qn]; - } - else - assert(l < head.rootLevel); - } - - // Make sure the root was set - assert(rootQuad != NULL); - - // Next read the string table - stringBuf = new char[head.stringSize]; - stringOffsets = new int[head.stringNum]; - stringNum = head.stringNum; - - // First read the main string buffer - ifile.read(stringBuf, head.stringSize); - - // Then read the string offsets - ifile.read((char*)stringOffsets, stringNum*sizeof(int)); - - // Read the vertex buffer data - int bufNum = head.rootLevel; - assert(bufNum == 7); - vertBufData.resize(bufNum); - indexBufData.resize(bufNum); - - // Fill the buffers. Start at level 1. - for(int i=1;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_esm.cpp b/terrain/cpp_esm.cpp deleted file mode 100644 index aef90a8cf..000000000 --- a/terrain/cpp_esm.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - Copyright (c) Jacob Essex 2009 - - This file is part of MWLand. - - MWLand is free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MWLand is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MWLand. If not, see . -*/ - -///generic subrecord -struct SubRecord -{ - SubRecord(){} - SubRecord(const std::string& n, const std::string& d) : subName(n), subData(d){} - std::string subName; - std::string subData; -}; - -///generic record -class Record { -public: - Record(const std::string& type) - : mType(type) {} - - inline void addSubRecord(const std::string& id, - const SubRecord& subRecord) - {mSubRecords[id] = subRecord; } - - std::string getSubRecordData(const std::string& recordName) - { - SubRecordMapItr itr = mSubRecords.find(recordName ); - if ( itr != mSubRecords.end() ) - return itr->second.subData; - return std::string(""); - } - - bool hasSubRecord(const std::string& recordName) - { - if ( mSubRecords.find(recordName ) != mSubRecords.end() ) - return true; - return false; - } - - inline const std::string& getID() { return mId; } - inline void setID( const std::string& id) { mId = id;} - const std::string& getType(){return mType;} - -private: - typedef std::map SubRecordMap; - typedef SubRecordMap::iterator SubRecordMapItr; - SubRecordMap mSubRecords; - - std::string mType; - std::string mId; - -}; - -typedef std::list RecordList; -typedef RecordList::iterator RecordListItr; - -typedef std::map RecordMap; -typedef RecordMap::iterator RecordMapItr; - -///top level class for loading and saving esp files. -class ESM -{ -private: - /// types of records to load - std::map mLoadTypes; - - /// map of the record - RecordMap mRecords; - - ///checks if the given type should be loaded - inline bool loadType(const std::string& t) - { - return ( mLoadTypes.find(t) != mLoadTypes.end() ); - } - -public: - inline void addRecordType(const std::string& t, - const std::string& i = "NAME") - { mLoadTypes[t] = i; } - - bool loadFile(const std::string& file) - { - std::ifstream esp (file.c_str(), std::ios::in | std::ios::binary); - - if ( !esp.is_open() ) return false; //check open - - esp.seekg(4); - - long hdrSize; //get offset for start of data - esp.read ((char *)&hdrSize, sizeof(long)); - - //get num records - esp.seekg(16 + 8 + 296); - long numRecords; - esp.read ((char *)&numRecords, sizeof(long)); - - esp.seekg(hdrSize + 16); //go to end of header - - for ( long i = 0; i < numRecords; i++ ){ - - char type[5]; - esp.get(type, 5); - type[4] = '\0'; - - long recordSize; - esp.read ((char *)&recordSize, 4); - esp.seekg(8, std::ofstream::cur); - long endPos = recordSize + esp.tellg(); - - if ( loadType(type) ) { - Record* record = new Record(type); - - //load all subrecords - while ( esp.tellg() < endPos ) { - char subType[5]; - esp.get(subType, 5); - - long subRecLength; - esp.read ((char *)&subRecLength, 4); - - long subRecEnd = subRecLength + esp.tellg(); - char* subRecData = new char[subRecLength]; - esp.read(subRecData, subRecLength); - - record->addSubRecord(subType, SubRecord(subType,std::string(subRecData, subRecLength))); - delete [] subRecData; - - assert(subRecEnd==esp.tellg()); - } - record->setID(record->getSubRecordData(mLoadTypes[type])); - mRecords[record->getSubRecordData(mLoadTypes[type])] = record; - }else{ - esp.seekg(endPos); - } - assert(endPos==esp.tellg()); - - } - esp.close(); - - return true; - } - - inline Record* getRecord(const std::string& id){ return mRecords[id]; } - - RecordList* getRecordsByType(const std::string& t) - { - RecordList* r = new RecordList; - for ( RecordMapItr iter = mRecords.begin(); iter != mRecords.end(); ++iter) - if ( t == iter->second->getType() ) - r->push_back(iter->second); - return r; - } -}; diff --git a/terrain/cpp_generator.cpp b/terrain/cpp_generator.cpp deleted file mode 100644 index ece115574..000000000 --- a/terrain/cpp_generator.cpp +++ /dev/null @@ -1,1208 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2009 Jacob Essex, Nicolay Korslund - WWW: http://openmw.sourceforge.net/ - - This file (cpp_generator.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/ . - -*/ - -typedef uint8_t ubyte; -typedef uint16_t ushort16; - -struct GenLevelResult -{ -private: - bool isAlpha; - -public: - QuadHolder quad; - PixelBox image; - bool hasMesh; - - GenLevelResult() - { - image.data = NULL; - isAlpha = false; - hasMesh = false; - } - - ~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(Record* record, const std::string& source) - { mMWLand.addLandTextureData(record, source); } - - 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); - max = std::max(mMWLand.getMaxY(), max); - max = std::max(-mMWLand.getMinX(), max); - max = std::max(-mMWLand.getMinY(), max); - - // Round up to nearest binary - int depth=1; - while(max) - { - 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"; - } - - // Generates the default texture images "2_default.png" etc - void genDefaults() - { - TRACE("genDefaults"); - - int size = texSizes.size()-1; - defaults.resize(size); - - for(int i=1; i 2); - genLevel2Map(NULL, defaults[2]); - - for(int i=3; i=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 offs=y*65+x; - - // The vertex data from the ESM - char data = verts.heightData[offs]; - - // 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) - { - // Set the height to the row height - height = rowheight; - - // First value in each row adjusts the row height - rowheight += data; - } - // Adjust the height from the previous value - height += data; - - // Calculate the min and max - max = std::max(max, height); - min = std::min(min, height); - - // 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); - isDef = false; - } - - // Store the final index - int index = cache.addTexture(texturePath); - ltex[ty][tx] = index; - - // Add the index to the map - if(textures.find(index) == textures.end()) - textures[index] = local++; - } - assert(local == textures.size()); - - // If we still only found default textures, exit now. - if(isDef) - return; - - const int imageRes = texSizes[1]; - const int dataSize = imageRes*imageRes; - const int texNum = textures.size(); - - // 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++) - { - // 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 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; - - // 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; - - const std::string tn(cache.getString(gIndex)); - if ( tn == "_land_default.dds" ) - continue; - - // 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()); - Ogre::TextureManager::getSingleton().remove(tex1->getHandle()); - 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); - } - - 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); - - //output pass - Ogre::CompositionTargetPass* ctp = cp->createTechnique()->getOutputTargetPass(); - Ogre::CompositionPass* cpass = ctp->createPass(); - cpass->setType(Ogre::CompositionPass::PT_RENDERQUAD); - cpass->setMaterial(mp); - - //create a texture to write the texture to... - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton(). - createManual( - name, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - texSize, - texSize, - 0, - tt, - Ogre::TU_RENDERTARGET - ); - - Ogre::RenderTexture* renderTexture = texture->getBuffer()->getRenderTarget(); - Ogre::Viewport* vp = renderTexture->addViewport(mCamera); - - Ogre::CompositorManager::getSingleton().addCompositor(vp, "Rtt_Comp"); - Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp,"Rtt_Comp", true); - - renderTexture->update(); - - // Call the OGRE renderer. - Ogre::Root::getSingleton().renderOneFrame(); - - Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp"); - Ogre::CompositorManager::getSingleton().remove(cp->getHandle()); - - renderTexture->removeAllViewports(); - - return texture; - } -}; diff --git a/terrain/cpp_landdata.cpp b/terrain/cpp_landdata.cpp deleted file mode 100644 index 963757a5f..000000000 --- a/terrain/cpp_landdata.cpp +++ /dev/null @@ -1,223 +0,0 @@ -struct VHGT -{ ///height data - float heightOffset; - char heightData[LAND_NUM_VERTS]; - short unknown1; - char unknown2; -}; - -class MWLand -{ -public: - MWLand() - { - mMaxX = mMaxY = mMinX = mMinY = 0; - } - - void addLandTextureData(Record* record, const std::string& source) - { - LandTexture l; - l.name = record->getSubRecordData("NAME"); - l.data = record->getSubRecordData("DATA"); - l.intv = *((short*) record->getSubRecordData("INTV").c_str()); - mLTEXRecords[source][l.intv] = l; - } - - void addLandData(Record* record, const std::string& source) - { - if ( !record->hasSubRecord("VHGT") || !record->hasSubRecord("VTEX") ) //ensure all records exist - return; - - //copy these, else we end up with invliad data - LAND::INTV intv = *(LAND::INTV*)record->getSubRecordData("INTV").c_str(); - VHGT *vhgt = (VHGT*) record->getSubRecordData("VHGT").c_str(); - LAND::VNML vnml = *(LAND::VNML*)record->getSubRecordData("VNML").c_str(); - LAND::VTEX vtex = *(LAND::VTEX*)record->getSubRecordData("VTEX").c_str(); - - // FIXME: What happens to the memory allocation of vhgt here? - // Doesn't matter much, we're killing this entire file soon - // anyway. - mLandRecords[intv.x][intv.y].heights = vhgt; - mLandRecords[intv.x][intv.y].normals = parseNormals(&vnml); - mLandRecords[intv.x][intv.y].textures = parseTextures(&vtex); - mLandRecords[intv.x][intv.y].source = source; - - mMaxX = std::max(mMaxX, intv.x); - mMaxY = std::max(mMaxY, intv.y); - mMinX = std::min(mMinX, intv.x); - mMinY = std::min(mMinY, intv.y); - } - - ///Maximum distance of a cell on the X plane from grid pos 0 in the positive direction - inline int getMaxX() const { return mMaxX; } - ///Maximum distance of a cell on the Y plane from grid pos 0 in the positvie direction - inline int getMaxY() const { return mMaxY; } - ///Maximum distance of a cell on the X plane from grid pos 0 in the negative direction - inline int getMinX() const { return mMinX; } - ///see others - inline int getMinY() const { return mMinY; } - - inline VHGT *getHeights(int x, int y) - { - if ( hasData(x,y) ) - return mLandRecords[x][y].heights; - assert(0); - } - - inline std::vector& getNormals(int x, int y) - { - if ( hasData(x,y) ) - return mLandRecords[x][y].normals; - assert(0); - } - - inline const std::string& getSource(int x, int y) - { - assert(hasData(x,y)); - return mLandRecords[x][y].source; - } - - inline bool hasData(int x, int y) const - { - std::map >::const_iterator itr = mLandRecords.find(x); - if ( itr == mLandRecords.end() ) - return false; - if ( itr->second.find(y) == itr->second.end() ) - return false; - return true; - } - - - inline std::string& getLTEXRecord(const std::string& source, short i) - { - return mLTEXRecords[source][i].data; - } - - inline bool hasLTEXRecord(const std::string& source, short index) const - { - std::map >::const_iterator itr = mLTEXRecords.find(source); - if ( itr == mLTEXRecords.end() ) - return false; - if ( itr->second.find(index) == itr->second.end() ) - return false; - return true; - } - - inline short getLTEXIndex(int x, int y, int pos) - { - assert(hasData(x,y)); - return mLandRecords[x][y].textures[pos]; - } - - inline short getLTEXIndex(int x1, int y1, int x2, int y2) - { - return getLTEXIndex(x1, y1, y2*LAND_LTEX_WIDTH+x2); - } - -private: - - ///the min/max size of cells - int mMaxY, mMinY; - int mMaxX, mMinX; - - // Land structure as held in the ESM - struct LAND { - struct INTV { /// x, y grid pos of the cell - long x; - long y; - }; - struct VNML { ///vertex normal data - struct NORMAL { - char x; - char y; - char z; - }; - NORMAL normals[LAND_NUM_VERTS]; - }; - struct VTEX { ///splat texture data - short index[LAND_NUM_LTEX]; - }; - INTV* intv; - VNML* vnml; - VHGT* vhgt; - VTEX* vtex; - }; - - /* - std::vector 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++) - { - // 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 ) - { - std::vector n; - n.resize(LAND_NUM_VERTS*3,0); - for ( int y = 0; y < LAND_VERT_WIDTH; y++ ) { //this could just be cast. - for ( int x = 0; x < LAND_VERT_WIDTH; x++ ) { //as a vector is a continus segment of mem... - n[(y*LAND_VERT_WIDTH+x)*3] = vnml->normals[y*LAND_VERT_WIDTH+x].x; - n[(y*LAND_VERT_WIDTH+x)*3+1] = vnml->normals[y*LAND_VERT_WIDTH+x].y; - n[(y*LAND_VERT_WIDTH+x)*3+2] = vnml->normals[y*LAND_VERT_WIDTH+x].z; - } - } - return n; - } - - std::vector parseTextures( LAND::VTEX* vtex ) - { - std::vector t; - t.resize(LAND_NUM_LTEX,0); - - //thanks to timeslip (MGE) for the code - int rpos = 0; //bit ugly, but it works - for ( int y1 = 0; y1 < 4; y1++ ) - for ( int x1 = 0; x1 < 4; x1++ ) - for ( int y2 = 0; y2 < 4; y2++) - for ( int x2 = 0; x2 < 4; x2++ ) - t[(y1*4+y2)*16+(x1*4+x2)]=vtex->index[rpos++]; - return t; - } - - /** - * Holds the representation of a cell in the way that is most usefull to me - */ - struct LandData - { - std::string source; //data file the data is from - VHGT *heights; - std::vector normals; - std::vector textures; - }; - - struct LandTexture - { - std::string name, data; - short intv; - }; - - std::map > mLTEXRecords; - std::map > mLandRecords; -}; diff --git a/terrain/cpp_quad.cpp b/terrain/cpp_quad.cpp deleted file mode 100644 index c3d6c1dca..000000000 --- a/terrain/cpp_quad.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/** - * defines an area of Landscape - * - * A quad can either hold a mesh, or 4 other sub quads The functions - * split and unsplit either break the current quad into smaller quads, - * or alternatively remove the lower quads and create the terrain mesh - * on the the current (now the lowest) level - */ -/* Previously for MeshInterface: - * Interface between the quad and the terrain renderble classes, to the - * quad it looks like this rendereds a single mesh for the quad. This - * may not be the case. - * - * It also could allow several optimizations (e.g. multiple splits) - */ - -class Quad -{ - typedef std::list MeshList; - -public: - - Quad(int cellX=0, int cellY=0, Quad* parent = NULL) - : mCellX(cellX), - mCellY(cellY) - { - RTRACE("Quad"); - - memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN); - - hasMesh = false; - hasChildren = false; - isStatic = false; - - // Do we have a parent? - if(parent != NULL) - { - mLevel = parent->mLevel-1; - - if(mLevel == 1) - { - // Create the terrain and leave it there. - buildTerrain(); - isStatic = true; - } - - // Coordinates relative to our parent - const int relX = cellX - parent->mCellX; - const int relY = cellY - parent->mCellY; - - // The coordinates give the top left corner of the quad, or our - // relative coordinates within that should always be positive. - assert(relX >= 0); - assert(relY >= 0); - - // Create a child scene node. The scene node position is given in - // world units, ie. CELL_WIDTH units per cell. - const Ogre::Vector3 pos(relX * CELL_WIDTH, - relY * CELL_WIDTH, - 0); - mSceneNode = parent->mSceneNode->createChildSceneNode(pos); - - // Get the archive data for this quad. - mInfo = g_archive.getQuad(mCellX,mCellY,mLevel); - } - else - { - // No parent, this is the top-most quad. Get all the info from - // the archive. - mInfo = g_archive.rootQuad; - - mLevel = mInfo->level; - cellX = mCellX = mInfo->cellX; - cellY = mCellY = mInfo->cellY; - - const Ogre::Vector3 pos(cellX * CELL_WIDTH, - cellY * CELL_WIDTH, - 0); - mSceneNode = g_rootTerrainNode-> - createChildSceneNode(pos); - - // Split up - split(); - - // The root can never be unsplit - isStatic = true; - } - - assert(mLevel >= 1); - assert(mSceneNode != NULL); - - // Set up the bounding box. Use MW coordinates all the way - mBounds.setExtents(0,0,mInfo->minHeight, - mInfo->worldWidth,mInfo->worldWidth, - mInfo->maxHeight); - - // Transform the box to world coordinates, so it can be compared - // with the camera later. - mBounds.transformAffine(mSceneNode->_getFullTransform()); - - const float radius = mInfo->boundingRadius; - - mSplitDistance = radius * SPLIT_FACTOR; - mUnsplitDistance = radius * UNSPLIT_FACTOR; - - // Square the distances - mSplitDistance *= mSplitDistance; - mUnsplitDistance *= mUnsplitDistance; - - // Update the terrain. This will create the mesh or children if - // necessary. - update(); - } - - ~Quad() - { - RTRACE("~Quad"); - if(hasMesh) - destroyTerrain(); - else if(hasChildren) - for (size_t i = 0; i < NUM_CHILDREN; i++) - delete mChildren[i]; - - mSceneNode->removeAndDestroyAllChildren(); - mSceneMgr->destroySceneNode(mSceneNode); - } - - // Remove the landscape for this quad, and create children. - void split() - { - RTRACE("split"); - - // Never split a static quad or a quad that already has children. - assert(!isStatic); - assert(!hasChildren); - assert(mLevel > 1); - - if(hasMesh) - destroyTerrain(); - - // Find the cell width of our children - int cWidth = 1 << (mLevel-2); - - // Create children - for ( size_t i = 0; i < NUM_CHILDREN; ++i ) - { - if(!mInfo->hasChild[i]) - continue; - - // The cell coordinates for this child quad - int x = (i%2)*cWidth + mCellX; - int y = (i/2)*cWidth + mCellY; - - mChildren[i] = new Quad(x,y,this); - } - hasChildren = true; - } - - // Removes children and rebuilds terrain - void unsplit() - { - RTRACE("unsplit"); - - // Never unsplit the root quad - assert(mLevel < g_archive.rootQuad->level); - // Never unsplit a static or quad that isn't split. - assert(!isStatic); - assert(hasChildren); - assert(!hasMesh); - - for( size_t i = 0; i < NUM_CHILDREN; i++ ) - { - delete mChildren[i]; - mChildren[i] = NULL; - } - - buildTerrain(); - - hasChildren = false; - } - - // Determines whether to split or unsplit the quad, and immediately - // does it. - void update() - { - RTRACE("Quad::update"); - - // Static quads don't change - if(isStatic) - return; - - assert(mUnsplitDistance > mSplitDistance); - - // Get (squared) camera distance. TODO: shouldn't this just be a - // simple vector difference from the mesh center? - float camDist; - { - const Ogre::Vector3 cpos = mCamera->getDerivedPosition(); - Ogre::Vector3 diff(0, 0, 0); - diff.makeFloor(cpos - mBounds.getMinimum() ); - diff.makeCeil(cpos - mBounds.getMaximum() ); - camDist = diff.squaredLength(); - } - - // No children? - if(!hasChildren) - { - // If we're close, split now. - if(camDist < mSplitDistance) - split(); - else - { - // We're not close, and don't have any children. Should we - // built terrain? - if(!hasMesh) - buildTerrain(); - - return; - } - } - - // If we get here, we either had children when we entered, or we - // just performed a split. - assert(!hasMesh); - assert(hasChildren); - - // If the camera is too far away, kill the children. - if( camDist > mUnsplitDistance ) - { - unsplit(); - return; - } - - // We have children and we're happy about it. Update them too. - for (size_t i = 0; i < NUM_CHILDREN; ++i) - { - Quad *q = mChildren[i]; - if(q != NULL) q->update(); - } - } - - // Build the terrain for this quad - void buildTerrain() - { - RTRACE("buildTerrain"); - assert(!hasMesh); - assert(!isStatic); - - // Map the terrain data into memory. - g_archive.mapQuad(mInfo); - - // Create one mesh for each segment in the quad. TerrainMesh takes - // care of the loading. - for(int i=0; i < mInfo->meshNum; i++) - mMeshList.push_back(new TerrainMesh(i, mSceneNode)); - } - - /** - * @brief destroys the terrain. - */ - void destroyTerrain() - { - RTRACE("destroyTerrain"); - assert(hasMesh); - - for ( MeshList::iterator itr = mMeshList.begin(); - itr != mMeshList.end(); ++itr ) - delete *itr; - - mMeshList.clear(); - } - -private: - - // List of meshes, if any - MeshList mMeshList; - - // Scene node. All child quads are added to this. - SceneNode* mSceneNode; - - // Bounding box, transformed to world coordinates. Used to calculate - // camera distance. - Ogre::AxisAlignedBox mBounds; - - Ogre::Real mSplitDistance,mUnsplitDistance; - - static const size_t NUM_CHILDREN = 4; - - Quad* mChildren[NUM_CHILDREN]; ///optionaly the children. Should be - ///0 if not exist - - // Contains the 'level' of this node. Level 1 is the closest and - // most detailed level - int mLevel; - int mCellX, mCellY; - - QuadInfo *mInfo; - - bool hasMesh; - bool hasChildren; - bool isStatic; // Static quads are never split or unsplit -}; diff --git a/terrain/cpp_terrain.cpp b/terrain/cpp_terrain.cpp index a59f88d8c..e83c4b753 100644 --- a/terrain/cpp_terrain.cpp +++ b/terrain/cpp_terrain.cpp @@ -20,83 +20,9 @@ */ -///no texture assigned -const int LAND_LTEX_NONE = 0; - -///the default land height that it defaults to in the TESCS -const int LAND_DEFAULT_HEIGHT = -2048; - -///how many verts wide (and long) the cell is -const int LAND_VERT_WIDTH = 65; - -///Number of verts that make up a cell -const int LAND_NUM_VERTS = LAND_VERT_WIDTH*LAND_VERT_WIDTH; - -const int LAND_LTEX_WIDTH = 16; -const int LAND_NUM_LTEX = LAND_LTEX_WIDTH*LAND_LTEX_WIDTH; - -const int CELL_WIDTH = 8192; - -// Scaling factor to apply to textures on once cell. A factor of 1/16 -// gives one repetition per square, since there are 16x16 texture -// 'squares' in acell. For reference, Yacoby's scaling was equivalent -// to having 1.0/10 here, or 10 repititions per cell. TODO: This looks -// a little blocky. Compare with screenshots from TES-CS. -const float TEX_SCALE = 1.0/16; - -// Multiplied with the size of the quad. If these are too close, a -// quad might end up splitting/unsplitting continuously, since the -// quad size changes when we split. -const float SPLIT_FACTOR = 0.5; -const float UNSPLIT_FACTOR = 2.0; - -//stops it crashing, now it leaks. -#define ENABLED_CRASHING 0 - -class Quad; -class TerrainMesh; -class BaseLand; - -// Cache directory and file -std::string g_cacheDir; -std::string g_cacheFile; - -// Enable or disable tracing of runtime functions. Making RTRACE do a -// trace slows down the code significantly even when -debug is off, so -// lets disable it for normal use. -#define RTRACE(x) -//#define RTRACE TRACE - /* -// Prerequisites -#include -#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_esm.cpp" -#include "cpp_landdata.cpp" -#include "cpp_generator.cpp" - -// For rendering -Quad *g_rootQuad; -BaseLand *g_baseLand; -SceneNode *g_rootTerrainNode; - #include "cpp_baseland.cpp" #include "cpp_mesh.cpp" -#include "cpp_quad.cpp" class TerrainFrameListener : public FrameListener { @@ -112,67 +38,56 @@ protected: extern "C" void d_superman(); */ -extern "C" void terr_setCacheDir(char *cacheDir) +extern "C" { - g_cacheDir = cacheDir; - g_cacheFile = g_cacheDir + "terrain.cache"; -} -// Set up the rendering system -extern "C" void terr_setupRendering() -{ - /* - // Add the terrain directory - ResourceGroupManager::getSingleton(). - addResourceLocation(g_cacheDir, "FileSystem", "General"); + SceneNode* terr_createChildNode(float relX, float relY, + SceneNode *parent) + {} - // Create a root scene node first. The 'root' node is rotated to - // match the MW coordinate system - g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT"); + void terr_destroyNode(SceneNode *node) + {} - // Open the archive file - g_archive.openFile(g_cacheFile); + void *terr_makeBounds(float minHeight, float maxHeight, + float width) + {} - // Create the root quad. - g_rootQuad = new Quad(); + float terr_getSqCamDist(void*) + {} - g_baseLand = new BaseLand(g_rootTerrainNode); + void *terr_makeMesh(int segment, SceneNode*) + {} - // Add the frame listener - mRoot->addFrameListener(new TerrainFrameListener); + void terr_killMesh(void*) + {} - // Enter superman mode - mCamera->setFarClipDistance(32*CELL_WIDTH); - //ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH); - d_superman(); - */ -} - -/* -// Generate all cached data. -extern "C" void terr_genData() -{ - Ogre::Root::getSingleton().renderOneFrame(); - - Generator mhm; + // Set up the rendering system + void terr_setupRendering() { - ESM esm; + /* + // Add the terrain directory + ResourceGroupManager::getSingleton(). + addResourceLocation(g_cacheDir, "FileSystem", "General"); - const std::string fn("data/Morrowind.esm"); + // Create a root scene node first. The 'root' node is rotated to + // match the MW coordinate system + g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT"); - esm.addRecordType("LAND", "INTV"); - esm.addRecordType("LTEX", "INTV"); + // Open the archive file + g_archive.openFile(g_cacheFile); - esm.loadFile(fn); - RecordList* land = esm.getRecordsByType("LAND"); - for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr ) - mhm.addLandData(*itr, fn); + // Create the root quad. + g_rootQuad = new Quad(); - RecordList* ltex = esm.getRecordsByType("LTEX"); - for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr ) - mhm.addLandTextureData(*itr, fn); + g_baseLand = new BaseLand(g_rootTerrainNode); + + // Add the frame listener + mRoot->addFrameListener(new TerrainFrameListener); + + // Enter superman mode + mCamera->setFarClipDistance(32*CELL_WIDTH); + //ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH); + d_superman(); + */ } - - mhm.generate(g_cacheFile); } -*/ diff --git a/terrain/esmland.d b/terrain/esmland.d new file mode 100644 index 000000000..816e8ba3c --- /dev/null +++ b/terrain/esmland.d @@ -0,0 +1,149 @@ +module terrain.esmland; + +import esm.loadltex; +import esm.loadcell; +import util.regions; +import esm.filereader; + +const int LAND_NUM_VERTS = 65*65; + +MWLand mwland; + +// Interface to the ESM landscape data +struct MWLand +{ + RegionManager reg; + + // These structs/types represent the way data is actually stored in + // the ESM files. + + // Heightmap + align(1) + struct VHGT + { + float heightOffset; + byte heightData[LAND_NUM_VERTS]; + short unknown1; + char unknown2; + } + + // Normals + typedef byte[LAND_NUM_VERTS*3] VNML; + + // Land textures. This is organized in 4x4 buffers of 4x4 squares + // each. This is how the original engine splits up the cell meshes, + // and it's probably a good idea for us to do the same. + typedef short[4][4][4][4] VTEX; + + static assert(VHGT.sizeof == 4232); + static assert(VNML.sizeof == 12675); + static assert(VTEX.sizeof == 512); + + // Landscape data for one cell + struct LandData + { + VHGT vhgt; + VNML normals; + } + + // Texture data for one cell + struct LTEXData + { + // TODO: Store the source file here too, so we can get the list + // from the right file. The source file is the same as the one we + // load the landscape from in loadCell(). + VTEX vtex; + + // Get the texture x2,y2 from the sub map x1,x2 + char[] getTexture(int x1, int y1, int x2, int y2) + { + // Get the texture index relative to the current esm/esp file + short texID = vtex[y1][x1][y2][x2]; + + // Hack, will only work for Morrowind.esm. Fix this later. + auto tl = landTextures.files["Morrowind.esm"]; + + // Return the 'new' texture name. This name has automatically + // been converted to .dds if the .tga file was not found. + return tl[texID].getNewName(); + } + + // Get a texture from the 16x16 grid in one cell + char[] getTexture(int x, int y) + { + return getTexture(x/4,y/4,x%4,y%4); + } + } + + // Get the maximum absolute coordinate value in any direction + int getMaxCoord() + { return cells.maxXY; } + + // Does the given cell exist and does it have land data? + bool hasData(int x, int y) + { + // Does the cell exist? + if(!cells.hasExt(x,y)) + return false; + + // And does it have terrain data? + auto ex = cells.getExt(x,y); + return ex.hasLand(); + } + + LandData *getLandData(int x, int y) + { + loadCell(x, y); + return ¤tLand; + } + + LTEXData *getLTEXData(int x, int y) + { + loadCell(x, y); + return ¤tLtex; + } + + private: + + int currentX = -1234; + int currentY = 4321; + + LandData currentLand; + LTEXData currentLtex; + + // Make sure the given cell is loaded + void loadCell(int x, int y) + { + // If the right cell is already loaded, don't do anything + if(x == currentX && y == currentY) + return; + + assert(hasData(x,y)); + + currentX = x; + currentY = y; + + // Get the file context for the terrain data. This can be used to + // skip to the right part of the ESM file. + auto cont = cells.getExt(x,y).land.context; + + // We should use an existing region later, or at least delete this + // once we're done with the gen process. + if(reg is null) + reg = new RegionManager(); + + // Open the ESM at this cell + esFile.restoreContext(cont, reg); + + // Store the data + esFile.readHNExact(currentLand.normals.ptr, + currentLand.normals.length, "VNML"); + esFile.readHNExact(¤tLand.vhgt, VHGT.sizeof, "VHGT"); + + // These aren't used yet + if(esFile.isNextSub("WNAM")) esFile.skipHSubSize(81); + if(esFile.isNextSub("VCLR")) esFile.skipHSubSize(12675); + + esFile.readHNExact(¤tLtex.vtex, VTEX.sizeof, "VTEX"); + } +} diff --git a/terrain/generator.d b/terrain/generator.d index 7e17d10cd..e40003081 100644 --- a/terrain/generator.d +++ b/terrain/generator.d @@ -27,16 +27,15 @@ module terrain.generator; /+ import std.stdio; import std.string; + import terrain.cachewriter; +import terrain.esmland; import util.cachefile; const float TEX_SCALE = 1.0/16; char[] cacheDir = "cache/terrain/"; -// Interface to the ESM landscape data -MWLand mwland; - int mCount; // Texture sizes for the various levels. For the most detailed level @@ -328,11 +327,11 @@ void genLevel1Meshes(ref GenLevelResult res) mi.worldWidth = vertSep*intervals; assert(mi.worldWidth == 8192); - auto land = mwland.getData(cellX, cellY); + auto land = mwland.getLandData(cellX, cellY); - byte[] heightData = land.heights; + byte[] heightData = land.vhgt.heights; byte[] normals = land.normals; - mi.heightOffset = land.heightOffset; + mi.heightOffset = land.vhgt.heightOffset; float max=-1000000.0; float min=1000000.0; diff --git a/terrain/quad.d b/terrain/quad.d new file mode 100644 index 000000000..1642f3625 --- /dev/null +++ b/terrain/quad.d @@ -0,0 +1,295 @@ +module terrain.quad; + +import terrain.archive; +import terrain.bindings; + +const int CELL_WIDTH = 8192; +const float SPLIT_FACTOR = 0.5; +const float UNSPLIT_FACTOR = 2.0; + +class Quad +{ + this(int cellX=0, int cellY=0, Quad parent = null) + { + mCellX = cellX; + mCellY = cellY; + + // Do we have a parent? + if(parent !is null) + { + mLevel = parent.mLevel-1; + + if(mLevel == 1) + { + // Create the terrain and leave it there. + buildTerrain(); + isStatic = true; + } + + // Coordinates relative to our parent + int relX = cellX - parent.mCellX; + int relY = cellY - parent.mCellY; + + // The coordinates give the top left corner of the quad, or our + // relative coordinates within that should always be positive. + assert(relX >= 0); + assert(relY >= 0); + + // Create a child scene node. The scene node position is given in + // world units, ie. CELL_WIDTH units per cell. + mNode = terr_createChildNode(relX*CELL_WIDTH, + relY*CELL_WIDTH, + parent.mNode); + /* + Ogre::Vector3 pos(relX * CELL_WIDTH, + relY * CELL_WIDTH, + 0); + mNode = parent.mNode.createChildSceneNode(pos); + */ + + // Get the archive data for this quad. + mInfo = g_archive.getQuad(mCellX,mCellY,mLevel); + } + else + { + // No parent, this is the top-most quad. Get all the info from + // the archive. + mInfo = g_archive.rootQuad; + + mLevel = mInfo.level; + cellX = mCellX = mInfo.cellX; + cellY = mCellY = mInfo.cellY; + + mNode = terr_createChildNode(cellX*CELL_WIDTH, + cellY*CELL_WIDTH, + null); + /* + mNode = g_rootTerrainNode. + createChildSceneNode(pos); + */ + + // Split up + split(); + + // The root can never be unsplit + isStatic = true; + } + + assert(mLevel >= 1); + assert(mNode !is null); + + // TODO: How do we store the C++ bounding box? + mBounds = terr_makeBounds(mInfo.minHeight, + mInfo.maxHeight, + mInfo.worldWidth); + /* + // Set up the bounding box. Use MW coordinates all the way + mBounds.setExtents(0,0,mInfo.minHeight, + mInfo.worldWidth,mInfo.worldWidth, + mInfo.maxHeight); + + // Transform the box to world coordinates, so it can be compared + // with the camera later. + mBounds.transformAffine(mNode._getFullTransform()); + */ + float radius = mInfo.boundingRadius; + + mSplitDistance = radius * SPLIT_FACTOR; + mUnsplitDistance = radius * UNSPLIT_FACTOR; + + // Square the distances + mSplitDistance *= mSplitDistance; + mUnsplitDistance *= mUnsplitDistance; + + // Update the terrain. This will create the mesh or children if + // necessary. + update(); + } + + ~this() + { + // TODO: We might rewrite the code so that the quads are never + // actually destroyed, just 'inactivated' by hiding their scene + // node. We only call update on our children if we don't have a + // mesh ourselves. + if(hasMesh) + destroyTerrain(); + else if(hasChildren) + for (size_t i = 0; i < 4; i++) + delete mChildren[i]; + + terr_destroyNode(mNode); + /* + mNode.removeAndDestroyAllChildren(); + mSceneMgr.destroySceneNode(mNode); + */ + } + + // Remove the landscape for this quad, and create children. + void split() + { + // Never split a static quad or a quad that already has children. + assert(!isStatic); + assert(!hasChildren); + assert(mLevel > 1); + + if(hasMesh) + destroyTerrain(); + + // Find the cell width of our children + int cWidth = 1 << (mLevel-2); + + // Create children + for ( size_t i = 0; i < 4; ++i ) + { + if(!mInfo.hasChild[i]) + continue; + + // The cell coordinates for this child quad + int x = (i%2)*cWidth + mCellX; + int y = (i/2)*cWidth + mCellY; + + mChildren[i] = new Quad(x,y,this); + } + hasChildren = true; + } + + // Removes children and rebuilds terrain + void unsplit() + { + // Never unsplit the root quad + assert(mLevel < g_archive.rootQuad.level); + // Never unsplit a static or quad that isn't split. + assert(!isStatic); + assert(hasChildren); + assert(!hasMesh); + + for( size_t i = 0; i < 4; i++ ) + { + delete mChildren[i]; + mChildren[i] = null; + } + + buildTerrain(); + + hasChildren = false; + } + + // Determines whether to split or unsplit the quad, and immediately + // does it. + void update() + { + // Static quads don't change + if(!isStatic) + { + assert(mUnsplitDistance > mSplitDistance); + + // Get (squared) camera distance. TODO: shouldn't this just + // be a simple vector difference from the mesh center? + float camDist = terr_getSqCamDist(mBounds); + /* + { + Ogre::Vector3 cpos = mCamera.getDerivedPosition(); + Ogre::Vector3 diff(0, 0, 0); + diff.makeFloor(cpos - mBounds.getMinimum() ); + diff.makeCeil(cpos - mBounds.getMaximum() ); + camDist = diff.squaredLength(); + } + */ + + // No children? + if(!hasChildren) + { + // If we're close, split now. + if(camDist < mSplitDistance) + split(); + else + { + // We're not close, and don't have any children. Should we + // built terrain? + if(!hasMesh) + buildTerrain(); + return; + } + } + + // If we get here, we either had children when we entered, + // or we just performed a split. + assert(!hasMesh); + assert(hasChildren); + + // If the camera is too far away, kill the children. + if(camDist > mUnsplitDistance) + { + unsplit(); + return; + } + } + else if(!hasChildren) + return; + + // We have children and we're happy about it. Update them too. + for(int i; i < 4; ++i) + { + Quad q = mChildren[i]; + if(q !is null) q.update(); + } + } + + // Build the terrain for this quad + void buildTerrain() + { + assert(!hasMesh); + assert(!isStatic); + + // Map the terrain data into memory. + g_archive.mapQuad(mInfo); + + // Create one mesh for each segment in the quad. TerrainMesh takes + // care of the loading. + meshList.length = mInfo.meshNum; + foreach(i, ref m; meshList) + m = terr_makeMesh(i, mNode); + + hasMesh = true; + } + + void destroyTerrain() + { + assert(hasMesh); + + foreach(m; meshList) + terr_killMesh(m); + + meshList[] = null; + hasMesh = false; + } + + private: + + // List of meshes, if any. The meshes are C++ objects. + MeshObj meshList[]; + + // Scene node. All child quads are added to this. + SceneNode mNode; + + // Bounding box, transformed to world coordinates. Used to calculate + // camera distance. + //Ogre::AxisAlignedBox mBounds; + Bounds mBounds; + + float mSplitDistance,mUnsplitDistance; + + Quad mChildren[4]; + + // Contains the 'level' of this node. Level 1 is the closest and + // most detailed level + int mLevel; + int mCellX, mCellY; + + QuadInfo *mInfo; + + bool hasMesh; + bool hasChildren; + bool isStatic; // Static quads are never split or unsplit +} diff --git a/terrain/terrain.d b/terrain/terrain.d index ffc6f74d5..2517d337c 100644 --- a/terrain/terrain.d +++ b/terrain/terrain.d @@ -24,6 +24,7 @@ module terrain.terrain; import terrain.generator; +import terrain.bindings; void initTerrain(bool doGen) { @@ -34,7 +35,3 @@ void initTerrain(bool doGen) //terr_setupRendering(); } - -extern(C): -void terr_genData(); -void terr_setupRendering();