You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
12 KiB
C++
414 lines
12 KiB
C++
/*
|
|
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/ .
|
|
|
|
*/
|
|
|
|
const int CELL_WIDTH = 8192;
|
|
|
|
SceneNode *g_rootTerrainNode;
|
|
int g_alphaSize;
|
|
|
|
struct MeshInfo;
|
|
struct AlphaInfo;
|
|
|
|
// D functions
|
|
extern "C"
|
|
{
|
|
void d_terr_superman();
|
|
void d_terr_terrainUpdate();
|
|
|
|
char *d_terr_getTexName(int32_t);
|
|
|
|
void d_terr_fillVertexBuffer(const MeshInfo*,float*,uint64_t);
|
|
void d_terr_fillIndexBuffer(const MeshInfo*,uint16_t*,uint64_t);
|
|
AlphaInfo *d_terr_getAlphaInfo(const MeshInfo*,int32_t);
|
|
|
|
void d_terr_fillAlphaBuffer(const AlphaInfo*,uint8_t*,uint64_t);
|
|
|
|
int32_t d_terr_getAlphaSize();
|
|
}
|
|
|
|
// Info about a submesh. This is a clone of the struct defined in
|
|
// archive.d. TODO: Make sure the D and C++ structs are of the same
|
|
// size and alignment.
|
|
struct MeshInfo
|
|
{
|
|
// Bounding box info
|
|
float minHeight, maxHeight;
|
|
float worldWidth;
|
|
|
|
// Vertex and index numbers
|
|
int32_t vertRows, vertCols;
|
|
|
|
// Height offset to apply to all vertices
|
|
float heightOffset;
|
|
|
|
// Size and offset of the vertex buffer
|
|
int64_t vertBufSize, vertBufOffset;
|
|
|
|
// Texture name. Index to the string table.
|
|
int32_t texName;
|
|
|
|
// Number and offset of AlphaInfo blocks
|
|
int32_t alphaNum;
|
|
uint64_t alphaOffset;
|
|
|
|
inline void fillVertexBuffer(float *buffer, uint64_t size) const
|
|
{
|
|
d_terr_fillVertexBuffer(this, buffer, size);
|
|
}
|
|
|
|
inline void fillIndexBuffer(uint16_t *buffer, uint64_t size) const
|
|
{
|
|
d_terr_fillIndexBuffer(this, buffer, size);
|
|
}
|
|
|
|
inline char* getTexName() const
|
|
{
|
|
return d_terr_getTexName(texName);
|
|
}
|
|
|
|
inline AlphaInfo *getAlphaInfo(int tnum) const
|
|
{
|
|
return d_terr_getAlphaInfo(this, tnum);
|
|
}
|
|
};
|
|
|
|
// Info about an alpha map belonging to a mesh
|
|
struct AlphaInfo
|
|
{
|
|
// Position of the actual image data
|
|
uint64_t bufSize, bufOffset;
|
|
|
|
// The texture name for this layer. The actual string is stored in
|
|
// the archive's string buffer.
|
|
int32_t texName;
|
|
int32_t alphaName;
|
|
|
|
inline char* getTexName() const
|
|
{
|
|
return d_terr_getTexName(texName);
|
|
}
|
|
|
|
inline char* getAlphaName() const
|
|
{
|
|
return d_terr_getTexName(alphaName);
|
|
}
|
|
|
|
inline void fillAlphaBuffer(uint8_t *buffer, uint64_t size) const
|
|
{
|
|
return d_terr_fillAlphaBuffer(this, buffer, size);
|
|
}
|
|
};
|
|
|
|
#include "cpp_baseland.cpp"
|
|
#include "cpp_mesh.cpp"
|
|
|
|
BaseLand *g_baseLand;
|
|
|
|
class TerrainFrameListener : public FrameListener
|
|
{
|
|
protected:
|
|
bool frameEnded(const FrameEvent& evt)
|
|
{
|
|
TRACE("Terrain frame");
|
|
d_terr_terrainUpdate();
|
|
if(g_baseLand)
|
|
g_baseLand->update();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// Renders a material into a texture
|
|
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);
|
|
|
|
Ogre::CompositionTargetPass* ctp = cp->createTechnique()->getOutputTargetPass();
|
|
Ogre::CompositionPass* cpass = ctp->createPass();
|
|
cpass->setType(Ogre::CompositionPass::PT_RENDERQUAD);
|
|
cpass->setMaterial(mp);
|
|
|
|
// Create the destination texture
|
|
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().
|
|
createManual(name + "_T",
|
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
|
Ogre::TEX_TYPE_2D,
|
|
texSize,
|
|
texSize,
|
|
0,
|
|
tt,
|
|
Ogre::TU_RENDERTARGET
|
|
);
|
|
|
|
Ogre::RenderTexture* renderTexture = texture->getBuffer()->getRenderTarget();
|
|
Ogre::Viewport* vp = renderTexture->addViewport(mCamera);
|
|
|
|
Ogre::CompositorManager::getSingleton().addCompositor(vp, "Rtt_Comp");
|
|
Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp,"Rtt_Comp", true);
|
|
|
|
renderTexture->update();
|
|
|
|
// Call the OGRE renderer.
|
|
Ogre::Root::getSingleton().renderOneFrame();
|
|
|
|
Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp");
|
|
Ogre::CompositorManager::getSingleton().remove(cp->getHandle());
|
|
|
|
renderTexture->removeAllViewports();
|
|
|
|
return texture;
|
|
}
|
|
|
|
// These are used between some functions below. Kinda messy. Since
|
|
// these are GLOBAL instances, they are terminated at program
|
|
// exit. However, OGRE itself is terminated before that, so we have to
|
|
// make sure we have no 'active' shared pointers after OGRE is
|
|
// finished (otherwise we get a segfault at exit.)
|
|
std::list<Ogre::ResourcePtr> createdResources;
|
|
Ogre::HardwarePixelBuffer *pixelBuffer;
|
|
MaterialPtr mat;
|
|
|
|
// Functions called from D
|
|
extern "C"
|
|
{
|
|
SceneNode* terr_createChildNode(float x, float y,
|
|
SceneNode *parent)
|
|
{
|
|
Ogre::Vector3 pos(x,y,0);
|
|
if(parent == NULL)
|
|
parent = g_rootTerrainNode;
|
|
|
|
assert(parent);
|
|
return parent->createChildSceneNode(pos);
|
|
}
|
|
|
|
void terr_destroyNode(SceneNode *node)
|
|
{
|
|
node->removeAndDestroyAllChildren();
|
|
mSceneMgr->destroySceneNode(node);
|
|
}
|
|
|
|
// TODO: We could make allocation a little more refined than new and
|
|
// delete. But that's true for everything here. A freelist based
|
|
// approach is best in most of these cases, as we have continuous
|
|
// allocation/deallocation of fixed-size structs.
|
|
Ogre::AxisAlignedBox *terr_makeBounds(float minHeight, float maxHeight,
|
|
float width, SceneNode* node)
|
|
{
|
|
TRACE("terr_makeBounds");
|
|
AxisAlignedBox *mBounds = new AxisAlignedBox;
|
|
|
|
assert(maxHeight >= minHeight);
|
|
|
|
mBounds->setExtents(0,0,minHeight,
|
|
width,width,maxHeight);
|
|
|
|
// Transform the box to world coordinates, so it can be compared
|
|
// with the camera later.
|
|
mBounds->transformAffine(node->_getFullTransform());
|
|
|
|
return mBounds;
|
|
}
|
|
|
|
void terr_killBounds(AxisAlignedBox *bounds)
|
|
{
|
|
TRACE("terr_killBounds");
|
|
delete bounds;
|
|
}
|
|
|
|
float terr_getSqCamDist(AxisAlignedBox *mBounds)
|
|
{
|
|
TRACE("terr_getSqCamDist");
|
|
Ogre::Vector3 cpos = mCamera->getDerivedPosition();
|
|
Ogre::Vector3 diff(0, 0, 0);
|
|
diff.makeFloor(cpos - mBounds->getMinimum() );
|
|
diff.makeCeil(cpos - mBounds->getMaximum() );
|
|
return diff.squaredLength();
|
|
}
|
|
|
|
TerrainMesh *terr_makeMesh(SceneNode *parent,
|
|
MeshInfo *info,
|
|
int level, float scale)
|
|
{
|
|
return new TerrainMesh(parent, *info, level, scale);
|
|
}
|
|
|
|
void terr_killMesh(TerrainMesh *mesh)
|
|
{
|
|
TRACE("terr_killMesh");
|
|
delete mesh;
|
|
}
|
|
|
|
// Set up the rendering system
|
|
void terr_setupRendering()
|
|
{
|
|
TRACE("terr_setupRendering()");
|
|
// Make sure the C++ sizes match the D sizes, since the structs
|
|
// are shared between the two.
|
|
assert(sizeof(MeshInfo) == 14*4);
|
|
assert(sizeof(AlphaInfo) == 6*4);
|
|
|
|
// Add the terrain directory as a resource location. TODO: Get the
|
|
// name from D.
|
|
ResourceGroupManager::getSingleton().
|
|
addResourceLocation("cache/terrain/", "FileSystem", "General");
|
|
|
|
// Enter superman mode
|
|
mCamera->setFarClipDistance(40*CELL_WIDTH);
|
|
//ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
|
|
d_terr_superman();
|
|
|
|
// Create a root scene node first. The 'root' node is rotated to
|
|
// match the MW coordinate system
|
|
g_rootTerrainNode = mwRoot->createChildSceneNode("TERRAIN_ROOT");
|
|
|
|
// Add the base land. This is the ground beneath the actual
|
|
// terrain mesh that makes the terrain look infinite.
|
|
//g_baseLand = new BaseLand();
|
|
|
|
g_alphaSize = d_terr_getAlphaSize();
|
|
|
|
// Add the frame listener
|
|
mRoot->addFrameListener(new TerrainFrameListener);
|
|
}
|
|
|
|
// The next four functions are called in the function genLevel2Map()
|
|
// only. This is very top-down-programming-ish and a bit messy, but
|
|
// that's what I get for mixing C++ and D like this.
|
|
void terr_makeLandMaterial(const char* name, float scale)
|
|
{
|
|
// Get a new material
|
|
mat = Ogre::MaterialManager::getSingleton().
|
|
create(name,
|
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
|
|
|
// Put the default texture in the bottom 'layer', so that we don't
|
|
// end up seeing through the landscape.
|
|
Ogre::Pass* np = mat->getTechnique(0)->getPass(0);
|
|
np->setLightingEnabled(false);
|
|
np->createTextureUnitState("_land_default.dds")
|
|
->setTextureScale(scale,scale);
|
|
}
|
|
|
|
uint8_t *terr_makeAlphaLayer(const char* name, int32_t width)
|
|
{
|
|
// Create alpha map for this texture.
|
|
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
|
|
createManual(name,
|
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
|
Ogre::TEX_TYPE_2D,
|
|
width, width,
|
|
1,0, // depth, mipmaps
|
|
Ogre::PF_A8, // One-channel alpha
|
|
Ogre::TU_STATIC_WRITE_ONLY);
|
|
|
|
createdResources.push_back(texPtr);
|
|
|
|
pixelBuffer = texPtr->getBuffer().get();
|
|
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
|
|
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
|
|
|
return static_cast<Ogre::uint8*>(pixelBox.data);
|
|
}
|
|
|
|
void terr_closeAlpha(const char *alphaName,
|
|
const char *texName, float scale)
|
|
{
|
|
// Close the alpha pixel buffer opened in the previous function
|
|
pixelBuffer->unlock();
|
|
|
|
// Create a pass containing the alpha map
|
|
Pass *np = mat->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);
|
|
|
|
// Set various blending options
|
|
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
|
|
Ogre::LBS_TEXTURE,
|
|
Ogre::LBS_TEXTURE);
|
|
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
|
Ogre::LBS_TEXTURE,
|
|
Ogre::LBS_TEXTURE);
|
|
tus->setIsAlpha(true);
|
|
|
|
// Add the terrain texture to the pass and scale it.
|
|
tus = np->createTextureUnitState(texName);
|
|
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
|
Ogre::LBS_TEXTURE,
|
|
Ogre::LBS_CURRENT);
|
|
tus->setTextureScale(scale, scale);
|
|
}
|
|
|
|
// Clean up after the above functions, render the material to
|
|
// texture and save the data in outdata and in the file outname.
|
|
void terr_cleanupAlpha(const char *outname,
|
|
void *outData, int32_t toSize)
|
|
{
|
|
TexturePtr tex1 = getRenderedTexture(mat,outname,
|
|
toSize,Ogre::PF_R8G8B8);
|
|
|
|
// Blit the texture into the given memory buffer
|
|
PixelBox pb = PixelBox(toSize, toSize, 1, PF_R8G8B8);
|
|
pb.data = outData;
|
|
tex1->getBuffer()->blitToMemory(pb);
|
|
|
|
// Clean up
|
|
TextureManager::getSingleton().remove(tex1->getHandle());
|
|
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());
|
|
createdResources.clear();
|
|
|
|
MaterialManager::getSingleton().remove(mat->getHandle());
|
|
mat.setNull();
|
|
}
|
|
|
|
void terr_resize(void* srcPtr, void* dstPtr, int32_t fromW, int32_t toW)
|
|
{
|
|
// Create pixelboxes
|
|
PixelBox src = PixelBox(fromW, fromW, 1, PF_R8G8B8);
|
|
PixelBox dst = PixelBox(toW, toW, 1, PF_R8G8B8);
|
|
|
|
src.data = srcPtr;
|
|
dst.data = dstPtr;
|
|
|
|
// Resize the image. The nearest neighbour filter makes sure
|
|
// there is no blurring.
|
|
Image::scale(src, dst, Ogre::Image::FILTER_NEAREST);
|
|
}
|
|
|
|
void terr_saveImage(void *data, int32_t width, const char* name)
|
|
{
|
|
Image img;
|
|
img.loadDynamicImage((uchar*)data, width, width, PF_R8G8B8);
|
|
img.save(name);
|
|
}
|
|
}
|