diff --git a/Makefile b/Makefile index 6a674010b..44eafa483 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,12 @@ DMD=gdmd -version=Posix # Some extra flags for niftool and bsatool 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 + # Compiler settings for Ogre, OIS and MyGUI -CF_OIS=$(shell pkg-config --cflags OIS OGRE MyGUI) +# TODO: the -I when we're done +CF_OIS=$(shell pkg-config --cflags OIS OGRE MyGUI) -Iterrain/ OGCC=$(CXX) $(CXXFLAGS) $(CF_OIS) # Compiler settings for ffmpeg. @@ -23,10 +27,15 @@ BGCC=$(CXX) $(CXXFLAGS) $(CF_BULLET) # passed to the compiler, the rest are dependencies. ogre_cpp=ogre framelistener interface bsaarchive -# MyGUI C++ files, gui/cpp_X.cpp. These are currently included into -# with cpp_ogre.cpp. +# MyGUI C++ files, gui/cpp_X.cpp. These are currently included +# cpp_ogre.o with cpp_ogre.cpp. mygui_cpp=mygui console +# Ditto for the landscape engine, in terrain/cpp_X.cpp +terrain_cpp=baseland esm framelistener generator index landdata\ +materialgen meshinterface mwheightmap mwquadmatgen palette point2\ +quad quaddata quadsegment terraincls terrain terrainmesh + # FFmpeg files, in the form sound/cpp_X.cpp. avcodec_cpp=avcodec @@ -35,7 +44,10 @@ bullet_cpp=bullet player scale #### No modifications should be required below this line. #### -ogre_cpp_files=$(ogre_cpp:%=ogre/cpp_%.cpp) $(mygui_cpp:%=gui/cpp_%.cpp) +ogre_cpp_files=\ + $(ogre_cpp:%=ogre/cpp_%.cpp) \ + $(mygui_cpp:%=gui/cpp_%.cpp) \ + $(terrain_cpp:%=terrain/cpp_%.cpp) avcodec_cpp_files=$(avcodec_cpp:%=sound/cpp_%.cpp) bullet_cpp_files=$(bullet_cpp:%=bullet/cpp_%.cpp) @@ -43,7 +55,7 @@ bullet_cpp_files=$(bullet_cpp:%=bullet/cpp_%.cpp) src := $(wildcard bsa/*.d) $(wildcard bullet/*.d) $(wildcard core/*.d) \ $(wildcard esm/*.d) $(wildcard input/*.d) $(wildcard nif/*.d) $(wildcard ogre/*.d) \ $(wildcard scene/*.d) $(wildcard sound/*.d) $(wildcard util/*.d) $(wildcard gui/*.d) -src := $(src) $(wildcard mscripts/*.d) +src := $(src) $(wildcard mscripts/*.d) $(wildcard terrain/*.d) src := $(src) monster/monster.d \ $(wildcard monster/vm/*.d) \ $(wildcard monster/compiler/*.d) \ @@ -85,10 +97,10 @@ nifobjs/%.o: %.d $(DMD) $(NIFFLAGS) -c $< -of$@ openmw: openmw.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o $(obj) - $(DMD) $^ -of$@ -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a + $(DMD) $^ -of$@ $(LFLAGS) esmtool: esmtool.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o $(obj) - $(DMD) $^ -of$@ -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a + $(DMD) $^ -of$@ $(LFLAGS) niftool: niftool.d $(obj_nif) $(DMD) $^ -of$@ diff --git a/gui/bindings.d b/gui/bindings.d index 4e91ec58e..a7f53b531 100644 --- a/gui/bindings.d +++ b/gui/bindings.d @@ -32,6 +32,7 @@ typedef void* WidgetPtr; void gui_setupGUI(int debugOut); void gui_toggleGui(); void gui_setCellName(char *str); +void gui_showHUD(); // Console stuff void gui_toggleConsole(); diff --git a/gui/cpp_mygui.cpp b/gui/cpp_mygui.cpp index 5b54707d1..88c2130d1 100644 --- a/gui/cpp_mygui.cpp +++ b/gui/cpp_mygui.cpp @@ -450,6 +450,12 @@ extern "C" void gui_toggleGui() extern "C" int32_t* gui_getGuiModePtr() { return &guiMode; } +extern "C" void gui_showHUD() +{ + if(hud) + hud->setVisible(true); +} + extern "C" void gui_setupGUI(int32_t debugOut) { ResourceGroupManager::getSingleton(). @@ -480,7 +486,6 @@ extern "C" void gui_setupGUI(int32_t debugOut) state.X.abs = mWidth / 2; state.Y.abs = mHeight / 2; - //* // Set up the HUD hud = new HUD(); @@ -494,7 +499,8 @@ extern "C" void gui_setupGUI(int32_t debugOut) hud->setSpellStatus(65, 100); hud->setEffect("icons\\s\\tx_s_chameleon.dds"); - //*/ + + hud->setVisible(false); //new MainMenu(); } diff --git a/gui/gui.d b/gui/gui.d index 5eb4a7160..dbb8a1ac2 100644 --- a/gui/gui.d +++ b/gui/gui.d @@ -232,6 +232,24 @@ void getHeight() stack.pushInt(gui_getHeight(null)); } +void initGUI(bool debugOut) +{ + // Load the GUI system + gui_setupGUI(debugOut); +} + +void startGUI() +{ + gui_showHUD(); + + // Run GUI scripts + // Create the HUD and windows + vm.run("makegui.mn"); + + // Run the fps ticker + vm.run("fpsticker.mn"); +} + void setupGUIScripts() { vm.addPath("mscripts/guiscripts/"); diff --git a/input/events.d b/input/events.d index f48a8080a..8f3006ea2 100644 --- a/input/events.d +++ b/input/events.d @@ -214,10 +214,14 @@ const float sndRefresh = 0.17; // Refresh rate for music fadeing, seconds. const float musRefresh = 0.05; +// Walking / floating speed, in points per second. +float speed = 300; + float sndCumTime = 0; float musCumTime = 0; -void initializeInput() +// Move the player according to playerData.position +void movePlayer() { // Move the player into place. TODO: This isn't really input-related // at all, and should be moved. @@ -228,6 +232,11 @@ void initializeInput() bullet_movePlayer(position[0], position[1], position[2]); } +} + +void initializeInput() +{ + movePlayer(); // TODO/FIXME: This should have been in config, but DMD's module // system is on the brink of collapsing, and it won't compile if I @@ -250,6 +259,22 @@ bool isPressed(Keys key) return false; } +// Enable superman mode, ie. flight and super-speed. This is getting +// very spaghetti-ish. +extern(C) void d_superman() +{ + bullet_fly(); + speed = 8000; + + with(*playerData.position) + { + position[0] = 0; + position[1] = 0; + position[2] = 12000; + } + movePlayer(); +} + extern(C) int d_frameStarted(float time) { if(doExit) return 0; @@ -270,9 +295,6 @@ extern(C) int d_frameStarted(float time) // The rest is ignored in pause or GUI mode if(pause || *guiMode > 0) return 1; - // Walking / floating speed, in points per second. - const float speed = 300; - // Check if the movement keys are pressed float moveX = 0, moveY = 0, moveZ = 0; float x, y, z, ox, oy, oz; diff --git a/mscripts/setup.d b/mscripts/setup.d index 11e568307..58960e0cd 100644 --- a/mscripts/setup.d +++ b/mscripts/setup.d @@ -54,17 +54,6 @@ void initMonsterScripts() setupGUIScripts(); } -// Run the GUI scripts. These should be run only after the -// GUI/rendering system has been initialized -void runGUIScripts() -{ - // Create the HUD and windows - vm.run("makegui.mn"); - - // Run the fps ticker - vm.run("fpsticker.mn"); -} - // This should probably not be here: import monster.vm.dbg; diff --git a/ogre/cpp_interface.cpp b/ogre/cpp_interface.cpp index a8fe32993..8a4bc0aab 100644 --- a/ogre/cpp_interface.cpp +++ b/ogre/cpp_interface.cpp @@ -59,34 +59,9 @@ extern "C" int32_t ogre_configure( mRoot = new Root(plugincfg, "ogre.cfg", ""); - // Add the BSA archive manager before reading the config file. + // Add the BSA archive manager ArchiveManager::getSingleton().addArchiveFactory( &mBSAFactory ); - /* The only entry we use from resources.cfg is the "BSA=internal" - entry, which we can put in manually. - - // Load resource paths from config file - ConfigFile cf; - cf.load("resources.cfg"); - - // Go through all sections & settings in the file - ConfigFile::SectionIterator seci = cf.getSectionIterator(); - - String secName, typeName, archName; - while (seci.hasMoreElements()) - { - secName = seci.peekNextKey(); - ConfigFile::SettingsMultiMap *settings = seci.getNext(); - ConfigFile::SettingsMultiMap::iterator i; - for (i = settings->begin(); i != settings->end(); ++i) - { - typeName = i->first; - archName = i->second; - ResourceGroupManager::getSingleton().addResourceLocation( - archName, typeName, secName); - } - } - */ ResourceGroupManager::getSingleton(). addResourceLocation("internal", "BSA", "General"); @@ -328,10 +303,7 @@ void cloneNode(SceneNode *from, SceneNode *to, char* name) SceneNode::ObjectIterator it = from->getAttachedObjectIterator(); while(it.hasMoreElements()) { - // We can't handle non-entities. To be honest I have no idea - // what dynamic_cast does or if it's correct here. I used to be - // a C++ person but after discovering D I dropped C++ like it - // was red hot iron and never looked back. + // We can't handle non-entities. Entity *e = dynamic_cast (it.getNext()); if(e) { diff --git a/ogre/cpp_ogre.cpp b/ogre/cpp_ogre.cpp index 9b676b865..c795c8a4c 100644 --- a/ogre/cpp_ogre.cpp +++ b/ogre/cpp_ogre.cpp @@ -70,8 +70,11 @@ int32_t guiMode = 0; // Morrowind. SceneNode *root; -// Include the other parts of the code, and make one big object file. +// Include the other parts of the code, and make one big happy object +// file. This is extremely against the grain of C++ "recomended +// practice", but I don't care. #include "../gui/cpp_mygui.cpp" #include "cpp_framelistener.cpp" #include "cpp_bsaarchive.cpp" #include "cpp_interface.cpp" +#include "../terrain/cpp_terrain.cpp" diff --git a/ogre/ogre.d b/ogre/ogre.d index 287230f80..4f220cd57 100644 --- a/ogre/ogre.d +++ b/ogre/ogre.d @@ -119,12 +119,6 @@ void setupOgre(bool debugOut) // exterior cells differently, etc. ogre_makeScene(); - // Load the GUI system - gui_setupGUI(debugOut); - - // Run the GUI scripts - runGUIScripts(); - ogreSetup = true; } diff --git a/openmw.d b/openmw.d index 235afc423..09ac44b94 100644 --- a/openmw.d +++ b/openmw.d @@ -1,6 +1,6 @@ /* OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008 Nicolay Korslund + Copyright (C) 2008-2009 Nicolay Korslund Email: < korslund@gmail.com > WWW: http://openmw.snaptoad.com/ @@ -31,6 +31,7 @@ import std.file; import ogre.ogre; import ogre.bindings; import gui.bindings; +import gui.gui; import bullet.bullet; @@ -51,6 +52,8 @@ import sound.audio; import input.events; +import terrain.terrain; + //* import std.gc; import gcstats; @@ -72,6 +75,7 @@ void main(char[][] args) bool showOgreFlag = false; bool noSound = false; bool debugOut = false; + bool extTest = false; // Some examples to try: // @@ -88,16 +92,10 @@ void main(char[][] args) // Cells to load char[][] cells; - int[] eCells; foreach(char[] a; args[1..$]) if(a == "-n") render = false; - else if(a.begins("-e")) - { - int i = find(a,','); - eCells ~= atoi(a[2..i]); - eCells ~= atoi(a[i+1..$]); - } + else if(a == "-ex") extTest = true; else if(a == "-h") help=true; else if(a == "-rk") resetKeys = true; else if(a == "-oc") showOgreFlag = true; @@ -112,7 +110,7 @@ void main(char[][] args) } else cells ~= a; - if(cells.length + eCells.length/2 > 1 ) + if(cells.length > 1) { writefln("More than one cell specified, rendering disabled"); render=false; @@ -123,7 +121,7 @@ void main(char[][] args) writefln("Syntax: %s [options] cell-name [cell-name]", args[0]); writefln(" Options:"); writefln(" -n Only load, do not render"); - writefln(" -ex,y Load exterior cell (x,y)"); + writefln(" -ex Test the terrain system"); writefln(" -rk Reset key bindings to default"); writefln(" -oc Show the Ogre config dialogue"); writefln(" -ns Completely disable sound"); @@ -163,14 +161,14 @@ void main(char[][] args) // setting. if(showOgreFlag) config.finalOgreConfig = true; - if(cells.length == 0 && eCells.length == 0) + if(cells.length == 0) if(config.defaultCell.length) cells ~= config.defaultCell; if(cells.length == 1) config.defaultCell = cells[0]; - if(cells.length == 0 && eCells.length == 0) + if(cells.length == 0) { showHelp(); return; @@ -206,27 +204,6 @@ Try specifying another cell name on the command line, or edit openmw.ini."); } } - for(int i; igetFarClipDistance(); + if ( vd > mMeshDistance ) { + destroyMaterial(); + destroyMesh(); + createMaterial(); + createMesh(); + } + + Ogre::Vector3 p = mCamera->getDerivedPosition(); + p.x -= ((int)p.x % 8192); + p.z -= ((int)p.z % 8192); + + float h = p.y + 2048; + h = pow(h/8192*2,2); + if ( h < 0 ) h = 0; + + mNode->setPosition(p.x, -32 - h, p.z); + } + +private: + void createMesh() + { + mObject = mSceneMgr->createManualObject("BaseLand"); + mObject->begin("BaseLandMat", Ogre::RenderOperation::OT_TRIANGLE_LIST); + + Ogre::Real vd = mCamera->getFarClipDistance(); + vd += 8192 - ((int)vd % 8192); + + mMeshDistance = vd; + + + mObject->position(-vd,-2048, vd); + mObject->textureCoord(0, 1); + + mObject->position(vd,-2048, vd); + mObject->textureCoord(1, 1); + + mObject->position(vd,-2048, -vd); + mObject->textureCoord(1, 0); + + mObject->position(-vd,-2048, -vd); + mObject->textureCoord(0, 0); + + mObject->quad(0,1,2,3); + + mObject->end(); + + + mNode = mTerrainSceneNode->createChildSceneNode(); + mNode->attachObject(mObject); + } + + void destroyMesh() + { + mNode->detachAllObjects(); + mSceneMgr->destroyManualObject(mObject); + mNode->getParentSceneNode()->removeAndDestroyChild(mNode->getName()); + } + + void createMaterial() + { + float vd = mCamera->getFarClipDistance(); + vd += 8192 - ((int)vd % 8192); + vd = vd/8192 * 2; + + mMat = Ogre::MaterialManager::getSingleton(). + create(std::string("BaseLandMat"), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + Ogre::TextureUnitState* us = mMat->getTechnique(0)->getPass(0)->createTextureUnitState("_land_default.dds"); + us->setTextureScale(0.1f/vd,0.1f/vd); + + mMat->getTechnique(0)->getPass(0)->setDepthBias(-1); + } + + void destroyMaterial() + { + mMat->getCreator()->remove(mMat->getHandle()); + mMat = Ogre::MaterialPtr(); + } + + ///the created mesh + Ogre::ManualObject* mObject; + + ///The material for the mesh + Ogre::MaterialPtr mMat; + + ///scene node for the mesh + Ogre::SceneNode* mNode; + + ///In essence, the farViewDistance of the camera last frame + Ogre::Real mMeshDistance; + + Ogre::SceneNode* mTerrainSceneNode; +}; diff --git a/terrain/cpp_esm.cpp b/terrain/cpp_esm.cpp new file mode 100644 index 000000000..1bb459d30 --- /dev/null +++ b/terrain/cpp_esm.cpp @@ -0,0 +1,169 @@ +/* + 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 boost::shared_ptr RecordPtr; +typedef std::list RecordList; +typedef RecordList::iterator RecordListItr; +typedef boost::shared_ptr RecordListPtr; + +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) ) { + RecordPtr record = RecordPtr(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 RecordPtr getRecord(const std::string& id){ return mRecords[id]; } + + RecordListPtr getRecordsByType(const std::string& t) + { + RecordListPtr r = RecordListPtr(new RecordList); //need pointer.... + 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_framelistener.cpp b/terrain/cpp_framelistener.cpp new file mode 100644 index 000000000..3c7e899f7 --- /dev/null +++ b/terrain/cpp_framelistener.cpp @@ -0,0 +1,58 @@ +class TerrainFrameListener : public FrameListener +{ +protected: + Terrain* mTerrain; + MWHeightmap* mHeights; + + /** + * Updates the quad tree + */ + bool frameEnded(const FrameEvent& evt) + { + mTerrain->update(evt.timeSinceLastFrame); + return true; + } + +public: + void setup() + { + // Add the frame listener + mRoot->addFrameListener(this); + + //our derived heightmap + mHeights = new MWHeightmap(); + mHeights->load(TERRAIN_OUTPUT); + + //setup terrain + mTerrain = new Terrain( mHeights, //heightmap + mSceneMgr->getRootSceneNode()->createChildSceneNode("TERRAIN_ROOT")); //root scene node + + //fix settings + mTerrain->setMorphingEnabled(false); + mTerrain->setTextureFadingEnabled(false); + + //create the quad node + mTerrain->create(); + } + + /* KILLME + void drawLine(std::string name, Ogre::Vector3 start, Ogre::Vector3 end) + { + Ogre::ManualObject* mo = mSceneMgr->createManualObject( name); + Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode( name+"node"); + + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create( name+"Material","debugger"); + mat->setReceiveShadows(false); + mat->getTechnique(0)->setLightingEnabled(true); + mat->getTechnique(0)->getPass(0)->setDiffuse(0,0,1,0); + mat->getTechnique(0)->getPass(0)->setAmbient(0,0,1); + mat->getTechnique(0)->getPass(0)->setSelfIllumination(0,0,1); + + mo->begin(name+"Material", Ogre::RenderOperation::OT_LINE_LIST); + mo->position(start); + mo->position(end); + mo->end(); + node->attachObject(mo); + } + */ +}; diff --git a/terrain/cpp_generator.cpp b/terrain/cpp_generator.cpp new file mode 100644 index 000000000..678a6f65c --- /dev/null +++ b/terrain/cpp_generator.cpp @@ -0,0 +1,441 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2009 Jacob Essex + 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/ . + +*/ + +class Generator +{ +public: + Generator(const std::string& baseFileName) : + mBaseFileName(baseFileName) + { + mDataO.open(std::string(baseFileName+".data").c_str(), std::ios::binary); + } + + inline void addLandData(RecordPtr record, const std::string& source) + { mMWLand.addLandData(record, source); } + + inline void addLandTextureData(RecordPtr record, const std::string& source) + { mMWLand.addLandTextureData(record, source); } + + void beginGeneration() + { + //maxiumum distance form 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 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) + for (long i = mIndex.getRootSideLength(); i > 8192; i/=2 ) + maxDepth++; + mIndex.setMaxDepth(maxDepth); + } + + void generateLODLevel(int level, bool createTexture, 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 + const int halfLevel = level/2; + assert(halfLevel > 0 ); + + // FIXME. Just search for all pow() calls, really + int cellDist = pow((float)2, mIndex.getMaxDepth()); + + // Temporary storage + MWQuadData qd(0); + 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); + + //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)); //cache this + 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 ) + generateLTEXData(x, y, gl); + else if ( createTexture ) + { + //this is the name of the file that OGRE will + //look for + std::string name = stringLevel + "_" + + Ogre::StringConverter::toString(x) + "_" + + Ogre::StringConverter::toString(y) + ".png"; + + //where as the arg1 here is the file name to save it. + 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() ) + { + //calcualte the level one higher + 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 filesystenm libs + FILE* fp = fopen((std::string(TEXTURE_OUTPUT) + higherName).c_str(), "r"); + if ( fp ) + { + qd.setParentTexture(higherName); + fclose(fp); + } + else + qd.setParentTexture(""); + } + 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; + } + + //check if we have used a default texture + if ( hasUsedDefault ) + { + 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); + } + } + + void endGeneration() + { + // FIXME: Just write one file? + std::ofstream ofi(std::string(mBaseFileName + ".index").c_str(), std::ios::binary); + std::ofstream ofp(std::string(mBaseFileName + ".palette").c_str(), std::ios::binary); + boost::archive::binary_oarchive oai(ofi); + boost::archive::binary_oarchive oap(ofp); + oai << mIndex; + oap << mPalette; + } + +private: + + void generateLTEXData(int x, int y, std::vector& index) + { + for ( int texY = 0; texY < LAND_LTEX_WIDTH+2; texY++ ) + for ( int texX = 0; texX < LAND_LTEX_WIDTH+2; texX++ ) + { + int tempX = x; + int tempY = y; + + int sourceX = texX - 1; + int sourceY = texY - 1; + + if ( sourceX == -1 ) + { + tempX--; + sourceX = LAND_LTEX_WIDTH-1; + } + else if ( sourceX == LAND_LTEX_WIDTH) + { + tempX++; + sourceX = 0; + } + + if ( sourceY == -1 ) + { + tempY--; + sourceY = LAND_LTEX_WIDTH-1; + } + else if ( sourceY == LAND_LTEX_WIDTH ) + { + tempY++; + sourceY = 0; + } + + std::string source; + short texID = 0; + + if ( mMWLand.hasData(tempX, tempY) ) + { + source = mMWLand.getSource(tempX, tempY); + texID = mMWLand.getLTEXIndex(tempX,tempY, sourceX, sourceY); + } + + std::string texturePath = "_land_default.dds"; + if ( texID != 0 && mMWLand.hasLTEXRecord(source,--texID) ) + texturePath = mMWLand.getLTEXRecord(source,texID); + + // 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); + + index[texY*(LAND_LTEX_WIDTH+2)+texX] = mPalette.getOrAddIndex(texturePath); + } + } + + bool generateTexture(const std::string& name, int size, + int blockX, int blockY, int halfLevel) + { + int width = size/(halfLevel*LAND_LTEX_WIDTH); + assert(width>=1); + + std::vector ltex; //prealloc, as we know exactly what size it needs to be + ltex.resize(halfLevel*LAND_LTEX_WIDTH*halfLevel*LAND_LTEX_WIDTH, 0); + + //for each cell + for ( int y = 0; y < halfLevel; y++ ) + for ( int x = 0; x < halfLevel; x++ ) + //for each texture in the cell + for ( int texY = 0; texY < LAND_LTEX_WIDTH; texY++ ) + for ( int texX = 0; texX < LAND_LTEX_WIDTH; texX++ ) + { + std::string source; + short texID = 0; + + if ( mMWLand.hasData(x + blockX, y + blockY) ) + { + source = mMWLand.getSource(x + blockX, y + blockY); + texID = mMWLand.getLTEXIndex(x + blockX, y + blockY, texX, texY); + } + + std::string texturePath = "_land_default.dds"; + if ( texID != 0 && mMWLand.hasLTEXRecord(source,--texID) ) + texturePath = mMWLand.getLTEXRecord(source,texID); + + //textures given as tga, when they are a dds. I hate that "feature" + //FIXME: ditto earlier comment + size_t d = texturePath.find_last_of(".") + 1; + texturePath = texturePath.substr(0, d) + "dds"; + //FIXME: BTW, didn't we make the BSA reader case insensitive? + std::transform(texturePath.begin(), texturePath.end(), texturePath.begin(), tolower); + const int index = (y*LAND_LTEX_WIDTH+texY)*halfLevel*LAND_LTEX_WIDTH + x*LAND_LTEX_WIDTH+texX; + ltex[index] = mPalette.getOrAddIndex(texturePath); + } + + //see if we have used anything at all + // FIXME: Now, I KNOW this isn't needed :) + int sum = 0; + for ( std::vector::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. + + void renderTexture(const std::string& outputName, std::vector& ltex, + int texSize, int alphaSize) + { + std::cout << " Creating " << outputName << "\n"; + + assert(Ogre::Math::Sqrt(ltex.size())==alphaSize); + std::list createdResources; + + MaterialGenerator mg; + mg.setTexturePaths(mPalette.getPalette()); + + const int scaleDiv = alphaSize/LAND_LTEX_WIDTH; + + //genetate material/aplahas + Ogre::MaterialPtr mp = mg.getAlphaMat("Rtt_Alpha1", ltex, alphaSize, 0, scaleDiv,createdResources); + Ogre::TexturePtr tex1 = getRenderedTexture(mp, "RTT_TEX_1",texSize, Ogre::PF_R8G8B8); + tex1->getBuffer()->getRenderTarget()->writeContentsToFile(outputName); + 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()); + } + } + + Ogre::TexturePtr getRenderedTexture(Ogre::MaterialPtr mp, const std::string& name, + int texSize, Ogre::PixelFormat tt) + { + 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(); + + //required for some reason? + //Not implementing resulted in a black tex using openGL, Linux, and a nvidia 6150 in 1.4.? + Ogre::Root::getSingleton().renderOneFrame(); + + Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp"); + Ogre::CompositorManager::getSingleton().remove(cp->getHandle()); + + renderTexture->removeAllViewports(); //needed? + + 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_index.cpp b/terrain/cpp_index.cpp new file mode 100644 index 000000000..2497fa686 --- /dev/null +++ b/terrain/cpp_index.cpp @@ -0,0 +1,91 @@ +/** +* @brief holds data about positions of data and general header info +*/ +class Index { +public: + ///saves my fingers :P + 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 new file mode 100644 index 000000000..e62851644 --- /dev/null +++ b/terrain/cpp_landdata.cpp @@ -0,0 +1,209 @@ +class MWLand +{ +public: + MWLand() + { + mMaxX = mMaxY = mMinX = mMinY = 0; + } + + void addLandTextureData(RecordPtr 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(RecordPtr 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(); + 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 + 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 std::vector& 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; + } + + 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; + } + + 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 VHGT { ///height data + float heightOffset; + char heightData[LAND_NUM_VERTS]; + short unknown1; + char unknown2; + }; + 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); + 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 + } + } + 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 + std::vector 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_materialgen.cpp b/terrain/cpp_materialgen.cpp new file mode 100644 index 000000000..72f591c6d --- /dev/null +++ b/terrain/cpp_materialgen.cpp @@ -0,0 +1,367 @@ +/** + * Handles the runtime generation of materials + * + */ +class MaterialGenerator +{ + class TextureSplatter + { + public: + inline TextureSplatter(const std::vector& ltex, + int texSize, + int ltexsize, + int border) : + mLTEX(ltex), + mTexSize(texSize), //the root of the size of the texture + mLTEXSize(ltexsize), //the root of the ltex array + mBorder(border) { //the size of the border of the ltex + mSizeDiff = texSize/(ltexsize-border*2); + } + + int getAlphaAtPixel(int x, int y, int tid) { + //offset for border + x += (mBorder*mSizeDiff); + y += (mBorder*mSizeDiff); + + 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; + //y = floor(float(y)/float(mSizeDiff)); + //x = floor(float(x)/float(mSizeDiff)); + + return mLTEX[(y)*mLTEXSize+(x)]; + } + + const std::vector& mLTEX; + const int mTexSize; + const int mLTEXSize; + const int mBorder; + int mSizeDiff; + }; + +public: + + /** + * @brief generates a material for a quad using a single texture + */ + Ogre::MaterialPtr generateSingleTexture(const std::string& matname, const std::string& texName, std::list createdResources) + { + 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(); + } + + /** + * Currently doesn't work + */ + Ogre::MaterialPtr getShaderAlpha(const std::string& materialName, std::vector& ltex, int size, std::list& generatedAlphas) + { + Ogre::TexturePtr currentAlphaTexture; //ptr to the texture we are currnly writing to + Ogre::uint8* currentAlphaPtr = 0; //pointer to the data + Ogre::HardwarePixelBufferSharedPtr currentPixelBuffer; //pointer to the buffer for ctrling locks/unlicjks + int currentColour = 0; //current colour index. ARGB. + std::vector texturesWritten; //a list of "done" textures. Order important + std::vector alphasGenerated; //alphas generated + + std::set tidDone; //holds the list of done textures + + const int rootSideLength = Ogre::Math::Sqrt(ltex.size()); //used for looping + + //loop over every splat possition + for ( int y1 = 0; y1 < rootSideLength; y1++ ) { + for ( int x1 = 0; x1 < rootSideLength; x1++ ) { + const short tid = ltex[y1*rootSideLength+x1]; + + //if already done. + if ( tidDone.find(tid) != tidDone.end()) continue; + + //insert it into the done list + tidDone.insert(tid); + + //get the textuyre path. If it is default we don't need to do anything, as it + //is done in the first pass. CHANGE? We end up using a whole pass for it?? + const std::string tn(mTexturePaths[tid]); + if ( tn == "_land_default.dds" ) continue; + + texturesWritten.push_back(tn); + + //unqiue alpha name + const std::string alphaName(materialName + "_A_" + tn); + + + if ( Ogre::TextureManager::getSingleton().resourceExists(alphaName) ) + OGRE_EXCEPT(0, "ALPHA Already Exists", ""); //shouldn't happen. + + //check if we need to create a new apla + if ( currentColour == 4 || //we only have 4 colours per tex ofc + currentAlphaTexture.isNull() ) { //no texture assigned yet + + //unlock old buffer, if we have one + if ( !currentAlphaTexture.isNull() ) + currentPixelBuffer->unlock(); + + //new texture + currentAlphaTexture = Ogre::TextureManager::getSingleton(). + createManual( + alphaName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + size,size, //size ofc + 1, 0, //depth, mipmaps + Ogre::PF_A8R8G8B8, //4 channesl for 4 splats + Ogre::TU_STATIC_WRITE_ONLY //we only need to write data + ); + + generatedAlphas.push_back(currentAlphaTexture); //record + alphasGenerated.push_back(alphaName); + + + currentPixelBuffer = currentAlphaTexture->getBuffer(); + currentPixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD); + const Ogre::PixelBox& pixelBox = currentPixelBuffer->getCurrentLock(); + + currentAlphaPtr = static_cast(pixelBox.data); + + //zero out all data + for ( int i = 0; i < size*size*4; i++ ) + currentAlphaPtr[i] = 0; + + + currentColour = 0; + } else { + currentColour++; //increase the colour we are working with by one + } + + + //for every splat that the texture is splatted, + //check if it is the same as the one in the current splat + for ( int y2 = 0; y2 < rootSideLength; y2++ ) { + for ( int x2 = 0; x2 < rootSideLength; x2++ ) { + if ( ltex[y2*rootSideLength+x2] == tid ) { + + //add splat to current alpha map + const int splatSize = size/rootSideLength; + + for ( int ys = 0; ys < splatSize ; ys++ ) { + for ( int xs = 0; xs < splatSize; xs++ ) { + //calc position on main texture + const int pxpos = splatSize*x2+xs; + const int pypos = splatSize*y2+ys; + + //write splat to buffer + const int index = (pypos*size*4)+(pxpos*4)+currentColour; + currentAlphaPtr[index] = 255; + } + } + } + } + }//for ( int y2 = 0; y2 < rootSideLength; y2++ ){ + + + } + }//for ( int y1 = 0; y1 < rootSideLength; y1++ ){ + + //check to see if we need to unlock a buff again + if ( !currentAlphaTexture.isNull() ) + currentPixelBuffer->unlock(); + + //sort material + assert(Ogre::MaterialManager::getSingleton().getByName("AlphaSplatTerrain").getPointer()); + + Ogre::MaterialPtr material = ((Ogre::Material*)Ogre::MaterialManager::getSingleton().getByName("AlphaSplatTerrain").getPointer())->clone(materialName); + + Ogre::Pass* pass = material->getTechnique(0)->getPass(0); + + //write alphas + if ( alphasGenerated.size() > 0 ) + pass->getTextureUnitState(0)->setTextureName(alphasGenerated[0]); + if ( alphasGenerated.size() > 1 ) + pass->getTextureUnitState(1)->setTextureName(alphasGenerated[1]); + + //write 8 textures + int c = 1; + for ( std::vector::iterator itr = texturesWritten.begin(); + itr != texturesWritten.end(); + ++itr ) { + if ( ++c > 8 ) break; + pass->getTextureUnitState(1+c)->setTextureName(*itr); + //ta["Splat" + Ogre::StringConverter::toString(c)] = *itr; + } + + //pass->applyTextureAliases(ta, true); + + return material; + + } + + /** + * gets the material for the alpha textures + */ + Ogre::MaterialPtr getAlphaMat(const std::string& materialName, + std::vector& ltex, + int size, + int border, + float scaleDiv, + std::list& createdResources) + { + const int sizeDiff = 4; + size *= sizeDiff; + + const int rootSideLength = Ogre::Math::Sqrt(ltex.size())-(border*2); //used for looping + const int ltexWidth = Ogre::Math::Sqrt(ltex.size()); + assert(size%rootSideLength==0); + + Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton(). + create(materialName,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + createdResources.push_back(material); + + + Ogre::Pass* np = material->getTechnique(0)->getPass(0); + np->setLightingEnabled(false); + np->createTextureUnitState("_land_default.dds")->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv); + + 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, border); + 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; + } + + std::string generateAlphaMap(const std::string& name, + std::vector& ltex, + int size, + int border, + float scaleDiv, + std::list& createdResources) + { + //std::string materialName = "TEX_" + Ogre::StringConverter::toString(cp.x) + "_" + Ogre::StringConverter::toString(cp.y); + + if ( Ogre::MaterialManager::getSingleton().resourceExists(name) ) + assert(0); + //return materialName; + + getAlphaMat(name, ltex, size, border, scaleDiv, createdResources); + //getShaderAlpha(materialName, ltex, size, newTextures); + + return name; + } + + inline void setTexturePaths( std::map r) { + mTexturePaths = r; + } +private: + /** + * Merged records accross all mods for LTEX data + */ + std::map mTexturePaths; +}; diff --git a/terrain/cpp_meshinterface.cpp b/terrain/cpp_meshinterface.cpp new file mode 100644 index 000000000..1dce015d5 --- /dev/null +++ b/terrain/cpp_meshinterface.cpp @@ -0,0 +1,110 @@ +MeshInterface::MeshInterface(Quad* p, Terrain* t) : + mParentQuad(p), + mTerrain(t), + mMax(0), + mMin(0), + mSplitState(SS_NONE) { + + mQuadData = t->getTerrainData()->getData(mParentQuad); + + //the mesh is created a zero, so an offset is applied + const Ogre::Vector3 pos(mParentQuad->getPosition().x - mParentQuad->getSideLength()/2, + 0, + mParentQuad->getPosition().y - mParentQuad->getSideLength()/2); + + mSceneNode = mTerrain->getTerrainSceneNode()->createChildSceneNode(pos); +} + +MeshInterface::~MeshInterface() { + for ( std::vector::iterator itr = mTerrainObjects.begin(); + itr != mTerrainObjects.end(); + ++itr ) + delete *itr; + + mSceneNode->removeAndDestroyAllChildren(); + mSceneMgr->destroySceneNode(mSceneNode); + + mTerrain->getTerrainSceneNode()->detachAllObjects(); + + mTerrain->_quadDestroyed(mQuadData); + delete mQuadData; + +} + +void MeshInterface::create() { + //LOG("Creating"); + if ( mParentQuad->getDepth() == mTerrain->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(); + mTerrain->_quadCreated(mQuadData); +} + +void MeshInterface::addNewObject(const Ogre::Vector3& pos, + int terrainSize, + bool skirts /*= true*/, + float segmentSize /*= 1*/, + float startX /*= 0*/, + float startY /*= 0*/ ) { + + TerrainObjectGroup* to = new TerrainObjectGroup(); + + to->segment = new QuadSegment(mQuadData, segmentSize, startX, startY); + to->node = mSceneNode->createChildSceneNode(pos); + to->terrain = new TerrainRenderable(mTerrain, to->segment, terrainSize, mParentQuad->getDepth(), skirts); + to->terrain->create(to->node); + + mMax = std::max(to->terrain->getMax(), mMax); + mMin = std::max(to->terrain->getMin(), mMin); + + mTerrainObjects.push_back(to); +} + +void MeshInterface::update(Ogre::Real time) { + 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 ( std::vector::iterator itr = mTerrainObjects.begin(); + itr != mTerrainObjects.end(); + ++itr ) + (*itr)->terrain->update(time, camDist, mUnsplitDistance, mMorphDistance); +} + +void MeshInterface::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 * 0.5, 2); + mUnsplitDistance = pow(mBoundingRadius * 2.0, 2); + mMorphDistance = pow(mBoundingRadius * 1.5, 2); +} diff --git a/terrain/cpp_meshinterface.h b/terrain/cpp_meshinterface.h new file mode 100644 index 000000000..426510b39 --- /dev/null +++ b/terrain/cpp_meshinterface.h @@ -0,0 +1,117 @@ +/** +* 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 optomizations (e.g. multiple splits) +*/ +class MeshInterface +{ + /** + * + * @brief Holds a group of objects and destorys them in the + * destructor. Avoids the needs for 100s of vectors + */ + struct TerrainObjectGroup { + /** + * @brief inits all ptrs at 0 + */ + inline TerrainObjectGroup() : segment(0), terrain(0), node(0) {} + + /** + * @brief destorys all objects + */ + inline ~TerrainObjectGroup() { + if ( node ) { + node->detachAllObjects(); + node->getCreator()->destroySceneNode(node); + } + delete terrain; + delete segment; + } + QuadSegment* segment; + TerrainRenderable* terrain; + Ogre::SceneNode* node; + }; +public: + enum SplitState { SS_NONE, SS_SPLIT, SS_UNSPLIT }; + + MeshInterface(Quad* p, Terrain* t); + ~MeshInterface() ; + + /** + * @brief creates all required meshes. If it is at the max depth, it creates 16, otherwise just one + */ + void create(); + + /** + * @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); + + inline SplitState getSplitState() { + return mSplitState; + } + + /** + * @brief propergates the just split through all terrain + */ + inline void justSplit() { + for ( std::vector::iterator itr = mTerrainObjects.begin(); + itr != mTerrainObjects.end(); + ++itr ) + (*itr)->terrain->justSplit(); + } + /** + * @brief propergates the just unsplit through all terrain + */ + inline void justUnsplit() { + for ( std::vector::iterator itr = mTerrainObjects.begin(); + itr != mTerrainObjects.end(); + ++itr ) + (*itr)->terrain->justUnsplit(); + } + +private: + + Quad* mParentQuad; + Terrain* mTerrain; + + ///Must be a ptr, else it destorys before we are ready + std::vector mTerrainObjects; + + Ogre::SceneNode* mSceneNode; + + ///use for split distances + Ogre::Real mBoundingRadius; + 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(); + + /** + * @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 ); + + + +}; diff --git a/terrain/cpp_mwheightmap.cpp b/terrain/cpp_mwheightmap.cpp new file mode 100644 index 000000000..a21ae7262 --- /dev/null +++ b/terrain/cpp_mwheightmap.cpp @@ -0,0 +1,84 @@ +/** + * @brief the class that deals with loading quads from the disk @todo a + * major improvment would be to store the data as a quad tree. It might + * improve lookup times. Then again. Might not + */ +class MWHeightmap : public TerrainHeightmap +{ +public: + QuadData* getData(Quad* q) + { + MWQuadData* data = loadQuad(q->getPosition().x,q->getPosition().y); + + if ( !data ) + assert(0); + + return data; + } + + inline bool hasData(Quad* q) { + return hasQuad(q->getPosition().x,q->getPosition().y); + } + + 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; + } + mMaterialGen.setTexturePaths(mPalette.getPalette()); + + mMaterialGenerator = new MWQuadMaterialGen(&mMaterialGen); + + mDataIFS.open(std::string(fn + ".data").c_str(), std::ios::binary); + return true; + } + +private: + + inline long hasQuad(long x, long y) { + return (mIndex.getOffset(x,y) != -1 ) ; + } + + /** + * @brief loads the quad data from the disk + */ + MWQuadData* loadQuad(long x, long y) + { + long offset = mIndex.getOffset(x,y); + if ( offset == -1 ) //check we have xy + return 0; + + mDataIFS.seekg(offset); + + //load the quad from the file + MWQuadData* q = new MWQuadData(this); + boost::archive::binary_iarchive oa(mDataIFS); + oa >> *q; + return q; + } + + ///ifs for the data file. Opned on load + std::ifstream mDataIFS; + ///holds the offsets of the quads + Index mIndex; + TexturePalette mPalette; + ///material generator. gens a ogre::material from quad data + MaterialGenerator mMaterialGen; +}; diff --git a/terrain/cpp_mwquadmatgen.cpp b/terrain/cpp_mwquadmatgen.cpp new file mode 100644 index 000000000..0ce8ccd76 --- /dev/null +++ b/terrain/cpp_mwquadmatgen.cpp @@ -0,0 +1,84 @@ + +/** + * @todo mergre with matgen class + */ +class MWQuadMaterialGen : public QuadMaterialGenerator +{ +public: + MWQuadMaterialGen(MaterialGenerator* mg) : mMatGen(mg), mCount(0){} + + Ogre::MaterialPtr getMaterial(QuadData* qd){ + return _getMaterial((MWQuadData*)qd); + } + Ogre::MaterialPtr getMaterialSegment(QuadData* qd, QuadSegment* qs){ + return _getMaterialSegment((MWQuadData*)qd,qs); + } +private: + + Ogre::MaterialPtr _getMaterial(MWQuadData* qd){ + assert(qd); + const std::string name("MAT" + Ogre::StringConverter::toString(mCount++)); + + if ( qd->getTexture().length() ){ + return mMatGen->generateSingleTexture(name, qd->getTexture(), qd->getUsedResourcesRef()); + + }else{ + assert(0); + } + + return Ogre::MaterialPtr(); + } + + Ogre::MaterialPtr _getMaterialSegment(MWQuadData* qd, QuadSegment* qs){ + const std::string name("MAT" + Ogre::StringConverter::toString(mCount++)); + + + if ( qd->getTexture().length() ) + assert(0&&"NOT IMPLEMENTED"); + + const std::vector& tref = qd->getTextureIndexRef(); + const int indexSize = sqrt(tref.size()); + const int cellIndexSize = indexSize - 2; + + + //plus 1 to take into account border + const int xoff = float(cellIndexSize) * qs->getStartX(); + const int yoff = float(cellIndexSize) * qs->getStartY(); + const int size = float(cellIndexSize) * qs->getSegmentSize(); + + 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)); + + for ( int y = 1; y < size+1; ++y ){ + for ( int x = 1; x < size+1; ++x ){ + + if ( y+yoff >= indexSize ) assert(0); + if ( x+xoff >= indexSize ) assert(0); + + ti.at((y-1)*(size)+(x-1)) = tref.at((y+yoff)*(indexSize)+(x+xoff)); + } + } + */ + Ogre::MaterialPtr t = Ogre::MaterialManager::getSingleton(). + getByName(mMatGen->generateAlphaMap + (name, + ti,size, + 1, 1.0f/size, + qd->getUsedResourcesRef())); + return t; + } + + MaterialGenerator* mMatGen; + unsigned int mCount; + +}; diff --git a/terrain/cpp_palette.cpp b/terrain/cpp_palette.cpp new file mode 100644 index 000000000..dab2b3809 --- /dev/null +++ b/terrain/cpp_palette.cpp @@ -0,0 +1,75 @@ +/** +* @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_point2.cpp b/terrain/cpp_point2.cpp new file mode 100644 index 000000000..a113d96e8 --- /dev/null +++ b/terrain/cpp_point2.cpp @@ -0,0 +1,52 @@ +/* + 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 . +*/ + +/** +* @brief utility class for holding two values +* +* This is mainly used for querying the position of the quad. +* Each quad has a center position, which we can use as a unique identifier +*/ +template +struct Point2 { + T x, y; //held values. + + inline Point2() {} + inline Point2(T ix, T iy) { + x = ix; + y = iy; + } + inline Point2(const Point2& i) { + x = i.x; + y = i.y; + } + + /** + * @brief comparison operator. Although not used directly, this + * class is used in std::map a lot, which used the < operator + */ + + inline bool operator<(const Point2& rhs) const{ + return ( x < rhs.x || !( rhs.x < x) && y < rhs.y ); + } + + inline Point2 operator + (const Point2& rhs) { + return Point2(x + rhs.x, y + rhs.y); + } +}; diff --git a/terrain/cpp_quad.cpp b/terrain/cpp_quad.cpp new file mode 100644 index 000000000..5b7bf18a1 --- /dev/null +++ b/terrain/cpp_quad.cpp @@ -0,0 +1,327 @@ +/** + * @brief defines an area of Landscape + * + * A quad can either hold a mesh, or 4 other sub quads + * The functions split and unslip 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 + * + */ +class Quad +{ +public: + + /** + * 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. + */ + + // 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, Terrain* t) + : mParent(p), mTerrain(t), mTerrainMesh(0), mLocation(l) + { + //as mentioned elsewhere, the children should all be null. + memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN); + + //find the location of the quad + if ( l != Quad::QL_ROOT ) + { //if it isn't the root node + mDepth = p->getDepth() + 1; + + mPosition = p->getPosition(); + mSideLength = p->getSideLength()/2; + + //horrible bit of code + 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;//get rid of warning + } + + //set after positions have been retrived + TerrainHeightmap* d = mTerrain->getTerrainData(); + mMaxDepth = d->getMaxDepth(); + mHasData = d->hasData(this); + + if ( needSplit() ) //need to "semi" build terrain + split(); + else if ( mHasData ) + { + buildTerrain(); + getMesh()->justSplit(); + } + + } + else + { //assume it is root node, get data and possition + mDepth = 0; //root + mSideLength = mTerrain->getTerrainData()->getRootSideLength(); + mPosition = Point2(0,0); //see Quad::getPosition as to why this is always 0 + + mHasData = false; + + //always split, as this node never has data + split(); + } + } + + /** + * 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() + { + destroyTerrain(); + for (size_t i = 0; i < NUM_CHILDREN; i++) + delete mChildren[i]; + } + + void update(Ogre::Real t) + { + if ( needSplit() ) + { + split(); + return; + } + else if ( needUnsplit() ) + { + unsplit(); + return; + } + + //deal with updating the mesh. + if ( mTerrainMesh ) + mTerrainMesh->update(t); + else if ( hasChildren() ) + { + for (size_t i = 0; i < NUM_CHILDREN; ++i) { + assert( mChildren[i] ); + mChildren[i]->update(t); + } + } + } + + /** + * @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() + { + if ( hasChildren() || + getDepth() == mMaxDepth || + !hasData() ) + return false; + return ( mTerrainMesh && mTerrainMesh->getSplitState() + == MeshInterface::SS_SPLIT ); + } + + /** + * The functions preforms work on the quad tree state. There is no + * requirement for this to be called every frame, but it is not + * thread safe due to interacting with OGRE The function checks if a + * split or unsplit is needed. It also calls update on all children + + * @param t the time since the last frame + */ + + /** + * Deletes the landscape, if there is any + * Creates children, and either splits them, or creates landscape for them + */ + void split() + { + destroyTerrain(); + + //create a new terrain + for ( size_t i = 0; i < NUM_CHILDREN; ++i ) + mChildren[i] = new Quad((QuadLocation)i, this, mTerrain); + + assert(!needUnsplit()); + } + + /** + * @brief removes all children, and builds terrain on this level + */ + void unsplit() + { + //shouldn't unsplit 0 depth + assert(getDepth()); + + for ( size_t i = 0; i < NUM_CHILDREN; i++ )//{ + delete mChildren[i]; + memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN); + + if ( mHasData ) { + buildTerrain(); + getMesh()->justUnsplit(); + } + assert(!needSplit()); + } + + /** + * @return true if the node needs to delete all its child nodes, and + * rebuild the terrain its level + */ + bool needUnsplit() + { + if ( hasChildren() && getDepth() ) { + for (size_t i=0;i< NUM_CHILDREN;i++) { + if ( mChildren[i]->hasData() ) { + if ( !mChildren[i]->hasMesh() ) + return false; + else if ( mChildren[i]->getMesh()->getSplitState() != MeshInterface::SS_UNSPLIT) + return false; + } + } + return true; + } + + //get depth ensures the root doesn't try and unsplit + if ( getDepth() && !hasData() ) + return true; + + return false; + } + + /** + * @brief constructs the terrain on this level. The terrain must on + * exist before hand + */ + void buildTerrain() + { + assert(hasData()); + assert(getMesh() == NULL); //the terrain sould not exist + mTerrainMesh = new MeshInterface(this, mTerrain); + mTerrainMesh->create(); + } + + /** + * @brief destroys the terrain. + */ + void destroyTerrain() + { + delete mTerrainMesh; + mTerrainMesh = 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 mTerrainMesh != 0; } + + /** + * @return true if there are any children + */ + inline bool hasChildren() const { return mChildren[0] != 0; } + + /** + * @return the mesh. 0 if there is no mesh + */ + MeshInterface* getMesh() + { + if ( mTerrainMesh == 0 ) return 0; + return mTerrainMesh; + } + + /** + * @brief checks if the quad has any data (i.e. a mesh avaible for rendering + */ + inline bool hasData() const{ return mHasData; } + +private: + static const size_t NUM_CHILDREN = 4; + + Quad* mParent; /// this is the node above this. 0 if this is root + Quad* mChildren[4]; ///optionaly the children. Should be 0 if not exist + + Terrain* mTerrain; ///the pointer to the root terrain + MeshInterface* mTerrainMesh; ///the terrain mesh, only used if this is the bottom node + 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 + + bool mHasData; ///holds if there is terrain data about this quad + int mMaxDepth; ///the maxmium depth. Cached. This is not valid is mDepth == 0 +}; + +//const size_t Quad::NUM_CHILDREN = 4; diff --git a/terrain/cpp_quaddata.cpp b/terrain/cpp_quaddata.cpp new file mode 100644 index 000000000..20476e43f --- /dev/null +++ b/terrain/cpp_quaddata.cpp @@ -0,0 +1,203 @@ +/** + * @brief gets a material for the quad data + */ +class QuadMaterialGenerator +{ +public: + virtual Ogre::MaterialPtr getMaterial(QuadData* qd) = 0; + /** + * @brief can't overload :( + */ + virtual Ogre::MaterialPtr getMaterialSegment(QuadData* qd,QuadSegment* qs) = 0; +private: + /** + * @brief fix for gcc not putting the vtable anywhere unless there is a none inline, none virtual function + * @remarks does absolutly nothing ofc + */ + void _vtablefix(); +}; + +/** + * @brief abstract class used for getting LOD data. + * + * This class enables storing of data in whatever form is wanted + */ +class TerrainHeightmap { +public: + TerrainHeightmap() : mMaterialGenerator(0){ + } + virtual ~TerrainHeightmap() { + delete mMaterialGenerator; + } + + /** + * @brief called to load some data for the given quad + * @return NULL if the data doesn't exist + * + * The deleting of the memory is handled by TerrainMesh + */ + virtual QuadData* getData(Quad* q) = 0; + + /** + * @brief check if any data exists for this level + * @param q the quad that is asking for the data + */ + virtual bool hasData(Quad* q) = 0; + + /** + * @brief get the distance from one end of the map to the other + */ + virtual long getRootSideLength() = 0; + virtual int getMaxDepth() = 0; + + inline QuadMaterialGenerator* getMaterialGenerator(){ + assert(mMaterialGenerator); + return mMaterialGenerator; + } +protected: + QuadMaterialGenerator* mMaterialGenerator; +}; + +/** + * @brief 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 optoizied meshes + */ +class QuadData +{ +public: + QuadData(TerrainHeightmap* p) + : mHeightmap(p) {} + + virtual ~QuadData() {} + + /** + * 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); + } + + /** + * @brief should be removed when we get around to developing optomized meshes + */ + inline int getVertexSeperation() { + return mVertexSeperation; + } + inline void setVertexSeperation(int vs) { + mVertexSeperation = vs; + } + + /** + * @brief lazy get function. Avoids creating materail until requested + */ + inline Ogre::MaterialPtr getMaterial() { + if ( mMaterial.isNull() ) + createMaterial(); + assert(!mMaterial.isNull()); + return mMaterial; + } + QuadMaterialGenerator* getMaterialGenerator() + { + return mHeightmap->getMaterialGenerator(); + } + + /** + * @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; + } + +protected: + void createMaterial() + { + mMaterial = getMaterialGenerator()->getMaterial(this); + } + TerrainHeightmap* mHeightmap; + Ogre::MaterialPtr mMaterial; + + std::string mParentTexture; + + int mVertexSeperation; + std::vector mHeights; + std::vector mNormals; +}; + +class MWQuadData : public QuadData +{ +public: + typedef std::list ResourceList; + typedef std::list::const_iterator ResourceListCItr; + + MWQuadData(TerrainHeightmap* thm) : QuadData(thm) {} + + ~MWQuadData () + { + const ResourceListCItr end = mResources.end(); + for ( ResourceListCItr itr = mResources.begin(); itr != end; ++itr ) + (*itr)->getCreator()->remove((*itr)->getHandle()); + } + + /** + * @return a ref to the list of used resourcs + */ + 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; } + +private: + ///Holds the resources used by the quad + ResourceList mResources; + + std::vector mTextureIndex; ///holds index that corespond the 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(MWQuadData, boost::serialization::track_never); diff --git a/terrain/cpp_quadsegment.cpp b/terrain/cpp_quadsegment.cpp new file mode 100644 index 000000000..e4c659456 --- /dev/null +++ b/terrain/cpp_quadsegment.cpp @@ -0,0 +1,145 @@ +/* Represents a segment of a quad. It can also represent a large segment + * + * this is needed for experimenting with splitting the lowest + * quad level into small segments for better culling. Due to the + * intented design of using optomized meshes, this should not work on + * optomized meshes, as there is no easy way to segment the data + * + * This uses floating point numbers to define the areas of the + * quads. There may be issues due to accuracy if we go to small but it + * should be caught in debug mode + */ +#define QSDEBUG + +class QuadSegment +{ +public: + /** + * @param qd the parent quad data + * @param size the proportion of the total size that the segment is. Must be greater than 0, but less than or equal to 1 + * @param x,y the start positions of the segment + */ + QuadSegment(QuadData* qd, float size = 1, float x = 0, float y = 0) + : mQuadData(qd), + mSegmentSize(size), + mX(x), + mY(y) + { + assert(qd); + assert(size>0&&size<=1); + assert(mY>=0&&mY<=1); + assert(mX>=0&&mY<=1); + +#ifdef QSDEBUG + { + //check sizes + const float qw = mQuadData->getVertexWidth()-1; + const float fsw = qw*size; + const int isw = (int)fsw; + assert(fsw==isw); + } +#endif + + //precalc offsets, as getVertex/getNormal get called a lot (1000s of times) + computeOffsets(); + } + + /** + * 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 getVertex(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); + } + + /** + * @brief lazy function for getting a material + */ + Ogre::MaterialPtr getMaterial() + { + if ( mMaterial.isNull() ) + createMaterial(); + assert(!mMaterial.isNull()); + return mMaterial; + } + + void createMaterial() + { + assert(mSegmentSize>0); + if ( mSegmentSize == 1 ) //we can use the top level material + mMaterial = mQuadData->getMaterial(); + else //generate a material spesificly for this + mMaterial = mQuadData->getMaterialGenerator()-> + getMaterialSegment(mQuadData,this); + assert(!mMaterial.isNull()); + } + + 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; + } + +private: + 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; + Ogre::MaterialPtr mMaterial; + + int mXOffset, mYOffset; +}; diff --git a/terrain/cpp_terrain.cpp b/terrain/cpp_terrain.cpp new file mode 100644 index 000000000..40d48211f --- /dev/null +++ b/terrain/cpp_terrain.cpp @@ -0,0 +1,151 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2009 Jacob Essex, Nicolay Korslund + WWW: http://openmw.sourceforge.net/ + + This file (cpp_terrain.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/ . + +*/ + +// 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; + +///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; + +//stops it crashing, now it leaks. +#define ENABLED_CRASHING 0 + +class Quad; +class QuadData; +class QuadSegment; +class Terrain; + +// Prerequisites +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// For generation +#include "cpp_esm.cpp" +#include "cpp_landdata.cpp" +#include "cpp_quaddata.cpp" +#include "cpp_point2.cpp" +#include "cpp_materialgen.cpp" +#include "cpp_index.cpp" +#include "cpp_palette.cpp" +#include "cpp_generator.cpp" + +// For rendering +#include "cpp_quadsegment.cpp" +#include "cpp_baseland.cpp" +#include "cpp_mwquadmatgen.cpp" + +// These depend on each other, so our usual hackery won't work. We +// need the header files first. +#include "cpp_terrainmesh.h" +#include "cpp_meshinterface.h" +#include "cpp_terraincls.h" + +#include "cpp_quad.cpp" + +#include "cpp_terraincls.cpp" +#include "cpp_terrainmesh.cpp" +#include "cpp_meshinterface.cpp" + +#include "cpp_mwheightmap.cpp" +#include "cpp_framelistener.cpp" + +TerrainFrameListener terrainListener; + +extern "C" void d_superman(); + +// Set up the rendering system +extern "C" void terr_setupRendering() +{ + // Add the terrain directory + ResourceGroupManager::getSingleton(). + addResourceLocation(TEXTURE_OUTPUT, "FileSystem", "General"); + + // Set up the terrain frame listener + terrainListener.setup(); + + // Enter superman mode + mCamera->setFarClipDistance(32*8192); + d_superman(); +} + +// Generate all cached data. +extern "C" void terr_genData() +{ + Ogre::Root::getSingleton().renderOneFrame(); + + Generator mhm(TERRAIN_OUTPUT); + { + ESM esm; + + const std::string fn("data/Morrowind.esm"); + + esm.addRecordType("LAND", "INTV"); + esm.addRecordType("LTEX", "INTV"); + + esm.loadFile(fn); + RecordListPtr land = esm.getRecordsByType("LAND"); + for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr ) + mhm.addLandData(*itr, fn); + + RecordListPtr ltex = esm.getRecordsByType("LTEX"); + for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr ) + mhm.addLandTextureData(*itr, fn); + } + + mhm.beginGeneration(); + + mhm.generateLODLevel(6, true, 1024); + mhm.generateLODLevel(5, true, 512); + mhm.generateLODLevel(4, true, 256); + mhm.generateLODLevel(3, true, 256); + mhm.generateLODLevel(2, true, 256); + mhm.generateLODLevel(1, false, 128); + + mhm.endGeneration(); +} diff --git a/terrain/cpp_terraincls.cpp b/terrain/cpp_terraincls.cpp new file mode 100644 index 000000000..e8e35f5ed --- /dev/null +++ b/terrain/cpp_terraincls.cpp @@ -0,0 +1,45 @@ +Terrain::Terrain(TerrainHeightmap* d, + Ogre::SceneNode* r) + : mTerrainData(d), + mTerrainSceneNode(r), + mQuadRoot(0), + mQuadCreateFunction(0), + mQuadDestroyFunction(0), + mMorphingEnabled(true), + mTextureFadingEnabled(true), + mBaseLand(r) +{ + +} +//---------------------------------------------- +Terrain::~Terrain(){ + delete mQuadRoot; +} +//---------------------------------------------- +void Terrain::create(){ + mQuadRoot = new Quad(Quad::QL_ROOT, 0, this); //cleaned in Terrain::~Terrain +} +//---------------------------------------------- +void Terrain::update(Ogre::Real t){ + assert(mQuadRoot); + mQuadRoot->update(t); + mBaseLand.update(); +} +//---------------------------------------------- +void Terrain::_quadCreated(QuadData* qd){ + if ( mQuadCreateFunction ) (*mQuadCreateFunction)(qd); +} +//---------------------------------------------- +void Terrain::_quadDestroyed(QuadData* qd){ + if ( mQuadDestroyFunction ) (*mQuadDestroyFunction)(qd); +} +//---------------------------------------------- +int Terrain::getMaxDepth(){ + return mTerrainData->getMaxDepth(); +} +//---------------------------------------------- +void Terrain::reload(){ + delete mQuadRoot; + mQuadRoot = new Quad(Quad::QL_ROOT, 0, this); +} +//---------------------------------------------- diff --git a/terrain/cpp_terraincls.h b/terrain/cpp_terraincls.h new file mode 100644 index 000000000..fd7f6bfdf --- /dev/null +++ b/terrain/cpp_terraincls.h @@ -0,0 +1,152 @@ +class Terrain +{ +public: + /** + * @brief inits vars and creates quad tree + * @param d the heightmap that contains the data + * @param s the current scne manager + * @param r the root scene node that all terrain nodes are barsed off + * @param c the camera that the poistion data is taken from + * + * The terrain is create in the constructor. + * @todo change quad root creation to create/destroy funcs? + */ + explicit Terrain(TerrainHeightmap* d, Ogre::SceneNode* r); + /** + * @brief deletes the quad tree + */ + ~Terrain(); + + /** + * @brief creates the quad tree + * @remarks This must be called before any call to upate(). We cannot call it from the constructor as options + * may not have been set yet + */ + void create(); + + /** + * @brief recreates the quad tree by destroying everything and starting again. + * @remarks this is very slow, as it drops all created alpha maps and meshes. + * @todo check this works + */ + void reload(); + + /** + * @brief sets the scene node that all of the terrain nodes are based off + * @param r the scene node to use for terrain + */ + inline void setTerrainSceneNode(Ogre::SceneNode* r){ mTerrainSceneNode = r; } + + /** + * @return the scene node that should be used as the root of the terrain + * + * This is mainly used by the quad tree + */ + inline Ogre::SceneNode* getTerrainSceneNode(){return mTerrainSceneNode;} + + /** + * @brief updates the quad tree, splittig and unsplitting where needed. + * + * There is no requirement for this to be called every frame + */ + void update(Ogre::Real t); + + /** + * @return the heightmap data + */ + inline TerrainHeightmap* getTerrainData(){ return mTerrainData; } + + /** + * @brief handles the actions to take on the creation of a terrain mesh + * @param qd the quad data. It is valid until the same variable is passed to _quadDestroyed + */ + void _quadCreated(QuadData* qd); + + /** + * @brief The quad as defined by the quad data qd has been destroyed + * @param qd the quad that has been destroyed. This is only valid for this function. Is is deleted just after + */ + void _quadDestroyed(QuadData* qd); + + /** + * @brief sets the function to be used in the callback when a quad is created + * @param f the function to use. Set to null to disable callbacks + */ + inline void setQuadCreateFunction(void (*f)(QuadData*)){ + mQuadCreateFunction = f; + } + /** + * @brief sets the function to be used in the callback when a quad is destroyed + * @param f the function to use. Set to null to disable callbacks + */ + inline void setQuadDestroyFunction(void (*f)(QuadData*)){ + mQuadDestroyFunction = f; + } + + /** + * @brief time in seconds to morph to full detail after an unsplit. + */ + inline Ogre::Real getMorphSpeed(){return 1.0f;} + /** + * @brief the time in seconds it takes for the texture to fade to the higher detail + */ + inline Ogre::Real getTextureFadeSpeed(){ return 2.0f;} + + /** + * @return the maximum depth of the quad tree + * @remarks this is used by the terrain renderable for adding morhping to textures. It doesn't want to happen + * on the lowest detail + */ + int getMaxDepth(); + + /** + * @brief disables/enables morphing + * @remarks This will not happen instantly unless reload() is called + */ + + inline void setMorphingEnabled(bool enabled){ + mMorphingEnabled = enabled; + + } + /** + * @return true if morphing is enabled + */ + inline bool isMorhpingEnabled() const{ + return mMorphingEnabled; + } + + /** + * @brief disables/enables texture fading. + * @remarks This will not happen instantly unless reload() is called. Morphing is required atm + */ + 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; + } + /** + * @return true if texture fading is enabled + */ + inline bool isTextureFadingEnabled() const{ + return mTextureFadingEnabled; + } +protected: + TerrainHeightmap* mTerrainData; + + /// the scenenode that every other node is decended from. This + /// should be surplied by the user + Ogre::SceneNode* mTerrainSceneNode; + + ///the root node for all the quads. + Quad* mQuadRoot; + + ///quad callback function + void (*mQuadCreateFunction)(QuadData*); + ///quad callback function + void (*mQuadDestroyFunction)(QuadData*); + + bool mMorphingEnabled; + bool mTextureFadingEnabled; + + BaseLand mBaseLand; +}; diff --git a/terrain/cpp_terrainmesh.cpp b/terrain/cpp_terrainmesh.cpp new file mode 100644 index 000000000..43ef218d0 --- /dev/null +++ b/terrain/cpp_terrainmesh.cpp @@ -0,0 +1,478 @@ +//---------------------------------------------- +TerrainRenderable::TerrainRenderable(Terrain* t, QuadSegment* qs,int width, int depth, bool skirts) : + Ogre::Renderable(), + Ogre::MovableObject(), + mWidth(width), + mUseSkirts(skirts), + mBuilt(false), + mDepth(depth), + mSegment(qs), + mTerrain(t), + mVertexes(0), + mIndices(0), + mLODMorphFactor(0), + mTextureFadeFactor(0), + mMin(30000), + mMax(-30000), + mExtraMorphAmount(0), + mHasFadePass(false) {} +//---------------------------------------------- +TerrainRenderable::~TerrainRenderable() { + destroy(); +} +//---------------------------------------------- +void TerrainRenderable::create(Ogre::SceneNode* sn) { + using namespace Ogre; + + if ( mBuilt ) return; + + createVertexBuffer(); + calculateVetexValues(); + calculateIndexValues(); + setBounds(); + + sn->attachObject(this); + + mMaterial = mSegment->getMaterial(); + + + + if ( mTerrain->isMorhpingEnabled() && mDepth != mTerrain->getMaxDepth() ) { + Ogre::Technique* tech = getMaterial()->getTechnique(0); + for ( size_t i = 0; i < tech->getNumPasses(); ++i ) { + assert(mTerrain->isMorhpingEnabled()); + tech->getPass(i)->setVertexProgram(MORPH_VERTEX_PROGRAM); + } + } + + if ( mTerrain->isMorhpingEnabled() ) + calculateDeltaValues(); + + mBuilt = true; +} +//---------------------------------------------- +void TerrainRenderable::destroy() { + if ( !mBuilt ) return; + + //deleting null values is fine iirc + delete mIndices; + +# if ENABLED_CRASHING == 1 + { + delete mVertexes; + } +# else + { + if ( mDepth != mTerrain->getMaxDepth() ){ + delete mVertexes; + } + } +# endif + + mBuilt = false; +} +//---------------------------------------------- +void TerrainRenderable::update(Ogre::Real time, Ogre::Real camDist, Ogre::Real usplitDist, Ogre::Real morphDist) { + //if ( USE_MORPH ){ + + //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/mTerrain->getMorphSpeed()); //decrease slowly + } + if ( mExtraFadeAmount > 0 ) { + mTextureFadeFactor += mExtraFadeAmount; + mExtraFadeAmount -= (time/mTerrain->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 ( mTerrain->isTextureFadingEnabled() && + !mHasFadePass && + mTextureFadeFactor > 0 && + mSegment->hasParentTexture() ) { + addFadePass(); + } + +} +//---------------------------------------------- +void TerrainRenderable::addFadePass() { + assert(mHasFadePass==false); + + if ( mDepth == mTerrain->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(mTerrain->isTextureFadingEnabled()); + newPass->setFragmentProgram(FADE_FRAGMENT_PROGRAM); + + if ( mTerrain->isMorhpingEnabled() && mDepth != mTerrain->getMaxDepth() ) { + assert(mTerrain->isMorhpingEnabled()); + newPass->setVertexProgram(MORPH_VERTEX_PROGRAM); + } + + + //set texture to be used + newPass->createTextureUnitState(mSegment->getParentTexture(), 1); +} +//---------------------------------------------- +void TerrainRenderable::removeFadePass() { + assert(mHasFadePass==true); + mHasFadePass = false; + Ogre::MaterialPtr mat = getMaterial(); + Ogre::Technique* tech = mat->getTechnique(0); + + tech->removePass(tech->getNumPasses()-1); +} +//---------------------------------------------- +void TerrainRenderable::justSplit() { + mExtraMorphAmount = 1; + mLODMorphFactor = 1; + mTextureFadeFactor = 1; + mExtraFadeAmount = 1; + + if ( mTerrain->isTextureFadingEnabled() && mSegment->hasParentTexture() ) + addFadePass(); +} + +//---------------------------------------------- +void TerrainRenderable::_updateCustomGpuParameter( + const GpuProgramParameters::AutoConstantEntry& constantEntry, + 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); + + +} +//---------------------------------------------- +float TerrainRenderable::getVertexHeight(int x, int y) { + return mSegment->getVertex(x,y); +} +//---------------------------------------------- +Ogre::Vector3 TerrainRenderable::getVertexPosition(int x, int y) { + return Ogre::Vector3(x*mSegment->getVertexSeperation(), getVertexHeight(x,y) , y*mSegment->getVertexSeperation()); +} +//---------------------------------------------- +void TerrainRenderable::calculateVetexValues() { + using namespace Ogre; + + //get the texture offsets for the higher uv + float xUVOffset = 0; + float yUVOffset = 0; + + if ( mTerrain->isTextureFadingEnabled() ) { + assert(0); + } + /* + switch (mInterface->getLocation()) { + case Quad::QL_NW : + yUVOffset = 32.0f/64.0f; + break; + case Quad::QL_NE: + yUVOffset = 32.0f/64.0f; + xUVOffset = 32.0f/64.0f; + break; + case Quad::QL_SE: + xUVOffset = 32.0f/64.0f; + break; + default: + break; + } + */ + + 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)*mSegment->getVertexSeperation(); + else *verts++ = x*mSegment->getVertexSeperation(); + + *verts++ = -4096; //2048 below base sea floor height + + if ( y < 0 ) *verts++ = 0; + else if ( y > (mWidth-1) ) *verts++ = (mWidth-1)*mSegment->getVertexSeperation(); + else *verts++ = y*mSegment->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 ( mTerrain->isTextureFadingEnabled() ) { + *verts++ = u; + *verts++ = v; + } + } else { + + assert(y*mWidth+x>=0&&y*mWidth+xgetVertexSeperation(); + *verts++ = getVertexHeight(x,y); + *verts++ = y*mSegment->getVertexSeperation(); + + mMax = std::max(mMax, getVertexHeight(x,y)); + mMin = std::min(mMin, getVertexHeight(x,y)); + + //normals + for ( Ogre::uint i = 0; i < 3; i++ ) + *verts++ = mSegment->getNormal(x,y,i); + //*verts++ = mInterface->getNormal((y*mWidth+x)*3+i); + + const float u = (float)(x) / (mWidth-1); + const float v = (float)(y) / (mWidth-1); + assert(u>=0&&v>=0); + assert(u<=1&&v<=1); + + *verts++ = u; + *verts++ = v; + + if ( mTerrain->isTextureFadingEnabled() ) { + *verts++ = u/2.0f + xUVOffset; + *verts++ = v/2.0f + yUVOffset; + } + } + } + } + mMainBuffer->unlock(); +} +//---------------------------------------------- +void TerrainRenderable::setBounds() { + mBounds.setExtents(0,mMin,0, + (mWidth - 1) * mSegment->getVertexSeperation(), + mMax, + (mWidth - 1) * mSegment->getVertexSeperation()); + + mCenter = Ogre::Vector3( ( (mWidth - 1) * mSegment->getVertexSeperation() ) / 2, + ( mMin + mMax ) / 2, + ( (mWidth - 1) * mSegment->getVertexSeperation() ) / 2); + + mBoundingRadius = (mBounds.getMaximum() - mBounds.getMinimum()).length() / 2; +} +//---------------------------------------------- +void TerrainRenderable::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 ( mTerrain->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 ( mTerrain->isMorhpingEnabled() ) + vertexDecl->addElement(DELTA_BINDING, 0, VET_FLOAT1, VES_BLEND_WEIGHTS); + + +} + +Ogre::HardwareVertexBufferSharedPtr TerrainRenderable::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 + +} + + + + +//---------------------------------------------- +#define SET_DELTA_AT(x, y, v) \ +if ( mUseSkirts ) pDeltas[( y + 1)*vw+ x + 1] = v; \ +else pDeltas[( y)*vw+ x] = v; +void TerrainRenderable::calculateDeltaValues() { + + using namespace Ogre; + size_t vw = mWidth; + if ( mUseSkirts ) vw += 2; + + //must be using morphing to use this function + assert(mTerrain->isMorhpingEnabled()); + + 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 + +//---------------------------------------------- +void TerrainRenderable::calculateIndexValues() { + using namespace Ogre; + + + + 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; +} diff --git a/terrain/cpp_terrainmesh.h b/terrain/cpp_terrainmesh.h new file mode 100644 index 000000000..245cc8432 --- /dev/null +++ b/terrain/cpp_terrainmesh.h @@ -0,0 +1,229 @@ +/** +* @brief Terrain for one cell. Handles building, destroying and LOD morphing +*/ +class TerrainRenderable : public Ogre::Renderable, public Ogre::MovableObject { +public: + + explicit TerrainRenderable(Terrain* t, QuadSegment* qs, int width, int depth, bool skirts); + + /** + * Calls destroy(); + */ + ~TerrainRenderable(); + + /** + * Creates the actual mesh, it has to be called manually, as it is not called in the constructor + */ + void create(Ogre::SceneNode* sn) ; + + /** + * Can be called manually and can be called from the destuctor. This destroyes the mesh + */ + void destroy(); + + /** + * @brief Checks if it needs to be split or unsplit and deals with the morph factor + * @brief time seconds since last frame + */ + void update(Ogre::Real time, Ogre::Real camDist, Ogre::Real usplitDist, Ogre::Real morphDist); + + /** + * @todo Needs to work out what this does (if it does what it is meant to) + */ + 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.indexData = mIndices; + } + + //----------------------------------------------------------------------- + void getWorldTransforms( Ogre::Matrix4* xform ) const { + *xform = mParentNode->_getFullTransform(); + } + //----------------------------------------------------------------------- + const Ogre::Quaternion& getWorldOrientation(void) const { + return mParentNode->_getDerivedOrientation(); + } + //----------------------------------------------------------------------- + const Ogre::Vector3& getWorldPosition(void) const { + return mParentNode->_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); + mLightListDirty = false; + } + return mLightList; + } + //----------------------------------------------------------------------- + virtual const Ogre::String& getMovableType( void ) const { + static Ogre::String t = "MW_TERRAIN"; + return t; + } + //----------------------------------------------------------------------- + void _updateRenderQueue( Ogre::RenderQueue* queue ) { + mLightListDirty = true; + queue->addRenderable(this, mRenderQueueID); + } + + const Ogre::AxisAlignedBox& getBoundingBox( void ) const { + return mBounds; + }; + //----------------------------------------------------------------------- + Ogre::Real getBoundingRadius(void) const { + return mBoundingRadius; + } + //----------------------------------------------------------------------- + + /** + * @brief passes the morph factor to the custom vertex program + */ + void _updateCustomGpuParameter(const Ogre::GpuProgramParameters::AutoConstantEntry& constantEntry, + Ogre::GpuProgramParameters* params) const; + + /** + * @brief sets the mExtraMorphAmount so it slowly regains detail from the lowest morph factor + */ + void justSplit(); + /** + * @brief Does nothing + */ + inline void justUnsplit(){ + } + + inline float getMax(){ + return mMax; + } + inline float getMin(){ + return mMin; + } + +private: + + /** + * @brief Adds another pass to the material to fade in/out the material from a higher level + */ + void addFadePass(); + /** + * @brief removes the last pass from the material. Assumed to be the fade pass + */ + void removeFadePass(); + + /** + * @return the height at the given vertex + */ + float getVertexHeight(int x, int y); + + /** + * Inits the vertex stuff + */ + void createVertexBuffer(); + + /** + * @brief fills the vertex buffer with data + * @todo I don't think tex co-ords are right + */ + void calculateVetexValues(); + + /** + * @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( ); + + /** + * @brief DOESN'T WORK FULLY + * @todo fix + */ + void calculateDeltaValues(); + /** + * @brief gets the position of a vertex. It will not interpolate + */ + Ogre::Vector3 getVertexPosition(int x, int y); + + /** + * @brief gets the indicies for the vertex data. + */ + void calculateIndexValues(); + + /** + * @brief sets the bounds on the renderable. This requires that mMin/mMax have been set. They are set in createVertexData + * @remarks 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(); + + + const int mWidth; + const bool mUseSkirts; + + ///true if the land has been built + bool mBuilt; + + int mDepth; + QuadSegment* mSegment; + Terrain* mTerrain; + + Ogre::MaterialPtr mMaterial; + + Ogre::ManualObject* mObject; + Ogre::SceneNode* mSceneNode; + + + Ogre::VertexData* mVertexes; + 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; + + + /** + * @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 new file mode 100644 index 000000000..c1f1e8afa --- /dev/null +++ b/terrain/terrain.d @@ -0,0 +1,68 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2009 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.snaptoad.com/ + + This file (terrain.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/ . + + */ + +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); +} + +void initTerrain() +{ + makePath("cache/terrain"); + + //terr_genData(); + terr_setupRendering(); +} + +extern(C): +void terr_genData(); +void terr_setupRendering();