openmohaa/code/renderer/tr_terrain.c

1436 lines
43 KiB
C
Raw Normal View History

2023-05-08 14:33:37 +02:00
/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code 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.
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tr_terrain.c : Terrain rendering
#include "tr_local.h"
2024-03-03 17:34:01 +01:00
cvar_t *ter_maxlod;
cvar_t *ter_maxtris;
cvar_t *ter_cull;
cvar_t *ter_lock;
cvar_t *ter_error;
cvar_t *ter_cautiousframes;
cvar_t *ter_count;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
#define TERRAIN_TABLE_SIZE 180
2023-05-18 14:41:19 +02:00
static float g_fDistanceTable[TERRAIN_TABLE_SIZE];
static float g_fClipDotProductTable[TERRAIN_TABLE_SIZE];
static float g_fClipDotSquaredTable[TERRAIN_TABLE_SIZE];
static float g_fFogDistance;
static float g_fClipDotProduct;
static float g_fClipDotSquared;
static vec3_t g_vClipOrigin;
static vec3_t g_vClipVector;
static vec3_t g_vViewOrigin;
static vec3_t g_vViewVector;
2024-03-03 17:34:01 +01:00
static int g_terVisCount;
static float g_fCheck;
2023-05-18 14:41:19 +02:00
static size_t g_nTris;
static size_t g_nVerts;
2024-03-03 17:34:01 +01:00
static int g_nMerge;
static int g_nSplit;
unsigned int g_uiTerDist;
vec2_t g_vTerOrg;
vec3_t g_vTerFwd;
static const unsigned int MAX_TERRAIN_LOD = 6;
static const float TERRAIN_LIGHTMAP_SIZE = 128.0f;
typedef struct poolInfo_s {
terraInt iFreeHead;
terraInt iCur;
size_t nFree;
2023-05-18 14:41:19 +02:00
} poolInfo_t;
static int modeTable[8];
2024-03-03 17:34:01 +01:00
terraTri_t *g_pTris;
terrainVert_t *g_pVert;
2023-05-18 14:41:19 +02:00
poolInfo_t g_tri;
poolInfo_t g_vert;
2024-03-03 17:34:01 +01:00
static void R_ValidateHeightmapForVertex(terraTri_t *pTri)
{
2024-03-03 17:34:01 +01:00
for (terraInt i = 0; i < 3; i++) {
terrainVert_t *pVert = &g_pVert[pTri->iPt[i]];
if (pVert->pHgt < pTri->patch->heightmap || pVert->pHgt > &pTri->patch->heightmap[80]) {
pVert->pHgt = pTri->patch->heightmap;
}
}
}
2024-03-03 17:34:01 +01:00
static terraInt R_AllocateVert(cTerraPatchUnpacked_t *patch)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraInt iVert = g_vert.iFreeHead;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_vert.iFreeHead = g_pVert[g_vert.iFreeHead].iNext;
g_pVert[g_vert.iFreeHead].iPrev = 0;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_pVert[iVert].iPrev = 0;
g_pVert[iVert].iNext = patch->drawinfo.iVertHead;
g_pVert[patch->drawinfo.iVertHead].iPrev = iVert;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
patch->drawinfo.iVertHead = iVert;
patch->drawinfo.nVerts++;
2024-03-03 14:03:38 +01:00
2024-03-03 17:34:01 +01:00
assert(g_vert.nFree > 0);
g_vert.nFree--;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_pVert[iVert].nRef = 0;
g_pVert[iVert].uiDistRecalc = 0;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
return iVert;
2023-05-09 20:49:04 +02:00
}
2024-03-03 17:34:01 +01:00
static void R_InterpolateVert(terraTri_t *pTri, terrainVert_t *pVert)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
const terrainVert_t *pVert0 = &g_pVert[pTri->iPt[0]];
const terrainVert_t *pVert1 = &g_pVert[pTri->iPt[1]];
const cTerraPatchUnpacked_t *pPatch = pTri->patch;
// Interpolate texture coordinates
pVert->texCoords[0][0] = (pVert0->texCoords[0][0] + pVert1->texCoords[0][0]) * 0.5f;
pVert->texCoords[0][1] = (pVert0->texCoords[0][1] + pVert1->texCoords[0][1]) * 0.5f;
pVert->texCoords[1][0] = (pVert0->texCoords[1][0] + pVert1->texCoords[1][0]) * 0.5f;
pVert->texCoords[1][1] = (pVert0->texCoords[1][1] + pVert1->texCoords[1][1]) * 0.5f;
pVert->pHgt = (uint8_t *)(((size_t)pVert0->pHgt + (size_t)pVert1->pHgt) >> 1);
pVert->fHgtAvg = (float)(*pVert0->pHgt + *pVert1->pHgt);
pVert->fHgtAdd = (float)(*pVert->pHgt * 2) - pVert->fHgtAvg;
pVert->fHgtAvg += pPatch->z0;
pVert->xyz[0] = (pVert0->xyz[0] + pVert1->xyz[0]) * 0.5f;
pVert->xyz[1] = (pVert0->xyz[1] + pVert1->xyz[1]) * 0.5f;
pVert->xyz[2] = pVert->fHgtAvg;
2023-05-08 14:33:37 +02:00
}
2023-05-15 14:21:16 +02:00
2024-03-03 17:34:01 +01:00
static void R_ReleaseVert(cTerraPatchUnpacked_t *patch, int iVert)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terrainVert_t *pVert = &g_pVert[iVert];
terraInt iPrev = pVert->iPrev;
terraInt iNext = pVert->iNext;
g_pVert[iPrev].iNext = iNext;
g_pVert[iNext].iPrev = iPrev;
assert(patch->drawinfo.nVerts > 0);
patch->drawinfo.nVerts--;
if (patch->drawinfo.iVertHead == iVert) {
patch->drawinfo.iVertHead = iNext;
}
pVert->iPrev = 0;
pVert->iNext = g_vert.iFreeHead;
g_pVert[g_vert.iFreeHead].iPrev = iVert;
g_vert.iFreeHead = iVert;
g_vert.nFree++;
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
terraInt R_AllocateTri(cTerraPatchUnpacked_t *patch, qboolean check)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraInt iTri = g_tri.iFreeHead;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_tri.iFreeHead = g_pTris[iTri].iNext;
g_pTris[g_tri.iFreeHead].iPrev = 0;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_pTris[iTri].iPrev = patch->drawinfo.iTriTail;
g_pTris[iTri].iNext = 0;
g_pTris[patch->drawinfo.iTriTail].iNext = iTri;
patch->drawinfo.iTriTail = iTri;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
if (!patch->drawinfo.iTriHead) {
patch->drawinfo.iTriHead = iTri;
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
patch->drawinfo.nTris++;
assert(g_tri.nFree > 0);
g_tri.nFree--;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_pTris[iTri].byConstChecks = check ? 0 : 4;
g_pTris[iTri].uiDistRecalc = 0;
patch->uiDistRecalc = 0;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
return iTri;
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
static void R_ReleaseTri(cTerraPatchUnpacked_t *patch, terraInt iTri)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraTri_t *pTri = &g_pTris[iTri];
terraInt iPrev = pTri->iPrev;
terraInt iNext = pTri->iNext;
g_pTris[iPrev].iNext = iNext;
g_pTris[iNext].iPrev = iPrev;
if (g_tri.iCur == iTri) {
g_tri.iCur = iNext;
}
patch->uiDistRecalc = 0;
assert(patch->drawinfo.nTris > 0);
patch->drawinfo.nTris--;
if (patch->drawinfo.iTriHead == iTri) {
patch->drawinfo.iTriHead = iNext;
}
if (patch->drawinfo.iTriTail == iTri) {
patch->drawinfo.iTriTail = iPrev;
}
pTri->iPrev = 0;
pTri->iNext = g_tri.iFreeHead;
g_pTris[g_tri.iFreeHead].iPrev = iTri;
g_tri.iFreeHead = iTri;
g_tri.nFree++;
for (int i = 0; i < 3; i++) {
terraInt ptNum = pTri->iPt[i];
g_pVert[ptNum].nRef--;
if (!g_pVert[ptNum].nRef) {
R_ReleaseVert(patch, ptNum);
}
}
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
static int R_ConstChecksForTri(terraTri_t *pTri)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
varnodeUnpacked_t vn;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
if (pTri->lod == MAX_TERRAIN_LOD) {
return 2;
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
vn = *pTri->varnode;
vn.s.flags &= 0xF0u;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
if (vn.fVariance == 0.0 && !(pTri->varnode->s.flags & 8)) {
return 2;
} else if (pTri->varnode->s.flags & 8) {
return 3;
} else if ((pTri->byConstChecks & 4) && !(pTri->varnode->s.flags & 4) && pTri->lod < ter_maxlod->integer) {
return 0;
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
return 2;
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
static void R_DemoteInAncestry(cTerraPatchUnpacked_t *patch, terraInt iTri)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraInt iPrev = g_pTris[iTri].iPrev;
terraInt iNext = g_pTris[iTri].iNext;
g_pTris[iPrev].iNext = iNext;
g_pTris[iNext].iPrev = iPrev;
if (g_tri.iCur == iTri) {
g_tri.iCur = iNext;
}
assert(patch->drawinfo.nTris > 0);
patch->drawinfo.nTris--;
if (patch->drawinfo.iTriHead == iTri) {
patch->drawinfo.iTriHead = iNext;
}
if (patch->drawinfo.iTriTail == iTri) {
patch->drawinfo.iTriTail = iPrev;
}
g_pTris[iTri].iPrev = 0;
g_pTris[iTri].iNext = patch->drawinfo.iMergeHead;
g_pTris[patch->drawinfo.iMergeHead].iPrev = iTri;
patch->drawinfo.iMergeHead = iTri;
2023-05-18 14:41:19 +02:00
}
static void R_TerrainHeapInit()
{
2024-03-03 17:34:01 +01:00
g_tri.iFreeHead = 1;
g_tri.nFree = g_nTris - 1;
g_vert.iFreeHead = 1;
g_vert.nFree = g_nVerts - 1;
for (size_t i = 0; i < g_nTris; i++) {
g_pTris[i].iPrev = (terraInt)i - 1;
g_pTris[i].iNext = (terraInt)i + 1;
}
g_pTris[0].iPrev = 0;
g_pTris[g_nTris - 1].iNext = 0;
for (size_t i = 0; i < g_nVerts; i++) {
g_pVert[i].iPrev = (terraInt)i - 1;
g_pVert[i].iNext = (terraInt)i + 1;
}
g_pVert[0].iPrev = 0;
g_pVert[g_nVerts - 1].iNext = 0;
2023-05-18 14:41:19 +02:00
}
static void R_TerrainPatchesInit()
{
2024-03-03 17:34:01 +01:00
int i;
for (i = 0; i < tr.world->numTerraPatches; i++) {
cTerraPatchUnpacked_t *patch = &tr.world->terraPatches[i];
patch->drawinfo.iTriHead = 0;
patch->drawinfo.iTriTail = 0;
patch->drawinfo.iMergeHead = 0;
patch->drawinfo.iVertHead = 0;
patch->drawinfo.nTris = 0;
patch->drawinfo.nVerts = 0;
}
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
void R_SplitTri(
terraInt iSplit, terraInt iNewPt, terraInt iLeft, terraInt iRight, terraInt iRightOfLeft, terraInt iLeftOfRight
)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraTri_t *pSplit = &g_pTris[iSplit];
terraTri_t *pLeft;
if (iLeft) {
pLeft = &g_pTris[iLeft];
} else {
pLeft = NULL;
}
terraTri_t *pRight;
if (iRight) {
pRight = &g_pTris[iRight];
} else {
pRight = NULL;
}
int iNextLod = pSplit->lod + 1;
int index = pSplit->index;
varnodeUnpacked_t *varnode = &pSplit->varnode[index];
if (pLeft) {
pLeft->patch = pSplit->patch;
pLeft->index = index * 2;
pLeft->varnode = varnode;
pLeft->lod = iNextLod;
pLeft->iLeft = iRight;
pLeft->iRight = iRightOfLeft;
pLeft->iBase = pSplit->iLeft;
pLeft->iPt[0] = pSplit->iPt[1];
pLeft->iPt[1] = pSplit->iPt[2];
pLeft->iPt[2] = iNewPt;
R_ValidateHeightmapForVertex(pLeft);
pLeft->byConstChecks |= R_ConstChecksForTri(pLeft);
g_pVert[pLeft->iPt[0]].nRef++;
g_pVert[pLeft->iPt[1]].nRef++;
g_pVert[pLeft->iPt[2]].nRef++;
g_pTris[pSplit->iParent].nSplit++;
pLeft->nSplit = 0;
}
if (pSplit->iLeft) {
if (g_pTris[pSplit->iLeft].lod == iNextLod) {
g_pTris[pSplit->iLeft].iBase = iLeft;
} else {
g_pTris[pSplit->iLeft].iRight = iLeft;
}
}
if (pRight) {
pRight->patch = pSplit->patch;
pRight->index = index * 2 + 1;
pRight->varnode = varnode + 1;
pRight->lod = iNextLod;
pRight->iLeft = iLeftOfRight;
pRight->iRight = iLeft;
pRight->iBase = pSplit->iRight;
pRight->iPt[0] = pSplit->iPt[2];
pRight->iPt[1] = pSplit->iPt[0];
pRight->iPt[2] = iNewPt;
R_ValidateHeightmapForVertex(pRight);
pRight->byConstChecks |= R_ConstChecksForTri(pRight);
g_pVert[pRight->iPt[0]].nRef++;
g_pVert[pRight->iPt[1]].nRef++;
g_pVert[pRight->iPt[2]].nRef++;
g_pTris[pSplit->iParent].nSplit++;
pRight->nSplit = 0;
}
if (pSplit->iRight) {
if (g_pTris[pSplit->iRight].lod == iNextLod) {
g_pTris[pSplit->iRight].iBase = iRight;
} else {
g_pTris[pSplit->iRight].iLeft = iRight;
}
}
2023-05-18 14:41:19 +02:00
}
static void R_ForceSplit(terraInt iTri)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraTri_t *pTri = &g_pTris[iTri];
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
g_nSplit++;
2024-03-03 17:34:01 +01:00
terraInt iBase = pTri->iBase;
terraTri_t *pBase = &g_pTris[iBase];
if (iBase && pBase->lod != pTri->lod) {
2024-03-03 14:03:38 +01:00
assert(g_pTris[pTri->iBase].iBase != iTri);
assert(g_tri.nFree >= 8);
2024-02-23 23:53:18 +01:00
2024-03-03 17:34:01 +01:00
R_ForceSplit(iBase);
assert(g_tri.nFree >= 4);
iBase = pTri->iBase;
pBase = &g_pTris[iBase];
}
uint8_t flags = pTri->varnode->s.flags;
terraInt iTriLeft = R_AllocateTri(pTri->patch, (flags & 2));
terraInt iTriRight = R_AllocateTri(pTri->patch, (flags & 1));
terraInt iNewPt = R_AllocateVert(pTri->patch);
R_InterpolateVert(pTri, &g_pVert[iNewPt]);
g_pVert[iNewPt].fVariance = pTri->varnode->fVariance;
terraInt iBaseLeft = 0;
terraInt iBaseRight = 0;
if (iBase) {
uint8_t flags2 = pBase->varnode->s.flags;
flags |= flags2;
iBaseLeft = R_AllocateTri(pBase->patch, (flags2 & 2));
iBaseRight = R_AllocateTri(pBase->patch, (flags2 & 1));
terraInt iNewBasePt = iNewPt;
if (pBase->patch != pTri->patch) {
iNewBasePt = R_AllocateVert(pBase->patch);
terrainVert_t *pVert = &g_pVert[iNewBasePt];
R_InterpolateVert(pBase, pVert);
pVert->fVariance = g_pVert[iNewPt].fVariance;
if (flags & 8) {
pVert->fHgtAvg += pVert->fHgtAdd;
pVert->fHgtAdd = 0.0;
pVert->fVariance = 0.0;
pVert->xyz[2] = pVert->fHgtAvg;
}
}
R_SplitTri(iBase, iNewBasePt, iBaseLeft, iBaseRight, iTriRight, iTriLeft);
pBase->iLeftChild = iBaseLeft;
pBase->iRightChild = iBaseRight;
g_pTris[iBaseLeft].iParent = iBase;
g_pTris[iBaseRight].iParent = iBase;
R_DemoteInAncestry(pBase->patch, iBase);
}
if (flags & 8) {
terrainVert_t *pVert = &g_pVert[iNewPt];
pVert->fHgtAvg += pVert->fHgtAdd;
pVert->fHgtAdd = 0.0;
pVert->fVariance = 0.0;
pVert->xyz[2] = pVert->fHgtAvg;
}
R_SplitTri(iTri, iNewPt, iTriLeft, iTriRight, iBaseRight, iBaseLeft);
pTri->iLeftChild = iTriLeft;
pTri->iRightChild = iTriRight;
g_pTris[iTriLeft].iParent = iTri;
g_pTris[iTriRight].iParent = iTri;
R_DemoteInAncestry(pTri->patch, iTri);
2023-05-18 14:41:19 +02:00
}
static void R_ForceMerge(terraInt iTri)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
terraTri_t *pTri = &g_pTris[iTri];
cTerraPatchUnpacked_t *patch = pTri->patch;
terraInt iPrev = pTri->iPrev;
terraInt iNext = pTri->iNext;
g_nMerge++;
if (pTri->iLeftChild) {
terraInt iLeft = g_pTris[pTri->iLeftChild].iBase;
pTri->iLeft = iLeft;
if (iLeft) {
if (g_pTris[iLeft].lod == pTri->lod) {
g_pTris[iLeft].iRight = iTri;
} else {
g_pTris[iLeft].iBase = iTri;
}
}
R_ReleaseTri(pTri->patch, pTri->iLeftChild);
pTri->iLeftChild = 0;
g_pTris[pTri->iParent].nSplit--;
}
if (pTri->iRightChild) {
terraInt iRight = g_pTris[pTri->iRightChild].iBase;
pTri->iRight = iRight;
if (iRight) {
if (g_pTris[iRight].lod == pTri->lod) {
g_pTris[iRight].iLeft = iTri;
} else {
g_pTris[iRight].iBase = iTri;
}
}
R_ReleaseTri(pTri->patch, pTri->iRightChild);
pTri->iLeftChild = 0;
g_pTris[pTri->iParent].nSplit--;
}
g_pTris[iPrev].iNext = iNext;
g_pTris[iNext].iPrev = iPrev;
if (g_tri.iCur == iTri) {
g_tri.iCur = iNext;
}
patch->drawinfo.nTris++;
if (patch->drawinfo.iMergeHead == iTri) {
patch->drawinfo.iMergeHead = iNext;
}
g_pTris[iTri].iPrev = patch->drawinfo.iTriTail;
g_pTris[iTri].iNext = 0;
g_pTris[patch->drawinfo.iTriTail].iNext = iTri;
patch->drawinfo.iTriTail = iTri;
if (!patch->drawinfo.iTriHead) {
patch->drawinfo.iTriHead = iTri;
}
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
static int R_TerraTriNeighbor(cTerraPatchUnpacked_t *terraPatches, int iPatch, int dir)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
int iNeighbor;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
if (iPatch < 0) {
return 0;
}
2023-05-18 14:41:19 +02:00
2024-03-03 16:43:27 +01:00
iNeighbor = 2 * iPatch + 1;
switch (dir) {
case 0:
return iNeighbor;
case 1:
2024-03-03 17:34:01 +01:00
if (terraPatches[iPatch].flags & 0x80) {
2024-03-03 16:43:27 +01:00
return iNeighbor;
2024-03-03 17:34:01 +01:00
} else {
2024-03-03 16:43:27 +01:00
return iNeighbor + 1;
}
break;
case 2:
return iNeighbor + 1;
case 3:
2024-03-03 17:34:01 +01:00
if (terraPatches[iPatch].flags & 0x80) {
2024-03-03 16:43:27 +01:00
return iNeighbor + 1;
2024-03-03 17:34:01 +01:00
} else {
2024-03-03 16:43:27 +01:00
return iNeighbor;
}
break;
}
2024-03-03 17:34:01 +01:00
return 0;
2023-05-18 14:41:19 +02:00
}
static void R_PreTessellateTerrain()
{
2024-03-03 17:34:01 +01:00
size_t numTerrainPatches = tr.world->numTerraPatches;
if (!numTerrainPatches) {
return;
}
R_SyncRenderThread();
if (ter_maxtris->integer < 4 * numTerrainPatches) {
Cvar_SetValue("ter_maxtris", 4 * numTerrainPatches);
} else if (ter_maxtris->integer > 65535) {
Cvar_SetValue("ter_maxtris", 65535);
}
Com_DPrintf("Using ter_maxtris = %d\n", ter_maxtris->integer);
g_nTris = ter_maxtris->integer * 2 + 1;
g_nVerts = ter_maxtris->integer + 1;
g_pTris = ri.Hunk_Alloc(g_nTris * sizeof(terraTri_t), h_dontcare);
g_pVert = ri.Hunk_Alloc(g_nVerts * sizeof(terrainVert_t), h_dontcare);
// Init triangles & vertices
R_TerrainHeapInit();
R_TerrainPatchesInit();
for (size_t i = 0; i < numTerrainPatches; i++) {
cTerraPatchUnpacked_t *patch = &tr.world->terraPatches[i];
patch->drawinfo.surfaceType = SF_TERRAIN_PATCH;
patch->drawinfo.nTris = 0;
patch->drawinfo.nVerts = 0;
patch->drawinfo.iTriHead = 0;
patch->drawinfo.iTriTail = 0;
patch->drawinfo.iVertHead = 0;
float sMin, tMin;
if (patch->texCoord[0][0][0] < patch->texCoord[0][1][0]) {
sMin = patch->texCoord[0][0][0];
} else {
sMin = patch->texCoord[0][1][0];
}
float sMin2;
if (patch->texCoord[1][0][0] < patch->texCoord[1][1][0]) {
sMin2 = patch->texCoord[1][0][0];
} else {
sMin2 = patch->texCoord[1][1][0];
}
if (sMin >= sMin2) {
if (patch->texCoord[1][0][0] >= patch->texCoord[1][1][0]) {
sMin = floor(patch->texCoord[1][1][0]);
} else {
sMin = floor(patch->texCoord[1][0][0]);
}
} else if (patch->texCoord[0][0][0] >= patch->texCoord[0][1][0]) {
sMin = floor(patch->texCoord[0][1][0]);
} else {
sMin = floor(patch->texCoord[0][0][0]);
}
if (patch->texCoord[0][0][1] < patch->texCoord[0][1][1]) {
tMin = patch->texCoord[0][0][1];
} else {
tMin = patch->texCoord[0][1][1];
}
float tMin2;
if (patch->texCoord[1][0][1] < patch->texCoord[1][1][1]) {
tMin2 = patch->texCoord[1][0][1];
} else {
tMin2 = patch->texCoord[1][1][1];
}
if (tMin >= tMin2) {
if (patch->texCoord[1][0][1] >= patch->texCoord[1][1][1]) {
tMin = floor(patch->texCoord[1][1][1]);
} else {
tMin = floor(patch->texCoord[1][0][1]);
}
} else if (patch->texCoord[0][0][1] >= patch->texCoord[0][1][1]) {
tMin = floor(patch->texCoord[0][1][1]);
} else {
tMin = floor(patch->texCoord[0][0][1]);
}
const float s00 = patch->texCoord[0][0][0] - sMin;
const float s01 = patch->texCoord[0][1][0] - sMin;
const float s10 = patch->texCoord[1][0][0] - sMin;
const float s11 = patch->texCoord[1][1][0] - sMin;
const float t00 = patch->texCoord[0][0][1] - tMin;
const float t01 = patch->texCoord[0][1][1] - tMin;
const float t10 = patch->texCoord[1][0][1] - tMin;
const float t11 = patch->texCoord[1][1][1] - tMin;
const float lmapSize = (float)(patch->drawinfo.lmapSize - 1) / TERRAIN_LIGHTMAP_SIZE;
const float ls = patch->s + lmapSize;
const float lt = patch->t + lmapSize;
terraInt iTri0 = R_AllocateTri(patch, qfalse);
terraInt iTri1 = R_AllocateTri(patch, qfalse);
terraInt i00 = R_AllocateVert(patch);
terraInt i01 = R_AllocateVert(patch);
terraInt i10 = R_AllocateVert(patch);
terraInt i11 = R_AllocateVert(patch);
terrainVert_t *pVert;
pVert = &g_pVert[i00];
pVert->xyz[0] = patch->x0;
pVert->xyz[1] = patch->y0;
pVert->xyz[2] = (float)(patch->heightmap[0] * 2) + patch->z0;
pVert->pHgt = &patch->heightmap[0];
pVert->fHgtAvg = pVert->xyz[2];
pVert->texCoords[0][0] = s00;
pVert->texCoords[0][1] = t00;
pVert->texCoords[1][0] = patch->s;
pVert->texCoords[1][1] = patch->t;
pVert->fVariance = 0.0f;
pVert->nRef = 4;
pVert = &g_pVert[i01];
pVert->xyz[0] = patch->x0;
pVert->xyz[1] = patch->y0 + 512.0f;
pVert->xyz[2] = (float)(patch->heightmap[72] * 2) + patch->z0;
pVert->pHgt = &patch->heightmap[72];
pVert->fHgtAvg = pVert->xyz[2];
pVert->texCoords[0][0] = s01;
pVert->texCoords[0][1] = t01;
pVert->texCoords[1][0] = patch->s;
pVert->texCoords[1][1] = lt;
pVert->fVariance = 0.0f;
pVert->nRef = 4;
pVert = &g_pVert[i10];
pVert->xyz[0] = patch->x0 + 512.0f;
pVert->xyz[1] = patch->y0;
pVert->xyz[2] = (float)(patch->heightmap[8] * 2) + patch->z0;
pVert->pHgt = &patch->heightmap[8];
pVert->fHgtAvg = pVert->xyz[2];
pVert->texCoords[0][0] = s10;
pVert->texCoords[0][1] = t10;
pVert->texCoords[1][0] = ls;
pVert->texCoords[1][1] = patch->t;
pVert->fVariance = 0.0f;
pVert->nRef = 4;
pVert = &g_pVert[i11];
pVert->xyz[0] = patch->x0 + 512.0f;
pVert->xyz[1] = patch->y0 + 512.0f;
pVert->xyz[2] = (float)(patch->heightmap[80] * 2) + patch->z0;
pVert->pHgt = &patch->heightmap[80];
pVert->fHgtAvg = pVert->xyz[2];
pVert->texCoords[0][0] = s11;
pVert->texCoords[0][1] = t11;
pVert->texCoords[1][0] = ls;
pVert->texCoords[1][1] = lt;
pVert->fVariance = 0.0f;
pVert->nRef = 4;
terraTri_t *pTri = &g_pTris[iTri0];
pTri->patch = patch;
pTri->varnode = &patch->varTree[0][0];
pTri->index = 1;
pTri->lod = 0;
pTri->byConstChecks |= R_ConstChecksForTri(pTri);
pTri->iBase = iTri1;
if ((patch->flags & 0x80u) == 0) {
pTri->iLeft = R_TerraTriNeighbor(tr.world->terraPatches, patch->iWest, 1);
pTri->iRight = R_TerraTriNeighbor(tr.world->terraPatches, patch->iNorth, 2);
if (patch->flags & 0x40) {
pTri->iPt[0] = i00;
pTri->iPt[1] = i11;
} else {
pTri->iPt[0] = i11;
pTri->iPt[1] = i00;
}
pTri->iPt[2] = i01;
} else {
pTri->iLeft = R_TerraTriNeighbor(tr.world->terraPatches, patch->iNorth, 2);
pTri->iRight = R_TerraTriNeighbor(tr.world->terraPatches, patch->iEast, 3);
if (patch->flags & 0x40) {
pTri->iPt[0] = i01;
pTri->iPt[1] = i10;
} else {
pTri->iPt[0] = i10;
pTri->iPt[1] = i01;
}
pTri->iPt[2] = i11;
}
R_ValidateHeightmapForVertex(pTri);
pTri = &g_pTris[iTri1];
pTri->patch = patch;
pTri->varnode = &patch->varTree[1][0];
pTri->index = 1;
pTri->lod = 0;
pTri->byConstChecks |= R_ConstChecksForTri(pTri);
pTri->iBase = iTri0;
if ((patch->flags & 0x80u) == 0) {
pTri->iLeft = R_TerraTriNeighbor(tr.world->terraPatches, patch->iEast, 3);
pTri->iRight = R_TerraTriNeighbor(tr.world->terraPatches, patch->iSouth, 0);
if (patch->flags & 0x40) {
pTri->iPt[0] = i11;
pTri->iPt[1] = i00;
} else {
pTri->iPt[0] = i00;
pTri->iPt[1] = i11;
}
pTri->iPt[2] = i10;
} else {
pTri->iLeft = R_TerraTriNeighbor(tr.world->terraPatches, patch->iSouth, 0);
pTri->iRight = R_TerraTriNeighbor(tr.world->terraPatches, patch->iWest, 1);
if (patch->flags & 0x40) {
pTri->iPt[0] = i10;
pTri->iPt[1] = i01;
} else {
pTri->iPt[0] = i01;
pTri->iPt[1] = i10;
}
pTri->iPt[2] = i00;
}
R_ValidateHeightmapForVertex(pTri);
}
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
static qboolean R_NeedSplitTri(terraTri_t *pTri)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
uint8_t byConstChecks = pTri->byConstChecks;
if (byConstChecks & 2) {
return byConstChecks & 1;
}
if (pTri->uiDistRecalc > g_uiTerDist) {
return qfalse;
}
float fRatio = ((g_pVert[pTri->iPt[0]].xyz[0] + g_pVert[pTri->iPt[1]].xyz[0]) * g_vViewVector[0]
+ (g_pVert[pTri->iPt[0]].xyz[1] + g_pVert[pTri->iPt[1]].xyz[1]) * g_vViewVector[1])
* 0.5
+ g_vViewVector[2] - pTri->varnode->fVariance * g_fCheck;
if (fRatio > 0) {
pTri->uiDistRecalc = floor(fRatio) + g_uiTerDist;
return qfalse;
}
return qtrue;
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
static void R_CalcVertMorphHeight(terrainVert_t *pVert)
{
2024-03-03 17:34:01 +01:00
float dot;
pVert->xyz[2] = pVert->fHgtAvg;
pVert->uiDistRecalc = g_uiTerDist + 1;
dot = (pVert->fVariance * g_fCheck)
- (g_vViewVector[0] * pVert->xyz[0] + g_vViewVector[1] * pVert->xyz[1] + g_vViewVector[2]);
if (dot > 0.0) {
float calc = dot / 400.0;
if (calc > 1.0) {
pVert->uiDistRecalc = (unsigned int)((g_uiTerDist - 400) + floor(dot));
calc = 1.0;
}
pVert->xyz[2] += pVert->fHgtAdd * calc;
} else {
pVert->uiDistRecalc = g_uiTerDist - (int)ceil(dot);
}
}
2024-03-03 17:34:01 +01:00
static void R_UpdateVertMorphHeight(terrainVert_t *pVert)
{
2024-03-03 17:34:01 +01:00
if (pVert->uiDistRecalc <= g_uiTerDist) {
R_CalcVertMorphHeight(pVert);
}
}
2023-05-18 14:41:19 +02:00
static void R_DoTriSplitting()
{
2024-03-03 17:34:01 +01:00
cTerraPatchUnpacked_t *patch;
for (patch = tr.world->activeTerraPatches; patch; patch = patch->pNextActive) {
if (patch->uiDistRecalc > g_uiTerDist) {
continue;
}
patch->uiDistRecalc = -1;
g_tri.iCur = patch->drawinfo.iTriHead;
while (g_tri.iCur != 0) {
terraTri_t *pTri = &g_pTris[g_tri.iCur];
if (R_NeedSplitTri(pTri)) {
//
// make sure there are sufficient number of tris
//
if (g_tri.nFree < 14 || g_vert.nFree < 14) {
Com_DPrintf("WARNING: aborting terrain tessellation -- insufficient tris\n");
return;
}
patch->uiDistRecalc = 0;
R_ForceSplit(g_tri.iCur);
if (&g_pTris[g_tri.iCur] == pTri) {
g_tri.iCur = g_pTris[g_tri.iCur].iNext;
}
} else {
if ((pTri->byConstChecks & 3) != 2) {
if (patch->uiDistRecalc > pTri->uiDistRecalc) {
patch->uiDistRecalc = pTri->uiDistRecalc;
}
}
g_tri.iCur = g_pTris[g_tri.iCur].iNext;
}
}
}
2023-05-18 14:41:19 +02:00
}
static void R_DoGeomorphs()
{
2024-03-03 17:34:01 +01:00
for (size_t n = 0; n < tr.world->numTerraPatches; n++) {
cTerraPatchUnpacked_t *patch = &tr.world->terraPatches[n];
g_vert.iCur = patch->drawinfo.iVertHead;
if (patch->visCountDraw == g_terVisCount) {
if (patch->byDirty) {
while (g_vert.iCur) {
terrainVert_t *pVert = &g_pVert[g_vert.iCur];
R_CalcVertMorphHeight(pVert);
g_vert.iCur = pVert->iNext;
}
patch->byDirty = qfalse;
} else {
while (g_vert.iCur) {
terrainVert_t *pVert = &g_pVert[g_vert.iCur];
R_UpdateVertMorphHeight(pVert);
g_vert.iCur = pVert->iNext;
}
}
} else {
if (!patch->byDirty) {
patch->byDirty = qtrue;
while (g_vert.iCur) {
terrainVert_t *pVert = &g_pVert[g_vert.iCur];
pVert->xyz[2] = pVert->fHgtAvg;
2024-03-03 17:34:01 +01:00
g_vert.iCur = pVert->iNext;
}
}
}
}
2023-05-18 14:41:19 +02:00
}
static qboolean R_MergeInternalAggressive()
{
2024-03-03 17:34:01 +01:00
terraTri_t *pTri = &g_pTris[g_tri.iCur];
terraTri_t *pBase;
2024-03-03 17:34:01 +01:00
if (pTri->nSplit) {
return qfalse;
}
2024-03-03 17:34:01 +01:00
if (g_pVert[g_pTris[pTri->iLeftChild].iPt[2]].xyz[2] != g_pVert[g_pTris[pTri->iLeftChild].iPt[2]].fHgtAvg) {
return qfalse;
}
2024-03-03 17:34:01 +01:00
if (pTri->iBase) {
pBase = &g_pTris[pTri->iBase];
if (pBase->nSplit) {
return qfalse;
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
R_ForceMerge(pTri->iBase);
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
R_ForceMerge(g_tri.iCur);
return qtrue;
2023-05-18 14:41:19 +02:00
}
static qboolean R_MergeInternalCautious()
{
2024-03-03 17:34:01 +01:00
terraTri_t *pTri;
terraTri_t *pBase;
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
pTri = &g_pTris[g_tri.iCur];
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
if (pTri->nSplit) {
return qfalse;
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
if (pTri->varnode->s.flags & 8) {
return qfalse;
}
2024-03-03 17:34:01 +01:00
if (g_pVert[g_pTris[pTri->iLeftChild].iPt[2]].xyz[2] != g_pVert[g_pTris[pTri->iLeftChild].iPt[2]].fHgtAvg) {
return qfalse;
}
2024-03-03 17:34:01 +01:00
if (pTri->iBase) {
pBase = &g_pTris[pTri->iBase];
if (pBase->nSplit || (pBase->varnode->s.flags & 8)) {
return qfalse;
}
2024-03-03 17:34:01 +01:00
R_ForceMerge(pTri->iBase);
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
R_ForceMerge(g_tri.iCur);
return qtrue;
2023-05-18 14:41:19 +02:00
}
static void R_DoTriMerging()
{
2024-03-03 17:34:01 +01:00
int iCautiousFrame = g_terVisCount - ter_cautiousframes->integer;
for (size_t n = 0; n < tr.world->numTerraPatches; n++) {
const cTerraPatchUnpacked_t *patch = &tr.world->terraPatches[n];
g_tri.iCur = patch->drawinfo.iMergeHead;
if (patch->visCountDraw >= iCautiousFrame) {
while (g_tri.iCur) {
if (!R_MergeInternalCautious()) {
g_tri.iCur = g_pTris[g_tri.iCur].iNext;
}
}
} else if (patch->visCountDraw > iCautiousFrame - 10) {
while (g_tri.iCur) {
if (!R_MergeInternalAggressive()) {
g_tri.iCur = g_pTris[g_tri.iCur].iNext;
}
}
}
}
2023-05-18 14:41:19 +02:00
}
void R_TessellateTerrain()
{
2024-03-03 17:34:01 +01:00
if (glConfig.smpActive) {
R_SyncRenderThread();
}
R_DoTriSplitting();
// Morph geometry according to the view
R_DoGeomorphs();
// Merge vertices
R_DoTriMerging();
2023-05-18 14:41:19 +02:00
}
void R_TerrainPrepareFrame()
{
2024-03-03 17:34:01 +01:00
float distance;
int index;
float fFov;
float fCheck;
float fDistBound;
if (ter_lock->integer) {
return;
}
if (tr.viewParms.isPortalSky) {
return;
}
g_terVisCount++;
tr.world->activeTerraPatches = NULL;
if (ter_error->value < 0.1) {
ri.Cvar_Set("ter_error", "0.1");
}
distance = 1.0;
fFov = tr.refdef.fov_x;
if (fFov < 1.0) {
fFov = 1.0;
}
fCheck = 1.0 / (tan(fFov / 114.0) * ter_error->value / 320.0);
if (g_terVisCount) {
float fFarPlane = tr.viewParms.farplane_distance;
if (fFarPlane == 0.0) {
fFarPlane = 4096.0;
}
fDistBound = fabs((fCheck - g_fCheck) * 510.0) + fabs((tr.refdef.vieworg[0] - g_vTerOrg[0]) * g_vTerFwd[0])
+ fabs((tr.refdef.vieworg[1] - g_vTerOrg[1]) * g_vTerFwd[1])
+ fabs((tr.refdef.viewaxis[0][0] - g_vTerFwd[0]) * fFarPlane)
+ fabs((tr.refdef.viewaxis[0][1] - g_vTerFwd[1]) * fFarPlane);
g_uiTerDist = (ceil(fDistBound) + (float)g_uiTerDist);
if (g_uiTerDist > 0xF0000000) {
for (index = 0; index < tr.world->numTerraPatches; index++) {
tr.world->terraPatches[index].uiDistRecalc = 0;
}
for (index = 0; index < g_nTris; index++) {
g_pTris[index].uiDistRecalc = 0;
}
for (index = 0; index < g_nVerts; index++) {
g_pVert[index].uiDistRecalc = 0;
}
g_uiTerDist = 0;
}
} else {
fDistBound = 0.0;
g_uiTerDist = 0;
}
VectorCopy2D(tr.refdef.vieworg, g_vTerOrg);
VectorCopy(tr.refdef.viewaxis[0], g_vTerFwd);
g_fCheck = fCheck;
if (fDistBound != 0.0) {
index = (int)tr.refdef.fov_x;
distance = g_fDistanceTable[index];
g_fClipDotSquared = g_fClipDotSquaredTable[index];
g_fClipDotProduct = g_fClipDotProductTable[index];
VectorCopy(tr.refdef.viewaxis[0], g_vClipVector);
g_vClipOrigin[0] = tr.refdef.vieworg[0] - distance * tr.refdef.viewaxis[0][0] - 256.0;
g_vClipOrigin[1] = tr.refdef.vieworg[1] - distance * tr.refdef.viewaxis[0][1] - 256.0;
g_vClipOrigin[2] = tr.refdef.vieworg[2] - distance * tr.refdef.viewaxis[0][2] - 255.0;
g_vViewVector[0] = tr.refdef.viewaxis[0][0];
g_vViewVector[1] = tr.refdef.viewaxis[0][1];
g_vViewVector[2] =
-(tr.refdef.viewaxis[0][0] * tr.refdef.vieworg[0] + tr.refdef.viewaxis[0][1] * tr.refdef.vieworg[1]);
VectorCopy(tr.refdef.vieworg, g_vViewOrigin);
if (tr.viewParms.farplane_distance > 0) {
g_fFogDistance = distance + tr.viewParms.farplane_distance + 768.0;
} else {
g_fFogDistance = 999999.0;
}
}
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
void R_MarkTerrainPatch(cTerraPatchUnpacked_t *pPatch)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
if (pPatch->visCountCheck == g_terVisCount) {
return;
}
pPatch->visCountCheck = g_terVisCount;
if (ter_cull->integer) {
vec3_t v;
float dot;
v[0] = pPatch->x0 - g_vClipOrigin[0];
v[1] = pPatch->y0 - g_vClipOrigin[1];
v[2] = pPatch->z0 - g_vClipOrigin[2];
dot = DotProduct(v, g_vClipVector);
if (dot > g_fFogDistance) {
return;
}
if (VectorLengthSquared(v) * g_fClipDotSquared > dot * dot) {
return;
}
}
pPatch->visCountDraw = g_terVisCount;
pPatch->pNextActive = tr.world->activeTerraPatches;
tr.world->activeTerraPatches = pPatch;
2023-05-18 14:41:19 +02:00
}
void R_AddTerrainSurfaces()
{
2024-03-03 17:34:01 +01:00
int i;
int dlight;
cTerraPatchUnpacked_t *patch;
if (tr.world->numTerraPatches < 0) {
return;
}
if (!ter_lock->integer) {
R_TessellateTerrain();
}
if (ter_count->integer || ter_lock->integer == 2) {
for (i = 0; i < tr.world->numTerraPatches; i++) {
patch = &tr.world->terraPatches[i];
if (ter_lock->integer == 2 || patch->visCountDraw == g_terVisCount) {
assert(patch->shader);
dlight = R_CheckDlightTerrain(patch, (1 << (tr.refdef.num_dlights)) - 1);
R_AddDrawSurf((surfaceType_t *)&patch->drawinfo, patch->shader, dlight);
}
if (ter_count->integer && (g_nSplit || g_nMerge)) {
if (ter_count->integer == 1 || g_nSplit * 2 != g_nMerge) {
Com_DPrintf(
2024-06-07 20:34:13 +02:00
"%5zu tris / %5zu verts / %4d splits / %4d merges\n",
2024-03-03 17:34:01 +01:00
g_nTris - g_tri.nFree,
g_nVerts - g_vert.nFree,
g_nSplit,
g_nMerge
);
}
g_nSplit = 0;
g_nMerge = 0;
}
}
} else {
for (patch = tr.world->activeTerraPatches; patch; patch = patch->pNextActive) {
assert(patch->shader);
dlight = R_CheckDlightTerrain(patch, (1 << (tr.refdef.num_dlights)) - 1);
R_AddDrawSurf((surfaceType_t *)&patch->drawinfo, patch->shader, dlight);
}
}
2023-05-18 14:41:19 +02:00
}
/**
* Calculates the height of a terrain polygon by interpolating the heights of its vertices.
* The function finds the triangle containing the given point (x, y) within the terrain patch,
* and then computes the height at each vertex of the polygon using barycentric interpolation.
* Finally, it updates the z-coordinate of each vertex to store the calculated height.
* Returns qtrue if the triangle was found and the height could be calculated and saved into its vertices.
*
* ToDo: try to use the Vector*(?) macros for the math operations below from q_shared.h and q_math.h, not sure which ones would be a good fit
*/
2024-03-03 17:34:01 +01:00
qboolean R_TerrainHeightForPoly(cTerraPatchUnpacked_t *pPatch, polyVert_t *pVerts, int nVerts)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
float x0 = 0;
float y0 = 0;
float x1 = 0;
float y1 = 0;
float x2 = 0;
float y2 = 0;
float fKx[3] = {0}, fKy[3] = {0}, fKz[3] = {0}, fArea[3] = {0};
float fAreaTotal = 0;
float x = pVerts->xyz[0];
float y = pVerts->xyz[1];
// Calculate the average of the x and y coordinates of all vertices
if (nVerts > 1) {
for (int i = 1; i < nVerts; i++) {
x += pVerts[i].xyz[0];
y += pVerts[i].xyz[1];
}
// Use the averaged values from this point onwards
x = x / nVerts;
y = y / nVerts;
}
// Get the first valid triangle in the patch
terraInt iTri = pPatch->drawinfo.iTriHead;
if (!iTri) {
assert(
!pPatch->drawinfo.iTriHead
&& va(
"R_TerrainHeightForPoly: point(%f %f) not in patch(%f %f %f)\n",
x,
y,
pPatch->x0,
pPatch->y0,
pPatch->z0
)
);
return qfalse;
}
// Find a triangle that contains the point (x, y)
while (qtrue) {
if (g_pTris[iTri].byConstChecks & 4) {
// Get all three x-y coordinates of the current triangle's vertices
x0 = g_pVert[g_pTris[iTri].iPt[0]].xyz[0];
y0 = g_pVert[g_pTris[iTri].iPt[0]].xyz[1];
x1 = g_pVert[g_pTris[iTri].iPt[1]].xyz[0];
y1 = g_pVert[g_pTris[iTri].iPt[1]].xyz[1];
x2 = g_pVert[g_pTris[iTri].iPt[2]].xyz[0];
y2 = g_pVert[g_pTris[iTri].iPt[2]].xyz[1];
// Calculate the signed areas of the three sub-triangles
fArea[0] = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1);
if (fArea[0] < -0.1) {
// The point is outside the triangle
continue;
}
fArea[1] = (x - x2) * (y0 - y2) - (y - y2) * (x0 - x2);
if (fArea[1] < -0.1) {
// The point is outside the triangle
continue;
}
fArea[2] = (x - x0) * (y1 - y0) - (y - y0) * (x1 - x0);
if (fArea[2] < -0.1) {
// The point is outside the triangle
continue;
}
fAreaTotal = fArea[0] + fArea[1] + fArea[2];
if (fAreaTotal > 0.0) {
// Found it - the point is inside the triangle
break;
}
}
iTri = g_pTris[iTri].iNext;
if (!iTri) {
// There's no triangle that contains point (x, y) - bail out
assert(
!g_pTris[iTri].iNext
&& va(
"R_TerrainHeightForPoly: point(%f %f) not in patch(%f %f %f)\n",
x,
y,
pPatch->x0,
pPatch->y0,
pPatch->z0
)
);
return qfalse;
}
}
// Calculate the barycentric coordinates (fKx, fKy, fKz) of the triangle
float z0 = g_pVert[g_pTris[iTri].iPt[0]].xyz[2] / fAreaTotal;
float z1 = g_pVert[g_pTris[iTri].iPt[1]].xyz[2] / fAreaTotal;
float z2 = g_pVert[g_pTris[iTri].iPt[2]].xyz[2] / fAreaTotal;
fKy[0] = z0 * (x2 - x1);
fKy[1] = z1 * (x0 - x2);
fKy[2] = z2 * (x1 - x0);
fKx[0] = z0 * (y2 - y1);
fKx[1] = z1 * (y0 - y2);
fKx[2] = z2 * (y1 - y0);
// Note: this could be done with the CrossProduct macro if everything were in a vector
fKz[0] = fKx[0] * x1 - y1 * fKy[0];
fKz[1] = fKx[1] * x2 - y2 * fKy[1];
fKz[2] = fKx[2] * x0 - y0 * fKy[2];
// Calculate the height for each vertex
for (int i = 0; i < nVerts; i++) {
float fScaleX = pVerts[i].xyz[0] * (fKx[0] + fKx[1] + fKx[2]);
float fScaleY = pVerts[i].xyz[1] * (fKy[0] + fKy[1] + fKy[2]);
float fConstZ = fKz[0] + fKz[1] + fKz[2];
// Write back the calculated height into the vertex
pVerts[i].xyz[2] = fScaleY - fScaleX - fConstZ;
}
return qtrue;
2023-05-18 14:41:19 +02:00
}
void R_TerrainRestart_f(void)
{
2024-03-03 17:34:01 +01:00
if (tr.world->numTerraPatches < 0) {
return;
}
2023-05-18 14:41:19 +02:00
if (g_pVert) {
ri.Free(g_pVert);
g_pVert = NULL;
}
if (g_pTris) {
ri.Free(g_pTris);
g_pTris = NULL;
}
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
R_PreTessellateTerrain();
2023-05-18 14:41:19 +02:00
}
2024-03-03 17:34:01 +01:00
void R_CraterTerrain(vec_t *pos /* 0x8 */, vec_t *dir, float fDepth, float fRadius)
2023-05-18 14:41:19 +02:00
{
2024-03-03 17:34:01 +01:00
// FIXME: unimplemented
2023-05-18 14:41:19 +02:00
}
void R_TerrainCrater_f()
{
2024-03-03 17:34:01 +01:00
vec3_t dir;
VectorNegate(tr.refdef.viewaxis[2], dir);
2023-05-18 14:41:19 +02:00
2024-03-03 17:34:01 +01:00
R_CraterTerrain(tr.refdef.vieworg, dir, 256.0, 256.0);
R_TerrainRestart_f();
2023-05-18 14:41:19 +02:00
}
void R_InitTerrain()
{
2024-03-03 17:34:01 +01:00
int i;
ter_maxlod = ri.Cvar_Get("ter_maxlod", "6", CVAR_ARCHIVE | CVAR_TERRAIN_LATCH);
2024-03-03 17:34:01 +01:00
ter_cull = ri.Cvar_Get("ter_cull", "1", 0);
ter_lock = ri.Cvar_Get("ter_lock", "0", 0);
ter_error = ri.Cvar_Get("ter_error", "4", CVAR_ARCHIVE);
2024-03-03 17:34:01 +01:00
ter_cautiousframes = ri.Cvar_Get("ter_cautiousframes", "1", CVAR_ARCHIVE);
ter_maxtris = ri.Cvar_Get("ter_maxtris", "24576", CVAR_TERRAIN_LATCH);
2024-03-03 17:34:01 +01:00
ter_count = ri.Cvar_Get("ter_count", "0", 0);
Cmd_AddCommand("ter_restart", R_TerrainRestart_f);
R_PreTessellateTerrain();
for (i = 0; i < TERRAIN_TABLE_SIZE; i++) {
float dot = (1.0 / (0.82 - cos((float)i / 57.29) * 0.18) - 1.0) * (16.0 / 9.0);
g_fClipDotSquaredTable[i] = dot;
g_fClipDotProductTable[i] = sqrt(dot);
g_fDistanceTable[i] = 443.5 / sqrt(1.0 - dot);
}
2023-05-18 14:41:19 +02:00
}
/*
====================
R_SwapTerraPatch
Swaps the patch on big-endian
====================
*/
void R_SwapTerraPatch(cTerraPatch_t* pPatch) {
#ifdef Q3_BIG_ENDIAN
int i;
pPatch->texCoord[0][0][0] = LittleFloat(pPatch->texCoord[0][0][0]);
pPatch->texCoord[0][0][1] = LittleFloat(pPatch->texCoord[0][0][1]);
pPatch->texCoord[0][1][0] = LittleFloat(pPatch->texCoord[0][1][0]);
pPatch->texCoord[0][1][1] = LittleFloat(pPatch->texCoord[0][1][1]);
pPatch->texCoord[1][0][0] = LittleFloat(pPatch->texCoord[1][0][0]);
pPatch->texCoord[1][0][1] = LittleFloat(pPatch->texCoord[1][0][1]);
pPatch->texCoord[1][1][0] = LittleFloat(pPatch->texCoord[1][1][0]);
pPatch->texCoord[1][1][1] = LittleFloat(pPatch->texCoord[1][1][1]);
pPatch->iBaseHeight = LittleShort(pPatch->iBaseHeight);
pPatch->iShader = LittleUnsignedShort(pPatch->iShader);
pPatch->iLightMap = LittleUnsignedShort(pPatch->iLightMap);
pPatch->iNorth = LittleShort(pPatch->iNorth);
pPatch->iEast = LittleShort(pPatch->iEast);
pPatch->iSouth = LittleShort(pPatch->iSouth);
pPatch->iWest = LittleShort(pPatch->iWest);
for (i = 0; i < MAX_TERRAIN_VARNODES; i++) {
pPatch->varTree[0][i].flags = LittleUnsignedShort(pPatch->varTree[0][i].flags);
pPatch->varTree[1][i].flags = LittleUnsignedShort(pPatch->varTree[1][i].flags);
}
#endif
}