diff --git a/monster/util/aa.d b/monster/util/aa.d index adefce0c0..11c2f2354 100644 --- a/monster/util/aa.d +++ b/monster/util/aa.d @@ -333,7 +333,7 @@ struct HashTable(Key, Value, Alloc = GCAlloc, Hash = DefHash, // Get a pointer to value of given key, or insert a new. Useful for // large value types when you want to avoid copying the value around // needlessly. Also useful if you want to do in-place - // editing. Returns true if a value was inserted. + // editing. Returns true if a new value was inserted. bool insertEdit(Key k, out Value *ptr) { Node *p; diff --git a/nif/property.d b/nif/property.d index cc4395c27..97e9072d2 100644 --- a/nif/property.d +++ b/nif/property.d @@ -55,7 +55,7 @@ class NiTexturingProperty : Property bool inUse; NiSourceTexture texture; - /* Clamp mode (i don't understand this) + /* Clamp mode 0 - clampS clampT 1 - clampS wrapT 2 - wrapS clampT @@ -89,8 +89,11 @@ class NiTexturingProperty : Property filter = nifFile.getIntIs(0,1,2); set = nifFile.getInt; - ps2L = nifFile.getShortIs(0); - ps2K = nifFile.getShortIs(-75,-2); + // The combination 1222, 322, 212 was used in a bump map + // NIF. Might just be bogus numbers, I should probably allow all + // values here since we ignore them anyway. + ps2L = nifFile.getShortIs(0,1222); + ps2K = nifFile.getShortIs(-75,-2,322); debug(verbose) { @@ -101,7 +104,7 @@ class NiTexturingProperty : Property writefln(" ps2K ", ps2K); } - unknown2 = nifFile.wgetShortIs(0,257); + unknown2 = nifFile.wgetShortIs(0,257,212); } } diff --git a/openmw.d b/openmw.d index 7a5c17292..10bb3ab14 100644 --- a/openmw.d +++ b/openmw.d @@ -54,6 +54,20 @@ import input.events; import terrain.terrain; +// Set up exit handler +alias void function() c_func; +extern(C) int atexit(c_func); + +bool cleanExit = false; + +void exitHandler() +{ + // If we exit uncleanly, print the function stack. + if(!cleanExit) + writefln(dbg.getTrace()); +} + + //* import std.gc; import gcstats; @@ -224,6 +238,10 @@ Try specifying another cell name on the command line, or edit openmw.ini."); // Set the name for the GUI (temporary hack) gui_setCellName(cd.inCell.id.ptr); + // Set up the exit handler + atexit(&exitHandler); + scope(exit) cleanExit = true; + if(render) { // Warm up OGRE diff --git a/terrain/archive.d b/terrain/archive.d index 4c5037761..3911080f7 100644 --- a/terrain/archive.d +++ b/terrain/archive.d @@ -20,12 +20,17 @@ */ -// This should also be part of the generic cache system. +module terrain.archive; + +const float TEX_SCALE = 1.0/16; + +// This should be part of the generic cache system. const int CACHE_MAGIC = 0x345AF815; import std.mmfile; -import std.stream; import std.string; +import std.stdio; +import terrain.myfile; version(Windows) static int pageSize = 64*1024; @@ -39,19 +44,20 @@ extern(C) { return g_archive.getString(index).ptr; } // Fill various hardware buffers from cache - void d_terr_fillVertexBuffer(MeshInfo *mi, float *buffer) - { mi.fillVertexBuffer(buffer); } + void d_terr_fillVertexBuffer(MeshInfo *mi, float *buffer, ulong size) + { mi.fillVertexBuffer(buffer[0..size]); } - void d_terr_fillIndexBuffer(MeshInfo *mi, ushort *buffer) - { mi.fillIndexBuffer(buffer); } + void d_terr_fillIndexBuffer(MeshInfo *mi, ushort *buffer, ulong size) + { mi.fillIndexBuffer(buffer[0..size]); } - void d_terr_fillAlphaBuffer(AlphaInfo *mi, ubyte *buffer) - { mi.fillAlphaBuffer(buffer); } + void d_terr_fillAlphaBuffer(AlphaInfo *mi, ubyte *buffer, ulong size) + { mi.fillAlphaBuffer(buffer[0..size]); } // Get a given alpha map struct belonging to a mesh AlphaInfo *d_terr_getAlphaInfo(MeshInfo *mi, int index) { return mi.getAlphaInfo(index); } + int d_terr_getAlphaSize() { return g_archive.alphaSize; } } // Info about the entire quad. TODO: Some of this (such as the texture @@ -69,9 +75,6 @@ struct QuadInfo float worldWidth; float boundingRadius; - // Texture scale for this quad - float texScale; - // True if we should make the given child bool hasChild[4]; @@ -92,13 +95,14 @@ struct AlphaInfo // The texture name for this layer. The actual string is stored in // the archive's string buffer. - int texName; - int alphaName; + int texName = -1; + int alphaName = -1; // Fill the alpha texture buffer - void fillAlphaBuffer(ubyte *abuf) + void fillAlphaBuffer(ubyte abuf[]) { - g_archive.copy(abuf, bufOffset, bufSize); + assert(abuf.length == bufSize); + g_archive.copy(abuf.ptr, bufOffset, bufSize); } } static assert(AlphaInfo.sizeof == 6*4); @@ -113,10 +117,6 @@ struct MeshInfo // Vertex and index numbers int vertRows, vertCols; - int indexCount; - - // Scene node position (relative to the parent node) - float x, y; // Height offset to apply to all vertices float heightOffset; @@ -129,21 +129,20 @@ struct MeshInfo ulong alphaOffset; // Texture name. Index to the string table. - int texName; + int texName = -1; // Fill the given vertex buffer - void fillVertexBuffer(float *vbuf) + void fillVertexBuffer(float vdest[]) { - //g_archive.copy(vbuf, vertBufOffset, vertBufSize); - // The height map and normals from the archive - char *hmap = cast(char*)g_archive.getRelSlice(vertBufOffset, vertBufSize).ptr; - - int level = getLevel(); - + byte *hmap = cast(byte*)g_archive.getRelSlice(vertBufOffset, vertBufSize).ptr; // The generic part, containing the x,y coordinates and the uv // maps. - float *gmap = g_archive.getVertexBuffer(level).ptr; + float *gmap = g_archive.getVertexBuffer(getLevel()).ptr; + + // Destination pointer + float *vbuf = vdest.ptr; + assert(vdest.length == vertRows*vertCols*8); // Calculate the factor to multiply each height value with. The // heights are very limited in range as they are stored in a @@ -151,7 +150,7 @@ struct MeshInfo // double this for each successive level since we're splicing // several vertices together and need to allow larger differences // for each vertex. The formula is 8*2^(level-1). - float scale = 4.0 * (1<= 1); quadMap[l][x][y] = index; + assert(index == quadMap[l][x][y]); // Store the root quad if(l == head.rootLevel) @@ -303,24 +306,24 @@ struct TerrainArchive // Make sure the root was set assert(rootQuad !is null); - // Next read the string table + // Next read the string table. First read the main string buffer. stringBuf = new char[head.stringSize]; - strings.length = head.stringNum; - - // First read the main string buffer - ifile.readExact(stringBuf.ptr, head.stringSize); + ifile.fillArray(stringBuf); // Then read the string offsets int[] offsets = new int[head.stringNum]; - ifile.readExact(offsets.ptr, offsets.length*int.sizeof); + ifile.fillArray(offsets); // Set up the string table char *strptr = stringBuf.ptr; + strings.length = head.stringNum; foreach(int i, ref str; strings) { // toString(char*) returns the string up to the zero // terminator byte str = toString(strptr + offsets[i]); + assert(str.ptr + str.length <= + stringBuf.ptr + stringBuf.length); } delete offsets; @@ -328,23 +331,16 @@ struct TerrainArchive int bufNum = head.rootLevel; assert(bufNum == 7); vertBufData.length = bufNum; - indexBufData.length = bufNum; - // Fill the buffers. Start at level 1. + // Fill the vertex buffers. Start at level 1. for(int i=1;i=1 && level level); vertBuf[level] = buf; } - // Add a common vertex buffer for a given level - void addIndexBuffer(int level, void[] buf) + // Add a common index buffer + void setIndexBuffer(ushort[] buf) { - assert(indexBuf.length > level); - indexBuf[level] = buf; + indexBuf = buf; } // Write a finished quad to the archive file. All the offsets and @@ -183,15 +174,16 @@ struct CacheWriter // the additional data in the Holder structs. void writeQuad(ref QuadHolder qh) { - // Make outbuffer a simple struct that uses a region and keeps - // track of all the slices we allocate. - OutBuffer buf; + scope auto _trc = new MTrace("writeQuad"); // Write the MeshInfo's first int meshNum = qh.meshes.length; MeshInfo meshes[] = buf.write!(MeshInfo)(meshNum); + float minh = float.infinity; + float maxh = -float.infinity; + // Then write the mesh data in approximately the order it's read for(int i=0; i 0); + assert(info.minHeight <= info.maxHeight); mBounds.setExtents(0,0,info.minHeight, - // was (mWidth-1) * vertexSeparation info.worldWidth, info.worldWidth, info.maxHeight); - mCenter = mBounds.getCenter(); mBoundingRadius = mBounds.getHalfSize().length(); @@ -56,16 +60,16 @@ public: // Fill the buffer float* verts = static_cast (mMainBuffer->lock(HardwareBuffer::HBL_DISCARD)); - info.fillVertexBuffer(verts); + info.fillVertexBuffer(verts,8*mVertices->vertexCount); mMainBuffer->unlock(); // Create the index data holder mIndices = new IndexData(); - mIndices->indexCount = info.indexCount; + mIndices->indexCount = 64*64*6; // TODO: Shouldn't be hard-coded mIndices->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer ( HardwareIndexBuffer::IT_16BIT, - info.indexCount, + mIndices->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false); @@ -74,17 +78,16 @@ public: (mIndices->indexBuffer->lock (0, mIndices->indexBuffer->getSizeInBytes(), HardwareBuffer::HBL_DISCARD)); - info.fillIndexBuffer(indices); + info.fillIndexBuffer(indices,mIndices->indexCount); mIndices->indexBuffer->unlock(); // Finally, create the material const std::string texName = info.getTexName(); - // TODO: A better thing to do here is to keep the material loaded - // and retrieve it if it exists. - assert(!MaterialManager::getSingleton().resourceExists(texName)); - mMaterial = MaterialManager::getSingleton().create - (texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + // Create or retrieve the material + mMaterial = MaterialManager::getSingleton().createOrRetrieve + (texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME).first; + Pass* pass = mMaterial->getTechnique(0)->getPass(0); pass->setLightingEnabled(false); @@ -119,29 +122,32 @@ public: // Name of the texture std::string tname = alpha.getTexName(); - // TODO: Need to store the result and either delete it in - // the destructor or fetch it again the next time we run. - Ogre::TexturePtr texPtr = Ogre::TextureManager:: - getSingleton().createManual - (alphaName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - g_alphaSize,g_alphaSize, - 1,0, // depth, mipmaps - Ogre::PF_A8, // One-channel alpha - Ogre::TU_STATIC_WRITE_ONLY); - - // Get the pointer - Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer(); - pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD); - const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock(); - Ogre::uint8* pDest = static_cast(pixelBox.data); - - // Copy alpha data from file - alpha.fillAlphaBuffer(pDest); - - // Finish everything up with a lot of Ogre-code - pixelBuffer->unlock(); + // Create the alpha texture if it doesn't exist + if(!TextureManager::getSingleton().resourceExists(alphaName)) + { + TexturePtr texPtr = Ogre::TextureManager:: + getSingleton().createManual + (alphaName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + g_alphaSize,g_alphaSize, + 1,0, // depth, mipmaps + Ogre::PF_A8, // One-channel alpha + Ogre::TU_STATIC_WRITE_ONLY); + + // Get the pointer + Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer(); + pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD); + const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock(); + Ogre::uint8* pDest = static_cast(pixelBox.data); + + // Copy alpha data from file + alpha.fillAlphaBuffer(pDest,g_alphaSize*g_alphaSize); + + // Close the buffer + pixelBuffer->unlock(); + } + pass = mMaterial->getTechnique(0)->createPass(); pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); pass->setLightingEnabled(false); @@ -169,7 +175,7 @@ public: } // Finally, set up the scene node. - mNode = parent->createChildSceneNode(Vector3(info.x, info.y, 0.0)); + mNode = parent->createChildSceneNode(); mNode->attachObject(this); } @@ -179,8 +185,8 @@ public: mNode->detachAllObjects(); mNode->getCreator()->destroySceneNode(mNode); - // TODO: This used to crash. See what happens now. - delete mVertices; + // TODO: This still crashes on level1 meshes. Find out why! + if(mLevel!=1)delete mVertices; delete mIndices; } @@ -237,9 +243,11 @@ public: mLightListDirty = true; queue->addRenderable(this, mRenderQueueID); } - const Ogre::AxisAlignedBox& getBoundingBox( void ) const { + const Ogre::AxisAlignedBox& getBoundingBox( void ) const + { return mBounds; - }; + } + Ogre::Real getBoundingRadius(void) const { return mBoundingRadius; } @@ -251,6 +259,8 @@ public: private: + int mLevel; + Ogre::SceneNode* mNode; Ogre::MaterialPtr mMaterial; diff --git a/terrain/cpp_terrain.cpp b/terrain/cpp_terrain.cpp index ef29419f2..65b2243a2 100644 --- a/terrain/cpp_terrain.cpp +++ b/terrain/cpp_terrain.cpp @@ -36,11 +36,13 @@ extern "C" char *d_terr_getTexName(int32_t); - void d_terr_fillVertexBuffer(const MeshInfo*,float*); - void d_terr_fillIndexBuffer(const MeshInfo*,uint16_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*); + 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 @@ -54,10 +56,6 @@ struct MeshInfo // Vertex and index numbers int32_t vertRows, vertCols; - int32_t indexCount; - - // Scene node position (relative to the parent node) - float x, y; // Height offset to apply to all vertices float heightOffset; @@ -72,14 +70,14 @@ struct MeshInfo // Texture name. Index to the string table. int32_t texName; - inline void fillVertexBuffer(float *buffer) const + inline void fillVertexBuffer(float *buffer, uint64_t size) const { - d_terr_fillVertexBuffer(this, buffer); + d_terr_fillVertexBuffer(this, buffer, size); } - inline void fillIndexBuffer(uint16_t *buffer) const + inline void fillIndexBuffer(uint16_t *buffer, uint64_t size) const { - d_terr_fillIndexBuffer(this, buffer); + d_terr_fillIndexBuffer(this, buffer, size); } inline char* getTexName() const @@ -114,9 +112,9 @@ struct AlphaInfo return d_terr_getTexName(alphaName); } - inline void fillAlphaBuffer(uint8_t *buffer) const + inline void fillAlphaBuffer(uint8_t *buffer, uint64_t size) const { - return d_terr_fillAlphaBuffer(this, buffer); + return d_terr_fillAlphaBuffer(this, buffer, size); } }; @@ -130,12 +128,67 @@ class TerrainFrameListener : public FrameListener protected: bool frameEnded(const FrameEvent& evt) { + TRACE("Terrain frame"); d_terr_terrainUpdate(); 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 createdResources; +Ogre::HardwarePixelBuffer *pixelBuffer; +MaterialPtr mat; + // Functions called from D extern "C" { @@ -163,8 +216,11 @@ extern "C" 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); @@ -177,11 +233,13 @@ extern "C" 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() ); @@ -193,19 +251,22 @@ extern "C" MeshInfo *info, int level, float scale) { - return new TerrainMesh(parent, *info, level, scale); } void terr_killMesh(TerrainMesh *mesh) - { delete 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 - // will be shared between the two. - assert(sizeof(MeshInfo) == 17*4); + // 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 @@ -226,7 +287,126 @@ extern "C" // 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(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::const_iterator iend = createdResources.end(); + for ( std::list::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); + } } diff --git a/terrain/esmland.d b/terrain/esmland.d index 816e8ba3c..1ea169ade 100644 --- a/terrain/esmland.d +++ b/terrain/esmland.d @@ -5,6 +5,8 @@ import esm.loadcell; import util.regions; import esm.filereader; +import std.stdio; + const int LAND_NUM_VERTS = 65*65; MWLand mwland; @@ -49,23 +51,27 @@ struct MWLand // Texture data for one cell struct LTEXData { - // TODO: Store the source file here too, so we can get the list - // from the right file. The source file is the same as the one we - // load the landscape from in loadCell(). + // Global list of land textures from the source ES file + LandTextureList.TextureList source; + + // Texture indices for this cell VTEX vtex; // Get the texture x2,y2 from the sub map x1,x2 char[] getTexture(int x1, int y1, int x2, int y2) { // Get the texture index relative to the current esm/esp file - short texID = vtex[y1][x1][y2][x2]; + short texID = vtex[y1][x1][y2][x2] - 1; - // Hack, will only work for Morrowind.esm. Fix this later. - auto tl = landTextures.files["Morrowind.esm"]; + if(texID == -1) + return null; - // Return the 'new' texture name. This name has automatically - // been converted to .dds if the .tga file was not found. - return tl[texID].getNewName(); + // Return the 'new' texture name. This name was automatically + // been converted to .dds at load time if the .tga file was not + // found. + assert(source !is null); + assert(texID >= 0 && texID < source.length); + return source[texID].getNewName(); } // Get a texture from the 16x16 grid in one cell @@ -127,6 +133,9 @@ struct MWLand // skip to the right part of the ESM file. auto cont = cells.getExt(x,y).land.context; + // Get the land texture list from the file + currentLtex.source = landTextures.files[cont.filename]; + // We should use an existing region later, or at least delete this // once we're done with the gen process. if(reg is null) @@ -135,7 +144,7 @@ struct MWLand // Open the ESM at this cell esFile.restoreContext(cont, reg); - // Store the data + // Store the cell-specific data esFile.readHNExact(currentLand.normals.ptr, currentLand.normals.length, "VNML"); esFile.readHNExact(¤tLand.vhgt, VHGT.sizeof, "VHGT"); diff --git a/terrain/generator.d b/terrain/generator.d index 3c0c955e2..a795b4363 100644 --- a/terrain/generator.d +++ b/terrain/generator.d @@ -27,12 +27,19 @@ module terrain.generator; import std.stdio; import std.string; +import std.c.string; + import terrain.cachewriter; +import terrain.archive; import terrain.esmland; import terrain.terrain; +import terrain.bindings; + import util.cachefile; -const float TEX_SCALE = 1.0/16; +import monster.util.aa; +import monster.util.string; +import monster.vm.dbg; int mCount; @@ -47,7 +54,7 @@ void generate(char[] filename) { makePath(cacheDir); - //cache.openFile(filename); + cache.openFile(filename); // Find the maxiumum distance from (0,0) in any direction int max = mwland.getMaxCoord(); @@ -76,12 +83,9 @@ void generate(char[] filename) texSizes[2] = 256; texSizes[1] = 64; - writefln("Data generation not implemented yet"); - // Set some general parameters for the runtime cache.setParams(depth+1, texSizes[1]); - /* // Create some common data first writefln("Generating common data"); genDefaults(); @@ -89,16 +93,186 @@ void generate(char[] filename) writefln("Generating quad data"); GenLevelResult gen; - // Start at one level above the top, but don't generate a mesh for - // it + + // Start at one level above the top, but don't generate data for it genLevel(depth+1, -max, -max, gen, false); + writefln("Writing index file"); cache.finish(); writefln("Pregeneration done. Results written to ", filename); - */ } -/+ +struct GenLevelResult +{ + QuadHolder quad; + + bool hasMesh; + bool isAlpha; + + int width; + + ubyte[] data; + + void allocImage(int _width) + { + assert(isEmpty()); + + width = _width; + data.length = width*width*3; + quad.meshes.length = 1; + + assert(!hasAlpha()); + } + + void allocAlphas(int _width, int texNum) + { + assert(isEmpty() || hasMesh); + + width = _width; + data.length = width*width*texNum; + isAlpha = true; + + // Set up the alpha images. TODO: We have to split these over + // several meshes, but for now pretend that we're using only + // one. + assert(quad.meshes.length == 1); + quad.meshes[0].alphas.length = texNum; + assert(alphaNum() == texNum); + + int s = width*width; + for(int i=0;i=0&&v>=0); assert(u<=1&&v<=1); - *vertPtr++ = u; - *vertPtr++ = v; + vertList[index++] = u; + vertList[index++] = v; } - assert(vertPtr-vertList.ptr == size); + assert(index == vertList.length); // Store the buffer cache.addVertexBuffer(lev,vertList); } @@ -178,7 +356,7 @@ void genIndexData() // Next up, triangle indices int size = 64*64*6; auto indList = new ushort[size]; - ushort *indPtr = indList.ptr; + int index = 0; bool flag = false; for ( int y = 0; y < 64; y++ ) @@ -190,45 +368,41 @@ void genIndexData() if ( flag ) { - *indPtr++ = line1; - *indPtr++ = line2; - *indPtr++ = line1 + 1; + indList[index++] = line1; + indList[index++] = line1 + 1; + indList[index++] = line2; - *indPtr++ = line1 + 1; - *indPtr++ = line2; - *indPtr++ = line2 + 1; + indList[index++] = line2; + indList[index++] = line1 + 1; + indList[index++] = line2 + 1; } else { - *indPtr++ = line1; - *indPtr++ = line2; - *indPtr++ = line2 + 1; + indList[index++] = line1; + indList[index++] = line2 + 1; + indList[index++] = line2; - *indPtr++ = line1; - *indPtr++ = line2 + 1; - *indPtr++ = line1 + 1; + indList[index++] = line1; + indList[index++] = line1 + 1; + indList[index++] = line2 + 1; } flag = !flag; //flip tris for next time } flag = !flag; //flip tries for next row } - assert(indPtr-indList.ptr==size); - - // The index buffers are the same for all levels - cache.addIndexBuffer(1,indList); - cache.addIndexBuffer(2,indList); - cache.addIndexBuffer(3,indList); - cache.addIndexBuffer(4,indList); - cache.addIndexBuffer(5,indList); - cache.addIndexBuffer(6,indList); + assert(index == indList.length); + + cache.setIndexBuffer(indList); } void genLevel(int level, int X, int Y, ref GenLevelResult result, bool makeData = true) { + scope auto _trc = new MTrace(format("genLevel(%s,%s,%s)",level,X,Y)); result.quad.info.cellX = X; result.quad.info.cellY = Y; result.quad.info.level = level; + result.quad.info.worldWidth = 8192 << (level-1); assert(result.isEmpty); @@ -270,6 +444,7 @@ void genLevel(int level, int X, int Y, ref GenLevelResult result, genLevel(level-1, X, Y+cells, sub[2]); // SW genLevel(level-1, X+cells, Y+cells, sub[3]); // SE + // Make sure we deallocate everything when the function exists scope(exit) { foreach(ref s; sub) @@ -314,9 +489,9 @@ void genLevel(int level, int X, int Y, ref GenLevelResult result, void genLevel1Meshes(ref GenLevelResult res) { // Constants - const int intervals = 64; - const int vertNum = intervals+1; - const int vertSep = 128; + int intervals = 64; + int vertNum = intervals+1; + int vertSep = 128; // Allocate the mesh buffer res.allocMesh(vertNum); @@ -328,20 +503,21 @@ void genLevel1Meshes(ref GenLevelResult res) MeshHolder *mh = &res.quad.meshes[0]; MeshInfo *mi = &mh.info; + // Set some basic data mi.worldWidth = vertSep*intervals; assert(mi.worldWidth == 8192); auto land = mwland.getLandData(cellX, cellY); - byte[] heightData = land.vhgt.heights; + byte[] heightData = land.vhgt.heightData; byte[] normals = land.normals; mi.heightOffset = land.vhgt.heightOffset; float max=-1000000.0; float min=1000000.0; - byte *vertPtr = mh.vertexBuffer.ptr; - assert(vertPtr !is null); + byte[] verts = mh.vertexBuffer; + int index = 0; // Loop over all the vertices in the mesh float rowheight = mi.heightOffset; @@ -356,7 +532,7 @@ void genLevel1Meshes(ref GenLevelResult res) byte data = heightData[offs]; // Write the height byte - *vertPtr++ = data; + verts[index++] = data; // Calculate the height here, even though we don't store // it. We use it to find the min and max values. @@ -368,8 +544,7 @@ void genLevel1Meshes(ref GenLevelResult res) // First value in each row adjusts the row height rowheight += data; } - // Adjust the accumulated height with the new data. The - // adjustment is a signed number. + // Adjust the accumulated height with the new data. height += data; // Calculate the min and max @@ -378,12 +553,11 @@ void genLevel1Meshes(ref GenLevelResult res) // Store the normals for(int k=0; k<3; k++) - *vertPtr++ = normals[offs*3+k]; + verts[index++] = normals[offs*3+k]; } // Make sure we wrote exactly the right amount of data - assert(vertPtr-mh.vertexBuffer.ptr == - mh.vertexBuffer.length - 1); + assert(index == verts.length-1); // Store the min/max values mi.minHeight = min * 8; @@ -393,10 +567,17 @@ void genLevel1Meshes(ref GenLevelResult res) // Generate the alpha splatting bitmap for one cell. void genCellAlpha(ref GenLevelResult res) { + scope auto _trc = new MTrace("genCellAlpha"); + int cellX = res.quad.info.cellX; int cellY = res.quad.info.cellY; assert(res.quad.info.level == 1); + // Set the texture name - it's used internally as the material name + // at runtime. + assert(res.quad.meshes.length == 1); + res.setTexName("AMAT_"~toString(cellX)~"_"~toString(cellY)); + // List of texture indices for this cell. A cell has 16x16 texture // squares. int ltex[16][16]; @@ -417,10 +598,15 @@ void genCellAlpha(ref GenLevelResult res) // Get the texture in a given cell char[] textureName = ltexData.getTexture(tx,ty); + // If the default texture is used, skip it. The background + // texture covers it (for now - we might change that later.) if(textureName == "") - textureName = "_land_default.dds"; - else - isDef = false; + { + ltex[ty][tx] = -1; + continue; + } + + isDef = false; // Store the global index int index = cache.addTexture(textureName); @@ -430,6 +616,7 @@ void genCellAlpha(ref GenLevelResult res) if(!(index in textureMap)) textureMap[index] = texNum++; } + assert(texNum == textureMap.length); // If we only found default textures, exit now. @@ -450,20 +637,26 @@ void genCellAlpha(ref GenLevelResult res) assert(texNum >= 1); // Allocate the alpha images - ubyte *uptr = res.allocAlphas(imageRes, texNum); - + res.allocAlphas(imageRes, texNum); assert(res.hasAlpha() && !res.isEmpty()); // Write the indices to the result list foreach(int global, int local; textureMap) res.setAlphaTex(local, global); + ubyte *uptr = res.data.ptr; + // Loop over all textures again. This time, do alpha splatting. for(int ty = 0; ty < 16; ty++) for(int tx = 0; tx < 16; tx++) { - // Get the local index for this square - int index = textures[ltex[ty][tx]]; + // Get the global texture index for this square, if any. + int index = ltex[ty][tx]; + if(index == -1) + continue; + + // Get the local index + index = textureMap[index]; // Get the offset of this square long offs = index*dataSize + pps*(ty*imageRes + tx); @@ -484,109 +677,79 @@ void genCellAlpha(ref GenLevelResult res) // Generate a texture for level 2 from four alpha maps generated in // level 1. -void genLevel2Map(GenLevelResult *maps, ref GenLevelResult res) +void genLevel2Map(GenLevelResult maps[], ref GenLevelResult res) { int fromSize = texSizes[1]; int toSize = texSizes[2]; - // Create a new int type that's automatcially initialized to -1. - typedef int mint=-1; - static assert(mint.init == -1); - typedef mint[4] LtexList; + struct LtexList + { + int[4] inds; + } // Create an overview of which texture is used where. The 'key' is // the global texture index, the 'value' is the corresponding // local indices in each of the four submaps. - LtexList[int] lmap; + HashTable!(int, LtexList) lmap; - if(maps !is null) // NULL means only render default + if(maps.length) // An empty list means use the default texture for(int mi=0;mi<4;mi++) { if(maps[mi].isEmpty()) continue; - assert(maps[mi].hasAlpha() && - maps[mi].image.getWidth() == fromSize); + assert(maps[mi].hasAlpha()); + assert(maps[mi].width == fromSize); for(int ltex=0;ltexgetTechnique(0)->getPass(0); - np->setLightingEnabled(false); - np->createTextureUnitState("_land_default.dds") - ->setTextureScale(scale,scale); - - // List of resources created - std::list createdResources; - */ + terr_makeLandMaterial(toStringz(materialName),scale); // Loop through all our textures - if(maps !is null) + if(maps.length) foreach(int gIndex, LtexList inds; lmap) { char[] name = cache.getString(gIndex); - if ( name == "_land_default.dds" ) + + // Skip default image, if present + if ( name.iBegins("_land_default.") ) continue; - // Instead of passing 'mat', it's better to just make it - // global in C++ even if it's messy and definitely not thread - // safe. - auto pDest = cpp_makeAlphaLayer(mat, materialName ~ "_A_" ~ name); - - /* - // Create alpha map for this texture - std::string alphaName(materialName + "_A_" + tn); - Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton(). - createManual(alphaName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - 2*fromSize,2*fromSize, - 1,0, // depth, mipmaps - Ogre::PF_A8, // One-channel alpha - Ogre::TU_STATIC_WRITE_ONLY); - - createdResources.push_back(texPtr); - - Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer(); - pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD); - const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock(); - - Ogre::uint8* pDest = static_cast(pixelBox.data); - */ + // Create a new alpha texture and get a pointer to the pixel + // data + char *alphaName = toStringz(materialName ~ "_A_" ~ name); + auto pDest = terr_makeAlphaLayer(alphaName, 2*fromSize); // Fill in the alpha values. TODO: Do all this with slices instead. memset(pDest, 0, 4*fromSize*fromSize); for(int i=0;i<4;i++) { // Does this sub-image have this texture? - if(inds[i] == -1) continue; + int index = inds.inds[i]; + if(index == -1) continue; assert(!maps[i].isEmpty()); // Find the right sub-texture in the alpha map - ubyte *from = maps[i].image.data + - (fromSize*fromSize)*inds[i]; + ubyte *from = maps[i].data.ptr + + (fromSize*fromSize)*index; // Find the right destination pointer int x = i%2; @@ -603,77 +766,20 @@ void genLevel2Map(GenLevelResult *maps, ref GenLevelResult res) } } - cpp_closeAlpha(name, scale); - /* - pixelBuffer->unlock(); - np = mp->getTechnique(0)->createPass(); - np->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); - np->setLightingEnabled(false); - np->setDepthFunction(Ogre::CMPF_EQUAL); - - Ogre::TextureUnitState* tus = np->createTextureUnitState(alphaName); - tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); - - tus->setAlphaOperation( Ogre::LBX_BLEND_TEXTURE_ALPHA, - Ogre::LBS_TEXTURE, - Ogre::LBS_TEXTURE); - tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA, - Ogre::LBS_TEXTURE, - Ogre::LBS_TEXTURE); - tus->setIsAlpha(true); - - tus = np->createTextureUnitState(tn); - tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA, - Ogre::LBS_TEXTURE, - Ogre::LBS_CURRENT); - - tus->setTextureScale(scale, scale); - */ + terr_closeAlpha(alphaName, toStringz(name), scale); } // Create the result buffer res.allocImage(toSize); - // Output file name. TODO: The result structure can do this for us - // now, it knows both the level and the cell coords. Figure out - // what to do in the default case though. - int X = res.quad.info.cellX; - int Y = res.quad.info.cellY; - char[] outname = - "2_" ~ toString(X) ~ "_" ~ toString(Y) ~ ".png"; - - // Override for the default image - if(maps == NULL) - outname = "2_default.png"; - - outname = g_cacheDir + outname; - - // TODO: Store the file name in the cache (ie. in res), so we don't - // have to generate it at runtime. - - cpp_cleanupAlpha(outname); - - /* - Ogre::TexturePtr tex1 = getRenderedTexture(mp,materialName + "_T", - toSize,Ogre::PF_R8G8B8); - // Blit the texture over - tex1->getBuffer()->blitToMemory(res.image); - - // Clean up - Ogre::MaterialManager::getSingleton().remove(mp->getHandle()); - Ogre::TextureManager::getSingleton().remove(tex1->getHandle()); - const std::list::const_iterator iend = createdResources.end(); - for ( std::list::const_iterator itr = createdResources.begin(); - itr != iend; - ++itr) { - (*itr)->getCreator()->remove((*itr)->getHandle()); - } + // Texture file name + char[] outname = res.getPNGName(maps.length == 0); + terr_cleanupAlpha(toStringz(outname), res.data.ptr, toSize); res.save(outname); - */ } -void mergeMaps(GenLevelResult *maps, ref GenLevelResult res) +void mergeMaps(GenLevelResult[] maps, ref GenLevelResult res) { int level = res.quad.info.level; @@ -689,94 +795,65 @@ void mergeMaps(GenLevelResult *maps, ref GenLevelResult res) // Add the four sub-textures for(int mi=0;mi<4;mi++) { - // Need to do this in pure D. Oh well. - PixelBox src; + ubyte[] src; // Use default texture if no source is present - if(maps == NULL || maps[mi].isEmpty()) - src = defaults[level-1].image; + if(maps.length == 0 || maps[mi].isEmpty()) + src = defaults[level-1].data; else - src = maps[mi].image; + src = maps[mi].data; + + assert(src.length == 3*fromSize*fromSize); // Find the sub-part of the destination buffer to write to int x = (mi%2) * fromSize; int y = (mi/2) * fromSize; - PixelBox dst = res.image.getSubVolume(Box(x,y,x+fromSize,y+fromSize)); - // Copy the image to the box. Might as well rewrite this to do - // what we want. - copyBox(dst, src); + // Copy the image into the new buffer + copyBox(src, res.data, fromSize, fromSize*2, x, y, 3); } // Resize image if necessary if(toSize != 2*fromSize) res.resize(toSize); - int X = res.quad.info.cellX; - int Y = res.quad.info.cellY; - // Texture file name - char[] outname = res.getPNGName(maps==NULL); - - outname = g_cacheDir + outname; + char[] outname = res.getPNGName(maps.length == 0); // Save the image res.save(outname); } -/* -// Renders a material into a texture -Ogre::TexturePtr getRenderedTexture(Ogre::MaterialPtr mp, const std::string& name, - int texSize, Ogre::PixelFormat tt) +// Copy from one buffer into a sub-region of another buffer +void copyBox(ubyte[] src, ubyte[] dst, + int srcWidth, int dstWidth, + int dstX, int dstY, int pixSize) { - TRACE("getRenderedTexture"); - - Ogre::CompositorPtr cp = Ogre::CompositorManager::getSingleton(). - create("Rtt_Comp", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - //output pass - Ogre::CompositionTargetPass* ctp = cp->createTechnique()->getOutputTargetPass(); - Ogre::CompositionPass* cpass = ctp->createPass(); - cpass->setType(Ogre::CompositionPass::PT_RENDERQUAD); - cpass->setMaterial(mp); - - //create a texture to write the texture to... - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton(). - createManual( - name, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - texSize, - texSize, - 0, - tt, - Ogre::TU_RENDERTARGET - ); - - Ogre::RenderTexture* renderTexture = texture->getBuffer()->getRenderTarget(); - Ogre::Viewport* vp = renderTexture->addViewport(mCamera); + int fskip = srcWidth * pixSize; + int tskip = dstWidth * pixSize; + int rows = srcWidth; + int rowSize = srcWidth*pixSize; - Ogre::CompositorManager::getSingleton().addCompositor(vp, "Rtt_Comp"); - Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp,"Rtt_Comp", true); + assert(src.length == pixSize*srcWidth*srcWidth); + assert(dst.length == pixSize*dstWidth*dstWidth); + assert(srcWidth <= dstWidth); + assert(dstX <= dstWidth-srcWidth && dstY <= dstWidth-srcWidth); - 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(); + // Source and destination pointers + ubyte *from = src.ptr; + ubyte *to = dst.ptr + dstY*tskip + dstX*pixSize; - return texture; + for(;rows>0;rows--) + { + memcpy(to, from, rowSize); + to += tskip; + from += fskip; + } } -*/ // Create the mesh for this level, by merging the meshes from the // previous levels. -void mergeMesh(GenLevelResult *sub, ref GenLevelResult res) +void mergeMesh(GenLevelResult[] sub, ref GenLevelResult res) { // How much to shift various numbers to the left at this level // (ie. multiply by 2^shift). The height at each vertex is @@ -799,9 +876,9 @@ void mergeMesh(GenLevelResult *sub, ref GenLevelResult res) MeshHolder *mh = &res.quad.meshes[0]; MeshInfo *mi = &mh.info; + // Basic info mi.worldWidth = vertSep*intervals; assert(mi.worldWidth == 8192<0;rows--) - { - memcpy(to, from, rowSize); - to += tskip; - from += fskip; - } -} - // ------- OLD CODE - use these snippets later ------- // About segments: @@ -1188,4 +1102,3 @@ false, //skirts //return mIndices; } */ -+/ diff --git a/terrain/quad.d b/terrain/quad.d index 17e385878..c68afc1ca 100644 --- a/terrain/quad.d +++ b/terrain/quad.d @@ -2,6 +2,8 @@ module terrain.quad; import terrain.archive; import terrain.bindings; +import std.stdio; +import monster.vm.dbg; const int CELL_WIDTH = 8192; const float SPLIT_FACTOR = 0.5; @@ -11,6 +13,8 @@ class Quad { this(int cellX=0, int cellY=0, Quad parent = null) { + scope auto _trc = new MTrace("Quad.this"); + mCellX = cellX; mCellY = cellY; @@ -19,13 +23,6 @@ class Quad { mLevel = parent.mLevel-1; - if(mLevel == 1) - { - // Create the terrain and leave it there. - buildTerrain(); - isStatic = true; - } - // Coordinates relative to our parent int relX = cellX - parent.mCellX; int relY = cellY - parent.mCellY; @@ -43,12 +40,36 @@ class Quad // Get the archive data for this quad. mInfo = g_archive.getQuad(mCellX,mCellY,mLevel); + + // Set up the bounding box. Use MW coordinates all the + // way. + mBounds = terr_makeBounds(mInfo.minHeight, + mInfo.maxHeight, + mInfo.worldWidth, + mNode); + + float radius = mInfo.boundingRadius; + + mSplitDistance = radius * SPLIT_FACTOR; + mUnsplitDistance = radius * UNSPLIT_FACTOR; + + // Square the distances + mSplitDistance *= mSplitDistance; + mUnsplitDistance *= mUnsplitDistance; + + if(mLevel == 1) + { + // Create the terrain and leave it there. + buildTerrain(); + isStatic = true; + } } else { // No parent, this is the top-most quad. Get all the info from // the archive. mInfo = g_archive.rootQuad; + assert(mInfo); mLevel = mInfo.level; cellX = mCellX = mInfo.cellX; @@ -68,21 +89,6 @@ class Quad assert(mLevel >= 1); assert(mNode !is null); - // Set up the bounding box. Use MW coordinates all the way - mBounds = terr_makeBounds(mInfo.minHeight, - mInfo.maxHeight, - mInfo.worldWidth, - mNode); - - float radius = mInfo.boundingRadius; - - mSplitDistance = radius * SPLIT_FACTOR; - mUnsplitDistance = radius * UNSPLIT_FACTOR; - - // Square the distances - mSplitDistance *= mSplitDistance; - mUnsplitDistance *= mUnsplitDistance; - // Update the terrain. This will create the mesh or children if // necessary. update(); @@ -90,6 +96,8 @@ class Quad ~this() { + scope auto _trc = new MTrace("Quad.~this"); + // TODO: We might rewrite the code so that the quads are never // actually destroyed, just 'inactivated' by hiding their scene // node. We only call update on our children if we don't have a @@ -101,12 +109,14 @@ class Quad delete mChildren[i]; terr_destroyNode(mNode); - terr_killBounds(mBounds); + if(mBounds !is null) + terr_killBounds(mBounds); } // Remove the landscape for this quad, and create children. void split() { + scope auto _trc = new MTrace("split"); // Never split a static quad or a quad that already has children. assert(!isStatic); assert(!hasChildren); @@ -136,6 +146,7 @@ class Quad // Removes children and rebuilds terrain void unsplit() { + scope auto _trc = new MTrace("unsplit"); // Never unsplit the root quad assert(mLevel < g_archive.rootQuad.level); // Never unsplit a static or quad that isn't split. @@ -158,6 +169,8 @@ class Quad // does it. void update() { + scope auto _trc = new MTrace("Quad.update()"); + // Static quads don't change if(!isStatic) { @@ -165,6 +178,7 @@ class Quad // Get (squared) camera distance. TODO: shouldn't this just // be a simple vector difference from the mesh center? + assert(mBounds !is null); float camDist = terr_getSqCamDist(mBounds); // No children? @@ -209,10 +223,13 @@ class Quad // Build the terrain for this quad void buildTerrain() { + scope auto _trc = new MTrace("buildTerrain"); + assert(!hasMesh); assert(!isStatic); // Map the terrain data into memory. + assert(mInfo); g_archive.mapQuad(mInfo); // Create one mesh for each segment in the quad. TerrainMesh takes @@ -221,7 +238,7 @@ class Quad foreach(i, ref m; meshList) { MeshInfo *mi = g_archive.getMeshInfo(i); - m = terr_makeMesh(mNode, mi, mInfo.level, mInfo.texScale); + m = terr_makeMesh(mNode, mi, mInfo.level, TEX_SCALE); } hasMesh = true; @@ -229,6 +246,8 @@ class Quad void destroyTerrain() { + scope auto _trc = new MTrace("destroyTerrain"); + assert(hasMesh); foreach(m; meshList) diff --git a/terrain/terrain.d b/terrain/terrain.d index 66fc70f67..f080ae067 100644 --- a/terrain/terrain.d +++ b/terrain/terrain.d @@ -31,6 +31,9 @@ import std.file, std.stdio; char[] cacheDir = "cache/terrain/"; +// Enable this to render one single terrain mesh +//debug=singleMesh; + void initTerrain(bool doGen) { char[] fname = cacheDir ~ "landscape.cache"; @@ -50,12 +53,26 @@ void initTerrain(bool doGen) terr_setupRendering(); - // Create the root quad - rootQuad = new Quad; + debug(singleMesh) + { + // Used for debugging single terrain meshes + auto node = terr_createChildNode(20000,-60000,null); + auto info = g_archive.getQuad(0,0,1); + g_archive.mapQuad(info); + auto mi = g_archive.getMeshInfo(0); + auto m = terr_makeMesh(node, mi, info.level, TEX_SCALE); + } + else + { + // Create the root quad + rootQuad = new Quad; + } } extern(C) void d_terr_terrainUpdate() { + debug(singleMesh) return; + // Update the root quad each frame. assert(rootQuad !is null); rootQuad.update();