TombEngine/TombEngine/Game/effects/lightning.cpp
2023-01-26 23:23:13 +11:00

327 lines
9 KiB
C++

#include "framework.h"
#include "Game/effects/lightning.h"
#include "Game/effects/effects.h"
#include "Game/people.h"
#include "Math/Math.h"
#include "Specific/setup.h"
using namespace TEN::Math;
namespace TEN::Effects::ElectricArc
{
static constexpr auto HELICAL_LASER_LIFE_MAX = 18.0f;
std::vector<ElectricArc> ElectricArcs = {};
std::vector<HelicalLaser> HelicalLasers = {};
std::array<Vector3, ELECTRIC_ARC_KNOTS_SIZE> ElectricArcKnots = {};
std::array<Vector3, ELECTRIC_ARC_BUFFER_SIZE> ElectricArcBuffer = {};
// BIG TODO: Make a family of Bezier, B-Spline, and Catmull-Rom curve classes.
// More standard version. Adopt this in place of the one below.
static Vector3 CatmullRomSpline(float alpha, const std::array<Vector3, 4>& knots)
{
auto point1 = knots[1] + ((knots[2] - knots[0]) * (1 / 6.0f));
auto point2 = knots[2];
auto point3 = knots[2] + ((knots[3] - knots[1]) * (-1 / 6.0f));
auto point4 = knots[3];
auto spline = ((point2 * 2) + (point3 - point1) * alpha) +
(((point1 * 2) - (point2 * 5) + (point3 * 4) - point4) * SQUARE(alpha)) +
(((point1 * -1) + (point2 * 3) - (point3 * 3) + point4) * CUBE(alpha));
return spline;
}
// 4-point Catmull-Rom spline interpolation.
// Function takes reference to array of knots and
// calculates using subset of 4 determined alpha value.
static Vector3 ElectricArcSpline(const std::array<Vector3, ELECTRIC_ARC_KNOTS_SIZE>& knots, float alpha)
{
alpha *= ELECTRIC_ARC_KNOTS_SIZE - 3;
int span = alpha;
if (span >= (ELECTRIC_ARC_KNOTS_SIZE - 3))
span = ELECTRIC_ARC_KNOTS_SIZE - 4;
float something = alpha - span;
// Determine subset of 4 knots.
const auto& knot0 = knots[span];
const auto& knot1 = knots[span + 1];
const auto& knot2 = knots[span + 2];
const auto& knot3 = knots[span + 3];
auto point1 = knot1 + (knot1 / 2) - (knot2 / 2) - knot2 + (knot3 / 2) + ((-knot0 - Vector3::One) / 2);
auto ret = point1 * something;
auto point2 = ret + Vector3(2.0f) * knot2 - 2 * knot1 - (knot1 / 2) - (knot3 / 2) + knot0;
ret = point2 * something;
auto point3 = ret + (knot2 / 2) + ((-knot0 - Vector3::One) / 2);
ret = point3 * something;
return (ret + knot1);
}
// TODO: Pass const Vector4& for color.
void SpawnElectricArc(const Vector3& origin, const Vector3& target, float amplitude, byte r, byte g, byte b, float life, int flags, float width, unsigned int numSegments)
{
auto arc = ElectricArc();
arc.pos1 = origin;
arc.pos2 = ((origin * 3) + target) / 4;
arc.pos3 = ((target * 3) + origin) / 4;
arc.pos4 = target;
arc.flags = flags;
for (int i = 0; i < arc.interpolation.size(); i++)
{
if (arc.flags & LI_MOVEEND || i < (arc.interpolation.size() - 1))
{
arc.interpolation[i] = Vector3(
fmod(Random::GenerateInt(), amplitude),
fmod(Random::GenerateInt(), amplitude),
fmod(Random::GenerateInt(), amplitude)) -
Vector3(amplitude/ 2);
}
else
{
arc.interpolation[i] = Vector3::Zero;
}
}
arc.r = r;
arc.g = g;
arc.b = b;
arc.life = life;
arc.segments = numSegments;
arc.amplitude = amplitude;
arc.width = width;
ElectricArcs.push_back(arc);
}
void SpawnElectricArcGlow(const Vector3& pos, float scale, byte r, byte g, byte b)
{
auto& spark = *GetFreeParticle();
spark.on = true;
spark.spriteIndex = Objects[ID_MISC_SPRITES].meshIndex;
spark.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark.x = pos.x;
spark.y = pos.y;
spark.z = pos.z;
spark.xVel = 0;
spark.yVel = 0;
spark.zVel = 0;
spark.sR = r;
spark.sG = g;
spark.sB = b;
spark.dR = r;
spark.dG = g;
spark.dB = b;
spark.life = 4;
spark.sLife = 4;
spark.colFadeSpeed = 2;
spark.fadeToBlack = 0;
spark.scalar = 3;
spark.maxYvel = 0;
spark.gravity = 0;
spark.sSize =
spark.dSize =
spark.size = scale + Random::GenerateInt(0, 4);
spark.flags = SP_DEF | SP_SCALE;
}
void SpawnHelicalLaser(const Vector3& origin, const Vector3& target)
{
static constexpr auto SEGMENTS_NUM_MAX = 128;
static constexpr auto COLOR = Vector4(0.0f, 0.375f, 1.0f, 1.0f);
static constexpr auto LENGTH_MAX = BLOCK(4);
static constexpr auto ROTATION = ANGLE(-10.0f);
static constexpr auto ELECTRIC_ARC_FLAGS = LI_THININ | LI_THINOUT;
auto laser = HelicalLaser();
laser.NumSegments = SEGMENTS_NUM_MAX;
laser.Origin = origin;
laser.Target = target;
laser.Orientation2D = Random::GenerateAngle();
laser.LightPosition = origin;
laser.Color = COLOR;
laser.Life = HELICAL_LASER_LIFE_MAX;
laser.Radius = 0.0f;
laser.Length = 0.0f;
laser.LengthEnd = LENGTH_MAX;
laser.Opacity = 1.0f;
laser.Rotation = ROTATION;
HelicalLasers.push_back(laser);
SpawnElectricArc(origin, target, 1, 0, laser.Color.x * UCHAR_MAX, laser.Color.z * UCHAR_MAX, 20, ELECTRIC_ARC_FLAGS, 19, 5);
SpawnElectricArc(origin, target, 1, 110, 255, 250, 20, ELECTRIC_ARC_FLAGS, 4, 5);
SpawnElectricArcGlow(laser.LightPosition, 0, 0, (laser.Color.x / 2) * UCHAR_MAX, (laser.Color.z / 2) * UCHAR_MAX);
}
void UpdateHelicalLasers()
{
static constexpr auto LIFE_START_FADING = HELICAL_LASER_LIFE_MAX / 2;
static constexpr auto LENGTH_LERP_ALPHA = 0.25f;
// No active effects; return early.
if (HelicalLasers.empty())
return;
for (auto& laser : HelicalLasers)
{
// Set to despawn.
laser.Life -= 1.0f;
if (laser.Life <= 0.0f)
continue;
// Update length.
laser.Length = Lerp(laser.Length, laser.LengthEnd, LENGTH_LERP_ALPHA);
// Update radius.
laser.Radius += 1 / 8.0f;
// Update opacity.
float alpha = laser.Life / LIFE_START_FADING;
laser.Opacity = Lerp(0.0f, 1.0f, alpha);
// Update orientation.
laser.Orientation2D += laser.Rotation;
}
// Despawn inactive effects.
HelicalLasers.erase(
std::remove_if(
HelicalLasers.begin(), HelicalLasers.end(),
[](const HelicalLaser& laser) { return (laser.Life <= 0.0f); }), HelicalLasers.end());
}
void UpdateElectricArcs()
{
// No active effects; return early.
if (ElectricArcs.empty())
return;
for (auto& arc : ElectricArcs)
{
// Set to despawn.
if (arc.life <= 0.0f)
continue;
// If/when this behaviour is changed, modify AddLightningArc accordingly.
arc.life -= 2.0f;
if (arc.life > 0.0f)
{
// TODO: Find a better way to do this.
auto* posPtr = (Vector3*)&arc.pos2;
for (auto& interpPos : arc.interpolation)
{
*posPtr += interpPos * 2;
interpPos -= (interpPos / 16);
posPtr++;
}
}
}
// Despawn inactive effects.
ElectricArcs.erase(
std::remove_if(
ElectricArcs.begin(), ElectricArcs.end(),
[](const ElectricArc& arc) { return (arc.life <= 0.0f); }), ElectricArcs.end());
}
void CalculateElectricArcSpline(const ElectricArc& arc, const std::array<Vector3, ELECTRIC_ARC_KNOTS_SIZE>& knots, std::array<Vector3, ELECTRIC_ARC_BUFFER_SIZE>& buffer)
{
int bufferIndex = 0;
buffer[bufferIndex] = knots[0];
bufferIndex++;
// Splined arc.
if (arc.flags & LI_SPLINE)
{
float interpStep = 1.0f / ((arc.segments * 3) - 1);
float alpha = interpStep;
if (((arc.segments * 3) - 2) > 0)
{
for (int i = (arc.segments * 3) - 2; i > 0; i--)
{
auto spline = ElectricArcSpline(knots, alpha);
auto sphere = BoundingSphere(Vector3::Zero, 8.0f);
auto offset = Random::GeneratePointInSphere(sphere);
buffer[bufferIndex] = spline + offset;
alpha += interpStep;
bufferIndex++;
}
}
}
// Straight arc.
else
{
int numSegments = (arc.segments * 3) - 1;
auto deltaPos = (knots[knots.size() - 1] - knots[0]) / numSegments;
auto pos = knots[0] + deltaPos + Vector3(
fmod(Random::GenerateInt(), arc.amplitude * 2),
fmod(Random::GenerateInt(), arc.amplitude * 2),
fmod(Random::GenerateInt(), arc.amplitude * 2)) -
Vector3(arc.amplitude);
if (((arc.segments * 3) - 2) > 0)
{
for (int i = (arc.segments * 3) - 2; i > 0; i--)
{
buffer[bufferIndex] = pos;
bufferIndex++;
pos += deltaPos + Vector3(
fmod(Random::GenerateInt(), arc.amplitude * 2),
fmod(Random::GenerateInt(), arc.amplitude * 2),
fmod(Random::GenerateInt(), arc.amplitude * 2)) -
Vector3(arc.amplitude);
}
}
}
buffer[bufferIndex] = knots[5];
}
void CalculateHelixSpline(const HelicalLaser& laser, std::array<Vector3, ELECTRIC_ARC_KNOTS_SIZE>& knots, std::array<Vector3, ELECTRIC_ARC_BUFFER_SIZE>& buffer)
{
int bufferIndex = 0;
buffer[bufferIndex] = knots[0];
bufferIndex++;
auto origin = knots[0];
auto target = knots[1];
auto direction = target - origin;
direction.Normalize();
float stepLength = laser.Length / laser.NumSegments;
float radiusStep = laser.Radius;
auto refPoint = Geometry::RotatePoint(Vector3::Right, EulerAngles(direction));
auto axisAngle = AxisAngle(direction, laser.Orientation2D);
for (int i = 0; i < laser.NumSegments; i++)
{
axisAngle.SetAngle(axisAngle.GetAngle() + ANGLE(25.0f));
auto pos = Geometry::RotatePoint(refPoint * (radiusStep * i), axisAngle);
buffer[bufferIndex] = origin + Geometry::TranslatePoint(pos, axisAngle.GetAxis(), stepLength * i);
bufferIndex++;
}
buffer[bufferIndex] = knots[1];
}
}