Files
LifeProject/LifeGraphics/vegetation/PlantManager.h
T
2025-05-13 02:46:21 +03:00

607 lines
17 KiB
C++

#ifndef PlantManager_h__
#define PlantManager_h__
#include "Globals.h"
#include "Terrain.h"
#include <vector>
#include "math/LifeMath.h"
#include <stdlib.h>
#include <BoundingBox3D.h>
struct GRASS_INSTANCE_INFO
{
LifeMath::float4 info;
};
class GrassCacheBlock
{
public:
LifeCore::BoundingBox3D bbox;
std::vector<LifeGraphics::IVertexBuffer*> 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; i<grassLayersCount; i++)
buffers.push_back(LifeGraphics::CG::me()->GetRenderer()->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<GRASS_INSTANCE_INFO> 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<GridCell*> 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<int>(floorf(position.X / static_cast<float>(cellSize)));
int pY = static_cast<int>(floorf(-position.Z / static_cast<float>(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<int>(floor(position.X / static_cast<float>(cellSize)));
int pY = static_cast<int>(floor(-position.Z / static_cast<float>(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(&params, 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<GRASS_INSTANCE_INFO*> buffers;
std::vector<GrassCacheBlock*> cache;
std::vector<GrassCacheBlock*> validBlocks;
std::vector<GrassCacheBlock*> visibleBlocks;
LifeGraphics::IVertexBuffer* plantInstBuf;
int blockSize;
int cacheStride;
int blockStride;
std::vector<LifeGraphics::IMesh*> models;
std::vector<PlantClass*> 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; i<pc; i++)
{
SAFE_DELETE(plants[i]);
}
pc = buffers.size();
for(int i = 0; i<pc; i++)
{
SAFE_DELETE_ARRAY(buffers[i]);
}
pc = models.size(); //FIX IT!!!! DELETE ALL MESHES! NOT ONLY 0 !!
for(int i = 0; i<1; i++)
{
SAFE_DELETE(models[i]);
}
validBlocks.clear();
visibleBlocks.clear();
int cacheSize = cache.size();
for(int i = 0; i<cacheSize; i++)
{
SAFE_DELETE(cache[i]);
}
}
void AddPlant(LifeGraphics::IMesh* plant)
{
PlantClass* pc = new PlantClass(terra, plant);
plants.push_back(pc);
}
void Render(LifeGraphics::PassType pass)
{
LifeGraphics::IRenderer* renderer = LifeGraphics::CG::me()->GetRenderer();
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; i<cnt; i++)
{
GrassCacheBlock* cb = visibleBlocks[i];
//render each plant type
for(size_t j = 0; j < models.size(); j++)
{
int plantCount = cb->counters[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; i<plants.size(); i++)
{
PlantClass* c = plants[i];
int blockCount = c->grid->visibleCells.size();
renderer->SetVSConstantBuffer(c->plantBendInfo, 10);
for(int j = 0; j<blockCount; j++)
{
GridCell* cell = c->grid->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; s<plant->subsetCount; 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<int>(floor(pos.X / static_cast<float>(blockSize)));
int row = static_cast<int>(floor(-pos.Z / static_cast<float>(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; i<vc; i++)
{
if(cam->getFrustum()->BoxInFrustum(validBlocks[i]->bbox))
visibleBlocks.push_back(validBlocks[i]);
}
/* printf("Visible grass chunks: %i\n", visibleBlocks.size());
for(int i = 0; i<visibleBlocks.size(); i++)
{
GrassCacheBlock* b = visibleBlocks[i];
for(int j = 0; j<4; j++)
{
printf("Visible block #%i, grass layer #%i, instance count:%i\n", i, j, b->counters[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(&params, 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__