1
0
forked from 0ad/0ad

# Fix terrain blending logic.

Add terrain priority rendering mode in Atlas.
Fix terrain painting in Atlas to be less profligate with priority
values.
Support hotloading of blend textures.
Clean up some trivial things.

This was SVN commit r8872.
This commit is contained in:
Ykkrosh 2011-01-29 16:31:48 +00:00
parent d0bc4253c9
commit 03c75100ab
15 changed files with 494 additions and 266 deletions

View File

@ -269,7 +269,15 @@ function init(window, bottomWindow)
Atlas.Message.SetViewParamS(Atlas.RenderView.GAME, "passability", evt.string); Atlas.Message.SetViewParamS(Atlas.RenderView.GAME, "passability", evt.string);
}; };
visualiseSettingsSizer.add(new wxStaticText(window, -1, 'Priorities'), 0, wxAlignment.RIGHT);
var passabilityClasses = Atlas.Message.GetTerrainPassabilityClasses().classnames;
var priorityCheckbox = new wxCheckBox(window, -1, "");
visualiseSettingsSizer.add(priorityCheckbox);
priorityCheckbox.onCheckBox = function (evt) {
Atlas.Message.SetViewParamB(Atlas.RenderView.GAME, "priorities", evt.checked);
};
var terrainGroups = Atlas.Message.GetTerrainGroups(); var terrainGroups = Atlas.Message.GetTerrainGroups();
var nb = new wxNotebook(bottomWindow, -1); var nb = new wxNotebook(bottomWindow, -1);
bottomWindow.sizer = new wxBoxSizer(wxOrientation.VERTICAL); bottomWindow.sizer = new wxBoxSizer(wxOrientation.VERTICAL);

View File

@ -51,7 +51,7 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr props, const Vf
for (;it!=m_Groups.end();++it) for (;it!=m_Groups.end();++it)
(*it)->AddTerrain(this); (*it)->AddTerrain(this);
m_Tag = fs::basename(path); m_Tag = CStr(CStrW(fs::basename(path)));
} }
CTerrainTextureEntry::~CTerrainTextureEntry() CTerrainTextureEntry::~CTerrainTextureEntry()

View File

@ -40,7 +40,7 @@ public:
private: private:
// Tag = file name stripped of path and extension (grass_dark_1) // Tag = file name stripped of path and extension (grass_dark_1)
CStrW m_Tag; CStr m_Tag;
// The property sheet used by this texture // The property sheet used by this texture
CTerrainPropertiesPtr m_pProperties; CTerrainPropertiesPtr m_pProperties;

View File

