From fb2f7915f01d3cbb469f3a2cbe9b8bb0c0eaeb3f Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:38:27 +0900 Subject: [PATCH 01/17] Adapt particle at runtime --- code/globalincs/utility.h | 6 ++ code/particle/EffectHost.h | 11 +++ code/particle/particle.cpp | 148 +++++++++++++++++++++---------------- code/particle/particle.h | 15 ++-- 4 files changed, 112 insertions(+), 68 deletions(-) diff --git a/code/globalincs/utility.h b/code/globalincs/utility.h index b5128b3d59f..ca541f176f4 100644 --- a/code/globalincs/utility.h +++ b/code/globalincs/utility.h @@ -517,4 +517,10 @@ const T* coalesce(const T* possibly_null, const T* value_if_null) return (possibly_null != nullptr) ? possibly_null : value_if_null; } +template +struct overloads : Ts... { + constexpr overloads(Ts... t) : Ts(t)... {} + using Ts::operator()...; +}; + #endif diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 9bdea7296d8..af46e4e8f6f 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -6,6 +6,17 @@ #include +namespace effects { + struct attachment_object { + int objnum = -1; + int sig = -1; + }; + struct attachment_particle { + particle::WeakParticlePtr particle = particle::WeakParticlePtr(); + }; +} +using EffectAttachment = std::variant; + class EffectHost { protected: diff --git a/code/particle/particle.cpp b/code/particle/particle.cpp index 7b1744bcf99..2541b00089c 100644 --- a/code/particle/particle.cpp +++ b/code/particle/particle.cpp @@ -11,6 +11,8 @@ #include "bmpman/bmpman.h" #include "particle/particle.h" + +#include "freespace.h" #include "particle/ParticleManager.h" #include "particle/ParticleEffect.h" #include "debugconsole/console.h" @@ -147,17 +149,82 @@ namespace particle DCF_BOOL2(particles, Particles_enabled, "Turns particles on/off", "Usage: particles [bool]\nTurns particle system on/off. If nothing passed, then toggles it.\n"); + static inline vec3d particle_get_global_pos(const particle& particle) { + return std::visit( overloads { + [&particle](const std::monostate&) { + return particle.pos; + }, + [&particle](const effects::attachment_object& obj) { + vec3d pos; + vm_vec_unrotate(&pos, &particle.pos, &Objects[obj.objnum].orient); + pos += Objects[obj.objnum].pos; + return pos; + }, + [&particle](const effects::attachment_particle& parent_part) { + return particle.pos + particle_get_global_pos(*parent_part.particle.lock()); + }, + }, particle.attachment); + } + + static inline vec3d particle_get_global_velocity(const particle& particle) { + return std::visit( overloads { + [&particle](const std::monostate&) { + return particle.pos; + }, + [&particle](const effects::attachment_object& obj) { + vec3d vel; + vm_vec_unrotate(&vel, &particle.velocity, &Objects[obj.objnum].orient); + return vel; + }, + [&particle](const effects::attachment_particle& parent_part) { + return particle.velocity + particle_get_global_velocity(*parent_part.particle.lock()); + }, + }, particle.attachment); + } + + static inline vec3d particle_get_global_last_pos(const particle& particle, const vec3d& last_pos) { + return std::visit( overloads { + [&last_pos](const std::monostate&) { + return last_pos; + }, + [&last_pos](const effects::attachment_object& obj) { + vec3d pos = last_pos; + if (obj.objnum >= 0) { + vm_vec_unrotate(&pos, &pos, &Objects[obj.objnum].last_orient); + pos += Objects[obj.objnum].last_pos; + } + return pos; + }, + [&last_pos](const effects::attachment_particle& parent_part) { + const auto& parent_part_lock = parent_part.particle.lock(); + //Not perfect, but should be sufficient + return last_pos + particle_get_global_pos(*parent_part_lock) - particle_get_global_velocity(*parent_part_lock) * flFrametime; + }, + }, particle.attachment); + } + + static inline bool particle_is_parent_dead(const particle& particle) { + return std::visit( overloads { + [](const std::monostate&) { + return false; + }, + [](const effects::attachment_object& obj) { + return obj.objnum < 0 || obj.objnum >= MAX_OBJECTS || Objects[obj.objnum].signature != obj.sig; + }, + [](const effects::attachment_particle& parent_part) { + return parent_part.particle.expired(); + }, + }, particle.attachment); + } + static bool maybe_cull_particle(const particle& new_particle) { if (!Particles_enabled) { return true; } - vec3d world_pos = new_particle.pos; - if (new_particle.attached_objnum >= 0) { - vm_vec_unrotate(&world_pos, &world_pos, &Objects[new_particle.attached_objnum].orient); - world_pos += Objects[new_particle.attached_objnum].pos; - } + vec3d world_pos = particle_get_global_pos(new_particle); + // treat particles on lower detail levels as 'further away' for the purposes of culling float adjusted_dist = vm_vec_dist(&Eye_position, &world_pos) * powf(2.5f, (float)(static_cast(DefaultDetailPreset::Num_detail_presets) - Detail.num_particles)); // treat bigger particles as 'closer' @@ -195,12 +262,7 @@ namespace particle } float getPixelSize(const particle& subject_particle) { - vec3d world_pos = subject_particle.pos; - - if (subject_particle.attached_objnum >= 0) { - vm_vec_unrotate(&world_pos, &world_pos, &Objects[subject_particle.attached_objnum].orient); - world_pos += Objects[subject_particle.attached_objnum].pos; - } + vec3d world_pos = particle_get_global_pos(subject_particle); float distance_to_eye = vm_vec_dist(&Eye_position, &world_pos); @@ -240,14 +302,9 @@ namespace particle } // if the particle is attached to an object which has become invalid, kill it - if (part->attached_objnum >= 0) + if (particle_is_parent_dead(*part)) { - // if the signature has changed, or it's bogus, kill it - if ((part->attached_objnum >= MAX_OBJECTS) || - (part->attached_sig != Objects[part->attached_objnum].signature)) - { - remove_particle = true; - } + remove_particle = true; } if (remove_particle) @@ -269,16 +326,7 @@ namespace particle if (Detail.lighting > 3 && source_effect.m_light_source) { const auto& light_source = *source_effect.m_light_source; - vec3d p_pos; - if (part->attached_objnum >= 0) - { - vm_vec_unrotate(&p_pos, &part->pos, &Objects[part->attached_objnum].orient); - vm_vec_add2(&p_pos, &Objects[part->attached_objnum].pos); - } - else - { - p_pos = part->pos; - } + vec3d p_pos = particle_get_global_pos(*part); float light_radius = light_source.light_radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_RADIUS_MULT, curve_input); float source_radius = light_source.source_radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_SOURCE_RADIUS_MULT, curve_input); @@ -297,26 +345,14 @@ namespace particle light_add_point(&p_pos, light_radius, light_radius, intensity, r, g, b, source_radius); break; case ParticleEffect::LightInformation::LightSourceMode::TO_LAST_POS: { - vec3d p_prev_pos; - if (part->attached_objnum >= 0) - { - vm_vec_unrotate(&p_prev_pos, &prev_pos, &Objects[part->attached_objnum].last_orient); - vm_vec_add2(&p_prev_pos, &Objects[part->attached_objnum].last_pos); - } - else - { - p_prev_pos = prev_pos; - } + vec3d p_prev_pos = particle_get_global_last_pos(*part, prev_pos); light_add_tube(&p_prev_pos, &p_pos, light_radius, light_radius, intensity, r, g, b, source_radius); } break; case ParticleEffect::LightInformation::LightSourceMode::AS_PARTICLE: if (part->length != 0.0f) { - vec3d p1; - vm_vec_copy_normalize_safe(&p1, &part->velocity); - if (part->attached_objnum >= 0) { - vm_vec_unrotate(&p1, &p1, &Objects[part->attached_objnum].orient); - } + vec3d p1 = particle_get_global_velocity(*part); + vm_vec_normalize_safe(&p1); p1 *= part->length * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LENGTH_MULT, curve_input); p1 += p_pos; light_add_tube(&p_pos, &p1, light_radius, light_radius, intensity, r, g, b, source_radius); @@ -328,11 +364,8 @@ namespace particle case ParticleEffect::LightInformation::LightSourceMode::CONE: { float cone_angle = light_source.cone_angle * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_CONE_ANGLE_MULT, curve_input); float cone_inner_angle = light_source.cone_inner_angle * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_CONE_INNER_ANGLE_MULT, curve_input); - vec3d p1; - vm_vec_copy_normalize_safe(&p1, &part->velocity); - if (part->attached_objnum >= 0) { - vm_vec_unrotate(&p1, &p1, &Objects[part->attached_objnum].orient); - } + vec3d p1 = particle_get_global_velocity(*part); + vm_vec_normalize_safe(&p1); light_add_cone(&p_pos, &p1, cone_angle, cone_inner_angle, false, light_radius, light_radius, intensity, r, g, b, source_radius); } @@ -411,16 +444,7 @@ namespace particle static bool render_particle(particle* part) { // skip back-facing particles (ripped from fullneb code) // Wanderer - add support for attached particles - vec3d p_pos; - if (part->attached_objnum >= 0) - { - vm_vec_unrotate(&p_pos, &part->pos, &Objects[part->attached_objnum].orient); - vm_vec_add2(&p_pos, &Objects[part->attached_objnum].pos); - } - else - { - p_pos = part->pos; - } + vec3d p_pos = particle_get_global_pos(*part); bool part_has_length = part->length != 0.0f; @@ -434,14 +458,12 @@ namespace particle //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. const auto& curve_input = std::forward_as_tuple(*part, vm_vec_mag_quick(&part->velocity) * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, std::forward_as_tuple(*part, vm_vec_mag_quick(&part->velocity)))); - + vec3d p1 = vmd_x_vector; if (part_has_length) { - vm_vec_copy_normalize_safe(&p1, &part->velocity); - if (part->attached_objnum >= 0) { - vm_vec_unrotate(&p1, &p1, &Objects[part->attached_objnum].orient); - } + p1 = particle_get_global_velocity(*part); + vm_vec_normalize_safe(&p1); p1 *= part->length * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LENGTH_MULT, curve_input); p1 += p_pos; diff --git a/code/particle/particle.h b/code/particle/particle.h index f96107f2940..5b880452689 100644 --- a/code/particle/particle.h +++ b/code/particle/particle.h @@ -16,6 +16,9 @@ #include "object/object.h" #include +#include + +#include "EffectHost.h" extern bool Randomize_particle_rotation; @@ -69,6 +72,11 @@ namespace particle extern int Anim_bitmap_id_smoke2; extern int Anim_num_frames_smoke2; + struct particle; + + typedef std::weak_ptr WeakParticlePtr; + typedef std::shared_ptr ParticlePtr; + typedef struct particle { // old style data vec3d pos; // position @@ -81,8 +89,8 @@ namespace particle int nframes; // If an ani, how many frames? // new style data - int attached_objnum; // if this is set, pos is relative to the attached object. velocity is ignored - int attached_sig; // to check for dead/nonexistent objects + EffectAttachment attachment; + bool reverse; // play any animations in reverse float length; // the length of the particle for laser-style rendering float angle; @@ -91,9 +99,6 @@ namespace particle ParticleSubeffectHandle parent_effect; } particle; - typedef std::weak_ptr WeakParticlePtr; - typedef std::shared_ptr ParticlePtr; - /** * @brief Creates a non-persistent particle * From 4b6aa067ac4612760531f700e251bbe426675892 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:50:32 +0900 Subject: [PATCH 02/17] Refactor EffectHosts --- code/globalincs/utility.h | 10 ++ code/particle/EffectHost.cpp | 115 +++++++++++++++++++ code/particle/EffectHost.h | 26 ++++- code/particle/ParticleEffect.cpp | 40 +++---- code/particle/ParticleEffect.h | 64 +++++------ code/particle/ParticleSource.cpp | 4 +- code/particle/hosts/EffectHostObject.cpp | 4 +- code/particle/hosts/EffectHostObject.h | 2 +- code/particle/hosts/EffectHostParticle.cpp | 28 ++--- code/particle/hosts/EffectHostParticle.h | 3 +- code/particle/hosts/EffectHostSubmodel.cpp | 4 +- code/particle/hosts/EffectHostSubmodel.h | 2 +- code/particle/hosts/EffectHostTurret.cpp | 4 +- code/particle/hosts/EffectHostTurret.h | 2 +- code/particle/particle.cpp | 85 ++------------ code/particle/volumes/ModelSurfaceVolume.cpp | 5 +- code/scripting/api/libs/graphics.cpp | 6 +- code/scripting/api/objs/particle.cpp | 7 +- code/source_groups.cmake | 1 + 19 files changed, 246 insertions(+), 166 deletions(-) create mode 100644 code/particle/EffectHost.cpp diff --git a/code/globalincs/utility.h b/code/globalincs/utility.h index ca541f176f4..628cda40c55 100644 --- a/code/globalincs/utility.h +++ b/code/globalincs/utility.h @@ -3,6 +3,8 @@ #define _FSO_UTILITY_H #include +#include +#include #include "globalincs/globals.h" #include "globalincs/toolchain.h" @@ -523,4 +525,12 @@ struct overloads : Ts... { using Ts::operator()...; }; +template +inline std::optional variant_get_optional(const std::variant& input) { + if (std::holds_alternative(input)) + return std::get(input); + else + return std::nullopt; +} + #endif diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp new file mode 100644 index 00000000000..88a9fa84cdd --- /dev/null +++ b/code/particle/EffectHost.cpp @@ -0,0 +1,115 @@ +#include "particle/EffectHost.h" + +#include "freespace.h" +#include "globalincs/utility.h" +#include "particle/particle.h" + +namespace effects { +vec3d attachment_local_pos_to_global(const EffectAttachment& attachment, const vec3d& local_pos, float interp) { + return std::visit(overloads{ + [&local_pos](const std::monostate&) { + return local_pos; + }, + [&local_pos, interp](const effects::attachment_object& obj) { + if (obj.objnum < 0) + return local_pos; + + vec3d pos; + vm_vec_unrotate(&pos, &local_pos, &Objects[obj.objnum].orient); + + vec3d parent_pos; + vm_vec_linear_interpolate(&parent_pos, &Objects[obj.objnum].pos, &Objects[obj.objnum].last_pos, interp); + pos += parent_pos; + + return pos; + }, + [&local_pos, interp](const effects::attachment_particle& parent_part) { + const auto& parent = parent_part.particle.lock(); + if (!parent) + return local_pos; + return local_pos + attachment_local_pos_to_global(parent->attachment, parent->pos, interp) + - attachment_local_vel_to_global(parent->attachment, parent->velocity) * interp * flFrametime; + }, + }, attachment); +} + +vec3d attachment_local_vel_to_global(const EffectAttachment& attachment, const vec3d& local_vel) { + return std::visit(overloads{ + [&local_vel](const std::monostate&) { + return local_vel; + }, + [&local_vel](const effects::attachment_object& obj) { + if (obj.objnum < 0) + return local_vel; + + vec3d vel; + vm_vec_unrotate(&vel, &local_vel, &Objects[obj.objnum].orient); + return vel; + }, + [&local_vel](const effects::attachment_particle& parent_part) { + const auto& parent = parent_part.particle.lock(); + if (!parent) + return local_vel; + return local_vel + attachment_local_vel_to_global(parent->attachment, parent->velocity); + }, + }, attachment); +} + +vec3d attachment_local_last_pos_to_global(const EffectAttachment& attachment, const vec3d& last_pos) { + return std::visit(overloads{ + [&last_pos](const std::monostate&) { + return last_pos; + }, + [&last_pos](const effects::attachment_object& obj) { + vec3d pos = last_pos; + if (obj.objnum >= 0) { + vm_vec_unrotate(&pos, &pos, &Objects[obj.objnum].last_orient); + pos += Objects[obj.objnum].last_pos; + } + return pos; + }, + [&last_pos](const effects::attachment_particle& parent_part) { + const auto& parent = parent_part.particle.lock(); + if (!parent) + return last_pos; + return last_pos + attachment_local_pos_to_global(parent->attachment, parent->pos, 0.0f) + - attachment_local_vel_to_global(parent->attachment, parent->velocity) * flFrametime; + }, + }, attachment); +} + +bool is_attachment_valid(const EffectAttachment& attachment) { + return std::visit(overloads{ + [](const std::monostate&) { + return true; + }, + [](const effects::attachment_object& obj) { + return obj.objnum >= 0 && obj.objnum < MAX_OBJECTS && Objects[obj.objnum].signature == obj.sig; + }, + [](const effects::attachment_particle& parent_part) { + return !parent_part.particle.expired(); + }, + }, attachment); +} + +std::pair get_attachment_frame(const EffectAttachment& attachment, float interp) { + return std::visit(overloads{ + [](const std::monostate&) -> std::pair { + return {ZERO_VECTOR, vmd_identity_matrix}; + }, + [interp](const effects::attachment_object& obj) -> std::pair { + if (obj.objnum < 0) + return {ZERO_VECTOR, vmd_identity_matrix}; + vec3d pos; + vm_vec_linear_interpolate(&pos, &Objects[obj.objnum].pos, &Objects[obj.objnum].last_pos, interp); + return {pos, Objects[obj.objnum].orient}; + }, + [interp](const effects::attachment_particle& parent_part) -> std::pair { + const auto& parent = parent_part.particle.lock(); + if (!parent) + return {ZERO_VECTOR, vmd_identity_matrix}; + return get_attachment_frame(parent->attachment, interp); + }, + }, attachment); +} +} diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index af46e4e8f6f..56df7cc2b56 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -2,10 +2,16 @@ #include "object/object.h" #include "globalincs/pstypes.h" +#include "globalincs/utility.h" #include "math/vecmat.h" #include +namespace particle { + struct particle; + typedef std::weak_ptr WeakParticlePtr; +} + namespace effects { struct attachment_object { int objnum = -1; @@ -17,6 +23,24 @@ namespace effects { } using EffectAttachment = std::variant; +namespace effects { + +vec3d attachment_local_pos_to_global(const EffectAttachment& attachment, const vec3d& local_pos, float interp = 0.0f); + +vec3d attachment_local_vel_to_global(const EffectAttachment& attachment, const vec3d& local_vel); + +vec3d attachment_local_last_pos_to_global(const EffectAttachment& attachment, const vec3d& last_pos); + +bool is_attachment_valid(const EffectAttachment& attachment); + +std::pair get_attachment_frame(const EffectAttachment& attachment, float interp = 0.0f); + +inline std::optional extract_attachment_object(const EffectAttachment& input) { + return variant_get_optional(input); +} + +} + class EffectHost { protected: @@ -37,7 +61,7 @@ class EffectHost { return vm_vec_mag_quick(&velocity); } - virtual std::pair getParentObjAndSig() const { return {-1, -1}; } + virtual EffectAttachment getParentAttachment() const { return {}; } virtual int getParentSubmodel() const { return -1; } virtual float getLifetime() const { return -1.f; } diff --git a/code/particle/ParticleEffect.cpp b/code/particle/ParticleEffect.cpp index 7a69bd42e59..040f6a13b89 100644 --- a/code/particle/ParticleEffect.cpp +++ b/code/particle/ParticleEffect.cpp @@ -185,23 +185,25 @@ void ParticleEffect::sampleNoise(vec3d& noiseTarget, const matrix* orientation, vm_vec_unrotate(&noiseTarget, &noiseSampleLocal, orientation); } -vec3d ParticleEffect::adaptPosition(const vec3d& pos, int parent) const { - if (parent < 0 || !m_local_position_scaling.has_value()) { +vec3d ParticleEffect::adaptPosition(const vec3d& pos, const EffectAttachment& attachment) const { + if (std::holds_alternative(attachment) || !m_local_position_scaling.has_value()) { return pos; } + auto [parent_pos, parent_orient] = effects::get_attachment_frame(attachment); + vec3d pos_local = pos; if (!m_parent_local) { - pos_local -= Objects[parent].pos; - vm_vec_rotate(&pos_local, &pos_local, &Objects[parent].orient); + pos_local -= parent_pos; + vm_vec_rotate(&pos_local, &pos_local, &parent_orient); } pos_local *= m_local_position_scaling->next(); if (!m_parent_local) { - vm_vec_unrotate(&pos_local, &pos_local, &Objects[parent].orient); - vm_vec_add2(&pos_local, &Objects[parent].pos); + vm_vec_unrotate(&pos_local, &pos_local, &parent_orient); + vm_vec_add2(&pos_local, &parent_pos); } return pos_local; @@ -213,7 +215,7 @@ vec3d ParticleEffect::adaptPosition(const vec3d& pos, int parent) const { * * */ template -auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const { +auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { using persistentParticlesList = std::conditional_t, bool>; persistentParticlesList createdParticles; @@ -236,12 +238,11 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s } const auto& [pos_hit, hostOrientation] = source.m_host->getPositionAndOrientation(m_parent_local, interp, m_manual_offset); - const vec3d& pos = adaptPosition(pos_hit, parent); + const vec3d& pos = adaptPosition(pos_hit, attachment); vec3d posGlobal = pos; - if (m_parent_local && parent >= 0) { - vm_vec_unrotate(&posGlobal, &posGlobal, &Objects[parent].orient); - vm_vec_add2(&posGlobal, &Objects[parent].pos); + if (m_parent_local) { + posGlobal = effects::attachment_local_pos_to_global(attachment, posGlobal, interp); } auto modularCurvesInput = std::forward_as_tuple(source, effectNumber, posGlobal); @@ -296,18 +297,13 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s info.velocity = velParent; if (m_parent_local) { - info.attached_objnum = parent; - info.attached_sig = parent_sig; - } - else { - info.attached_objnum = -1; - info.attached_sig = -1; + info.attachment = attachment; } if (m_vel_inherit_absolute) vm_vec_normalize_safe(&info.velocity, true); - info.velocity *= (m_ignore_velocity_inherit_if_has_parent && parent >= 0) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier; + info.velocity *= (m_ignore_velocity_inherit_if_has_parent && !std::holds_alternative(attachment)) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier; vec3d localVelocity = velNoise; vec3d localPos = posNoise; @@ -425,12 +421,12 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s return getCurrentFrequencyMult(modularCurvesInput); } -float ParticleEffect::processSource(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const { - return processSourceInternal(interp, source, effectNumber, velParent, parent, parent_sig, parentLifetime, parentRadius, particle_percent); +float ParticleEffect::processSource(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { + return processSourceInternal(interp, source, effectNumber, velParent, attachment, parentLifetime, parentRadius, particle_percent); } -SCP_vector ParticleEffect::processSourcePersistent(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const { - return processSourceInternal(interp, source, effectNumber, velParent, parent, parent_sig, parentLifetime, parentRadius, particle_percent); +SCP_vector ParticleEffect::processSourcePersistent(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { + return processSourceInternal(interp, source, effectNumber, velParent, attachment, parentLifetime, parentRadius, particle_percent); } void ParticleEffect::pageIn() { diff --git a/code/particle/ParticleEffect.h b/code/particle/ParticleEffect.h index 35708d813fd..56c0833cf0b 100644 --- a/code/particle/ParticleEffect.h +++ b/code/particle/ParticleEffect.h @@ -187,10 +187,10 @@ class ParticleEffect { float m_distanceCulled; //Kinda deprecated. Only used by the oldest of legacy effects. matrix getNewDirection(const matrix& hostOrientation, const std::optional& normal) const; - vec3d adaptPosition(const vec3d& pos, int parent) const; + vec3d adaptPosition(const vec3d& pos, const EffectAttachment& attachment) const; template - auto processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const; + auto processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; public: /** * @brief Initializes the base ParticleEffect @@ -232,8 +232,8 @@ class ParticleEffect { int bitmap ); - float processSource(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const; - SCP_vector processSourcePersistent(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const; + float processSource(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; + SCP_vector processSourcePersistent(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; void pageIn(); @@ -285,24 +285,24 @@ class ParticleEffect { modular_curves_global_submember_input, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Object Hitpoints", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &object::hull_strength>{}}, + std::pair {"Host Object Hitpoints", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>{}}, std::pair {"Host Ship Hitpoints Fraction", modular_curves_math_input< - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &object::hull_strength>, - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Object Shield", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &shield_get_strength>{}}, + std::pair {"Host Object Shield", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>{}}, std::pair {"Host Ship Shield Fraction", modular_curves_math_input< - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &shield_get_strength>, - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship AB Fuel Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, - std::pair {"Host Ship Countermeasures Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, - std::pair {"Host Ship Weapon Energy Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, - std::pair {"Host Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship EMP Intensity", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, - std::pair {"Host Ship Time Until Explosion", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) + std::pair {"Host Ship AB Fuel Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, + std::pair {"Host Ship Countermeasures Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, + std::pair {"Host Ship Weapon Energy Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, + std::pair {"Host Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Host Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Host Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Host Ship EMP Intensity", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, + std::pair {"Host Ship Time Until Explosion", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) .derive_modular_curves_input_only_subset( //Effect Number std::pair {"Spawntime Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingTime>{}}, std::pair {"Life Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingLife>{}}, @@ -345,24 +345,24 @@ class ParticleEffect { modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, std::pair {"Velocity", modular_curves_submember_input<&particle::velocity, &vm_vec_mag_quick>{}}, - std::pair {"Parent Object Hitpoints", modular_curves_submember_input<&particle::attached_objnum, &Objects, &object::hull_strength>{}}, + std::pair {"Parent Object Hitpoints", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>{}}, std::pair {"Parent Ship Hitpoints Fraction", modular_curves_math_input< - modular_curves_submember_input<&particle::attached_objnum, &Objects, &object::hull_strength>, - modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, + modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>, + modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Object Shield", modular_curves_submember_input<&particle::attached_objnum, &Objects, &shield_get_strength>{}}, + std::pair {"Parent Object Shield", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>{}}, std::pair {"Parent Ship Shield Fraction", modular_curves_math_input< - modular_curves_submember_input<&particle::attached_objnum, &Objects, &shield_get_strength>, - modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, + modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>, + modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship AB Fuel Left", modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, - std::pair {"Parent Ship Countermeasures Left", modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, - std::pair {"Parent Ship Weapon Energy Left", modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, - std::pair {"Parent Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship EMP Intensity", modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, - std::pair {"Parent Ship Time Until Explosion", modular_curves_submember_input<&particle::attached_objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) + std::pair {"Parent Ship AB Fuel Left", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, + std::pair {"Parent Ship Countermeasures Left", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, + std::pair {"Parent Ship Weapon Energy Left", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, + std::pair {"Parent Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Parent Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Parent Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Parent Ship EMP Intensity", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, + std::pair {"Parent Ship Time Until Explosion", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) .derive_modular_curves_input_only_subset( std::pair {"Post-Curves Velocity", modular_curves_self_input{}} ); diff --git a/code/particle/ParticleSource.cpp b/code/particle/ParticleSource.cpp index 559d8feb14d..d68219a7549 100644 --- a/code/particle/ParticleSource.cpp +++ b/code/particle/ParticleSource.cpp @@ -55,7 +55,7 @@ bool ParticleSource::process() { const auto& effectList = getEffect(); const vec3d& vel = m_host->getVelocity(); - const auto& [parent, parent_sig] = m_host->getParentObjAndSig(); + const auto& attachment = m_host->getParentAttachment(); float parent_radius = m_host->getScale(); float parent_lifetime = m_host->getLifetime(); float particleMultiplier = m_host->getParticleMultiplier(); @@ -72,7 +72,7 @@ bool ParticleSource::process() { float interp = static_cast(timestamp_since(timing.m_nextCreation)) / (f2fl(Frametime) * 1000.0f); // Some of these - float freqMult = effect.processSource(interp, *this, i, vel, parent, parent_sig, parent_lifetime, parent_radius, particleMultiplier); + float freqMult = effect.processSource(interp, *this, i, vel, attachment, parent_lifetime, parent_radius, particleMultiplier); // we need to clamp this to 1 because a spawn delay lower than it takes to spawn the particle in ms means we try to spawn infinite particles auto time_diff_ms = std::max(fl2i(effect.getNextSpawnDelay() / freqMult * MILLISECONDS_PER_SECOND), 1); diff --git a/code/particle/hosts/EffectHostObject.cpp b/code/particle/hosts/EffectHostObject.cpp index d6496ed5680..5a0501e281d 100644 --- a/code/particle/hosts/EffectHostObject.cpp +++ b/code/particle/hosts/EffectHostObject.cpp @@ -51,8 +51,8 @@ vec3d EffectHostObject::getVelocity() const { return Objects[m_objnum].phys_info.vel; } -std::pair EffectHostObject::getParentObjAndSig() const { - return { m_objnum, m_objsig }; +EffectAttachment EffectHostObject::getParentAttachment() const { + return effects::attachment_object{m_objnum, m_objsig}; } float EffectHostObject::getHostRadius() const { diff --git a/code/particle/hosts/EffectHostObject.h b/code/particle/hosts/EffectHostObject.h index a3f1ff29620..0635426e505 100644 --- a/code/particle/hosts/EffectHostObject.h +++ b/code/particle/hosts/EffectHostObject.h @@ -17,7 +17,7 @@ class EffectHostObject : public EffectHost { vec3d getVelocity() const override; - std::pair getParentObjAndSig() const override; + EffectAttachment getParentAttachment() const override; float getHostRadius() const override; diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 5fc3a26b067..7d5bd43027a 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -16,7 +16,9 @@ EffectHostParticle::EffectHostParticle(particle::WeakParticlePtr particle, matri std::pair EffectHostParticle::getPositionAndOrientation(bool relativeToParent, float interp, const std::optional& tabled_offset) const { const auto& particle = m_particle.lock(); - relativeToParent &= particle->attached_objnum >= 0; + auto* obj = std::get_if(&particle->attachment); + bool has_obj_parent = obj && obj->objnum >= 0; + relativeToParent &= has_obj_parent; vec3d pos; if (interp != 0.0f) { @@ -27,14 +29,7 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela pos = particle->pos; } - //We might need to convert the position to global space if the parent particle has a parent - if (particle->attached_objnum >= 0) { - vec3d global_pos; - vm_vec_linear_interpolate(&global_pos, &Objects[particle->attached_objnum].pos, &Objects[particle->attached_objnum].last_pos, interp); - - vm_vec_unrotate(&pos, &pos, &Objects[particle->attached_objnum].orient); - pos += global_pos; - } + pos = effects::attachment_local_pos_to_global(particle->attachment, pos, interp); // find the particle direction (normalized vector) // note: this can't be computed for particles with 0 velocity, so use the safe version @@ -51,13 +46,15 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix_norm(&orientation, &particle_dir) : m_orientationOverride; } else { + auto [parent_pos, parent_orient] = effects::get_attachment_frame(particle->attachment, interp); + //The position data here is in world space //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. matrix global_orient_transpose; - orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_copy_transpose(&global_orient_transpose, &Objects[particle->attached_objnum].orient); + orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_copy_transpose(&global_orient_transpose, &parent_orient); - vm_vec_sub2(&pos, &Objects[particle->attached_objnum].pos); - vm_vec_rotate(&pos, &pos, &Objects[particle->attached_objnum].orient); + vm_vec_sub2(&pos, &parent_pos); + vm_vec_rotate(&pos, &pos, &parent_orient); } return { pos, orientation }; @@ -67,9 +64,8 @@ vec3d EffectHostParticle::getVelocity() const { return m_particle.lock()->velocity; } -std::pair EffectHostParticle::getParentObjAndSig() const { - const auto& particle = m_particle.lock(); - return {particle->attached_objnum, particle->attached_sig}; +EffectAttachment EffectHostParticle::getParentAttachment() const { + return effects::attachment_particle{m_particle}; } float EffectHostParticle::getLifetime() const { @@ -88,4 +84,4 @@ float EffectHostParticle::getScale() const { bool EffectHostParticle::isValid() const { return !m_particle.expired(); -} \ No newline at end of file +} diff --git a/code/particle/hosts/EffectHostParticle.h b/code/particle/hosts/EffectHostParticle.h index 1ee74cfb3f7..8e2b45e232c 100644 --- a/code/particle/hosts/EffectHostParticle.h +++ b/code/particle/hosts/EffectHostParticle.h @@ -14,8 +14,7 @@ class EffectHostParticle : public EffectHost { vec3d getVelocity() const override; - //Particles can inherit parent particle's parents, but cannot actually be parented to another particle - std::pair getParentObjAndSig() const override; + EffectAttachment getParentAttachment() const override; float getLifetime() const override; diff --git a/code/particle/hosts/EffectHostSubmodel.cpp b/code/particle/hosts/EffectHostSubmodel.cpp index 39e332f6b3c..056b525851c 100644 --- a/code/particle/hosts/EffectHostSubmodel.cpp +++ b/code/particle/hosts/EffectHostSubmodel.cpp @@ -56,8 +56,8 @@ vec3d EffectHostSubmodel::getVelocity() const { return Objects[m_objnum].phys_info.vel; } -std::pair EffectHostSubmodel::getParentObjAndSig() const { - return { m_objnum, m_objsig }; +EffectAttachment EffectHostSubmodel::getParentAttachment() const { + return effects::attachment_object{m_objnum, m_objsig}; } int EffectHostSubmodel::getParentSubmodel() const { diff --git a/code/particle/hosts/EffectHostSubmodel.h b/code/particle/hosts/EffectHostSubmodel.h index ec7c1be67ef..5c7f373c613 100644 --- a/code/particle/hosts/EffectHostSubmodel.h +++ b/code/particle/hosts/EffectHostSubmodel.h @@ -15,7 +15,7 @@ class EffectHostSubmodel : public EffectHost { vec3d getVelocity() const override; - std::pair getParentObjAndSig() const override; + EffectAttachment getParentAttachment() const override; int getParentSubmodel() const override; float getHostRadius() const override; diff --git a/code/particle/hosts/EffectHostTurret.cpp b/code/particle/hosts/EffectHostTurret.cpp index 747e4dbf519..70fcd7680b3 100644 --- a/code/particle/hosts/EffectHostTurret.cpp +++ b/code/particle/hosts/EffectHostTurret.cpp @@ -70,8 +70,8 @@ vec3d EffectHostTurret::getVelocity() const { return Objects[m_objnum].phys_info.vel; } -std::pair EffectHostTurret::getParentObjAndSig() const { - return { m_objnum, m_objsig }; +EffectAttachment EffectHostTurret::getParentAttachment() const { + return effects::attachment_object{m_objnum, m_objsig}; } int EffectHostTurret::getParentSubmodel() const { diff --git a/code/particle/hosts/EffectHostTurret.h b/code/particle/hosts/EffectHostTurret.h index 112486199db..a647c659036 100644 --- a/code/particle/hosts/EffectHostTurret.h +++ b/code/particle/hosts/EffectHostTurret.h @@ -14,7 +14,7 @@ class EffectHostTurret : public EffectHost { vec3d getVelocity() const override; - std::pair getParentObjAndSig() const override; + EffectAttachment getParentAttachment() const override; int getParentSubmodel() const override; float getHostRadius() const override; diff --git a/code/particle/particle.cpp b/code/particle/particle.cpp index 2541b00089c..ec9bcb2b5d0 100644 --- a/code/particle/particle.cpp +++ b/code/particle/particle.cpp @@ -149,73 +149,6 @@ namespace particle DCF_BOOL2(particles, Particles_enabled, "Turns particles on/off", "Usage: particles [bool]\nTurns particle system on/off. If nothing passed, then toggles it.\n"); - static inline vec3d particle_get_global_pos(const particle& particle) { - return std::visit( overloads { - [&particle](const std::monostate&) { - return particle.pos; - }, - [&particle](const effects::attachment_object& obj) { - vec3d pos; - vm_vec_unrotate(&pos, &particle.pos, &Objects[obj.objnum].orient); - pos += Objects[obj.objnum].pos; - return pos; - }, - [&particle](const effects::attachment_particle& parent_part) { - return particle.pos + particle_get_global_pos(*parent_part.particle.lock()); - }, - }, particle.attachment); - } - - static inline vec3d particle_get_global_velocity(const particle& particle) { - return std::visit( overloads { - [&particle](const std::monostate&) { - return particle.pos; - }, - [&particle](const effects::attachment_object& obj) { - vec3d vel; - vm_vec_unrotate(&vel, &particle.velocity, &Objects[obj.objnum].orient); - return vel; - }, - [&particle](const effects::attachment_particle& parent_part) { - return particle.velocity + particle_get_global_velocity(*parent_part.particle.lock()); - }, - }, particle.attachment); - } - - static inline vec3d particle_get_global_last_pos(const particle& particle, const vec3d& last_pos) { - return std::visit( overloads { - [&last_pos](const std::monostate&) { - return last_pos; - }, - [&last_pos](const effects::attachment_object& obj) { - vec3d pos = last_pos; - if (obj.objnum >= 0) { - vm_vec_unrotate(&pos, &pos, &Objects[obj.objnum].last_orient); - pos += Objects[obj.objnum].last_pos; - } - return pos; - }, - [&last_pos](const effects::attachment_particle& parent_part) { - const auto& parent_part_lock = parent_part.particle.lock(); - //Not perfect, but should be sufficient - return last_pos + particle_get_global_pos(*parent_part_lock) - particle_get_global_velocity(*parent_part_lock) * flFrametime; - }, - }, particle.attachment); - } - - static inline bool particle_is_parent_dead(const particle& particle) { - return std::visit( overloads { - [](const std::monostate&) { - return false; - }, - [](const effects::attachment_object& obj) { - return obj.objnum < 0 || obj.objnum >= MAX_OBJECTS || Objects[obj.objnum].signature != obj.sig; - }, - [](const effects::attachment_particle& parent_part) { - return parent_part.particle.expired(); - }, - }, particle.attachment); - } static bool maybe_cull_particle(const particle& new_particle) { if (!Particles_enabled) @@ -223,7 +156,7 @@ namespace particle return true; } - vec3d world_pos = particle_get_global_pos(new_particle); + vec3d world_pos = effects::attachment_local_pos_to_global(new_particle.attachment, new_particle.pos); // treat particles on lower detail levels as 'further away' for the purposes of culling float adjusted_dist = vm_vec_dist(&Eye_position, &world_pos) * powf(2.5f, (float)(static_cast(DefaultDetailPreset::Num_detail_presets) - Detail.num_particles)); @@ -262,7 +195,7 @@ namespace particle } float getPixelSize(const particle& subject_particle) { - vec3d world_pos = particle_get_global_pos(subject_particle); + vec3d world_pos = effects::attachment_local_pos_to_global(subject_particle.attachment, subject_particle.pos); float distance_to_eye = vm_vec_dist(&Eye_position, &world_pos); @@ -302,7 +235,7 @@ namespace particle } // if the particle is attached to an object which has become invalid, kill it - if (particle_is_parent_dead(*part)) + if (!effects::is_attachment_valid(part->attachment)) { remove_particle = true; } @@ -326,7 +259,7 @@ namespace particle if (Detail.lighting > 3 && source_effect.m_light_source) { const auto& light_source = *source_effect.m_light_source; - vec3d p_pos = particle_get_global_pos(*part); + vec3d p_pos = effects::attachment_local_pos_to_global(part->attachment, part->pos); float light_radius = light_source.light_radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_RADIUS_MULT, curve_input); float source_radius = light_source.source_radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_SOURCE_RADIUS_MULT, curve_input); @@ -345,13 +278,13 @@ namespace particle light_add_point(&p_pos, light_radius, light_radius, intensity, r, g, b, source_radius); break; case ParticleEffect::LightInformation::LightSourceMode::TO_LAST_POS: { - vec3d p_prev_pos = particle_get_global_last_pos(*part, prev_pos); + vec3d p_prev_pos = effects::attachment_local_last_pos_to_global(part->attachment, prev_pos); light_add_tube(&p_prev_pos, &p_pos, light_radius, light_radius, intensity, r, g, b, source_radius); } break; case ParticleEffect::LightInformation::LightSourceMode::AS_PARTICLE: if (part->length != 0.0f) { - vec3d p1 = particle_get_global_velocity(*part); + vec3d p1 = effects::attachment_local_vel_to_global(part->attachment, part->velocity); vm_vec_normalize_safe(&p1); p1 *= part->length * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LENGTH_MULT, curve_input); p1 += p_pos; @@ -364,7 +297,7 @@ namespace particle case ParticleEffect::LightInformation::LightSourceMode::CONE: { float cone_angle = light_source.cone_angle * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_CONE_ANGLE_MULT, curve_input); float cone_inner_angle = light_source.cone_inner_angle * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_CONE_INNER_ANGLE_MULT, curve_input); - vec3d p1 = particle_get_global_velocity(*part); + vec3d p1 = effects::attachment_local_vel_to_global(part->attachment, part->velocity); vm_vec_normalize_safe(&p1); light_add_cone(&p_pos, &p1, cone_angle, cone_inner_angle, false, light_radius, light_radius, intensity, r, g, b, source_radius); @@ -444,7 +377,7 @@ namespace particle static bool render_particle(particle* part) { // skip back-facing particles (ripped from fullneb code) // Wanderer - add support for attached particles - vec3d p_pos = particle_get_global_pos(*part); + vec3d p_pos = effects::attachment_local_pos_to_global(part->attachment, part->pos); bool part_has_length = part->length != 0.0f; @@ -462,7 +395,7 @@ namespace particle vec3d p1 = vmd_x_vector; if (part_has_length) { - p1 = particle_get_global_velocity(*part); + p1 = effects::attachment_local_vel_to_global(part->attachment, part->velocity); vm_vec_normalize_safe(&p1); p1 *= part->length * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LENGTH_MULT, curve_input); p1 += p_pos; diff --git a/code/particle/volumes/ModelSurfaceVolume.cpp b/code/particle/volumes/ModelSurfaceVolume.cpp index 55fcbc35ea2..4e61a37e3c5 100644 --- a/code/particle/volumes/ModelSurfaceVolume.cpp +++ b/code/particle/volumes/ModelSurfaceVolume.cpp @@ -8,7 +8,10 @@ ModelSurfaceVolume::ModelSurfaceVolume() : m_modelScale(::util::UniformFloatRang }; vec3d ModelSurfaceVolume::sampleRandomPoint(const matrix &orientation, decltype(ParticleEffect::modular_curves_definition)::input_type_t source, float particlesFraction, const EffectHost& host) { - int obj_num = host.getParentObjAndSig().first; + auto attachment = host.getParentAttachment(); + int obj_num = -1; + if (auto* obj = std::get_if(&attachment)) + obj_num = obj->objnum; int submodel = host.getParentSubmodel(); vec3d point = ZERO_VECTOR; diff --git a/code/scripting/api/libs/graphics.cpp b/code/scripting/api/libs/graphics.cpp index 3222a5624c7..7870e60dbe2 100644 --- a/code/scripting/api/libs/graphics.cpp +++ b/code/scripting/api/libs/graphics.cpp @@ -2321,7 +2321,7 @@ static int spawnParticles(lua_State *L, bool persistent) { // 2. we NEED the return particle ptrs for the persistent path // 3. Scripting gets to set certain values at runtime which are usually encoded as a behaviour in the particle effect and thus tabled statically. - const auto& [parent, parent_sig] = host->getParentObjAndSig(); + auto attachment = host->getParentAttachment(); particle::ParticleSource source; source.setEffect(handle); @@ -2333,7 +2333,7 @@ static int spawnParticles(lua_State *L, bool persistent) { auto spawned_particles = particle::ParticleManager::get() ->getEffect(handle) .front() - .processSourcePersistent(0, source, 0, vel, parent, parent_sig, lifetime, rad, 1); + .processSourcePersistent(0, source, 0, vel, attachment, lifetime, rad, 1); Assertion(spawned_particles.size() == 1, "Did not spawn a single particle in createPersistentParticle"); @@ -2345,7 +2345,7 @@ static int spawnParticles(lua_State *L, bool persistent) { return persistent ? ADE_RETURN_NIL : ADE_RETURN_FALSE; } else { - particle::ParticleManager::get()->getEffect(handle).front().processSource(0, source, 0, vel, parent, parent_sig, lifetime, rad, 1); + particle::ParticleManager::get()->getEffect(handle).front().processSource(0, source, 0, vel, attachment, lifetime, rad, 1); return persistent ? ADE_RETURN_NIL : ADE_RETURN_FALSE; } } diff --git a/code/scripting/api/objs/particle.cpp b/code/scripting/api/objs/particle.cpp index a6dffc926a1..47f34e1d09b 100644 --- a/code/scripting/api/objs/particle.cpp +++ b/code/scripting/api/objs/particle.cpp @@ -205,10 +205,13 @@ ADE_VIRTVAR(AttachedObject, l_Particle, "object", "The object this particle is a if (ADE_SETTING_VAR) { if (newObj != nullptr && newObj->isValid()) - ph->Get().lock()->attached_objnum = newObj->sig; + ph->Get().lock()->attachment = effects::attachment_object{newObj->objnum, newObj->sig}; } - return ade_set_object_with_breed(L, ph->Get().lock()->attached_objnum); + if (auto* obj = std::get_if(&ph->Get().lock()->attachment)) + return ade_set_object_with_breed(L, obj->objnum); + else + return ade_set_object_with_breed(L, -1); } ADE_FUNC(isValid, l_Particle, NULL, "Detects whether this handle is valid", "boolean", "true if valid false if not") diff --git a/code/source_groups.cmake b/code/source_groups.cmake index d1483a75dbc..f98d989f82e 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1125,6 +1125,7 @@ add_file_folder("Parse\\\\SEXP" # Particle files add_file_folder("Particle" particle/EffectHost.h + particle/EffectHost.cpp particle/particle.cpp particle/particle.h particle/ParticleEffect.cpp From 87feffd1431794f982c16734ad84ac46da20f2ba Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:51:21 +0900 Subject: [PATCH 03/17] fix issues --- code/particle/EffectHost.cpp | 101 ++++++++++++++++----- code/particle/EffectHost.h | 28 ++---- code/particle/ParticleEffect.cpp | 14 +-- code/particle/ParticleEffect.h | 66 +++++++------- code/particle/ParticleParse.cpp | 3 + code/particle/hosts/EffectHostObject.cpp | 2 +- code/particle/hosts/EffectHostObject.h | 2 +- code/particle/hosts/EffectHostParticle.cpp | 6 +- code/particle/hosts/EffectHostParticle.h | 2 +- code/particle/hosts/EffectHostSubmodel.cpp | 2 +- code/particle/hosts/EffectHostSubmodel.h | 2 +- code/particle/hosts/EffectHostTurret.cpp | 2 +- code/particle/hosts/EffectHostTurret.h | 2 +- code/particle/particle.cpp | 18 ++-- code/particle/particle.h | 2 +- 15 files changed, 151 insertions(+), 101 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 88a9fa84cdd..347d4a259c8 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -1,16 +1,17 @@ #include "particle/EffectHost.h" #include "freespace.h" +#include "ParticleEffect.h" #include "globalincs/utility.h" #include "particle/particle.h" namespace effects { -vec3d attachment_local_pos_to_global(const EffectAttachment& attachment, const vec3d& local_pos, float interp) { - return std::visit(overloads{ +vec3d EffectAttachment::local_pos_to_global(const vec3d& local_pos, float interp) const { + return std::visit(overloads { [&local_pos](const std::monostate&) { return local_pos; }, - [&local_pos, interp](const effects::attachment_object& obj) { + [&local_pos, interp](const attachment_object& obj) { if (obj.objnum < 0) return local_pos; @@ -23,22 +24,29 @@ vec3d attachment_local_pos_to_global(const EffectAttachment& attachment, const v return pos; }, - [&local_pos, interp](const effects::attachment_particle& parent_part) { + [&local_pos, interp, this](const attachment_particle& parent_part) { const auto& parent = parent_part.particle.lock(); if (!parent) return local_pos; - return local_pos + attachment_local_pos_to_global(parent->attachment, parent->pos, interp) - - attachment_local_vel_to_global(parent->attachment, parent->velocity) * interp * flFrametime; + if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) + return parent->attachment.local_pos_to_global(local_pos, interp); + else { + const auto& [parent_pos, parent_orient] = get_frame(interp); + vec3d pos; + vm_vec_unrotate(&pos, &local_pos, &parent_orient); + + return pos + parent_pos; + } }, - }, attachment); + }, *this); } -vec3d attachment_local_vel_to_global(const EffectAttachment& attachment, const vec3d& local_vel) { - return std::visit(overloads{ +vec3d EffectAttachment::local_vel_to_global(const vec3d& local_vel) const { + return std::visit(overloads { [&local_vel](const std::monostate&) { return local_vel; }, - [&local_vel](const effects::attachment_object& obj) { + [&local_vel](const attachment_object& obj) { if (obj.objnum < 0) return local_vel; @@ -46,21 +54,29 @@ vec3d attachment_local_vel_to_global(const EffectAttachment& attachment, const v vm_vec_unrotate(&vel, &local_vel, &Objects[obj.objnum].orient); return vel; }, - [&local_vel](const effects::attachment_particle& parent_part) { + [&local_vel, this](const attachment_particle& parent_part) { const auto& parent = parent_part.particle.lock(); if (!parent) return local_vel; - return local_vel + attachment_local_vel_to_global(parent->attachment, parent->velocity); + if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) + return parent->attachment.local_vel_to_global(local_vel); + else { + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d vel; + vm_vec_unrotate(&vel, &local_vel, &parent_orient); + + return vel + parent->attachment.local_vel_to_global(parent->velocity); + } }, - }, attachment); + }, *this); } -vec3d attachment_local_last_pos_to_global(const EffectAttachment& attachment, const vec3d& last_pos) { +vec3d EffectAttachment::local_last_pos_to_global(const vec3d& last_pos) const { return std::visit(overloads{ [&last_pos](const std::monostate&) { return last_pos; }, - [&last_pos](const effects::attachment_object& obj) { + [&last_pos](const attachment_object& obj) { vec3d pos = last_pos; if (obj.objnum >= 0) { vm_vec_unrotate(&pos, &pos, &Objects[obj.objnum].last_orient); @@ -68,17 +84,24 @@ vec3d attachment_local_last_pos_to_global(const EffectAttachment& attachment, co } return pos; }, - [&last_pos](const effects::attachment_particle& parent_part) { + [&last_pos, this](const attachment_particle& parent_part) { const auto& parent = parent_part.particle.lock(); if (!parent) return last_pos; - return last_pos + attachment_local_pos_to_global(parent->attachment, parent->pos, 0.0f) - - attachment_local_vel_to_global(parent->attachment, parent->velocity) * flFrametime; + if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) + return parent->attachment.local_last_pos_to_global(last_pos); + else { + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d pos; + vm_vec_unrotate(&pos, &last_pos, &parent_orient); + + return pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime; + } }, - }, attachment); + }, *this); } -bool is_attachment_valid(const EffectAttachment& attachment) { +bool EffectAttachment::is_valid() const { return std::visit(overloads{ [](const std::monostate&) { return true; @@ -87,12 +110,12 @@ bool is_attachment_valid(const EffectAttachment& attachment) { return obj.objnum >= 0 && obj.objnum < MAX_OBJECTS && Objects[obj.objnum].signature == obj.sig; }, [](const effects::attachment_particle& parent_part) { - return !parent_part.particle.expired(); + return !parent_part.particle.expired() && (parent_part.particle.lock()->parent_effect.getParticleEffect().m_parent_is_transitive && parent_part.particle.lock()->attachment.is_valid()); }, - }, attachment); + }, *this); } -std::pair get_attachment_frame(const EffectAttachment& attachment, float interp) { +std::pair EffectAttachment::get_frame(float interp) const { return std::visit(overloads{ [](const std::monostate&) -> std::pair { return {ZERO_VECTOR, vmd_identity_matrix}; @@ -108,8 +131,36 @@ std::pair get_attachment_frame(const EffectAttachment& attachment const auto& parent = parent_part.particle.lock(); if (!parent) return {ZERO_VECTOR, vmd_identity_matrix}; - return get_attachment_frame(parent->attachment, interp); + if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) + return parent->attachment.get_frame(interp); + else { + auto [parent_pos, orient] = parent->attachment.get_frame(interp); + + vec3d parent_global_orient_local_velocity; + vm_vec_unrotate(&parent_global_orient_local_velocity, &parent->velocity, &orient); + + return {parent_pos + parent->pos - parent_global_orient_local_velocity * flFrametime * interp, orient}; + } + }, + }, *this); +} + +std::optional EffectAttachment::extract_object() const { + return std::visit(overloads{ + [](const std::monostate&) -> std::optional { + return std::nullopt; + }, + [](const attachment_object& obj) -> std::optional { + return obj; + }, + [](const attachment_particle& parent_part) -> std::optional { + const auto& parent = parent_part.particle.lock(); + const auto& effect = parent->parent_effect.getParticleEffect(); + if (effect.m_parent_is_transitive) + return parent->attachment.extract_object(); + else + return std::nullopt; }, - }, attachment); + }, *this); } } diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 56df7cc2b56..2fe055ca835 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -20,25 +20,17 @@ namespace effects { struct attachment_particle { particle::WeakParticlePtr particle = particle::WeakParticlePtr(); }; -} -using EffectAttachment = std::variant; - -namespace effects { - -vec3d attachment_local_pos_to_global(const EffectAttachment& attachment, const vec3d& local_pos, float interp = 0.0f); - -vec3d attachment_local_vel_to_global(const EffectAttachment& attachment, const vec3d& local_vel); -vec3d attachment_local_last_pos_to_global(const EffectAttachment& attachment, const vec3d& last_pos); - -bool is_attachment_valid(const EffectAttachment& attachment); - -std::pair get_attachment_frame(const EffectAttachment& attachment, float interp = 0.0f); - -inline std::optional extract_attachment_object(const EffectAttachment& input) { - return variant_get_optional(input); -} + struct EffectAttachment : public std::variant { + using std::variant::variant; + vec3d local_pos_to_global(const vec3d& local_pos, float interp = 0.0f) const; + vec3d local_vel_to_global(const vec3d& local_vel) const; + vec3d local_last_pos_to_global(const vec3d& last_pos) const; + bool is_valid() const; + std::pair get_frame(float interp = 0.0f) const; + std::optional extract_object() const; + }; } class EffectHost { @@ -61,7 +53,7 @@ class EffectHost { return vm_vec_mag_quick(&velocity); } - virtual EffectAttachment getParentAttachment() const { return {}; } + virtual effects::EffectAttachment getParentAttachment() const { return {}; } virtual int getParentSubmodel() const { return -1; } virtual float getLifetime() const { return -1.f; } diff --git a/code/particle/ParticleEffect.cpp b/code/particle/ParticleEffect.cpp index 040f6a13b89..a7916041023 100644 --- a/code/particle/ParticleEffect.cpp +++ b/code/particle/ParticleEffect.cpp @@ -26,6 +26,7 @@ ParticleEffect::ParticleEffect(SCP_string name) m_vel_inherit_from_position_absolute(false), m_reverseAnimation(false), m_ignore_velocity_inherit_if_has_parent(false), + m_parent_is_transitive(true), m_bitmap_list({}), m_bitmap_range(::util::UniformRange(0)), m_delayRange(::util::UniformFloatRange(0.0f)), @@ -96,6 +97,7 @@ ParticleEffect::ParticleEffect(SCP_string name, m_vel_inherit_from_position_absolute(velInheritFromPositionAbsolute), m_reverseAnimation(reverseAnimation), m_ignore_velocity_inherit_if_has_parent(ignoreVelocityInheritIfParented), + m_parent_is_transitive(true), m_bitmap_list({bitmap}), m_bitmap_range(::util::UniformRange(0)), m_delayRange(::util::UniformFloatRange(0.0f)), @@ -185,12 +187,12 @@ void ParticleEffect::sampleNoise(vec3d& noiseTarget, const matrix* orientation, vm_vec_unrotate(&noiseTarget, &noiseSampleLocal, orientation); } -vec3d ParticleEffect::adaptPosition(const vec3d& pos, const EffectAttachment& attachment) const { +vec3d ParticleEffect::adaptPosition(const vec3d& pos, const effects::EffectAttachment& attachment) const { if (std::holds_alternative(attachment) || !m_local_position_scaling.has_value()) { return pos; } - auto [parent_pos, parent_orient] = effects::get_attachment_frame(attachment); + auto [parent_pos, parent_orient] = attachment.get_frame(); vec3d pos_local = pos; @@ -215,7 +217,7 @@ vec3d ParticleEffect::adaptPosition(const vec3d& pos, const EffectAttachment& at * * */ template -auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { +auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { using persistentParticlesList = std::conditional_t, bool>; persistentParticlesList createdParticles; @@ -242,7 +244,7 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s vec3d posGlobal = pos; if (m_parent_local) { - posGlobal = effects::attachment_local_pos_to_global(attachment, posGlobal, interp); + posGlobal = attachment.local_pos_to_global(posGlobal, interp); } auto modularCurvesInput = std::forward_as_tuple(source, effectNumber, posGlobal); @@ -421,11 +423,11 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s return getCurrentFrequencyMult(modularCurvesInput); } -float ParticleEffect::processSource(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { +float ParticleEffect::processSource(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { return processSourceInternal(interp, source, effectNumber, velParent, attachment, parentLifetime, parentRadius, particle_percent); } -SCP_vector ParticleEffect::processSourcePersistent(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { +SCP_vector ParticleEffect::processSourcePersistent(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { return processSourceInternal(interp, source, effectNumber, velParent, attachment, parentLifetime, parentRadius, particle_percent); } diff --git a/code/particle/ParticleEffect.h b/code/particle/ParticleEffect.h index 56c0833cf0b..c549cbf92a7 100644 --- a/code/particle/ParticleEffect.h +++ b/code/particle/ParticleEffect.h @@ -125,6 +125,7 @@ class ParticleEffect { }; private: + friend struct effects::EffectAttachment; friend struct ParticleParse; friend class ParticleManager; friend int ::parse_weapon(int, bool, const char*); @@ -150,6 +151,7 @@ class ParticleEffect { bool m_vel_inherit_from_position_absolute; bool m_reverseAnimation; bool m_ignore_velocity_inherit_if_has_parent; + bool m_parent_is_transitive; SCP_vector m_bitmap_list; ::util::UniformRange m_bitmap_range; @@ -187,10 +189,10 @@ class ParticleEffect { float m_distanceCulled; //Kinda deprecated. Only used by the oldest of legacy effects. matrix getNewDirection(const matrix& hostOrientation, const std::optional& normal) const; - vec3d adaptPosition(const vec3d& pos, const EffectAttachment& attachment) const; + vec3d adaptPosition(const vec3d& pos, const effects::EffectAttachment& attachment) const; template - auto processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; + auto processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, const effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; public: /** * @brief Initializes the base ParticleEffect @@ -232,8 +234,8 @@ class ParticleEffect { int bitmap ); - float processSource(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; - SCP_vector processSourcePersistent(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, const EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; + float processSource(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, const effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; + SCP_vector processSourcePersistent(float interp, const ParticleSource& host, size_t effectNumber, const vec3d& vel, const effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const; void pageIn(); @@ -285,24 +287,24 @@ class ParticleEffect { modular_curves_global_submember_input, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Object Hitpoints", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>{}}, + std::pair {"Host Object Hitpoints", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>{}}, std::pair {"Host Ship Hitpoints Fraction", modular_curves_math_input< - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>, - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Object Shield", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>{}}, + std::pair {"Host Object Shield", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>{}}, std::pair {"Host Ship Shield Fraction", modular_curves_math_input< - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>, - modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>, + modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship AB Fuel Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, - std::pair {"Host Ship Countermeasures Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, - std::pair {"Host Ship Weapon Energy Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, - std::pair {"Host Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Host Ship EMP Intensity", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, - std::pair {"Host Ship Time Until Explosion", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) + std::pair {"Host Ship AB Fuel Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, + std::pair {"Host Ship Countermeasures Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, + std::pair {"Host Ship Weapon Energy Left", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, + std::pair {"Host Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Host Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Host Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Host Ship EMP Intensity", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, + std::pair {"Host Ship Time Until Explosion", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentAttachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) .derive_modular_curves_input_only_subset( //Effect Number std::pair {"Spawntime Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingTime>{}}, std::pair {"Life Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingLife>{}}, @@ -345,24 +347,24 @@ class ParticleEffect { modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, std::pair {"Velocity", modular_curves_submember_input<&particle::velocity, &vm_vec_mag_quick>{}}, - std::pair {"Parent Object Hitpoints", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>{}}, + std::pair {"Parent Object Hitpoints", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>{}}, std::pair {"Parent Ship Hitpoints Fraction", modular_curves_math_input< - modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>, - modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, + modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &object::hull_strength>, + modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_hull_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Object Shield", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>{}}, + std::pair {"Parent Object Shield", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>{}}, std::pair {"Parent Ship Shield Fraction", modular_curves_math_input< - modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>, - modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, + modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &shield_get_strength>, + modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::ship_max_shield_strength>, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship AB Fuel Left", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, - std::pair {"Parent Ship Countermeasures Left", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, - std::pair {"Parent Ship Weapon Energy Left", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, - std::pair {"Parent Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, - std::pair {"Parent Ship EMP Intensity", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, - std::pair {"Parent Ship Time Until Explosion", modular_curves_submember_input<&particle::attachment, &effects::extract_attachment_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) + std::pair {"Parent Ship AB Fuel Left", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::afterburner_fuel>{}}, + std::pair {"Parent Ship Countermeasures Left", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::cmeasure_count>{}}, + std::pair {"Parent Ship Weapon Energy Left", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::weapon_energy>{}}, + std::pair {"Parent Ship ETS Engines", modular_curves_math_input, &ship::engine_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Parent Ship ETS Shields", modular_curves_math_input, &ship::shield_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Parent Ship ETS Weapons", modular_curves_math_input, &ship::weapon_recharge_index>, modular_curves_global_submember_input, ModularCurvesMathOperators::division>{}}, + std::pair {"Parent Ship EMP Intensity", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::emp_intensity>{}}, + std::pair {"Parent Ship Time Until Explosion", modular_curves_submember_input<&particle::attachment, &effects::EffectAttachment::extract_object, &effects::attachment_object::objnum, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) .derive_modular_curves_input_only_subset( std::pair {"Post-Curves Velocity", modular_curves_self_input{}} ); diff --git a/code/particle/ParticleParse.cpp b/code/particle/ParticleParse.cpp index 3a619e30451..875b3b1e3ac 100644 --- a/code/particle/ParticleParse.cpp +++ b/code/particle/ParticleParse.cpp @@ -104,6 +104,9 @@ namespace particle { if (optional_string("+Remain local to parent:")) { stuff_boolean(&effect.m_parent_local); } + if (optional_string("+Transitive parenting:")) { + stuff_boolean(&effect.m_parent_is_transitive); + } } template static void parseParticleNumber(ParticleEffect &effect) { diff --git a/code/particle/hosts/EffectHostObject.cpp b/code/particle/hosts/EffectHostObject.cpp index 5a0501e281d..4b8fac2c739 100644 --- a/code/particle/hosts/EffectHostObject.cpp +++ b/code/particle/hosts/EffectHostObject.cpp @@ -51,7 +51,7 @@ vec3d EffectHostObject::getVelocity() const { return Objects[m_objnum].phys_info.vel; } -EffectAttachment EffectHostObject::getParentAttachment() const { +effects::EffectAttachment EffectHostObject::getParentAttachment() const { return effects::attachment_object{m_objnum, m_objsig}; } diff --git a/code/particle/hosts/EffectHostObject.h b/code/particle/hosts/EffectHostObject.h index 0635426e505..b7c3006f4fe 100644 --- a/code/particle/hosts/EffectHostObject.h +++ b/code/particle/hosts/EffectHostObject.h @@ -17,7 +17,7 @@ class EffectHostObject : public EffectHost { vec3d getVelocity() const override; - EffectAttachment getParentAttachment() const override; + effects::EffectAttachment getParentAttachment() const override; float getHostRadius() const override; diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 7d5bd43027a..14b25fcf922 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -29,7 +29,7 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela pos = particle->pos; } - pos = effects::attachment_local_pos_to_global(particle->attachment, pos, interp); + pos = particle->attachment.local_pos_to_global(pos, interp); // find the particle direction (normalized vector) // note: this can't be computed for particles with 0 velocity, so use the safe version @@ -46,7 +46,7 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix_norm(&orientation, &particle_dir) : m_orientationOverride; } else { - auto [parent_pos, parent_orient] = effects::get_attachment_frame(particle->attachment, interp); + auto [parent_pos, parent_orient] = particle->attachment.get_frame(interp); //The position data here is in world space //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. @@ -64,7 +64,7 @@ vec3d EffectHostParticle::getVelocity() const { return m_particle.lock()->velocity; } -EffectAttachment EffectHostParticle::getParentAttachment() const { +effects::EffectAttachment EffectHostParticle::getParentAttachment() const { return effects::attachment_particle{m_particle}; } diff --git a/code/particle/hosts/EffectHostParticle.h b/code/particle/hosts/EffectHostParticle.h index 8e2b45e232c..f7d210942a0 100644 --- a/code/particle/hosts/EffectHostParticle.h +++ b/code/particle/hosts/EffectHostParticle.h @@ -14,7 +14,7 @@ class EffectHostParticle : public EffectHost { vec3d getVelocity() const override; - EffectAttachment getParentAttachment() const override; + effects::EffectAttachment getParentAttachment() const override; float getLifetime() const override; diff --git a/code/particle/hosts/EffectHostSubmodel.cpp b/code/particle/hosts/EffectHostSubmodel.cpp index 056b525851c..97e7b1aa514 100644 --- a/code/particle/hosts/EffectHostSubmodel.cpp +++ b/code/particle/hosts/EffectHostSubmodel.cpp @@ -56,7 +56,7 @@ vec3d EffectHostSubmodel::getVelocity() const { return Objects[m_objnum].phys_info.vel; } -EffectAttachment EffectHostSubmodel::getParentAttachment() const { +effects::EffectAttachment EffectHostSubmodel::getParentAttachment() const { return effects::attachment_object{m_objnum, m_objsig}; } diff --git a/code/particle/hosts/EffectHostSubmodel.h b/code/particle/hosts/EffectHostSubmodel.h index 5c7f373c613..5b09a505d64 100644 --- a/code/particle/hosts/EffectHostSubmodel.h +++ b/code/particle/hosts/EffectHostSubmodel.h @@ -15,7 +15,7 @@ class EffectHostSubmodel : public EffectHost { vec3d getVelocity() const override; - EffectAttachment getParentAttachment() const override; + effects::EffectAttachment getParentAttachment() const override; int getParentSubmodel() const override; float getHostRadius() const override; diff --git a/code/particle/hosts/EffectHostTurret.cpp b/code/particle/hosts/EffectHostTurret.cpp index 70fcd7680b3..a482c965406 100644 --- a/code/particle/hosts/EffectHostTurret.cpp +++ b/code/particle/hosts/EffectHostTurret.cpp @@ -70,7 +70,7 @@ vec3d EffectHostTurret::getVelocity() const { return Objects[m_objnum].phys_info.vel; } -EffectAttachment EffectHostTurret::getParentAttachment() const { +effects::EffectAttachment EffectHostTurret::getParentAttachment() const { return effects::attachment_object{m_objnum, m_objsig}; } diff --git a/code/particle/hosts/EffectHostTurret.h b/code/particle/hosts/EffectHostTurret.h index a647c659036..c9e323529fc 100644 --- a/code/particle/hosts/EffectHostTurret.h +++ b/code/particle/hosts/EffectHostTurret.h @@ -14,7 +14,7 @@ class EffectHostTurret : public EffectHost { vec3d getVelocity() const override; - EffectAttachment getParentAttachment() const override; + effects::EffectAttachment getParentAttachment() const override; int getParentSubmodel() const override; float getHostRadius() const override; diff --git a/code/particle/particle.cpp b/code/particle/particle.cpp index ec9bcb2b5d0..28882cbd32f 100644 --- a/code/particle/particle.cpp +++ b/code/particle/particle.cpp @@ -156,7 +156,7 @@ namespace particle return true; } - vec3d world_pos = effects::attachment_local_pos_to_global(new_particle.attachment, new_particle.pos); + vec3d world_pos = new_particle.attachment.local_pos_to_global(new_particle.pos); // treat particles on lower detail levels as 'further away' for the purposes of culling float adjusted_dist = vm_vec_dist(&Eye_position, &world_pos) * powf(2.5f, (float)(static_cast(DefaultDetailPreset::Num_detail_presets) - Detail.num_particles)); @@ -195,7 +195,7 @@ namespace particle } float getPixelSize(const particle& subject_particle) { - vec3d world_pos = effects::attachment_local_pos_to_global(subject_particle.attachment, subject_particle.pos); + vec3d world_pos = subject_particle.attachment.local_pos_to_global(subject_particle.pos); float distance_to_eye = vm_vec_dist(&Eye_position, &world_pos); @@ -235,7 +235,7 @@ namespace particle } // if the particle is attached to an object which has become invalid, kill it - if (!effects::is_attachment_valid(part->attachment)) + if (!part->attachment.is_valid()) { remove_particle = true; } @@ -259,7 +259,7 @@ namespace particle if (Detail.lighting > 3 && source_effect.m_light_source) { const auto& light_source = *source_effect.m_light_source; - vec3d p_pos = effects::attachment_local_pos_to_global(part->attachment, part->pos); + vec3d p_pos = part->attachment.local_pos_to_global(part->pos); float light_radius = light_source.light_radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_RADIUS_MULT, curve_input); float source_radius = light_source.source_radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_SOURCE_RADIUS_MULT, curve_input); @@ -278,13 +278,13 @@ namespace particle light_add_point(&p_pos, light_radius, light_radius, intensity, r, g, b, source_radius); break; case ParticleEffect::LightInformation::LightSourceMode::TO_LAST_POS: { - vec3d p_prev_pos = effects::attachment_local_last_pos_to_global(part->attachment, prev_pos); + vec3d p_prev_pos = part->attachment.local_last_pos_to_global(prev_pos); light_add_tube(&p_prev_pos, &p_pos, light_radius, light_radius, intensity, r, g, b, source_radius); } break; case ParticleEffect::LightInformation::LightSourceMode::AS_PARTICLE: if (part->length != 0.0f) { - vec3d p1 = effects::attachment_local_vel_to_global(part->attachment, part->velocity); + vec3d p1 = part->attachment.local_vel_to_global(part->velocity); vm_vec_normalize_safe(&p1); p1 *= part->length * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LENGTH_MULT, curve_input); p1 += p_pos; @@ -297,7 +297,7 @@ namespace particle case ParticleEffect::LightInformation::LightSourceMode::CONE: { float cone_angle = light_source.cone_angle * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_CONE_ANGLE_MULT, curve_input); float cone_inner_angle = light_source.cone_inner_angle * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LIGHT_CONE_INNER_ANGLE_MULT, curve_input); - vec3d p1 = effects::attachment_local_vel_to_global(part->attachment, part->velocity); + vec3d p1 = part->attachment.local_vel_to_global(part->velocity); vm_vec_normalize_safe(&p1); light_add_cone(&p_pos, &p1, cone_angle, cone_inner_angle, false, light_radius, light_radius, intensity, r, g, b, source_radius); @@ -377,7 +377,7 @@ namespace particle static bool render_particle(particle* part) { // skip back-facing particles (ripped from fullneb code) // Wanderer - add support for attached particles - vec3d p_pos = effects::attachment_local_pos_to_global(part->attachment, part->pos); + vec3d p_pos = part->attachment.local_pos_to_global(part->pos); bool part_has_length = part->length != 0.0f; @@ -395,7 +395,7 @@ namespace particle vec3d p1 = vmd_x_vector; if (part_has_length) { - p1 = effects::attachment_local_vel_to_global(part->attachment, part->velocity); + p1 = part->attachment.local_vel_to_global(part->velocity); vm_vec_normalize_safe(&p1); p1 *= part->length * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::LENGTH_MULT, curve_input); p1 += p_pos; diff --git a/code/particle/particle.h b/code/particle/particle.h index 5b880452689..ac91a01bdd0 100644 --- a/code/particle/particle.h +++ b/code/particle/particle.h @@ -89,7 +89,7 @@ namespace particle int nframes; // If an ani, how many frames? // new style data - EffectAttachment attachment; + effects::EffectAttachment attachment; bool reverse; // play any animations in reverse float length; // the length of the particle for laser-style rendering From e9898ade928cf77b3aa1f993baa1a510335698be Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:54:45 +0900 Subject: [PATCH 04/17] cleanup --- code/globalincs/utility.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/code/globalincs/utility.h b/code/globalincs/utility.h index 628cda40c55..2b6f0400774 100644 --- a/code/globalincs/utility.h +++ b/code/globalincs/utility.h @@ -525,12 +525,4 @@ struct overloads : Ts... { using Ts::operator()...; }; -template -inline std::optional variant_get_optional(const std::variant& input) { - if (std::holds_alternative(input)) - return std::get(input); - else - return std::nullopt; -} - #endif From e42785f7e2053e3c99f5e41535f0e181b091e459 Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 09:24:41 +0900 Subject: [PATCH 05/17] Remove live transitive particles --- code/particle/EffectHost.cpp | 84 ++++++++++++---------- code/particle/EffectHost.h | 1 + code/particle/hosts/EffectHostParticle.cpp | 2 +- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 347d4a259c8..9d1d76f92d4 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -26,17 +26,15 @@ vec3d EffectAttachment::local_pos_to_global(const vec3d& local_pos, float interp }, [&local_pos, interp, this](const attachment_particle& parent_part) { const auto& parent = parent_part.particle.lock(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); if (!parent) return local_pos; - if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) - return parent->attachment.local_pos_to_global(local_pos, interp); - else { - const auto& [parent_pos, parent_orient] = get_frame(interp); - vec3d pos; - vm_vec_unrotate(&pos, &local_pos, &parent_orient); - - return pos + parent_pos; - } + + const auto& [parent_pos, parent_orient] = get_frame(interp); + vec3d pos; + vm_vec_unrotate(&pos, &local_pos, &parent_orient); + + return pos + parent_pos; }, }, *this); } @@ -56,17 +54,15 @@ vec3d EffectAttachment::local_vel_to_global(const vec3d& local_vel) const { }, [&local_vel, this](const attachment_particle& parent_part) { const auto& parent = parent_part.particle.lock(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); if (!parent) return local_vel; - if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) - return parent->attachment.local_vel_to_global(local_vel); - else { - const auto& [parent_pos, parent_orient] = get_frame(); - vec3d vel; - vm_vec_unrotate(&vel, &local_vel, &parent_orient); - - return vel + parent->attachment.local_vel_to_global(parent->velocity); - } + + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d vel; + vm_vec_unrotate(&vel, &local_vel, &parent_orient); + + return vel + parent->attachment.local_vel_to_global(parent->velocity); }, }, *this); } @@ -86,17 +82,15 @@ vec3d EffectAttachment::local_last_pos_to_global(const vec3d& last_pos) const { }, [&last_pos, this](const attachment_particle& parent_part) { const auto& parent = parent_part.particle.lock(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); if (!parent) return last_pos; - if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) - return parent->attachment.local_last_pos_to_global(last_pos); - else { - const auto& [parent_pos, parent_orient] = get_frame(); - vec3d pos; - vm_vec_unrotate(&pos, &last_pos, &parent_orient); - - return pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime; - } + + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d pos; + vm_vec_unrotate(&pos, &last_pos, &parent_orient); + + return pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime; }, }, *this); } @@ -110,7 +104,7 @@ bool EffectAttachment::is_valid() const { return obj.objnum >= 0 && obj.objnum < MAX_OBJECTS && Objects[obj.objnum].signature == obj.sig; }, [](const effects::attachment_particle& parent_part) { - return !parent_part.particle.expired() && (parent_part.particle.lock()->parent_effect.getParticleEffect().m_parent_is_transitive && parent_part.particle.lock()->attachment.is_valid()); + return !parent_part.particle.expired(); }, }, *this); } @@ -129,18 +123,16 @@ std::pair EffectAttachment::get_frame(float interp) const { }, [interp](const effects::attachment_particle& parent_part) -> std::pair { const auto& parent = parent_part.particle.lock(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); if (!parent) return {ZERO_VECTOR, vmd_identity_matrix}; - if (parent->parent_effect.getParticleEffect().m_parent_is_transitive) - return parent->attachment.get_frame(interp); - else { - auto [parent_pos, orient] = parent->attachment.get_frame(interp); - vec3d parent_global_orient_local_velocity; - vm_vec_unrotate(&parent_global_orient_local_velocity, &parent->velocity, &orient); + auto [parent_pos, orient] = parent->attachment.get_frame(interp); - return {parent_pos + parent->pos - parent_global_orient_local_velocity * flFrametime * interp, orient}; - } + vec3d parent_global_orient_local_velocity; + vm_vec_unrotate(&parent_global_orient_local_velocity, &parent->velocity, &orient); + + return {parent_pos + parent->pos - parent_global_orient_local_velocity * flFrametime * interp, orient}; }, }, *this); } @@ -153,13 +145,27 @@ std::optional EffectAttachment::extract_object() const { [](const attachment_object& obj) -> std::optional { return obj; }, - [](const attachment_particle& parent_part) -> std::optional { + [](const attachment_particle&) -> std::optional { + return std::nullopt; + }, + }, *this); +} + +EffectAttachment EffectAttachment::resolve_true_parent() const { + return std::visit(overloads{ + [](const std::monostate&) -> EffectAttachment { + return std::monostate{}; + }, + [](const attachment_object& obj) -> EffectAttachment { + return obj; + }, + [](const attachment_particle& parent_part) -> EffectAttachment { const auto& parent = parent_part.particle.lock(); const auto& effect = parent->parent_effect.getParticleEffect(); if (effect.m_parent_is_transitive) - return parent->attachment.extract_object(); + return parent->attachment.resolve_true_parent(); else - return std::nullopt; + return parent_part; }, }, *this); } diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 2fe055ca835..51ee56b80b1 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -30,6 +30,7 @@ namespace effects { bool is_valid() const; std::pair get_frame(float interp = 0.0f) const; std::optional extract_object() const; + EffectAttachment resolve_true_parent() const; }; } diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 14b25fcf922..cb6383720f1 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -65,7 +65,7 @@ vec3d EffectHostParticle::getVelocity() const { } effects::EffectAttachment EffectHostParticle::getParentAttachment() const { - return effects::attachment_particle{m_particle}; + return effects::EffectAttachment(effects::attachment_particle{m_particle}).resolve_true_parent(); } float EffectHostParticle::getLifetime() const { From 52621cc6d111decb4af0565ab5288881edc6fef1 Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 09:46:57 +0900 Subject: [PATCH 06/17] paticle hosts now do local correctly --- code/globalincs/utility.h | 2 -- code/particle/EffectHost.cpp | 6 ++-- code/particle/EffectHost.h | 1 + code/particle/hosts/EffectHostParticle.cpp | 35 +++++++++++----------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/code/globalincs/utility.h b/code/globalincs/utility.h index 2b6f0400774..ca541f176f4 100644 --- a/code/globalincs/utility.h +++ b/code/globalincs/utility.h @@ -3,8 +3,6 @@ #define _FSO_UTILITY_H #include -#include -#include #include "globalincs/globals.h" #include "globalincs/toolchain.h" diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 9d1d76f92d4..012337ef23b 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -90,7 +90,8 @@ vec3d EffectAttachment::local_last_pos_to_global(const vec3d& last_pos) const { vec3d pos; vm_vec_unrotate(&pos, &last_pos, &parent_orient); - return pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime; + float vel_scalar = parent->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, std::forward_as_tuple(*parent, vm_vec_mag_quick(&parent->velocity))); + return pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime * vel_scalar; }, }, *this); } @@ -131,8 +132,9 @@ std::pair EffectAttachment::get_frame(float interp) const { vec3d parent_global_orient_local_velocity; vm_vec_unrotate(&parent_global_orient_local_velocity, &parent->velocity, &orient); + float vel_scalar = parent->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, std::forward_as_tuple(*parent, vm_vec_mag_quick(&parent->velocity))); - return {parent_pos + parent->pos - parent_global_orient_local_velocity * flFrametime * interp, orient}; + return {parent_pos + parent->pos - parent_global_orient_local_velocity * flFrametime * interp * vel_scalar, orient}; }, }, *this); } diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 51ee56b80b1..fc3395900ee 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -6,6 +6,7 @@ #include "math/vecmat.h" #include +#include namespace particle { struct particle; diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index cb6383720f1..5127db64587 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -12,7 +12,6 @@ EffectHostParticle::EffectHostParticle(particle::WeakParticlePtr particle, matrix orientationOverride, bool orientationOverrideRelative) : EffectHost(orientationOverride, orientationOverrideRelative), m_particle(std::move(particle)) {} -//Particle hosts can never have a parent, so it'll always return global space std::pair EffectHostParticle::getPositionAndOrientation(bool relativeToParent, float interp, const std::optional& tabled_offset) const { const auto& particle = m_particle.lock(); @@ -20,30 +19,30 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela bool has_obj_parent = obj && obj->objnum >= 0; relativeToParent &= has_obj_parent; - vec3d pos; - if (interp != 0.0f) { - 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))); - vec3d pos_last = particle->pos - (particle->velocity * vel_scalar * flFrametime); - vm_vec_linear_interpolate(&pos, &particle->pos, &pos_last, interp); - } else { - pos = particle->pos; - } - - pos = particle->attachment.local_pos_to_global(pos, interp); + vec3d pos = ZERO_VECTOR; // find the particle direction (normalized vector) // note: this can't be computed for particles with 0 velocity, so use the safe version vec3d particle_dir; vm_vec_copy_normalize_safe(&particle_dir, &particle->velocity); - if (tabled_offset) - pos += particle_dir * tabled_offset->xyz.z; - matrix orientation; if (!relativeToParent) { + vec3d global_pos; + if (interp != 0.0f) { + 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))); + vec3d pos_last = particle->pos - (particle->velocity * vel_scalar * flFrametime); + vm_vec_linear_interpolate(&global_pos, &particle->pos, &pos_last, interp); + } else { + global_pos = particle->pos; + } + + particle_dir = particle->attachment.local_vel_to_global(particle->velocity); + pos += particle->attachment.local_pos_to_global(global_pos, interp); + //As there's no sensible uvec in this particle orientation, relative override orientation is not that sensible. Nonetheless, allow it for compatibility, or future orientation-aware particles - orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix_norm(&orientation, &particle_dir) : m_orientationOverride; + orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; } else { auto [parent_pos, parent_orient] = particle->attachment.get_frame(interp); @@ -52,11 +51,11 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. matrix global_orient_transpose; orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_copy_transpose(&global_orient_transpose, &parent_orient); - - vm_vec_sub2(&pos, &parent_pos); - vm_vec_rotate(&pos, &pos, &parent_orient); } + if (tabled_offset) + pos += particle_dir * tabled_offset->xyz.z; + return { pos, orientation }; } From 109154886b4bb39a19d34cf9728025b14eabafaf Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:06:53 +0900 Subject: [PATCH 07/17] Fix effecthostparticle local mode --- code/particle/hosts/EffectHostParticle.cpp | 26 +++++++++------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 5127db64587..e29e811f16a 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -15,11 +15,14 @@ EffectHostParticle::EffectHostParticle(particle::WeakParticlePtr particle, matri std::pair EffectHostParticle::getPositionAndOrientation(bool relativeToParent, float interp, const std::optional& tabled_offset) const { const auto& particle = m_particle.lock(); - auto* obj = std::get_if(&particle->attachment); - bool has_obj_parent = obj && obj->objnum >= 0; - relativeToParent &= has_obj_parent; - - vec3d pos = ZERO_VECTOR; + vec3d local_pos; + if (interp != 0.0f) { + 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))); + vec3d pos_last = particle->pos - (particle->velocity * vel_scalar * flFrametime); + vm_vec_linear_interpolate(&local_pos, &particle->pos, &pos_last, interp); + } else { + local_pos = particle->pos; + } // find the particle direction (normalized vector) // note: this can't be computed for particles with 0 velocity, so use the safe version @@ -27,19 +30,11 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela vm_vec_copy_normalize_safe(&particle_dir, &particle->velocity); matrix orientation; + vec3d pos; if (!relativeToParent) { - vec3d global_pos; - if (interp != 0.0f) { - 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))); - vec3d pos_last = particle->pos - (particle->velocity * vel_scalar * flFrametime); - vm_vec_linear_interpolate(&global_pos, &particle->pos, &pos_last, interp); - } else { - global_pos = particle->pos; - } - particle_dir = particle->attachment.local_vel_to_global(particle->velocity); - pos += particle->attachment.local_pos_to_global(global_pos, interp); + pos = particle->attachment.local_pos_to_global(local_pos, interp); //As there's no sensible uvec in this particle orientation, relative override orientation is not that sensible. Nonetheless, allow it for compatibility, or future orientation-aware particles orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; @@ -47,7 +42,6 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela else { auto [parent_pos, parent_orient] = particle->attachment.get_frame(interp); - //The position data here is in world space //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. matrix global_orient_transpose; orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_copy_transpose(&global_orient_transpose, &parent_orient); From ea2ef99f4fd7949bbea4f5a5f1c5274f9e59900e Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:16:39 +0900 Subject: [PATCH 08/17] again --- code/particle/EffectHost.cpp | 29 ++++++++++++++++++++++ code/particle/EffectHost.h | 1 + code/particle/hosts/EffectHostParticle.cpp | 11 ++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 012337ef23b..86a5d23b859 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -39,6 +39,35 @@ vec3d EffectAttachment::local_pos_to_global(const vec3d& local_pos, float interp }, *this); } +vec3d EffectAttachment::global_to_local(const vec3d& global_pos) const { + return std::visit(overloads { + [&global_pos](const std::monostate&) { + return global_pos; + }, + [&global_pos](const attachment_object& obj) { + if (obj.objnum < 0) + return global_pos; + + vec3d pos = global_pos - Objects[obj.objnum].pos; + vm_vec_rotate(&pos, &pos, &Objects[obj.objnum].orient); + + return pos; + }, + [&global_pos, this](const attachment_particle& parent_part) { + const auto& parent = parent_part.particle.lock(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); + if (!parent) + return global_pos; + + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d pos = global_pos - parent_pos; + vm_vec_rotate(&pos, &pos, &parent_orient); + + return pos; + }, + }, *this); +} + vec3d EffectAttachment::local_vel_to_global(const vec3d& local_vel) const { return std::visit(overloads { [&local_vel](const std::monostate&) { diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index fc3395900ee..795635888b1 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -26,6 +26,7 @@ namespace effects { using std::variant::variant; vec3d local_pos_to_global(const vec3d& local_pos, float interp = 0.0f) const; + vec3d global_to_local(const vec3d& global_pos) const ; vec3d local_vel_to_global(const vec3d& local_vel) const; vec3d local_last_pos_to_global(const vec3d& last_pos) const; bool is_valid() const; diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index e29e811f16a..efd392fcca6 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -15,13 +15,13 @@ EffectHostParticle::EffectHostParticle(particle::WeakParticlePtr particle, matri std::pair EffectHostParticle::getPositionAndOrientation(bool relativeToParent, float interp, const std::optional& tabled_offset) const { const auto& particle = m_particle.lock(); - vec3d local_pos; + vec3d pos; if (interp != 0.0f) { 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))); vec3d pos_last = particle->pos - (particle->velocity * vel_scalar * flFrametime); - vm_vec_linear_interpolate(&local_pos, &particle->pos, &pos_last, interp); + vm_vec_linear_interpolate(&pos, &particle->pos, &pos_last, interp); } else { - local_pos = particle->pos; + pos = particle->pos; } // find the particle direction (normalized vector) @@ -30,11 +30,10 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela vm_vec_copy_normalize_safe(&particle_dir, &particle->velocity); matrix orientation; - vec3d pos; if (!relativeToParent) { particle_dir = particle->attachment.local_vel_to_global(particle->velocity); - pos = particle->attachment.local_pos_to_global(local_pos, interp); + pos = particle->attachment.local_pos_to_global(pos, interp); //As there's no sensible uvec in this particle orientation, relative override orientation is not that sensible. Nonetheless, allow it for compatibility, or future orientation-aware particles orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; @@ -42,6 +41,8 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela else { auto [parent_pos, parent_orient] = particle->attachment.get_frame(interp); + pos = particle->attachment.global_to_local(pos); + //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. matrix global_orient_transpose; orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_copy_transpose(&global_orient_transpose, &parent_orient); From d9b62bf0981c2c7cccd762cebe64403467da0a22 Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:28:11 +0900 Subject: [PATCH 09/17] Set particle host orientation --- code/particle/EffectHost.cpp | 33 +++++++++++++++++++++- code/particle/EffectHost.h | 1 + code/particle/hosts/EffectHostParticle.cpp | 8 +++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 86a5d23b859..f805b82fe5e 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -96,6 +96,34 @@ vec3d EffectAttachment::local_vel_to_global(const vec3d& local_vel) const { }, *this); } +vec3d EffectAttachment::global_vel_to_local(const vec3d& global_vel) const { + return std::visit(overloads { + [&global_vel](const std::monostate&) { + return global_vel; + }, + [&global_vel](const attachment_object& obj) { + if (obj.objnum < 0) + return global_vel; + + vec3d vel; + vm_vec_rotate(&vel, &global_vel, &Objects[obj.objnum].orient); + return vel; + }, + [&global_vel, this](const attachment_particle& parent_part) { + const auto& parent = parent_part.particle.lock(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); + if (!parent) + return global_vel; + + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d vel; + vm_vec_rotate(&vel, &global_vel, &parent_orient); + + return vel + parent->attachment.global_vel_to_local(parent->velocity); + }, + }, *this); +} + vec3d EffectAttachment::local_last_pos_to_global(const vec3d& last_pos) const { return std::visit(overloads{ [&last_pos](const std::monostate&) { @@ -163,7 +191,10 @@ std::pair EffectAttachment::get_frame(float interp) const { vm_vec_unrotate(&parent_global_orient_local_velocity, &parent->velocity, &orient); float vel_scalar = parent->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, std::forward_as_tuple(*parent, vm_vec_mag_quick(&parent->velocity))); - return {parent_pos + parent->pos - parent_global_orient_local_velocity * flFrametime * interp * vel_scalar, orient}; + vec3d pos_rotated; + vm_vec_unrotate(&pos_rotated, &parent->pos, &orient); + + return {parent_pos + pos_rotated - parent_global_orient_local_velocity * flFrametime * interp * vel_scalar, orient}; }, }, *this); } diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 795635888b1..304d31aeeac 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -28,6 +28,7 @@ namespace effects { vec3d local_pos_to_global(const vec3d& local_pos, float interp = 0.0f) const; vec3d global_to_local(const vec3d& global_pos) const ; vec3d local_vel_to_global(const vec3d& local_vel) const; + vec3d global_vel_to_local(const vec3d& global_vel) const; vec3d local_last_pos_to_global(const vec3d& last_pos) const; bool is_valid() const; std::pair get_frame(float interp = 0.0f) const; diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index efd392fcca6..b153997878f 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -33,19 +33,19 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela if (!relativeToParent) { particle_dir = particle->attachment.local_vel_to_global(particle->velocity); + vm_vec_normalize_safe(&particle_dir); pos = particle->attachment.local_pos_to_global(pos, interp); //As there's no sensible uvec in this particle orientation, relative override orientation is not that sensible. Nonetheless, allow it for compatibility, or future orientation-aware particles orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; } else { - auto [parent_pos, parent_orient] = particle->attachment.get_frame(interp); - + particle_dir = particle->attachment.global_vel_to_local(particle->velocity); + vm_vec_normalize_safe(&particle_dir); pos = particle->attachment.global_to_local(pos); //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. - matrix global_orient_transpose; - orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_copy_transpose(&global_orient_transpose, &parent_orient); + orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir); } if (tabled_offset) From be6548bf8b84685b157e1ecc9956a3ffe397c0cf Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:35:53 +0900 Subject: [PATCH 10/17] I don't like mixed local and global vectors --- code/particle/hosts/EffectHostParticle.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index b153997878f..10496d988a6 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -35,19 +35,15 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela particle_dir = particle->attachment.local_vel_to_global(particle->velocity); vm_vec_normalize_safe(&particle_dir); pos = particle->attachment.local_pos_to_global(pos, interp); - - //As there's no sensible uvec in this particle orientation, relative override orientation is not that sensible. Nonetheless, allow it for compatibility, or future orientation-aware particles - orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; } else { particle_dir = particle->attachment.global_vel_to_local(particle->velocity); vm_vec_normalize_safe(&particle_dir); pos = particle->attachment.global_to_local(pos); - - //Since we're operating in local space, we can take the orientation override at face value if it's relative, but we need to convert it from global to local otherwise. - orientation = m_orientationOverrideRelative ? m_orientationOverride : m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir); } + orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; + if (tabled_offset) pos += particle_dir * tabled_offset->xyz.z; From 75dec5f0a6d9695ab175ac6dc09dab5e64c5fa83 Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:49:20 +0900 Subject: [PATCH 11/17] agaiiin --- code/particle/hosts/EffectHostParticle.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 10496d988a6..0063675d8fa 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -28,18 +28,25 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela // note: this can't be computed for particles with 0 velocity, so use the safe version vec3d particle_dir; vm_vec_copy_normalize_safe(&particle_dir, &particle->velocity); + particle_dir = particle->attachment.local_vel_to_global(particle->velocity); + vm_vec_normalize_safe(&particle_dir); matrix orientation; + orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; if (!relativeToParent) { particle_dir = particle->attachment.local_vel_to_global(particle->velocity); vm_vec_normalize_safe(&particle_dir); pos = particle->attachment.local_pos_to_global(pos, interp); + + orientation = m_orientationOverrideRelative ? m_orientationOverride * orientation : m_orientationOverride; } else { - particle_dir = particle->attachment.global_vel_to_local(particle->velocity); - vm_vec_normalize_safe(&particle_dir); + const auto& [global_pos, global_orient] = particle->attachment.get_frame(); pos = particle->attachment.global_to_local(pos); + + matrix global_orient_transpose; + orientation = orientation * *vm_copy_transpose(&global_orient_transpose, &global_orient); } orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; From 8bb4f9b9bb50c1d0be41e4c37cd441edf1b37672 Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 11:03:49 +0900 Subject: [PATCH 12/17] Fix validity check --- code/particle/EffectHost.cpp | 8 ++++++-- code/particle/hosts/EffectHostParticle.cpp | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index f805b82fe5e..0d9a4d6acf7 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -224,8 +224,12 @@ EffectAttachment EffectAttachment::resolve_true_parent() const { [](const attachment_particle& parent_part) -> EffectAttachment { const auto& parent = parent_part.particle.lock(); const auto& effect = parent->parent_effect.getParticleEffect(); - if (effect.m_parent_is_transitive) - return parent->attachment.resolve_true_parent(); + if (effect.m_parent_is_transitive) { + if (parent->attachment.is_valid()) + return parent->attachment.resolve_true_parent(); + else + return std::monostate{}; + } else return parent_part; }, diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 0063675d8fa..823f83623e3 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -38,8 +38,6 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela particle_dir = particle->attachment.local_vel_to_global(particle->velocity); vm_vec_normalize_safe(&particle_dir); pos = particle->attachment.local_pos_to_global(pos, interp); - - orientation = m_orientationOverrideRelative ? m_orientationOverride * orientation : m_orientationOverride; } else { const auto& [global_pos, global_orient] = particle->attachment.get_frame(); From 55a0368a28cadbbdec05d80185a72982a736089d Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sat, 13 Jun 2026 11:12:40 +0900 Subject: [PATCH 13/17] parent foo --- code/particle/hosts/EffectHostParticle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 823f83623e3..24434ebbdc4 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -41,7 +41,7 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela } else { const auto& [global_pos, global_orient] = particle->attachment.get_frame(); - pos = particle->attachment.global_to_local(pos); + pos = getParentAttachment().global_to_local(pos); matrix global_orient_transpose; orientation = orientation * *vm_copy_transpose(&global_orient_transpose, &global_orient); From b304d0ffe58ba30e51b3ec485c174bc61c4ad74d Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:02:52 +0900 Subject: [PATCH 14/17] Fix bugs, hopefully finally --- code/particle/EffectHost.cpp | 6 ++--- code/particle/hosts/EffectHostParticle.cpp | 29 ++++++++++------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 0d9a4d6acf7..57dc60de4cb 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -116,10 +116,10 @@ vec3d EffectAttachment::global_vel_to_local(const vec3d& global_vel) const { return global_vel; const auto& [parent_pos, parent_orient] = get_frame(); - vec3d vel; - vm_vec_rotate(&vel, &global_vel, &parent_orient); + vec3d relative_vel = global_vel - parent->attachment.local_vel_to_global(parent->velocity); + vm_vec_rotate(&relative_vel, &relative_vel, &parent_orient); - return vel + parent->attachment.global_vel_to_local(parent->velocity); + return relative_vel; }, }, *this); } diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 24434ebbdc4..8017b5c20a7 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -24,30 +24,27 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela pos = particle->pos; } - // find the particle direction (normalized vector) - // note: this can't be computed for particles with 0 velocity, so use the safe version - vec3d particle_dir; - vm_vec_copy_normalize_safe(&particle_dir, &particle->velocity); - particle_dir = particle->attachment.local_vel_to_global(particle->velocity); + vec3d particle_dir = particle->attachment.local_vel_to_global(particle->velocity); vm_vec_normalize_safe(&particle_dir); matrix orientation; - orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; + pos = particle->attachment.local_pos_to_global(pos, interp); if (!relativeToParent) { - particle_dir = particle->attachment.local_vel_to_global(particle->velocity); - vm_vec_normalize_safe(&particle_dir); - pos = particle->attachment.local_pos_to_global(pos, interp); - } - else { - const auto& [global_pos, global_orient] = particle->attachment.get_frame(); + orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; + } else { pos = getParentAttachment().global_to_local(pos); - matrix global_orient_transpose; - orientation = orientation * *vm_copy_transpose(&global_orient_transpose, &global_orient); - } + const auto& [parent_pos, parent_orient] = getParentAttachment().get_frame(interp); + vm_vec_rotate(&particle_dir, &particle_dir, &parent_orient); - orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; + if (m_orientationOverrideRelative) { + orientation = m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir); + } else { + matrix parent_orient_transpose; + orientation = m_orientationOverride * *vm_copy_transpose(&parent_orient_transpose, &parent_orient); + } + } if (tabled_offset) pos += particle_dir * tabled_offset->xyz.z; From ab4eb166bd5151674ba35eeeabc65489d34e9a5f Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:03:47 +0900 Subject: [PATCH 15/17] refactor --- code/particle/EffectHost.cpp | 2 +- code/particle/EffectHost.h | 2 +- code/particle/hosts/EffectHostParticle.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index 57dc60de4cb..f604700361c 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -39,7 +39,7 @@ vec3d EffectAttachment::local_pos_to_global(const vec3d& local_pos, float interp }, *this); } -vec3d EffectAttachment::global_to_local(const vec3d& global_pos) const { +vec3d EffectAttachment::global_pos_to_local(const vec3d& global_pos) const { return std::visit(overloads { [&global_pos](const std::monostate&) { return global_pos; diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 304d31aeeac..88d5bd3db27 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -26,7 +26,7 @@ namespace effects { using std::variant::variant; vec3d local_pos_to_global(const vec3d& local_pos, float interp = 0.0f) const; - vec3d global_to_local(const vec3d& global_pos) const ; + vec3d global_pos_to_local(const vec3d& global_pos) const ; vec3d local_vel_to_global(const vec3d& local_vel) const; vec3d global_vel_to_local(const vec3d& global_vel) const; vec3d local_last_pos_to_global(const vec3d& last_pos) const; diff --git a/code/particle/hosts/EffectHostParticle.cpp b/code/particle/hosts/EffectHostParticle.cpp index 8017b5c20a7..139efbcf890 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -33,7 +33,7 @@ std::pair EffectHostParticle::getPositionAndOrientation(bool rela if (!relativeToParent) { orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; } else { - pos = getParentAttachment().global_to_local(pos); + pos = getParentAttachment().global_pos_to_local(pos); const auto& [parent_pos, parent_orient] = getParentAttachment().get_frame(interp); vm_vec_rotate(&particle_dir, &particle_dir, &parent_orient); From 9d26cf9c35b1e9d7668ec6af21eb2d9e2c702495 Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Mon, 15 Jun 2026 07:40:09 +0900 Subject: [PATCH 16/17] Flip new table option for intuitiveness --- code/particle/ParticleParse.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/particle/ParticleParse.cpp b/code/particle/ParticleParse.cpp index 875b3b1e3ac..5253199be7c 100644 --- a/code/particle/ParticleParse.cpp +++ b/code/particle/ParticleParse.cpp @@ -104,8 +104,9 @@ namespace particle { if (optional_string("+Remain local to parent:")) { stuff_boolean(&effect.m_parent_local); } - if (optional_string("+Transitive parenting:")) { + if (optional_string("+Intransitive parenting:")) { stuff_boolean(&effect.m_parent_is_transitive); + effect.m_parent_is_transitive = !effect.m_parent_is_transitive; } } From f8abf05a1ec7500cec5b0c97eb18b1c0a38c43df Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Mon, 15 Jun 2026 08:42:37 +0900 Subject: [PATCH 17/17] Fix support for GCC9 due to issues with LWG3052 --- code/particle/EffectHost.cpp | 32 ++++++++++++-------- code/particle/EffectHost.h | 12 ++++++-- code/particle/ParticleEffect.cpp | 4 +-- code/particle/hosts/EffectHostObject.cpp | 2 +- code/particle/hosts/EffectHostSubmodel.cpp | 2 +- code/particle/hosts/EffectHostTurret.cpp | 2 +- code/particle/volumes/ModelSurfaceVolume.cpp | 8 ++--- code/scripting/api/objs/particle.cpp | 4 +-- 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/code/particle/EffectHost.cpp b/code/particle/EffectHost.cpp index f604700361c..a7ccaa1c26b 100644 --- a/code/particle/EffectHost.cpp +++ b/code/particle/EffectHost.cpp @@ -5,6 +5,8 @@ #include "globalincs/utility.h" #include "particle/particle.h" +#include + namespace effects { vec3d EffectAttachment::local_pos_to_global(const vec3d& local_pos, float interp) const { return std::visit(overloads { @@ -36,7 +38,7 @@ vec3d EffectAttachment::local_pos_to_global(const vec3d& local_pos, float interp return pos + parent_pos; }, - }, *this); + }, m_variant); } vec3d EffectAttachment::global_pos_to_local(const vec3d& global_pos) const { @@ -65,7 +67,7 @@ vec3d EffectAttachment::global_pos_to_local(const vec3d& global_pos) const { return pos; }, - }, *this); + }, m_variant); } vec3d EffectAttachment::local_vel_to_global(const vec3d& local_vel) const { @@ -93,7 +95,7 @@ vec3d EffectAttachment::local_vel_to_global(const vec3d& local_vel) const { return vel + parent->attachment.local_vel_to_global(parent->velocity); }, - }, *this); + }, m_variant); } vec3d EffectAttachment::global_vel_to_local(const vec3d& global_vel) const { @@ -121,7 +123,7 @@ vec3d EffectAttachment::global_vel_to_local(const vec3d& global_vel) const { return relative_vel; }, - }, *this); + }, m_variant); } vec3d EffectAttachment::local_last_pos_to_global(const vec3d& last_pos) const { @@ -150,7 +152,7 @@ vec3d EffectAttachment::local_last_pos_to_global(const vec3d& last_pos) const { float vel_scalar = parent->parent_effect.getParticleEffect().m_lifetime_curves.get_output(particle::ParticleEffect::ParticleLifetimeCurvesOutput::VELOCITY_MULT, std::forward_as_tuple(*parent, vm_vec_mag_quick(&parent->velocity))); return pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime * vel_scalar; }, - }, *this); + }, m_variant); } bool EffectAttachment::is_valid() const { @@ -164,7 +166,11 @@ bool EffectAttachment::is_valid() const { [](const effects::attachment_particle& parent_part) { return !parent_part.particle.expired(); }, - }, *this); + }, m_variant); +} + +bool EffectAttachment::is_not_attached() const { + return std::holds_alternative(m_variant); } std::pair EffectAttachment::get_frame(float interp) const { @@ -196,7 +202,7 @@ std::pair EffectAttachment::get_frame(float interp) const { return {parent_pos + pos_rotated - parent_global_orient_local_velocity * flFrametime * interp * vel_scalar, orient}; }, - }, *this); + }, m_variant); } std::optional EffectAttachment::extract_object() const { @@ -210,16 +216,16 @@ std::optional EffectAttachment::extract_object() const { [](const attachment_particle&) -> std::optional { return std::nullopt; }, - }, *this); + }, m_variant); } EffectAttachment EffectAttachment::resolve_true_parent() const { return std::visit(overloads{ [](const std::monostate&) -> EffectAttachment { - return std::monostate{}; + return {}; }, [](const attachment_object& obj) -> EffectAttachment { - return obj; + return {obj}; }, [](const attachment_particle& parent_part) -> EffectAttachment { const auto& parent = parent_part.particle.lock(); @@ -228,11 +234,11 @@ EffectAttachment EffectAttachment::resolve_true_parent() const { if (parent->attachment.is_valid()) return parent->attachment.resolve_true_parent(); else - return std::monostate{}; + return {}; } else - return parent_part; + return {parent_part}; }, - }, *this); + }, m_variant); } } diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 88d5bd3db27..efd12cad1db 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -22,18 +22,26 @@ namespace effects { particle::WeakParticlePtr particle = particle::WeakParticlePtr(); }; - struct EffectAttachment : public std::variant { - using std::variant::variant; + struct EffectAttachment { + private: + using underlying_type = std::variant; + underlying_type m_variant; + public: vec3d local_pos_to_global(const vec3d& local_pos, float interp = 0.0f) const; vec3d global_pos_to_local(const vec3d& global_pos) const ; vec3d local_vel_to_global(const vec3d& local_vel) const; vec3d global_vel_to_local(const vec3d& global_vel) const; vec3d local_last_pos_to_global(const vec3d& last_pos) const; bool is_valid() const; + bool is_not_attached() const; std::pair get_frame(float interp = 0.0f) const; std::optional extract_object() const; EffectAttachment resolve_true_parent() const; + + constexpr EffectAttachment() : m_variant(std::monostate()) {}; + EffectAttachment(const underlying_type& variant) : m_variant(variant) {}; + EffectAttachment(underlying_type&& variant) : m_variant(variant) {}; }; } diff --git a/code/particle/ParticleEffect.cpp b/code/particle/ParticleEffect.cpp index a7916041023..bcee8367ce6 100644 --- a/code/particle/ParticleEffect.cpp +++ b/code/particle/ParticleEffect.cpp @@ -188,7 +188,7 @@ void ParticleEffect::sampleNoise(vec3d& noiseTarget, const matrix* orientation, } vec3d ParticleEffect::adaptPosition(const vec3d& pos, const effects::EffectAttachment& attachment) const { - if (std::holds_alternative(attachment) || !m_local_position_scaling.has_value()) { + if (attachment.is_not_attached() || !m_local_position_scaling.has_value()) { return pos; } @@ -305,7 +305,7 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s if (m_vel_inherit_absolute) vm_vec_normalize_safe(&info.velocity, true); - info.velocity *= (m_ignore_velocity_inherit_if_has_parent && !std::holds_alternative(attachment)) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier; + info.velocity *= (m_ignore_velocity_inherit_if_has_parent && !attachment.is_not_attached()) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier; vec3d localVelocity = velNoise; vec3d localPos = posNoise; diff --git a/code/particle/hosts/EffectHostObject.cpp b/code/particle/hosts/EffectHostObject.cpp index 4b8fac2c739..250a3b54022 100644 --- a/code/particle/hosts/EffectHostObject.cpp +++ b/code/particle/hosts/EffectHostObject.cpp @@ -52,7 +52,7 @@ vec3d EffectHostObject::getVelocity() const { } effects::EffectAttachment EffectHostObject::getParentAttachment() const { - return effects::attachment_object{m_objnum, m_objsig}; + return {effects::attachment_object{m_objnum, m_objsig}}; } float EffectHostObject::getHostRadius() const { diff --git a/code/particle/hosts/EffectHostSubmodel.cpp b/code/particle/hosts/EffectHostSubmodel.cpp index 97e7b1aa514..71e45924a0c 100644 --- a/code/particle/hosts/EffectHostSubmodel.cpp +++ b/code/particle/hosts/EffectHostSubmodel.cpp @@ -57,7 +57,7 @@ vec3d EffectHostSubmodel::getVelocity() const { } effects::EffectAttachment EffectHostSubmodel::getParentAttachment() const { - return effects::attachment_object{m_objnum, m_objsig}; + return {effects::attachment_object{m_objnum, m_objsig}}; } int EffectHostSubmodel::getParentSubmodel() const { diff --git a/code/particle/hosts/EffectHostTurret.cpp b/code/particle/hosts/EffectHostTurret.cpp index a482c965406..7cb1b193a4b 100644 --- a/code/particle/hosts/EffectHostTurret.cpp +++ b/code/particle/hosts/EffectHostTurret.cpp @@ -71,7 +71,7 @@ vec3d EffectHostTurret::getVelocity() const { } effects::EffectAttachment EffectHostTurret::getParentAttachment() const { - return effects::attachment_object{m_objnum, m_objsig}; + return {effects::attachment_object{m_objnum, m_objsig}}; } int EffectHostTurret::getParentSubmodel() const { diff --git a/code/particle/volumes/ModelSurfaceVolume.cpp b/code/particle/volumes/ModelSurfaceVolume.cpp index 4e61a37e3c5..c6f6a24ec42 100644 --- a/code/particle/volumes/ModelSurfaceVolume.cpp +++ b/code/particle/volumes/ModelSurfaceVolume.cpp @@ -9,17 +9,15 @@ ModelSurfaceVolume::ModelSurfaceVolume() : m_modelScale(::util::UniformFloatRang vec3d ModelSurfaceVolume::sampleRandomPoint(const matrix &orientation, decltype(ParticleEffect::modular_curves_definition)::input_type_t source, float particlesFraction, const EffectHost& host) { auto attachment = host.getParentAttachment(); - int obj_num = -1; - if (auto* obj = std::get_if(&attachment)) - obj_num = obj->objnum; + auto obj = attachment.extract_object(); int submodel = host.getParentSubmodel(); vec3d point = ZERO_VECTOR; auto curveSource = std::tuple_cat(source, std::make_tuple(particlesFraction)); - if (obj_num >= 0) { - const polymodel* pm = object_get_model(&Objects[obj_num]); + if (obj) { + const polymodel* pm = object_get_model(&Objects[obj->objnum]); if (pm != nullptr) { if (submodel < 0) { SCP_vector eligible_submodels; diff --git a/code/scripting/api/objs/particle.cpp b/code/scripting/api/objs/particle.cpp index 47f34e1d09b..46fea62a2e1 100644 --- a/code/scripting/api/objs/particle.cpp +++ b/code/scripting/api/objs/particle.cpp @@ -205,10 +205,10 @@ ADE_VIRTVAR(AttachedObject, l_Particle, "object", "The object this particle is a if (ADE_SETTING_VAR) { if (newObj != nullptr && newObj->isValid()) - ph->Get().lock()->attachment = effects::attachment_object{newObj->objnum, newObj->sig}; + ph->Get().lock()->attachment = {effects::attachment_object{newObj->objnum, newObj->sig}}; } - if (auto* obj = std::get_if(&ph->Get().lock()->attachment)) + if (auto obj = ph->Get().lock()->attachment.extract_object()) return ade_set_object_with_breed(L, obj->objnum); else return ade_set_object_with_breed(L, -1);