#include "terraingrid.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "material.hpp" #include "storage.hpp" namespace { class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback { public: StaticBoundingBoxCallback(const osg::BoundingBox& bounds) : mBoundingBox(bounds) { } virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return mBoundingBox; } private: osg::BoundingBox mBoundingBox; }; } namespace Terrain { TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) , mUnrefQueue(unrefQueue) { mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } TerrainGrid::~TerrainGrid() { while (!mGrid.empty()) { unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); } } osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { if (chunkSize * mNumSplits > 1.f) { // keep splitting osg::ref_ptr group (new osg::Group); if (parent) parent->addChild(group); float newChunkSize = chunkSize/2.f; buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, newChunkSize/2.f)); buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, -newChunkSize/2.f)); buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, newChunkSize/2.f)); buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, -newChunkSize/2.f)); return group; } else { float minH, maxH; if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH)) return NULL; // no terrain defined osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); if (parent) parent->addChild(transform); osg::ref_ptr positions (new osg::Vec3Array); osg::ref_ptr normals (new osg::Vec3Array); osg::ref_ptr colors (new osg::Vec4Array); osg::ref_ptr vbo (new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); normals->setVertexBufferObject(vbo); colors->setVertexBufferObject(vbo); mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors); osg::ref_ptr geometry (new osg::Geometry); geometry->setVertexArray(positions); geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); geometry->addPrimitiveSet(mCache.getIndexBuffer(0)); // we already know the bounding box, so no need to let OSG compute it. osg::Vec3f min(-0.5f*mStorage->getCellWorldSize()*chunkSize, -0.5f*mStorage->getCellWorldSize()*chunkSize, minH); osg::Vec3f max (0.5f*mStorage->getCellWorldSize()*chunkSize, 0.5f*mStorage->getCellWorldSize()*chunkSize, maxH); osg::BoundingBox bounds(min, max); geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds)); std::vector layerList; std::vector > blendmaps; mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList); // For compiling textures, I don't think the osgFX::Effect does it correctly osg::ref_ptr textureCompileDummy (new osg::Node); unsigned int dummyTextureCounter = 0; std::vector > layerTextures; for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; if (!texture) { texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(texture); mTextureCache[it->mDiffuseMap] = texture; } layerTextures.push_back(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } std::vector > blendmapTextures; for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) { osg::ref_ptr texture (new osg::Texture2D); texture->setImage(*it); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setResizeNonPowerOfTwoHint(false); texture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); blendmapTextures.push_back(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); } // use texture coordinates for both texture units, the layer texture and blend texture for (unsigned int i=0; i<2; ++i) geometry->setTexCoordArray(i, mCache.getUVBuffer()); float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); effect->addCullCallback(new SceneUtil::LightListCallback); transform->addChild(effect); #if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Node* toAttach = geometry.get(); #else osg::ref_ptr geode (new osg::Geode); geode->addDrawable(geometry); osg::Node* toAttach = geode.get(); #endif effect->addChild(toAttach); if (mIncrementalCompileOperation) { mIncrementalCompileOperation->add(toAttach); mIncrementalCompileOperation->add(textureCompileDummy); } return transform; } } void TerrainGrid::loadCell(int x, int y) { if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded osg::Vec2f center(x+0.5f, y+0.5f); osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); if (!terrainNode) return; // no terrain defined mTerrainRoot->addChild(terrainNode); mGrid[std::make_pair(x,y)] = terrainNode; } void TerrainGrid::unloadCell(int x, int y) { Grid::iterator it = mGrid.find(std::make_pair(x,y)); if (it == mGrid.end()) return; osg::Node* terrainNode = it->second; mTerrainRoot->removeChild(terrainNode); if (mUnrefQueue.get()) mUnrefQueue->push(terrainNode); mGrid.erase(it); } void TerrainGrid::clearCache() { for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) { if (it->second->referenceCount() <= 1) { if (mUnrefQueue.get()) mUnrefQueue->push(it->second); mTextureCache.erase(it++); } else ++it; } } }