/* =========================================================================== Copyright (C) 2023-2024 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_ghost.cpp #include "tr_local.h" #include "tr_ghost.h" GhostManager ghostManager; int frameTime; int lastTime; /* ==================== RandomizeRange ==================== */ float RandomizeRange(float min, float max) { return min + (max - min) * (rand() / (float)RAND_MAX); } /* ==================== Particle::Particle Initializes the particle with the specified parameters ==================== */ Particle::Particle( Vector pos, Vector vel, Vector acc, int srcColor, int dstColor, float colorRate, qboolean wavy, float wavyDist, float currentTime, float dieTime, float maxspeed, Vector parentOrg, qboolean swarm, int freq, int delta ) { m_position = pos; m_realposition = pos; m_velocity = vel; m_acceleration = acc; m_srcColor = srcColor; m_dstColor = dstColor; m_color = srcColor; m_colorRate = colorRate; m_wavy = wavy; m_wavyDist = wavyDist; m_wavyOffset = RandomizeRange(-wavyDist, wavyDist); m_swarm = swarm; m_swarmfrequency = freq; m_swarmdelta = delta; m_startTime = currentTime; m_dieTime = dieTime; m_life = dieTime - currentTime; m_maxspeed = maxspeed; m_parentOrigin = parentOrg; Vector tempvec = m_velocity; VectorNormalize(tempvec); m_perp.x = tempvec.x * cos(M_PI / 2) - tempvec.y * sin(M_PI / 2); m_perp.y = tempvec.x * sin(M_PI / 2) + tempvec.y * cos(M_PI / 2); m_srcR = (m_srcColor & 0xff); m_srcG = (m_srcColor & 0xff00) >> 8; m_srcB = (m_srcColor & 0xff0000) >> 16; m_dstR = (m_dstColor & 0xff); m_dstG = (m_dstColor & 0xff00) >> 8; m_dstB = (m_dstColor & 0xff0000) >> 16; m_deltaR = m_dstR - m_srcR; m_deltaG = m_dstG - m_srcG; m_deltaB = m_dstB - m_srcB; } /* ==================== Particle::Update Updates the particle based on the current time ==================== */ void Particle::Update(float currentTime) { int r, g, b; float ftime; float time2; float factor; Vector offset; ftime = frameTime / 1000.0; time2 = Square(ftime) * 0.5; if (m_swarm) { if (!(rand() % m_swarmfrequency)) { m_velocity[0] = crandom() * m_maxspeed; m_velocity[1] = crandom() * m_maxspeed; m_velocity[2] = crandom() * m_maxspeed; } if (m_parentOrigin[0] > m_position[0]) { m_velocity[0] += m_swarmdelta; } else { m_velocity[0] -= m_swarmdelta; } if (m_parentOrigin[1] > m_position[1]) { m_velocity[1] += m_swarmdelta; } else { m_velocity[1] -= m_swarmdelta; } if (m_parentOrigin[2] > m_position[2]) { m_velocity[2] += m_swarmdelta; } else { m_velocity[2] -= m_swarmdelta; } } m_position = m_realposition; m_position += m_velocity * ftime + m_acceleration * time2; m_velocity += m_acceleration * ftime; m_realposition = m_position; if (m_wavy) { offset = m_perp * sin(currentTime + m_wavyOffset) * (m_wavyOffset + m_wavyDist); m_position += offset; } factor = 1.0 - (m_dieTime - currentTime) / m_life; r = m_deltaR * factor; g = m_deltaG * factor; b = m_deltaB * factor; m_color = (r) | (g << 8) | (b << 8); } /* ==================== Particle::GetDieTime Returns the time at which the particle stops ==================== */ float Particle::GetDieTime() { return m_dieTime; } /* ==================== ParticleEmitter::ParticleEmitter Initializes the particle emitter with specified parameters ==================== */ ParticleEmitter::ParticleEmitter( Vector position, float minSpeed, float maxSpeed, float angle, float angleVar, float minAccSpeed, float maxAccSpeed, float accAngle, float accAngleVar, float minRate, float maxRate, int srcColor, int dstColor, float minColorRate, float maxColorRate, qboolean wavy, float minWavyDist, float maxWavyDist, float minLife, float maxLife, qboolean particles, qboolean gravityWell, float gravityWellStrength, qboolean ballLightning, int minBallLightningRadius, int maxBallLightningRadius, float lightningVar, int lightningSubdivisions, qboolean swarm, int swarmfreq, int swarmdelta ) { m_position = position; m_minSpeed = minSpeed; m_maxSpeed = maxSpeed; m_angle = angle; m_angleVar = angleVar; m_minAccSpeed = minAccSpeed; m_maxAccSpeed = maxAccSpeed; m_accAngle = accAngle; m_accAngleVar = accAngleVar; m_maxRate = 1.0 / maxRate; m_minRate = 1.0 / minRate; m_srcColor = srcColor; m_dstColor = dstColor; m_minColorRate = minColorRate; m_maxColorRate = maxColorRate; m_minWavyDist = minWavyDist; m_maxWavyDist = maxWavyDist; m_minLife = minLife; m_maxLife = maxLife; m_wavy = wavy; m_particles = particles; m_gravityWellStrength = gravityWellStrength; m_gravityWell = gravityWell; m_ballLightning = ballLightning; m_minBallLightningRadius = minBallLightningRadius; m_lightningVar = lightningVar; m_maxBallLightningRadius = maxBallLightningRadius; m_lightningSubdivisions = lightningSubdivisions; m_swarm = swarm; m_swarmfrequency = swarmfreq; m_swarmdelta = swarmdelta; m_time = tr.refdef.time; m_lasttime = m_time; m_gravityEffectVector = vec_zero; } /* ==================== ParticleEmitter::ParticleEmitter ==================== */ ParticleEmitter::ParticleEmitter() { m_lasttime = tr.refdef.time / 1000.0; } /* ==================== ParticleEmitter::~ParticleEmitter ==================== */ ParticleEmitter::~ParticleEmitter() {} /* ==================== ParticleEmitter::Emit Creates new particles based on the current time ==================== */ void ParticleEmitter::Emit(float currentTime) { Particle *p; float rate; float delta; rate = GetRate(); delta = currentTime = m_lasttime; if (!rate) { m_lasttime = currentTime; return; } for (; delta > rate; delta -= rate) { p = new Particle( m_position, GetVelocity(), GetAcceleration(), GetSrcColor(), GetDstColor(), GetColorRate(), IsWavy(), GetWavyDistance(), currentTime, GetDieTime(currentTime), m_maxSpeed, GetPosition(), IsSwarm(), m_swarmfrequency, m_swarmdelta ); particleList.AddObject(p); m_lasttime = currentTime; } } /* ==================== ParticleEmitter::UpdateValues Updates all particles with the last renderer time ==================== */ void ParticleEmitter::UpdateValues() { int i; int numParticles; numParticles = particleList.NumObjects(); for (i = 1; i <= numParticles; i++) { Particle *p = particleList.ObjectAt(i); p->Update(m_lasttime); } } /* ==================== ParticleEmitter::GetPosition Returns the current particle position ==================== */ Vector ParticleEmitter::GetPosition() { return m_position; } /* ==================== ParticleEmitter::GetVelocity Returns the random velocity based on random angle, and random speed ==================== */ Vector ParticleEmitter::GetVelocity() { float var; float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_angle, m_angleVar); v = CalculateDirection(angle); // // Calculate the velocity from random speed // var = RandomizeRange(m_minSpeed, m_maxSpeed); v *= var; return v; } /* ==================== ParticleEmitter::GetVelocityDirection Returns the direction of the random velocity ==================== */ Vector ParticleEmitter::GetVelocityDirection() { float var; float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_angle, m_angleVar); v = CalculateDirection(angle); // // Calculate the velocity from random speed // var = RandomizeRange(m_minSpeed, m_maxSpeed); v *= var; v.normalize(); return v; } /* ==================== ParticleEmitter::GetVelocityDirectionMin Returns the random velocity direction based on random angle, and minimum speed ==================== */ Vector ParticleEmitter::GetVelocityDirectionMin() { float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_angle, m_angleVar); v = CalculateDirection(angle); // Use the minimum speed v *= m_minSpeed; v.normalize(); return v; } /* ==================== ParticleEmitter::GetVelocityDirectionMax Returns the random velocity direction based on random angle, and maximum speed ==================== */ Vector ParticleEmitter::GetVelocityDirectionMax() { float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_angle, m_angleVar); v = CalculateDirection(angle); // Use the minimum speed v *= m_maxSpeed; v.normalize(); return v; } /* ==================== ParticleEmitter::GetAcceleration Returns the random acceleration based on random angle, and random speed ==================== */ Vector ParticleEmitter::GetAcceleration() { float var; float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_accAngle, m_accAngleVar); v = CalculateDirection(angle); // // Calculate the velocity from random speed // var = RandomizeRange(m_minSpeed, m_maxSpeed); v += m_gravityEffectVector * var; return v; } /* ==================== ParticleEmitter::GetAccelerationDirection Returns the direction of the random acceleration ==================== */ Vector ParticleEmitter::GetAccelerationDirection() { float var; float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_accAngle, m_accAngleVar); v = CalculateDirection(angle); // // Calculate the velocity from random speed // var = RandomizeRange(m_minSpeed, m_maxSpeed); v += m_gravityEffectVector * var; v.normalize(); return v; } /* ==================== ParticleEmitter::GetAccelerationDirectionMin Returns the random acceleration direction based on random angle, and minimum speed ==================== */ Vector ParticleEmitter::GetAccelerationDirectionMin() { float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_accAngle, m_accAngleVar); v = CalculateDirection(angle); // Use the minimum speed v += m_gravityEffectVector * m_minSpeed; v.normalize(); return v; } /* ==================== ParticleEmitter::GetAccelerationDirectionMax Returns the random acceleration direction based on random angle, and maximum speed ==================== */ Vector ParticleEmitter::GetAccelerationDirectionMax() { float angle; Vector v; // // Calculate the direction from random angle // angle = RandomizeAngle(m_accAngle, m_accAngleVar); v = CalculateDirection(angle); // Use the maximum speed v += m_gravityEffectVector * m_minSpeed; v.normalize(); return v; } /* ==================== ParticleEmitter::GetSrcColor Returns the source color ==================== */ int ParticleEmitter::GetSrcColor() { return m_srcColor; } /* ==================== ParticleEmitter::GetDstColor Returns the destination color ==================== */ int ParticleEmitter::GetDstColor() { return m_dstColor; } /* ==================== ParticleEmitter::GetGravityWellStrength Returns the strength of the gravity ==================== */ float ParticleEmitter::GetGravityWellStrength() { return m_gravityWellStrength; } /* ==================== ParticleEmitter::GetRate Returns a random rate between minimum and maximum ==================== */ float ParticleEmitter::GetRate() { return RandomizeRange(m_minRate, m_maxRate); } /* ==================== ParticleEmitter::GetColorRate Returns a random color between minimum and maximum ==================== */ float ParticleEmitter::GetColorRate() { return RandomizeRange(m_minColorRate, m_maxColorRate); } /* ==================== ParticleEmitter::GetWavyDistance Returns a random wavy distance between minimum and maximum ==================== */ float ParticleEmitter::GetWavyDistance() { return RandomizeRange(m_minWavyDist, m_maxWavyDist); } /* ==================== ParticleEmitter::GetDieTime Returns a random time at which the particle will stop, between minimum and maximum ==================== */ float ParticleEmitter::GetDieTime(float currentTime) { return currentTime + RandomizeRange(m_minLife, m_maxLife); } /* ==================== ParticleEmitter::GetBallLightningRadius Returns a random lightning radius between minimum and maximum ==================== */ int ParticleEmitter::GetBallLightningRadius() { return RandomizeRange(m_minBallLightningRadius, m_maxBallLightningRadius); } /* ==================== ParticleEmitter::GetLightningVar Returns the maximum lightning value ==================== */ float ParticleEmitter::GetLightningVar() { return m_lightningVar; } /* ==================== ParticleEmitter::GetLightningSubdivisions Returns the number of lightning subdivisions ==================== */ int ParticleEmitter::GetLightningSubdivisions() { return m_lightningSubdivisions; } /* ==================== ParticleEmitter::IsWavy Returns whether or not this is a wavy particle ==================== */ qboolean ParticleEmitter::IsWavy() { return m_wavy; } /* ==================== ParticleEmitter::IsGravityWell Returns whether or not this particle has gravity ==================== */ qboolean ParticleEmitter::IsGravityWell() { return m_gravityWell; } /* ==================== ParticleEmitter::IsParticles Returns whether or not this contains particles ==================== */ qboolean ParticleEmitter::IsParticles() { return m_particles; } /* ==================== ParticleEmitter::IsBallLightning Returns whether or not ball lightning can be applied ==================== */ qboolean ParticleEmitter::IsBallLightning() { return m_ballLightning; } /* ==================== ParticleEmitter::IsSwarm Returns whether or not this is a swarm of particles ==================== */ qboolean ParticleEmitter::IsSwarm() { return m_swarm; } /* ==================== ParticleEmitter::SetMinSpeed Sets the minimum speed ==================== */ void ParticleEmitter::SetMinSpeed(float value) { m_minSpeed = value; } /* ==================== ParticleEmitter::SetMaxSpeed Sets the maximum speed ==================== */ void ParticleEmitter::SetMaxSpeed(float value) { m_maxSpeed = value; } /* ==================== ParticleEmitter::SetAngle Sets the angle/minimum angle ==================== */ void ParticleEmitter::SetAngle(float value) { m_angle = value; } /* ==================== ParticleEmitter::SetAngleVar Sets the maximum speed ==================== */ void ParticleEmitter::SetAngleVar(float value) { m_angleVar = value; } /* ==================== ParticleEmitter::SetMinAccSpeed Sets the minimum acceleration speed ==================== */ void ParticleEmitter::SetMinAccSpeed(float value) { m_minAccSpeed = value; } /* ==================== ParticleEmitter::SetMaxAccSpeed Sets the maximum acceleration speed ==================== */ void ParticleEmitter::SetMaxAccSpeed(float value) { m_maxAccSpeed = value; } /* ==================== ParticleEmitter::SetAccAngle Sets the acceleration angle / minimum acceleration angle ==================== */ void ParticleEmitter::SetAccAngle(float value) { m_accAngle = value; } /* ==================== ParticleEmitter::SetAccAngleVar Sets the maximum acceleration speed ==================== */ void ParticleEmitter::SetAccAngleVar(float value) { m_accAngleVar = value; } /* ==================== ParticleEmitter::SetMinRate Sets the minimum rate ==================== */ void ParticleEmitter::SetMinRate(float value) { m_minRate = value; } /* ==================== ParticleEmitter::SetMaxRate Sets the maximum rate ==================== */ void ParticleEmitter::SetMaxRate(float value) { m_maxRate = value; } /* ==================== ParticleEmitter::SetSrcColor Sets the source color ==================== */ void ParticleEmitter::SetSrcColor(int value) { m_srcColor = value; } /* ==================== ParticleEmitter::SetDstColor Sets the destination color ==================== */ void ParticleEmitter::SetDstColor(int value) { m_dstColor = value; } /* ==================== ParticleEmitter::SetMinColorRate Sets the minimum color rate ==================== */ void ParticleEmitter::SetMinColorRate(float value) { m_minColorRate = value; } /* ==================== ParticleEmitter::SetMaxColorRate Sets the maximum color rate ==================== */ void ParticleEmitter::SetMaxColorRate(float value) { m_maxColorRate = value; } /* ==================== ParticleEmitter::SetWavy Sets whether or not this is a wavy particle ==================== */ void ParticleEmitter::SetWavy(int value) { m_wavy = value; } /* ==================== ParticleEmitter::SetParticles Sets whether or not this contains particles ==================== */ void ParticleEmitter::SetParticles(int value) { m_particles = value; } /* ==================== ParticleEmitter::SetGravityWell Sets whether or not this has gravity ==================== */ void ParticleEmitter::SetGravityWell(int value) { m_gravityWell = value; } /* ==================== ParticleEmitter::SetMinWavyDist Sets the minimum wavy distance ==================== */ void ParticleEmitter::SetMinWavyDist(float value) { m_minWavyDist = value; } /* ==================== ParticleEmitter::SetMaxWavyDist Sets the maximum wavy distance ==================== */ void ParticleEmitter::SetMaxWavyDist(float value) { m_maxWavyDist = value; } /* ==================== ParticleEmitter::SetMinLife Sets the minimum life ==================== */ void ParticleEmitter::SetMinLife(float value) { m_minLife = value; } /* ==================== ParticleEmitter::SetMaxLife Sets the maximum life ==================== */ void ParticleEmitter::SetMaxLife(float value) { m_maxLife = value; } /* ==================== ParticleEmitter::SetGravityWellStrength Sets the strength of the gravity ==================== */ void ParticleEmitter::SetGravityWellStrength(float value) { m_gravityWellStrength = value; } /* ==================== ParticleEmitter::SetGravityEffectVector Sets the direction at which gravity is applied ==================== */ void ParticleEmitter::SetGravityEffectVector(Vector value) { m_gravityEffectVector = value; } /* ==================== ParticleEmitter::SetBallLightning Sets whether or not it has ball lightning ==================== */ void ParticleEmitter::SetBallLightning(int value) { m_ballLightning = value; } /* ==================== ParticleEmitter::SetMinBallLightningRadius Sets the minimum ball lightning radius ==================== */ void ParticleEmitter::SetMinBallLightningRadius(int value) { m_minBallLightningRadius = value; } /* ==================== ParticleEmitter::SetMaxBallLightningRadius Sets the maximum ball lightning radius ==================== */ void ParticleEmitter::SetMaxBallLightningRadius(int value) { m_maxBallLightningRadius = value; } /* ==================== ParticleEmitter::SetLightningVar Sets the maximum lightning value ==================== */ void ParticleEmitter::SetLightningVar(float value) { m_lightningVar = value; } /* ==================== ParticleEmitter::SetLightningSubdivisions Sets the number of subdivisions for the lightning ==================== */ void ParticleEmitter::SetLightningSubdivisions(int value) { m_lightningSubdivisions = value; } /* ==================== ParticleEmitter::RandomizeRange Returns a random value between the range [min, max] ==================== */ float ParticleEmitter::RandomizeRange(float min, float max) { return min + (max - min) * (rand() / (float)RAND_MAX); } /* ==================== ParticleEmitter::RandomizeAngle Returns a random angle between the range [min, max] ==================== */ float ParticleEmitter::RandomizeAngle(float min, float max) { return min + ((rand() - (RAND_MAX / 2)) / (float)RAND_MAX) * max; } /* ==================== ParticleEmitter::CalculateDirection Returns a random Vector direction based on the angle value ==================== */ Vector ParticleEmitter::CalculateDirection(float value) { float deg = DEG2RAD(value); return Vector(cos(deg), -sin(deg), 0); } /* ==================== ParticleEmitter::Load Loads the specified particle emitter from the string buffer ==================== */ void ParticleEmitter::Load(char **buf_p) { m_position[0] = atoi(COM_ParseExt(buf_p, qtrue)); m_position[1] = atoi(COM_ParseExt(buf_p, qtrue)); m_position[2] = atoi(COM_ParseExt(buf_p, qtrue)); m_srcColor = atoi(COM_ParseExt(buf_p, qtrue)); m_dstColor = atoi(COM_ParseExt(buf_p, qtrue)); m_minColorRate = atoi(COM_ParseExt(buf_p, qtrue)); m_maxColorRate = atoi(COM_ParseExt(buf_p, qtrue)); m_minRate = atof(COM_ParseExt(buf_p, qtrue)); m_maxRate = atof(COM_ParseExt(buf_p, qtrue)); m_minLife = atoi(COM_ParseExt(buf_p, qtrue)); m_maxLife = atoi(COM_ParseExt(buf_p, qtrue)); m_angle = atoi(COM_ParseExt(buf_p, qtrue)); m_angleVar = atoi(COM_ParseExt(buf_p, qtrue)); m_accAngle = atoi(COM_ParseExt(buf_p, qtrue)); m_accAngleVar = atoi(COM_ParseExt(buf_p, qtrue)); m_minSpeed = atof(COM_ParseExt(buf_p, qtrue)); m_maxSpeed = atof(COM_ParseExt(buf_p, qtrue)); m_minAccSpeed = atof(COM_ParseExt(buf_p, qtrue)); m_maxAccSpeed = atof(COM_ParseExt(buf_p, qtrue)); m_minWavyDist = atof(COM_ParseExt(buf_p, qtrue)); m_maxWavyDist = atof(COM_ParseExt(buf_p, qtrue)); m_wavy = atoi(COM_ParseExt(buf_p, qtrue)); m_particles = atoi(COM_ParseExt(buf_p, qtrue)); m_gravityWell = atoi(COM_ParseExt(buf_p, qtrue)); m_gravityWellStrength = atof(COM_ParseExt(buf_p, qtrue)); m_ballLightning = atoi(COM_ParseExt(buf_p, qtrue)); m_minBallLightningRadius = atof(COM_ParseExt(buf_p, qtrue)); m_maxBallLightningRadius = atof(COM_ParseExt(buf_p, qtrue)); m_lightningVar = atof(COM_ParseExt(buf_p, qtrue)); m_lightningSubdivisions = atof(COM_ParseExt(buf_p, qtrue)); m_swarm = atoi(COM_ParseExt(buf_p, qtrue)); m_swarmfrequency = atoi(COM_ParseExt(buf_p, qtrue)); m_swarmdelta = atoi(COM_ParseExt(buf_p, qtrue)); } /* ==================== GhostTexture::GhostTexture ==================== */ GhostTexture::GhostTexture() { m_image = NULL; m_texture = NULL; } /* ==================== GhostTexture::SetTexel Sets color at the specified xy texel location ==================== */ void GhostTexture::SetTexel(int x, int y, unsigned int color) { m_texture[x + m_width * y] = color; } /* ==================== GhostTexture::Burn Applies fade/burn effect to the ghost texture ==================== */ void GhostTexture::Burn() { int x, y; int r, g, b; GLint pixel; GLuint *line; if (m_isfade) { for (y = 1; y < m_height - 1; y++) { line = &m_texture[m_width * y]; for (x = 1; x < m_width - 1; x++) { r = (line[x] & 0xff) - m_faderate; g = ((line[x] & 0xff00) >> 8) - m_faderate; b = ((line[x] & 0xff0000) >> 16) - m_faderate; if (r < 0) { r = 0; } if (g < 0) { g = 0; } if (b < 0) { b = 0; } line[x] = (r) | (g << 8) | (b << 16); } } } else if (m_isburn) { for (y = 1; y < m_height - 1; y++) { line = &m_texture[m_width * y]; for (x = 1; x < m_width - 1; x++) { r = (((line[m_width + x] & 0xff) + (line[x + 1] & 0xff) + (line[x - 1] & 0xff) + (line[x] & 0xff)) >> 2) - m_burnrate; g = ((((line[m_width + x] & 0xFF00) >> 8) + ((line[x + 1] & 0xFF00) >> 8) + ((line[x - 1] & 0xFF00) >> 8) + ((line[x] & 0xFF00) >> 8)) >> 2) - m_burnrate; b = ((((line[m_width + x] & 0xFF0000) >> 16) + ((line[x + 1] & 0xFF0000) >> 16) + ((line[x - 1] & 0xFF0000) >> 16) + ((line[x] & 0xFF0000) >> 16)) >> 2) - m_burnrate; if (r < 0) { r = 0; } if (g < 0) { g = 0; } if (b < 0) { b = 0; } pixel = (r) | (g << 8) | (b << 16); if (pixel > 0) { line[x] = pixel; } } } } line = m_texture; for (x = 0; x < m_width; x++) { line[x] = 0; } for (y = 0; y < m_height; ++y) { line[m_width * y] = 0; line[m_width - 1 + m_width * y] = 0; } line = &m_texture[m_width * (m_height - 1)]; for (x = 0; x < m_width; x++) { line[x] = 0; } } /* ==================== GhostTexture::ComputeOutCode Returns a flag value indicating if x/y variable reached xmin/xmax ==================== */ outcode GhostTexture::ComputeOutCode(int x, int y, int xmin, int xmax, int ymin, int ymax) { outcode code = 0; if (y > ymax) { code = 1; } else if (y < ymin) { code = 2; } if (x > xmax) { code |= 4; } else if (x < xmin) { code |= 8; } return code; } /* ==================== GhostTexture::ClipAndDrawLine Draws a line from p0 to p1 with clipping ==================== */ void GhostTexture::ClipAndDrawLine(Vector p0, Vector p1, int color) { int x0, x1; int y0, y1; int d; int x, y; int ax, ay; int sx, sy; int dx, dy; int xmax, ymax; bool accept; outcode outcode0; outcode outcode1; x0 = p0.x; y0 = p0.y; x1 = p1.x; y1 = p1.y; xmax = m_width - 1; ymax = m_height - 1; accept = false; outcode0 = ComputeOutCode(x0, y0, 0, xmax, 0, ymax); outcode1 = ComputeOutCode(x1, y1, 0, xmax, 0, ymax); while (outcode1 | outcode0) { outcode outCodeOut; if (outcode0 & outcode1) { break; } outCodeOut = outcode0 ? outcode0 : outcode1; if (outCodeOut & 1) { x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0); y = ymax; } else if (outCodeOut & 2) { x = x0 - y0 * (x1 - x0) / (y1 - y0); y = 0; } else if (outCodeOut & 4) { x = xmax; y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0); } else if (outCodeOut * 8) { x = 0; y = y0 - x0 * (y1 - y0) / (x1 - x0); } else { x = x0; y = y0; } if (outCodeOut == outcode0) { y0 = y; x0 = x; outcode0 = ComputeOutCode(x, y, 0, xmax, 0, ymax); } else { x1 = x; y1 = y; outcode1 = ComputeOutCode(x, y, 0, xmax, 0, ymax); } } accept = true; if (!accept) { return; } dx = x1 - x0; if (dx < 0) { dx = x0 - x1; } ax = dx * 2; sx = dx >= 0 ? 1 : -1; dy = y1 - y0; if (dy < 0) { dy = y0 - y1; } ay = dy * 2; sy = dy >= 0 ? 1 : -1; x = x0; y = y0; if (ax > ay) { for (d = ay - (ax >> 1);; d += ay, x += sx) { SetTexel(x, y, color); if (x == x1) { break; } if (d >= 0) { y += sy; d -= ax; } } } else { for (d = ax - (ay >> 1);; d += ax, y += sy) { SetTexel(x, y, color); if (y == y1) { break; } if (d >= 0) { x += sx; d -= ay; } } } } /* ==================== GhostTexture::RotateVector Rotates the vector from angle ==================== */ Vector GhostTexture::RotateVector(Vector v, float angle) { Vector vec; float rad; rad = DEG2RAD(angle); vec.x = cos(rad) * v.x - sin(rad) * v.y; vec.y = cos(rad) * v.y + sin(rad) * v.x; vec.z = 0; return vec; } /* ==================== GhostTexture::GenerateLightning Generates a lightning effect ==================== */ void GhostTexture::GenerateLightning( Vector p1, Vector p2, int color, float angleVar, int numSubdivisions, int maxSubdivisions ) { Vector mid; Vector delta; float length; if (numSubdivisions == maxSubdivisions) { p1[0] += 0.5; p1[1] += 0.5; p2[0] += 0.5; p2[1] += 0.5; ClipAndDrawLine(p1, p2, color); return; } delta = p2 - p1; length = delta.length() * 0.5; delta.normalize(); mid = RotateVector(delta, RandomizeRange(-angleVar, angleVar)); mid = p1 + mid * length; GenerateLightning(p1, mid, color, angleVar, numSubdivisions + 1, maxSubdivisions); GenerateLightning(mid, p2, color, angleVar, numSubdivisions + 1, maxSubdivisions); } /* ==================== GhostTexture::Update Updates the ghost texture ==================== */ void GhostTexture::Update() { Vector pos; int i, j; int numEmitters; float currentTime; currentTime = tr.refdef.time / 1000.0; numEmitters = m_emitterList.NumObjects(); for (i = 1; i <= numEmitters; i++) { ParticleEmitter *pe = m_emitterList.ObjectAt(i); if (pe->IsGravityWell()) { break; } if (pe->IsBallLightning()) { Vector p1, p2; if (pe->GetBallLightningRadius()) { p1 = pe->GetPosition(); p2 = pe->GetVelocityDirection(); p2 = p1 + p2 * pe->GetBallLightningRadius(); GenerateLightning(p1, p2, pe->GetSrcColor(), pe->GetLightningVar(), 0, pe->GetLightningSubdivisions()); } } else if (pe->IsParticles()) { pe->Emit(currentTime); } else { Vector p = pe->GetPosition(); SetTexel(p.x, p.y, pe->GetSrcColor()); } for (j = pe->particleList.NumObjects(); j > 0; j--) { Particle *p = pe->particleList.ObjectAt(j); p->Update(currentTime); if (p->m_position.x < m_width && p->m_position.x >= 0 && p->m_position.y < m_height && p->m_position.y >= 0 && currentTime <= p->GetDieTime()) { SetTexel(p->m_position.x, p->m_position.y, p->m_color); } else { delete p; pe->particleList.RemoveObjectAt(j); } } } } /* ==================== R_UpdateGhostTextures Updates all ghost textures ==================== */ void R_UpdateGhostTextures() { int i; int numTextures; frameTime = tr.refdef.time - lastTime; lastTime = tr.refdef.time; numTextures = ghostManager.m_textureList.NumObjects(); for (i = 1; i <= numTextures; i++) { GhostTexture *gt = ghostManager.m_textureList.ObjectAt(i); gt->Update(); gt->Burn(); GL_BindToTMU(gt->m_image, 0); qglTexImage2D(GL_TEXTURE_2D, 0, 3, gt->m_width, gt->m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, gt->m_texture); } } /* ==================== R_SetGhostImage Applies an image to the specified named ghost texture ==================== */ void R_SetGhostImage(const char *name, image_t *image) { int i; int numTextures; numTextures = ghostManager.m_textureList.NumObjects(); for (i = 1; i <= numTextures; i++) { GhostTexture *gt = ghostManager.m_textureList.ObjectAt(i); if (gt->m_name == name) { gt->m_image = image; } } } /* ==================== LoadGHOST Loads a GHOST image file ==================== */ void LoadGHOST(const char *name, byte **pic, int *width, int *height) { char *buf_p; char *buffer; int numPixels; int numParticles; int i; GhostTexture *gt; if (ri.FS_ReadFile(name, (void **)&buffer) == -1) { return; } buf_p = buffer; COM_ParseExt(&buf_p, qtrue); *width = atoi(COM_ParseExt(&buf_p, qtrue)); *height = atoi(COM_ParseExt(&buf_p, qtrue)); numParticles = atoi(COM_ParseExt(&buf_p, qtrue)); numPixels = *height * *width; *pic = new byte[numPixels * 4]; gt = new GhostTexture(); gt->m_width = *width; gt->m_height = *height; gt->m_texture = new unsigned int[numPixels]; gt->m_isburn = atoi(COM_ParseExt(&buf_p, qtrue)); gt->m_burnrate = atof(COM_ParseExt(&buf_p, qtrue)); gt->m_isfade = atoi(COM_ParseExt(&buf_p, qtrue)); gt->m_faderate = atof(COM_ParseExt(&buf_p, qtrue)); ghostManager.m_textureList.AddObject(gt); for (i = 0; i < numParticles; i++) { ParticleEmitter *pe = new ParticleEmitter(); gt->m_emitterList.AddObject(pe); pe->Load(&buf_p); } }