#include "precompiled.h" #include <set> #include <algorithm> #include "ps/Pyrogenesis.h" #include "lib/res/graphics/ogl_tex.h" #include "graphics/LightEnv.h" #include "Renderer.h" #include "renderer/PatchRData.h" #include "AlphaMapCalculator.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "ps/Game.h" #include "ps/World.h" #include "maths/MathUtil.h" #include "simulation/LOSManager.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" const int BlendOffsets[8][2] = { { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 } }; /////////////////////////////////////////////////////////////////// // CPatchRData constructor CPatchRData::CPatchRData(CPatch* patch) : m_Patch(patch), m_VBBase(0), m_VBBlends(0), m_Vertices(0) { debug_assert(patch); Build(); } /////////////////////////////////////////////////////////////////// // CPatchRData destructor CPatchRData::~CPatchRData() { // delete copy of vertex data delete[] m_Vertices; // release vertex buffer chunks if (m_VBBase) g_VBMan.Release(m_VBBase); if (m_VBBlends) g_VBMan.Release(m_VBBlends); } static Handle GetTerrainTileTexture(CTerrain* terrain,int gx,int gz) { CMiniPatch* mp=terrain->GetTile(gx,gz); return mp ? mp->Tex1 : 0; } const float uvFactor = 0.125f / sqrt(2.f); static void CalculateUV(float uv[2], int x, int z) { // The UV axes are offset 45 degrees from XZ uv[0] = ( x-z)*uvFactor; uv[1] = (-x-z)*uvFactor; } struct STmpSplat { Handle m_Texture; u16 m_Indices[4]; }; void CPatchRData::BuildBlends() { m_BlendIndices.clear(); m_BlendSplats.clear(); m_BlendVertices.clear(); m_BlendVertexIndices.clear(); CTerrain* terrain=m_Patch->m_Parent; // temporary list of splats std::vector<STmpSplat> splats; // set of textures used for splats std::set<Handle> splatTextures; // for each tile in patch .. for (int j=0;j<PATCH_SIZE;j++) { for (int i=0;i<PATCH_SIZE;i++) { u32 gx,gz; CMiniPatch* mp=&m_Patch->m_MiniPatches[j][i]; mp->GetTileIndex(gx,gz); // build list of textures of higher priority than current tile that are used by neighbouring tiles std::vector<STex> neighbourTextures; for (int m=-1;m<=1;m++) { for (int k=-1;k<=1;k++) { CMiniPatch* nmp=terrain->GetTile(gx+k,gz+m); if (nmp && nmp->Tex1 != mp->Tex1) { if (nmp->Tex1Priority>mp->Tex1Priority || (nmp->Tex1Priority==mp->Tex1Priority && nmp->Tex1>mp->Tex1)) { STex tex; tex.m_Handle=nmp->Tex1; tex.m_Priority=nmp->Tex1Priority; if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) { neighbourTextures.push_back(tex); } } } } } if (neighbourTextures.size()>0) { // sort textures from lowest to highest priority std::sort(neighbourTextures.begin(),neighbourTextures.end()); // for each of the neighbouring textures .. uint count=(uint)neighbourTextures.size(); for (uint k=0;k<count;++k) { // now build the grid of blends dependent on whether the tile adjacent to the current tile // uses the current neighbour texture BlendShape8 shape; for (int m=0;m<8;m++) { int ox=gx+BlendOffsets[m][1]; int oz=gz+BlendOffsets[m][0]; // get texture on adjacent tile Handle atex=GetTerrainTileTexture(terrain,ox,oz); // fill 0/1 into shape array shape[m]=(atex==neighbourTextures[k].m_Handle) ? 0 : 1; } // calculate the required alphamap and the required rotation of the alphamap from blendshape unsigned int alphamapflags; int alphamap=CAlphaMapCalculator::Calculate(shape,alphamapflags); // now actually render the blend tile (if we need one) if (alphamap!=-1) { float u0=g_Renderer.m_AlphaMapCoords[alphamap].u0; float u1=g_Renderer.m_AlphaMapCoords[alphamap].u1; float v0=g_Renderer.m_AlphaMapCoords[alphamap].v0; float v1=g_Renderer.m_AlphaMapCoords[alphamap].v1; if (alphamapflags & BLENDMAP_FLIPU) { // flip u float t=u0; u0=u1; u1=t; } if (alphamapflags & BLENDMAP_FLIPV) { // flip v float t=v0; v0=v1; v1=t; } int base=0; if (alphamapflags & BLENDMAP_ROTATE90) { // rotate 1 base=1; } else if (alphamapflags & BLENDMAP_ROTATE180) { // rotate 2 base=2; } else if (alphamapflags & BLENDMAP_ROTATE270) { // rotate 3 base=3; } SBlendVertex vtx[4]; vtx[(base+0)%4].m_AlphaUVs[0]=u0; vtx[(base+0)%4].m_AlphaUVs[1]=v0; vtx[(base+1)%4].m_AlphaUVs[0]=u1; vtx[(base+1)%4].m_AlphaUVs[1]=v0; vtx[(base+2)%4].m_AlphaUVs[0]=u1; vtx[(base+2)%4].m_AlphaUVs[1]=v1; vtx[(base+3)%4].m_AlphaUVs[0]=u0; vtx[(base+3)%4].m_AlphaUVs[1]=v1; int vsize=PATCH_SIZE+1; SBlendVertex dst; int vindex=(int)m_BlendVertices.size(); const SBaseVertex& vtx0=m_Vertices[(j*vsize)+i]; CalculateUV(dst.m_UVs, gx, gz); dst.m_AlphaUVs[0]=vtx[0].m_AlphaUVs[0]; dst.m_AlphaUVs[1]=vtx[0].m_AlphaUVs[1]; dst.m_LOSColor=vtx0.m_LOSColor; dst.m_Position=vtx0.m_Position; m_BlendVertices.push_back(dst); m_BlendVertexIndices.push_back((j*vsize)+i); const SBaseVertex& vtx1=m_Vertices[(j*vsize)+i+1]; CalculateUV(dst.m_UVs, gx+1, gz); dst.m_AlphaUVs[0]=vtx[1].m_AlphaUVs[0]; dst.m_AlphaUVs[1]=vtx[1].m_AlphaUVs[1]; dst.m_LOSColor=vtx1.m_LOSColor; dst.m_Position=vtx1.m_Position; m_BlendVertices.push_back(dst); m_BlendVertexIndices.push_back((j*vsize)+i+1); const SBaseVertex& vtx2=m_Vertices[((j+1)*vsize)+i+1]; CalculateUV(dst.m_UVs, gx+1, gz+1); dst.m_AlphaUVs[0]=vtx[2].m_AlphaUVs[0]; dst.m_AlphaUVs[1]=vtx[2].m_AlphaUVs[1]; dst.m_LOSColor=vtx2.m_LOSColor; dst.m_Position=vtx2.m_Position; m_BlendVertices.push_back(dst); m_BlendVertexIndices.push_back(((j+1)*vsize)+i+1); const SBaseVertex& vtx3=m_Vertices[((j+1)*vsize)+i]; CalculateUV(dst.m_UVs, gx, gz+1); dst.m_AlphaUVs[0]=vtx[3].m_AlphaUVs[0]; dst.m_AlphaUVs[1]=vtx[3].m_AlphaUVs[1]; dst.m_LOSColor=vtx3.m_LOSColor; dst.m_Position=vtx3.m_Position; m_BlendVertices.push_back(dst); m_BlendVertexIndices.push_back(((j+1)*vsize)+i); // build a splat for this quad STmpSplat splat; splat.m_Texture=neighbourTextures[k].m_Handle; splat.m_Indices[0]=(u16)(vindex); splat.m_Indices[1]=(u16)(vindex+1); splat.m_Indices[2]=(u16)(vindex+2); splat.m_Indices[3]=(u16)(vindex+3); splats.push_back(splat); // add this texture to set of unique splat textures splatTextures.insert(splat.m_Texture); } } } } } // build vertex data if (m_VBBlends) { // release existing vertex buffer chunk g_VBMan.Release(m_VBBlends); m_VBBlends=0; } if (m_BlendVertices.size()) { m_VBBlends=g_VBMan.Allocate(sizeof(SBlendVertex),m_BlendVertices.size(),true); m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends,&m_BlendVertices[0]); // now build outgoing splats m_BlendSplats.resize(splatTextures.size()); int splatCount=0; debug_assert(m_VBBlends->m_Index < 65536); unsigned short base = (unsigned short)m_VBBlends->m_Index; std::set<Handle>::iterator iter=splatTextures.begin(); for (;iter!=splatTextures.end();++iter) { Handle tex=*iter; SSplat& splat=m_BlendSplats[splatCount]; splat.m_IndexStart=(u32)m_BlendIndices.size(); splat.m_Texture=tex; for (uint k=0;k<(uint)splats.size();k++) { if (splats[k].m_Texture==tex) { m_BlendIndices.push_back(splats[k].m_Indices[0]+base); m_BlendIndices.push_back(splats[k].m_Indices[1]+base); m_BlendIndices.push_back(splats[k].m_Indices[2]+base); m_BlendIndices.push_back(splats[k].m_Indices[3]+base); splat.m_IndexCount+=4; } } splatCount++; } } } void CPatchRData::BuildIndices() { // must have allocated some vertices before trying to build corresponding indices debug_assert(m_VBBase); // number of vertices in each direction in each patch int vsize=PATCH_SIZE+1; // release existing indices and bins m_Indices.clear(); m_ShadowMapIndices.clear(); m_Splats.clear(); // build grid of textures on this patch and boundaries of adjacent patches std::vector<Handle> textures; Handle texgrid[PATCH_SIZE][PATCH_SIZE]; for (int j=0;j<PATCH_SIZE;j++) { for (int i=0;i<PATCH_SIZE;i++) { Handle h=m_Patch->m_MiniPatches[j][i].Tex1; texgrid[j][i]=h; if (std::find(textures.begin(),textures.end(),h)==textures.end()) { textures.push_back(h); } } } // now build base splats from interior textures m_Splats.resize(textures.size()); // build indices for base splats u32 base=(u32)m_VBBase->m_Index; for (uint i=0;i<(uint)m_Splats.size();i++) { Handle h=textures[i]; SSplat& splat=m_Splats[i]; splat.m_Texture=h; splat.m_IndexStart=(u32)m_Indices.size(); for (int j=0;j<PATCH_SIZE;j++) { for (int i=0;i<PATCH_SIZE;i++) { if (texgrid[j][i]==h){ m_Indices.push_back(((j+0)*vsize+(i+0))+base); m_Indices.push_back(((j+0)*vsize+(i+1))+base); m_Indices.push_back(((j+1)*vsize+(i+1))+base); m_Indices.push_back(((j+1)*vsize+(i+0))+base); } } } splat.m_IndexCount=(u32)m_Indices.size()-splat.m_IndexStart; } // build indices for the shadow map pass for (int j=0;j<PATCH_SIZE;j++) { for (int i=0;i<PATCH_SIZE;i++) { m_ShadowMapIndices.push_back(((j+0)*vsize+(i+0))+base); m_ShadowMapIndices.push_back(((j+0)*vsize+(i+1))+base); m_ShadowMapIndices.push_back(((j+1)*vsize+(i+1))+base); m_ShadowMapIndices.push_back(((j+1)*vsize+(i+0))+base); } } } void CPatchRData::BuildVertices() { // create both vertices and lighting colors CVector3D normal; // number of vertices in each direction in each patch int vsize=PATCH_SIZE+1; if (!m_Vertices) { m_Vertices=new SBaseVertex[vsize*vsize]; } SBaseVertex* vertices=m_Vertices; // get index of this patch u32 px=m_Patch->m_X; u32 pz=m_Patch->m_Z; CTerrain* terrain=m_Patch->m_Parent; const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); // build vertices for (int j=0;j<vsize;j++) { for (int i=0;i<vsize;i++) { int ix=px*PATCH_SIZE+i; int iz=pz*PATCH_SIZE+j; int v=(j*vsize)+i; // calculate vertex data terrain->CalcPosition(ix,iz,vertices[v].m_Position); *(uint32_t*)&vertices[v].m_LOSColor = 0; // will be set to the proper value in Update() CalculateUV(vertices[v].m_UVs, ix, iz); // Calculate diffuse lighting for this vertex // Ambient is added by the lighting pass (since ambient is the same // for all vertices, it need not be stored in the vertex structure) terrain->CalcNormal(ix,iz,normal); RGBColor diffuse; lightEnv.EvaluateDirect(normal, diffuse); *(u32*)&vertices[v].m_DiffuseColor = ConvertRGBColorTo4ub(diffuse); } } // upload to vertex buffer if (!m_VBBase) { m_VBBase=g_VBMan.Allocate(sizeof(SBaseVertex),vsize*vsize,true); } m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase,m_Vertices); } void CPatchRData::Build() { BuildVertices(); BuildIndices(); BuildBlends(); } void CPatchRData::Update() { if (m_UpdateFlags!=0) { // TODO,RC 11/04/04 - need to only rebuild necessary bits of renderdata rather // than everything; it's complicated slightly because the blends are dependent // on both vertex and index data BuildVertices(); BuildIndices(); BuildBlends(); m_UpdateFlags=0; } // Update vertex colors, which are affected by LOS u32 px=m_Patch->m_X; u32 pz=m_Patch->m_Z; CTerrain* terrain=m_Patch->m_Parent; int mapSize=terrain->GetVerticesPerSide(); int vsize=PATCH_SIZE+1; SColor4ub baseColour = terrain->GetBaseColour(); if (g_Game) { CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); // this is very similar to BuildVertices(), but just for color for (int j=0;j<vsize;j++) { for (int i=0;i<vsize;i++) { int ix=px*PATCH_SIZE+i; int iz=pz*PATCH_SIZE+j; int v=(j*vsize)+i; const int DX[] = {1,1,0,0}; const int DZ[] = {0,1,1,0}; SColor4ub losMod = baseColour; for(int k=0; k<4; k++) { int tx = ix -DX[k]; int tz = iz -DZ[k]; if(tx >= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2) { ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer()); if(s==LOS_EXPLORED && losMod.R > 178) losMod = SColor4ub(178, 178, 178, 255); else if(s==LOS_UNEXPLORED && losMod.R > 0) losMod = SColor4ub(0, 0, 0, 255); } } m_Vertices[v].m_LOSColor = losMod; } } } else { for (int j = 0; j < vsize; ++j) { for (int i = 0; i < vsize; ++i) { int v = (j*vsize)+i; m_Vertices[v].m_LOSColor = baseColour; } } } // upload base vertices into their vertex buffer m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase,m_Vertices); // update blend colors by copying them from vertex colors for(uint i=0; i<m_BlendVertices.size(); i++) { m_BlendVertices[i].m_LOSColor = m_Vertices[m_BlendVertexIndices[i]].m_LOSColor; } // upload blend vertices into their vertex buffer too if(m_BlendVertices.size()) { m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends,&m_BlendVertices[0]); } } void CPatchRData::RenderBase(bool losColor) { debug_assert(m_UpdateFlags==0); SBaseVertex *base=(SBaseVertex *)m_VBBase->m_Owner->Bind(); // setup data pointers u32 stride=sizeof(SBaseVertex); glVertexPointer(3,GL_FLOAT,stride,&base->m_Position[0]); glColorPointer(4,GL_UNSIGNED_BYTE,stride,losColor ? &base->m_LOSColor : &base->m_DiffuseColor); glTexCoordPointer(2,GL_FLOAT,stride,&base->m_UVs[0]); // render each splat for (uint i=0;i<(uint)m_Splats.size();i++) { SSplat& splat=m_Splats[i]; ogl_tex_bind(splat.m_Texture); if (!g_Renderer.m_SkipSubmit) { glDrawElements(GL_QUADS, splat.m_IndexCount, GL_UNSIGNED_SHORT, &m_Indices[splat.m_IndexStart]); } // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris+=splat.m_IndexCount/2; } } void CPatchRData::RenderStreams(u32 streamflags, bool losColor) { debug_assert(m_UpdateFlags==0); SBaseVertex* base=(SBaseVertex *)m_VBBase->m_Owner->Bind(); // setup data pointers u32 stride=sizeof(SBaseVertex); glVertexPointer(3, GL_FLOAT, stride, &base->m_Position); if (streamflags & STREAM_UV0) { glTexCoordPointer(2, GL_FLOAT, stride, &base->m_UVs); } else if (streamflags & STREAM_POSTOUV0) { glTexCoordPointer(3, GL_FLOAT, stride, &base->m_Position); } if (streamflags & STREAM_COLOR) { glColorPointer(4,GL_UNSIGNED_BYTE,stride,losColor ? &base->m_LOSColor : &base->m_DiffuseColor); } // render all base splats at once if (!g_Renderer.m_SkipSubmit) { glDrawElements(GL_QUADS,(GLsizei)m_Indices.size(),GL_UNSIGNED_SHORT,&m_Indices[0]); } // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris+=(u32)m_Indices.size()/2; } void CPatchRData::RenderBlends() { debug_assert(m_UpdateFlags==0); if (m_BlendVertices.size()==0) return; u8* base=m_VBBlends->m_Owner->Bind(); // setup data pointers u32 stride=sizeof(SBlendVertex); // ((GCC warns about offsetof: SBlendVertex contains a CVector3D which has // a constructor, and so is not a POD type, and so offsetof is theoretically // invalid - see http://gcc.gnu.org/ml/gcc/2003-11/msg00281.html - but it // doesn't seem to be worth changing this code since it works anyway.)) glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_Position)); glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBlendVertex,m_LOSColor)); pglClientActiveTextureARB(GL_TEXTURE0); glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_UVs[0])); pglClientActiveTextureARB(GL_TEXTURE1); glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_AlphaUVs[0])); for (uint i=0;i<(uint)m_BlendSplats.size();i++) { SSplat& splat=m_BlendSplats[i]; ogl_tex_bind(splat.m_Texture); if (!g_Renderer.m_SkipSubmit) { glDrawElements(GL_QUADS, splat.m_IndexCount, GL_UNSIGNED_SHORT, &m_BlendIndices[splat.m_IndexStart]); } // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_BlendSplats++; g_Renderer.m_Stats.m_TerrainTris+=splat.m_IndexCount/2; } } void CPatchRData::RenderOutline() { int i; uint vsize=PATCH_SIZE+1; glBegin(GL_LINES); for (i=0;i<PATCH_SIZE;i++) { glVertex3fv(&m_Vertices[i].m_Position.X); glVertex3fv(&m_Vertices[i+1].m_Position.X); } glEnd(); glBegin(GL_LINES); for (i=0;i<PATCH_SIZE;i++) { glVertex3fv(&m_Vertices[PATCH_SIZE+(i*(PATCH_SIZE+1))].m_Position.X); glVertex3fv(&m_Vertices[PATCH_SIZE+((i+1)*(PATCH_SIZE+1))].m_Position.X); } glEnd(); glBegin(GL_LINES); for (i=1;i<PATCH_SIZE;i++) { glVertex3fv(&m_Vertices[(vsize*vsize)-i].m_Position.X); glVertex3fv(&m_Vertices[(vsize*vsize)-(i+1)].m_Position.X); } glEnd(); glBegin(GL_LINES); for (i=1;i<PATCH_SIZE;i++) { glVertex3fv(&m_Vertices[(vsize*(vsize-1))-(i*vsize)].m_Position.X); glVertex3fv(&m_Vertices[(vsize*(vsize-1))-((i+1)*vsize)].m_Position.X); } glEnd(); }