Files

269 lines
8.9 KiB
C++

#include "gamelib/effects/ribbon.h"
#include <algorithm>
#include <cmath>
bool GameLib_GetRibbonParameters(const idDeclRibbon* decl,
idRibbonParameters& parameters);
void GameLib_UpdateRibbonGeometry(idRenderModelBeam* beamModel,
const idDeclRibbon* decl, const idRibbon::ribbonNode_t* nodes,
int firstNode, int numNodes, int currentTime,
const idMat3& viewAxis, const idVec3& viewOrigin,
bool explicitBothEdges, const idVec4& globalColor);
namespace {
idVec4 MultiplyColor(const idVec4& left, const idVec4& right) {
return idVec4(left.x * right.x, left.y * right.y,
left.z * right.z, left.w * right.w);
}
idVec3 Lerp(const idVec3& from, const idVec3& to, const float fraction) {
return from + (to - from) * fraction;
}
} // namespace
idRibbon::idRibbon(const idDeclRibbon* const decl)
: ribbonState(RIBBON_INACTIVE)
, spawnOrg()
, spawnTime(0)
, numActiveNodes(0)
, currHeadIdx(-1)
, startVariance(0.0f)
, prevTime(-1)
, prevOrigin()
, prevOrigin2()
, ribbonDecl(decl)
, globalColor(1.0f, 1.0f, 1.0f, 1.0f)
, ribbonNodes{}
, explicitDefineBothEdges(false) {
ResetNodes();
// The recovered constructor advances the same LCG once for start phase.
static unsigned int ribbonSeed = 0;
ribbonSeed = 1664525u * ribbonSeed + 1013904223u;
startVariance = ((ribbonSeed >> 10) & 0x7FFFu) *
(1.0f / 32768.0f);
}
void idRibbon::ResetNodes() {
prevTime = -1;
currHeadIdx = -1;
ribbonState = RIBBON_INACTIVE;
spawnTime = 0;
numActiveNodes = 0;
explicitDefineBothEdges = false;
for (ribbonNode_t& node : ribbonNodes) {
node.origin.Zero();
node.origin2.Zero();
node.velocity.Zero();
node.orientVec.Zero();
node.color = idVec4(0.0f, 0.0f, 0.0f, 0.0f);
node.spawnTime = 0;
node.nodeLength = 0.0f;
}
}
void idRibbon::StartRibbon(const int time, const idVec3& origin) {
ResetNodes();
ribbonState = RIBBON_ACTIVE;
spawnOrg = origin;
prevOrigin = origin;
prevOrigin2 = origin;
spawnTime = time;
prevTime = time;
}
void idRibbon::CalcTurbulence(const idVec3& origin, const idMat3& axis,
idVec3& turbulence) const {
turbulence.Zero();
idRibbonParameters parms{};
if (!GameLib_GetRibbonParameters(ribbonDecl, parms) ||
std::fabs(parms.turbulenceMagnitude) <= 1.1920929e-7f) {
return;
}
const float frequency = parms.turbulenceFrequency;
const idVec4& magic = parms.turbulenceMagic;
const idVec3 local(
std::sin(magic.x * origin.x * frequency) -
std::cos(magic.w * origin.y * frequency),
std::sin(magic.y * origin.y * frequency) -
std::cos(magic.w * origin.x * frequency),
std::cos(magic.z * origin.z * frequency) -
std::sin(magic.w * origin.z * frequency));
turbulence = axis * (local * parms.turbulenceMagnitude);
}
void idRibbon::CalcHelix(const int currentTime, const idVec3& origin,
const idMat3& axis, const idVec3& velocity, idVec3& helix) const {
(void)origin;
helix.Zero();
idRibbonParameters parms{};
if (!GameLib_GetRibbonParameters(ribbonDecl, parms) ||
parms.helixRadius <= 1.1920929e-7f) {
return;
}
const float phase = parms.helixRotationScale *
static_cast<float>(currentTime - spawnTime) +
startVariance * 6.28318530718f;
idVec3 direction = velocity;
if (direction.NormalizeFast() == 0.0f) {
direction = axis[0];
}
idVec3 side = direction.Cross(axis[2]);
if (side.NormalizeFast() == 0.0f) {
side = axis[1];
}
idVec3 up = direction.Cross(side);
up.NormalizeFast();
helix = (side * std::cos(phase) + up * std::sin(phase)) *
parms.helixRadius;
}
void idRibbon::AddNode(const int currentTime, const idVec3& origin,
const idVec3& origin2, const idMat3& axis, const idVec3& velocity,
const idVec4& color) {
idRibbonParameters parms{};
if (!GameLib_GetRibbonParameters(ribbonDecl, parms)) {
return;
}
explicitDefineBothEdges = &origin != &origin2 ||
(origin - origin2).LengthSqr() > 0.0f;
idVec3 turbulence1, turbulence2, helix1, helix2;
CalcTurbulence(origin, axis, turbulence1);
CalcHelix(currentTime, origin, axis, velocity, helix1);
if (explicitDefineBothEdges) {
CalcTurbulence(origin2, axis, turbulence2);
CalcHelix(currentTime, origin2, axis, velocity, helix2);
} else {
turbulence2 = turbulence1;
helix2 = helix1;
}
currHeadIdx = (currHeadIdx + 1) % MAX_NODES;
ribbonNode_t& node = ribbonNodes[currHeadIdx];
node.spawnTime = currentTime;
node.origin = origin + turbulence1 + helix1;
node.origin2 = origin2 + turbulence2 + helix2;
node.velocity = velocity + axis * parms.velocity +
turbulence1 * parms.turbulenceVelocity +
helix1 * parms.helixVelocity;
node.color = MultiplyColor(parms.color, color);
node.orientVec = axis[0];
if (numActiveNodes > 0) {
const ribbonNode_t& previous =
ribbonNodes[(currHeadIdx + MAX_NODES - 1) % MAX_NODES];
node.nodeLength = (node.origin - previous.origin).Length();
} else {
node.nodeLength = 0.0f;
}
if (numActiveNodes < MAX_NODES) {
++numActiveNodes;
}
}
void idRibbon::UpdatePosition(const int currentTime, const idVec3& origin,
const idMat3& axis, const idVec3& velocity, const idVec4& color) {
UpdatePosition(currentTime, origin, origin, axis, velocity, color);
}
void idRibbon::UpdatePosition(const int currentTime, const idVec3& origin,
const idVec3& origin2, const idMat3& axis, const idVec3& velocity,
const idVec4& color) {
idRibbonParameters parms{};
if (ribbonState != RIBBON_ACTIVE ||
!GameLib_GetRibbonParameters(ribbonDecl, parms)) {
return;
}
const float distanceSqr = (prevOrigin - origin).LengthSqr();
if (parms.minNodeDistance > 0.0f &&
distanceSqr <= parms.minNodeDistance * parms.minNodeDistance) {
return;
}
int subdivisions = 1;
if (parms.nodeSubdivision > 0.0f && prevTime >= 0) {
subdivisions = (std::min)(MAX_NODES,
static_cast<int>(std::sqrt(distanceSqr) /
parms.nodeSubdivision) + 1);
}
for (int index = 1; index <= subdivisions; ++index) {
const float fraction = index / static_cast<float>(subdivisions);
const int nodeTime = prevTime + static_cast<int>(
(currentTime - prevTime) * fraction);
AddNode(nodeTime, Lerp(prevOrigin, origin, fraction),
Lerp(prevOrigin2, origin2, fraction), axis, velocity, color);
}
prevTime = currentTime;
prevOrigin = origin;
prevOrigin2 = origin2;
}
void idRibbon::RemoveOldNodes(const int currentTime) {
if (numActiveNodes == 0) {
return;
}
idRibbonParameters parms{};
if (!GameLib_GetRibbonParameters(ribbonDecl, parms)) {
return;
}
if (parms.maxLength != 0.0f) {
float length = 0.0f;
int retained = 0;
for (int offset = 0; offset < numActiveNodes; ++offset) {
const int index = (currHeadIdx - offset + MAX_NODES) % MAX_NODES;
length += ribbonNodes[index].nodeLength;
if (length <= parms.maxLength) ++retained;
}
numActiveNodes = retained;
}
while (numActiveNodes > 0) {
const int oldest =
(currHeadIdx - numActiveNodes + 1 + MAX_NODES) % MAX_NODES;
if (parms.duration > 0 &&
ribbonNodes[oldest].spawnTime + parms.duration < currentTime) {
--numActiveNodes;
} else if (parms.duration <= 0 &&
ribbonState == RIBBON_DEACTIVATING) {
--numActiveNodes;
} else {
break;
}
}
if (numActiveNodes <= 0) {
numActiveNodes = 0;
explicitDefineBothEdges = false;
ribbonState = RIBBON_INACTIVE;
}
}
int idRibbon::GetRemainingTime(const int currentTime) const {
idRibbonParameters parms{};
if (!GameLib_GetRibbonParameters(ribbonDecl, parms) ||
parms.duration <= 0) {
return 0;
}
int remaining = 0;
for (int offset = 0; offset < numActiveNodes; ++offset) {
const int index =
(currHeadIdx - offset + MAX_NODES) % MAX_NODES;
remaining = (std::max)(remaining,
ribbonNodes[index].spawnTime + parms.duration - currentTime);
}
return remaining;
}
void idRibbon::UpdateGeometry(const int currentTime,
idRenderModelBeam* const beamModel, const idMat3& viewAxis,
const idVec3& viewOrigin) {
RemoveOldNodes(currentTime);
if (beamModel == nullptr) {
return;
}
const int firstNode = numActiveNodes > 0
? (currHeadIdx - numActiveNodes + 1 + MAX_NODES) % MAX_NODES
: 0;
GameLib_UpdateRibbonGeometry(beamModel, ribbonDecl, ribbonNodes,
firstNode, numActiveNodes, currentTime, viewAxis, viewOrigin,
explicitDefineBothEdges, globalColor);
}