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.cpp b/code/particle/EffectHost.cpp new file mode 100644 index 00000000000..a7ccaa1c26b --- /dev/null +++ b/code/particle/EffectHost.cpp @@ -0,0 +1,244 @@ +#include "particle/EffectHost.h" + +#include "freespace.h" +#include "ParticleEffect.h" +#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 { + [&local_pos](const std::monostate&) { + return local_pos; + }, + [&local_pos, interp](const 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, 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; + + const auto& [parent_pos, parent_orient] = get_frame(interp); + vec3d pos; + vm_vec_unrotate(&pos, &local_pos, &parent_orient); + + return pos + parent_pos; + }, + }, m_variant); +} + +vec3d EffectAttachment::global_pos_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; + }, + }, m_variant); +} + +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 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, 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; + + 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); + }, + }, m_variant); +} + +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 relative_vel = global_vel - parent->attachment.local_vel_to_global(parent->velocity); + vm_vec_rotate(&relative_vel, &relative_vel, &parent_orient); + + return relative_vel; + }, + }, m_variant); +} + +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 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, 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; + + const auto& [parent_pos, parent_orient] = get_frame(); + vec3d pos; + vm_vec_unrotate(&pos, &last_pos, &parent_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 pos + parent_pos - parent->attachment.local_vel_to_global(parent->velocity) * flFrametime * vel_scalar; + }, + }, m_variant); +} + +bool EffectAttachment::is_valid() const { + 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(); + }, + }, m_variant); +} + +bool EffectAttachment::is_not_attached() const { + return std::holds_alternative(m_variant); +} + +std::pair EffectAttachment::get_frame(float interp) const { + 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(); + Assertion(!parent->parent_effect.getParticleEffect().m_parent_is_transitive, "Encountered live transitive parent in effect attachment."); + if (!parent) + return {ZERO_VECTOR, vmd_identity_matrix}; + + 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); + 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))); + + 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}; + }, + }, m_variant); +} + +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&) -> std::optional { + return std::nullopt; + }, + }, m_variant); +} + +EffectAttachment EffectAttachment::resolve_true_parent() const { + return std::visit(overloads{ + [](const std::monostate&) -> EffectAttachment { + return {}; + }, + [](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) { + if (parent->attachment.is_valid()) + return parent->attachment.resolve_true_parent(); + else + return {}; + } + else + return {parent_part}; + }, + }, m_variant); +} +} diff --git a/code/particle/EffectHost.h b/code/particle/EffectHost.h index 9bdea7296d8..efd12cad1db 100644 --- a/code/particle/EffectHost.h +++ b/code/particle/EffectHost.h @@ -2,9 +2,48 @@ #include "object/object.h" #include "globalincs/pstypes.h" +#include "globalincs/utility.h" #include "math/vecmat.h" #include +#include + +namespace particle { + struct particle; + typedef std::weak_ptr WeakParticlePtr; +} + +namespace effects { + struct attachment_object { + int objnum = -1; + int sig = -1; + }; + struct attachment_particle { + particle::WeakParticlePtr particle = particle::WeakParticlePtr(); + }; + + 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) {}; + }; +} class EffectHost { @@ -26,7 +65,7 @@ class EffectHost { return vm_vec_mag_quick(&velocity); } - virtual std::pair getParentObjAndSig() const { return {-1, -1}; } + 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 7a69bd42e59..bcee8367ce6 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,23 +187,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 effects::EffectAttachment& attachment) const { + if (attachment.is_not_attached() || !m_local_position_scaling.has_value()) { return pos; } + auto [parent_pos, parent_orient] = attachment.get_frame(); + 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 +217,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 effects::EffectAttachment& attachment, float parentLifetime, float parentRadius, float particle_percent) const { using persistentParticlesList = std::conditional_t, bool>; persistentParticlesList createdParticles; @@ -236,12 +240,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 = attachment.local_pos_to_global(posGlobal, interp); } auto modularCurvesInput = std::forward_as_tuple(source, effectNumber, posGlobal); @@ -296,18 +299,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 && !attachment.is_not_attached()) ? 0.f : m_vel_inherit.next() * inheritVelocityMultiplier; vec3d localVelocity = velNoise; vec3d localPos = posNoise; @@ -425,12 +423,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 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, 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 effects::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..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, int parent) 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, 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 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, 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 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::getParentObjAndSig, 0, &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::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::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::getParentObjAndSig, 0, &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::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::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::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::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::attached_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::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::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::attached_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::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::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::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::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..5253199be7c 100644 --- a/code/particle/ParticleParse.cpp +++ b/code/particle/ParticleParse.cpp @@ -104,6 +104,10 @@ namespace particle { if (optional_string("+Remain local to parent:")) { stuff_boolean(&effect.m_parent_local); } + if (optional_string("+Intransitive parenting:")) { + stuff_boolean(&effect.m_parent_is_transitive); + effect.m_parent_is_transitive = !effect.m_parent_is_transitive; + } } template static void parseParticleNumber(ParticleEffect &effect) { 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..250a3b54022 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 }; +effects::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..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; - std::pair getParentObjAndSig() 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 5fc3a26b067..139efbcf890 100644 --- a/code/particle/hosts/EffectHostParticle.cpp +++ b/code/particle/hosts/EffectHostParticle.cpp @@ -12,12 +12,9 @@ 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(); - relativeToParent &= particle->attached_objnum >= 0; - 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))); @@ -27,39 +24,31 @@ 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; - } - - // 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; + vec3d particle_dir = particle->attachment.local_vel_to_global(particle->velocity); + vm_vec_normalize_safe(&particle_dir); matrix orientation; + pos = particle->attachment.local_pos_to_global(pos, interp); if (!relativeToParent) { - //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; - } - else { - //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); - - vm_vec_sub2(&pos, &Objects[particle->attached_objnum].pos); - vm_vec_rotate(&pos, &pos, &Objects[particle->attached_objnum].orient); + orientation = m_orientationOverrideRelative ? m_orientationOverride * *vm_vector_2_matrix(&orientation, &particle_dir) : m_orientationOverride; + } else { + 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); + + 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; + return { pos, orientation }; } @@ -67,9 +56,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}; +effects::EffectAttachment EffectHostParticle::getParentAttachment() const { + return effects::EffectAttachment(effects::attachment_particle{m_particle}).resolve_true_parent(); } float EffectHostParticle::getLifetime() const { @@ -88,4 +76,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..f7d210942a0 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; + effects::EffectAttachment getParentAttachment() const override; float getLifetime() const override; diff --git a/code/particle/hosts/EffectHostSubmodel.cpp b/code/particle/hosts/EffectHostSubmodel.cpp index 39e332f6b3c..71e45924a0c 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 }; +effects::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..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; - std::pair getParentObjAndSig() 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 747e4dbf519..7cb1b193a4b 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 }; +effects::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..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; - std::pair getParentObjAndSig() 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 7b1744bcf99..28882cbd32f 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,15 @@ 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 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 = 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)); // treat bigger particles as 'closer' @@ -195,12 +195,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 = subject_particle.attachment.local_pos_to_global(subject_particle.pos); float distance_to_eye = vm_vec_dist(&Eye_position, &world_pos); @@ -240,14 +235,9 @@ namespace particle } // if the particle is attached to an object which has become invalid, kill it - if (part->attached_objnum >= 0) + if (!part->attachment.is_valid()) { - // 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 +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; - 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 = 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); @@ -297,26 +278,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 = 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; - 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 = 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; light_add_tube(&p_pos, &p1, light_radius, light_radius, intensity, r, g, b, source_radius); @@ -328,11 +297,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 = 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); } @@ -411,16 +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; - 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 = part->attachment.local_pos_to_global(part->pos); bool part_has_length = part->length != 0.0f; @@ -434,14 +391,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 = 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 f96107f2940..ac91a01bdd0 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 + effects::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 * diff --git a/code/particle/volumes/ModelSurfaceVolume.cpp b/code/particle/volumes/ModelSurfaceVolume.cpp index 55fcbc35ea2..c6f6a24ec42 100644 --- a/code/particle/volumes/ModelSurfaceVolume.cpp +++ b/code/particle/volumes/ModelSurfaceVolume.cpp @@ -8,15 +8,16 @@ 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(); + 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/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..46fea62a2e1 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 = ph->Get().lock()->attachment.extract_object()) + 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