@ -229,6 +229,8 @@ void Render()
ogl_WarnIfError(); ogl_WarnIfError();
g_Renderer.RenderTextOverlays();
// Temp GUI message GeeTODO // Temp GUI message GeeTODO
PROFILE_START("render gui"); PROFILE_START("render gui");
if(g_DoRenderGui) g_GUI->Draw(); if(g_DoRenderGui) g_GUI->Draw();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games. /* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -17,25 +17,28 @@
#include "precompiled.h" #include "precompiled.h"
#include <set> #include <set>
#include <algorithm> #include <algorithm>
#include "ps/Pyrogenesis.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.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 "graphics/Patch.h" #include "graphics/Patch.h"
#include "graphics/Terrain.h" #include "graphics/Terrain.h"
#include "lib/res/graphics/unifont.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/World.h"
#include "ps/GameSetup/Config.h"
#include "renderer/AlphaMapCalculator.h"
#include "renderer/PatchRData.h"
#include "renderer/Renderer.h"
#include "simulation2/Simulation2.h" #include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpRangeManager.h"
const ssize_t BlendOffsets[8][2] = { const ssize_t BlendOffsets[9][2] = {
{ 0, -1 }, { 0, -1 },
{ -1, -1 }, { -1, -1 },
{ -1, 0 }, { -1, 0 },
@ -43,13 +46,10 @@ const ssize_t BlendOffsets[8][2] = {
{ 0, 1 }, { 0, 1 },
{ 1, 1 }, { 1, 1 },
{ 1, 0 }, { 1, 0 },
{ 1, -1 } { 1, -1 },
{ 0, 0 }
}; };
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
// CPatchRData constructor // CPatchRData constructor
CPatchRData::CPatchRData(CPatch* patch) : m_Patch(patch), m_VBBase(0), m_VBBlends(0), m_Vertices(0) CPatchRData::CPatchRData(CPatch* patch) : m_Patch(patch), m_VBBase(0), m_VBBlends(0), m_Vertices(0)
@ -69,15 +69,6 @@ CPatchRData::~CPatchRData()
if (m_VBBlends) g_VBMan.Release(m_VBBlends); if (m_VBBlends) g_VBMan.Release(m_VBBlends);
} }
static CTerrainTextureEntry* GetTerrainTileTexture(CTerrain* terrain, ssize_t gx, ssize_t gz)
{
CMiniPatch* mp = terrain->GetTile(gx, gz);
if (!mp)
return 0;
return mp->GetTextureEntry();
}
const float uvFactor = 0.125f / sqrt(2.f); const float uvFactor = 0.125f / sqrt(2.f);
static void CalculateUV(float uv[2], ssize_t x, ssize_t z) static void CalculateUV(float uv[2], ssize_t x, ssize_t z)
{ {
@ -86,9 +77,60 @@ static void CalculateUV(float uv[2], ssize_t x, ssize_t z)
uv[1] = (-x-z)*uvFactor; uv[1] = (-x-z)*uvFactor;
} }
struct STmpSplat { /**
* Represents a blend for a single tile, texture and shape.
*/
struct STileBlend
{
CTerrainTextureEntry* m_Texture; CTerrainTextureEntry* m_Texture;
u16 m_Indices[4]; int m_Priority;
u16 m_TileMask; // bit n set if this blend contains neighbour tile BlendOffsets[n]
struct DecreasingPriority
{
bool operator()(const STileBlend& a, const STileBlend& b) const
{
if (a.m_Priority > b.m_Priority)
return true;
if (a.m_Priority < b.m_Priority)
return false;
if (a.m_Texture && b.m_Texture)
return a.m_Texture->GetTag() > b.m_Texture->GetTag();
return false;
}
};
struct CurrentTile
{
bool operator()(const STileBlend& a) const
{
return a.m_TileMask & (1 << 8);
}
};
};
/**
* Represents the ordered collection of blends drawn on a particular tile.
*/
struct STileBlendStack
{
u8 i, j;
std::vector<STileBlend> blends; // back of vector is lowest-priority texture
};
/**
* Represents a batched collection of blends using the same texture.
*/
struct SBlendLayer
{
struct Tile
{
u8 i, j;
u8 shape;
};
CTerrainTextureEntry* m_Texture;
std::vector<Tile> m_Tiles;
}; };
void CPatchRData::BuildBlends() void CPatchRData::BuildBlends()
@ -98,200 +140,260 @@ void CPatchRData::BuildBlends()
m_BlendVertices.clear(); m_BlendVertices.clear();
m_BlendVertexIndices.clear(); m_BlendVertexIndices.clear();
CTerrain* terrain=m_Patch->m_Parent; CTerrain* terrain = m_Patch->m_Parent;
// temporary list of splats std::vector<STileBlendStack> blendStacks;
std::vector<STmpSplat> splats; blendStacks.reserve(PATCH_SIZE*PATCH_SIZE);
// set of textures used for splats
std::set<CTerrainTextureEntry*> splatTextures;
// for each tile in patch .. // For each tile in patch ..
for (ssize_t j=0;j<PATCH_SIZE;j++) { for (ssize_t j = 0; j < PATCH_SIZE; ++j)
for (ssize_t i=0;i<PATCH_SIZE;i++) { {
CMiniPatch* mp=&m_Patch->m_MiniPatches[j][i]; for (ssize_t i = 0; i < PATCH_SIZE; ++i)
{
ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gx = m_Patch->m_X * PATCH_SIZE + i;
ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j;
// build list of textures of higher priority than current tile that are used by neighbouring tiles std::vector<STileBlend> blends;
std::vector<STex> neighbourTextures; blends.reserve(9);
for (int m=-1;m<=1;m++) {
for (int k=-1;k<=1;k++) { // Compute a blend for every tile in the 3x3 square around this tile
CMiniPatch* nmp=terrain->GetTile(gx+k,gz+m); for (size_t n = 0; n < 9; ++n)
if (nmp && nmp->GetTextureEntry() != mp->GetTextureEntry()) { {
if (nmp->GetPriority() > mp->GetPriority() || (nmp->GetPriority() == mp->GetPriority() && nmp->GetTextureEntry() > mp->GetTextureEntry())) { ssize_t ox = gx + BlendOffsets[n][1];
STex tex; ssize_t oz = gz + BlendOffsets[n][0];
tex.m_Texture=nmp->GetTextureEntry();
tex.m_Priority=nmp->GetPriority(); CMiniPatch* nmp = terrain->GetTile(ox, oz);
if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) { if (!nmp)
neighbourTextures.push_back(tex); continue;
}
} STileBlend blend;
} blend.m_Texture = nmp->GetTextureEntry();
} blend.m_Priority = nmp->GetPriority();
blend.m_TileMask = 1 << n;
blends.push_back(blend);
} }
if (neighbourTextures.size()>0) {
// sort textures from lowest to highest priority
std::sort(neighbourTextures.begin(),neighbourTextures.end());
// for each of the neighbouring textures .. // Sort the blends, highest priority first
size_t count=neighbourTextures.size(); std::sort(blends.begin(), blends.end(), STileBlend::DecreasingPriority());
for (size_t k=0;k<count;++k) {
// now build the grid of blends dependent on whether the tile adjacent to the current tile STileBlendStack blendStack;
// uses the current neighbour texture blendStack.i = i;
BlendShape8 shape; blendStack.j = j;
for (size_t m=0;m<8;m++) {
ssize_t ox=gx+BlendOffsets[m][1];
ssize_t oz=gz+BlendOffsets[m][0];
// get texture on adjacent tile // Put the blends into the tile's stack, merging any adjacent blends with the same texture
CTerrainTextureEntry* atex=GetTerrainTileTexture(terrain,ox,oz); for (size_t k = 0; k < blends.size(); ++k)
// fill 0/1 into shape array {
shape[m]=(atex==neighbourTextures[k].m_Texture) ? 0 : 1; if (!blendStack.blends.empty() && blendStack.blends.back().m_Texture == blends[k].m_Texture)
} blendStack.blends.back().m_TileMask |= blends[k].m_TileMask;
else
blendStack.blends.push_back(blends[k]);
}
// calculate the required alphamap and the required rotation of the alphamap from blendshape // Remove blends that are after (i.e. lower priority than) the current tile
unsigned int alphamapflags; // (including the current tile), since we don't want to render them on top of
int alphamap=CAlphaMapCalculator::Calculate(shape,alphamapflags); // the tile's base texture
blendStack.blends.erase(
std::find_if(blendStack.blends.begin(), blendStack.blends.end(), STileBlend::CurrentTile()),
blendStack.blends.end());
// now actually render the blend tile (if we need one) blendStacks.push_back(blendStack);
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) { // Given the blend stack per tile, we want to batch together as many blends as possible.
// flip v // Group them into a series of layers (each of which has a single texture):
float t=v0;
v0=v1;
v1=t;
}
int base=0; std::vector<SBlendLayer> blendLayers;
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]; while (true)
vtx[(base+0)%4].m_AlphaUVs[0]=u0; {
vtx[(base+0)%4].m_AlphaUVs[1]=v0; if (!blendLayers.empty())
vtx[(base+1)%4].m_AlphaUVs[0]=u1; {
vtx[(base+1)%4].m_AlphaUVs[1]=v0; // Try to grab as many tiles as possible that match our current layer,
vtx[(base+2)%4].m_AlphaUVs[0]=u1; // from off the blend stacks of all the tiles
vtx[(base+2)%4].m_AlphaUVs[1]=v1;
vtx[(base+3)%4].m_AlphaUVs[0]=u0;
vtx[(base+3)%4].m_AlphaUVs[1]=v1;
ssize_t vsize=PATCH_SIZE+1; CTerrainTextureEntry* tex = blendLayers.back().m_Texture;
SBlendVertex dst; for (size_t k = 0; k < blendStacks.size(); ++k)
const size_t vindex=m_BlendVertices.size(); {
if (!blendStacks[k].blends.empty() && blendStacks[k].blends.back().m_Texture == tex)
const SBaseVertex& vtx0=m_Vertices[(j*vsize)+i]; {
CalculateUV(dst.m_UVs, gx, gz); SBlendLayer::Tile t = { blendStacks[k].i, blendStacks[k].j, blendStacks[k].blends.back().m_TileMask };
dst.m_AlphaUVs[0]=vtx[0].m_AlphaUVs[0]; blendLayers.back().m_Tiles.push_back(t);
dst.m_AlphaUVs[1]=vtx[0].m_AlphaUVs[1]; blendStacks[k].blends.pop_back();
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_Texture;
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);
}
} }
// (We've already merged adjacent entries of the same texture in each stack,
// so we don't need to bother looping to check the next entry in this stack again)
}
}
// We've grabbed as many tiles as possible; now we need to start a new layer.
// The new layer's texture could come from the back of any non-empty stack;
// choose the longest stack as a heuristic to reduce the number of layers
CTerrainTextureEntry* bestTex = NULL;
size_t bestStackSize = 0;
for (size_t k = 0; k < blendStacks.size(); ++k)
{
if (blendStacks[k].blends.size() > bestStackSize)
{
bestStackSize = blendStacks[k].blends.size();
bestTex = blendStacks[k].blends.back().m_Texture;
}
}
// If all our stacks were empty, we're done
if (bestStackSize == 0)
break;
// Otherwise add the new layer, then loop back and start filling it in
SBlendLayer layer;
layer.m_Texture = bestTex;
blendLayers.push_back(layer);
}
// Now build outgoing splats
m_BlendSplats.resize(blendLayers.size());
for (size_t k = 0; k < blendLayers.size(); ++k)
{
SSplat& splat = m_BlendSplats[k];
splat.m_IndexStart = m_BlendIndices.size();
splat.m_Texture = blendLayers[k].m_Texture;
for (size_t t = 0; t < blendLayers[k].m_Tiles.size(); ++t)
{
SBlendLayer::Tile& tile = blendLayers[k].m_Tiles[t];
ssize_t index = AddBlend(tile.i, tile.j, tile.shape);
if (index != -1)
{
// (These indices will get incremented by the VB base offset later)
m_BlendIndices.push_back(index + 0);
m_BlendIndices.push_back(index + 1);
m_BlendIndices.push_back(index + 2);
m_BlendIndices.push_back(index + 3);
splat.m_IndexCount += 4;
} }
} }
} }
// build vertex data // Release existing vertex buffer chunk
if (m_VBBlends) { if (m_VBBlends)
// release existing vertex buffer chunk {
g_VBMan.Release(m_VBBlends); g_VBMan.Release(m_VBBlends);
m_VBBlends=0; 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 if (m_BlendVertices.size())
m_BlendSplats.resize(splatTextures.size()); {
size_t splatCount=0; // Construct vertex buffer
m_VBBlends = g_VBMan.Allocate(sizeof(SBlendVertex), m_BlendVertices.size(), true);
m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends, &m_BlendVertices[0]);
debug_assert(m_VBBlends->m_Index < 65536); debug_assert(m_VBBlends->m_Index < 65536);
unsigned short base = (unsigned short)m_VBBlends->m_Index; unsigned short base = (unsigned short)m_VBBlends->m_Index;
std::set<CTerrainTextureEntry*>::iterator iter=splatTextures.begin();
for (;iter!=splatTextures.end();++iter) {
CTerrainTextureEntry* tex=*iter;
SSplat& splat=m_BlendSplats[splatCount]; // Update the indices to include the base offset
splat.m_IndexStart=m_BlendIndices.size(); for (size_t k = 0; k < m_BlendIndices.size(); ++k)
splat.m_Texture=tex; m_BlendIndices[k] += base;
for (size_t k=0;k<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++;
}
} }
} }
ssize_t CPatchRData::AddBlend(u16 i, u16 j, u8 shape)
{
ssize_t gx = m_Patch->m_X * PATCH_SIZE + i;
ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j;
// uses the current neighbour texture
BlendShape8 shape8;
for (size_t m = 0; m < 8; ++m)
shape8[m] = (shape & (1 << m)) ? 0 : 1;
// calculate the required alphamap and the required rotation of the alphamap from blendshape
unsigned int alphamapflags;
int alphamap = CAlphaMapCalculator::Calculate(shape8, alphamapflags);
// now actually render the blend tile (if we need one)
if (alphamap == -1)
return -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)
std::swap(u0, u1);
if (alphamapflags & BLENDMAP_FLIPV)
std::swap(v0, v1);
int base = 0;
if (alphamapflags & BLENDMAP_ROTATE90)
base = 1;
else if (alphamapflags & BLENDMAP_ROTATE180)
base = 2;
else if (alphamapflags & BLENDMAP_ROTATE270)
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;
ssize_t vsize = PATCH_SIZE + 1;
SBlendVertex dst;
const size_t vindex = 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);
return vindex;
}
void CPatchRData::BuildIndices() void CPatchRData::BuildIndices()
{ {
// must have allocated some vertices before trying to build corresponding indices // must have allocated some vertices before trying to build corresponding indices
@ -559,7 +661,8 @@ void CPatchRData::RenderBlends()
{ {
debug_assert(m_UpdateFlags==0); debug_assert(m_UpdateFlags==0);
if (m_BlendVertices.size()==0) return; if (m_BlendVertices.empty())
return;
u8* base=m_VBBlends->m_Owner->Bind(); u8* base=m_VBBlends->m_Owner->Bind();
@ -629,3 +732,38 @@ void CPatchRData::RenderOutline()
} }
glEnd(); glEnd();
} }
void CPatchRData::RenderPriorities()
{
CTerrain* terrain = m_Patch->m_Parent;
CCamera* camera = g_Game->GetView()->GetCamera();
for (ssize_t j = 0; j < PATCH_SIZE; ++j)
{
for (ssize_t i = 0; i < PATCH_SIZE; ++i)
{
ssize_t gx = m_Patch->m_X * PATCH_SIZE + i;
ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j;
CVector3D pos;
terrain->CalcPosition(gx, gz, pos);
// Move a bit towards the center of the tile
pos.X += CELL_SIZE/4.f;
pos.Z += CELL_SIZE/4.f;
float x, y;
camera->GetScreenCoordinates(pos, x, y);
glPushMatrix();
glTranslatef(x, g_yres - y, 0.f);
// Draw the text upside-down, because it's aligned with
// the GUI (which uses the top-left as (0,0))
glScalef(1.0f, -1.0f, 1.0f);
glwprintf(L"%d", m_Patch->m_MiniPatches[j][i].Priority);
glPopMatrix();
}
}
}

