mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 08:26:39 +00:00 
			
		
		
		
	git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@138 ea6a568a-9f4f-0410-981a-c910a81bb256
		
			
				
	
	
		
			413 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			413 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);
 | 
						|
  }
 | 
						|
}
 |