diff --git a/code/particle/ParticleEffect.cpp b/code/particle/ParticleEffect.cpp index 7a69bd42e59..144f91adcc5 100644 --- a/code/particle/ParticleEffect.cpp +++ b/code/particle/ParticleEffect.cpp @@ -16,6 +16,7 @@ ParticleEffect::ParticleEffect(SCP_string name) m_rotation_type(RotationType::DEFAULT), m_direction(ShapeDirection::ALIGNED), m_velocity_directional_scaling(VelocityScaling::NONE), + m_decalOrientationMode(DecalOrientationMode::TOWARDS_CENTER), m_affectedByDetail(false), m_parentLifetime(false), m_parentScale(false), @@ -26,6 +27,8 @@ ParticleEffect::ParticleEffect(SCP_string name) m_vel_inherit_from_position_absolute(false), m_reverseAnimation(false), m_ignore_velocity_inherit_if_has_parent(false), + m_renderAsDecal(false), + m_decalEmissive(false), m_bitmap_list({}), m_bitmap_range(::util::UniformRange(0)), m_delayRange(::util::UniformFloatRange(0.0f)), @@ -86,6 +89,7 @@ ParticleEffect::ParticleEffect(SCP_string name, m_rotation_type(RotationType::DEFAULT), m_direction(direction), m_velocity_directional_scaling(velocity_directional_scaling), + m_decalOrientationMode(DecalOrientationMode::TOWARDS_CENTER), m_affectedByDetail(affectedByDetail), m_parentLifetime(false), m_parentScale(false), @@ -96,6 +100,8 @@ ParticleEffect::ParticleEffect(SCP_string name, m_vel_inherit_from_position_absolute(velInheritFromPositionAbsolute), m_reverseAnimation(reverseAnimation), m_ignore_velocity_inherit_if_has_parent(ignoreVelocityInheritIfParented), + m_renderAsDecal(false), + m_decalEmissive(false), m_bitmap_list({bitmap}), m_bitmap_range(::util::UniformRange(0)), m_delayRange(::util::UniformFloatRange(0.0f)), diff --git a/code/particle/ParticleEffect.h b/code/particle/ParticleEffect.h index 35708d813fd..9414fb1bcb2 100644 --- a/code/particle/ParticleEffect.h +++ b/code/particle/ParticleEffect.h @@ -69,6 +69,10 @@ class ParticleEffect { SCREEN_ALIGNED }; + enum class DecalOrientationMode : uint8_t { + TOWARDS_CENTER + }; + enum class ParticleCurvesOutput : uint8_t { PARTICLE_NUM_MULT, PARTICLE_FREQ_MULT, @@ -130,6 +134,7 @@ class ParticleEffect { friend int ::parse_weapon(int, bool, const char*); friend ParticleEffectHandle scripting::api::getLegacyScriptingParticleEffect(int bitmap, bool reversed); friend bool move_particle(float frametime, particle* part); + friend bool render_particle(particle* part); SCP_string m_name; //!< The name of this effect @@ -139,6 +144,7 @@ class ParticleEffect { RotationType m_rotation_type; ShapeDirection m_direction; VelocityScaling m_velocity_directional_scaling; + DecalOrientationMode m_decalOrientationMode; bool m_affectedByDetail; //Kinda deprecated. Only used by the oldest of legacy effects. bool m_parentLifetime; @@ -150,6 +156,8 @@ class ParticleEffect { bool m_vel_inherit_from_position_absolute; bool m_reverseAnimation; bool m_ignore_velocity_inherit_if_has_parent; + bool m_renderAsDecal; + bool m_decalEmissive; SCP_vector m_bitmap_list; ::util::UniformRange m_bitmap_range; diff --git a/code/particle/ParticleParse.cpp b/code/particle/ParticleParse.cpp index 3a619e30451..0f11d19d599 100644 --- a/code/particle/ParticleParse.cpp +++ b/code/particle/ParticleParse.cpp @@ -343,6 +343,35 @@ namespace particle { } } + static void parseRenderAsDecal(ParticleEffect &effect) { + if (optional_string("$Render As Decal:")) { + stuff_boolean(&effect.m_renderAsDecal); + + if (effect.m_renderAsDecal) { + if (!effect.m_parent_local) { + error_display(0, "Particle must be local to parent for decal rendering. Disabling decal rendering."); + effect.m_renderAsDecal = false; + //We still parse the rest to allow parsing to properly complete for the other effects. + } + + if (optional_string("+Orientation Mode:")) { + switch (required_string_one_of(1, "Towards Center")) { + case 0: + required_string("Towards Center"); + effect.m_decalOrientationMode = ParticleEffect::DecalOrientationMode::TOWARDS_CENTER; + break; + default: + UNREACHABLE("Unexpected orientation mode"); + } + } + + if (optional_string("+Emissive:")) { + stuff_boolean(&effect.m_decalEmissive); + } + } + } + } + static void parseModularCurvesLifetime(ParticleEffect& effect) { effect.m_lifetime_curves.parse("$Particle Lifetime Curve:"); } @@ -386,6 +415,7 @@ namespace particle { parseLifetime(effect); parseLocalPositionScaling(effect); parseParentLocal(effect); + parseRenderAsDecal(effect); parseLightEmissionSettings(effect); //Spawner Settings diff --git a/code/particle/particle.cpp b/code/particle/particle.cpp index 7b1744bcf99..bff5c3c8c1a 100644 --- a/code/particle/particle.cpp +++ b/code/particle/particle.cpp @@ -10,6 +10,7 @@ #include "bmpman/bmpman.h" +#include "decals/decals.h" #include "particle/particle.h" #include "particle/ParticleManager.h" #include "particle/ParticleEffect.h" @@ -408,7 +409,7 @@ namespace particle * @param part The particle to render * @return @c true if the particle has been added to the rendering batch, @c false otherwise */ - static bool render_particle(particle* part) { + bool render_particle(particle* part) { // skip back-facing particles (ripped from fullneb code) // Wanderer - add support for attached particles vec3d p_pos; @@ -434,7 +435,64 @@ 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)))); - + + // figure out which frame we should be using + int framenum; + int cur_frame; + if (part->nframes > 1) { + if (source_effect.m_lifetime_curves.has_curve(ParticleEffect::ParticleLifetimeCurvesOutput::ANIM_STATE)) { + cur_frame = fl2i(i2fl(part->nframes - 1) * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::ANIM_STATE, curve_input)); + } + else { + framenum = bm_get_anim_frame(part->bitmap, part->age, part->max_life, part->looping); + cur_frame = part->reverse ? (part->nframes - framenum - 1) : framenum; + } + } + else + { + cur_frame = 0; + } + + framenum = part->bitmap; + Assert( (cur_frame < part->nframes) || (part->nframes == 0 && cur_frame == 0) ); + + int actual_frame = cur_frame + framenum; + + if (source_effect.m_renderAsDecal) { + if (part->attached_objnum < 0 || Objects[part->attached_objnum].type != OBJ_SHIP) { + return false; + } + + float radius = part->radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::RADIUS_MULT, curve_input); + + decals::Decal decalInfo; + + if (source_effect.m_decalEmissive) { + decalInfo.definition_handle = std::tuple(-1, actual_frame, -1); + } else { + decalInfo.definition_handle = std::tuple(actual_frame, -1, -1); + } + + decalInfo.object = &Objects[part->attached_objnum]; + decalInfo.submodel = -1; + decalInfo.creation_time = f2fl(Missiontime); + decalInfo.lifetime = 1.0f; + decalInfo.position = part->pos; + decalInfo.scale = {{{ radius, radius, radius }}}; + + switch (source_effect.m_decalOrientationMode) { + case ParticleEffect::DecalOrientationMode::TOWARDS_CENTER: + vm_vector_2_matrix(&decalInfo.orientation, &part->pos, nullptr, nullptr); + break; + default: + decalInfo.orientation = vmd_identity_matrix; + break; + } + + decals::addSingleFrameDecal(std::move(decalInfo)); + return false; + } + vec3d p1 = vmd_x_vector; if (part_has_length) { @@ -480,36 +538,15 @@ namespace particle g3_transfer_vertex(&pos, &p_pos); - // figure out which frame we should be using - int framenum; - int cur_frame; - if (part->nframes > 1) { - if (source_effect.m_lifetime_curves.has_curve(ParticleEffect::ParticleLifetimeCurvesOutput::ANIM_STATE)) { - cur_frame = fl2i(i2fl(part->nframes - 1) * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::ANIM_STATE, curve_input)); - } - else { - framenum = bm_get_anim_frame(part->bitmap, part->age, part->max_life, part->looping); - cur_frame = part->reverse ? (part->nframes - framenum - 1) : framenum; - } - } - else - { - cur_frame = 0; - } - - framenum = part->bitmap; - - Assert( (cur_frame < part->nframes) || (part->nframes == 0 && cur_frame == 0) ); - float radius = part->radius * source_effect.m_lifetime_curves.get_output(ParticleEffect::ParticleLifetimeCurvesOutput::RADIUS_MULT, curve_input); if (part_has_length) { vec3d p0 = p_pos; - batching_add_laser(framenum + cur_frame, &p0, radius, &p1, radius); + batching_add_laser(actual_frame, &p0, radius, &p1, radius); } else { // it will subtract Physics_viewer_bank, so without the flag we counter that and make it screen-aligned again - batching_add_volume_bitmap_rotated(framenum + cur_frame, &pos, part->use_angle ? part->angle : Physics_viewer_bank, radius, alpha); + batching_add_volume_bitmap_rotated(actual_frame, &pos, part->use_angle ? part->angle : Physics_viewer_bank, radius, alpha); } return true;