/* =========================================================================== 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: // Beam effects #include "cg_local.h" #include "vector.h" #include "container.h" #include "cg_commands.h" class beam_t : public Class { public: beam_t(); int entity; qhandle_t hModel; int endtime; Vector start, end; float scale; float alpha; int flags; int parent; float max_offset; float min_offset; int numSubdivisions; int overlap; int beamshader; byte shaderRGBA[4]; int update_time; int delay; float life; int numspherebeams; float sphereradius; int toggletime; int toggledelay; qboolean active; float alphastep; int renderfx; str name; beam_t *next; beam_t *prev; }; beam_t::beam_t() { entity = 0; hModel = 0; endtime = 0; scale = 0; alpha = 0; flags = 0; parent = ENTITYNUM_NONE; max_offset = 0; min_offset = 0; numSubdivisions = 0; overlap = 0; beamshader = 0; update_time = 0; delay = 0; life = 0; numspherebeams = 0; sphereradius = 0; toggletime = 0; toggledelay = 0; active = 0; alphastep = 0; renderfx = 0; memset(shaderRGBA, 0, 4); next = NULL; prev = NULL; } beam_t cl_beams[MAX_BEAMS]; beam_t *cl_free_beams; beam_t *cl_active_beams; static int seed = 100; // Recursive beam builder - I don't use it anymore /* void CG_BuildRenderBeam_r ( Vector start, Vector end, float angleVar, int numSubdivisions, int maxSubdivisions ) { if ( numSubdivisions == maxSubdivisions ) { return; } else { // subdivide line and call on both halves numSubdivisions += 1; mid = ( p1 * 0.5 ) + ( p2 * 0.5 ); int seed = 100; mid[0] += Q_crandom( &seed ) * angleVar; mid[1] += Q_crandom( &seed ) * angleVar; mid[2] += Q_crandom( &seed ) * angleVar; CG_BuildRendererBeam( p1, mid, angleVar, numSubdivisions, maxSubdivisions, color, beamshader, scale ); CG_BuildRendererBeam( mid, p2, angleVar, numSubdivisions, maxSubdivisions, color, beamshader, scale ); } } */ #define MAX_BEAM_BACKUP 6 #define MAX_BEAM_SEGMENTS 32 typedef struct beamSegment_t { polyVert_t points[4]; } beamSegment_t; typedef struct beamList_t { int time; int updatetime; int numsegments; beamSegment_t segments[MAX_BEAM_SEGMENTS]; } beamList_t; typedef struct beamEnt_t { int owner; int numbeams; int life; int renderfx; beamList_t beamlist[MAX_BEAM_BACKUP]; } beamEnt_t; Container beamManager; void ClientGameCommandManager::InitializeBeams() { int i; beam_t *b; cl_free_beams = NULL; cl_active_beams = NULL; for (i = 0; i < MAX_BEAMS; i++) { b = &cl_beams[i]; if (cl_free_beams) { cl_free_beams->prev = b; } b->next = cl_free_beams; b->prev = 0; cl_free_beams = b; } } void RemoveBeamList(int owner) { int i, num; num = beamManager.NumObjects(); for (i = 1; i <= num; i++) { beamEnt_t *be = beamManager.ObjectAt(i); if (be->owner == owner) { beamManager.RemoveObjectAt(i); delete be; return; } } } beamEnt_t *FindBeamList(int owner) { int i, num; num = beamManager.NumObjects(); for (i = 1; i <= num; i++) { beamEnt_t *be = beamManager.ObjectAt(i); if (be->owner == owner) { return be; } } return NULL; } int CreateNewBeamEntity(int owner, float life) { beamEnt_t *be; int i, oldest, oldest_time; be = FindBeamList(owner); if (!be) { be = new beamEnt_t; if (!be) { cgi.Error(ERR_DROP, "Could not allocate memory for beamEnt.\n"); } memset(be, 0, sizeof(beamEnt_t)); memset(be->beamlist, 0, sizeof(beamList_t) * MAX_BEAM_BACKUP); be->owner = owner; be->life = life; beamManager.AddObject(be); } // find the oldest beam and overwrite it. oldest = -1; oldest_time = 999999999; for (i = 0; i < MAX_BEAM_BACKUP; i++) { // Check for update time float t = be->beamlist[i].time; if (!t) { oldest = i; break; } if (t < oldest_time) { oldest = i; oldest_time = t; } } // Use the oldest beam for the next beam. be->beamlist[oldest].numsegments = 0; be->beamlist[oldest].time = cg.time; be->beamlist[oldest].updatetime = cg.time + be->life; return oldest; } void RemoveBeamEntity(int owner) { RemoveBeamList(owner); } void AddBeamSegmentToList(int owner, polyVert_t points[4], int beamnum, int segnum, int renderfx) { beamEnt_t *be; be = FindBeamList(owner); if (!be) { cgi.DPrintf("Could not find beam entity for owner:%d\n", owner); return; } if (segnum >= MAX_BEAM_SEGMENTS) { return; } be->renderfx = renderfx; // Copy the 4 points memcpy(&be->beamlist[beamnum].segments[segnum].points, points, 4 * sizeof(polyVert_t)); // Increase the segment counter be->beamlist[beamnum].numsegments++; } void CG_AddBeamsFromList(int owner, int beamshader) { int i, j, k, l; float frac, fade; beamEnt_t *be = FindBeamList(owner); polyVert_t newpoints[4]; if (!be) { return; } for (i = 0; i < MAX_BEAM_BACKUP; i++) { beamList_t *bl = &be->beamlist[i]; if (!bl->time) { continue; } // Calculate the blend factor for fading frac = (float)(cg.time - bl->time) / (float)be->life; fade = 1.0f - frac; if (fade <= 0) { bl->time = 0; // RemoveBeamList( owner ); continue; } // Go through each segment and draw it with the new modulate assert(bl->numsegments < MAX_BEAM_SEGMENTS); for (j = 0; j < bl->numsegments; j++) { memcpy(newpoints, bl->segments[j].points, 4 * sizeof(polyVert_t)); for (k = 0; k < 4; k++) { for (l = 0; l < 4; l++) { newpoints[k].modulate[l] = bl->segments[j].points[k].modulate[l] * fade; } } cgi.R_AddPolyToScene(beamshader, 4, newpoints, be->renderfx); } } } void RenderSegment(Vector pt1a, Vector pt1b, Vector pt2a, Vector pt2b, byte modulate[4], int beamshader, int renderfx) { int i, j; polyVert_t points[4]; VectorCopy(pt1a, points[0].xyz); VectorCopy(pt2a, points[1].xyz); VectorCopy(pt2b, points[2].xyz); VectorCopy(pt1b, points[3].xyz); points[0].st[0] = 1; points[0].st[1] = 1; points[1].st[0] = 0; points[1].st[1] = 1; points[2].st[0] = 0; points[2].st[1] = 0; points[3].st[0] = 1; points[3].st[1] = 0; // Set the color of the verts for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { points[i].modulate[j] = modulate[j]; } } // Add a segment to the list cgi.R_AddPolyToScene(beamshader, 4, points, renderfx); } const int MAX_SUBPOINTS = 256; static Vector subpoints[MAX_SUBPOINTS]; static int ptctr = 0; /* =============== CG_Subdivide a, b, and c are control points. the subdivided sequence will be: a, out1, out2, out3, c =============== */ static void CG_Subdivide(Vector a, Vector b, Vector c, Vector& out1, Vector& out2, Vector& out3) { out1 = 0.5 * (a + b); out3 = 0.5 * (b + c); out2 = 0.5 * (out1 + out3); } void CG_MultiBeamBegin(void) { ptctr = 0; } void CG_MultiBeamAddPoints( vec3_t start, vec3_t end, int numsegments, int flags, float minoffset, float maxoffset, qboolean addstartpoint ) { Vector delta, dir, randdir; float length; int i; if (ptctr > MAX_SUBPOINTS) { return; } if (addstartpoint) { subpoints[ptctr++] = start; } delta = Vector(end) - Vector(start); length = delta.length(); length /= numsegments; // get the dir of beam dir = delta; dir.normalize(); for (i = 1; i < numsegments; i++) { Vector newpt; if (ptctr > MAX_SUBPOINTS) { return; } randdir = Vector(crandom(), crandom(), crandom()); newpt = Vector(start) + dir * i * length; newpt += minoffset * randdir + maxoffset * randdir; subpoints[ptctr++] = newpt; } subpoints[ptctr++] = end; } void CG_MultiBeamEnd( float scale, int renderfx, const char *beamshadername, byte modulate[4], int flags, int owner, float life ) { Vector prevpt, currpt; Vector p1, p2, p3, p4, v1, v2, up, currpt1, currpt2, prevpt1, prevpt2; qboolean prevptvalid = false; int i, beamshader; beamshader = cgi.R_RegisterShader(beamshadername); prevpt = subpoints[0]; prevptvalid = false; for (i = 1; i < ptctr; i++) { currpt = subpoints[i]; // Generate the up vector v1 = prevpt - cg.refdef.vieworg; v2 = currpt - cg.refdef.vieworg; #if 0 cgi.R_DebugLine(prevpt, currpt, 1, 1, 1, 1); Vector pt = prevpt + up * 5; cgi.R_DebugLine(prevpt, pt, 0, 0, 1, 1); #endif up.CrossProduct(v1, v2); up.normalize(); // Calculate the first points currpt1 = currpt + (up * scale); currpt2 = currpt + (up * -scale); if (!prevptvalid) { prevpt1 = prevpt + up * scale; prevpt2 = prevpt + up * -scale; prevptvalid = true; } #if 1 RenderSegment(currpt1, currpt2, prevpt1, prevpt2, modulate, beamshader, renderfx); #endif prevpt = currpt; prevpt1 = currpt1; prevpt2 = currpt2; } if (flags & BEAM_PERSIST_EFFECT) { CG_AddBeamsFromList(owner, beamshader); } } static void CG_MultiBeamSubdivide(centity_t *cent) { Vector pt1, pt2, pt3; Vector out1, out2, out3, out4, out5, out6, out7, out8, out9; centity_t *current; ptctr = 0; current = cent; // Multibeam requires at least 3 points to start with // Get pt1 if (current->currentState.tag_num == ENTITYNUM_NONE) { cgi.DPrintf("CG_MultiBeamSubdivide : Multi beam entity does not have a child\n"); return; } pt1 = current->lerpOrigin; // Get pt2 current = &cg_entities[current->currentState.tag_num]; // Make sure that child is a multibeam if (current->currentState.eType != ET_MULTIBEAM) { return; } if (current->currentState.tag_num == ENTITYNUM_NONE) { cgi.DPrintf("CG_MultiBeamSubdivide : Multi beam entity does not have a child\n"); return; } pt2 = current->lerpOrigin; // Get pt3 current = &cg_entities[current->currentState.tag_num]; // Make sure that child is a multibeam if (current->currentState.eType != ET_MULTIBEAM) { return; } if (current->currentState.tag_num == ENTITYNUM_NONE) { cgi.DPrintf("CG_MultiBeamSubdivide : Multi beam entity does not have a child\n"); return; } pt3 = current->lerpOrigin; // First point into the subdivided points subpoints[ptctr++] = pt1; while (1) { // Do the subdivide CG_Subdivide(pt1, pt2, pt3, out1, out2, out3); CG_Subdivide(pt1, out1, out2, out4, out5, out6); if ((ptctr + 4) > MAX_SUBPOINTS) { break; } // Save the points subpoints[ptctr++] = out4; subpoints[ptctr++] = out5; subpoints[ptctr++] = out6; subpoints[ptctr++] = out2; // end condition if ((current->currentState.tag_num == ENTITYNUM_NONE) || (!current->currentValid)) { CG_Subdivide(out2, out3, pt3, out7, out8, out9); subpoints[ptctr++] = out7; subpoints[ptctr++] = out8; subpoints[ptctr++] = out9; subpoints[ptctr++] = pt3; break; } // Advance to next ent current = &cg_entities[current->currentState.tag_num]; if (!current->currentValid) { break; } // Advance the points down the line pt1 = out2; pt2 = pt3; pt3 = current->lerpOrigin; } } void CG_MultiBeam(centity_t *cent) { Vector prevpt, currpt; entityState_t *s1; Vector p1, p2, p3, p4, v1, v2, up, currpt1, currpt2, prevpt1, prevpt2; const char *beamshadername; int beamshader; byte modulate[4]; qboolean prevptvalid = false; int i; s1 = ¢->currentState; // If this isn't the parent of the beam, then return if (!s1->surfaces[0]) { return; } // Subdivide up the segments CG_MultiBeamSubdivide(cent); // This is the top of the beam ent list, build up a renderer beam based on all the children beamshadername = CG_ConfigString(CS_IMAGES + s1->surfaces[1]); // index for shader configstring beamshader = cgi.R_RegisterShader(beamshadername); //beamshader = cgi.R_RegisterShader( "" ); for (i = 0; i < 4; i++) { modulate[i] = cent->color[i] * 255; } if (ptctr < 3) { return; } prevpt = subpoints[0]; prevptvalid = false; for (i = 1; i < ptctr; i++) { currpt = subpoints[i]; // Generate the up vector v1 = prevpt - cg.refdef.vieworg; v2 = currpt - cg.refdef.vieworg; #if 0 cgi.R_DebugLine(prevpt, currpt, 1, 1, 1, 1); Vector pt = prevpt + up * 5; cgi.R_DebugLine(prevpt, pt, 0, 0, 1, 1); #endif up.CrossProduct(v1, v2); up.normalize(); // Calculate the first points currpt1 = currpt + (up * s1->scale); currpt2 = currpt + (up * -s1->scale); if (!prevptvalid) { prevpt1 = prevpt + up * s1->scale; prevpt2 = prevpt + up * -s1->scale; prevptvalid = true; } RenderSegment(currpt1, currpt2, prevpt1, prevpt2, modulate, beamshader, s1->renderfx); prevpt = currpt; prevpt1 = currpt1; prevpt2 = currpt2; } } void CG_BuildRendererBeam( Vector start, Vector end, float angleVar, int numSubdivisions, byte color[4], int beamshader, float scale, float overlap, int owner, float life, int flags, float startalpha, float alphastep, int renderfx ) { Vector p1, p2, v1, v2, dir, prevpt1, prevpt2, nextpt, mid, delta, up; int i, ii, jj; polyVert_t points[4]; float length; int segnum = 0; int beamnum = 0; float alphafactor; int picW; // Create or increment the number of beams for this owner and check to // see if we should add a new beam if (flags & BEAM_PERSIST_EFFECT) { beamnum = CreateNewBeamEntity(owner, life); if (beamnum < 0) { return; } } // For debugging texture coords //beamshader = cgi.R_RegisterShader( "" ); picW = cgi.R_GetShaderWidth(beamshader); // calcluate length of beam segment delta = end - start; length = delta.length(); length /= numSubdivisions; // get the dir of beam dir = delta; dir.normalize(); // Calculate the first up vector v1 = start - cg.refdef.vieworg; v2 = end - cg.refdef.vieworg; up.CrossProduct(v1, v2); up.normalize(); // Calculate the first points prevpt1 = start + (up * scale); prevpt2 = start + (up * -scale); p1 = start; // go through and calculate each point of the beam and offset it by the anglevar for (i = 1; i <= numSubdivisions; i++) { // Calculate the next point along the beam p2 = start + (dir * i * length); // Random variance on the next point ( except if it's the last ) if (i != numSubdivisions) { if (flags & BEAM_WAVE_EFFECT) { float phase = p2.x + p2.y; p2.z += sin(phase + cg.time) * angleVar; } else if (flags & BEAM_USE_NOISE) { p2.x += cgi.R_Noise(p2.x, p2.y, p2.z, cg.time) * angleVar; p2.y += cgi.R_Noise(p2.x, p2.y, p2.z, cg.time) * angleVar; p2.z += cgi.R_Noise(p2.x, p2.y, p2.z, cg.time) * angleVar; } else { p2.x += Q_crandom(&seed) * angleVar; p2.y += Q_crandom(&seed) * angleVar; p2.z += Q_crandom(&seed) * angleVar; } } // Create the up vec for the beam which is parallel to the viewplane v1 = p1 - cg.refdef.vieworg; v2 = p2 - cg.refdef.vieworg; up.CrossProduct(v1, v2); up.normalize(); // Build the quad VectorMA(p2, scale, up, points[0].xyz); VectorCopy(prevpt1, points[1].xyz); VectorCopy(prevpt2, points[2].xyz); VectorMA(p2, -scale, up, points[3].xyz); if (flags & BEAM_TILESHADER) // Tile the shader across the beam { float startS = (length * (i - 1)) / (float)picW; float endS = (length * (i)) / (float)picW; points[0].st[0] = startS; points[0].st[1] = 1; points[1].st[0] = endS; points[1].st[1] = 1; points[2].st[0] = endS; points[2].st[1] = 0; points[3].st[0] = startS; points[3].st[1] = 0; } else { points[0].st[0] = 1; points[0].st[1] = 1; points[1].st[0] = 0; points[1].st[1] = 1; points[2].st[0] = 0; points[2].st[1] = 0; points[3].st[0] = 1; points[3].st[1] = 0; } if (!alphastep) { alphafactor = 1.0f; } else { alphafactor = startalpha + (alphastep * i); } // Set the color of the verts for (ii = 0; ii < 4; ii++) { for (jj = 0; jj < 4; jj++) { points[ii].modulate[jj] = color[jj] * alphafactor; } } if (flags & BEAM_PERSIST_EFFECT) { // Save the segment for backup for drawing faded out AddBeamSegmentToList(owner, points, beamnum, segnum++, renderfx); } else { // Add it to the ref cgi.R_AddPolyToScene(beamshader, 4, points, renderfx); } // Subtract off the overlap if (overlap) { p2 = p2 + (dir * -overlap); } // Save off the last point to use as the first point on the next quad VectorMA(p2, scale, up, prevpt1); VectorMA(p2, -scale, up, prevpt2); p1 = p2; } } void CG_BuildRendererBeam_Fast( Vector start, Vector end, float angleVar, int numSubdivisions, byte color[4], int beamshader, float scale, float overlap, int owner, float life, int flags, float startalpha, float alphastep, int renderfx ) { int ii, jj; polyVert_t points[4]; int beamnum; float alphafactor; // Create or increment the number of beams for this owner and check to // see if we should add a new beam if (flags & BEAM_PERSIST_EFFECT) { beamnum = CreateNewBeamEntity(owner, life); if (beamnum < 0) { return; } } VectorMA(end, scale, cg.refdef.viewaxis[1], points[0].xyz); VectorMA(start, scale, cg.refdef.viewaxis[1], points[1].xyz); VectorMA(start, -scale, cg.refdef.viewaxis[1], points[2].xyz); VectorMA(end, -scale, cg.refdef.viewaxis[1], points[3].xyz); points[0].st[0] = 1.0; points[0].st[1] = 1.0; points[1].st[0] = 0.0; points[1].st[1] = 1.0; points[2].st[0] = 0.0; points[2].st[1] = 0.0; points[3].st[0] = 1.0; points[3].st[1] = 0.0; if (!alphastep) { for (ii = 0; ii < 4; ++ii) { for (jj = 0; jj < 4; ++jj) { points[ii].modulate[jj] = color[jj]; } } } else { alphafactor = startalpha + alphastep; for (ii = 0; ii < 4; ++ii) { for (jj = 0; jj < 4; ++jj) { points[ii].modulate[jj] = (int)((float)color[jj] * alphafactor); } } } if (flags & BEAM_PERSIST_EFFECT) { AddBeamSegmentToList(owner, points, beamnum, 0, renderfx); } else { cgi.R_AddPolyToScene(beamshader, 4, points, renderfx); } } void CG_CreateModelBeam(beam_t *b, vec3_t org, vec3_t dist, float total_length, vec3_t ndir, vec3_t left, vec3_t up) { dtiki_t *tiki; vec3_t mins, maxs; int single_beam_length; refEntity_t ent; int count; int j; float factor[3]; float t; vec3_t angles; int i; // Find the length of a single beam tiki = cgi.R_Model_GetHandle(b->hModel); // Calculate the bounds of the model to get it's length cgi.TIKI_CalculateBounds(tiki, 1.0, mins, maxs); single_beam_length = maxs[0] - mins[0]; // Create the beam entity memset(&ent, 0, sizeof(ent)); count = 0; // Initialize the factors for (j = 0; j < 3; j++) { factor[j] = 0.3f * crandom(); } t = 0; while (t >= 0 && t < 1) { float dot; vec3_t pdir; float delta; vec3_t distance_point; count++; // Set the origin of the current beam using the last calculated org VectorCopy(org, ent.origin); // Advance the org one beam length in the new direction ( dist is the newly calculated direction ) for (j = 0; j < 3; j++) { org[j] += dist[j] * (single_beam_length - b->overlap); } // Offset the org by a random amount to simulate lightning VectorMA(org, single_beam_length * factor[2], up, org); VectorMA(org, single_beam_length * factor[1], left, org); // Calculate (t) - how far this new point is along the overall distance VectorSubtract(org, b->start, pdir); dot = DotProduct(pdir, ndir); t = dot / total_length; // Calculate point at current distance along center beam VectorMA(b->start, total_length * t, ndir, distance_point); // Allow any variations if (t > 0.1 && t < 0.9) { for (j = 0; j < 3; j++) { delta = org[j] - distance_point[j]; if (delta > b->max_offset) { org[j] = distance_point[j] + b->max_offset; factor[j] = -0.3 * crandom(); } else if (delta < -b->max_offset) { org[j] = distance_point[j] - b->max_offset; factor[j] = 0.3 * crandom(); } else { factor[j] = 0.3 * crandom(); } } } else // Clamp to mins { for (j = 0; j < 3; j++) { delta = org[j] - distance_point[j]; if (delta > b->min_offset) { org[j] -= 0.4 * single_beam_length; factor[j] = -0.2f; } else if (delta < -b->min_offset) { org[j] += 0.4 * single_beam_length; factor[j] = 0.2f; } else { factor[j] = 0; } } } // Calculate the new dist vector so we can get pitch and yaw for this beam VectorSubtract(org, ent.origin, dist); // Set the pitch and the yaw based off this new vector vectoangles(dist, angles); // Fill in the ent fields ent.hModel = b->hModel; ent.scale = b->scale; ent.renderfx = b->renderfx; for (i = 0; i < 4; i++) { ent.shaderRGBA[i] = b->shaderRGBA[i]; } VectorCopy(ent.origin, ent.oldorigin); AnglesToAxis(angles, ent.axis); // Add in this beam to the ref cgi.R_AddRefEntityToScene(&ent, ENTITYNUM_NONE); } } void CG_AddBeams(void) { int i, ii; beam_t *b; beam_t *bNext; vec3_t delta; vec3_t angles; vec3_t forward, left, up; float length; byte color[4]; float fade; for (i = 0, b = cl_active_beams; b; i++, b = bNext) { bNext = b->next; // If no model is set or the endtime < current time remove the whole beam entity if (!b->hModel || b->endtime < cg.time) { RemoveBeamList(b->entity); b->entity = ENTITYNUM_NONE; b->endtime = 0; if (b->next) { b->next->prev = b->prev; } if (b->prev) { b->prev->next = b->next; } if (b == cl_active_beams) { cl_active_beams = b->next; } if (cl_free_beams) { cl_free_beams->prev = b; } b->next = cl_free_beams; b->prev = 0; cl_free_beams = b; continue; } // Fade the beam based on it's life fade = (float)(b->endtime - cg.time) / (float)b->life; if (b->flags & BEAM_FADE) { for (ii = 0; ii < 4; ii++) { color[ii] = b->shaderRGBA[ii] * fade; } } else { for (ii = 0; ii < 4; ii++) { color[ii] = b->shaderRGBA[ii]; } } // Check to see if the beam should be toggled if (b->flags & BEAM_TOGGLE) { if (cg.time > b->toggletime) { b->active = !b->active; if (b->flags & BEAM_RANDOM_TOGGLEDELAY) { b->toggletime = cg.time + random() * b->toggledelay; } else { b->toggletime = cg.time + b->toggledelay; } } } if (!b->active) { CG_AddBeamsFromList(b->entity, b->beamshader); continue; } if ((b->flags & BEAM_PERSIST_EFFECT) && (b->update_time > cg.time)) { CG_AddBeamsFromList(b->entity, b->beamshader); continue; } b->update_time = cg.time + b->delay; if (!b->active) { continue; } if (b->flags & BEAM_USEMODEL) { // Calculate the direction VectorSubtract(b->start, b->end, delta); // Calculate the beam length length = VectorLength(delta); // Get the perpendicular vectors to this vector vectoangles(delta, angles); AngleVectors(angles, forward, left, up); CG_CreateModelBeam(b, b->start, delta, length, forward, left, up); } else { // Do a sphere effect if (b->flags & BEAM_SPHERE_EFFECT) { int k; // Calculate the direction VectorSubtract(b->start, b->end, delta); // Calculate the beam length length = VectorLength(delta); for (k = 0; k < b->numspherebeams; k++) { Vector offset(crandom(), crandom(), crandom()); Vector start(b->start + offset * b->sphereradius); Vector end(b->start + offset * length); CG_BuildRendererBeam( start, end, b->max_offset, b->numSubdivisions, color, b->beamshader, b->scale, b->overlap, b->entity, b->life, b->flags, b->alpha, b->alphastep, b->renderfx ); } } else if (b->flags & BEAM_INVERTED) { vec3_t vCurrStart, vCurrEnd; vec3_t vDir; float fLength; // Calculate the direction VectorSubtract(b->end, b->start, vDir); VectorMA(b->start, 1.0 - fade, vDir, vCurrEnd); fLength = VectorNormalize(vDir); VectorMA(vCurrEnd, -b->toggledelay, vDir, vCurrStart); CG_BuildRendererBeam( vCurrStart, vCurrEnd, b->max_offset, b->numSubdivisions, color, b->beamshader, b->scale, b->overlap, b->entity, b->life, b->flags, b->alpha, b->alphastep, b->renderfx ); } else if (b->flags & BEAM_INVERTED_FAST) { vec3_t vCurrStart, vCurrEnd; vec3_t vDir; // Calculate the direction VectorSubtract(b->end, b->start, vDir); VectorMA(b->start, 1.0 - fade, vDir, vCurrEnd); VectorNormalizeFast(vDir); VectorMA(vCurrEnd, -b->toggledelay, vDir, vCurrStart); CG_BuildRendererBeam_Fast( vCurrStart, vCurrEnd, b->max_offset, b->numSubdivisions, color, b->beamshader, b->scale, b->overlap, b->entity, b->life, b->flags, b->alpha, b->alphastep, b->renderfx ); } else { //cgi.DPrintf( "%2f %2f %2f\n", b->start[0],b->start[1],b->start[2] ); CG_BuildRendererBeam( b->start, b->end, b->max_offset, b->numSubdivisions, color, b->beamshader, b->scale, b->overlap, b->entity, b->life, b->flags, b->alpha, b->alphastep, b->renderfx ); } if (b->flags & BEAM_PERSIST_EFFECT) { CG_AddBeamsFromList(b->entity, b->beamshader); } } } } void CG_CreateBeam( const vec3_t start, const vec3_t dir, int owner, qhandle_t hModel, float alpha, float scale, int flags, float length, int life, qboolean create, const vec3_t endpointvec, int min_offset, int max_offset, int overlap, int numSubdivisions, int delay, const char *beamshadername, float modulate[4], int numspherebeams, float sphereradius, int toggledelay, float endalpha, int renderfx, const char *name ) { int i; beam_t *b; vec3_t end; trace_t trace; // Check to see if endpoint is specified if (endpointvec) { VectorCopy(endpointvec, end); } else { // Trace to find the endpoint with a shot VectorMA(start, length, dir, end); CG_Trace(&trace, start, vec3_origin, vec3_origin, end, 0, MASK_SHOT, false, true, "Create Beam"); VectorCopy(trace.endpos, end); } // If we aren't creating a beam, then search the beams for this one already active if (!create) { for (i = 0, b = cl_active_beams; b; i++, b = b->next) { if (b->entity == owner) { if (name && b->name == name) { b->endtime = cg.time + life; b->hModel = hModel; b->scale = scale; b->flags = flags; b->overlap = overlap; b->min_offset = min_offset; b->max_offset = max_offset; b->alpha = alpha; b->beamshader = cgi.R_RegisterShader(beamshadername); b->numSubdivisions = numSubdivisions; b->delay = delay; b->life = life; b->numspherebeams = numspherebeams; b->sphereradius = sphereradius; b->renderfx = renderfx; // take the alpha from the entity if less than 1, else grab it from the client commands version if (alpha < 1) { b->shaderRGBA[3] = alpha * 255; } else { b->shaderRGBA[3] = modulate[3] * 255; } // Modulation based off the color for (i = 0; i < 3; i++) { b->shaderRGBA[i] = modulate[i] * (float)b->shaderRGBA[3]; } b->alphastep = ((float)(endalpha - alpha) / (float)b->numSubdivisions); VectorCopy(start, b->start); VectorCopy(end, b->end); return; } } } } // find a free beam if (cl_free_beams) { b = cl_free_beams; cl_free_beams = cl_free_beams->next; if (cl_active_beams) { cl_active_beams->prev = b; } b->next = cl_active_beams; b->prev = 0; cl_active_beams = b; b->entity = owner; b->endtime = cg.time + life; b->hModel = hModel; b->alpha = alpha; b->scale = scale; b->flags = flags; b->overlap = overlap; b->min_offset = min_offset; b->max_offset = max_offset; b->beamshader = cgi.R_RegisterShader(beamshadername); b->numSubdivisions = numSubdivisions; b->delay = delay; b->update_time = 0; //cg.time + delay; b->life = life; b->numspherebeams = numspherebeams; b->sphereradius = sphereradius; b->active = true; b->toggledelay = toggledelay; b->renderfx = renderfx; b->name = name; // take the alpha from the entity if less than 1, else grab it from the client commands version if (alpha < 1) { b->shaderRGBA[3] = alpha * 255; } else { b->shaderRGBA[3] = modulate[3] * 255; } // Modulation based off the color for (i = 0; i < 3; i++) { b->shaderRGBA[i] = modulate[i] * (float)b->shaderRGBA[3]; } b->alphastep = ((float)(endalpha - alpha) / (float)b->numSubdivisions); VectorCopy(start, b->start); VectorCopy(end, b->end); return; } return; } void CG_KillBeams(int entity_number) { int i; beam_t *b; for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (b->entity == entity_number) { b->entity = ENTITYNUM_NONE; b->endtime = 0; if (b->next) { b->next->prev = b->prev; } if (b->prev) { b->prev->next = b->next; } if (b == cl_active_beams) { cl_active_beams = b->next; } if (cl_free_beams) { cl_free_beams->prev = b; } b->next = cl_free_beams; cl_free_beams = b; b->prev = 0; } } } void CG_RestartBeams(int timedelta) { int i; beam_t *b; for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (b->active && (b->update_time > cg.time)) { b->endtime -= timedelta; b->update_time -= timedelta; if (b->toggletime) { b->toggletime -= timedelta; } } } } void CG_Rope(centity_t *cent) { Vector prevpt, currpt; entityState_t *s1; Vector top, mid, bottom, up, v1, v2; Vector currpt1, currpt2, prevpt1, prevpt2; const char *beamshadername; int beamshader; byte modulate[4]; float picH, length, endT; int i, j; polyVert_t points[4]; s1 = ¢->currentState; top = s1->origin2; mid = cent->lerpOrigin; bottom = cent->lerpOrigin; bottom.z -= s1->alpha; // This is the top of the beam ent list, build up a renderer beam based on all the children beamshadername = CG_ConfigString(CS_IMAGES + s1->surfaces[0]); // index for shader configstring beamshader = cgi.R_RegisterShader(beamshadername); picH = cgi.R_GetShaderHeight(beamshader); for (i = 0; i < 4; i++) { modulate[i] = cent->color[i] * 255; } // Generate the up vector v1 = top - cg.refdef.vieworg; v2 = bottom - cg.refdef.vieworg; up.CrossProduct(v1, v2); up.normalize(); // Set the color of the verts for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { points[i].modulate[j] = modulate[j]; } } // set the s coordinates points[0].st[0] = 1; points[1].st[0] = 1; points[2].st[0] = 0; points[3].st[0] = 0; // Calculate the first points prevpt1 = top + (up * s1->scale); prevpt2 = top + (up * -s1->scale); // draw the top section currpt1 = mid + (up * s1->scale); currpt2 = mid + (up * -s1->scale); length = Vector(mid - top).length(); VectorCopy(currpt1, points[0].xyz); VectorCopy(prevpt1, points[1].xyz); VectorCopy(prevpt2, points[2].xyz); VectorCopy(currpt2, points[3].xyz); endT = length / picH; points[0].st[1] = endT; points[3].st[1] = endT; points[1].st[1] = 0; points[2].st[1] = 0; // Add a segment to the list cgi.R_AddPolyToScene(beamshader, 4, points, s1->renderfx); if (s1->alpha > 0) { // draw the bottom section prevpt1 = currpt1; prevpt2 = currpt2; currpt1 = bottom + (up * s1->scale); currpt2 = bottom + (up * -s1->scale); VectorCopy(currpt1, points[0].xyz); VectorCopy(prevpt1, points[1].xyz); VectorCopy(prevpt2, points[2].xyz); VectorCopy(currpt2, points[3].xyz); // add on the rest of the rope length += s1->alpha; // use previous T value for the start of this segment points[1].st[1] = endT; points[2].st[1] = endT; endT = length / picH; points[0].st[1] = endT; points[3].st[1] = endT; // Add a segment to the list cgi.R_AddPolyToScene(beamshader, 4, points, s1->renderfx); } }