mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 12:41:35 -10:00
* add beam particles * tweaks * more tweaks * fix warning * dumb c++ * fix switch * spelling
264 lines
7.9 KiB
C++
264 lines
7.9 KiB
C++
#ifndef GENERIC_SHAPE_EFFECT_H
|
|
#define GENERIC_SHAPE_EFFECT_H
|
|
#include "osapi/dialogs.h"
|
|
#include "parse/parselo.h"
|
|
#pragma once
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#include "particle/ParticleEffect.h"
|
|
#include "particle/ParticleManager.h"
|
|
#include "particle/util/ParticleProperties.h"
|
|
#include "particle/util/EffectTiming.h"
|
|
#include "utils/RandomRange.h"
|
|
#include "weapon/beam.h"
|
|
|
|
namespace particle {
|
|
namespace effects {
|
|
|
|
enum class ConeDirection {
|
|
Incoming,
|
|
Normal,
|
|
Reflected,
|
|
Reverse
|
|
};
|
|
|
|
/**
|
|
* @brief A generic particle effect with a customizable shape
|
|
*
|
|
* @ingroup particleEffects
|
|
*/
|
|
template<typename TShape>
|
|
class GenericShapeEffect : public ParticleEffect {
|
|
private:
|
|
util::ParticleProperties m_particleProperties;
|
|
|
|
ConeDirection m_direction = ConeDirection::Incoming;
|
|
::util::UniformFloatRange m_velocity;
|
|
::util::UniformUIntRange m_particleNum;
|
|
float m_particleChance;
|
|
::util::UniformFloatRange m_particleRoll;
|
|
ParticleEffectHandle m_particleTrail = ParticleEffectHandle::invalid();
|
|
|
|
util::EffectTiming m_timing;
|
|
|
|
::util::UniformFloatRange m_vel_inherit;
|
|
|
|
TShape m_shape;
|
|
|
|
vec3d getNewDirection(const ParticleSource* source) const {
|
|
switch (m_direction) {
|
|
case ConeDirection::Incoming:
|
|
return source->getOrientation()->getDirectionVector(source->getOrigin());
|
|
case ConeDirection::Normal: {
|
|
vec3d normal;
|
|
if (!source->getOrientation()->getNormal(&normal)) {
|
|
mprintf(("Effect '%s' tried to use normal direction for source without a normal!\n", m_name.c_str()));
|
|
return source->getOrientation()->getDirectionVector(source->getOrigin());
|
|
}
|
|
|
|
return normal;
|
|
}
|
|
case ConeDirection::Reflected: {
|
|
vec3d out = source->getOrientation()->getDirectionVector(source->getOrigin());
|
|
vec3d normal;
|
|
if (!source->getOrientation()->getNormal(&normal)) {
|
|
mprintf(("Effect '%s' tried to use normal direction for source without a normal!\n", m_name.c_str()));
|
|
return out;
|
|
}
|
|
|
|
// Compute reflect vector as R = V - 2*(V dot N)*N where N
|
|
// is the normal and V is the incoming direction
|
|
|
|
auto dot = 2 * vm_vec_dot(&out, &normal);
|
|
|
|
vm_vec_scale(&normal, dot);
|
|
vm_vec_sub(&out, &out, &normal);
|
|
|
|
return out;
|
|
}
|
|
case ConeDirection::Reverse: {
|
|
vec3d out = source->getOrientation()->getDirectionVector(source->getOrigin());
|
|
vm_vec_scale(&out, -1.0f);
|
|
return out;
|
|
}
|
|
default:
|
|
Error(LOCATION, "Unhandled direction value!");
|
|
return vmd_zero_vector;
|
|
}
|
|
}
|
|
|
|
public:
|
|
explicit GenericShapeEffect(const SCP_string& name) : ParticleEffect(name) {
|
|
}
|
|
|
|
bool processSource(ParticleSource* source) override {
|
|
if (!m_timing.continueProcessing(source)) {
|
|
return false;
|
|
}
|
|
|
|
// This uses the internal features of the timing class for determining if and how many effects should be
|
|
// triggered this frame
|
|
util::EffectTiming::TimingState time_state;
|
|
while (m_timing.shouldCreateEffect(source, time_state)) {
|
|
auto num = m_particleNum.next();
|
|
|
|
if (source->getOrigin()->getType() == SourceOriginType::BEAM) {
|
|
// beam particle numbers are per km
|
|
object* b_obj = source->getOrigin()->getObjectHost();
|
|
float dist = vm_vec_dist(&Beams[b_obj->instance].last_start, &Beams[b_obj->instance].last_shot) / 1000.0f;
|
|
float km;
|
|
float remainder = modff(dist, &km);
|
|
uint old_num = num;
|
|
num = (uint)(old_num * km); // multiply by the number of kilometers
|
|
num += (uint)(old_num * remainder); // try to add any remainders if we have more than 1 per kilometer
|
|
// if we still have nothing let's give it one last shot
|
|
if (num < 1 && frand() < remainder * old_num)
|
|
num += 1;
|
|
}
|
|
|
|
vec3d dir = getNewDirection(source);
|
|
matrix dirMatrix;
|
|
vm_vector_2_matrix(&dirMatrix, &dir, nullptr, nullptr);
|
|
for (uint i = 0; i < num; ++i) {
|
|
if (m_particleChance < 1.0f) {
|
|
auto roll = m_particleRoll.next();
|
|
if (roll <= 0.0f)
|
|
continue;
|
|
}
|
|
|
|
matrix velRotation = m_shape.getDisplacementMatrix();
|
|
|
|
matrix rotatedVel;
|
|
vm_matrix_x_matrix(&rotatedVel, &dirMatrix, &velRotation);
|
|
|
|
particle_info info;
|
|
|
|
source->getOrigin()->applyToParticleInfo(info);
|
|
|
|
vec3d velocity = rotatedVel.vec.fvec;
|
|
if (TShape::scale_velocity_deviation()) {
|
|
// Scale the vector with a random velocity sample and also multiply that with cos(angle between
|
|
// info.vel and sourceDir) That should produce good looking directions where the maximum velocity is
|
|
// only achieved when the particle travels directly on the normal/reflect vector
|
|
vm_vec_scale(&velocity, vm_vec_dot(&velocity, &dir));
|
|
}
|
|
vm_vec_scale(&velocity, m_velocity.next());
|
|
|
|
info.vel *= m_vel_inherit.next();
|
|
info.vel += velocity;
|
|
|
|
if (m_particleTrail.isValid()) {
|
|
auto part = m_particleProperties.createPersistentParticle(info);
|
|
|
|
// There are some possibilities where we can get a null pointer back. Those are very rare but we
|
|
// still shouldn't crash in those circumstances.
|
|
if (!part.expired()) {
|
|
auto trailSource = ParticleManager::get()->createSource(m_particleTrail);
|
|
trailSource.moveToParticle(part);
|
|
|
|
trailSource.finish();
|
|
}
|
|
} else {
|
|
// We don't have a trail so we don't need a persistent particle
|
|
m_particleProperties.createParticle(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void parseValues(bool nocreate) override {
|
|
m_particleProperties.parse(nocreate);
|
|
|
|
m_shape.parse(nocreate);
|
|
|
|
if (internal::required_string_if_new("+Velocity:", nocreate)) {
|
|
m_velocity = ::util::parseUniformRange<float>();
|
|
}
|
|
|
|
if (internal::required_string_if_new("+Number:", nocreate)) {
|
|
m_particleNum = ::util::parseUniformRange<uint>();
|
|
}
|
|
if (!nocreate) {
|
|
m_particleChance = 1.0f;
|
|
}
|
|
if (optional_string("+Chance:")) {
|
|
float chance;
|
|
stuff_float(&chance);
|
|
if (chance <= 0.0f) {
|
|
Warning(LOCATION,
|
|
"Particle %s tried to set +Chance: %f\nChances below 0 would result in no particles.",
|
|
m_name.c_str(), chance);
|
|
} else if (chance > 1.0f) {
|
|
Warning(LOCATION,
|
|
"Particle %s tried to set +Chance: %f\nChances above 1 are ignored, please use +Number: (min,max) "
|
|
"to spawn multiple particles.", m_name.c_str(), chance);
|
|
chance = 1.0f;
|
|
}
|
|
m_particleChance = chance;
|
|
}
|
|
m_particleRoll = ::util::UniformFloatRange(m_particleChance - 1.0f, m_particleChance);
|
|
|
|
if (optional_string("+Direction:")) {
|
|
SCP_string dirStr;
|
|
stuff_string(dirStr, F_NAME);
|
|
|
|
if (!stricmp(dirStr.c_str(), "Incoming")) {
|
|
m_direction = ConeDirection::Incoming;
|
|
}
|
|
else if (!stricmp(dirStr.c_str(), "Normal")) {
|
|
m_direction = ConeDirection::Normal;
|
|
}
|
|
else if (!stricmp(dirStr.c_str(), "Reflected")) {
|
|
m_direction = ConeDirection::Reflected;
|
|
}
|
|
else if (!stricmp(dirStr.c_str(), "Reverse")) {
|
|
m_direction = ConeDirection::Reverse;
|
|
}
|
|
else {
|
|
error_display(0, "Unknown direction name '%s'!", dirStr.c_str());
|
|
}
|
|
}
|
|
|
|
bool saw_deprecated_effect_location = false;
|
|
if (optional_string("+Trail effect:")) {
|
|
// This is the deprecated location since this introduces ambiguities in the parsing process
|
|
m_particleTrail = internal::parseEffectElement();
|
|
saw_deprecated_effect_location = true;
|
|
}
|
|
|
|
if (optional_string("+Parent Velocity Factor:")) {
|
|
m_vel_inherit = ::util::parseUniformRange<float>();
|
|
}
|
|
|
|
m_timing = util::EffectTiming::parseTiming();
|
|
|
|
if (optional_string("+Trail effect:")) {
|
|
// This is the new and correct location. This might create duplicate effects but the warning should be clear
|
|
// enough to avoid that
|
|
if (saw_deprecated_effect_location) {
|
|
error_display(0, "Found two trail effect options! Specifying '+Trail effect:' before '+Duration:' is "
|
|
"deprecated since that can cause issues with conflicting effect options.");
|
|
}
|
|
m_particleTrail = internal::parseEffectElement();
|
|
}
|
|
}
|
|
|
|
void initializeSource(ParticleSource& source) override {
|
|
m_timing.applyToSource(&source);
|
|
}
|
|
|
|
EffectType getType() const override { return m_shape.getType(); }
|
|
|
|
void pageIn() override {
|
|
m_particleProperties.pageIn();
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif // GENERIC_SHAPE_EFFECT_H
|