/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "DecalRData.h"
#include "graphics/Decal.h"
#include "graphics/LightEnv.h"
#include "graphics/Model.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/TerrainRenderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpWaterManager.h"
// TODO: Currently each decal is a separate CDecalRData. We might want to use
// lots of decals for special effects like shadows, footprints, etc, in which
// case we should probably redesign this to batch them all together for more
// efficient rendering.
CDecalRData::CDecalRData(CModelDecal* decal, CSimulation2* simulation)
: m_Decal(decal), m_IndexArray(GL_STATIC_DRAW), m_Array(GL_STATIC_DRAW), m_Simulation(simulation)
{
m_Position.type = GL_FLOAT;
m_Position.elems = 3;
m_Array.AddAttribute(&m_Position);
m_Normal.type = GL_FLOAT;
m_Normal.elems = 3;
m_Array.AddAttribute(&m_Normal);
m_DiffuseColor.type = GL_UNSIGNED_BYTE;
m_DiffuseColor.elems = 4;
m_Array.AddAttribute(&m_DiffuseColor);
m_UV.type = GL_FLOAT;
m_UV.elems = 2;
m_Array.AddAttribute(&m_UV);
BuildArrays();
}
CDecalRData::~CDecalRData()
{
}
void CDecalRData::Update(CSimulation2* simulation)
{
m_Simulation = simulation;
if (m_UpdateFlags != 0)
{
BuildArrays();
m_UpdateFlags = 0;
}
}
void CDecalRData::RenderDecals(std::vector& decals, const CShaderDefines& context,
ShadowMap* shadow, bool isDummyShader, const CShaderProgramPtr& dummy)
{
CShaderDefines contextDecal = context;
contextDecal.Add(str_DECAL, str_1);
for (size_t i = 0; i < decals.size(); ++i)
{
CDecalRData *decal = decals[i];
CMaterial &material = decal->m_Decal->m_Decal.m_Material;
if (material.GetShaderEffect().length() == 0)
{
LOGERROR("Terrain renderer failed to load shader effect.\n");
continue;
}
int numPasses = 1;
CShaderTechniquePtr techBase;
if (!isDummyShader)
{
techBase = g_Renderer.GetShaderManager().LoadEffect(
material.GetShaderEffect(), contextDecal, material.GetShaderDefines(0));
if (!techBase)
{
LOGERROR("Terrain renderer failed to load shader effect (%s)\n",
material.GetShaderEffect().string().c_str());
continue;
}
numPasses = techBase->GetNumPasses();
}
for (int pass = 0; pass < numPasses; ++pass)
{
if (!isDummyShader)
{
techBase->BeginPass(pass);
TerrainRenderer::PrepareShader(techBase->GetShader(), shadow);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
const CShaderProgramPtr& shader = isDummyShader ? dummy : techBase->GetShader(pass);
if (material.GetSamplers().size() != 0)
{
const CMaterial::SamplersVector& samplers = material.GetSamplers();
size_t samplersNum = samplers.size();
for (size_t s = 0; s < samplersNum; ++s)
{
const CMaterial::TextureSampler& samp = samplers[s];
shader->BindTexture(samp.Name, samp.Sampler);
}
material.GetStaticUniforms().BindUniforms(shader);
// TODO: Need to handle floating decals correctly. In particular, we need
// to render non-floating before water and floating after water (to get
// the blending right), and we also need to apply the correct lighting in
// each case, which doesn't really seem possible with the current
// TerrainRenderer.
// Also, need to mark the decals as dirty when water height changes.
// glDisable(GL_TEXTURE_2D);
// m_Decal->GetBounds().Render();
// glEnable(GL_TEXTURE_2D);
u8* base = decal->m_Array.Bind();
GLsizei stride = (GLsizei)decal->m_Array.GetStride();
u8* indexBase = decal->m_IndexArray.Bind();
#if !CONFIG2_GLES
if (isDummyShader)
{
glColor3fv(decal->m_Decal->GetShadingColor().FloatArray());
}
else
#endif
{
shader->Uniform(str_shadingColor, decal->m_Decal->GetShadingColor());
}
shader->VertexPointer(3, GL_FLOAT, stride, base + decal->m_Position.offset);
shader->NormalPointer(GL_FLOAT, stride, base + decal->m_Normal.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + decal->m_DiffuseColor.offset);
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, stride, base + decal->m_UV.offset);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
{
glDrawElements(GL_TRIANGLES, (GLsizei)decal->m_IndexArray.GetNumVertices(), GL_UNSIGNED_SHORT, indexBase);
}
// bump stats
g_Renderer.m_Stats.m_DrawCalls++;
g_Renderer.m_Stats.m_TerrainTris += decal->m_IndexArray.GetNumVertices() / 3;
CVertexBuffer::Unbind();
}
if (!isDummyShader)
{
glDisable(GL_BLEND);
techBase->EndPass();
}
}
}
}
void CDecalRData::BuildArrays()
{
PROFILE("decal build");
const SDecal& decal = m_Decal->m_Decal;
// TODO: Currently this constructs an axis-aligned bounding rectangle around
// the decal. It would be more efficient for rendering if we excluded tiles
// that are outside the (non-axis-aligned) decal rectangle.
ssize_t i0, j0, i1, j1;
m_Decal->CalcVertexExtents(i0, j0, i1, j1);
// Construct vertex data arrays
CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY);
m_Array.SetNumVertices((i1-i0+1)*(j1-j0+1));
m_Array.Layout();
VertexArrayIterator Position = m_Position.GetIterator();
VertexArrayIterator Normal = m_Normal.GetIterator();
VertexArrayIterator DiffuseColor = m_DiffuseColor.GetIterator();
VertexArrayIterator UV = m_UV.GetIterator();
const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
bool cpuLighting = (g_Renderer.GetRenderPath() == CRenderer::RP_FIXED);
for (ssize_t j = j0; j <= j1; ++j)
{
for (ssize_t i = i0; i <= i1; ++i)
{
CVector3D pos;
m_Decal->m_Terrain->CalcPosition(i, j, pos);
if (decal.m_Floating && cmpWaterManager)
pos.Y = std::max(pos.Y, cmpWaterManager->GetExactWaterLevel(pos.X, pos.Z));
*Position = pos;
++Position;
CVector3D normal;
m_Decal->m_Terrain->CalcNormal(i, j, normal);
*Normal = normal;
Normal++;
*DiffuseColor = cpuLighting ? lightEnv.EvaluateTerrainDiffuseScaled(normal) : lightEnv.EvaluateTerrainDiffuseFactor(normal);
++DiffuseColor;
// Map from world space back into decal texture space
CVector3D inv = m_Decal->GetInvTransform().Transform(pos);
(*UV)[0] = 0.5f + (inv.X - decal.m_OffsetX) / decal.m_SizeX;
(*UV)[1] = 0.5f - (inv.Z - decal.m_OffsetZ) / decal.m_SizeZ; // flip V to match our texture convention
++UV;
}
}
m_Array.Upload();
m_Array.FreeBackingStore();
// Construct index arrays for each terrain tile
m_IndexArray.SetNumVertices((i1-i0)*(j1-j0)*6);
m_IndexArray.Layout();
VertexArrayIterator Index = m_IndexArray.GetIterator();
u16 base = 0;
ssize_t w = i1-i0+1;
for (ssize_t dj = 0; dj < j1-j0; ++dj)
{
for (ssize_t di = 0; di < i1-i0; ++di)
{
bool dir = m_Decal->m_Terrain->GetTriangulationDir(i0+di, j0+dj);
if (dir)
{
*Index++ = u16(((dj+0)*w+(di+0))+base);
*Index++ = u16(((dj+0)*w+(di+1))+base);
*Index++ = u16(((dj+1)*w+(di+0))+base);
*Index++ = u16(((dj+0)*w+(di+1))+base);
*Index++ = u16(((dj+1)*w+(di+1))+base);
*Index++ = u16(((dj+1)*w+(di+0))+base);
}
else
{
*Index++ = u16(((dj+0)*w+(di+0))+base);
*Index++ = u16(((dj+0)*w+(di+1))+base);
*Index++ = u16(((dj+1)*w+(di+1))+base);
*Index++ = u16(((dj+1)*w+(di+1))+base);
*Index++ = u16(((dj+1)*w+(di+0))+base);
*Index++ = u16(((dj+0)*w+(di+0))+base);
}
}
}
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
}