View File

@ -41,6 +41,7 @@ public:
void RenderBlends(); void RenderBlends();
void RenderOutline(); void RenderOutline();
void RenderStreams(int streamflags, bool losColor); void RenderStreams(int streamflags, bool losColor);
void RenderPriorities();
private: private:
struct SSplat { struct SSplat {
@ -86,6 +87,8 @@ private:
// build this renderdata object // build this renderdata object
void Build(); void Build();
ssize_t AddBlend(u16 i, u16 j, u8 shape);
void BuildBlends(); void BuildBlends();
void BuildIndices(); void BuildIndices();
void BuildVertices(); void BuildVertices();

View File

@ -25,9 +25,13 @@
#include <map> #include <map>
#include <set> #include <set>
#include <algorithm> #include <algorithm>
#include <boost/algorithm/string.hpp>
#include "Renderer.h"
#include "lib/bits.h" // is_pow2 #include "lib/bits.h" // is_pow2
#include "lib/res/graphics/ogl_tex.h" #include "lib/res/graphics/ogl_tex.h"
#include "Renderer.h"
#include "maths/Matrix3D.h" #include "maths/Matrix3D.h"
#include "maths/MathUtil.h" #include "maths/MathUtil.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
@ -358,6 +362,7 @@ CRenderer::CRenderer()
m_SortAllTransparent = false; m_SortAllTransparent = false;
m_DisplayFrustum = false; m_DisplayFrustum = false;
m_DisableCopyShadow = false; m_DisableCopyShadow = false;
m_DisplayTerrainPriorities = false;
m_FastPlayerColor = true; m_FastPlayerColor = true;
m_SkipSubmit = false; m_SkipSubmit = false;
m_RenderTerritories = true; m_RenderTerritories = true;
@ -387,12 +392,16 @@ CRenderer::CRenderer()
AddLocalProperty(L"waterShininess", &m->waterManager.m_Shininess, false); AddLocalProperty(L"waterShininess", &m->waterManager.m_Shininess, false);
AddLocalProperty(L"waterSpecularStrength", &m->waterManager.m_SpecularStrength, false); AddLocalProperty(L"waterSpecularStrength", &m->waterManager.m_SpecularStrength, false);
AddLocalProperty(L"waterWaviness", &m->waterManager.m_Waviness, false); AddLocalProperty(L"waterWaviness", &m->waterManager.m_Waviness, false);
RegisterFileReloadFunc(ReloadChangedFileCB, this);
} }
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
// CRenderer destructor // CRenderer destructor
CRenderer::~CRenderer() CRenderer::~CRenderer()
{ {
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
// model rendering // model rendering
for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType) for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType)
{ {
@ -1302,7 +1311,12 @@ void CRenderer::RenderSubmissions()
m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera);
PROFILE_END("render fg overlays"); PROFILE_END("render fg overlays");
ogl_WarnIfError(); ogl_WarnIfError();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// EndFrame: signal frame end
void CRenderer::EndFrame()
{
// empty lists // empty lists
m->terrainRenderer->EndFrame(); m->terrainRenderer->EndFrame();
m->overlayRenderer.EndFrame(); m->overlayRenderer.EndFrame();
@ -1315,18 +1329,12 @@ void CRenderer::RenderSubmissions()
if (m->Model.Player != m->Model.PlayerInstancing) if (m->Model.Player != m->Model.PlayerInstancing)
m->Model.PlayerInstancing->EndFrame(); m->Model.PlayerInstancing->EndFrame();
m->Model.Transp->EndFrame(); m->Model.Transp->EndFrame();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// EndFrame: signal frame end
void CRenderer::EndFrame()
{
ogl_tex_bind(0, 0); ogl_tex_bind(0, 0);
static bool once=false; if (glGetError())
if (!once && glGetError()) { {
LOGERROR(L"CRenderer::EndFrame: GL errors occurred"); ONCE(LOGERROR(L"CRenderer::EndFrame: GL errors occurred"));
once=true;
} }
} }
@ -1355,6 +1363,18 @@ void CRenderer::DisplayFrustum()
glDepthMask(1); glDepthMask(1);
} }
///////////////////////////////////////////////////////////////////////////////////////////////////
// Text overlay rendering
void CRenderer::RenderTextOverlays()
{
PROFILE("render text overlays");
if (m_DisplayTerrainPriorities)
m->terrainRenderer->RenderPriorities();
ogl_WarnIfError();
}
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
// SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view
// The camera always represents the actual camera used to render a scene, not any virtual camera // The camera always represents the actual camera used to render a scene, not any virtual camera
@ -1479,22 +1499,22 @@ int CRenderer::LoadAlphaMaps()
// load all textures and store Handle in array // load all textures and store Handle in array
// //
Handle textures[NumAlphaMaps] = {0}; Handle textures[NumAlphaMaps] = {0};
VfsPath path(L"art/textures/terrain/alphamaps/special"); VfsPath path(L"art/textures/terrain/alphamaps/standard");
const wchar_t* fnames[NumAlphaMaps] = { const wchar_t* fnames[NumAlphaMaps] = {
L"blendcircle.dds", L"blendcircle.png",
L"blendlshape.dds", L"blendlshape.png",
L"blendedge.dds", L"blendedge.png",
L"blendedgecorner.dds", L"blendedgecorner.png",
L"blendedgetwocorners.dds", L"blendedgetwocorners.png",
L"blendfourcorners.dds", L"blendfourcorners.png",
L"blendtwooppositecorners.dds", L"blendtwooppositecorners.png",
L"blendlshapecorner.dds", L"blendlshapecorner.png",
L"blendtwocorners.dds", L"blendtwocorners.png",
L"blendcorner.dds", L"blendcorner.png",
L"blendtwoedges.dds", L"blendtwoedges.png",
L"blendthreecorners.dds", L"blendthreecorners.png",
L"blendushape.dds", L"blendushape.png",
L"blendbad.dds" L"blendbad.png"
}; };
size_t base = 0; // texture width/height (see below) size_t base = 0; // texture width/height (see below)
// for convenience, we require all alpha maps to be of the same BPP // for convenience, we require all alpha maps to be of the same BPP
@ -1507,13 +1527,6 @@ int CRenderer::LoadAlphaMaps()
textures[i] = ogl_tex_load(g_VFS, path/fnames[i]); textures[i] = ogl_tex_load(g_VFS, path/fnames[i]);
RETURN_ERR(textures[i]); RETURN_ERR(textures[i]);
// quick hack: we require plain RGB(A) format, so convert to that.
// ideally the texture would be in uncompressed form; then this wouldn't
// be necessary.
size_t flags;
ogl_tex_get_format(textures[i], &flags, 0);
ogl_tex_transform_to(textures[i], flags & ~TEX_DXT);
// get its size and make sure they are all equal. // get its size and make sure they are all equal.
// (the packing algo assumes this) // (the packing algo assumes this)
size_t this_width = 0, this_bpp = 0; // fail-safe size_t this_width = 0, this_bpp = 0; // fail-safe
@ -1602,6 +1615,23 @@ void CRenderer::UnloadAlphaMaps()
LibError CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path)
{
CRenderer* renderer = static_cast<CRenderer*>(param);
// If an alpha map changed, and we already loaded them, then reload them
if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/"))
{
if (renderer->m_hCompositeAlphaMap)
{
renderer->UnloadAlphaMaps();
renderer->LoadAlphaMaps();
}
}
return INFO::OK;
}
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
// Scripting Interface // Scripting Interface

