Skip to content

Commit 1e7431a

Browse files
authored
Give Particles Access to Their Spawner Effect (scp-fs2open#7034)
* Make particles aware of the effect that spawned them * Allow modular curves to call global functions from submember inputs * Add more in and outputs for particle lifetime curves * Allow post-curve velocity as a curve input * fix warning and incorrect assig * Disable CheckTriviallyCopyableMove * Clang Tidy
1 parent b0166dd commit 1e7431a

File tree

10 files changed

+165
-209
lines changed

10 files changed

+165
-209
lines changed

.clang-tidy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,7 @@ CheckOptions:
4848
value: 'std::vector;std::deque;std::list;SCP_vector;SCP_deque;SCP_list'
4949
- key: 'readability-braces-around-statements.ShortStatementLines'
5050
value: '4' # Avoid flagging simple if (...) return false; statements
51+
- key: 'performance-move-const-arg.CheckTriviallyCopyableMove' # This isn't actually performance relevant, but using move on trivially copyable types can well indicate that the variable should not be used anymore, such as when passing a builder type to the finialization function
52+
value: 'false'
5153
...
5254

code/particle/ParticleEffect.cpp

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ ParticleEffect::ParticleEffect(SCP_string name)
4848
m_manual_offset (std::nullopt),
4949
m_manual_velocity_offset(std::nullopt),
5050
m_particleTrail(ParticleEffectHandle::invalid()),
51-
m_size_lifetime_curve(-1),
52-
m_vel_lifetime_curve (-1),
5351
m_particleChance(1.f),
5452
m_distanceCulled(-1.f)
5553
{}
@@ -119,8 +117,6 @@ ParticleEffect::ParticleEffect(SCP_string name,
119117
m_manual_offset(offsetLocal),
120118
m_manual_velocity_offset(velocityOffsetLocal),
121119
m_particleTrail(particleTrail),
122-
m_size_lifetime_curve(-1),
123-
m_vel_lifetime_curve (-1),
124120
m_particleChance(particleChance),
125121
m_distanceCulled(distanceCulled) {}
126122

@@ -268,11 +264,11 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
268264
for (uint i = 0; i < num_spawn; ++i) {
269265
float particleFraction = static_cast<float>(i) / static_cast<float>(num_spawn);
270266

271-
particle_info info;
267+
particle info;
272268

273269
info.reverse = m_reverseAnimation;
274270
info.pos = pos;
275-
info.vel = velParent;
271+
info.velocity = velParent;
276272

277273
if (m_parent_local) {
278274
info.attached_objnum = parent;
@@ -284,9 +280,9 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
284280
}
285281

286282
if (m_vel_inherit_absolute)
287-
vm_vec_normalize_safe(&info.vel, true);
283+
vm_vec_normalize_safe(&info.velocity, true);
288284

289-
info.vel *= (m_ignore_velocity_inherit_if_has_parent && parent >= 0) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier;
285+
info.velocity *= (m_ignore_velocity_inherit_if_has_parent && parent >= 0) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier;
290286

291287
vec3d localVelocity = velNoise;
292288
vec3d localPos = posNoise;
@@ -325,32 +321,43 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
325321
m_velocity_directional_scaling == VelocityScaling::DOT ? dot : 1.f / std::max(0.001f, dot));
326322
}
327323

328-
info.vel += localVelocity;
329-
info.pos += localPos + info.vel * (interp * f2fl(Frametime));
324+
info.velocity += localVelocity;
325+
info.pos += localPos + info.velocity * (interp * f2fl(Frametime));
330326

331327
info.bitmap = m_bitmap_list[m_bitmap_range.next()];
332328

333329
if (m_parentScale)
334330
// if we were spawned by a particle, parentRadius is the parent's radius and m_radius is a factor of that
335-
info.rad = parentRadius * m_radius.next() * radiusMultiplier;
331+
info.radius = parentRadius * m_radius.next() * radiusMultiplier;
336332
else
337-
info.rad = m_radius.next() * radiusMultiplier;
333+
info.radius = m_radius.next() * radiusMultiplier;
338334

339335
info.length = m_length.next() * lengthMultiplier;
340-
if (m_hasLifetime) {
341-
if (m_parentLifetime)
342-
// if we were spawned by a particle, parentLifetime is the parent's remaining lifetime and m_lifetime is a factor of that
343-
info.lifetime = parentLifetime * m_lifetime.next() * lifetimeMultiplier;
344-
else
345-
info.lifetime = m_lifetime.next() * lifetimeMultiplier;
346336

347-
info.lifetime_from_animation = m_keep_anim_length_if_available;
337+
int fps = 1;
338+
if (info.nframes < 0) {
339+
Assertion(bm_is_valid(info.bitmap), "Invalid bitmap handle passed to particle create.");
340+
bm_get_info(info.bitmap, nullptr, nullptr, nullptr, &info.nframes, &fps);
341+
}
342+
343+
if (m_hasLifetime) {
344+
if (m_keep_anim_length_if_available && info.nframes > 1) {
345+
// Recalculate max life for ani's
346+
info.max_life = i2fl(info.nframes) / i2fl(fps);
347+
}
348+
else {
349+
if (m_parentLifetime)
350+
// if we were spawned by a particle, parentLifetime is the parent's remaining lifetime and m_lifetime is a factor of that
351+
info.max_life = parentLifetime * m_lifetime.next() * lifetimeMultiplier;
352+
else
353+
info.max_life = m_lifetime.next() * lifetimeMultiplier;
354+
}
348355
}
349356

350-
info.starting_age = interp * f2fl(Frametime);
351-
352-
info.size_lifetime_curve = m_size_lifetime_curve;
353-
info.vel_lifetime_curve = m_vel_lifetime_curve;
357+
info.age = interp * f2fl(Frametime);
358+
info.looping = false;
359+
info.angle = frand_range(0.0f, PI2);
360+
info.parent_effect = m_self;
354361

355362
switch (m_rotation_type) {
356363
case RotationType::DEFAULT:
@@ -367,7 +374,7 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
367374
}
368375

369376
if (m_particleTrail.isValid()) {
370-
auto part = createPersistent(&info);
377+
auto part = createPersistent(std::move(info));
371378

372379
if constexpr (isPersistent)
373380
createdParticles.push_back(part);
@@ -381,12 +388,12 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
381388
}
382389
} else {
383390
if constexpr (isPersistent){
384-
auto part = createPersistent(&info);
391+
auto part = createPersistent(std::move(info));
385392
createdParticles.push_back(part);
386393
}
387394
else {
388395
// We don't have a trail so we don't need a persistent particle
389-
create(&info);
396+
create(std::move(info));
390397
}
391398
}
392399
}

