- integrated Yacobys landscape. First commit.
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@112 ea6a568a-9f4f-0410-981a-c910a81bb256actorid
parent
ea486dd770
commit
a46804dae3
@ -0,0 +1,116 @@
|
|||||||
|
class BaseLand
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BaseLand(Ogre::SceneNode* s)
|
||||||
|
: mTerrainSceneNode(s)
|
||||||
|
{
|
||||||
|
createMaterial();
|
||||||
|
createMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
~BaseLand()
|
||||||
|
{
|
||||||
|
destroyMaterial();
|
||||||
|
destroyMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief repositions the mesh, and ensures that it is the right size
|
||||||
|
*/
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
Ogre::Real vd = mCamera->getFarClipDistance();
|
||||||
|
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;
|
||||||
|
};
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
///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<std::string, SubRecord> SubRecordMap;
|
||||||
|
typedef SubRecordMap::iterator SubRecordMapItr;
|
||||||
|
SubRecordMap mSubRecords;
|
||||||
|
|
||||||
|
std::string mType;
|
||||||
|
std::string mId;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef boost::shared_ptr<Record> RecordPtr;
|
||||||
|
typedef std::list<RecordPtr> RecordList;
|
||||||
|
typedef RecordList::iterator RecordListItr;
|
||||||
|
typedef boost::shared_ptr<RecordList> RecordListPtr;
|
||||||
|
|
||||||
|
typedef std::map<std::string, RecordPtr> RecordMap;
|
||||||
|
typedef RecordMap::iterator RecordMapItr;
|
||||||
|
|
||||||
|
///top level class for loading and saving esp files.
|
||||||
|
class ESM
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// types of records to load
|
||||||
|
std::map<std::string, std::string> mLoadTypes;
|
||||||
|
|
||||||
|
/// map<id, record> 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;
|
||||||
|
}
|
||||||
|
};
|
@ -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);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
};
|
@ -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<int>(mMWLand.getMaxX(), max);
|
||||||
|
max = std::max<int>(mMWLand.getMaxY(), max);
|
||||||
|
max = std::max<int>(-mMWLand.getMinX(), max);
|
||||||
|
max = std::max<int>(-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<float>& gh = qd.getHeightsRef(); //ref to the data storage in the quad
|
||||||
|
std::vector<char>& 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<int>& 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<int> 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<int>& 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<int> 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<int>::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<int>& ltex,
|
||||||
|
int texSize, int alphaSize)
|
||||||
|
{
|
||||||
|
std::cout << " Creating " << outputName << "\n";
|
||||||
|
|
||||||
|
assert(Ogre::Math::Sqrt(ltex.size())==alphaSize);
|
||||||
|
std::list<Ogre::ResourcePtr> 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<Ogre::ResourcePtr>::const_iterator iend = createdResources.end();
|
||||||
|
for ( std::list<Ogre::ResourcePtr>::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<float>& gh, std::vector<char>& 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<float>& ch = mMWLand.getHeights(cellX,cellY);
|
||||||
|
std::vector<char>& 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
|
||||||
|
};
|
@ -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<long, std::map<long, long> >::iterator OffsetItr;
|
||||||
|
typedef std::map<long, std::map<long, long> >::const_iterator OffsetConstItr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief sets the root quads side length in gu
|
||||||
|
* @param l the side length
|
||||||
|
*
|
||||||
|
* This is used for working out the locations of quad children.
|
||||||
|
* I am assuming a long is enough...
|
||||||
|
*/
|
||||||
|
inline void setRootSideLength(long l) {
|
||||||
|
mRootSideLength = l;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return the side length of the root quad.
|
||||||
|
*/
|
||||||
|
inline long getRootSideLength() const {
|
||||||
|
return mRootSideLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void setMaxDepth(int d) {
|
||||||
|
mMaxDepth = d;
|
||||||
|
}
|
||||||
|
inline int getMaxDepth() const {
|
||||||
|
return mMaxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return -1 is returned if there is no offset
|
||||||
|
* @param x, y the position of the quad in gu
|
||||||
|
*
|
||||||
|
* Slightly faster using hasOffset to check if it exists
|
||||||
|
* Shouldn't be noticable diffrence.
|
||||||
|
*/
|
||||||
|
inline long getOffset(long x, long y) const { //inline?
|
||||||
|
OffsetConstItr itr1 = mQuadOffsets.find(x);
|
||||||
|
if ( itr1 == mQuadOffsets.end() ) return -1;
|
||||||
|
std::map<long, long>::const_iterator itr2 = itr1->second.find(y);
|
||||||
|
if ( itr2 == itr1->second.end() ) return -1;
|
||||||
|
return itr2->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief checks if a quad for the given position exists
|
||||||
|
* @return true/false
|
||||||
|
* @param x, y the position of the quad in gu
|
||||||
|
*
|
||||||
|
* @todo Would it be worth merging this with getOffset?
|
||||||
|
*/
|
||||||
|
inline bool hasOffset(long x, long y) const {
|
||||||
|
OffsetConstItr itr = mQuadOffsets.find(x);
|
||||||
|
if ( itr == mQuadOffsets.end() ) return false;
|
||||||
|
return (itr->second.find(y) != itr->second.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief sets an offset of a quad
|
||||||
|
* @param x, y the position in gu of the quad
|
||||||
|
* @param o the offset within the file of the records for this quad
|
||||||
|
*/
|
||||||
|
inline void setOffset(long x, long y, long o) {
|
||||||
|
mQuadOffsets[x][y] = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::map<long, std::map<long, long> > mQuadOffsets;
|
||||||
|
long mRootSideLength; ///length in gu of the root quad
|
||||||
|
int mMaxDepth; ///maximum depth assuming root quad depth = 0
|
||||||
|
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
/**
|
||||||
|
* Saves the data for the max depth, the root side legnth, and the quad offsets
|
||||||
|
*/
|
||||||
|
template<class Archive>
|
||||||
|
inline void serialize(Archive& ar, const unsigned int version){
|
||||||
|
|
||||||
|
ar &mMaxDepth;
|
||||||
|
ar &mRootSideLength;
|
||||||
|
ar &mQuadOffsets;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_CLASS_TRACKING(Index, boost::serialization::track_never);
|
@ -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<int>(mMaxX, intv.x);
|
||||||
|
mMaxY = std::max<int>(mMaxY, intv.y);
|
||||||
|
mMinX = std::min<int>(mMinX, intv.x);
|
||||||
|
mMinY = std::min<int>(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<float>& getHeights(int x, int y)
|
||||||
|
{
|
||||||
|
if ( hasData(x,y) )
|
||||||
|
return mLandRecords[x][y].heights;
|
||||||
|
static std::vector<float> e(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<char>& getNormals(int x, int y)
|
||||||
|
{
|
||||||
|
if ( hasData(x,y) )
|
||||||
|
return mLandRecords[x][y].normals;
|
||||||
|
static std::vector<char> e(LAND_NUM_VERTS*3,0);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<int , std::map<int, LandData> >::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<std::string, std::map<short, LandTexture> >::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<float> parseHeights(LAND::VHGT* vhgt)
|
||||||
|
{
|
||||||
|
std::vector<float> 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<char> parseNormals( LAND::VNML* vnml )
|
||||||
|
{
|
||||||
|
std::vector<char> 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<short> parseTextures( LAND::VTEX* vtex )
|
||||||
|
{
|
||||||
|
std::vector<short> 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<float> heights;
|
||||||
|
std::vector<char> normals;
|
||||||
|
std::vector<short> textures;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LandTexture
|
||||||
|
{
|
||||||
|
std::string name, data;
|
||||||
|
short intv;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::string, std::map<short, LandTexture> > mLTEXRecords;
|
||||||
|
std::map<int, std::map<int,LandData> > mLandRecords;
|
||||||
|
};
|
@ -0,0 +1,367 @@
|
|||||||
|
/**
|
||||||
|
* Handles the runtime generation of materials
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MaterialGenerator
|
||||||
|
{
|
||||||
|
class TextureSplatter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline TextureSplatter(const std::vector<int>& 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<int>& 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<Ogre::ResourcePtr> 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<int>& ltex, int size, std::list<Ogre::TexturePtr>& 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<std::string> texturesWritten; //a list of "done" textures. Order important
|
||||||
|
std::vector<std::string> alphasGenerated; //alphas generated
|
||||||
|
|
||||||
|
std::set<short> 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<Ogre::uint8*>(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<std::string>::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<int>& ltex,
|
||||||
|
int size,
|
||||||
|
int border,
|
||||||
|
float scaleDiv,
|
||||||
|
std::list<Ogre::ResourcePtr>& 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<int> textures;
|
||||||
|
for ( int y1 = 0; y1 < ltexWidth; y1++ ) {
|
||||||
|
for ( int x1 = 0; x1 < ltexWidth; x1++ ) {
|
||||||
|
textures.insert(ltex[(y1)*ltexWidth+x1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( std::set<int>::iterator itr = textures.begin(); itr != textures.end(); ++itr )
|
||||||
|
{
|
||||||
|
|
||||||
|
int tid = *itr;
|
||||||
|
|
||||||
|
const std::string tn(mTexturePaths[tid]);
|
||||||
|
if ( tn == "_land_default.dds" )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//std::cout << " Generating texture " << tn << "\n";
|
||||||
|
|
||||||
|
std::string alphaName(materialName + "_A_" + tn);
|
||||||
|
if ( Ogre::TextureManager::getSingleton().resourceExists(alphaName) )
|
||||||
|
OGRE_EXCEPT(0, "ALPHA Already Exists", "");
|
||||||
|
|
||||||
|
//create alpha map
|
||||||
|
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
|
||||||
|
createManual(
|
||||||
|
alphaName, // Name of texture
|
||||||
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, // Name of resource group in which the texture should be created
|
||||||
|
Ogre::TEX_TYPE_2D,
|
||||||
|
size,size, //size ofc
|
||||||
|
1, 0, //depth, mipmaps
|
||||||
|
Ogre::PF_A8, //we only need one channel to hold the splatting texture
|
||||||
|
Ogre::TU_STATIC_WRITE_ONLY //best performace, we shopuldn't need the data again
|
||||||
|
);
|
||||||
|
|
||||||
|
createdResources.push_back(texPtr);
|
||||||
|
|
||||||
|
|
||||||
|
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
|
||||||
|
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
|
||||||
|
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||||
|
|
||||||
|
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
|
||||||
|
memset(pDest,0, sizeof(Ogre::uint8)*size*size);
|
||||||
|
|
||||||
|
TextureSplatter ts(ltex, size, ltexWidth, 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<int>& ltex,
|
||||||
|
int size,
|
||||||
|
int border,
|
||||||
|
float scaleDiv,
|
||||||
|
std::list<Ogre::ResourcePtr>& 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<int, std::string> r) {
|
||||||
|
mTexturePaths = r;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Merged records accross all mods for LTEX data
|
||||||
|
*/
|
||||||
|
std::map<int, std::string> mTexturePaths;
|
||||||
|
};
|
@ -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<TerrainObjectGroup*>::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<TerrainObjectGroup*>::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);
|
||||||
|
}
|
@ -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<TerrainObjectGroup*>::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<TerrainObjectGroup*>::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<TerrainObjectGroup*> 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 );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
@ -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;
|
||||||
|
};
|
@ -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<int>& 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<int> ti;
|
||||||
|
|
||||||
|
|
||||||
|
ti.resize((size+2)*(size+2), -1);
|
||||||
|
|
||||||
|
for ( int y = 0; y < size+2; ++y ){
|
||||||
|
for ( int x = 0; x < size+2; ++x ){
|
||||||
|
ti[(y)*(size+2)+(x)] = tref.at((y+yoff)*(indexSize)+(x+xoff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
ti.resize((size)*(size));
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
};
|
@ -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<int, std::string>& getPalette() {
|
||||||
|
return mPalette;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
typedef std::map<int, std::string> Palette;
|
||||||
|
Palette mPalette;
|
||||||
|
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
/**
|
||||||
|
* @brief saves the palette
|
||||||
|
*/
|
||||||
|
template<class Archive>
|
||||||
|
inline void serialize(Archive& ar, const unsigned int version){
|
||||||
|
ar &mPalette;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_CLASS_TRACKING(TexturePalette, boost::serialization::track_never);
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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<class T>
|
||||||
|
struct Point2 {
|
||||||
|
T x, y; //held values.
|
||||||
|
|
||||||
|
inline Point2() {}
|
||||||
|
inline Point2(T ix, T iy) {
|
||||||
|
x = ix;
|
||||||
|
y = iy;
|
||||||
|
}
|
||||||
|
inline Point2(const Point2<T>& 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<T>& rhs) const{
|
||||||
|
return ( x < rhs.x || !( rhs.x < x) && y < rhs.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Point2 operator + (const Point2<T>& rhs) {
|
||||||
|
return Point2(x + rhs.x, y + rhs.y);
|
||||||
|
}
|
||||||
|
};
|
@ -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<long>(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<long> getPosition() const{ return mPosition; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the location of the quad. E.g. North West
|
||||||
|
*/
|
||||||
|
inline QuadLocation getLocation() const{ return mLocation; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief simply the length of one side of the current quad.
|
||||||
|
* @return the side length of the current quad
|
||||||
|
*
|
||||||
|
* As all quads are square, all sides are this length.
|
||||||
|
* Obviously this has to fit in sizeof(long)
|
||||||
|
*/
|
||||||
|
inline long getSideLength() const{ return mSideLength;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The depth is how many splits have taken place since the root node.
|
||||||
|
* @return the depth of the current quad
|
||||||
|
*
|
||||||
|
* The root node has a depth 0. As this is the case, all of its children will have
|
||||||
|
* a depth of one. Simply depth = parent depth + 1
|
||||||
|
*
|
||||||
|
* Set in the consturctor (Quad::Quad)
|
||||||
|
*/
|
||||||
|
inline int getDepth() const{return mDepth;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if their is a terrain mesh alocated
|
||||||
|
*/
|
||||||
|
inline bool hasMesh() const{ return 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<long> mPosition; ///the center of the mesh. this is a long so can be used as comparison. See Quad::getPosition
|
||||||
|
long mSideLength; ///the length in units of one side of the quad. See Quad::getSideLength
|
||||||
|
int mDepth; ///depth of the node. See Quad::getDepth for more info
|
||||||
|
|
||||||
|
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;
|
@ -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<float>& getHeightsRef() {
|
||||||
|
return mHeights;
|
||||||
|
}
|
||||||
|
inline float getVertex(int offset){
|
||||||
|
return getHeightsRef().at(offset);
|
||||||
|
}
|
||||||
|
inline std::vector<char>& getNormalsRef() {
|
||||||
|
return mNormals;
|
||||||
|
}
|
||||||
|
inline float getNormal(int offset){
|
||||||
|
return getNormalsRef().at(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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<float> mHeights;
|
||||||
|
std::vector<char> mNormals;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MWQuadData : public QuadData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::list<Ogre::ResourcePtr> ResourceList;
|
||||||
|
typedef std::list<Ogre::ResourcePtr>::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<int>& getTextureIndexRef()
|
||||||
|
{ return mTextureIndex; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
///Holds the resources used by the quad
|
||||||
|
ResourceList mResources;
|
||||||
|
|
||||||
|
std::vector<int> 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<class Archive>
|
||||||
|
inline void serialize(Archive& ar, const unsigned int version){
|
||||||
|
ar &mVertexSeperation;
|
||||||
|
ar &mHeights;
|
||||||
|
ar &mNormals;
|
||||||
|
ar &mParentTexture;
|
||||||
|
|
||||||
|
ar &mTextureIndex;
|
||||||
|
ar &mTexture;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_CLASS_TRACKING(MWQuadData, boost::serialization::track_never);
|
@ -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(x<vw);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return mQuadData->getVertex((mYOffset + y)*mQuadData->getVertexWidth()+(mXOffset+x));
|
||||||
|
}
|
||||||
|
|
||||||
|
float getNormal(int x, int y, int z)
|
||||||
|
{
|
||||||
|
assert(z>=0&&z<3);
|
||||||
|
return mQuadData->getNormal(((mYOffset + y)*mQuadData->getVertexWidth()+(mXOffset+x))*3+z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
};
|
@ -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 <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <list>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/shared_ptr.hpp>
|
||||||
|
#include <boost/archive/binary_iarchive.hpp>
|
||||||
|
#include <boost/archive/binary_oarchive.hpp>
|
||||||
|
#include <boost/serialization/string.hpp>
|
||||||
|
#include <boost/serialization/vector.hpp>
|
||||||
|
#include <boost/serialization/map.hpp>
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
//----------------------------------------------
|
@ -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;
|
||||||
|
};
|
@ -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<float*>(mMainBuffer->lock(HardwareBuffer::HBL_DISCARD));
|
||||||
|
for ( int y = start; y < end; y++ ) {
|
||||||
|
for ( int x = start; x < end; x++ ) {
|
||||||
|
|
||||||
|
//the skirts
|
||||||
|
if ( y < 0 || y > (mWidth-1) || x < 0 || x > (mWidth-1) ) {
|
||||||
|
|
||||||
|
if ( x < 0 ) *verts++ = 0;
|
||||||
|
else if ( x > (mWidth-1) ) *verts++ = (mWidth-1)*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+x<mWidth*mWidth);
|
||||||
|
|
||||||
|
//verts
|
||||||
|
*verts++ = x*mSegment->getVertexSeperation();
|
||||||
|
*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<float*>(mDeltaBuffer->lock(HardwareBuffer::HBL_DISCARD));
|
||||||
|
memset(pDeltas, 0, (vw)*(vw) * sizeof(float));
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool flag=false;
|
||||||
|
for ( size_t y = 0; y < mWidth-step; y += step ) {
|
||||||
|
for ( size_t x = 0; x < mWidth-step; x += step ) {
|
||||||
|
//create the diffrence between the full vertex if the vertex wasn't their
|
||||||
|
|
||||||
|
float bottom_left = getVertexHeight(x,y);
|
||||||
|
float bottom_right = getVertexHeight(x+step,y);
|
||||||
|
|
||||||
|
float top_left = getVertexHeight(x,y+step);
|
||||||
|
float top_right = getVertexHeight(x+step,y+step);
|
||||||
|
|
||||||
|
//const int vw = mWidth+2;
|
||||||
|
SET_DELTA_AT(x, y+1, (bottom_left+top_left)/2 - getVertexHeight(x, y+1)) //left
|
||||||
|
SET_DELTA_AT(x+2, y+1, (bottom_right+top_right)/2 - getVertexHeight(x+2, y+1)) //right
|
||||||
|
|
||||||
|
SET_DELTA_AT(x+1, y+2, (top_right+top_left)/2 - getVertexHeight(x+1, y+2)) //top
|
||||||
|
SET_DELTA_AT(x+1, y, (bottom_right+bottom_left)/2 - getVertexHeight(x+1, y)) //bottom
|
||||||
|
|
||||||
|
//this might not be correct
|
||||||
|
if ( !flag )
|
||||||
|
SET_DELTA_AT(x+1, y+1, (bottom_left+top_right)/2 - getVertexHeight(x+1, y+1)) //center
|
||||||
|
else
|
||||||
|
SET_DELTA_AT(x+1, y+1, (bottom_right+top_left)/2 - getVertexHeight(x+1, y+1)) //center
|
||||||
|
|
||||||
|
flag = !flag;
|
||||||
|
}
|
||||||
|
flag = !flag; //flip tries for next row
|
||||||
|
}
|
||||||
|
|
||||||
|
mDeltaBuffer->unlock();
|
||||||
|
mVertexes->vertexBufferBinding->setBinding(DELTA_BINDING,mDeltaBuffer);
|
||||||
|
|
||||||
|
}
|
||||||
|
#undef SET_DELTA_AT
|
||||||
|
|
||||||
|
//----------------------------------------------
|
||||||
|
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<unsigned short*>(mIndices->indexBuffer->lock(0,
|
||||||
|
mIndices->indexBuffer->getSizeInBytes(),
|
||||||
|
HardwareBuffer::HBL_DISCARD));
|
||||||
|
|
||||||
|
bool flag = false;
|
||||||
|
Ogre::uint indNum = 0;
|
||||||
|
for ( Ogre::uint y = 0; y < (vw); y+=1 ) {
|
||||||
|
for ( Ogre::uint x = 0; x < (vw); x+=1 ) {
|
||||||
|
|
||||||
|
const int line1 = y * (vw+1) + x;
|
||||||
|
const int line2 = (y + 1) * (vw+1) + x;
|
||||||
|
|
||||||
|
if ( flag ) {
|
||||||
|
*indices++ = line1;
|
||||||
|
*indices++ = line2;
|
||||||
|
*indices++ = line1 + 1;
|
||||||
|
|
||||||
|
*indices++ = line1 + 1;
|
||||||
|
*indices++ = line2;
|
||||||
|
*indices++ = line2 + 1;
|
||||||
|
} else {
|
||||||
|
*indices++ = line1;
|
||||||
|
*indices++ = line2;
|
||||||
|
*indices++ = line2 + 1;
|
||||||
|
|
||||||
|
*indices++ = line1;
|
||||||
|
*indices++ = line2 + 1;
|
||||||
|
*indices++ = line1 + 1;
|
||||||
|
}
|
||||||
|
flag = !flag; //flip tris for next time
|
||||||
|
|
||||||
|
indNum+=6;
|
||||||
|
}
|
||||||
|
flag = !flag; //flip tries for next row
|
||||||
|
}
|
||||||
|
assert(indNum==indexCount);
|
||||||
|
mIndices->indexBuffer->unlock();
|
||||||
|
//return mIndices;
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
@ -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();
|
Loading…
Reference in New Issue