#include "chunkmanager.hpp" #include #include #include #include #include #include #include "compositemaprenderer.hpp" #include "material.hpp" #include "storage.hpp" #include "terraindrawable.hpp" #include "texturemanager.hpp" namespace Terrain { ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer, ESM::RefId worldspace, double expiryDelay) : GenericResourceManager(nullptr, expiryDelay) , QuadTreeWorld::ChunkManager(worldspace) , mStorage(storage) , mSceneManager(sceneMgr) , mTextureManager(textureManager) , mCompositeMapRenderer(renderer) , mNodeMask(0) , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) { mMultiPassRoot = new osg::StateSet; mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); osg::ref_ptr material(new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { // Override lod with the vertexLodMod adjusted value. // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. lod = static_cast(lodFlags >> (4 * 4)); const ChunkKey key{ .mCenter = center, .mLod = lod, .mLodFlags = lodFlags }; if (osg::ref_ptr obj = mCache->getRefFromObjectCache(key)) return static_cast(obj.get()); const TerrainDrawable* templateGeometry = nullptr; const TemplateKey templateKey{ .mCenter = center, .mLod = lod }; const auto pair = mCache->lowerBound(templateKey); if (pair.has_value() && templateKey == TemplateKey{ .mCenter = pair->first.mCenter, .mLod = pair->first.mLod }) templateGeometry = static_cast(pair->second.get()); osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); mCache->addEntryToObjectCache(key, node.get()); return node; } void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { Resource::reportStats("Terrain Chunk", frameNumber, mCache->getStats(), *stats); } void ChunkManager::clearCache() { GenericResourceManager::clearCache(); mBufferCache.clearCache(); } void ChunkManager::releaseGLObjects(osg::State* state) { GenericResourceManager::releaseGLObjects(state); mBufferCache.releaseGLObjects(state); } osg::ref_ptr ChunkManager::createCompositeMapRTT() { osg::ref_ptr texture = new osg::Texture2D; texture->setTextureWidth(mCompositeMapSize); texture->setTextureHeight(mCompositeMapSize); texture->setInternalFormat(GL_RGB); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); return texture; } void ChunkManager::createCompositeMapGeometry( float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& compositeMap) { if (chunkSize > mMaxCompGeometrySize) { createCompositeMapGeometry(chunkSize / 2.f, chunkCenter + osg::Vec2f(chunkSize / 4.f, chunkSize / 4.f), osg::Vec4f( texCoords.x() + texCoords.z() / 2.f, texCoords.y(), texCoords.z() / 2.f, texCoords.w() / 2.f), compositeMap); createCompositeMapGeometry(chunkSize / 2.f, chunkCenter + osg::Vec2f(-chunkSize / 4.f, chunkSize / 4.f), osg::Vec4f(texCoords.x(), texCoords.y(), texCoords.z() / 2.f, texCoords.w() / 2.f), compositeMap); createCompositeMapGeometry(chunkSize / 2.f, chunkCenter + osg::Vec2f(chunkSize / 4.f, -chunkSize / 4.f), osg::Vec4f(texCoords.x() + texCoords.z() / 2.f, texCoords.y() + texCoords.w() / 2.f, texCoords.z() / 2.f, texCoords.w() / 2.f), compositeMap); createCompositeMapGeometry(chunkSize / 2.f, chunkCenter + osg::Vec2f(-chunkSize / 4.f, -chunkSize / 4.f), osg::Vec4f( texCoords.x(), texCoords.y() + texCoords.w() / 2.f, texCoords.z() / 2.f, texCoords.w() / 2.f), compositeMap); } else { float left = texCoords.x() * 2.f - 1; float top = texCoords.y() * 2.f - 1; float width = texCoords.z() * 2.f; float height = texCoords.w() * 2.f; std::vector> passes = createPasses(chunkSize, chunkCenter, true); for (std::vector>::iterator it = passes.begin(); it != passes.end(); ++it) { osg::ref_ptr geom = osg::createTexturedQuadGeometry( osg::Vec3(left, top, 0), osg::Vec3(width, 0, 0), osg::Vec3(0, height, 0)); geom->setUseDisplayList( false); // don't bother making a display list for an object that is just rendered once. geom->setUseVertexBufferObjects(false); geom->setTexCoordArray(1, geom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); geom->setStateSet(*it); compositeMap.mDrawables.emplace_back(geom); } } } std::vector> ChunkManager::createPasses( float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap) { std::vector layerList; std::vector> blendmaps; mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace); bool useShaders = mSceneManager->getForceShaders(); if (!mSceneManager->getClampLighting()) useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between // a terrain chunk with normal maps and one without normal maps std::vector layers; { for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { TextureLayer textureLayer; textureLayer.mParallax = it->mParallax; textureLayer.mSpecular = it->mSpecular; textureLayer.mDiffuseMap = mTextureManager->getTexture(it->mDiffuseMap); if (!forCompositeMap && !it->mNormalMap.empty()) textureLayer.mNormalMap = mTextureManager->getTexture(it->mNormalMap); if (it->requiresShaders()) useShaders = true; layers.push_back(textureLayer); } } if (forCompositeMap) useShaders = false; 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); blendmapTextures.push_back(texture); } float blendmapScale = mStorage->getBlendmapScale(chunkSize); return ::Terrain::createPasses( useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry) { osg::ref_ptr geometry(new TerrainDrawable); if (!templateGeometry) { osg::ref_ptr positions(new osg::Vec3Array); osg::ref_ptr normals(new osg::Vec3Array); osg::ref_ptr colors(new osg::Vec4ubArray); colors->setNormalize(true); mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors); osg::ref_ptr vbo(new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); normals->setVertexBufferObject(vbo); colors->setVertexBufferObject(vbo); geometry->setVertexArray(positions); geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); } else { // Unfortunately we need to copy vertex data because of poor coupling with VertexBufferObject. osg::ref_ptr positions = static_cast(templateGeometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr normals = static_cast(templateGeometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr colors = static_cast(templateGeometry->getColorArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr vbo(new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); normals->setVertexBufferObject(vbo); colors->setVertexBufferObject(vbo); 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); if (chunkSize <= 1.f) geometry->setLightListCallback(new SceneUtil::LightListCallback); unsigned int numVerts = (mStorage->getCellVertices(mWorldspace) - 1) * chunkSize / (1 << lod) + 1; geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags)); bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; geometry->setTexCoordArrayList(osg::Geometry::ArrayList(numUvSets, mBufferCache.getUVBuffer(numVerts))); geometry->createClusterCullingCallback(); geometry->setStateSet(mMultiPassRoot); if (templateGeometry) { if (templateGeometry->getCompositeMap()) { geometry->setCompositeMap(templateGeometry->getCompositeMap()); geometry->setCompositeMapRenderer(mCompositeMapRenderer); } geometry->setPasses(templateGeometry->getPasses()); } else { if (useCompositeMap) { osg::ref_ptr compositeMap = new CompositeMap; compositeMap->mTexture = createCompositeMapRTT(); createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0, 0, 1, 1), *compositeMap); mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); geometry->setCompositeMap(compositeMap); geometry->setCompositeMapRenderer(mCompositeMapRenderer); TextureLayer layer; layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; geometry->setPasses(::Terrain::createPasses( mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector(1, layer), std::vector>(), 1.f, 1.f)); } else { geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); } } geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize(mWorldspace) / numVerts); if (!templateGeometry && compile && mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } geometry->setNodeMask(mNodeMask); return geometry; } }