code/particle/ParticleEffect.h

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,25 @@ class ParticleEffect {
8484
NUM_VALUES
8585
};
8686

87+
enum class ParticleLifetimeCurvesOutput : uint8_t {
88+
VELOCITY_MULT,
89+
RADIUS_MULT,
90+
LENGTH_MULT,
91+
ANIM_STATE,
92+
93+
NUM_VALUES
94+
};
95+
8796
private:
8897
friend struct ParticleParse;
89-
98+
friend class ParticleManager;
9099
friend int ::parse_weapon(int, bool, const char*);
91-
92100
friend ParticleEffectHandle scripting::api::getLegacyScriptingParticleEffect(int bitmap, bool reversed);
93101

94102
SCP_string m_name; //!< The name of this effect
95103

104+
ParticleSubeffectHandle m_self;
105+
96106
Duration m_duration;
97107
RotationType m_rotation_type;
98108
ShapeDirection m_direction;
@@ -138,9 +148,6 @@ class ParticleEffect {
138148

139149
ParticleEffectHandle m_particleTrail;
140150

141-
int m_size_lifetime_curve; //This is a curve of the particle, not of the particle effect, as such, it should not be part of the curve set
142-
int m_vel_lifetime_curve; //This is a curve of the particle, not of the particle effect, as such, it should not be part of the curve set
143-
144151
float m_particleChance; //Deprecated. Use particle num random ranges instead.
145152
float m_distanceCulled; //Kinda deprecated. Only used by the oldest of legacy effects.
146153

@@ -251,7 +258,28 @@ class ParticleEffect {
251258
ModularCurvesMathOperators::division>{}}
252259
);
253260

