#ifndef PlantManager_h__ #define PlantManager_h__ #include "Globals.h" #include "Terrain.h" #include #include "math/LifeMath.h" #include #include struct GRASS_INSTANCE_INFO { LifeMath::float4 info; }; class GrassCacheBlock { public: LifeCore::BoundingBox3D bbox; std::vector buffers; int bw; int cr; int counters[4]; GrassCacheBlock(int blockWidth, int coverageResolution, int grassLayersCount) { bw = blockWidth; cr = coverageResolution; int subBlocksStride = blockWidth / coverageResolution; int subBlockCnt = subBlocksStride * subBlocksStride; int instanceCnt = subBlockCnt * 3; for(int i = 0; iGetRenderer()->CreateVertexBufferDynamic(instanceCnt * sizeof(GRASS_INSTANCE_INFO), sizeof(GRASS_INSTANCE_INFO))); } void UpdateBuffer(int plantId, GRASS_INSTANCE_INFO* tmpBuffer, int count) { counters[plantId] = count; LifeGraphics::IVertexBuffer* vb = buffers[plantId]; LifeCore::IDataStream* ds = vb->Map(0, LifeGraphics::LF_Discard); ds->Write(tmpBuffer, count * sizeof(GRASS_INSTANCE_INFO)); vb->Unmap(); } ~GrassCacheBlock() { for(size_t i = 0; i < buffers.size(); i++) delete buffers[i]; } }; class GridCell { public: LifeGraphics::Rectangle2D rect; float minHeight; float maxHeight; std::vector instances; int instanceCount; GridCell() :instanceCount(0) { } ~GridCell() { instances.clear(); } void pushInstance(const GRASS_INSTANCE_INFO& instance) { instances.push_back(instance); instanceCount++; } }; class PlantGrid { public: GridCell* cells; int gridSize; int cellSize; int cellStride; std::vector visibleCells; void pushInstance(const GRASS_INSTANCE_INFO& info) { int id = getCellId(info.info); getCell(id)->pushInstance(info); } int getCellId(const LifeMath::float3& position) { int pX = static_cast(floorf(position.X / static_cast(cellSize))); int pY = static_cast(floorf(-position.Z / static_cast(cellSize))); if(pX < 0 || pY < 0) return -1; if(pX >= cellStride || pY >= cellStride) return -1; return pY * cellStride + pX; } int getCellId(const LifeMath::float4& position) { int pX = static_cast(floor(position.X / static_cast(cellSize))); int pY = static_cast(floor(-position.Z / static_cast(cellSize))); if(pX < 0 || pY < 0) return -1; if(pX >= cellStride || pY >= cellStride) return -1; return pY * cellStride + pX; } GridCell* getCell(int id) { return cells + id; } PlantGrid(int _gridSize, int _cellSize) { gridSize = _gridSize; cellSize = _cellSize; cellStride = gridSize / cellSize; int cellCount = cellStride * cellStride; cells = new GridCell[cellCount]; for(int y = 0; y < cellStride; y++) { for(int x = 0; x < cellStride; x++) { GridCell* c = cells + (y * cellStride + x); c->rect.minX = (float)(x * cellSize); c->rect.maxX = (float)(x * cellSize) + (float)cellSize; c->rect.minY = -((float)(y * cellSize)); c->rect.maxY = -((float)(y * cellSize) + (float)cellSize); c->minHeight = 0.0f; c->maxHeight = 255.0f; } } } ~PlantGrid() { visibleCells.clear(); delete[] cells; } void Update(LifeGraphics::Camera* cam) { visibleCells.clear(); for(int y = 0; y< cellStride; y++) { for(int x = 0; x< cellStride; x++) { int offset = y * cellStride + x; GridCell* c = cells + offset; LifeMath::float3 vmin(c->rect.minX, 0.0f, c->rect.maxY); LifeMath::float3 vmax(c->rect.maxX, 255.0f, c->rect.minY); LifeCore::BoundingBox3D box(vmin, vmax); if(cam->getFrustum()->BoxInFrustum(box)) { visibleCells.push_back(c); } } } // } }; struct PLANT_BEND_PARAMS { float fBendScale; float fBendSpeed; LifeMath::float2 dummy; }; class PlantClass { private: LifeGraphics::Terrain* terra; public: PlantGrid* grid; LifeGraphics::IShader* imposterPsh; LifeGraphics::IShader* imposterVsh; LifeGraphics::IConstantBuffer* plantBendInfo; LifeGraphics::ITexture2D* imposterTextures[9]; PlantClass(LifeGraphics::Terrain* _terra, LifeGraphics::IMesh* mesh) { imposterVsh = LifeGraphics::ResourceManager::me()->CompileUserShaderVS(AS_STRING("Plant_simple.vsh")); imposterPsh =LifeGraphics:: ResourceManager::me()->CompileUserShaderPS(AS_STRING("Plant_simple.psh")); terra =_terra; plantCount = 0; plantGeom = mesh; grid = new PlantGrid(_terra->TerrainWidth, 64); plantBendInfo = LifeGraphics::CG::me()->GetRenderer()->CreateConstantBuffer(sizeof(PLANT_BEND_PARAMS)); PLANT_BEND_PARAMS params; // params.fBendScale = 1.0f / mesh->getBoundingBox()->sizeY() * 0.15f; //FIXME!!!!!!!!!!!!!!!!!!!!!!!!!!! params.fBendSpeed = 0.7f; plantBendInfo->Update(¶ms, sizeof(PLANT_BEND_PARAMS)); //generate textures for imposter rendering } ~PlantClass() { SAFE_DELETE(grid); SAFE_DELETE(plantBendInfo); SAFE_DELETE(plantGeom); } void AddInstance(const GRASS_INSTANCE_INFO& instance) { grid->pushInstance(instance); } LifeGraphics::IMesh* plantGeom; int plantCount; }; class PlantManager { private: int currentCell; LifeGraphics::Terrain* terra; byte* coverageMask; PlantGrid* grid; std::vector buffers; std::vector cache; std::vector validBlocks; std::vector visibleBlocks; LifeGraphics::IVertexBuffer* plantInstBuf; int blockSize; int cacheStride; int blockStride; std::vector models; std::vector plants; LifeGraphics::IShader* grassVs; LifeGraphics::IShader* grassPs; LifeGraphics::IShader* grassPs_g; LifeGraphics::IShader* plantTrunkVs; LifeGraphics::IShader* plantLeafVs; LifeGraphics::IConstantBuffer* grassBendInfo; LifeGraphics::IRasterizerState* rsCullNone; LifeGraphics::IRasterizerState* rsCullBack; LifeGraphics::IInputLayout* layout; LifeGraphics::texture_ptr tex; public: PlantManager() :currentCell(-1), blockSize(16), cacheStride(3) { } ~PlantManager() { SAFE_DELETE(grassBendInfo); SAFE_DELETE(rsCullNone); SAFE_DELETE(rsCullBack); SAFE_DELETE(plantInstBuf); SAFE_DELETE(grassVs); SAFE_DELETE(grassPs); SAFE_DELETE(grassPs_g); SAFE_DELETE(plantLeafVs); SAFE_DELETE(plantTrunkVs); SAFE_DELETE(grassBendInfo); delete[]coverageMask; SAFE_DELETE(grid); //SAFE_DELETE(layout); tex->Release(); //delete plants int pc = plants.size(); for(int i = 0; iGetRenderer(); renderer->SetRasterizerState(rsCullNone); renderer->SetPSTexture(tex.ptr(), 0); //render grass if(pass != LifeGraphics::PT_RENDER_SHADOW_MAP_PSSM) { renderer->SetPrimitiveTopology(PT_TRIANGLE_LIST); renderer->SetVertexShader(grassVs); renderer->SetVSConstantBuffer(grassBendInfo, 10); renderer->SetInputLayout(layout); int cnt = visibleBlocks.size(); for(int i = 0; icounters[j]; if(plantCount > 0) { LifeGraphics::IMesh* plant = models[j]; renderer->SetIndexBuffer(plant->indexData); LifeGraphics::IVertexBuffer* buffers[2]; buffers[0] = plant->vertexData; buffers[1] = cb->buffers[j]; renderer->SetVertexBuffer(buffers,2); if(pass == LifeGraphics::PT_COMPOSE_G_BUF) renderer->SetPixelShader(grassPs_g); else if(pass == LifeGraphics::PT_COMPOSE_FINAL) renderer->SetPixelShader(grassPs); renderer->DrawIndexedInstanced(0, plant->subsets[0].Length, 0, plantCount); } } } } //render other plants for(size_t i = 0; igrid->visibleCells.size(); renderer->SetVSConstantBuffer(c->plantBendInfo, 10); for(int j = 0; jgrid->visibleCells[j]; int plantCount = cell->instanceCount; if(plantCount > 0) { LifeCore::IDataStream* buf = plantInstBuf->Map(0, LifeGraphics::LF_Discard); buf->Write(&cell->instances[0], plantCount * sizeof(GRASS_INSTANCE_INFO)); plantInstBuf->Unmap(); LifeGraphics::IMesh* plant = c->plantGeom; renderer->SetIndexBuffer(plant->indexData); LifeGraphics::IVertexBuffer* buffers[2]; buffers[0] = plant->vertexData; buffers[1] = plantInstBuf; renderer->SetVertexBuffer(buffers,2); if(pass != LifeGraphics::PT_RENDER_SHADOW_MAP_PSSM) { if(pass == LifeGraphics::PT_COMPOSE_G_BUF) renderer->SetPixelShader(grassPs_g); else if(pass == LifeGraphics::PT_COMPOSE_FINAL) renderer->SetPixelShader(grassPs); } for(int s = 0; ssubsetCount; s++) { if(s==0) { renderer->SetVertexShader(plantLeafVs); } else { renderer->SetVertexShader(plantTrunkVs); } //renderer->SetPSTexture(plant->subsets[s].material.diffuseMap.ptr(),0); renderer->DrawIndexedInstanced(plant->subsets[s].Offset, plant->subsets[s].Length, 0, plantCount); } } } } LifeGraphics::CG::me()->GetRenderer()->SetRasterizerState(rsCullBack); } void Update(float timeElapsed, LifeGraphics::Camera* cam) { //update plants int plantCount = plants.size(); for(int i = 0; i< plantCount; i++) { PlantClass* c = plants[i]; c->grid->Update(cam); } //update grass LifeMath::float3 pos = cam->getPosition(); int cell = grid->getCellId(pos); int offset[] = {0,0,0,0}; if(cell != currentCell ) { validBlocks.clear(); currentCell = cell; if(currentCell != -1) { //cell has been changed! regenerate plant buffer int cellOffset = (cacheStride - 1) / 2; int col = static_cast(floor(pos.X / static_cast(blockSize))); int row = static_cast(floor(-pos.Z / static_cast(blockSize))); //scan each cache block for(int y = (row - cellOffset); y <= (row + cellOffset); y++) { for(int x = (col - cellOffset); x <= (col + cellOffset); x++) { int check = y * blockStride + x; //if we are not out of terrain bounds if(y>=0 && x >=0 && y < blockStride && x< blockStride) { int cacheOffset = (y - row + cellOffset) * cacheStride + (x - col + cellOffset); GrassCacheBlock* cb = cache[cacheOffset]; GridCell* cell = grid->getCell(check); validBlocks.push_back(cb); //null all counters for(int k = 0; k<4; k++) { offset[k] = 0; } LifeMath::float3 vmin(cell->rect.minX, 0.0f, cell->rect.maxY); LifeMath::float3 vmax(cell->rect.maxX, 255.0f, cell->rect.minY); cb->bbox.resize(vmin, vmax); //cell is selected//run through it's sub cells for(int sy = -(int)cell->rect.minY; sy < -((int)cell->rect.maxY); sy++) { for(int sx = (int)cell->rect.minX; sx < (int)cell->rect.maxX; sx++) { byte pd = coverageMask[sy * terra->TerrainWidth + sx]; for(int i = 0; i<4; i++) { //read plants count for i grass type byte cnt = (pd >> (2 * i)) & (0x3); //and finally generate plants info for(byte j = 0; j < (cnt); j++) { srand(sy * sx *(i + 1 + j)); GRASS_INSTANCE_INFO ni; ni.info.X = (float)sx + ((float)(rand() % 100)) / 100.0f; ni.info.Z = -((float)sy + ((float)(rand() % 100)) / 100.0f); ni.info.Y = terra->GetHeight(ni.info.X, ni.info.Z); ni.info.W = 1.0f; buffers[i][offset[i]] = ni; offset[i] = offset[i] + 1; } } } } for(int l = 0; l<4; l++) { cb->UpdateBuffer(l, buffers[l],offset[l]); } } } } } } int vc = validBlocks.size(); visibleBlocks.clear(); for(int i = 0; igetFrustum()->BoxInFrustum(validBlocks[i]->bbox)) visibleBlocks.push_back(validBlocks[i]); } /* printf("Visible grass chunks: %i\n", visibleBlocks.size()); for(int i = 0; icounters[j] ); } }*/ } void Init(LifeGraphics::Terrain* _terra) { grassBendInfo = LifeGraphics::CG::me()->GetRenderer()->CreateConstantBuffer(sizeof(PLANT_BEND_PARAMS)); PLANT_BEND_PARAMS params; params.fBendScale = 0.09f; params.fBendSpeed = 0.5f; grassBendInfo->Update(¶ms, sizeof(PLANT_BEND_PARAMS)); rsCullNone = LifeGraphics::CG::me()->GetRenderer()->CreateRasterizerState(FILL_SOLID, CULL_NONE); rsCullBack = LifeGraphics::CG::me()->GetRenderer()->CreateRasterizerState(FILL_SOLID, CULL_BACK); plantInstBuf = LifeGraphics::CG::me()->GetRenderer()->CreateVertexBufferDynamic(128 * sizeof(GRASS_INSTANCE_INFO), sizeof(GRASS_INSTANCE_INFO)); grassVs = LifeGraphics::ResourceManager::me()->CompileUserShaderVS(AS_STRING("Grass.vsh")); grassPs = LifeGraphics::ResourceManager::me()->CompileUserShaderPS(AS_STRING("Grass.psh")); grassPs_g = LifeGraphics::ResourceManager::me()->CompileUserShaderPS(AS_STRING("Grass_g.psh")); plantLeafVs = LifeGraphics::ResourceManager::me()->CompileUserShaderVS(AS_STRING("Plant_leafs.vsh")); plantTrunkVs = LifeGraphics::ResourceManager::me()->CompileUserShaderVS(AS_STRING("Plant_trunk.vsh")); tex = LifeGraphics::ResourceManager::me()->GetTexture(AS_STRING("root/Data/Textures/cst_grass_long.dds")); terra = _terra; coverageMask = new byte[terra->TerrainWidth * terra->TerrainWidth]; for(int i = 0; i< (terra->TerrainWidth * terra->TerrainWidth); i++) { coverageMask[i] = ((byte)(0x3 << 6)) | ((byte)(0x0 << 4)) | ((byte)(0x0 << 2)) | ((byte)(0x0 << 0)); } //memset(coverageMask, 0, terra->TerrainWidth * terra->TerrainWidth); grid = new PlantGrid(terra->TerrainWidth, blockSize); for(int i = 0; i<4; i++) buffers.push_back(new GRASS_INSTANCE_INFO[blockSize*blockSize*3]); blockStride = terra->TerrainWidth / blockSize; for(int i = 0; i< (cacheStride * cacheStride); i++) { cache.push_back(new GrassCacheBlock(blockSize,1,4)); } //load grass meshes LifeGraphics::IMesh* tm = LifeGraphics::ResourceManager::me()->GetMesh(AS_STRING("root/Data/Objects/grass_a.vmf")); models.push_back(tm); models.push_back(tm); models.push_back(tm); models.push_back(tm); /*models.push_back(ResourceManager::me()->GetMesh(AS_STRING("root/Data/Objects/g_1.vmf"))); models.push_back(ResourceManager::me()->GetMesh(AS_STRING("root/Data/Objects/g_2.vmf"))); models.push_back(ResourceManager::me()->GetMesh(AS_STRING("root/Data/Objects/g_3.vmf")));*/ LifeGraphics::IInputElement* elements = new LifeGraphics::IInputElement[6]; elements[0] = LifeGraphics::IInputElement("POSITION", 0, 0, FORMAT_R32G32B32_FLOAT, 0, 0, 0); elements[1] = LifeGraphics::IInputElement("TEXCOORD", 0, 12, FORMAT_R32G32_FLOAT, 0, 0, 0); elements[2] = LifeGraphics::IInputElement("NORMAL", 0, 20, FORMAT_R32G32B32_FLOAT, 0, 0, 0); elements[3] = LifeGraphics::IInputElement("TANGENT", 0, 32, FORMAT_R32G32B32_FLOAT, 0, 0, 0); elements[4] = LifeGraphics::IInputElement("BINORMAL", 0, 44, FORMAT_R32G32B32_FLOAT, 0, 0, 0); elements[5] = LifeGraphics::IInputElement("INSTANCEDATA", 0, 0, FORMAT_R32G32B32A32_FLOAT, 1, 1, 1); layout = LifeGraphics::CG::me()->GetRenderer()->CreateInputLayout(elements, 6, grassVs); //fix up models declaration for(int i = 0; i<1; i++) { LifeGraphics::IMesh* m = models[i]; m->inputLayout = layout; } LifeGraphics::IMesh* elka = LifeGraphics::ResourceManager::me()->GetMesh(AS_STRING("root/Data/Objects/Tree_test.vmf")); //IMesh* elka = ResourceManager::me()->GetMesh(AS_STRING("root/Data/Objects/Tree_test.vmf")); //elka->subsets[0].material.diffuseMap = ResourceManager::me()->GetTexture(AS_STRING("root/Data/Textures/Tree_2_2.dds")); //elka->subsets[2].material.diffuseMap = ResourceManager::me()->GetTexture(AS_STRING("root/Data/Textures/BARK5.jpg")); // elka->subsets[1].material.diffuseMap = ResourceManager::me()->GetTexture(AS_STRING("root/Data/Textures/BARK5.jpg")); //elka->subsets[0].material.diffuseMap = ResourceManager::me()->GetTexture(AS_STRING("root/Data/Textures/cod5_tree_branch.dds")); AddPlant(elka); PlantClass* pc = plants[0]; GRASS_INSTANCE_INFO tree; for(int x = 0; x<10; x++) { for(int y = 0; y<10; y++) { tree.info = LifeMath::float4( (float)x*6.0f + 100.0f,terra->GetHeight((float)x*6.0f + 100.0f, -(float)y*6.0f - 20.0f), -(float)y*6.0f - 20.0f, 1.0f ); pc->AddInstance(tree); } } //tree.info = LifeMath::float4(70.0f,terra->GetHeight(70.0f, -20.0f), -20.0f, 1.0f ); //pc->AddInstance(tree); } }; #endif // PlantManager_h__