View File

@ -216,20 +216,12 @@ public:
*/ */
void RenderScene(Scene* scene); void RenderScene(Scene* scene);
// basic primitive rendering operations in 2 and 3D; handy for debugging stuff, but also useful in /**
// editor tools (eg for highlighting specific terrain patches) * Render text overlays on top of the scene.
// note: * Assumes the caller has set up the GL environment for orthographic rendering
// * all 3D vertices specified in world space * with texturing and blending.
// * primitive operations rendered immediatedly, never batched */
// * primitives rendered in current material (set via SetMaterial) void RenderTextOverlays();
// void RenderLine(const SVertex2D* vertices);
// void RenderLineLoop(int len,const SVertex2D* vertices);
// void RenderTri(const SVertex2D* vertices);
// void RenderQuad(const SVertex2D* vertices);
// void RenderLine(const SVertex3D* vertices);
// void RenderLineLoop(int len,const SVertex3D* vertices);
// void RenderTri(const SVertex3D* vertices);
// void RenderQuad(const SVertex3D* vertices);
// set the current lighting environment; (note: the passed pointer is just copied to a variable within the renderer, // set the current lighting environment; (note: the passed pointer is just copied to a variable within the renderer,
// so the lightenv passed must be scoped such that it is not destructed until after the renderer is no longer rendering) // so the lightenv passed must be scoped such that it is not destructed until after the renderer is no longer rendering)
@ -247,6 +239,9 @@ public:
// get the mode to render subsequent models // get the mode to render subsequent models
ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; } ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; }
// debugging
void SetDisplayTerrainPriorities(bool enabled) { m_DisplayTerrainPriorities = enabled; }
// bind a GL texture object to active unit // bind a GL texture object to active unit
void BindTexture(int unit,GLuint tex); void BindTexture(int unit,GLuint tex);
@ -365,6 +360,9 @@ protected:
// enable oblique frustum clipping with the given clip plane // enable oblique frustum clipping with the given clip plane
void SetObliqueFrustumClipping(const CVector4D& clipPlane, int sign); void SetObliqueFrustumClipping(const CVector4D& clipPlane, int sign);
// hotloading
static LibError ReloadChangedFileCB(void* param, const VfsPath& path);
// RENDERER DATA: // RENDERER DATA:
/// Private data that is not needed by inline functions /// Private data that is not needed by inline functions
CRendererInternals* m; CRendererInternals* m;
@ -453,6 +451,11 @@ protected:
*/ */
bool m_DisableCopyShadow; bool m_DisableCopyShadow;
/**
* Enable rendering of terrain tile priority text overlay, for debugging.
*/
bool m_DisplayTerrainPriorities;
public: public:
/** /**
* m_ShadowZBias: Z bias used when rendering shadows into a depth texture. * m_ShadowZBias: Z bias used when rendering shadows into a depth texture.

View File

@ -124,7 +124,6 @@ protected:
* Draw an outlined quad on top of the given tile. * Draw an outlined quad on top of the given tile.
*/ */
void RenderTileOutline(const CColor& colour, int line_width, bool draw_hidden, ssize_t i, ssize_t j); void RenderTileOutline(const CColor& colour, int line_width, bool draw_hidden, ssize_t i, ssize_t j);
public: public:
/** /**
@ -132,14 +131,6 @@ public:
*/ */
static void RenderOverlays(); static void RenderOverlays();
#if 0
/**
* Kai: added function to draw out line segments for triangulation
*/
static void RenderEntityEdges();
#endif
private: private:
/// Copying not allowed. /// Copying not allowed.
TerrainOverlay(const TerrainOverlay&); TerrainOverlay(const TerrainOverlay&);