261+
constexpr static auto modular_curves_lifetime_definition = make_modular_curve_definition<particle, ParticleLifetimeCurvesOutput>(
262+
std::array {
263+
std::pair {"Radius", ParticleLifetimeCurvesOutput::RADIUS_MULT},
264+
std::pair {"Velocity", ParticleLifetimeCurvesOutput::VELOCITY_MULT},
265+
std::pair {"Length", ParticleLifetimeCurvesOutput::LENGTH_MULT},
266+
std::pair {"Anim State", ParticleLifetimeCurvesOutput::ANIM_STATE},
267+
},
268+
//Should you ever need to access something from the effect as a modular curve input:
269+
//std::pair {"", modular_curves_submember_input<&particle::parent_effect, &ParticleSubeffectHandle::getParticleEffect, &ParticleEffect::>{}}
270+
std::pair {"Age", modular_curves_submember_input<&particle::age>{}},
271+
std::pair {"Lifetime", modular_curves_math_input<
272+
modular_curves_submember_input<&particle::age>,
273+
modular_curves_submember_input<&particle::max_life>,
274+
ModularCurvesMathOperators::division>{}},
275+
std::pair {"Radius", modular_curves_submember_input<&particle::radius>{}},
276+
std::pair {"Velocity", modular_curves_submember_input<&particle::velocity, &vm_vec_mag_quick>{}})
277+
.derive_modular_curves_input_only_subset<float>(
278+
std::pair {"Post-Curves Velocity", modular_curves_self_input{}}
279+
);
280+
254281
MODULAR_CURVE_SET(m_modular_curves, modular_curves_definition);
282+
MODULAR_CURVE_SET(m_lifetime_curves, modular_curves_lifetime_definition);
255283

256284
private:
257285
float getCurrentFrequencyMult(decltype(modular_curves_definition)::input_type_t source) const;

code/particle/ParticleManager.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,13 @@ ParticleEffectHandle ParticleManager::addEffect(SCP_vector<ParticleEffect>&& eff
135135
}
136136
#endif
137137

138-
m_effects.emplace_back(std::move(effect));
138+
auto& effect_after_emplace = m_effects.emplace_back(std::move(effect));
139139

140-
return ParticleEffectHandle(static_cast<ParticleEffectHandle::impl_type>(m_effects.size() - 1));
140+
auto handle = ParticleEffectHandle(static_cast<ParticleEffectHandle::impl_type>(m_effects.size() - 1));
141+
for (size_t i = 0; i < effect_after_emplace.size(); i++)
142+
effect_after_emplace[i].m_self = ParticleSubeffectHandle{handle, i};
143+
144+
return handle;
141145
}
142146

