mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-05-02 23:57:58 +03:00
1184 lines
30 KiB
C
1184 lines
30 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 2023 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
|
|
===========================================================================
|
|
*/
|
|
|
|
// DESCRIPTION:
|
|
// wall marks
|
|
|
|
#include "cg_local.h"
|
|
|
|
/*
|
|
===================================================================
|
|
|
|
MARK POLYS
|
|
|
|
===================================================================
|
|
*/
|
|
|
|
#define MAX_MARK_FRAGMENTS 128
|
|
#define MAX_MARK_POINTS 384
|
|
|
|
static vec3_t cg_vEntAngles;
|
|
static vec3_t cg_vEntOrigin;
|
|
static vec3_t cg_fEntAxis[3];
|
|
static qboolean cg_bEntAnglesSet;
|
|
|
|
static int cg_iLastEntIndex;
|
|
static int cg_iLastEntTime;
|
|
static qboolean cg_bLastEntValid;
|
|
|
|
markPoly_t* cg_freeMarkPolys; // single linked list
|
|
markObj_t cg_activeMarkObjs;
|
|
markObj_t* cg_freeMarkObjs;
|
|
treadMark_t cg_treadMarks[MAX_TREAD_MARKS];
|
|
|
|
markPoly_t* cg_markPolys;
|
|
int cg_iNumMarkPolys;
|
|
markObj_t* cg_markObjs;
|
|
int cg_iNumFreeMarkObjs;
|
|
int cg_iMinFreeMarkObjs;
|
|
qboolean cg_bMarksInitialized;
|
|
cvar_t* cg_treadmark_test;
|
|
|
|
vec3_t vec_upwards;
|
|
|
|
typedef struct cg_impactmarkinfo_s {
|
|
vec3_t axis[3];
|
|
vec3_t origin;
|
|
float fSCenter;
|
|
float fTCenter;
|
|
float texCoordScaleS;
|
|
float texCoordScaleT;
|
|
byte colors[4];
|
|
int leafnum;
|
|
} cg_impactmarkinfo_t;
|
|
|
|
typedef struct cg_treadmarkinfo_s {
|
|
treadMark_t* pTread;
|
|
vec3_t vDirection;
|
|
vec3_t vRight;
|
|
float fStartDist;
|
|
float fStartTex;
|
|
float fStartAlpha;
|
|
float fLeftStartDist;
|
|
float fRightStartDist;
|
|
float fRightCenterDist;
|
|
float fLeftTexScale;
|
|
float fRightTexScale;
|
|
float fCenterTexScale;
|
|
float fLeftAlphaScale;
|
|
float fRightAlphaScale;
|
|
float fCenterAlphaScale;
|
|
float fOOWidth;
|
|
float fOODoubleWidth;
|
|
byte colors[4];
|
|
int leafnum;
|
|
} cg_treadmarkinfo_t;
|
|
|
|
int CG_GetMarkFragments(
|
|
int numVerts,
|
|
const vec3_t* pVerts,
|
|
const vec3_t vProjection,
|
|
const vec3_t* pPointBuffer,
|
|
markFragment_t* pFragmentBuffer,
|
|
float fRadiusSquared
|
|
) {
|
|
int i, j;
|
|
int iNumEnts;
|
|
int iCurrPoints, iCurrFragments;
|
|
int iCurrMaxPoints, iCurrMaxFragments;
|
|
int iNewPoints, iNewFragments;
|
|
clipHandle_t cmodel;
|
|
vec3_t vProjectionDir;
|
|
vec3_t vMins, vMaxs;
|
|
vec3_t vTemp;
|
|
vec3_t vAngles, vOrigin;
|
|
const vec3_t* pCurrPoint;
|
|
centity_t* pEntList[64];
|
|
entityState_t* pEnt;
|
|
markFragment_t* pCurrFragment;
|
|
markFragment_t* pFragment;
|
|
|
|
iNewFragments = cgi.R_MarkFragments(
|
|
numVerts,
|
|
pVerts,
|
|
vProjection,
|
|
MAX_MARK_POINTS,
|
|
(float*)pPointBuffer,
|
|
MAX_MARK_FRAGMENTS,
|
|
pFragmentBuffer,
|
|
fRadiusSquared
|
|
);
|
|
|
|
if (iNewFragments > MAX_MARK_FRAGMENTS) {
|
|
return iNewFragments;
|
|
}
|
|
|
|
iNewPoints = 0;
|
|
for (i = 0, pFragment = pFragmentBuffer; i < iNewFragments; i++, pFragment++) {
|
|
iNewPoints += pFragment->numPoints;
|
|
}
|
|
|
|
if (iNewPoints >= MAX_MARK_POINTS) {
|
|
return iNewFragments;
|
|
}
|
|
|
|
iCurrPoints = iNewPoints;
|
|
iCurrFragments = iNewFragments;
|
|
iCurrMaxPoints = MAX_MARK_POINTS - iNewPoints;
|
|
iCurrMaxFragments = MAX_MARK_FRAGMENTS - iNewFragments;
|
|
pCurrPoint = &pPointBuffer[iNewPoints];
|
|
pCurrFragment = &pFragmentBuffer[iNewFragments];
|
|
|
|
VectorNormalize2(vProjection, vProjectionDir);
|
|
ClearBounds(vMins, vMaxs);
|
|
|
|
for (i = 0; i < numVerts; i++) {
|
|
AddPointToBounds(pVerts[i], vMins, vMaxs);
|
|
VectorAdd(pVerts[i], vProjection, vTemp);
|
|
AddPointToBounds(vTemp, vMins, vMaxs);
|
|
VectorMA(pVerts[i], -20.0f, vProjectionDir, vTemp);
|
|
AddPointToBounds(vTemp, vMins, vMaxs);
|
|
}
|
|
|
|
iNumEnts = CG_GetBrushEntitiesInBounds(ARRAY_LEN(pEntList), pEntList, vMins, vMaxs);
|
|
if (!iNumEnts) {
|
|
return iNewFragments;
|
|
}
|
|
|
|
for (i = 0; i < iNumEnts; i++) {
|
|
pEnt = &pEntList[i]->currentState;
|
|
VectorCopy(pEntList[i]->lerpAngles, vAngles);
|
|
VectorCopy(pEntList[i]->lerpOrigin, vOrigin);
|
|
|
|
cmodel = cgi.CM_InlineModel(pEnt->modelindex);
|
|
|
|
iNewFragments = cgi.R_MarkFragmentsForInlineModel(
|
|
cmodel,
|
|
vAngles,
|
|
vOrigin,
|
|
numVerts,
|
|
pVerts,
|
|
vProjection,
|
|
iCurrMaxPoints,
|
|
(float*)pCurrPoint,
|
|
iCurrMaxFragments,
|
|
pCurrFragment,
|
|
fRadiusSquared
|
|
);
|
|
|
|
iCurrFragments += iNewFragments;
|
|
if (iCurrFragments >= MAX_MARK_FRAGMENTS) {
|
|
break;
|
|
}
|
|
|
|
iNewPoints = 0;
|
|
for (j = 0, pFragment = pCurrFragment; j < iNewPoints; j++, pFragment++) {
|
|
pFragment->firstPoint += iCurrPoints;
|
|
pFragment->iIndex = -pEnt->number;
|
|
iNewPoints += pFragment->numPoints;
|
|
}
|
|
|
|
iCurrPoints += iNewPoints;
|
|
if (iCurrPoints >= MAX_MARK_POINTS) {
|
|
break;
|
|
}
|
|
|
|
pCurrPoint += iNewPoints;
|
|
iCurrMaxPoints -= iNewPoints;
|
|
iCurrMaxFragments -= iNewFragments;
|
|
pCurrFragment += iNewFragments;
|
|
}
|
|
|
|
return iCurrFragments;
|
|
}
|
|
|
|
static qboolean CG_GetMarkInlineModelOrientation(int iIndex) {
|
|
centity_t* pCEnt;
|
|
|
|
if (iIndex == cg_iLastEntIndex && cg_iLastEntTime == cg.time) {
|
|
return cg_bLastEntValid;
|
|
}
|
|
|
|
pCEnt = &cg_entities[-iIndex];
|
|
if (pCEnt->currentValid && pCEnt->currentState.modelindex < cgs.inlineDrawModel[ENTITYNUM_NONE])
|
|
{
|
|
VectorCopy(pCEnt->lerpAngles, cg_vEntAngles);
|
|
VectorCopy(pCEnt->lerpOrigin, cg_vEntOrigin);
|
|
|
|
if (cg_vEntAngles[0] || cg_vEntAngles[1] || cg_vEntAngles[2]) {
|
|
AngleVectorsLeft(cg_vEntAngles, cg_fEntAxis[0], cg_fEntAxis[1], cg_fEntAxis[2]);
|
|
cg_bEntAnglesSet = qtrue;
|
|
} else if (cg_bEntAnglesSet) {
|
|
AxisClear(cg_fEntAxis);
|
|
cg_bEntAnglesSet = qfalse;
|
|
}
|
|
|
|
cg_iLastEntIndex = iIndex;
|
|
cg_iLastEntTime = cg.time;
|
|
return cg_bLastEntValid;
|
|
}
|
|
|
|
cg_bLastEntValid = qfalse;
|
|
VectorClear(cg_vEntAngles);
|
|
VectorClear(cg_vEntOrigin);
|
|
|
|
if (cg_bEntAnglesSet) {
|
|
AxisClear(cg_fEntAxis);
|
|
cg_bEntAnglesSet = qfalse;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
static void CG_FragmentPosToWorldPos(const vec3_t vFrom, vec3_t vTo) {
|
|
if (cg_bEntAnglesSet)
|
|
{
|
|
VectorMA(cg_vEntOrigin, vFrom[0], cg_fEntAxis[0], vTo);
|
|
VectorMA(vTo, vFrom[1], cg_fEntAxis[1], vTo);
|
|
VectorMA(vTo, vFrom[2], cg_fEntAxis[2], vTo);
|
|
}
|
|
else
|
|
{
|
|
vTo[0] = cg_vEntOrigin[0] + *vFrom;
|
|
vTo[1] = cg_vEntOrigin[1] + vFrom[1];
|
|
vTo[2] = cg_vEntOrigin[2] + vFrom[2];
|
|
}
|
|
}
|
|
|
|
qboolean CG_UpdateMarkPosition(markObj_t* pMark) {
|
|
vec3_t v;
|
|
vec3_t pt;
|
|
int iIndex;
|
|
|
|
iIndex = pMark->markPolys->iIndex;
|
|
if (!CG_GetMarkInlineModelOrientation(iIndex)) {
|
|
return qfalse;
|
|
}
|
|
|
|
VectorCopy(pMark->markPolys->verts[0].xyz, v);
|
|
|
|
if (cg_bEntAnglesSet)
|
|
{
|
|
VectorScale(cg_fEntAxis[0], v[0], pt);
|
|
VectorMA(pt, v[1], cg_fEntAxis[1], pt);
|
|
VectorMA(pt, v[2], cg_fEntAxis[2], pt);
|
|
VectorAdd(pt, cg_vEntOrigin, pt);
|
|
} else {
|
|
VectorAdd(v, cg_vEntOrigin, pt);
|
|
}
|
|
|
|
pMark->leafnum = cgi.CM_PointLeafnum(pt);
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
void CG_AddFragmentToScene(
|
|
int iIndex,
|
|
qhandle_t hShader,
|
|
int iNumVerts,
|
|
polyVert_t* pVerts
|
|
) {
|
|
if (!iIndex) {
|
|
cgi.R_AddPolyToScene(hShader, iNumVerts, pVerts, 0);
|
|
return;
|
|
}
|
|
|
|
if (iIndex > 0) {
|
|
cgi.R_AddTerrainMarkToScene(iIndex, hShader, iNumVerts, pVerts, 0);
|
|
return;
|
|
}
|
|
|
|
if (cg_bEntAnglesSet) {
|
|
int i;
|
|
vec3_t vTmp;
|
|
polyVert_t* pCurrVert;
|
|
|
|
for (i = 0; i < iNumVerts; i++) {
|
|
pCurrVert = &pVerts[i];
|
|
|
|
vTmp[0] = cg_fEntAxis[0][0] * pCurrVert->xyz[0];
|
|
vTmp[1] = cg_fEntAxis[0][1] * pCurrVert->xyz[0];
|
|
vTmp[2] = cg_fEntAxis[0][2] * pCurrVert->xyz[0];
|
|
VectorMA(vTmp, pCurrVert->xyz[1], cg_fEntAxis[1], vTmp);
|
|
VectorMA(vTmp, pCurrVert->xyz[2], cg_fEntAxis[2], vTmp);
|
|
VectorAdd(vTmp, cg_vEntOrigin, pCurrVert->xyz);
|
|
}
|
|
} else {
|
|
int i;
|
|
polyVert_t* pCurrVert;
|
|
|
|
for (i = 0; i < iNumVerts; i++) {
|
|
pCurrVert = &pVerts[i];
|
|
VectorAdd(pCurrVert->xyz, cg_vEntOrigin, pCurrVert->xyz);
|
|
}
|
|
}
|
|
|
|
cgi.R_AddPolyToScene(hShader, iNumVerts, pVerts, 0);
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_InitMarkPolys
|
|
|
|
This is called at startup and for tournement restarts
|
|
===================
|
|
*/
|
|
void CG_InitMarks(void)
|
|
{
|
|
int i;
|
|
int iMaxMarks;
|
|
|
|
iMaxMarks = cg_maxMarks->integer;
|
|
if (iMaxMarks < 32) {
|
|
iMaxMarks = 32;
|
|
}
|
|
|
|
cg_iMinFreeMarkObjs = iMaxMarks / 32;
|
|
if (cg_iMinFreeMarkObjs < 8) {
|
|
cg_iMinFreeMarkObjs = 8;
|
|
}
|
|
if (cg_iMinFreeMarkObjs > 32) {
|
|
cg_iMinFreeMarkObjs = 32;
|
|
}
|
|
|
|
cg_iNumMarkPolys = 7 * iMaxMarks / 4;
|
|
|
|
if (cg_markPolys) {
|
|
cgi.Free(cg_markPolys);
|
|
}
|
|
|
|
cg_markPolys = (markPoly_t*)cgi.Malloc(sizeof(markPoly_t) * cg_iNumMarkPolys);
|
|
|
|
if (!cg_markPolys) {
|
|
cgi.Error(ERR_DROP, "CG_InitMarks: Could not allocate array for mark polys");
|
|
}
|
|
|
|
if (cg_markObjs) {
|
|
cgi.Free(cg_markObjs);
|
|
}
|
|
|
|
cg_markObjs = (markObj_t*)cgi.Malloc(sizeof(markObj_t) * iMaxMarks);
|
|
|
|
if (!cg_markObjs) {
|
|
cgi.Error(ERR_DROP, "CG_InitMarks: Could not allocate array for mark objects");
|
|
}
|
|
|
|
memset(cg_markPolys, 0, sizeof(markPoly_t) * cg_iNumMarkPolys);
|
|
|
|
cg_freeMarkPolys = cg_markPolys;
|
|
for (i = 0; i < cg_iNumMarkPolys - 1; i++) {
|
|
cg_markPolys[i].nextPoly = &cg_markPolys[i + 1];
|
|
}
|
|
cg_markPolys[i].nextPoly = NULL;
|
|
|
|
cg_activeMarkObjs.nextMark = &cg_activeMarkObjs;
|
|
cg_activeMarkObjs.prevMark = &cg_activeMarkObjs;
|
|
memset(cg_markObjs, 0, sizeof(markObj_t) * iMaxMarks);
|
|
cg_freeMarkObjs = cg_markObjs;
|
|
|
|
for (i = 0; i < iMaxMarks - 1; i++) {
|
|
cg_markObjs[i].nextMark = &cg_markObjs[i + 1];
|
|
}
|
|
cg_markObjs[i].nextMark = NULL;
|
|
|
|
cg_iNumFreeMarkObjs = iMaxMarks;
|
|
cg_bMarksInitialized = qtrue;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_FreeMarkPoly
|
|
==================
|
|
*/
|
|
void CG_FreeMarkPoly(markPoly_t *le)
|
|
{
|
|
if (!cg_bMarksInitialized) {
|
|
return;
|
|
}
|
|
|
|
le->nextPoly = cg_freeMarkPolys;
|
|
cg_freeMarkPolys = le;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_FreeMarkObj
|
|
==================
|
|
*/
|
|
void CG_FreeMarkObj(markObj_t* pMark) {
|
|
markPoly_t* pPoly;
|
|
markPoly_t* pNextPoly;
|
|
|
|
pPoly = pMark->markPolys;
|
|
for (pPoly = pMark->markPolys; pPoly; pPoly = pNextPoly) {
|
|
pNextPoly = pPoly->nextPoly;
|
|
CG_FreeMarkPoly(pPoly);
|
|
}
|
|
|
|
pMark->prevMark->nextMark = pMark->nextMark;
|
|
pMark->nextMark->prevMark = pMark->prevMark;
|
|
pMark->nextMark = cg_freeMarkObjs;
|
|
cg_freeMarkObjs = pMark;
|
|
cg_iNumFreeMarkObjs++;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_FreeBestMarkObj
|
|
==================
|
|
*/
|
|
void CG_FreeBestMarkObj(qboolean bAllowFade) {
|
|
markObj_t* pMark;
|
|
|
|
for (pMark = cg_activeMarkObjs.prevMark; pMark != &cg_activeMarkObjs; pMark = pMark->prevMark)
|
|
{
|
|
if (pMark->lastVisTime < cg.time - 250)
|
|
{
|
|
CG_FreeMarkObj(pMark);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!cg_iNumFreeMarkObjs || !bAllowFade) {
|
|
CG_FreeMarkObj(cg_activeMarkObjs.prevMark);
|
|
return;
|
|
}
|
|
|
|
for (pMark = cg_activeMarkObjs.prevMark; pMark != &cg_activeMarkObjs; pMark = pMark->prevMark)
|
|
{
|
|
if (!pMark->alphaFade || pMark->time > cg.time - 9000) {
|
|
pMark->time = cg.time - 9000;
|
|
pMark->alphaFade = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_AllocMark
|
|
|
|
Will allways succeed, even if it requires freeing an old active mark
|
|
===================
|
|
*/
|
|
markObj_t* CG_AllocMark(int iNumPolys)
|
|
{
|
|
int iPolyCount;
|
|
markPoly_t* pPoly;
|
|
markObj_t* pMark;
|
|
|
|
if (!cg_bMarksInitialized) {
|
|
return NULL;
|
|
}
|
|
|
|
if (iNumPolys < 1) {
|
|
return NULL;
|
|
}
|
|
|
|
if (cg_iNumFreeMarkObjs <= cg_iMinFreeMarkObjs) {
|
|
CG_FreeBestMarkObj(1);
|
|
}
|
|
|
|
pMark = cg_freeMarkObjs;
|
|
cg_freeMarkObjs = cg_freeMarkObjs->nextMark;
|
|
|
|
memset(pMark, 0, sizeof(markObj_t));
|
|
pMark->lastVisTime = cg.time;
|
|
|
|
for (iPolyCount = 0; iPolyCount < iNumPolys; iPolyCount++)
|
|
{
|
|
while (!cg_freeMarkPolys) {
|
|
CG_FreeBestMarkObj(qfalse);
|
|
}
|
|
|
|
pPoly = cg_freeMarkPolys;
|
|
cg_freeMarkPolys = pPoly->nextPoly;
|
|
|
|
memset(pPoly, 0, sizeof(*pPoly));
|
|
pPoly->nextPoly = pMark->markPolys;
|
|
pMark->markPolys = pPoly;
|
|
}
|
|
|
|
|
|
// link into the active list
|
|
pMark->nextMark = cg_activeMarkObjs.nextMark;
|
|
pMark->prevMark = &cg_activeMarkObjs;
|
|
cg_activeMarkObjs.nextMark->prevMark = pMark;
|
|
cg_activeMarkObjs.nextMark = pMark;
|
|
cg_iNumFreeMarkObjs--;
|
|
return pMark;
|
|
}
|
|
|
|
int CG_ImpactMark_GetLeafCallback(markFragment_t* mf, void* pCustom) {
|
|
return ((cg_impactmarkinfo_t*)pCustom)->leafnum;
|
|
}
|
|
|
|
qboolean CG_ImpactMark_PerPolyCallback(
|
|
const vec3_t* markPoints,
|
|
markFragment_t* mf,
|
|
polyVert_t* verts,
|
|
void* pCustom
|
|
) {
|
|
int j;
|
|
polyVert_t* v;
|
|
cg_impactmarkinfo_t* pInfo;
|
|
|
|
pInfo = (cg_impactmarkinfo_t*)pCustom;
|
|
|
|
if (mf->iIndex >= 0)
|
|
{
|
|
for (j = 0; j < mf->numPoints; j++) {
|
|
vec3_t delta;
|
|
|
|
v = &verts[j];
|
|
VectorCopy(markPoints[j + mf->firstPoint], v->xyz);
|
|
VectorSubtract(v->xyz, pInfo->origin, delta);
|
|
v->st[0] = pInfo->fSCenter + DotProduct(delta, pInfo->axis[1]) * pInfo->texCoordScaleS;
|
|
v->st[1] = pInfo->fTCenter + DotProduct(delta, pInfo->axis[2]) * pInfo->texCoordScaleT;
|
|
v->modulate[0] = pInfo->colors[0];
|
|
v->modulate[1] = pInfo->colors[1];
|
|
v->modulate[2] = pInfo->colors[2];
|
|
v->modulate[3] = pInfo->colors[3];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!CG_GetMarkInlineModelOrientation(mf->iIndex)) {
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
for (j = 0; j < mf->numPoints; j++) {
|
|
vec3_t vWorldPos;
|
|
vec3_t delta;
|
|
|
|
v = &verts[j];
|
|
VectorCopy(markPoints[j + mf->firstPoint], v->xyz);
|
|
|
|
CG_FragmentPosToWorldPos(v->xyz, vWorldPos);
|
|
VectorSubtract(vWorldPos, pInfo->origin, delta);
|
|
v->st[0] = pInfo->fSCenter + DotProduct(delta, pInfo->axis[1]) * pInfo->texCoordScaleS;
|
|
v->st[1] = pInfo->fTCenter + DotProduct(delta, pInfo->axis[2]) * pInfo->texCoordScaleT;
|
|
v->modulate[0] = pInfo->colors[0];
|
|
v->modulate[1] = pInfo->colors[1];
|
|
v->modulate[2] = pInfo->colors[2];
|
|
v->modulate[3] = pInfo->colors[3];
|
|
}
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_ImpactMark
|
|
|
|
origin should be a point within a unit of the plane
|
|
dir should be the plane normal
|
|
|
|
temporary marks will not be stored or randomly oriented, but immediately
|
|
passed to the renderer.
|
|
=================
|
|
*/
|
|
|
|
void CG_ImpactMark(
|
|
qhandle_t markShader,
|
|
const vec3_t origin,
|
|
const vec3_t dir,
|
|
float orientation,
|
|
float fSScale,
|
|
float fTScale,
|
|
float red,
|
|
float green,
|
|
float blue,
|
|
float alpha,
|
|
qboolean alphaFade,
|
|
qboolean temporary,
|
|
qboolean dolighting,
|
|
qboolean fadein,
|
|
float fSCenter,
|
|
float fTCenter
|
|
)
|
|
{
|
|
int i;
|
|
int numFragments;
|
|
float fSScale2, fTScale2;
|
|
float fSScale3, fTScale3;
|
|
vec3_t originalPoints[4];
|
|
vec3_t markPoints[MAX_MARK_POINTS];
|
|
vec3_t projection;
|
|
markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf;
|
|
vec3_t v;
|
|
float fRadiusSquared;
|
|
cg_impactmarkinfo_t info;
|
|
|
|
if (!cg_bMarksInitialized) {
|
|
return;
|
|
}
|
|
|
|
if (!cg_addMarks->integer
|
|
&& markShader != cgs.media.shadowMarkShader
|
|
&& markShader != cgs.media.footShadowMarkShader) {
|
|
return;
|
|
}
|
|
|
|
if (fSScale == 0.0) {
|
|
fSScale = 1.0;
|
|
}
|
|
if (fTScale == 0.0) {
|
|
fTScale = 1.0;
|
|
}
|
|
|
|
fRadiusSquared = fSScale * fSScale + fTScale * fTScale;
|
|
info.texCoordScaleS = 0.5 / fSScale;
|
|
info.texCoordScaleT = 0.5 / fTScale;
|
|
|
|
if (fSCenter < 0.0f || fSCenter > 1.0f) {
|
|
fSCenter = 0.5f;
|
|
}
|
|
|
|
if (fTCenter < 0.0f || fTCenter > 1.0f) {
|
|
fTCenter = 0.5f;
|
|
}
|
|
|
|
fSScale2 = (fSCenter + fSCenter) * fSScale;
|
|
fTScale2 = (fTCenter + fTCenter) * fTScale;
|
|
fSScale3 = (1.0 - fSCenter) * fSScale + (1.0 - fSCenter) * fSScale;
|
|
fTScale3 = (1.0 - fTCenter) * fTScale + (1.0 - fTCenter) * fTScale;
|
|
|
|
// create the texture axis
|
|
|
|
if (orientation) {
|
|
VectorNormalize2(dir, info.axis[0]);
|
|
PerpendicularVector(info.axis[1], info.axis[0]);
|
|
RotatePointAroundVector(info.axis[2], info.axis[0], info.axis[1], orientation);
|
|
CrossProduct(info.axis[0], info.axis[2], info.axis[1]);
|
|
} else {
|
|
vec3_t angles;
|
|
vec3_t tmp;
|
|
|
|
VectorNormalize2(dir, info.axis[0]);
|
|
VectorCopy(dir, tmp);
|
|
vectoangles(tmp, angles);
|
|
AnglesToAxis(angles, info.axis);
|
|
VectorScale(info.axis[2], -1, info.axis[2]);
|
|
}
|
|
|
|
// create the full polygon
|
|
for (i = 0; i < 3; i++) {
|
|
originalPoints[0][i] = origin[i] - fSScale2 * info.axis[1][i] - fTScale2 * info.axis[2][i];
|
|
originalPoints[1][i] = origin[i] + fSScale3 * info.axis[1][i] - fTScale2 * info.axis[2][i];
|
|
originalPoints[2][i] = origin[i] + fSScale3 * info.axis[1][i] + fTScale3 * info.axis[2][i];
|
|
originalPoints[3][i] = origin[i] - fSScale2 * info.axis[1][i] + fTScale3 * info.axis[2][i];
|
|
}
|
|
|
|
// get the fragments
|
|
VectorScale(dir, -32, projection);
|
|
|
|
numFragments = CG_GetMarkFragments(
|
|
4,
|
|
originalPoints,
|
|
projection,
|
|
markPoints,
|
|
markFragments,
|
|
fRadiusSquared
|
|
);
|
|
|
|
if (dolighting)
|
|
{
|
|
vec3_t vLight;
|
|
cgi.R_GetLightingForDecal(vLight, dir, origin);
|
|
|
|
info.colors[0] = (int)(red * vLight[0]);
|
|
info.colors[1] = (int)(green * vLight[1]);
|
|
info.colors[2] = (int)(blue * vLight[2]);
|
|
}
|
|
else
|
|
{
|
|
info.colors[0] = (int)(red * 255.0f);
|
|
info.colors[1] = (int)(green * 255.0f);
|
|
info.colors[2] = (int)(blue * 255.0f);
|
|
}
|
|
|
|
if (fadein) {
|
|
info.colors[3] = 0;
|
|
} else {
|
|
info.colors[3] = (int)(alpha * 255.0);
|
|
}
|
|
|
|
info.fSCenter = fSCenter;
|
|
info.fTCenter = fTCenter;
|
|
VectorCopy(origin, info.origin);
|
|
VectorAdd(origin, dir, v);
|
|
info.leafnum = cgi.CM_PointLeafnum(v);
|
|
|
|
if (temporary)
|
|
{
|
|
polyVert_t verts[8];
|
|
|
|
for (i = 0, mf = markFragments; i < numFragments; i++, mf++) {
|
|
if (mf->numPoints > 8) {
|
|
mf->numPoints = 8;
|
|
}
|
|
|
|
if (CG_ImpactMark_PerPolyCallback(markPoints, mf, verts, (void*)&info)) {
|
|
CG_AddFragmentToScene(mf->iIndex, markShader, mf->numPoints, verts);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_AssembleFinalMarks(
|
|
markPoints,
|
|
markFragments,
|
|
numFragments,
|
|
&CG_ImpactMark_PerPolyCallback,
|
|
&CG_ImpactMark_GetLeafCallback,
|
|
(void*)&info,
|
|
info.origin,
|
|
sqrt(fRadiusSquared),
|
|
markShader,
|
|
fadein,
|
|
alphaFade
|
|
);
|
|
}
|
|
}
|
|
|
|
void CG_AssembleFinalMarks(
|
|
vec3_t *markPoints,
|
|
markFragment_t *markFragments,
|
|
int numFragments,
|
|
qboolean (*PerPolyCallback)(const vec3_t* markPoints, markFragment_t* mf, polyVert_t* verts, void* pCustom),
|
|
int (*GetLeafCallback)(markFragment_t* mf, void* pCustom),
|
|
void *pCustom,
|
|
vec3_t pos,
|
|
float radius,
|
|
qhandle_t markShader,
|
|
qboolean fadein,
|
|
qboolean alphaFade
|
|
)
|
|
{
|
|
markObj_t* pMark;
|
|
markPoly_t* pPoly;
|
|
int iGroup, iFirstNewGroup;
|
|
int numFragsForMark;
|
|
int i;
|
|
markFragment_t* mf;
|
|
|
|
iFirstNewGroup = 0;
|
|
do
|
|
{
|
|
i = iFirstNewGroup;
|
|
mf = &markFragments[iFirstNewGroup];
|
|
iGroup = mf->iIndex;
|
|
numFragsForMark = 0;
|
|
|
|
for (; i < numFragments; i++, mf++) {
|
|
if (iGroup < 0 && mf->iIndex == iGroup) {
|
|
numFragsForMark++;
|
|
} else if (iGroup >= 0 && mf->iIndex >= 0) {
|
|
numFragsForMark++;
|
|
}
|
|
}
|
|
|
|
pMark = CG_AllocMark(numFragsForMark);
|
|
if (!pMark) {
|
|
break;
|
|
}
|
|
|
|
pMark->time = cg.time;
|
|
pMark->alphaFade = alphaFade;
|
|
pMark->markShader = markShader;
|
|
pMark->fadein = fadein;
|
|
VectorCopy(pos, pMark->pos);
|
|
pMark->radius = radius;
|
|
|
|
i = iFirstNewGroup;
|
|
mf = &markFragments[iFirstNewGroup];
|
|
iFirstNewGroup = 0;
|
|
|
|
if (mf->iIndex < 0) {
|
|
VectorSubtract(pMark->pos, cg_entities[-mf->iIndex].lerpOrigin, pMark->pos);
|
|
}
|
|
|
|
pPoly = pMark->markPolys;
|
|
for (; i < numFragments; i++, mf++) {
|
|
if (iGroup < 0 && mf->iIndex != iGroup) {
|
|
if (!iFirstNewGroup) {
|
|
iFirstNewGroup = i;
|
|
}
|
|
} else if (iGroup >= 0 && mf->iIndex < 0) {
|
|
if (!iFirstNewGroup) {
|
|
iFirstNewGroup = i;
|
|
}
|
|
} else {
|
|
if (mf->numPoints > 8) {
|
|
mf->numPoints = 8;
|
|
}
|
|
|
|
if (PerPolyCallback(markPoints, mf, pPoly->verts, pCustom)) {
|
|
pPoly->numVerts = mf->numPoints;
|
|
pPoly->iIndex = mf->iIndex;
|
|
pPoly = pPoly->nextPoly;
|
|
}
|
|
}
|
|
}
|
|
|
|
pMark->leafnum = GetLeafCallback(mf, pCustom);
|
|
} while (iFirstNewGroup);
|
|
}
|
|
|
|
void CG_ImpactMarkSimple(
|
|
qhandle_t markShader,
|
|
const vec3_t origin,
|
|
const vec3_t dir,
|
|
float orientation,
|
|
float fRadius,
|
|
float red,
|
|
float green,
|
|
float blue,
|
|
float alpha,
|
|
qboolean alphaFade,
|
|
qboolean temporary,
|
|
qboolean dolighting,
|
|
qboolean fadein
|
|
)
|
|
{
|
|
if (cg_bMarksInitialized) {
|
|
CG_ImpactMark(
|
|
markShader,
|
|
origin,
|
|
dir,
|
|
orientation,
|
|
fRadius,
|
|
fRadius,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha,
|
|
alphaFade,
|
|
temporary,
|
|
dolighting,
|
|
fadein,
|
|
0.5f,
|
|
0.5f
|
|
);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_AddMarks
|
|
===============
|
|
*/
|
|
#define MARK_TOTAL_TIME 10000
|
|
#define MARK_FADE_TIME 1000
|
|
|
|
void CG_AddMarks(void)
|
|
{
|
|
int j;
|
|
int t;
|
|
int fade;
|
|
markObj_t* pMark;
|
|
markObj_t* pNext;
|
|
markPoly_t* pPoly;
|
|
polyVert_t* pVert;
|
|
polyVert_t tmpVerts[8];
|
|
int viewleafnum;
|
|
|
|
if (!cg_bMarksInitialized) {
|
|
return;
|
|
}
|
|
|
|
if (!cg_addMarks->integer) {
|
|
return;
|
|
}
|
|
|
|
viewleafnum = cgi.CM_PointLeafnum(cg.refdef.vieworg);
|
|
for (pMark = cg_activeMarkObjs.nextMark; pMark != &cg_activeMarkObjs; pMark = pNext) {
|
|
// grab next now, so if the local entity is freed we
|
|
// still have it
|
|
pNext = pMark->nextMark;
|
|
|
|
// see if it is time to completely remove it
|
|
if (pMark->alphaFade && cg.time > pMark->time + MARK_TOTAL_TIME)
|
|
{
|
|
CG_FreeMarkObj(pMark);
|
|
continue;
|
|
}
|
|
|
|
if (pMark->markPolys->iIndex < 0 && !CG_UpdateMarkPosition(pMark)) {
|
|
if (pMark->lastVisTime < cg.time - 3000) {
|
|
CG_FreeMarkObj(pMark);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!cgi.CM_LeafInPVS(viewleafnum, pMark->leafnum)) {
|
|
continue;
|
|
}
|
|
|
|
if (pMark->markPolys->iIndex < 0) {
|
|
if (CG_FrustumCullSphere(pMark->pos, pMark->radius)) {
|
|
continue;
|
|
}
|
|
} else {
|
|
vec3_t vWorldPos;
|
|
CG_FragmentPosToWorldPos(pMark->pos, vWorldPos);
|
|
if (CG_FrustumCullSphere(vWorldPos, pMark->radius)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pMark->lastVisTime = cg.time;
|
|
if (pMark->fadein) {
|
|
fade = 255 * (cg.time - pMark->time) / MARK_FADE_TIME;
|
|
if (fade > 255) {
|
|
fade = 255;
|
|
pMark->fadein = 0;
|
|
}
|
|
|
|
|
|
for (pPoly = pMark->markPolys; pPoly; pPoly = pPoly->nextPoly) {
|
|
for (j = 0; j < pPoly->numVerts; j++) {
|
|
pPoly->verts[j].modulate[3] = fade;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pMark->alphaFade) {
|
|
t = pMark->time + MARK_TOTAL_TIME - cg.time;
|
|
if (t < MARK_FADE_TIME) {
|
|
fade = 255 * t / MARK_FADE_TIME;
|
|
for (pPoly = pMark->markPolys; pPoly; pPoly = pPoly->nextPoly) {
|
|
for (j = 0; j < pPoly->numVerts; j++) {
|
|
pPoly->verts[j].modulate[3] = fade;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (pPoly = pMark->markPolys; pPoly; pPoly = pPoly->nextPoly)
|
|
{
|
|
if (pPoly->iIndex < 0)
|
|
{
|
|
memcpy(tmpVerts, pPoly->verts, 24 * pPoly->numVerts);
|
|
pVert = tmpVerts;
|
|
} else {
|
|
pVert = pPoly->verts;
|
|
}
|
|
|
|
CG_AddFragmentToScene(pPoly->iIndex, pMark->markShader, pPoly->numVerts, pVert);
|
|
}
|
|
}
|
|
|
|
CG_AddTreadMarks();
|
|
}
|
|
|
|
qboolean CG_CheckMakeMarkOnEntity(int iEntIndex)
|
|
{
|
|
if (iEntIndex == ENTITYNUM_WORLD) {
|
|
return qtrue;
|
|
}
|
|
|
|
if (iEntIndex == ENTITYNUM_NONE) {
|
|
return qfalse;
|
|
}
|
|
|
|
if (cg_entities[iEntIndex].currentState.solid != SOLID_BMODEL) {
|
|
return qfalse;
|
|
}
|
|
|
|
if (cg_entities[iEntIndex].currentState.modelindex < 0 || cg_entities[iEntIndex].currentState.modelindex > cgi.CM_NumInlineModels()) {
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
void CG_InitTestTreadMark()
|
|
{
|
|
cg_treadmark_test = cgi.Cvar_Get("cg_treadmark_test", "0", 0);
|
|
}
|
|
|
|
int CG_StartTreadMark(int iReference, qhandle_t treadShader, const vec3_t vStartPos, float fWidth, float fAlpha)
|
|
{
|
|
int i;
|
|
int iTreadNum;
|
|
treadMark_t* pTread;
|
|
|
|
if (!cg_bMarksInitialized) {
|
|
return -1;
|
|
}
|
|
|
|
if (!cg_addMarks->integer) {
|
|
return -1;
|
|
}
|
|
|
|
iTreadNum = -1;
|
|
for (i = 0; i < MAX_TREAD_MARKS; i++) {
|
|
if (!cg_treadMarks[i].iState) {
|
|
iTreadNum = i;
|
|
pTread = &cg_treadMarks[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iTreadNum == -1) {
|
|
return -1;
|
|
}
|
|
|
|
memset(pTread, 0, sizeof(*pTread));
|
|
pTread->iState = 1;
|
|
pTread->iReferenceNumber = iReference;
|
|
pTread->iLastTime = cg.time;
|
|
pTread->hTreadShader = treadShader;
|
|
pTread->fWidth = fWidth * 0.5;
|
|
VectorCopy(vStartPos, pTread->vMidPos);
|
|
VectorCopy(vStartPos, pTread->vEndPos);
|
|
|
|
if (fAlpha < 0.0) {
|
|
pTread->fMidAlpha = 255.0;
|
|
} else {
|
|
pTread->fMidAlpha = fAlpha * 255.0;
|
|
}
|
|
|
|
pTread->fEndAlpha = pTread->fMidAlpha;
|
|
return iTreadNum;
|
|
}
|
|
|
|
qboolean CG_MakeTreadMarkDecal_PerPolyCallback(const vec3_t *markPoints, markFragment_t *mf, polyVert_t *verts, void *pCustom)
|
|
{
|
|
// FIXME: unimplemented
|
|
return qfalse;
|
|
}
|
|
|
|
int CG_MakeTreadMarkDecal_GetLeafCallback(markFragment_t *mf, void *pCustom)
|
|
{
|
|
return ((cg_treadmarkinfo_t*)pCustom)->leafnum;
|
|
}
|
|
|
|
void CG_MakeTreadMarkDecal(treadMark_t *pTread, qboolean bStartSegment, qboolean bTemporary)
|
|
{
|
|
// FIXME: unimplemented
|
|
}
|
|
|
|
int CG_UpdateTreadMark(int iReference, vec_t *vNewPos, float fAlpha)
|
|
{
|
|
// FIXME: unimplemented
|
|
return 0;
|
|
}
|
|
|
|
void CG_AddTreadMarks()
|
|
{
|
|
trace_t trace;
|
|
vec3_t vPos, vEnd;
|
|
int i;
|
|
|
|
if (cg_treadmark_test->integer) {
|
|
VectorCopy(cg.predicted_player_state.origin, vPos);
|
|
VectorCopy(vPos, vEnd);
|
|
vPos[2] += 32.0f;
|
|
vEnd[2] -= 128.0f;
|
|
|
|
CG_Trace(
|
|
&trace,
|
|
vPos,
|
|
vec3_origin,
|
|
vec3_origin,
|
|
vEnd,
|
|
cg.snap->ps.clientNum,
|
|
MASK_SHOT,
|
|
qfalse,
|
|
qtrue,
|
|
"CG_AddTreadMarks test"
|
|
);
|
|
|
|
if (trace.fraction < 1.0 && CG_UpdateTreadMark(1, trace.endpos, 0.f) == -1) {
|
|
qhandle_t shader = cgi.R_RegisterShader("testtread");
|
|
|
|
CG_StartTreadMark(1, shader, trace.endpos, cg_treadmark_test->integer, 1.0f);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < MAX_TREAD_MARKS; i++) {
|
|
if (!cg_treadMarks[i].iState) {
|
|
continue;
|
|
}
|
|
|
|
if (cg.time - cg_treadMarks[i].iLastTime > 500) {
|
|
CG_MakeTreadMarkDecal(
|
|
&cg_treadMarks[i],
|
|
cg_treadMarks[i].iState == 3 ? qtrue : qfalse,
|
|
qfalse
|
|
);
|
|
cg_treadMarks[i].iState = 0;
|
|
}
|
|
else {
|
|
CG_MakeTreadMarkDecal(
|
|
&cg_treadMarks[i],
|
|
cg_treadMarks[i].iState == 3 ? qtrue : qfalse,
|
|
qtrue
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
int CG_PermanentMark(
|
|
const vec3_t origin,
|
|
const vec3_t dir,
|
|
float orientation,
|
|
float fSScale,
|
|
float fTScale,
|
|
float red,
|
|
float green,
|
|
float blue,
|
|
float alpha,
|
|
qboolean dolighting,
|
|
float fSCenter,
|
|
float fTCenter,
|
|
markFragment_t *pMarkFragments,
|
|
void *pVoidPolyVerts
|
|
)
|
|
{
|
|
// FIXME: unimplemented
|
|
return 0;
|
|
}
|
|
|
|
int CG_PermanentTreadMarkDecal(
|
|
treadMark_t *pTread,
|
|
qboolean bStartSegment,
|
|
qboolean dolighting,
|
|
markFragment_t *pMarkFragments,
|
|
void *pVoidPolyVerts
|
|
)
|
|
{
|
|
// FIXME: unimplemented
|
|
return 0;
|
|
}
|
|
|
|
int CG_PermanentUpdateTreadMark(
|
|
treadMark_t *pTread, float fAlpha, float fMinSegment, float fMaxSegment, float fMaxOffset, float fTexScale
|
|
)
|
|
{
|
|
// FIXME: unimplemented
|
|
return 0;
|
|
}
|