// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h // The code for the TerrainSceneNode is based on the GeoMipMapSceneNode // developed by Spintz. He made it available for Irrlicht and allowed it to be // distributed under this licence. I only modified some parts. A lot of thanks // go to him. #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_TERRAIN_SCENENODE_ #include "CTerrainSceneNode.h" #include "CTerrainTriangleSelector.h" #include "IVideoDriver.h" #include "ISceneManager.h" #include "ICameraSceneNode.h" #include "SViewFrustum.h" #include "irrMath.h" #include "os.h" #include "IGUIFont.h" #include "IFileSystem.h" #include "IReadFile.h" #include "ITextSceneNode.h" #include "IAnimatedMesh.h" #include "SMesh.h" #include "CDynamicMeshBuffer.h" namespace irr { namespace scene { //! constructor CTerrainSceneNode::CTerrainSceneNode(ISceneNode* parent, ISceneManager* mgr, io::IFileSystem* fs, s32 id, s32 maxLOD, E_TERRAIN_PATCH_SIZE patchSize, const core::vector3df& position, const core::vector3df& rotation, const core::vector3df& scale) : ITerrainSceneNode(parent, mgr, id, position, rotation, scale), TerrainData(patchSize, maxLOD, position, rotation, scale), RenderBuffer(0), VerticesToRender(0), IndicesToRender(0), DynamicSelectorUpdate(false), OverrideDistanceThreshold(false), UseDefaultRotationPivot(true), ForceRecalculation(true), FixedBorderLOD(-1), CameraMovementDelta(10.0f), CameraRotationDelta(1.0f),CameraFOVDelta(0.1f), TCoordScale1(1.0f), TCoordScale2(1.0f), SmoothFactor(0), FileSystem(fs) { #ifdef _DEBUG setDebugName("CTerrainSceneNode"); #endif Mesh = new SMesh(); RenderBuffer = new CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT); RenderBuffer->setHardwareMappingHint(scene::EHM_STATIC, scene::EBT_VERTEX); RenderBuffer->setHardwareMappingHint(scene::EHM_DYNAMIC, scene::EBT_INDEX); if (FileSystem) FileSystem->grab(); setAutomaticCulling(scene::EAC_OFF); } //! destructor CTerrainSceneNode::~CTerrainSceneNode() { delete [] TerrainData.Patches; if (FileSystem) FileSystem->drop(); if (Mesh) Mesh->drop(); if (RenderBuffer) RenderBuffer->drop(); } //! Initializes the terrain data. Loads the vertices from the heightMapFile bool CTerrainSceneNode::loadHeightMap(io::IReadFile* file, video::SColor vertexColor, s32 smoothFactor) { if (!file) return false; Mesh->MeshBuffers.clear(); const u32 startTime = os::Timer::getRealTime(); video::IImage* heightMap = SceneManager->getVideoDriver()->createImageFromFile(file); if (!heightMap) { os::Printer::log("Unable to load heightmap."); return false; } HeightmapFile = file->getFileName(); SmoothFactor = smoothFactor; // Get the dimension of the heightmap data TerrainData.Size = heightMap->getDimension().Width; switch (TerrainData.PatchSize) { case ETPS_9: if (TerrainData.MaxLOD > 3) { TerrainData.MaxLOD = 3; } break; case ETPS_17: if (TerrainData.MaxLOD > 4) { TerrainData.MaxLOD = 4; } break; case ETPS_33: if (TerrainData.MaxLOD > 5) { TerrainData.MaxLOD = 5; } break; case ETPS_65: if (TerrainData.MaxLOD > 6) { TerrainData.MaxLOD = 6; } break; case ETPS_129: if (TerrainData.MaxLOD > 7) { TerrainData.MaxLOD = 7; } break; } // --- Generate vertex data from heightmap ---- // resize the vertex array for the mesh buffer one time (makes loading faster) scene::CDynamicMeshBuffer *mb=0; const u32 numVertices = TerrainData.Size * TerrainData.Size; if (numVertices <= 65536) { //small enough for 16bit buffers mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT); RenderBuffer->getIndexBuffer().setType(video::EIT_16BIT); } else { //we need 32bit buffers mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_32BIT); RenderBuffer->getIndexBuffer().setType(video::EIT_32BIT); } mb->getVertexBuffer().set_used(numVertices); // Read the heightmap to get the vertex data // Apply positions changes, scaling changes const f32 tdSize = 1.0f/(f32)(TerrainData.Size-1); s32 index = 0; float fx=0.f; float fx2=0.f; for (s32 x = 0; x < TerrainData.Size; ++x) { float fz=0.f; float fz2=0.f; for (s32 z = 0; z < TerrainData.Size; ++z) { video::S3DVertex2TCoords& vertex= static_cast(mb->getVertexBuffer().pointer())[index++]; vertex.Normal.set(0.0f, 1.0f, 0.0f); vertex.Color = vertexColor; vertex.Pos.X = fx; vertex.Pos.Y = (f32) heightMap->getPixel(TerrainData.Size-x-1,z).getLightness(); vertex.Pos.Z = fz; vertex.TCoords.X = vertex.TCoords2.X = 1.f-fx2; vertex.TCoords.Y = vertex.TCoords2.Y = fz2; ++fz; fz2 += tdSize; } ++fx; fx2 += tdSize; } // drop heightMap, no longer needed heightMap->drop(); smoothTerrain(mb, smoothFactor); // calculate smooth normals for the vertices calculateNormals(mb); // add the MeshBuffer to the mesh Mesh->addMeshBuffer(mb); // We copy the data to the renderBuffer, after the normals have been calculated. RenderBuffer->getVertexBuffer().set_used(numVertices); for (u32 i = 0; i < numVertices; ++i) { RenderBuffer->getVertexBuffer()[i] = mb->getVertexBuffer()[i]; RenderBuffer->getVertexBuffer()[i].Pos *= TerrainData.Scale; RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.Position; } // We no longer need the mb mb->drop(); // calculate all the necessary data for the patches and the terrain calculateDistanceThresholds(); createPatches(); calculatePatchData(); // set the default rotation pivot point to the terrain nodes center TerrainData.RotationPivot = TerrainData.Center; // Rotate the vertices of the terrain by the rotation // specified. Must be done after calculating the terrain data, // so we know what the current center of the terrain is. setRotation(TerrainData.Rotation); // Pre-allocate memory for indices RenderBuffer->getIndexBuffer().set_used( TerrainData.PatchCount * TerrainData.PatchCount * TerrainData.CalcPatchSize * TerrainData.CalcPatchSize * 6); RenderBuffer->setDirty(); const u32 endTime = os::Timer::getRealTime(); c8 tmp[255]; snprintf_irr(tmp, 255, "Generated terrain data (%dx%d) in %.4f seconds", TerrainData.Size, TerrainData.Size, (endTime - startTime) / 1000.0f ); os::Printer::log(tmp); return true; } //! Initializes the terrain data. Loads the vertices from the heightMapFile bool CTerrainSceneNode::loadHeightMapRAW(io::IReadFile* file, s32 bitsPerPixel, bool signedData, bool floatVals, s32 width, video::SColor vertexColor, s32 smoothFactor) { if (!file) return false; if (floatVals && bitsPerPixel != 32) return false; // start reading const u32 startTime = os::Timer::getTime(); Mesh->MeshBuffers.clear(); const size_t bytesPerPixel = (size_t)bitsPerPixel / 8; // Get the dimension of the heightmap data const long filesize = file->getSize(); if (!width) TerrainData.Size = core::floor32(sqrtf((f32)(filesize / bytesPerPixel))); else { if ((filesize-file->getPos())/bytesPerPixel>(size_t)(width*width)) { os::Printer::log("Error reading heightmap RAW file", "File is too small."); return false; } TerrainData.Size = width; } switch (TerrainData.PatchSize) { case ETPS_9: if (TerrainData.MaxLOD > 3) { TerrainData.MaxLOD = 3; } break; case ETPS_17: if (TerrainData.MaxLOD > 4) { TerrainData.MaxLOD = 4; } break; case ETPS_33: if (TerrainData.MaxLOD > 5) { TerrainData.MaxLOD = 5; } break; case ETPS_65: if (TerrainData.MaxLOD > 6) { TerrainData.MaxLOD = 6; } break; case ETPS_129: if (TerrainData.MaxLOD > 7) { TerrainData.MaxLOD = 7; } break; } // --- Generate vertex data from heightmap ---- // resize the vertex array for the mesh buffer one time (makes loading faster) scene::CDynamicMeshBuffer *mb=0; const u32 numVertices = TerrainData.Size * TerrainData.Size; if (numVertices <= 65536) { //small enough for 16bit buffers mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT); RenderBuffer->getIndexBuffer().setType(video::EIT_16BIT); } else { //we need 32bit buffers mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_32BIT); RenderBuffer->getIndexBuffer().setType(video::EIT_32BIT); } mb->getVertexBuffer().reallocate(numVertices); video::S3DVertex2TCoords vertex; vertex.Normal.set(0.0f, 1.0f, 0.0f); vertex.Color = vertexColor; // Read the heightmap to get the vertex data // Apply positions changes, scaling changes const f32 tdSize = 1.0f/(f32)(TerrainData.Size-1); float fx=0.f; float fx2=0.f; for (s32 x = 0; x < TerrainData.Size; ++x) { float fz=0.f; float fz2=0.f; for (s32 z = 0; z < TerrainData.Size; ++z) { bool failure=false; vertex.Pos.X = fx; if (floatVals) { if (file->read(&vertex.Pos.Y, bytesPerPixel) != bytesPerPixel) failure=true; } else if (signedData) { switch (bytesPerPixel) { case 1: { s8 val; if (file->read(&val, bytesPerPixel) != bytesPerPixel) failure=true; vertex.Pos.Y=val; } break; case 2: { s16 val; if (file->read(&val, bytesPerPixel) != bytesPerPixel) failure=true; vertex.Pos.Y=val/256.f; } break; case 4: { s32 val; if (file->read(&val, bytesPerPixel) != bytesPerPixel) failure=true; vertex.Pos.Y=val/16777216.f; } break; } } else { switch (bytesPerPixel) { case 1: { u8 val; if (file->read(&val, bytesPerPixel) != bytesPerPixel) failure=true; vertex.Pos.Y=val; } break; case 2: { u16 val; if (file->read(&val, bytesPerPixel) != bytesPerPixel) failure=true; vertex.Pos.Y=val/256.f; } break; case 4: { u32 val; if (file->read(&val, bytesPerPixel) != bytesPerPixel) failure=true; vertex.Pos.Y=val/16777216.f; } break; } } if (failure) { os::Printer::log("Error reading heightmap RAW file."); mb->drop(); return false; } vertex.Pos.Z = fz; vertex.TCoords.X = vertex.TCoords2.X = 1.f-fx2; vertex.TCoords.Y = vertex.TCoords2.Y = fz2; mb->getVertexBuffer().push_back(vertex); ++fz; fz2 += tdSize; } ++fx; fx2 += tdSize; } smoothTerrain(mb, smoothFactor); // calculate smooth normals for the vertices calculateNormals(mb); // add the MeshBuffer to the mesh Mesh->addMeshBuffer(mb); const u32 vertexCount = mb->getVertexCount(); // We copy the data to the renderBuffer, after the normals have been calculated. RenderBuffer->getVertexBuffer().set_used(vertexCount); for (u32 i = 0; i < vertexCount; i++) { RenderBuffer->getVertexBuffer()[i] = mb->getVertexBuffer()[i]; RenderBuffer->getVertexBuffer()[i].Pos *= TerrainData.Scale; RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.Position; } // We no longer need the mb mb->drop(); // calculate all the necessary data for the patches and the terrain calculateDistanceThresholds(); createPatches(); calculatePatchData(); // set the default rotation pivot point to the terrain nodes center TerrainData.RotationPivot = TerrainData.Center; // Rotate the vertices of the terrain by the rotation specified. Must be done // after calculating the terrain data, so we know what the current center of the // terrain is. setRotation(TerrainData.Rotation); // Pre-allocate memory for indices RenderBuffer->getIndexBuffer().set_used( TerrainData.PatchCount*TerrainData.PatchCount* TerrainData.CalcPatchSize*TerrainData.CalcPatchSize*6); const u32 endTime = os::Timer::getTime(); c8 tmp[255]; snprintf_irr(tmp, 255, "Generated terrain data (%dx%d) in %.4f seconds", TerrainData.Size, TerrainData.Size, (endTime - startTime) / 1000.0f); os::Printer::log(tmp); return true; } //! Returns the mesh IMesh* CTerrainSceneNode::getMesh() { return Mesh; } //! Returns the material based on the zero based index i. video::SMaterial& CTerrainSceneNode::getMaterial(u32 i) { return Mesh->getMeshBuffer(i)->getMaterial(); } //! Returns amount of materials used by this scene node ( always 1 ) u32 CTerrainSceneNode::getMaterialCount() const { return Mesh->getMeshBufferCount(); } //! Sets the scale of the scene node. //! \param scale: New scale of the node void CTerrainSceneNode::setScale(const core::vector3df& scale) { TerrainData.Scale = scale; applyTransformation(); calculateNormals(RenderBuffer); ForceRecalculation = true; } //! Sets the rotation of the node. This only modifies //! the relative rotation of the node. //! \param rotation: New rotation of the node in degrees. void CTerrainSceneNode::setRotation(const core::vector3df& rotation) { TerrainData.Rotation = rotation; applyTransformation(); ForceRecalculation = true; } //! Sets the pivot point for rotation of this node. This is useful for the TiledTerrainManager to //! rotate all terrain tiles around a global world point. //! NOTE: The default for the RotationPivot will be the center of the individual tile. void CTerrainSceneNode::setRotationPivot(const core::vector3df& pivot) { UseDefaultRotationPivot = false; TerrainData.RotationPivot = pivot; } //! Sets the position of the node. //! \param newpos: New postition of the scene node. void CTerrainSceneNode::setPosition(const core::vector3df& newpos) { TerrainData.Position = newpos; applyTransformation(); ForceRecalculation = true; } //! Apply transformation changes(scale, position, rotation) void CTerrainSceneNode::applyTransformation() { if (!Mesh->getMeshBufferCount()) return; core::matrix4 rotMatrix; rotMatrix.setRotationDegrees(TerrainData.Rotation); const s32 vtxCount = Mesh->getMeshBuffer(0)->getVertexCount(); for (s32 i = 0; i < vtxCount; ++i) { RenderBuffer->getVertexBuffer()[i].Pos = Mesh->getMeshBuffer(0)->getPosition(i) * TerrainData.Scale + TerrainData.Position; RenderBuffer->getVertexBuffer()[i].Pos -= TerrainData.RotationPivot; rotMatrix.inverseRotateVect(RenderBuffer->getVertexBuffer()[i].Pos); RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.RotationPivot; } calculateDistanceThresholds(true); calculatePatchData(); RenderBuffer->setDirty(EBT_VERTEX); } //! Updates the scene nodes indices if the camera has moved or rotated by a certain //! threshold, which can be changed using the SetCameraMovementDeltaThreshold and //! SetCameraRotationDeltaThreshold functions. This also determines if a given patch //! for the scene node is within the view frustum and if it's not the indices are not //! generated for that patch. void CTerrainSceneNode::OnRegisterSceneNode() { if (!IsVisible || !SceneManager->getActiveCamera()) return; SceneManager->registerNodeForRendering(this); preRenderCalculationsIfNeeded(); // Do Not call ISceneNode::OnRegisterSceneNode(), this node should have no children (luke: is this comment still true, as ISceneNode::OnRegisterSceneNode() is called?) ISceneNode::OnRegisterSceneNode(); ForceRecalculation = false; } void CTerrainSceneNode::preRenderCalculationsIfNeeded() { scene::ICameraSceneNode * camera = SceneManager->getActiveCamera(); if (!camera) return; // Determine the camera rotation, based on the camera direction. const core::vector3df cameraPosition = camera->getAbsolutePosition(); const core::vector3df cameraRotation = core::line3d(cameraPosition, camera->getTarget()).getVector().getHorizontalAngle(); core::vector3df cameraUp = camera->getUpVector(); cameraUp.normalize(); const f32 CameraFOV = SceneManager->getActiveCamera()->getFOV(); // Only check on the Camera's Y Rotation if (!ForceRecalculation) { if ((fabsf(cameraRotation.X - OldCameraRotation.X) < CameraRotationDelta) && (fabsf(cameraRotation.Y - OldCameraRotation.Y) < CameraRotationDelta)) { if ((fabs(cameraPosition.X - OldCameraPosition.X) < CameraMovementDelta) && (fabs(cameraPosition.Y - OldCameraPosition.Y) < CameraMovementDelta) && (fabs(cameraPosition.Z - OldCameraPosition.Z) < CameraMovementDelta)) { if (fabs(CameraFOV-OldCameraFOV) < CameraFOVDelta && cameraUp.dotProduct(OldCameraUp) > (1.f - (cos(core::DEGTORAD * CameraRotationDelta)))) { return; } } } } //we need to redo calculations... OldCameraPosition = cameraPosition; OldCameraRotation = cameraRotation; OldCameraUp = cameraUp; OldCameraFOV = CameraFOV; preRenderLODCalculations(); preRenderIndicesCalculations(); } void CTerrainSceneNode::preRenderLODCalculations() { scene::ICameraSceneNode * camera = SceneManager->getActiveCamera(); if (!camera) return; const core::vector3df cameraPosition = camera->getAbsolutePosition(); const SViewFrustum* frustum = camera->getViewFrustum(); // Determine each patches LOD based on distance from camera (and whether or not they are in // the view frustum). const s32 count = TerrainData.PatchCount * TerrainData.PatchCount; for (s32 j = 0; j < count; ++j) { if (frustum->getBoundingBox().intersectsWithBox(TerrainData.Patches[j].BoundingBox)) { const f32 distance = cameraPosition.getDistanceFromSQ(TerrainData.Patches[j].Center); if ( FixedBorderLOD >= 0 ) { TerrainData.Patches[j].CurrentLOD = FixedBorderLOD; if (j < TerrainData.PatchCount || j >= (count - TerrainData.PatchCount) || (j % TerrainData.PatchCount) == 0 || (j % TerrainData.PatchCount) == TerrainData.PatchCount-1) continue; } TerrainData.Patches[j].CurrentLOD = 0; for (s32 i = TerrainData.MaxLOD - 1; i>0; --i) { if (distance >= TerrainData.LODDistanceThreshold[i]) { TerrainData.Patches[j].CurrentLOD = i; break; } } } else { TerrainData.Patches[j].CurrentLOD = -1; } } } void CTerrainSceneNode::preRenderIndicesCalculations() { scene::IIndexBuffer& indexBuffer = RenderBuffer->getIndexBuffer(); IndicesToRender = 0; indexBuffer.set_used(0); s32 index = 0; // Then generate the indices for all patches that are visible. for (s32 i = 0; i < TerrainData.PatchCount; ++i) { for (s32 j = 0; j < TerrainData.PatchCount; ++j) { if (TerrainData.Patches[index].CurrentLOD >= 0) { s32 x = 0; s32 z = 0; // calculate the step we take this patch, based on the patches current LOD const s32 step = 1 << TerrainData.Patches[index].CurrentLOD; // Loop through patch and generate indices while (z < TerrainData.CalcPatchSize) { const s32 index11 = getIndex(j, i, index, x, z); const s32 index21 = getIndex(j, i, index, x + step, z); const s32 index12 = getIndex(j, i, index, x, z + step); const s32 index22 = getIndex(j, i, index, x + step, z + step); indexBuffer.push_back(index12); indexBuffer.push_back(index11); indexBuffer.push_back(index22); indexBuffer.push_back(index22); indexBuffer.push_back(index11); indexBuffer.push_back(index21); IndicesToRender+=6; // increment index position horizontally x += step; // we've hit an edge if (x >= TerrainData.CalcPatchSize) { x = 0; z += step; } } } ++index; } } RenderBuffer->setDirty(EBT_INDEX); if (DynamicSelectorUpdate && TriangleSelector) { CTerrainTriangleSelector* selector = (CTerrainTriangleSelector*)TriangleSelector; selector->setTriangleData(this, -1); } } //! Render the scene node void CTerrainSceneNode::render() { if (!IsVisible || !SceneManager->getActiveCamera()) return; if (!Mesh->getMeshBufferCount()) return; video::IVideoDriver* driver = SceneManager->getVideoDriver(); driver->setTransform (video::ETS_WORLD, core::IdentityMatrix); driver->setMaterial(Mesh->getMeshBuffer(0)->getMaterial()); RenderBuffer->getIndexBuffer().set_used(IndicesToRender); // For use with geomorphing driver->drawMeshBuffer(RenderBuffer); RenderBuffer->getIndexBuffer().set_used(RenderBuffer->getIndexBuffer().allocated_size()); // for debug purposes only: if (DebugDataVisible) { video::SMaterial m; m.Lighting = false; driver->setMaterial(m); if (DebugDataVisible & scene::EDS_BBOX) driver->draw3DBox(TerrainData.BoundingBox, video::SColor(255,255,255,255)); const s32 count = TerrainData.PatchCount * TerrainData.PatchCount; s32 visible = 0; if (DebugDataVisible & scene::EDS_BBOX_BUFFERS) { for (s32 j = 0; j < count; ++j) { driver->draw3DBox(TerrainData.Patches[j].BoundingBox, video::SColor(255,255,0,0)); visible += (TerrainData.Patches[j].CurrentLOD >= 0); } } if (DebugDataVisible & scene::EDS_NORMALS) { // draw normals const f32 debugNormalLength = SceneManager->getParameters()->getAttributeAsFloat(DEBUG_NORMAL_LENGTH); const video::SColor debugNormalColor = SceneManager->getParameters()->getAttributeAsColor(DEBUG_NORMAL_COLOR); driver->drawMeshBufferNormals(RenderBuffer, debugNormalLength, debugNormalColor); } driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); static u32 lastTime = 0; const u32 now = os::Timer::getRealTime(); if (now - lastTime > 1000) { char buf[64]; snprintf_irr(buf, 64, "Count: %d, Visible: %d", count, visible); os::Printer::log(buf); lastTime = now; } } } //! Return the bounding box of the entire terrain. const core::aabbox3d& CTerrainSceneNode::getBoundingBox() const { return TerrainData.BoundingBox; } //! Return the bounding box of a patch const core::aabbox3d& CTerrainSceneNode::getBoundingBox(s32 patchX, s32 patchZ) const { return TerrainData.Patches[patchX * TerrainData.PatchCount + patchZ].BoundingBox; } //! Gets the meshbuffer data based on a specified Level of Detail. //! \param mb: A reference to an SMeshBuffer object //! \param LOD: The Level Of Detail you want the indices from. void CTerrainSceneNode::getMeshBufferForLOD(IDynamicMeshBuffer& mb, s32 LOD ) const { if (!Mesh->getMeshBufferCount()) return; LOD = core::clamp(LOD, 0, TerrainData.MaxLOD - 1); const u32 numVertices = Mesh->getMeshBuffer(0)->getVertexCount(); mb.getVertexBuffer().reallocate(numVertices); video::S3DVertex2TCoords* vertices = (video::S3DVertex2TCoords*)Mesh->getMeshBuffer(0)->getVertices(); for (u32 n=0; ngetIndexBuffer().getType()); // calculate the step we take for all patches, since LOD is the same const s32 step = 1 << LOD; // Generate the indices for all patches at the specified LOD s32 index = 0; for (s32 i=0; i= TerrainData.CalcPatchSize) // we've hit an edge { x = 0; z += step; } } ++index; } } } //! Gets the indices for a specified patch at a specified Level of Detail. //! \param mb: A reference to an array of u32 indices. //! \param patchX: Patch x coordinate. //! \param patchZ: Patch z coordinate. //! \param LOD: The level of detail to get for that patch. If -1, then get //! the CurrentLOD. If the CurrentLOD is set to -1, meaning it's not shown, //! then it will retrieve the triangles at the highest LOD (0). //! \return: Number if indices put into the buffer. s32 CTerrainSceneNode::getIndicesForPatch(core::array& indices, s32 patchX, s32 patchZ, s32 LOD) { if (patchX < 0 || patchX > TerrainData.PatchCount-1 || patchZ < 0 || patchZ > TerrainData.PatchCount-1) return -1; if (LOD < -1 || LOD > TerrainData.MaxLOD - 1) return -1; core::array cLODs; bool setLODs = false; // If LOD of -1 was passed in, use the CurrentLOD of the patch specified if (LOD == -1) { LOD = TerrainData.Patches[patchX * TerrainData.PatchCount + patchZ].CurrentLOD; } else { getCurrentLODOfPatches(cLODs); setCurrentLODOfPatches(LOD); setLODs = true; } if (LOD < 0) return -2; // Patch not visible, don't generate indices. // calculate the step we take for this LOD const s32 step = 1 << LOD; // Generate the indices for the specified patch at the specified LOD const s32 index = patchX * TerrainData.PatchCount + patchZ; s32 x = 0; s32 z = 0; indices.set_used(TerrainData.PatchSize * TerrainData.PatchSize * 6); // Loop through patch and generate indices s32 rv=0; while (z= TerrainData.CalcPatchSize) // we've hit an edge { x = 0; z += step; } } if (setLODs) setCurrentLODOfPatches(cLODs); return rv; } //! Populates an array with the CurrentLOD of each patch. //! \param LODs: A reference to a core::array to hold the values //! \return Returns the number of elements in the array s32 CTerrainSceneNode::getCurrentLODOfPatches(core::array& LODs) const { s32 numLODs; LODs.clear(); const s32 count = TerrainData.PatchCount * TerrainData.PatchCount; for (numLODs = 0; numLODs < count; numLODs++) LODs.push_back(TerrainData.Patches[numLODs].CurrentLOD); return LODs.size(); } //! Manually sets the LOD of a patch //! \param patchX: Patch x coordinate. //! \param patchZ: Patch z coordinate. //! \param LOD: The level of detail to set the patch to. void CTerrainSceneNode::setLODOfPatch(s32 patchX, s32 patchZ, s32 LOD) { TerrainData.Patches[patchX * TerrainData.PatchCount + patchZ].CurrentLOD = LOD; } //! Override the default generation of distance thresholds for determining the LOD a patch //! is rendered at. bool CTerrainSceneNode::overrideLODDistance(s32 LOD, f64 newDistance) { OverrideDistanceThreshold = true; if (LOD < 0 || LOD > TerrainData.MaxLOD - 1) return false; TerrainData.LODDistanceThreshold[LOD] = newDistance * newDistance; return true; } //! Creates a planar texture mapping on the terrain //! \param resolution: resolution of the planar mapping. This is the value //! specifying the relation between world space and texture coordinate space. void CTerrainSceneNode::scaleTexture(f32 resolution, f32 resolution2) { TCoordScale1 = resolution; TCoordScale2 = resolution2; const f32 resBySize = resolution / (f32)(TerrainData.Size-1); const f32 res2BySize = resolution2 / (f32)(TerrainData.Size-1); u32 index = 0; f32 xval = 0.f; f32 x2val = 0.f; for (s32 x=0; xgetVertexBuffer()[index].TCoords.X = 1.f-xval; RenderBuffer->getVertexBuffer()[index].TCoords.Y = zval; if (RenderBuffer->getVertexType()==video::EVT_2TCOORDS) { if (resolution2 == 0) { ((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2 = RenderBuffer->getVertexBuffer()[index].TCoords; } else { ((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2.X = 1.f-x2val; ((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2.Y = z2val; } } ++index; zval += resBySize; z2val += res2BySize; } xval += resBySize; x2val += res2BySize; } RenderBuffer->setDirty(EBT_VERTEX); } //! used to get the indices when generating index data for patches at varying levels of detail. u32 CTerrainSceneNode::getIndex(const s32 PatchX, const s32 PatchZ, const s32 PatchIndex, u32 vX, u32 vZ) const { // top border if (vZ == 0) { if (TerrainData.Patches[PatchIndex].Top && TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Top->CurrentLOD && (vX % (1 << TerrainData.Patches[PatchIndex].Top->CurrentLOD)) != 0 ) { vX -= vX % (1 << TerrainData.Patches[PatchIndex].Top->CurrentLOD); } } else if (vZ == (u32)TerrainData.CalcPatchSize) // bottom border { if (TerrainData.Patches[PatchIndex].Bottom && TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Bottom->CurrentLOD && (vX % (1 << TerrainData.Patches[PatchIndex].Bottom->CurrentLOD)) != 0) { vX -= vX % (1 << TerrainData.Patches[PatchIndex].Bottom->CurrentLOD); } } // left border if (vX == 0) { if (TerrainData.Patches[PatchIndex].Left && TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Left->CurrentLOD && (vZ % (1 << TerrainData.Patches[PatchIndex].Left->CurrentLOD)) != 0) { vZ -= vZ % (1 << TerrainData.Patches[PatchIndex].Left->CurrentLOD); } } else if (vX == (u32)TerrainData.CalcPatchSize) // right border { if (TerrainData.Patches[PatchIndex].Right && TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Right->CurrentLOD && (vZ % (1 << TerrainData.Patches[PatchIndex].Right->CurrentLOD)) != 0) { vZ -= vZ % (1 << TerrainData.Patches[PatchIndex].Right->CurrentLOD); } } if (vZ >= (u32)TerrainData.PatchSize) vZ = TerrainData.CalcPatchSize; if (vX >= (u32)TerrainData.PatchSize) vX = TerrainData.CalcPatchSize; return (vZ + ((TerrainData.CalcPatchSize) * PatchZ)) * TerrainData.Size + (vX + ((TerrainData.CalcPatchSize) * PatchX)); } //! smooth the terrain void CTerrainSceneNode::smoothTerrain(IDynamicMeshBuffer* mb, s32 smoothFactor) { for (s32 run = 0; run < smoothFactor; ++run) { s32 yd = TerrainData.Size; for (s32 y = 1; y < TerrainData.Size - 1; ++y) { for (s32 x = 1; x < TerrainData.Size - 1; ++x) { mb->getVertexBuffer()[x + yd].Pos.Y = (mb->getVertexBuffer()[x-1 + yd].Pos.Y + //left mb->getVertexBuffer()[x+1 + yd].Pos.Y + //right mb->getVertexBuffer()[x + yd - TerrainData.Size].Pos.Y + //above mb->getVertexBuffer()[x + yd + TerrainData.Size].Pos.Y) * 0.25f; //below } yd += TerrainData.Size; } } } //! calculate smooth normals void CTerrainSceneNode::calculateNormals(IDynamicMeshBuffer* mb) { s32 count; core::vector3df a, b, c, t; for (s32 x=0; x0 && z>0) { a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z-1].Pos; b = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z].Pos; c = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z-1].Pos; b = mb->getVertexBuffer()[x*TerrainData.Size+z-1].Pos; c = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; count += 2; } // top right if (x>0 && zgetVertexBuffer()[(x-1)*TerrainData.Size+z].Pos; b = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z+1].Pos; c = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z].Pos; b = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos; c = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; count += 2; } // bottom right if (xgetVertexBuffer()[x*TerrainData.Size+z+1].Pos; b = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos; c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z+1].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; a = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos; b = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z+1].Pos; c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; count += 2; } // bottom left if (x0) { a = mb->getVertexBuffer()[x*TerrainData.Size+z-1].Pos; b = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos; c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; a = mb->getVertexBuffer()[x*TerrainData.Size+z-1].Pos; b = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z].Pos; c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z-1].Pos; b -= a; c -= a; t = b.crossProduct(c); t.normalize(); normal += t; count += 2; } if (count != 0) { normal.normalize(); } else { normal.set(0.0f, 1.0f, 0.0f); } mb->getVertexBuffer()[x * TerrainData.Size + z].Normal = normal; } } } //! create patches, stuff that needs to be done only once for patches goes here. void CTerrainSceneNode::createPatches() { TerrainData.PatchCount = (TerrainData.Size - 1) / (TerrainData.CalcPatchSize); if (TerrainData.Patches) delete [] TerrainData.Patches; TerrainData.Patches = new SPatch[TerrainData.PatchCount * TerrainData.PatchCount]; } //! used to calculate the internal STerrainData structure both at creation and after scaling/position calls. void CTerrainSceneNode::calculatePatchData() { // Reset the Terrains Bounding Box for re-calculation TerrainData.BoundingBox.reset(RenderBuffer->getPosition(0)); for (s32 x = 0; x < TerrainData.PatchCount; ++x) { for (s32 z = 0; z < TerrainData.PatchCount; ++z) { const s32 index = x * TerrainData.PatchCount + z; SPatch& patch = TerrainData.Patches[index]; patch.CurrentLOD = 0; const s32 xstart = x*TerrainData.CalcPatchSize; const s32 xend = xstart+TerrainData.CalcPatchSize; const s32 zstart = z*TerrainData.CalcPatchSize; const s32 zend = zstart+TerrainData.CalcPatchSize; // For each patch, calculate the bounding box (mins and maxes) patch.BoundingBox.reset(RenderBuffer->getPosition(xstart*TerrainData.Size + zstart)); for (s32 xx = xstart; xx <= xend; ++xx) for (s32 zz = zstart; zz <= zend; ++zz) patch.BoundingBox.addInternalPoint(RenderBuffer->getVertexBuffer()[xx * TerrainData.Size + zz].Pos); // Reconfigure the bounding box of the terrain as a whole TerrainData.BoundingBox.addInternalBox(patch.BoundingBox); // get center of Patch patch.Center = patch.BoundingBox.getCenter(); // Assign Neighbours // Top if (x > 0) patch.Top = &TerrainData.Patches[(x-1) * TerrainData.PatchCount + z]; else patch.Top = 0; // Bottom if (x < TerrainData.PatchCount - 1) patch.Bottom = &TerrainData.Patches[(x+1) * TerrainData.PatchCount + z]; else patch.Bottom = 0; // Left if (z > 0) patch.Left = &TerrainData.Patches[x * TerrainData.PatchCount + z - 1]; else patch.Left = 0; // Right if (z < TerrainData.PatchCount - 1) patch.Right = &TerrainData.Patches[x * TerrainData.PatchCount + z + 1]; else patch.Right = 0; } } // get center of Terrain TerrainData.Center = TerrainData.BoundingBox.getCenter(); // if the default rotation pivot is still being used, update it. if (UseDefaultRotationPivot) { TerrainData.RotationPivot = TerrainData.Center; } } //! used to calculate or recalculate the distance thresholds void CTerrainSceneNode::calculateDistanceThresholds(bool scalechanged) { // Only update the LODDistanceThreshold if it's not manually changed if (!OverrideDistanceThreshold) { TerrainData.LODDistanceThreshold.set_used(0); // Determine new distance threshold for determining what LOD to draw patches at TerrainData.LODDistanceThreshold.reallocate(TerrainData.MaxLOD); const f64 size = TerrainData.PatchSize * TerrainData.PatchSize * TerrainData.Scale.X * TerrainData.Scale.Z; for (s32 i=0; i& lodarray) { const s32 count = TerrainData.PatchCount * TerrainData.PatchCount; for (s32 i=0; igetMeshBufferCount()) return 0; core::matrix4 rotMatrix; rotMatrix.setRotationDegrees(TerrainData.Rotation); core::vector3df pos(x, 0.0f, z); rotMatrix.rotateVect(pos); pos -= TerrainData.Position; pos /= TerrainData.Scale; s32 X(core::floor32(pos.X)); s32 Z(core::floor32(pos.Z)); f32 height = -FLT_MAX; if (X >= 0 && X < TerrainData.Size-1 && Z >= 0 && Z < TerrainData.Size-1) { const video::S3DVertex2TCoords* Vertices = (const video::S3DVertex2TCoords*)Mesh->getMeshBuffer(0)->getVertices(); const core::vector3df& a = Vertices[X * TerrainData.Size + Z].Pos; const core::vector3df& b = Vertices[(X + 1) * TerrainData.Size + Z].Pos; const core::vector3df& c = Vertices[X * TerrainData.Size + (Z + 1)].Pos; const core::vector3df& d = Vertices[(X + 1) * TerrainData.Size + (Z + 1)].Pos; // offset from integer position const f32 dx = pos.X - X; const f32 dz = pos.Z - Z; if (dx > dz) height = a.Y + (d.Y - b.Y)*dz + (b.Y - a.Y)*dx; else height = a.Y + (d.Y - c.Y)*dx + (c.Y - a.Y)*dz; height *= TerrainData.Scale.Y; height += TerrainData.Position.Y; } return height; } //! Writes attributes of the scene node. void CTerrainSceneNode::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const { ISceneNode::serializeAttributes(out, options); out->addString("Heightmap", HeightmapFile.c_str()); out->addFloat("TextureScale1", TCoordScale1); out->addFloat("TextureScale2", TCoordScale2); out->addInt("SmoothFactor", SmoothFactor); } //! Reads attributes of the scene node. void CTerrainSceneNode::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) { io::path newHeightmap = in->getAttributeAsString("Heightmap"); f32 tcoordScale1 = in->getAttributeAsFloat("TextureScale1"); f32 tcoordScale2 = in->getAttributeAsFloat("TextureScale2"); s32 smoothFactor = in->getAttributeAsInt("SmoothFactor"); // set possible new heightmap if (newHeightmap.size() != 0 && newHeightmap != HeightmapFile) { io::IReadFile* file = FileSystem->createAndOpenFile(newHeightmap.c_str()); if (file) { loadHeightMap(file, video::SColor(255,255,255,255), smoothFactor); file->drop(); } else os::Printer::log("could not open heightmap", newHeightmap.c_str()); } // set possible new scale if (core::equals(tcoordScale1, 0.f)) tcoordScale1 = 1.0f; if (core::equals(tcoordScale2, 0.f)) tcoordScale2 = 1.0f; if (!core::equals(tcoordScale1, TCoordScale1) || !core::equals(tcoordScale2, TCoordScale2)) { scaleTexture(tcoordScale1, tcoordScale2); } ISceneNode::deserializeAttributes(in, options); } //! Creates a clone of this scene node and its children. ISceneNode* CTerrainSceneNode::clone(ISceneNode* newParent, ISceneManager* newManager) { if (!newParent) newParent = Parent; if (!newManager) newManager = SceneManager; CTerrainSceneNode* nb = new CTerrainSceneNode( newParent, newManager, FileSystem, ID, 4, ETPS_17, getPosition(), getRotation(), getScale()); nb->cloneMembers(this, newManager); // instead of cloning the data structures, recreate the terrain. // (temporary solution) // load file io::IReadFile* file = FileSystem->createAndOpenFile(HeightmapFile.c_str()); if (file) { nb->loadHeightMap(file, video::SColor(255,255,255,255), 0); file->drop(); } // scale textures nb->scaleTexture(TCoordScale1, TCoordScale2); // copy materials for (unsigned int m = 0; mgetMeshBufferCount(); ++m) { if (nb->Mesh->getMeshBufferCount()>m && nb->Mesh->getMeshBuffer(m) && Mesh->getMeshBuffer(m)) { nb->Mesh->getMeshBuffer(m)->getMaterial() = Mesh->getMeshBuffer(m)->getMaterial(); } } nb->RenderBuffer->getMaterial() = RenderBuffer->getMaterial(); // finish if ( newParent ) nb->drop(); return nb; } } // end namespace scene } // end namespace irr #endif // _IRR_COMPILE_WITH_TERRAIN_SCENENODE_