143147
void ParticleManager::pageIn() {

code/particle/ParticleParse.cpp

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -283,20 +283,7 @@ namespace particle {
283283
}
284284

285285
static void parseModularCurvesLifetime(ParticleEffect& effect) {
286-
//TODO The following loop behaves as a true subset of how parsing will work once the particle modular curve set is implemented.
287-
//As such, once that's added the loop can be replaced with a modular_curve_set.parse without worry about breaking tables.
288-
while (optional_string("$Particle Lifetime Curve:")) {
289-
required_string("+Input: Lifetime");
290-
291-
required_string("+Output:");
292-
int output = required_string_one_of(2, "Radius", "Velocity");
293-
//The required string part enforces this to be either 0 or 1
294-
required_string(output == 0 ? "Radius" : "Velocity");
295-
int& curve = output == 0 ? effect.m_size_lifetime_curve : effect.m_vel_lifetime_curve;
296-
297-
required_string_either("+Curve Name:", "+Curve:", true);
298-
curve = curve_parse(" Unknown curve requested for modular curves!");
299-
}
286+
effect.m_lifetime_curves.parse("$Particle Lifetime Curve:");
300287
}
301288

302289
static void parseModularCurvesSource(ParticleEffect& effect) {
@@ -368,13 +355,13 @@ namespace particle {
368355

369356
static void parseSizeLifetimeCurve(ParticleEffect &effect) {
370357
if (optional_string("+Size over lifetime curve:")) {
371-
effect.m_size_lifetime_curve = curve_parse("");
358+
effect.m_lifetime_curves.add_curve("Lifetime", ParticleEffect::ParticleLifetimeCurvesOutput::RADIUS_MULT, modular_curves_entry{curve_parse("")});
372359
}
373360
}
374361

375362
static void parseVelocityLifetimeCurve(ParticleEffect &effect) {
376363
if (optional_string("+Velocity scalar over lifetime curve:")) {
377-
effect.m_vel_lifetime_curve = curve_parse("");
364+
effect.m_lifetime_curves.add_curve("Lifetime", ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, modular_curves_entry{curve_parse("")});
378365
}
379366
}
380367

code/particle/ParticleSource.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ struct weapon_info;
1919
enum class WeaponState: uint32_t;
2020

2121
namespace particle {
22-
23-
class ParticleEffect;
24-
struct particle_effect_tag {
25-
};
26-
using ParticleEffectHandle = ::util::ID<particle_effect_tag, ptrdiff_t, -1>;
2722

2823
/**
2924
* @brief The orientation of a particle source

code/particle/hosts/EffectHostParticle.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "freespace.h"
99

10+
#include "particle/ParticleEffect.h"
11+
1012
EffectHostParticle::EffectHostParticle(particle::WeakParticlePtr particle, matrix orientationOverride, bool orientationOverrideRelative) :
1113
EffectHost(orientationOverride, orientationOverrideRelative), m_particle(std::move(particle)) {}
1214

@@ -16,10 +18,7 @@ std::pair<vec3d, matrix> EffectHostParticle::getPositionAndOrientation(bool /*re
1618

1719
vec3d pos;
1820
if (interp != 0.0f) {
19-
float vel_scalar = 1.0f;
20-
if (particle->vel_lifetime_curve >= 0) {
21-
vel_scalar = Curves[particle->vel_lifetime_curve].GetValue(particle->age / particle->max_life);
22-
}
21+
float vel_scalar = particle->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, std::forward_as_tuple(*particle, vm_vec_mag_quick(&particle->velocity)));
2322
vec3d pos_last = particle->pos - (particle->velocity * vel_scalar * flFrametime);
2423
vm_vec_linear_interpolate(&pos, &particle->pos, &pos_last, interp);
2524
} else {
@@ -52,11 +51,11 @@ float EffectHostParticle::getLifetime() const {
5251

5352
float EffectHostParticle::getScale() const {
5453
const auto& particle = m_particle.lock();
55-
int idx = particle->size_lifetime_curve;
56-
if (idx >= 0)
57-
return particle->radius * Curves[idx].GetValue(particle->age / particle->max_life);
58-
else
59-
return particle->radius;
54+
//For anything apart from the velocity curve, "Post-Curves Velocity" is well defined. This is needed to facilitate complex but common particle scaling and appearance curves.
55+
const auto& curve_input = std::forward_as_tuple(*particle,
56+
vm_vec_mag_quick(&particle->velocity) * particle->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::ANIM_STATE, std::forward_as_tuple(*particle, vm_vec_mag_quick(&particle->velocity))));
57+
58+
return particle->radius * particle->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::RADIUS_MULT, curve_input);
6059
}
6160

6261
bool EffectHostParticle::isValid() const {

0 commit comments

Comments
 (0)