View File

@ -32,6 +32,7 @@
#include "ps/Filesystem.h" #include "ps/Filesystem.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
#include "ps/Font.h"
#include "ps/Game.h" #include "ps/Game.h"
#include "ps/Profile.h" #include "ps/Profile.h"
#include "ps/World.h" #include "ps/World.h"
@ -655,3 +656,19 @@ void TerrainRenderer::RenderWater()
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
} }
void TerrainRenderer::RenderPriorities()
{
PROFILE("render priorities");
CFont font(L"mono-stroke-10");
font.Bind();
glColor3f(1, 1, 0);
for (size_t i = 0; i < m->visiblePatches.size(); ++i)
{
CPatchRData* patchdata = (CPatchRData*)m->visiblePatches[i]->GetRenderData();
patchdata->RenderPriorities();
}
}

View File

@ -111,6 +111,11 @@ public:
*/ */
void RenderWater(); void RenderWater();
/**
* Render priority text for all submitted patches, for debugging.
*/
void RenderPriorities();
private: private:
TerrainRendererInternals* m; TerrainRendererInternals* m;
}; };

View File

@ -39,8 +39,10 @@ struct Brush
float Get(ssize_t x, ssize_t y) const float Get(ssize_t x, ssize_t y) const
{ {
debug_assert(x >= 0 && x < m_W && y >= 0 && y < m_H); if (x >= 0 && x < m_W && y >= 0 && y < m_H)
return m_Data[x + y*m_W]; return m_Data[x + y*m_W];
else
return 0.f;
} }
ssize_t m_W, m_H; ssize_t m_W, m_H;

