1516 lines
42 KiB
C++
1516 lines
42 KiB
C++
#include "verus.h"
|
|
|
|
using namespace verus;
|
|
using namespace verus::Scene;
|
|
|
|
CGI::ShaderPwns<Terrain::SHADER_COUNT> Terrain::s_shader;
|
|
Terrain::UB_TerrainVS Terrain::s_ubTerrainVS;
|
|
Terrain::UB_TerrainFS Terrain::s_ubTerrainFS;
|
|
Terrain::UB_SimpleTerrainVS Terrain::s_ubSimpleTerrainVS;
|
|
Terrain::UB_SimpleTerrainFS Terrain::s_ubSimpleTerrainFS;
|
|
|
|
// TerrainPhysics:
|
|
|
|
TerrainPhysics::TerrainPhysics()
|
|
{
|
|
}
|
|
|
|
TerrainPhysics::~TerrainPhysics()
|
|
{
|
|
Done();
|
|
}
|
|
|
|
void TerrainPhysics::Init(Physics::PUserPtr p, int w, int h, const void* pData, float heightScale)
|
|
{
|
|
VERUS_QREF_BULLET;
|
|
|
|
_pShape = new btHeightfieldTerrainShape(
|
|
w,
|
|
h,
|
|
pData,
|
|
heightScale,
|
|
-SHRT_MAX * heightScale,
|
|
SHRT_MAX * heightScale,
|
|
1,
|
|
PHY_SHORT,
|
|
false);
|
|
|
|
_pShape->setLocalScaling(btVector3(1, 1, 1));
|
|
|
|
btTransform tr;
|
|
tr.setIdentity();
|
|
tr.setOrigin(btVector3(-0.5f, 0, -0.5f));
|
|
_pRigidBody = bullet.AddNewRigidBody(0, tr, _pShape, +Physics::Group::terrain);
|
|
_pRigidBody->setFriction(Physics::Bullet::GetFriction(Physics::Material::wood));
|
|
_pRigidBody->setRestitution(Physics::Bullet::GetRestitution(Physics::Material::wood));
|
|
_pRigidBody->setUserPointer(p);
|
|
|
|
EnableDebugDraw(false);
|
|
}
|
|
|
|
void TerrainPhysics::Done()
|
|
{
|
|
if (_pRigidBody)
|
|
{
|
|
VERUS_QREF_BULLET;
|
|
bullet.GetWorld()->removeRigidBody(_pRigidBody);
|
|
delete _pRigidBody->getMotionState();
|
|
delete _pRigidBody;
|
|
_pRigidBody = nullptr;
|
|
}
|
|
VERUS_SMART_DELETE(_pShape);
|
|
}
|
|
|
|
void TerrainPhysics::EnableDebugDraw(bool b)
|
|
{
|
|
_debugDraw = b;
|
|
if (_debugDraw)
|
|
{
|
|
const int f = _pRigidBody->getCollisionFlags();
|
|
_pRigidBody->setCollisionFlags(f & ~btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT);
|
|
}
|
|
else
|
|
{
|
|
const int f = _pRigidBody->getCollisionFlags();
|
|
_pRigidBody->setCollisionFlags(f | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT);
|
|
}
|
|
}
|
|
|
|
// TerrainLOD:
|
|
|
|
void TerrainLOD::Init(int sidePoly, int step, bool addEdgeTess)
|
|
{
|
|
_side = sidePoly + 1;
|
|
_vertCount = _side * _side;
|
|
if (1 == step)
|
|
Math::CreateListGrid(sidePoly, sidePoly, _vIB);
|
|
else
|
|
Math::CreateStripGrid(sidePoly, sidePoly, _vIB);
|
|
_firstIndex = 0;
|
|
|
|
if (addEdgeTess)
|
|
{
|
|
const int extraVerts = sidePoly * 4;
|
|
_vIB.reserve(_vIB.capacity() + extraVerts * 4);
|
|
_vIB.push_back(0xFFFF);
|
|
auto ComputeIndex = [this](int i, int j)
|
|
{
|
|
return i * _side + j;
|
|
};
|
|
|
|
// Bottom, forwards:
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
_vIB.push_back(ComputeIndex(sidePoly, i));
|
|
_vIB.push_back(_vertCount + i);
|
|
_vIB.push_back(ComputeIndex(sidePoly, i + 1));
|
|
_vIB.push_back(0xFFFF);
|
|
}
|
|
// Right, backwards:
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
_vIB.push_back(ComputeIndex(sidePoly - i, sidePoly));
|
|
_vIB.push_back(_vertCount + sidePoly + i);
|
|
_vIB.push_back(ComputeIndex(sidePoly - i - 1, sidePoly));
|
|
_vIB.push_back(0xFFFF);
|
|
}
|
|
// Top, backwards:
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
_vIB.push_back(ComputeIndex(0, sidePoly - i));
|
|
_vIB.push_back(_vertCount + sidePoly * 2 + i);
|
|
_vIB.push_back(ComputeIndex(0, sidePoly - i - 1));
|
|
_vIB.push_back(0xFFFF);
|
|
}
|
|
// Left, forwards:
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
_vIB.push_back(ComputeIndex(i, 0));
|
|
_vIB.push_back(_vertCount + sidePoly * 3 + i);
|
|
_vIB.push_back(ComputeIndex(i + 1, 0));
|
|
if (_vIB.size() < _vIB.capacity())
|
|
_vIB.push_back(0xFFFF);
|
|
}
|
|
|
|
_vertCount += extraVerts;
|
|
VERUS_RT_ASSERT(Math::CheckIndexBuffer(_vIB, _vertCount - 1));
|
|
}
|
|
_indexCount = Utils::Cast32(_vIB.size());
|
|
}
|
|
|
|
void TerrainLOD::InitGeo(short* pV, UINT16* pI, int vertexOffset, bool addEdgeTess)
|
|
{
|
|
const int edge = _side - 1;
|
|
const int step = 16 / edge;
|
|
VERUS_FOR(i, _side)
|
|
{
|
|
const int offset = i * _side;
|
|
VERUS_FOR(j, _side)
|
|
{
|
|
// X and Z:
|
|
pV[((offset + j) << 2) + 0] = j * step;
|
|
pV[((offset + j) << 2) + 2] = i * step;
|
|
|
|
// Y and W, edge correction:
|
|
if (i == 0) pV[((offset + j) << 2) + 3] = 1;
|
|
else if (i == edge) pV[((offset + j) << 2) + 3] = -1;
|
|
else pV[((offset + j) << 2) + 3] = 0;
|
|
if (j == 0) pV[((offset + j) << 2) + 1] = 1;
|
|
else if (j == edge) pV[((offset + j) << 2) + 1] = -1;
|
|
else pV[((offset + j) << 2) + 1] = 0;
|
|
}
|
|
}
|
|
VERUS_FOR(i, _vIB.size())
|
|
{
|
|
if (_vIB[i] != 0xFFFF)
|
|
_vIB[i] += vertexOffset;
|
|
}
|
|
memcpy(pI, _vIB.data(), _indexCount * sizeof(UINT16));
|
|
VERUS_FOR(i, _vIB.size())
|
|
{
|
|
if (_vIB[i] != 0xFFFF)
|
|
_vIB[i] -= vertexOffset;
|
|
}
|
|
|
|
if (addEdgeTess)
|
|
{
|
|
const int halfStep = step / 2;
|
|
const int vertCount = _side * _side;
|
|
const int sidePoly = _side - 1;
|
|
const short minPos = 0;
|
|
const short maxPos = 16;
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
pV[((vertCount + i) << 2) + 0] = halfStep + i * step;
|
|
pV[((vertCount + i) << 2) + 2] = maxPos;
|
|
pV[((vertCount + i) << 2) + 3] = -1;
|
|
}
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
pV[((vertCount + sidePoly + i) << 2) + 0] = maxPos;
|
|
pV[((vertCount + sidePoly + i) << 2) + 2] = halfStep + (sidePoly - i - 1) * step;
|
|
pV[((vertCount + sidePoly + i) << 2) + 1] = -1;
|
|
}
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
pV[((vertCount + sidePoly * 2 + i) << 2) + 0] = halfStep + (sidePoly - i - 1) * step;
|
|
pV[((vertCount + sidePoly * 2 + i) << 2) + 2] = minPos;
|
|
pV[((vertCount + sidePoly * 2 + i) << 2) + 3] = 1;
|
|
}
|
|
VERUS_FOR(i, sidePoly)
|
|
{
|
|
pV[((vertCount + sidePoly * 3 + i) << 2) + 0] = minPos;
|
|
pV[((vertCount + sidePoly * 3 + i) << 2) + 2] = halfStep + i * step;
|
|
pV[((vertCount + sidePoly * 3 + i) << 2) + 1] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TerrainPatch:
|
|
|
|
TerrainPatch::TerrainPatch()
|
|
{
|
|
VERUS_ZERO_MEM(_ijCoord);
|
|
VERUS_ZERO_MEM(_layerForChannel);
|
|
VERUS_ZERO_MEM(_height);
|
|
VERUS_ZERO_MEM(_mainLayer);
|
|
}
|
|
|
|
void TerrainPatch::BindTBN(PTBN p)
|
|
{
|
|
_pTBN = p;
|
|
}
|
|
|
|
void TerrainPatch::InitFlat(short height, int mainLayer)
|
|
{
|
|
char tan[3] = { 127, 0, 0 };
|
|
char bin[3] = { 0, 0, 127 };
|
|
char nrm[3] = { 0, 127, 0 };
|
|
|
|
VERUS_FOR(i, 16 * 16)
|
|
{
|
|
_height[i] = height;
|
|
_mainLayer[i] = mainLayer;
|
|
memcpy(&_pTBN[0]._normals0[i][0], tan, 3);
|
|
memcpy(&_pTBN[1]._normals0[i][0], bin, 3);
|
|
memcpy(&_pTBN[2]._normals0[i][0], nrm, 3);
|
|
}
|
|
VERUS_FOR(i, 8 * 8)
|
|
{
|
|
memcpy(&_pTBN[0]._normals1[i][0], tan, 3);
|
|
memcpy(&_pTBN[1]._normals1[i][0], bin, 3);
|
|
memcpy(&_pTBN[2]._normals1[i][0], nrm, 3);
|
|
}
|
|
VERUS_FOR(i, 4 * 4)
|
|
{
|
|
memcpy(&_pTBN[0]._normals2[i][0], tan, 3);
|
|
memcpy(&_pTBN[1]._normals2[i][0], bin, 3);
|
|
memcpy(&_pTBN[2]._normals2[i][0], nrm, 3);
|
|
}
|
|
VERUS_FOR(i, 2 * 2)
|
|
{
|
|
memcpy(&_pTBN[0]._normals3[i][0], tan, 3);
|
|
memcpy(&_pTBN[1]._normals3[i][0], bin, 3);
|
|
memcpy(&_pTBN[2]._normals3[i][0], nrm, 3);
|
|
}
|
|
VERUS_FOR(i, 1 * 1)
|
|
{
|
|
memcpy(&_pTBN[0]._normals4[i][0], tan, 3);
|
|
memcpy(&_pTBN[1]._normals4[i][0], bin, 3);
|
|
memcpy(&_pTBN[2]._normals4[i][0], nrm, 3);
|
|
}
|
|
}
|
|
|
|
void TerrainPatch::UpdateNormals(PTerrain p, int lod)
|
|
{
|
|
if (!lod) // First LOD, full normal map calculation:
|
|
{
|
|
VERUS_FOR(i, 16)
|
|
{
|
|
VERUS_FOR(j, 16)
|
|
{
|
|
const int ijTL[] = { i + _ijCoord[0] - 1, j + _ijCoord[1] - 1 };
|
|
const int ijL[] = { i + _ijCoord[0] + 0, j + _ijCoord[1] - 1 };
|
|
const int ijBL[] = { i + _ijCoord[0] + 1, j + _ijCoord[1] - 1 };
|
|
const int ijT[] = { i + _ijCoord[0] - 1, j + _ijCoord[1] + 0 };
|
|
const int ijB[] = { i + _ijCoord[0] + 1, j + _ijCoord[1] + 0 };
|
|
const int ijTR[] = { i + _ijCoord[0] - 1, j + _ijCoord[1] + 1 };
|
|
const int ijR[] = { i + _ijCoord[0] + 0, j + _ijCoord[1] + 1 };
|
|
const int ijBR[] = { i + _ijCoord[0] + 1, j + _ijCoord[1] + 1 };
|
|
|
|
const float dampF = 0.125f; // 1/8 gives correct normal in 1 meter case.
|
|
|
|
const float tl = dampF * p->GetHeightAt(ijTL);
|
|
const float l = dampF * p->GetHeightAt(ijL);
|
|
const float bl = dampF * p->GetHeightAt(ijBL);
|
|
const float t = dampF * p->GetHeightAt(ijT);
|
|
const float b = dampF * p->GetHeightAt(ijB);
|
|
const float tr = dampF * p->GetHeightAt(ijTR);
|
|
const float r = dampF * p->GetHeightAt(ijR);
|
|
const float br = dampF * p->GetHeightAt(ijBR);
|
|
|
|
const float dX = -tl - 2 * l - bl + tr + 2 * r + br;
|
|
const float dY = -tl - 2 * t - tr + bl + 2 * b + br;
|
|
|
|
const Vector3 normal = VMath::normalize(Vector3(-dX, 1, -dY));
|
|
|
|
Convert::SnormToSint8(normal.ToPointer(), _pTBN[+TerrainTBN::normal]._normals0[(i << 4) + j], 3);
|
|
|
|
// TBN:
|
|
glm::vec3 nreal = normal.GLM(), nrm(0, 1, 0), tan(1, 0, 0), bin(0, 0, 1);
|
|
const float angle = glm::angle(nrm, nreal);
|
|
VERUS_RT_ASSERT(angle <= VERUS_PI * 0.5f);
|
|
if (angle >= Math::ToRadians(1))
|
|
{
|
|
const glm::vec3 axis = glm::normalize(glm::cross(nrm, nreal));
|
|
const glm::quat q = glm::angleAxis(angle, axis);
|
|
const glm::mat3 mat = glm::mat3_cast(q);
|
|
tan = mat * tan;
|
|
bin = mat * bin;
|
|
}
|
|
Convert::SnormToSint8(Vector3(tan).ToPointer(), _pTBN[+TerrainTBN::tangent]._normals0[(i << 4) + j], 3);
|
|
Convert::SnormToSint8(Vector3(bin).ToPointer(), _pTBN[+TerrainTBN::binormal]._normals0[(i << 4) + j], 3);
|
|
}
|
|
}
|
|
}
|
|
else // Lower LOD, some 'average' value:
|
|
{
|
|
const int step = 1 << lod;
|
|
const int side = 16 / step;
|
|
const int e = step >> 1;
|
|
|
|
VERUS_FOR(i, side)
|
|
{
|
|
VERUS_FOR(j, side)
|
|
{
|
|
const int ijA[] = { _ijCoord[0] + i * step + 0, _ijCoord[1] + j * step + 0 };
|
|
const int ijB[] = { _ijCoord[0] + i * step + 0, _ijCoord[1] + j * step + e };
|
|
const int ijC[] = { _ijCoord[0] + i * step + e, _ijCoord[1] + j * step + 0 };
|
|
const int ijD[] = { _ijCoord[0] + i * step + e, _ijCoord[1] + j * step + e };
|
|
|
|
VERUS_FOR(tbn, 3)
|
|
{
|
|
char* normals[] =
|
|
{
|
|
nullptr,
|
|
_pTBN[tbn]._normals1[0],
|
|
_pTBN[tbn]._normals2[0],
|
|
_pTBN[tbn]._normals3[0],
|
|
_pTBN[tbn]._normals4[0]
|
|
};
|
|
|
|
float temp[3];
|
|
Vector3 a, b, c, d;
|
|
Convert::Sint8ToSnorm(p->GetNormalAt(ijA, lod - 1, static_cast<TerrainTBN>(tbn)), temp, 3); a = Vector3::MakeFromPointer(temp);
|
|
Convert::Sint8ToSnorm(p->GetNormalAt(ijB, lod - 1, static_cast<TerrainTBN>(tbn)), temp, 3); b = Vector3::MakeFromPointer(temp);
|
|
Convert::Sint8ToSnorm(p->GetNormalAt(ijC, lod - 1, static_cast<TerrainTBN>(tbn)), temp, 3); c = Vector3::MakeFromPointer(temp);
|
|
Convert::Sint8ToSnorm(p->GetNormalAt(ijD, lod - 1, static_cast<TerrainTBN>(tbn)), temp, 3); d = Vector3::MakeFromPointer(temp);
|
|
const float ratio = 1.f + (4 - lod) * 4;
|
|
const Vector3 normal = VMath::normalize(a * ratio + b + c + d);
|
|
|
|
Convert::SnormToSint8(normal.ToPointer(), &normals[lod][((i << (4 - lod)) + j) << 2], 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int TerrainPatch::GetSplatChannelForLayer(int layer) const
|
|
{
|
|
VERUS_FOR(i, _usedChannelCount)
|
|
{
|
|
if (_layerForChannel[i] == layer)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Terrain:
|
|
|
|
Terrain::Terrain()
|
|
{
|
|
}
|
|
|
|
Terrain::~Terrain()
|
|
{
|
|
Done();
|
|
}
|
|
|
|
void Terrain::InitStatic()
|
|
{
|
|
VERUS_QREF_CONST_SETTINGS;
|
|
|
|
s_shader[SHADER_MAIN].Init("[Shaders]:DS_Terrain.hlsl");
|
|
s_shader[SHADER_MAIN]->CreateDescriptorSet(0, &s_ubTerrainVS, sizeof(s_ubTerrainVS), settings.GetLimits()._terrain_ubDrawDepthCapacity,
|
|
{
|
|
CGI::Sampler::linearMipN, // Height
|
|
CGI::Sampler::linearMipN // Normal
|
|
}, CGI::ShaderStageFlags::vs_hs_ds);
|
|
s_shader[SHADER_MAIN]->CreateDescriptorSet(1, &s_ubTerrainFS, sizeof(s_ubTerrainFS), 1000,
|
|
{
|
|
CGI::Sampler::aniso, // Normal
|
|
CGI::Sampler::aniso, // Blend
|
|
CGI::Sampler::aniso, // Layers
|
|
CGI::Sampler::aniso, // LayersNM
|
|
CGI::Sampler::aniso // Detail
|
|
}, CGI::ShaderStageFlags::fs);
|
|
s_shader[SHADER_MAIN]->CreatePipelineLayout();
|
|
|
|
s_shader[SHADER_SIMPLE].Init("[Shaders]:SimpleTerrain.hlsl");
|
|
s_shader[SHADER_SIMPLE]->CreateDescriptorSet(0, &s_ubSimpleTerrainVS, sizeof(s_ubSimpleTerrainVS), settings.GetLimits()._terrain_ubDrawDepthCapacity,
|
|
{
|
|
CGI::Sampler::linearMipN, // Height
|
|
CGI::Sampler::linearMipN // Normal
|
|
}, CGI::ShaderStageFlags::vs);
|
|
s_shader[SHADER_SIMPLE]->CreateDescriptorSet(1, &s_ubSimpleTerrainFS, sizeof(s_ubSimpleTerrainFS), 1000,
|
|
{
|
|
CGI::Sampler::linearMipN, // Normal
|
|
CGI::Sampler::linearMipN, // Blend
|
|
CGI::Sampler::linearMipN, // Layers
|
|
CGI::Sampler::shadow
|
|
}, CGI::ShaderStageFlags::fs);
|
|
s_shader[SHADER_SIMPLE]->CreatePipelineLayout();
|
|
}
|
|
|
|
void Terrain::DoneStatic()
|
|
{
|
|
s_shader.Done();
|
|
}
|
|
|
|
void Terrain::Init(RcDesc desc)
|
|
{
|
|
VERUS_INIT();
|
|
VERUS_QREF_RENDERER;
|
|
VERUS_QREF_CONST_SETTINGS;
|
|
VERUS_QREF_ATMO;
|
|
|
|
if (!Math::IsPowerOfTwo(desc._mapSide))
|
|
throw VERUS_RECOVERABLE << "Init(), mapSide must be power of two";
|
|
|
|
_mapSide = desc._mapSide;
|
|
_mapShift = Math::HighestBit(desc._mapSide);
|
|
|
|
const int patchSide = _mapSide >> 4;
|
|
const int patchShift = _mapShift - 4;
|
|
const int patchCount = patchSide * patchSide;
|
|
const int maxInstances = patchCount * 8;
|
|
|
|
_vPatches.resize(patchCount);
|
|
_vPatchTBNs.resize(patchCount * 3);
|
|
VERUS_P_FOR(i, patchSide)
|
|
{
|
|
const int offset = i << patchShift;
|
|
VERUS_FOR(j, patchSide)
|
|
{
|
|
RTerrainPatch patch = _vPatches[offset + j];
|
|
patch.BindTBN(&_vPatchTBNs[(offset + j) * 3]);
|
|
patch.InitFlat(desc._height, desc._layer);
|
|
patch._ijCoord[0] = i << 4;
|
|
patch._ijCoord[1] = j << 4;
|
|
patch._layerForChannel[0] = desc._layer;
|
|
}
|
|
});
|
|
if (desc._debugHills)
|
|
{
|
|
if (desc._debugHills < 0)
|
|
{
|
|
VERUS_P_FOR(i, _mapSide)
|
|
{
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const int ij[] = { i, j };
|
|
if (((i >> 4) + (j >> 4)) & 0x1)
|
|
SetHeightAt(ij, (i & 0xF) * 100);
|
|
else
|
|
SetHeightAt(ij, (j & 0xF) * 100);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
const float scale = 1.f / desc._debugHills;
|
|
VERUS_P_FOR(i, _mapSide)
|
|
{
|
|
const float z = i * scale;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const float x = j * scale;
|
|
const float res = sin(x * VERUS_2PI) * sin(z * VERUS_2PI);
|
|
const int ij[] = { i, j };
|
|
SetHeightAt(ij, short(res * 1000.f));
|
|
}
|
|
});
|
|
}
|
|
|
|
VERUS_FOR(lod, 5)
|
|
{
|
|
VERUS_P_FOR(i, Utils::Cast32(_vPatches.size()))
|
|
{
|
|
_vPatches[i].UpdateNormals(this, lod);
|
|
});
|
|
}
|
|
}
|
|
_vSortedPatchIndices.resize(patchCount);
|
|
|
|
// Init LODs:
|
|
VERUS_FOR(i, VERUS_COUNT_OF(_lods))
|
|
_lods[i].Init(16 >> i, 1 << i, i > 0);
|
|
|
|
Vector<short> vVB;
|
|
Vector<UINT16> vIB;
|
|
int vbSize = 0, ibSize = 0;
|
|
VERUS_FOR(i, VERUS_COUNT_OF(_lods))
|
|
{
|
|
vbSize += _lods[i]._vertCount * 4;
|
|
ibSize += _lods[i]._indexCount;
|
|
}
|
|
vVB.resize(vbSize);
|
|
vIB.resize(ibSize);
|
|
vbSize = 0, ibSize = 0;
|
|
VERUS_FOR(i, VERUS_COUNT_OF(_lods))
|
|
{
|
|
_lods[i].InitGeo(&vVB[vbSize * 4], &vIB[ibSize], vbSize, i > 0);
|
|
_lods[i]._firstIndex = ibSize;
|
|
vbSize += _lods[i]._vertCount;
|
|
ibSize += _lods[i]._indexCount;
|
|
}
|
|
_vInstanceBuffer.resize(maxInstances);
|
|
|
|
CGI::GeometryDesc geoDesc;
|
|
const CGI::VertexInputAttrDesc viaDesc[] =
|
|
{
|
|
{ 0, 0, CGI::ViaType::shorts, 4, CGI::ViaUsage::position, 0},
|
|
{-1, offsetof(PerInstanceData, _posPatch), CGI::ViaType::shorts, 4, CGI::ViaUsage::instData, 0},
|
|
{-1, offsetof(PerInstanceData, _layers), CGI::ViaType::shorts, 4, CGI::ViaUsage::instData, 1},
|
|
CGI::VertexInputAttrDesc::End()
|
|
};
|
|
geoDesc._pVertexInputAttrDesc = viaDesc;
|
|
const int strides[] = { sizeof(short) * 4, sizeof(PerInstanceData), 0 };
|
|
geoDesc._pStrides = strides;
|
|
_geo.Init(geoDesc);
|
|
_geo->CreateVertexBuffer(vbSize, 0);
|
|
_geo->CreateVertexBuffer(maxInstances, 1);
|
|
_geo->CreateIndexBuffer(ibSize);
|
|
_geo->UpdateVertexBuffer(vVB.data(), 0);
|
|
_geo->UpdateIndexBuffer(vIB.data());
|
|
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_MAIN], "#", renderer.GetDS().GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._colorAttachBlendEqs[1] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._colorAttachBlendEqs[2] = VERUS_COLOR_BLEND_OFF;
|
|
_pipe[PIPE_LIST].Init(pipeDesc);
|
|
pipeDesc._topology = CGI::PrimitiveTopology::triangleStrip;
|
|
pipeDesc._primitiveRestartEnable = true;
|
|
_pipe[PIPE_STRIP].Init(pipeDesc);
|
|
}
|
|
if (settings._gpuTessellation)
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_MAIN], "#Tess", renderer.GetDS().GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._colorAttachBlendEqs[1] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._colorAttachBlendEqs[2] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._topology = CGI::PrimitiveTopology::patchList3;
|
|
_pipe[PIPE_TESS].Init(pipeDesc);
|
|
}
|
|
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_MAIN], "#Depth", atmo.GetShadowMap().GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = "";
|
|
_pipe[PIPE_DEPTH_LIST].Init(pipeDesc);
|
|
pipeDesc._topology = CGI::PrimitiveTopology::triangleStrip;
|
|
pipeDesc._primitiveRestartEnable = true;
|
|
_pipe[PIPE_DEPTH_STRIP].Init(pipeDesc);
|
|
}
|
|
if (settings._gpuTessellation)
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_MAIN], "#DepthTess", atmo.GetShadowMap().GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = "";
|
|
pipeDesc._topology = CGI::PrimitiveTopology::patchList3;
|
|
_pipe[PIPE_DEPTH_TESS].Init(pipeDesc);
|
|
}
|
|
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_MAIN], "#SolidColor", renderer.GetDS().GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._colorAttachBlendEqs[1] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._colorAttachBlendEqs[2] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._rasterizationState._polygonMode = CGI::PolygonMode::line;
|
|
pipeDesc._rasterizationState._depthBiasEnable = true;
|
|
pipeDesc._rasterizationState._depthBiasConstantFactor = -50;
|
|
pipeDesc._depthCompareOp = CGI::CompareOp::lessOrEqual;
|
|
_pipe[PIPE_WIREFRAME_LIST].Init(pipeDesc);
|
|
pipeDesc._topology = CGI::PrimitiveTopology::triangleStrip;
|
|
pipeDesc._primitiveRestartEnable = true;
|
|
_pipe[PIPE_WIREFRAME_STRIP].Init(pipeDesc);
|
|
}
|
|
|
|
CGI::TextureDesc texDesc;
|
|
texDesc._format = CGI::Format::floatR16;
|
|
texDesc._width = _mapSide;
|
|
texDesc._height = _mapSide;
|
|
texDesc._mipLevels = 0;
|
|
texDesc._flags = CGI::TextureDesc::Flags::anyShaderResource;
|
|
_tex[TEX_HEIGHTMAP].Init(texDesc);
|
|
|
|
texDesc._format = CGI::Format::unormR8G8B8A8;
|
|
texDesc._width = _mapSide;
|
|
texDesc._height = _mapSide;
|
|
texDesc._mipLevels = 0;
|
|
texDesc._flags = CGI::TextureDesc::Flags::anyShaderResource | CGI::TextureDesc::Flags::generateMips;
|
|
_tex[TEX_NORMALS].Init(texDesc);
|
|
texDesc._flags = CGI::TextureDesc::Flags::generateMips;
|
|
_tex[TEX_BLEND].Init(texDesc);
|
|
|
|
texDesc.Reset();
|
|
texDesc._format = CGI::Format::unormR8;
|
|
texDesc._width = _mapSide;
|
|
texDesc._height = _mapSide;
|
|
texDesc._flags = CGI::TextureDesc::Flags::anyShaderResource;
|
|
_tex[TEX_MAIN_LAYER].Init(texDesc);
|
|
|
|
s_shader[SHADER_MAIN]->FreeDescriptorSet(_cshVS);
|
|
_cshVS = s_shader[SHADER_MAIN]->BindDescriptorSetTextures(0, { _tex[TEX_HEIGHTMAP], _tex[TEX_NORMALS] });
|
|
|
|
s_shader[SHADER_SIMPLE]->FreeDescriptorSet(_cshSimpleVS);
|
|
_cshSimpleVS = s_shader[SHADER_SIMPLE]->BindDescriptorSetTextures(0, { _tex[TEX_HEIGHTMAP], _tex[TEX_NORMALS] });
|
|
|
|
_vBlendBuffer.resize(_mapSide * _mapSide);
|
|
VERUS_P_FOR(i, Utils::Cast32(_vBlendBuffer.size()))
|
|
{
|
|
_vBlendBuffer[i] = VERUS_COLOR_RGBA(255, 0, 0, 255);
|
|
});
|
|
UpdateBlendTexture();
|
|
UpdateMainLayerTexture();
|
|
|
|
OnHeightModified();
|
|
AddNewRigidBody();
|
|
|
|
_vLayerUrls.reserve(s_maxLayers);
|
|
}
|
|
|
|
void Terrain::InitByWater()
|
|
{
|
|
VERUS_QREF_WATER;
|
|
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_SIMPLE], "#", water.GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = VERUS_COLOR_BLEND_OFF;
|
|
pipeDesc._rasterizationState._cullMode = CGI::CullMode::front;
|
|
_pipe[PIPE_REFLECTION_LIST].Init(pipeDesc);
|
|
pipeDesc._topology = CGI::PrimitiveTopology::triangleStrip;
|
|
pipeDesc._primitiveRestartEnable = true;
|
|
_pipe[PIPE_REFLECTION_STRIP].Init(pipeDesc);
|
|
}
|
|
{
|
|
CGI::PipelineDesc pipeDesc(_geo, s_shader[SHADER_SIMPLE], "#", water.GetRenderPassHandle());
|
|
pipeDesc._colorAttachBlendEqs[0] = VERUS_COLOR_BLEND_OFF;
|
|
_pipe[PIPE_UNDERWATER_LIST].Init(pipeDesc);
|
|
pipeDesc._topology = CGI::PrimitiveTopology::triangleStrip;
|
|
pipeDesc._primitiveRestartEnable = true;
|
|
_pipe[PIPE_UNDERWATER_STRIP].Init(pipeDesc);
|
|
}
|
|
}
|
|
|
|
void Terrain::Done()
|
|
{
|
|
s_shader[SHADER_SIMPLE]->FreeDescriptorSet(_cshSimpleVS);
|
|
s_shader[SHADER_SIMPLE]->FreeDescriptorSet(_cshSimpleFS);
|
|
s_shader[SHADER_MAIN]->FreeDescriptorSet(_cshVS);
|
|
s_shader[SHADER_MAIN]->FreeDescriptorSet(_cshFS);
|
|
|
|
_tex.Done();
|
|
_pipe.Done();
|
|
_geo.Done();
|
|
|
|
_physics.Done();
|
|
|
|
VERUS_DONE(Terrain);
|
|
}
|
|
|
|
void Terrain::ResetInstanceCount()
|
|
{
|
|
_instanceCount = 0;
|
|
}
|
|
|
|
void Terrain::Layout()
|
|
{
|
|
VERUS_QREF_CONST_SETTINGS;
|
|
VERUS_QREF_ATMO;
|
|
VERUS_QREF_SM;
|
|
|
|
// Reset LOD data:
|
|
std::for_each(_vPatches.begin(), _vPatches.end(), [](RTerrainPatch patch) {patch._quadtreeLOD = -1; });
|
|
|
|
PCamera pPrevCamera = nullptr;
|
|
// For CSM we need to create geometry beyond the view frustum (1st slice):
|
|
if (settings._sceneShadowQuality >= App::Settings::ShadowQuality::cascaded && atmo.GetShadowMap().IsRendering())
|
|
{
|
|
PCamera pCameraCSM = atmo.GetShadowMap().GetCameraCSM();
|
|
if (pCameraCSM)
|
|
pPrevCamera = sm.SetCamera(pCameraCSM);
|
|
}
|
|
|
|
_visiblePatchCount = 0;
|
|
_quadtree.TraverseVisible();
|
|
SortVisiblePatches();
|
|
|
|
// Back to original camera:
|
|
if (pPrevCamera)
|
|
sm.SetCamera(pPrevCamera);
|
|
}
|
|
|
|
void Terrain::Draw(RcDrawDesc dd)
|
|
{
|
|
VERUS_QREF_RENDERER;
|
|
VERUS_QREF_SM;
|
|
VERUS_QREF_ATMO;
|
|
VERUS_QREF_CONST_SETTINGS;
|
|
|
|
if (!_visiblePatchCount)
|
|
return;
|
|
|
|
const bool drawingDepth = Scene::SceneManager::IsDrawingDepth(Scene::DrawDepth::automatic);
|
|
|
|
const Transform3 matW = Transform3::identity();
|
|
|
|
auto cb = renderer.GetCommandBuffer();
|
|
|
|
s_ubTerrainVS._matW = matW.UniformBufferFormat();
|
|
s_ubTerrainVS._matWV = Transform3(sm.GetCamera()->GetMatrixV() * matW).UniformBufferFormat();
|
|
s_ubTerrainVS._matV = sm.GetCamera()->GetMatrixV().UniformBufferFormat();
|
|
s_ubTerrainVS._matVP = sm.GetCamera()->GetMatrixVP().UniformBufferFormat();
|
|
s_ubTerrainVS._matP = sm.GetCamera()->GetMatrixP().UniformBufferFormat();
|
|
s_ubTerrainVS._eyePos_mapSideInv = float4(atmo.GetEyePosition().GLM(), 0);
|
|
s_ubTerrainVS._eyePos_mapSideInv.w = 1.f / _mapSide;
|
|
s_ubTerrainVS._viewportSize = cb->GetViewportSize().GLM();
|
|
s_ubTerrainFS._matWV = s_ubTerrainVS._matWV;
|
|
VERUS_FOR(i, VERUS_COUNT_OF(_layerData))
|
|
{
|
|
s_ubTerrainFS._vSpecStrength[i >> 2][i & 0x3] = _layerData[i]._specStrength;
|
|
s_ubTerrainFS._vDetailStrength[i >> 2][i & 0x3] = _layerData[i]._detailStrength;
|
|
}
|
|
s_ubTerrainFS._lamScaleBias.x = _lamScale;
|
|
s_ubTerrainFS._lamScaleBias.y = _lamBias;
|
|
|
|
cb->BindVertexBuffers(_geo);
|
|
cb->BindIndexBuffer(_geo);
|
|
|
|
bool bindStrip = true;
|
|
const int half = _mapSide >> 1;
|
|
int firstInstance = 0;
|
|
int edge = _visiblePatchCount - 1;
|
|
int lod = _vPatches[_vSortedPatchIndices.front()]._quadtreeLOD;
|
|
s_shader[SHADER_MAIN]->BeginBindDescriptors();
|
|
VERUS_FOR(i, _visiblePatchCount)
|
|
{
|
|
RcTerrainPatch patch = _vPatches[_vSortedPatchIndices[i]];
|
|
if (patch._quadtreeLOD != lod || i == edge)
|
|
{
|
|
if (i == edge)
|
|
i++; // Drawing patches [firstInstance, i).
|
|
|
|
if (!lod)
|
|
{
|
|
const bool tess = dd._allowTess && settings._gpuTessellation;
|
|
if (dd._wireframe)
|
|
cb->BindPipeline(_pipe[PIPE_WIREFRAME_LIST]);
|
|
else if (drawingDepth)
|
|
cb->BindPipeline(_pipe[tess ? PIPE_DEPTH_TESS : PIPE_DEPTH_LIST]);
|
|
else
|
|
cb->BindPipeline(_pipe[tess ? PIPE_TESS : PIPE_LIST]);
|
|
cb->BindDescriptors(s_shader[SHADER_MAIN], 0, _cshVS);
|
|
cb->BindDescriptors(s_shader[SHADER_MAIN], 1, _cshFS);
|
|
}
|
|
else if (bindStrip)
|
|
{
|
|
bindStrip = false;
|
|
if (dd._wireframe)
|
|
cb->BindPipeline(_pipe[PIPE_WIREFRAME_STRIP]);
|
|
else if (drawingDepth)
|
|
cb->BindPipeline(_pipe[PIPE_DEPTH_STRIP]);
|
|
else
|
|
cb->BindPipeline(_pipe[PIPE_STRIP]);
|
|
cb->BindDescriptors(s_shader[SHADER_MAIN], 0, _cshVS);
|
|
cb->BindDescriptors(s_shader[SHADER_MAIN], 1, _cshFS);
|
|
}
|
|
|
|
const int instanceCount = i - firstInstance; // Drawing patches [firstInstance, i).
|
|
for (int inst = firstInstance; inst < i; ++inst)
|
|
{
|
|
const int at = _instanceCount + inst;
|
|
RcTerrainPatch patchToDraw = _vPatches[_vSortedPatchIndices[inst]];
|
|
_vInstanceBuffer[at]._posPatch[0] = patchToDraw._ijCoord[1] - half;
|
|
_vInstanceBuffer[at]._posPatch[1] = patchToDraw._patchHeight;
|
|
_vInstanceBuffer[at]._posPatch[2] = patchToDraw._ijCoord[0] - half;
|
|
_vInstanceBuffer[at]._posPatch[3] = 0;
|
|
VERUS_ZERO_MEM(_vInstanceBuffer[at]._layers);
|
|
if (dd._wireframe)
|
|
{
|
|
switch (patchToDraw._usedChannelCount)
|
|
{
|
|
case 1: _vInstanceBuffer[at]._layers[0] = 0; _vInstanceBuffer[at]._layers[1] = 0; _vInstanceBuffer[at]._layers[2] = 1; break;
|
|
case 2: _vInstanceBuffer[at]._layers[0] = 0; _vInstanceBuffer[at]._layers[1] = 1; _vInstanceBuffer[at]._layers[2] = 0; break;
|
|
case 3: _vInstanceBuffer[at]._layers[0] = 1; _vInstanceBuffer[at]._layers[1] = 1; _vInstanceBuffer[at]._layers[2] = 0; break;
|
|
case 4: _vInstanceBuffer[at]._layers[0] = 1; _vInstanceBuffer[at]._layers[1] = 0; _vInstanceBuffer[at]._layers[2] = 0; break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VERUS_FOR(ch, 4)
|
|
_vInstanceBuffer[at]._layers[ch] = patchToDraw._layerForChannel[ch];
|
|
}
|
|
}
|
|
|
|
cb->DrawIndexed(_lods[lod]._indexCount, instanceCount, _lods[lod]._firstIndex, 0, _instanceCount + firstInstance);
|
|
|
|
lod = patch._quadtreeLOD;
|
|
firstInstance = i;
|
|
}
|
|
}
|
|
s_shader[SHADER_MAIN]->EndBindDescriptors();
|
|
|
|
_instanceCount += _visiblePatchCount;
|
|
_geo->UpdateVertexBuffer(_vInstanceBuffer.data(), 1);
|
|
}
|
|
|
|
void Terrain::DrawReflection()
|
|
{
|
|
VERUS_QREF_RENDERER;
|
|
VERUS_QREF_SM;
|
|
VERUS_QREF_ATMO;
|
|
VERUS_QREF_CONST_SETTINGS;
|
|
VERUS_QREF_WATER;
|
|
|
|
if (!_visiblePatchCount)
|
|
return;
|
|
|
|
const Transform3 matW = Transform3::identity();
|
|
|
|
auto cb = renderer.GetCommandBuffer();
|
|
|
|
s_ubSimpleTerrainVS._matW = matW.UniformBufferFormat();
|
|
s_ubSimpleTerrainVS._matVP = sm.GetCamera()->GetMatrixVP().UniformBufferFormat();
|
|
s_ubSimpleTerrainVS._eyePos = float4(atmo.GetEyePosition().GLM(), 0);
|
|
s_ubSimpleTerrainVS._mapSideInv_clipDistanceOffset.x = 1.f / _mapSide;
|
|
s_ubSimpleTerrainVS._mapSideInv_clipDistanceOffset.y = static_cast<float>(water.IsUnderwater() ? USHRT_MAX : 0);
|
|
VERUS_FOR(i, VERUS_COUNT_OF(_layerData))
|
|
s_ubSimpleTerrainFS._vSpecStrength[i >> 2][i & 0x3] = _layerData[i]._specStrength;
|
|
s_ubSimpleTerrainFS._lamScaleBias.x = _lamScale;
|
|
s_ubSimpleTerrainFS._lamScaleBias.y = _lamBias;
|
|
s_ubSimpleTerrainFS._ambientColor = float4(atmo.GetAmbientColor().GLM(), 0);
|
|
s_ubSimpleTerrainFS._fogColor = Vector4(atmo.GetFogColor(), atmo.GetFogDensity()).GLM();
|
|
s_ubSimpleTerrainFS._dirToSun = float4(atmo.GetDirToSun().GLM(), 0);
|
|
s_ubSimpleTerrainFS._sunColor = float4(atmo.GetSunColor().GLM(), 0);
|
|
s_ubSimpleTerrainFS._matSunShadow = atmo.GetShadowMap().GetShadowMatrix(0).UniformBufferFormat();
|
|
s_ubSimpleTerrainFS._matSunShadowCSM1 = atmo.GetShadowMap().GetShadowMatrix(1).UniformBufferFormat();
|
|
s_ubSimpleTerrainFS._matSunShadowCSM2 = atmo.GetShadowMap().GetShadowMatrix(2).UniformBufferFormat();
|
|
s_ubSimpleTerrainFS._matSunShadowCSM3 = atmo.GetShadowMap().GetShadowMatrix(3).UniformBufferFormat();
|
|
memcpy(&s_ubSimpleTerrainFS._shadowConfig, &atmo.GetShadowMap().GetConfig(), sizeof(s_ubSimpleTerrainFS._shadowConfig));
|
|
s_ubSimpleTerrainFS._splitRanges = atmo.GetShadowMap().GetSplitRanges().GLM();
|
|
|
|
cb->BindVertexBuffers(_geo);
|
|
cb->BindIndexBuffer(_geo);
|
|
|
|
bool bindStrip = true;
|
|
const int half = _mapSide >> 1;
|
|
int firstInstance = 0;
|
|
int edge = _visiblePatchCount - 1;
|
|
int lod = _vPatches[_vSortedPatchIndices.front()]._quadtreeLOD;
|
|
s_shader[SHADER_SIMPLE]->BeginBindDescriptors();
|
|
VERUS_FOR(i, _visiblePatchCount)
|
|
{
|
|
RcTerrainPatch patch = _vPatches[_vSortedPatchIndices[i]];
|
|
if (patch._quadtreeLOD != lod || i == edge)
|
|
{
|
|
if (i == edge)
|
|
i++; // Drawing patches [firstInstance, i).
|
|
|
|
if (!lod)
|
|
{
|
|
if (water.IsUnderwater())
|
|
cb->BindPipeline(_pipe[PIPE_UNDERWATER_LIST]);
|
|
else
|
|
cb->BindPipeline(_pipe[PIPE_REFLECTION_LIST]);
|
|
cb->BindDescriptors(s_shader[SHADER_SIMPLE], 0, _cshSimpleVS);
|
|
cb->BindDescriptors(s_shader[SHADER_SIMPLE], 1, _cshSimpleFS);
|
|
}
|
|
else if (bindStrip)
|
|
{
|
|
bindStrip = false;
|
|
if (water.IsUnderwater())
|
|
cb->BindPipeline(_pipe[PIPE_UNDERWATER_STRIP]);
|
|
else
|
|
cb->BindPipeline(_pipe[PIPE_REFLECTION_STRIP]);
|
|
cb->BindDescriptors(s_shader[SHADER_SIMPLE], 0, _cshSimpleVS);
|
|
cb->BindDescriptors(s_shader[SHADER_SIMPLE], 1, _cshSimpleFS);
|
|
}
|
|
|
|
const int instanceCount = i - firstInstance; // Drawing patches [firstInstance, i).
|
|
for (int inst = firstInstance; inst < i; ++inst)
|
|
{
|
|
const int at = _instanceCount + inst;
|
|
RcTerrainPatch patchToDraw = _vPatches[_vSortedPatchIndices[inst]];
|
|
_vInstanceBuffer[at]._posPatch[0] = patchToDraw._ijCoord[1] - half;
|
|
_vInstanceBuffer[at]._posPatch[1] = patchToDraw._patchHeight;
|
|
_vInstanceBuffer[at]._posPatch[2] = patchToDraw._ijCoord[0] - half;
|
|
_vInstanceBuffer[at]._posPatch[3] = 0;
|
|
VERUS_ZERO_MEM(_vInstanceBuffer[at]._layers);
|
|
VERUS_FOR(ch, 4)
|
|
_vInstanceBuffer[at]._layers[ch] = patchToDraw._layerForChannel[ch];
|
|
}
|
|
|
|
cb->DrawIndexed(_lods[lod]._indexCount, instanceCount, _lods[lod]._firstIndex, 0, _instanceCount + firstInstance);
|
|
|
|
lod = patch._quadtreeLOD;
|
|
firstInstance = i;
|
|
}
|
|
}
|
|
s_shader[SHADER_SIMPLE]->EndBindDescriptors();
|
|
|
|
_instanceCount += _visiblePatchCount;
|
|
_geo->UpdateVertexBuffer(_vInstanceBuffer.data(), 1);
|
|
}
|
|
|
|
void Terrain::SortVisiblePatches()
|
|
{
|
|
std::sort(_vSortedPatchIndices.begin(), _vSortedPatchIndices.begin() + _visiblePatchCount, [this](int a, int b)
|
|
{
|
|
RcTerrainPatch patchA = GetPatch(a);
|
|
RcTerrainPatch patchB = GetPatch(b);
|
|
|
|
if (patchA._quadtreeLOD != patchB._quadtreeLOD)
|
|
return patchA._quadtreeLOD < patchB._quadtreeLOD;
|
|
|
|
return patchA._distToCameraSq < patchB._distToCameraSq;
|
|
});
|
|
}
|
|
|
|
int Terrain::UserPtr_GetType()
|
|
{
|
|
return 0;
|
|
//return +NodeType::terrain;
|
|
}
|
|
|
|
void Terrain::QuadtreeIntegral_ProcessVisibleNode(const short ij[2], RcPoint3 center)
|
|
{
|
|
VERUS_QREF_ATMO;
|
|
|
|
const RcPoint3 posEye = atmo.GetEyePosition();
|
|
|
|
const Vector3 toCenter = center - posEye;
|
|
const float dist = VMath::length(toCenter);
|
|
|
|
const float lodF = log2(Math::Clamp((dist - 12) * (2 / 100.f), 1.f, 18.f));
|
|
const int lod = Math::Clamp<int>(int(lodF), 0, 4);
|
|
|
|
if (4 == lod && center.getY() <= -20) // Cull underwater patches:
|
|
return;
|
|
|
|
// Locate this patch:
|
|
const int shiftPatch = _mapShift - 4;
|
|
const int iPatch = ij[0] >> 4;
|
|
const int jPatch = ij[1] >> 4;
|
|
const int offsetPatch = (iPatch << shiftPatch) + jPatch;
|
|
|
|
// Update this patch:
|
|
RTerrainPatch patch = _vPatches[offsetPatch];
|
|
patch._distToCameraSq = int(dist * dist);
|
|
patch._patchHeight = ConvertHeight(center.getY());
|
|
patch._quadtreeLOD = lod;
|
|
|
|
_vSortedPatchIndices[_visiblePatchCount++] = offsetPatch;
|
|
}
|
|
|
|
void Terrain::QuadtreeIntegral_GetHeights(const short ij[2], float height[2])
|
|
{
|
|
float mn = FLT_MAX, mx = -FLT_MAX;
|
|
VERUS_FOR(i, 17)
|
|
{
|
|
VERUS_FOR(j, 17)
|
|
{
|
|
const int ijTile[] = { ij[0] + i, ij[1] + j };
|
|
mn = Math::Min(mn, GetHeightAt(ijTile));
|
|
mx = Math::Max(mx, GetHeightAt(ijTile));
|
|
}
|
|
}
|
|
height[0] = mn - 1;
|
|
height[1] = mx + 1;
|
|
}
|
|
|
|
void Terrain::FattenQuadtreeNodesBy(float x)
|
|
{
|
|
_quadtreeFatten = x;
|
|
_quadtree.Done();
|
|
_quadtree.Init(_mapSide, 16, this, _quadtreeFatten);
|
|
}
|
|
|
|
float Terrain::GetHeightAt(const float xz[2]) const
|
|
{
|
|
VERUS_QREF_BULLET;
|
|
|
|
const btVector3 from(xz[0], 500, xz[1]);
|
|
const btVector3 to(xz[0], -500, xz[1]);
|
|
btCollisionWorld::ClosestRayResultCallback crrc(from, to);
|
|
crrc.m_collisionFilterMask = +Physics::Group::terrain;
|
|
bullet.GetWorld()->rayTest(from, to, crrc);
|
|
if (crrc.hasHit())
|
|
return crrc.m_hitPointWorld.getY();
|
|
return 0;
|
|
}
|
|
|
|
float Terrain::GetHeightAt(RcPoint3 pos) const
|
|
{
|
|
const float xz[2] = { pos.getX(), pos.getZ() };
|
|
return GetHeightAt(xz);
|
|
}
|
|
|
|
float Terrain::GetHeightAt(const int ij[2], int lod, short* pRaw) const
|
|
{
|
|
VERUS_RT_ASSERT(lod >= 0 && lod <= 4);
|
|
|
|
const int mapEdge = _mapSide - 1;
|
|
const int i = Math::Clamp(ij[0], 0, mapEdge);
|
|
const int j = Math::Clamp(ij[1], 0, mapEdge);
|
|
|
|
const int iLocal = i & 0xF;
|
|
const int jLocal = j & 0xF;
|
|
|
|
const int shiftPatch = _mapShift - 4;
|
|
const int iPatch = i >> 4;
|
|
const int jPatch = j >> 4;
|
|
const int offsetPatch = (iPatch << shiftPatch) + jPatch;
|
|
|
|
const short h = _vPatches[offsetPatch]._height[(iLocal << 4) + jLocal];
|
|
if (pRaw)
|
|
*pRaw = h;
|
|
return ConvertHeight(h);
|
|
}
|
|
|
|
void Terrain::SetHeightAt(const int ij[2], short h)
|
|
{
|
|
const int mapEdge = _mapSide - 1;
|
|
const int i = Math::Clamp(ij[0], 0, mapEdge);
|
|
const int j = Math::Clamp(ij[1], 0, mapEdge);
|
|
|
|
const int iLocal = i & 0xF;
|
|
const int jLocal = j & 0xF;
|
|
|
|
const int shiftPatch = _mapShift - 4;
|
|
const int iPatch = i >> 4;
|
|
const int jPatch = j >> 4;
|
|
const int offsetPatch = (iPatch << shiftPatch) + jPatch;
|
|
|
|
_vPatches[offsetPatch]._height[(iLocal << 4) + jLocal] = h;
|
|
}
|
|
|
|
void Terrain::UpdateHeightBuffer()
|
|
{
|
|
_vHeightBuffer.resize(_mapSide * _mapSide);
|
|
VERUS_P_FOR(i, _mapSide)
|
|
{
|
|
const int offset = i << _mapShift;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const int ij[] = { i, j };
|
|
short h;
|
|
GetHeightAt(ij, 0, &h);
|
|
_vHeightBuffer[offset + j] = h;
|
|
}
|
|
});
|
|
}
|
|
|
|
const char* Terrain::GetNormalAt(const int ij[2], int lod, TerrainTBN tbn) const
|
|
{
|
|
VERUS_RT_ASSERT(lod >= 0 && lod <= 4);
|
|
|
|
const int mapEdge = _mapSide - 1;
|
|
const int i = Math::Clamp(ij[0], 0, mapEdge);
|
|
const int j = Math::Clamp(ij[1], 0, mapEdge);
|
|
|
|
const int iLocal = i & 0xF;
|
|
const int jLocal = j & 0xF;
|
|
|
|
const int shiftPatch = _mapShift - 4;
|
|
const int iPatch = i >> 4;
|
|
const int jPatch = j >> 4;
|
|
const int offsetPatch = (iPatch << shiftPatch) + jPatch;
|
|
|
|
if (lod)
|
|
{
|
|
const char* normals[] =
|
|
{
|
|
nullptr,
|
|
_vPatches[offsetPatch]._pTBN[+tbn]._normals1[0],
|
|
_vPatches[offsetPatch]._pTBN[+tbn]._normals2[0],
|
|
_vPatches[offsetPatch]._pTBN[+tbn]._normals3[0],
|
|
_vPatches[offsetPatch]._pTBN[+tbn]._normals4[0]
|
|
};
|
|
const int iLOD = iLocal >> lod;
|
|
const int jLOD = jLocal >> lod;
|
|
return &normals[lod][((iLOD << (4 - lod)) + jLOD) << 2];
|
|
}
|
|
|
|
return _vPatches[offsetPatch]._pTBN[+tbn]._normals0[(iLocal << 4) + jLocal];
|
|
}
|
|
|
|
Matrix3 Terrain::GetBasisAt(const int ij[2]) const
|
|
{
|
|
glm::vec3 c0, c1, c2;
|
|
Convert::Sint8ToSnorm(GetNormalAt(ij, 0, TerrainTBN::tangent), &c0.x, 3);
|
|
Convert::Sint8ToSnorm(GetNormalAt(ij, 0, TerrainTBN::binormal), &c1.x, 3);
|
|
Convert::Sint8ToSnorm(GetNormalAt(ij, 0, TerrainTBN::normal), &c2.x, 3);
|
|
return Matrix3(Vector3(c0), Vector3(c2), Vector3(c1));
|
|
}
|
|
|
|
void Terrain::InsertLayerUrl(int layer, CSZ url)
|
|
{
|
|
VERUS_RT_ASSERT(layer >= 0 && layer < s_maxLayers);
|
|
VERUS_RT_ASSERT(url);
|
|
if (_vLayerUrls.size() <= layer)
|
|
_vLayerUrls.resize(layer + 1);
|
|
_vLayerUrls[layer] = url;
|
|
}
|
|
|
|
void Terrain::DeleteAllLayerUrls()
|
|
{
|
|
_vLayerUrls.clear();
|
|
}
|
|
|
|
void Terrain::GetLayerUrls(Vector<CSZ>& v)
|
|
{
|
|
v.clear();
|
|
v.reserve(_vLayerUrls.size());
|
|
for (const auto& url : _vLayerUrls)
|
|
v.push_back(_C(url));
|
|
}
|
|
|
|
void Terrain::LoadLayersFromFile(CSZ url)
|
|
{
|
|
DeleteAllLayerUrls();
|
|
if (!url)
|
|
url = "[Misc]:TerrainLayers.xml";
|
|
Vector<BYTE> vData;
|
|
IO::FileSystem::LoadResource(url, vData);
|
|
pugi::xml_document doc;
|
|
const pugi::xml_parse_result result = doc.load_buffer_inplace(vData.data(), vData.size());
|
|
if (!result)
|
|
throw VERUS_RECOVERABLE << "load_buffer_inplace(), " << result.description();
|
|
pugi::xml_node root = doc.first_child();
|
|
for (auto node : root.children("layer"))
|
|
{
|
|
const int id = node.attribute("id").as_int();
|
|
InsertLayerUrl(id, node.attribute("url").as_string());
|
|
}
|
|
LoadLayerTextures();
|
|
}
|
|
|
|
void Terrain::LoadLayerTextures()
|
|
{
|
|
if (_vLayerUrls.empty())
|
|
return;
|
|
|
|
VERUS_QREF_MM;
|
|
VERUS_QREF_RENDERER;
|
|
VERUS_QREF_ATMO;
|
|
|
|
renderer->WaitIdle();
|
|
|
|
_tex[TEX_LAYERS].Done();
|
|
_tex[TEX_LAYERS_NM].Done();
|
|
|
|
Vector<CSZ> vLayerUrls;
|
|
vLayerUrls.resize(_vLayerUrls.size() + 1);
|
|
VERUS_FOR(i, _vLayerUrls.size())
|
|
vLayerUrls[i] = _C(_vLayerUrls[i]);
|
|
|
|
CGI::TextureDesc texDesc;
|
|
texDesc._urls = vLayerUrls.data();
|
|
_tex[TEX_LAYERS].Init(texDesc);
|
|
|
|
Vector<String> vLayerUrlsNM;
|
|
Vector<CSZ> vLayerUrlsPtrNM;
|
|
vLayerUrlsNM.reserve(vLayerUrls.size());
|
|
vLayerUrlsPtrNM.reserve(vLayerUrls.size());
|
|
for (CSZ name : vLayerUrls)
|
|
{
|
|
if (name)
|
|
{
|
|
String nm = name;
|
|
Str::ReplaceExtension(nm, ".NM.dds");
|
|
vLayerUrlsNM.push_back(nm);
|
|
vLayerUrlsPtrNM.push_back(_C(vLayerUrlsNM.back()));
|
|
}
|
|
}
|
|
vLayerUrlsPtrNM.push_back(nullptr);
|
|
|
|
texDesc._urls = vLayerUrlsPtrNM.data();
|
|
_tex[TEX_LAYERS_NM].Init(texDesc);
|
|
|
|
s_shader[SHADER_MAIN]->FreeDescriptorSet(_cshFS);
|
|
_cshFS = s_shader[SHADER_MAIN]->BindDescriptorSetTextures(1, { _tex[TEX_NORMALS], _tex[TEX_BLEND], _tex[TEX_LAYERS], _tex[TEX_LAYERS_NM], mm.GetDetailTexture() });
|
|
s_shader[SHADER_SIMPLE]->FreeDescriptorSet(_cshSimpleFS);
|
|
_cshSimpleFS = s_shader[SHADER_SIMPLE]->BindDescriptorSetTextures(1, { _tex[TEX_NORMALS], _tex[TEX_BLEND], _tex[TEX_LAYERS], atmo.GetShadowMap().GetTexture() });
|
|
}
|
|
|
|
int Terrain::GetMainLayerAt(const int ij[2]) const
|
|
{
|
|
const int mapEdge = _mapSide - 1;
|
|
const int i = Math::Clamp(ij[0], 0, mapEdge);
|
|
const int j = Math::Clamp(ij[1], 0, mapEdge);
|
|
|
|
const int iLocal = i & 0xF;
|
|
const int jLocal = j & 0xF;
|
|
|
|
const int shiftPatch = _mapShift - 4;
|
|
const int iPatch = i >> 4;
|
|
const int jPatch = j >> 4;
|
|
const int offsetPatch = (iPatch << shiftPatch) + jPatch;
|
|
|
|
return _vPatches[offsetPatch]._mainLayer[(iLocal << 4) + jLocal];
|
|
}
|
|
|
|
void Terrain::UpdateMainLayerAt(const int ij[2])
|
|
{
|
|
const int mapEdge = _mapSide - 1;
|
|
const int i = Math::Clamp(ij[0], 0, mapEdge);
|
|
const int j = Math::Clamp(ij[1], 0, mapEdge);
|
|
|
|
const int iLocal = i & 0xF;
|
|
const int jLocal = j & 0xF;
|
|
|
|
const int shiftPatch = _mapShift - 4;
|
|
const int iPatch = i >> 4;
|
|
const int jPatch = j >> 4;
|
|
const int offsetPatch = (iPatch << shiftPatch) + jPatch;
|
|
|
|
int maxSplat = 0;
|
|
int maxSplatChannel = 0;
|
|
const int offset = (i << _mapShift) + j;
|
|
const BYTE* rgba = reinterpret_cast<const BYTE*>(&_vBlendBuffer[offset]);
|
|
VERUS_FOR(i, 4)
|
|
{
|
|
const int value = (i != 3) ? rgba[i] : 255 - rgba[0] - rgba[1] - rgba[2];
|
|
if (value > maxSplat)
|
|
{
|
|
maxSplat = value;
|
|
maxSplatChannel = i;
|
|
}
|
|
}
|
|
|
|
_vPatches[offsetPatch]._mainLayer[(iLocal << 4) + jLocal] =
|
|
_vPatches[offsetPatch]._layerForChannel[maxSplatChannel];
|
|
}
|
|
|
|
void Terrain::UpdateHeightmapTexture()
|
|
{
|
|
VERUS_QREF_RENDERER;
|
|
_vHeightmapSubresData.resize(_mapSide * _mapSide);
|
|
const int mipLevels = Math::ComputeMipLevels(_mapSide, _mapSide);
|
|
VERUS_FOR(lod, mipLevels)
|
|
{
|
|
const int side = _mapSide >> lod;
|
|
const int step = _mapSide / side;
|
|
VERUS_P_FOR(i, side)
|
|
{
|
|
const int offset = i * side;
|
|
VERUS_FOR(j, side)
|
|
{
|
|
const int ij[] = { i * step, j * step };
|
|
short h;
|
|
GetHeightAt(ij, 0, &h);
|
|
_vHeightmapSubresData[offset + j] = glm::packHalf1x16(h);
|
|
}
|
|
});
|
|
_tex[TEX_HEIGHTMAP]->UpdateSubresource(_vHeightmapSubresData.data(), lod);
|
|
}
|
|
}
|
|
|
|
CGI::TexturePtr Terrain::GetHeightmapTexture() const
|
|
{
|
|
return _tex[TEX_HEIGHTMAP];
|
|
}
|
|
|
|
void Terrain::UpdateNormalsTexture()
|
|
{
|
|
_vNormalsSubresData.resize(_mapSide * _mapSide);
|
|
VERUS_P_FOR(i, _mapSide)
|
|
{
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const int ij[] = { i, j };
|
|
char nrm[4];
|
|
char tan[4];
|
|
memcpy(nrm, GetNormalAt(ij, 0, TerrainTBN::normal), 3);
|
|
memcpy(tan, GetNormalAt(ij, 0, TerrainTBN::tangent), 3);
|
|
BYTE rgba[4] =
|
|
{
|
|
static_cast<BYTE>(nrm[0] + 127),
|
|
static_cast<BYTE>(nrm[2] + 127),
|
|
static_cast<BYTE>(tan[1] + 127),
|
|
static_cast<BYTE>(tan[2] + 127)
|
|
};
|
|
memcpy(&_vNormalsSubresData[(i << _mapShift) + j], rgba, sizeof(UINT32));
|
|
}
|
|
});
|
|
_tex[TEX_NORMALS]->UpdateSubresource(_vNormalsSubresData.data());
|
|
_tex[TEX_NORMALS]->GenerateMips();
|
|
}
|
|
|
|
CGI::TexturePtr Terrain::GetNormalsTexture() const
|
|
{
|
|
return _tex[TEX_NORMALS];
|
|
}
|
|
|
|
void Terrain::UpdateBlendTexture()
|
|
{
|
|
_tex[TEX_BLEND]->UpdateSubresource(_vBlendBuffer.data());
|
|
_tex[TEX_BLEND]->GenerateMips();
|
|
}
|
|
|
|
CGI::TexturePtr Terrain::GetBlendTexture() const
|
|
{
|
|
return _tex[TEX_BLEND];
|
|
}
|
|
|
|
void Terrain::UpdateMainLayerTexture()
|
|
{
|
|
_vMainLayerSubresData.resize(_mapSide * _mapSide);
|
|
VERUS_P_FOR(i, _mapSide)
|
|
{
|
|
const int offset = i << _mapShift;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const int ij[] = { i, j };
|
|
_vMainLayerSubresData[offset + j] = GetMainLayerAt(ij) * 16 + 4;
|
|
}
|
|
});
|
|
_tex[TEX_MAIN_LAYER]->UpdateSubresource(_vMainLayerSubresData.data());
|
|
}
|
|
|
|
CGI::TexturePtr Terrain::GetMainLayerTexture() const
|
|
{
|
|
return _tex[TEX_MAIN_LAYER];
|
|
}
|
|
|
|
void Terrain::OnHeightModified()
|
|
{
|
|
_quadtree.Done();
|
|
_quadtree.Init(_mapSide, 16, this, _quadtreeFatten);
|
|
|
|
UpdateHeightBuffer();
|
|
UpdateHeightmapTexture();
|
|
UpdateNormalsTexture();
|
|
}
|
|
|
|
void Terrain::AddNewRigidBody()
|
|
{
|
|
_physics.Done();
|
|
_physics.Init(this, _mapSide, _mapSide, _vHeightBuffer.data(), ConvertHeight(static_cast<short>(1)));
|
|
}
|
|
|
|
void Terrain::Serialize(IO::RSeekableStream stream)
|
|
{
|
|
stream.WriteText(VERUS_CRNL VERUS_CRNL "<TR>");
|
|
stream.BeginBlock();
|
|
stream.WriteString(_C(std::to_string(_mapSide)));
|
|
|
|
// Tiles (height):
|
|
VERUS_FOR(i, _mapSide)
|
|
{
|
|
const int offset = i << _mapShift;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const int ij[] = { i, j };
|
|
short h;
|
|
GetHeightAt(ij, 0, &h);
|
|
stream << h;
|
|
stream << char(0);
|
|
}
|
|
}
|
|
|
|
// Texture names:
|
|
BYTE layerCount = Utils::Cast32(_vLayerUrls.size());
|
|
stream << layerCount;
|
|
VERUS_FOR(i, layerCount)
|
|
{
|
|
stream.WriteString(_C(_vLayerUrls[i]));
|
|
stream << _layerData[i]._specStrength;
|
|
stream << _layerData[i]._detailStrength;
|
|
}
|
|
|
|
// Patches:
|
|
const int patchesSide = _mapSide >> 4;
|
|
const int patchCount = patchesSide * patchesSide;
|
|
VERUS_FOR(i, patchCount)
|
|
{
|
|
RTerrainPatch patch = _vPatches[i];
|
|
const BYTE count = patch._usedChannelCount;
|
|
stream << count;
|
|
VERUS_FOR(j, patch._usedChannelCount)
|
|
{
|
|
const char layer = patch._layerForChannel[j];
|
|
stream << layer;
|
|
}
|
|
}
|
|
|
|
// Blend channels:
|
|
VERUS_FOR(i, _mapSide)
|
|
{
|
|
const int offset = i << _mapShift;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
const UINT32 b = _vBlendBuffer[offset + j];
|
|
const UINT16 data = Convert::Uint8x4ToUint4x4(b);
|
|
stream << data;
|
|
}
|
|
}
|
|
|
|
stream.EndBlock();
|
|
}
|
|
|
|
void Terrain::Deserialize(IO::RStream stream)
|
|
{
|
|
VERUS_QREF_CONST_SETTINGS;
|
|
VERUS_QREF_SM;
|
|
|
|
char buffer[IO::Stream::s_bufferSize] = {};
|
|
|
|
Done();
|
|
stream.ReadString(buffer);
|
|
Desc desc;
|
|
desc._mapSide = atoi(buffer);
|
|
Init(desc);
|
|
|
|
const int patchSide = _mapSide >> 4;
|
|
const int patchShift = _mapShift - 4;
|
|
const int patchCount = patchSide * patchSide;
|
|
|
|
// <Height>
|
|
VERUS_FOR(i, _mapSide)
|
|
{
|
|
const int offset = i << _mapShift;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
short h;
|
|
char hole;
|
|
stream >> h;
|
|
stream >> hole;
|
|
const int ij[] = { i, j };
|
|
SetHeightAt(ij, h);
|
|
}
|
|
}
|
|
// </Height>
|
|
|
|
// <Layers>
|
|
DeleteAllLayerUrls();
|
|
BYTE layerCount = 0;
|
|
stream >> layerCount;
|
|
VERUS_FOR(i, layerCount)
|
|
{
|
|
char url[IO::Stream::s_bufferSize] = {};
|
|
stream.ReadString(url);
|
|
|
|
if (*url != '[')
|
|
{
|
|
String newUrl("[");
|
|
newUrl += url;
|
|
Str::ReplaceAll(newUrl, ":", "]:");
|
|
strcpy_s(url, _C(newUrl));
|
|
}
|
|
|
|
InsertLayerUrl(i, url);
|
|
stream >> _layerData[i]._specStrength;
|
|
stream >> _layerData[i]._detailStrength;
|
|
}
|
|
LoadLayerTextures();
|
|
// </Layers>
|
|
|
|
// <Patches>
|
|
VERUS_FOR(i, patchCount)
|
|
{
|
|
BYTE count = 0;
|
|
stream >> count;
|
|
_vPatches[i]._usedChannelCount = Math::Clamp<int>(count, 1, 4);
|
|
VERUS_FOR(j, 4)
|
|
{
|
|
if (j < count)
|
|
{
|
|
BYTE layer = 0;
|
|
stream >> layer;
|
|
_vPatches[i]._layerForChannel[j] = Math::Clamp<int>(layer, 0, s_maxLayers - 1);
|
|
}
|
|
else
|
|
{
|
|
_vPatches[i]._layerForChannel[j] = 0;
|
|
}
|
|
}
|
|
}
|
|
VERUS_FOR(lod, 5)
|
|
{
|
|
VERUS_FOR(i, _vPatches.size())
|
|
_vPatches[i].UpdateNormals(this, lod);
|
|
}
|
|
// </Patches>
|
|
|
|
// <Blend>
|
|
VERUS_FOR(i, _mapSide)
|
|
{
|
|
const int offset = i << _mapShift;
|
|
VERUS_FOR(j, _mapSide)
|
|
{
|
|
UINT16 data;
|
|
stream >> data;
|
|
_vBlendBuffer[offset + j] = Convert::Uint4x4ToUint8x4(data, true);
|
|
const int ij[] = { i, j };
|
|
UpdateMainLayerAt(ij);
|
|
}
|
|
}
|
|
UpdateBlendTexture();
|
|
UpdateMainLayerTexture();
|
|
// </Blend>
|
|
|
|
OnHeightModified();
|
|
AddNewRigidBody();
|
|
}
|