/* =========================================================================== 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 =================================================================== */ markPoly_t cg_activeMarkPolys; // double linked list markPoly_t *cg_freeMarkPolys; // single linked list markPoly_t cg_markPolys[MAX_MARK_POLYS]; vec3_t vec_upwards; /* =================== CG_InitMarkPolys This is called at startup and for tournement restarts =================== */ void CG_InitMarks(void) { int i; memset(cg_markPolys, 0, sizeof(cg_markPolys)); cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; cg_freeMarkPolys = cg_markPolys; for (i = 0; i < MAX_MARK_POLYS - 1; i++) { cg_markPolys[i].nextMark = &cg_markPolys[i + 1]; } } /* ================== CG_FreeMarkPoly ================== */ void CG_FreeMarkPoly(markPoly_t* le) { if (!le->prevMark) { cgi.Error(ERR_DROP, "CG_FreeLocalEntity: not active"); } // remove from the doubly linked active list le->prevMark->nextMark = le->nextMark; le->nextMark->prevMark = le->prevMark; // the free list is only singly linked le->nextMark = cg_freeMarkPolys; cg_freeMarkPolys = le; } /* =================== CG_AllocMark Will allways succeed, even if it requires freeing an old active mark =================== */ markPoly_t* CG_AllocMark(void) { markPoly_t* le; if (!cg_freeMarkPolys) { int time; // no free entities, so free the one at the end of the chain // remove the oldest active entity time = cg_activeMarkPolys.prevMark->time; while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { CG_FreeMarkPoly(cg_activeMarkPolys.prevMark); } } le = cg_freeMarkPolys; cg_freeMarkPolys = cg_freeMarkPolys->nextMark; memset(le, 0, sizeof(*le)); // link into the active list le->nextMark = cg_activeMarkPolys.nextMark; le->prevMark = &cg_activeMarkPolys; cg_activeMarkPolys.nextMark->prevMark = le; cg_activeMarkPolys.nextMark = le; return le; } /* ================= 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. ================= */ #define MAX_MARK_FRAGMENTS 128 #define MAX_MARK_POINTS 384 void CG_ImpactMark ( qhandle_t markShader, const vec3_t origin, const vec3_t dir, float orientation, float red, float green, float blue, float alpha, qboolean alphaFade, float radius, qboolean temporary, int lightstyle, qboolean fadein, float fSCenter, float fTCenter ) { vec3_t axis[3]; float texCoordScale; vec3_t originalPoints[4]; byte colors[4]; int i, j; int numFragments; markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; vec3_t markPoints[MAX_MARK_POINTS]; vec3_t projection; if (!cg_addMarks->integer) { return; } if (radius <= 0) { cgi.Error(ERR_DROP, "CG_ImpactMark called with <= 0 radius"); } // create the texture axis if (orientation) { VectorNormalize2(dir, axis[0]); PerpendicularVector(axis[1], axis[0]); RotatePointAroundVector(axis[2], axis[0], axis[1], orientation); CrossProduct(axis[0], axis[2], axis[1]); } else { vec3_t angles; vec3_t tmp; VectorNormalize2(dir, axis[0]); VectorCopy(dir, tmp); tmp[0] = -tmp[0]; tmp[1] = -tmp[1]; vectoangles(tmp, angles); AnglesToMat(angles, axis); VectorScale(axis[2], -1, axis[2]); } texCoordScale = 0.5 * 1.0 / radius; // create the full polygon for (i = 0; i < 3; i++) { originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; } // get the fragments VectorScale(dir, -32, projection); // FIXME: fRadiusSquared numFragments = cgi.R_MarkFragments(4, (void*)originalPoints, projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments, 0.f); if (fadein) { colors[0] = 0; colors[1] = 0; colors[2] = 0; colors[3] = 0; } else { colors[0] = red * 255; colors[1] = green * 255; colors[2] = blue * 255; colors[3] = alpha * 255; } for (i = 0, mf = markFragments; i < numFragments; i++, mf++) { polyVert_t* v; polyVert_t verts[MAX_VERTS_ON_POLY]; markPoly_t* mark; // we have an upper limit on the complexity of polygons // that we store persistantly if (mf->numPoints > MAX_VERTS_ON_POLY) { mf->numPoints = MAX_VERTS_ON_POLY; } for (j = 0, v = verts; j < mf->numPoints; j++, v++) { vec3_t delta; VectorCopy(markPoints[mf->firstPoint + j], v->xyz); VectorSubtract(v->xyz, origin, delta); v->st[0] = 0.5 + DotProduct(delta, axis[1]) * texCoordScale; v->st[1] = 0.5 + DotProduct(delta, axis[2]) * texCoordScale; *(int*)v->modulate = *(int*)colors; } // if it is a temporary (shadow) mark, add it immediately and forget // about it if (temporary) { cgi.R_AddPolyToScene(markShader, mf->numPoints, verts, 0); continue; } // otherwise save it persistantly mark = CG_AllocMark(); mark->time = cg.time; mark->alphaFade = alphaFade; mark->markShader = markShader; mark->poly.numVerts = mf->numPoints; mark->color[0] = red; mark->color[1] = green; mark->color[2] = blue; mark->color[3] = alpha; mark->fadein = fadein; mark->lightstyle = lightstyle; memcpy(mark->verts, verts, mf->numPoints * sizeof(verts[0])); } } void CG_ImpactMarkSimple( qhandle_t markShader, vec_t* origin, vec_t* dir, float orientation, float fRadius, float red, float green, float blue, float alpha, qboolean alphaFade, qboolean temporary, qboolean dolighting, qboolean fadein ) { // FIXME: unimplemented } /* =============== CG_AddMarks =============== */ #define MARK_TOTAL_TIME 10000 #define MARK_FADE_TIME 1000 void CG_AddMarks(void) { int j; markPoly_t *mp, *next; int t; int fade; if (!cg_addMarks->integer) { return; } mp = cg_activeMarkPolys.nextMark; for (; mp != &cg_activeMarkPolys; mp = next) { // grab next now, so if the local entity is freed we // still have it next = mp->nextMark; // see if it is time to completely remove it if (cg.time > mp->time + MARK_TOTAL_TIME) { CG_FreeMarkPoly(mp); continue; } // fade all marks out with time t = mp->time + MARK_TOTAL_TIME - cg.time; if (mp->lightstyle > 0) { CG_LightStyleColor(mp->lightstyle, t, mp->color, qtrue); if (mp->color[3] <= 0) { CG_FreeMarkPoly(mp); continue; } for (j = 0; j < mp->poly.numVerts; j++) { mp->verts[j].modulate[0] = mp->color[0] * 255; mp->verts[j].modulate[1] = mp->color[1] * 255; mp->verts[j].modulate[2] = mp->color[2] * 255; mp->verts[j].modulate[3] = mp->color[3] * 255; } } // Fade in marks if (mp->fadein) { fade = 255 * (cg.time - mp->time) / MARK_FADE_TIME; if (fade > 255) { fade = 255; } if (mp->alphaFade) { for (j = 0; j < mp->poly.numVerts; j++) { mp->verts[j].modulate[3] = fade; } } else { for (j = 0; j < mp->poly.numVerts; j++) { mp->verts[j].modulate[0] = mp->color[0] * fade; mp->verts[j].modulate[1] = mp->color[1] * fade; mp->verts[j].modulate[2] = mp->color[2] * fade; } } } // Fade out marks if (t < MARK_FADE_TIME) { fade = 255 * t / MARK_FADE_TIME; if (mp->alphaFade) { for (j = 0; j < mp->poly.numVerts; j++) { mp->verts[j].modulate[3] = fade; } } else { for (j = 0; j < mp->poly.numVerts; j++) { mp->verts[j].modulate[0] = mp->color[0] * fade; mp->verts[j].modulate[1] = mp->color[1] * fade; mp->verts[j].modulate[2] = mp->color[2] * fade; } } } cgi.R_AddPolyToScene(mp->markShader, mp->poly.numVerts, mp->verts, 0); } } qboolean CG_CheckMakeMarkOnEntity(int iEntIndex) { // FIXME: unimplemented return qfalse; } void CG_InitTestTreadMark() { // FIXME: unimplemented } int CG_StartTreadMark( int iReference, qhandle_t treadShader, vec_t* vStartPos, float fWidth, float fAlpha) { // FIXME: unimplemented return 0; } qboolean CG_MakeTreadMarkDecal_PerPolyCallback( vec3_t* markPoints, markFragment_t* mf, polyVert_t* verts, void* pCustom ) { // FIXME: unimplemented return qfalse; } int CG_MakeTreadMarkDecal_GetLeafCallback( markFragment_t* mf, void* pCustom ) { // FIXME: unimplemented return 0; } 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() { // FIXME: unimplemented } int CG_PermanentMark( vec_t* origin, vec_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; }