View File

@ -153,25 +153,31 @@ BEGIN_COMMAND(PaintTerrain)
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
} }
void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority)
{
// Ignore out-of-bounds tiles
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return;
CMiniPatch* tile = m_Terrain->GetTile(x, y);
if (!tile)
return;
// If this tile matches the current texture, we just want to match its
// priority; otherwise we want to exceed its priority
if (tile->GetTextureEntry() == tex)
priority = std::max(priority, tile->GetPriority()*priorityScale);
else
priority = std::max(priority, tile->GetPriority()*priorityScale + 1);
}
void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority) void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority)
{ {
// Ignore out-of-bounds tiles // Ignore out-of-bounds tiles
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1)) if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return; return;
// Priority system: If the new tile should have a high priority, set(x,y, TerrainTile(tex, priority));
// set it to one plus the maximum priority of all surrounding tiles
// (so that it's definitely the highest).
// Similar for low priority.
ssize_t greatest = 0;
ssize_t scale = (priority == ePaintTerrainPriority::HIGH ? +1 : -1);
CMiniPatch* tile;
#define TILE(dx, dy) tile = m_Terrain->GetTile(x dx, y dy); if (tile && tile->GetPriority()*scale > greatest) greatest = tile->GetPriority()*scale;
TILE(-1, -1) TILE(+0, -1) TILE(+1, -1)
TILE(-1, +0) TILE(+1, +0)
TILE(-1, +1) TILE(+0, +1) TILE(+1, +1)
#undef TILE
set(x,y, TerrainTile(tex, (greatest+1)*scale));
} }
protected: protected:
@ -223,12 +229,28 @@ BEGIN_COMMAND(PaintTerrain)
return; return;
} }
// Priority system: If the new tile should have a high priority,
// set it to one plus the maximum priority of all surrounding tiles
// that aren't included in the brush (so that it's definitely the highest).
// Similar for low priority.
ssize_t priorityScale = (msg->priority == ePaintTerrainPriority::HIGH ? +1 : -1);
ssize_t priority = 0;
for (ssize_t dy = -1; dy < g_CurrentBrush.m_H+1; ++dy)
{
for (ssize_t dx = -1; dx < g_CurrentBrush.m_W+1; ++dx)
{
if (!(g_CurrentBrush.Get(dx, dy) > 0.5f)) // ignore tiles that will be painted over
m_TerrainDelta.UpdatePriority(x0+dx, y0+dy, texentry, priorityScale, priority);
}
}
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy) for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{ {
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx) for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{ {
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, msg->priority); m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, priority*priorityScale);
} }
} }

View File

@ -240,6 +240,12 @@ void ViewGame::Render()
Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas); Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas);
} }
void ViewGame::SetParam(const std::wstring& name, bool value)
{
if (name == L"priorities")
g_Renderer.SetDisplayTerrainPriorities(value);
}
void ViewGame::SetParam(const std::wstring& name, const std::wstring& value) void ViewGame::SetParam(const std::wstring& name, const std::wstring& value)
{ {
if (name == L"passability") if (name == L"passability")

View File

@ -79,6 +79,7 @@ public:
virtual CSimulation2* GetSimulation2(); virtual CSimulation2* GetSimulation2();
virtual bool WantsHighFramerate(); virtual bool WantsHighFramerate();
virtual void SetParam(const std::wstring& name, bool value);
virtual void SetParam(const std::wstring& name, const std::wstring& value); virtual void SetParam(const std::wstring& name, const std::wstring& value);
void SetSpeedMultiplier(float speed); void SetSpeedMultiplier(float speed);