halflife-photomode/cl_dll/particleman/CBaseParticle.cpp

511 lines
11 KiB
C++

/***
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
* Use, distribution, and modification of this source code and/or resulting
* object code is restricted to non-commercial enhancements to products from
* Valve LLC. All other use, distribution, or modification is prohibited
* without written permission from Valve LLC.
*
****/
#include "hud.h"
#include "cl_util.h"
#undef clamp
#include <algorithm>
#include "event_api.h"
#include "triangleapi.h"
#include "particleman.h"
#include "particleman_internal.h"
#include "CBaseParticle.h"
#include "pm_defs.h"
#include "pmtrace.h"
void CBaseParticle::InitializeSprite(Vector org, Vector normal, model_s* sprite, float size, float brightness)
{
m_flSize = m_flOriginalSize = 10;
m_flScaleSpeed = 0;
m_flContractSpeed = 0;
m_flStretchX = m_flStretchY = 1;
m_pTexture = nullptr;
m_flOriginalBrightness = 255;
m_flBrightness = 255;
strncpy(m_szClassname, "particle", sizeof(m_szClassname) - 1);
m_szClassname[sizeof(m_szClassname) - 1] = '\0';
m_flDieTime = 0;
m_flDampingTime = 0;
m_flNextPVSCheck = gEngfuncs.GetClientTime();
m_flGravity = 0;
m_flNextCollisionTime = 0;
m_bInPVS = true;
m_flFadeSpeed = -1;
m_iFramerate = 0;
m_iNumFrames = 0;
m_iFrame = 0;
m_iCollisionFlags = 0;
m_iRendermode = kRenderNormal;
m_iRenderFlags = 0;
m_vVelocity.x = 0;
m_vVelocity.y = 0;
m_vVelocity.z = 0;
m_flBounceFactor = 1;
m_vOriginalAngles = g_vecZero;
m_vAngles = g_vecZero;
m_vAVelocity = g_vecZero;
m_bInWater = false;
m_bAffectedByForce = false;
m_vColor.x = 255;
m_vColor.y = 255;
m_vColor.z = 255;
m_flTimeCreated = gEngfuncs.GetClientTime();
m_vPrevOrigin = m_vOrigin = org;
m_vOriginalAngles = m_vAngles = normal;
m_pTexture = sprite;
m_flOriginalSize = m_flSize = size;
m_flOriginalBrightness = m_flBrightness = brightness;
Vector forward, right, up;
gEngfuncs.pfnAngleVectors(normal, forward, right, up);
const Vector scaledRight = right * size;
const Vector scaledUp = up * size;
m_vLowLeft = org - scaledRight * 0.5 - scaledUp * 0.5;
//TODO: not sure if these are correct. If low left is half of the scaled directions then the full direction * 2 results in double size particles.
m_vLowRight = m_vLowLeft + scaledRight + scaledRight;
m_vTopLeft = m_vLowLeft + scaledUp + scaledUp;
}
bool CBaseParticle::CheckVisibility()
{
const float radius = m_flSize / 5.0;
if (gEngfuncs.GetClientTime() >= m_flNextPVSCheck)
{
const Vector radiusVector{radius, radius, radius};
Vector mins = m_vOrigin - radiusVector;
Vector maxs = m_vOrigin + radiusVector;
m_bInPVS = gEngfuncs.pTriAPI->BoxInPVS(mins, maxs) != 0;
m_flNextPVSCheck = gEngfuncs.GetClientTime() + 0.1;
}
if ((m_iRenderFlags & CULL_FRUSTUM_SPHERE) != 0)
{
if (!g_cFrustum.SphereInsideFrustum(m_vOrigin.x, m_vOrigin.y, m_vOrigin.z, radius))
{
return false;
}
}
else if ((m_iRenderFlags & CULL_FRUSTUM_PLANE) != 0)
{
if (!g_cFrustum.PlaneInsideFrustum(m_vOrigin.x, m_vOrigin.y, m_vOrigin.z, radius))
{
return false;
}
}
else if ((m_iRenderFlags & CULL_FRUSTUM_POINT) != 0)
{
if (!g_cFrustum.PointInsideFrustum(m_vOrigin.x, m_vOrigin.y, m_vOrigin.z))
{
return false;
}
}
if (!m_bInPVS)
{
return (m_iRenderFlags & CULL_PVS) == 0;
}
return true;
}
void CBaseParticle::Draw()
{
if (m_flDieTime == gEngfuncs.GetClientTime())
{
return;
}
Vector vColor;
float intensity = 0.0;
if ((m_iRenderFlags & LIGHT_NONE) == 0)
{
gEngfuncs.pTriAPI->LightAtPoint(m_vOrigin, vColor);
intensity = (vColor.x + vColor.y + vColor.z) / 3.0;
}
if ((m_iRenderFlags & (RENDER_FACEPLAYER | RENDER_FACEPLAYER_ROTATEZ)) != 0)
{
m_vAngles.x = g_vViewAngles.x;
m_vAngles.y = g_vViewAngles.y;
if ((m_iRenderFlags & RENDER_FACEPLAYER_ROTATEZ) == 0)
{
m_vAngles.z = g_vViewAngles.z;
}
}
Vector resultColor;
if ((m_iRenderFlags & LIGHT_NONE) != 0)
{
resultColor = m_vColor;
}
else if ((m_iRenderFlags & LIGHT_COLOR) != 0)
{
resultColor.x = vColor.x / (m_vColor.x * 255);
resultColor.y = vColor.y / (m_vColor.y * 255);
resultColor.z = vColor.z / (m_vColor.z * 255);
}
else if ((m_iRenderFlags & LIGHT_INTENSITY) != 0)
{
resultColor.x = intensity / (m_vColor.x * 255);
resultColor.y = intensity / (m_vColor.y * 255);
resultColor.z = intensity / (m_vColor.z * 255);
}
resultColor.x = std::clamp(resultColor.x, 0.f, 255.f);
resultColor.y = std::clamp(resultColor.y, 0.f, 255.f);
resultColor.z = std::clamp(resultColor.z, 0.f, 255.f);
Vector forward, right, up;
gEngfuncs.pfnAngleVectors(m_vAngles, forward, right, up);
const float radius = m_flSize;
const Vector width = right * radius * m_flStretchX;
const Vector height = up * radius * m_flStretchY;
//TODO: shouldn't this be accounting for stretch Y?
const Vector lowLeft = m_vOrigin - (width * 0.5) - (up * radius * 0.5);
const Vector lowRight = lowLeft + width;
const Vector topLeft = lowLeft + height;
const Vector topRight = lowRight + height;
gEngfuncs.pTriAPI->SpriteTexture(m_pTexture, m_iFrame);
gEngfuncs.pTriAPI->RenderMode(m_iRendermode);
gEngfuncs.pTriAPI->CullFace(TRI_NONE);
gEngfuncs.pTriAPI->Begin(TRI_QUADS);
gEngfuncs.pTriAPI->Color4f(resultColor.x / 255, resultColor.y / 255, resultColor.z / 255, m_flBrightness / 255);
gEngfuncs.pTriAPI->TexCoord2f(0, 0);
gEngfuncs.pTriAPI->Vertex3fv(topLeft);
gEngfuncs.pTriAPI->TexCoord2f(0, 1);
gEngfuncs.pTriAPI->Vertex3fv(lowLeft);
gEngfuncs.pTriAPI->TexCoord2f(1, 1);
gEngfuncs.pTriAPI->Vertex3fv(lowRight);
gEngfuncs.pTriAPI->TexCoord2f(1, 0);
gEngfuncs.pTriAPI->Vertex3fv(topRight);
gEngfuncs.pTriAPI->End();
gEngfuncs.pTriAPI->RenderMode(kRenderNormal);
gEngfuncs.pTriAPI->CullFace(TRI_FRONT);
}
void CBaseParticle::Animate(float time)
{
if (0 != m_iFramerate && 0 != m_iNumFrames)
{
m_iFrame = static_cast<int>(m_iFramerate * (time - m_flTimeCreated)) % m_iNumFrames;
}
}
void CBaseParticle::AnimateAndDie(float time)
{
if (0 != m_iFramerate && 0 != m_iNumFrames)
{
const float frame = m_iFramerate * (time - m_flTimeCreated);
if (frame > m_iNumFrames)
{
m_flDieTime = time;
}
else
{
m_iFrame = frame;
}
if (m_iFrame > m_iNumFrames)
{
m_iFrame = m_iNumFrames - 1;
}
}
}
void CBaseParticle::Expand(float time)
{
if (m_flScaleSpeed != 0)
{
m_flSize = (m_flScaleSpeed * 30.0 * (time - m_flTimeCreated)) + m_flOriginalSize;
if (m_flSize < 0.0001)
{
m_flDieTime = time;
}
}
}
void CBaseParticle::Contract(float time)
{
if (m_flContractSpeed != 0)
{
m_flSize = m_flOriginalSize - (m_flContractSpeed * 30.0 * (time - m_flTimeCreated));
if (m_flSize < 0.0001)
{
m_flDieTime = time;
}
}
}
void CBaseParticle::Fade(float time)
{
if (m_flFadeSpeed >= -0.5)
{
if (m_flFadeSpeed == 0)
{
m_flBrightness = (1.0 - (time - m_flTimeCreated) / (m_flDieTime - m_flTimeCreated)) * m_flOriginalBrightness;
}
else
{
m_flBrightness = m_flOriginalBrightness - m_flFadeSpeed * 30.0 * (time - m_flTimeCreated);
}
if (m_flBrightness < 1)
{
m_flDieTime = time;
}
}
}
void CBaseParticle::Spin(float time)
{
if (m_vAVelocity == g_vecZero)
{
return;
}
//Superfluous; overwritten below.
//m_vAngles = m_vAngles + m_vAVelocity;
Vector point = m_vAVelocity;
const float length = VectorNormalize(point) * 30.0 * (time - m_flTimeCreated);
m_vAngles = m_vOriginalAngles + m_vAVelocity * length;
}
void CBaseParticle::CalculateVelocity(float time)
{
const float deltaTime = time - g_flOldTime;
const float gravity = -deltaTime * g_flGravity * m_flGravity;
if (m_vVelocity == g_vecZero && gravity == 0)
{
return;
}
if ((m_iCollisionFlags & TRI_SPIRAL) != 0)
{
m_vOrigin = m_vOrigin + m_vVelocity * deltaTime;
m_vOrigin.x += sin(time * 5.0 + reinterpret_cast<std::intptr_t>(this)) * 2;
m_vOrigin.y += sin(time * 7.5 + reinterpret_cast<std::intptr_t>(this));
}
else
{
m_vOrigin = m_vOrigin + m_vVelocity * deltaTime;
}
m_vVelocity.z = m_vVelocity.z + gravity;
}
void CBaseParticle::CheckCollision(float time)
{
if (m_iCollisionFlags == 0)
{
return;
}
m_flNextCollisionTime = time;
if ((m_iCollisionFlags & (TRI_WATERTRACE | TRI_COLLIDEALL | TRI_COLLIDEWORLD)) == 0)
{
return;
}
pmtrace_t trace;
bool collided = false;
if ((m_iCollisionFlags & TRI_COLLIDEALL) != 0)
{
gEngfuncs.pEventAPI->EV_SetTraceHull(2);
gEngfuncs.pEventAPI->EV_PlayerTrace(m_vPrevOrigin, m_vOrigin, PM_STUDIO_BOX, -1, &trace);
if (trace.fraction != 1.0)
{
//Called but never used, probably unfinished code for colliding with other entities.
//auto entity = gEngfuncs.pEventAPI->EV_GetPhysent(trace.ent);
//Collided with something other than world, ignore.
if (0 == trace.ent)
{
collided = true;
}
}
}
else if ((m_iCollisionFlags & TRI_COLLIDEWORLD) != 0)
{
gEngfuncs.pEventAPI->EV_SetTraceHull(2);
gEngfuncs.pEventAPI->EV_PlayerTrace(m_vPrevOrigin, m_vOrigin, PM_WORLD_ONLY | PM_STUDIO_BOX, -1, &trace);
if (trace.fraction != 1.0)
{
m_vVelocity = m_vVelocity * 0.6;
if (m_vVelocity.Length() < 10)
{
m_iCollisionFlags = 0;
m_vVelocity = g_vecZero;
m_vAVelocity = g_vecZero;
m_vOrigin = trace.endpos;
}
collided = true;
}
}
if (collided)
{
const float frametime = time - g_flOldTime;
m_vOrigin = m_vPrevOrigin + m_vVelocity * (trace.fraction * frametime);
float bounce;
bool dead = false;
if (trace.plane.normal.z <= 0.9 || m_vVelocity.z > 0 || (m_vVelocity.z < -frametime * g_flGravity * m_flGravity * 3.0))
{
if ((m_iCollisionFlags & TRI_COLLIDEKILL) != 0)
{
m_flDieTime = gEngfuncs.GetClientTime();
dead = true;
}
else
{
bounce = m_flBounceFactor * 0.5;
if (bounce != 0)
{
const float dot = DotProduct(trace.plane.normal, m_vVelocity) * -2;
m_vVelocity = m_vVelocity + trace.plane.normal * dot;
m_vAngles.y = -m_vAngles.y;
}
}
}
else
{
//Particle fell on an (almost) flat surface and has no velocity to bounce back up; disable collisions from now on.
bounce = 0;
m_vVelocity = g_vecZero;
m_vAVelocity = g_vecZero;
m_iCollisionFlags = 0;
}
if (!dead && bounce != 1)
{
m_vVelocity = m_vVelocity * bounce;
m_vAngles = m_vAngles * 0.9;
}
Touch(trace.endpos, trace.plane.normal, trace.ent);
}
else if ((m_iCollisionFlags & TRI_WATERTRACE) != 0)
{
if (gEngfuncs.PM_PointContents(m_vOrigin, nullptr) == CONTENTS_WATER && !m_bInWater)
{
Touch(m_vOrigin, {0, 0, 1}, 0);
m_bInWater = true;
if ((m_iCollisionFlags & TRI_COLLIDEKILL) != 0)
{
m_flDieTime = gEngfuncs.GetClientTime();
}
}
}
m_vPrevOrigin = m_vOrigin;
}
void CBaseParticle::Touch(Vector pos, Vector normal, int index)
{
//Nothing.
}
void CBaseParticle::Die()
{
//Nothing.
}
void CBaseParticle::Force()
{
//Nothing.
}
void CBaseParticle::Think(float time)
{
if ((m_iCollisionFlags & TRI_ANIMATEDIE) != 0)
{
AnimateAndDie(time);
}
else
{
Animate(time);
}
Contract(time);
Expand(time);
Fade(time);
Spin(time);
CalculateVelocity(time);
CheckCollision(time);
}