From a74849b3e6db6aac34e1aeb21a138124e68204bd Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sat, 7 Sep 2024 23:37:24 -0400 Subject: [PATCH 1/6] electrical arc upgrade, stage 1: refactor data structures --- code/debris/debris.cpp | 62 +++++++++++----- code/debris/debris.h | 9 ++- code/model/model.h | 41 +++++----- code/model/modelread.cpp | 15 ++-- code/model/modelrender.cpp | 16 ++-- code/object/object.cpp | 24 +++--- code/scripting/api/objs/ship.cpp | 35 ++++----- code/ship/ship.cpp | 44 ++++++----- code/ship/ship.h | 20 ++--- code/ship/shipfx.cpp | 124 ++++++++++++++++--------------- 10 files changed, 214 insertions(+), 176 deletions(-) diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index 64320568b9c..665fdee206b 100644 --- a/code/debris/debris.cpp +++ b/code/debris/debris.cpp @@ -56,6 +56,8 @@ int Debris_num_submodels = 0; #define DEBRIS_INDEX(dp) (int)(dp-Debris.data()) +// Find the first available arc slot. +debris_electrical_arc *debris_find_electrical_arc_slot(debris *db); /** * Start the sequence of a piece of debris writhing in unholy agony!!! @@ -262,36 +264,36 @@ void debris_process_post(object * obj, float frame_time) // Create the spark effects for (int i=0; iarc_timestamp[i].isValid() ) { + auto arc = &db->electrical_arcs[i]; + if ( !arc->timestamp.isValid() ) { - db->arc_timestamp[i] = _timestamp(lifetime); // live up to a second + arc->timestamp = _timestamp(lifetime); // live up to a second switch( n ) { case 0: - db->arc_pts[i][0] = v1; - db->arc_pts[i][1] = v2; + arc->endpoint_1 = v1; + arc->endpoint_2 = v2; break; case 1: - db->arc_pts[i][0] = v2; - db->arc_pts[i][1] = v3; + arc->endpoint_1 = v2; + arc->endpoint_2 = v3; break; case 2: - db->arc_pts[i][0] = v2; - db->arc_pts[i][1] = v4; + arc->endpoint_1 = v2; + arc->endpoint_2 = v4; break; default: Int3(); } - + n++; if ( n == n_arcs ) break; // Don't need to create anymore } } - // rotate v2 out of local coordinates into world. // Use v2 since it is used in every bolt. See above switch(). vec3d snd_pos; @@ -318,16 +320,20 @@ void debris_process_post(object * obj, float frame_time) } } - for (int i=0; iarc_timestamp[i].isValid() ) { - if ( timestamp_elapsed( db->arc_timestamp[i] ) ) { + for (auto &arc: db->electrical_arcs) { + if (arc.timestamp.isValid()) { + if (timestamp_elapsed(arc.timestamp)) { // Kill off the spark - db->arc_timestamp[i] = TIMESTAMP::invalid(); + arc.timestamp = TIMESTAMP::invalid(); } else { // Maybe move a vertex.... 20% of the time maybe? int mr = Random::next(); if ( mr < Random::MAX_VALUE/5 ) { - db->arc_pts[i][mr % 2] = submodel_get_random_point(db->model_num, db->submodel_num); + auto pt = submodel_get_random_point(db->model_num, db->submodel_num); + if (mr % 2 == 0) + arc.endpoint_1 = pt; + else + arc.endpoint_2 = pt; } } } @@ -578,7 +584,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ db->damage_mult = 1.0f; for (int i=0; iarc_timestamp[i] = TIMESTAMP::invalid(); + db->electrical_arcs[i].timestamp = TIMESTAMP::invalid(); } if ( db->is_hull ) { @@ -1145,7 +1151,7 @@ void calc_debris_physics_properties( physics_info *pi, vec3d *mins, vec3d *maxs, */ void debris_render(object * obj, model_draw_list *scene) { - int i, num, swapped; + int num, swapped; debris *db; swapped = -1; @@ -1180,9 +1186,9 @@ void debris_render(object * obj, model_draw_list *scene) // Only render electrical arcs if within 500m of the eye (for a 10m piece) if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) { - for (i=0; iarc_timestamp[i].isValid() ) { - model_instance_add_arc( pm, pmi, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_DAMAGED ); + for (auto &arc: db->electrical_arcs) { + if ( arc.timestamp.isValid() ) { + model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, MARC_TYPE_DAMAGED ); } } } @@ -1232,3 +1238,19 @@ void create_generic_debris(object* ship_objp, vec3d* pos, float min_num_debris, debris_create(ship_objp, model_num, -1, &create_pos, pos, 0, speed_mult); } } + +debris_electrical_arc *debris_find_electrical_arc_slot(debris *db) +{ + size_t i = 0; + for (auto& ii : db->electrical_arcs) + { + if (!ii.timestamp.isValid()) + break; + i++; + } + + if (i == MAX_DEBRIS_ARCS) + return nullptr; + + return &db->electrical_arcs[i]; +} diff --git a/code/debris/debris.h b/code/debris/debris.h index 7753fb7077b..ab03ed133f8 100644 --- a/code/debris/debris.h +++ b/code/debris/debris.h @@ -30,6 +30,12 @@ FLAG_LIST(Debris_Flags) { NUM_VALUES }; +struct debris_electrical_arc +{ + vec3d endpoint_1; + vec3d endpoint_2; + TIMESTAMP timestamp; // When this times out, the spark goes away. Invalid is not used +}; typedef struct debris { flagset flags; // See DEBRIS_??? defines @@ -50,8 +56,7 @@ typedef struct debris { TIMESTAMP sound_delay; // timestamp to signal when sound should start fix time_started; // time when debris was created - vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc - TIMESTAMP arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. Invalid is not used + debris_electrical_arc electrical_arcs[MAX_DEBRIS_ARCS]; int arc_frequency; // Starts at 1000, gets bigger int parent_alt_name; diff --git a/code/model/model.h b/code/model/model.h index 9474e5a0f2e..91835f913ae 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -83,6 +83,23 @@ extern const char *Subsystem_types[SUBSYSTEM_MAX]; #define MAX_SPLIT_PLANE 5 // number of artist specified split planes (used in big ship explosions) +// Electrical Arc Effect Info +// Sets a spark for this submodel between vertex v1 and v2 +struct electrical_arc +{ + color primary_color_1; + color primary_color_2; + color secondary_color; + float width; // only used for MARC_TYPE_SHIP and MARC_TYPE_SCRIPTED + vec3d endpoint_1; + vec3d endpoint_2; + ubyte type; // see MARC_TYPE_* defines +}; + +struct model_electrical_arc : electrical_arc +{ +}; + // Data specific to a particular instance of a submodel. struct submodel_instance { @@ -114,30 +131,16 @@ struct submodel_instance vec3d canonical_offset = vmd_zero_vector; vec3d canonical_prev_offset = vmd_zero_vector; - // --- these fields used to be in bsp_info --- - - // Electrical Arc Effect Info - // Sets a spark for this submodel between vertex v1 and v2 - int num_arcs = 0; // See model_add_arc for more info - color arc_primary_color_1[MAX_ARC_EFFECTS]; - color arc_primary_color_2[MAX_ARC_EFFECTS]; - color arc_secondary_color[MAX_ARC_EFFECTS]; - float arc_width[MAX_ARC_EFFECTS]; // only used for MARC_TYPE_SHIP and MARC_TYPE_SCRIPTED - vec3d arc_pts[MAX_ARC_EFFECTS][2]; - ubyte arc_type[MAX_ARC_EFFECTS]; // see MARC_TYPE_* defines + int num_arcs = 0; // See model_add_arc for more info + model_electrical_arc electrical_arcs[MAX_ARC_EFFECTS]; //SMI-Specific movement axis. Only valid in MOVEMENT_TYPE_TRIGGERED. - vec3d rotation_axis; - vec3d translation_axis; + vec3d rotation_axis = vmd_zero_vector; + vec3d translation_axis = vmd_zero_vector; submodel_instance() { - memset(&arc_pts, 0, MAX_ARC_EFFECTS * 2 * sizeof(vec3d)); - memset(&arc_primary_color_1, 0, MAX_ARC_EFFECTS * sizeof(color)); - memset(&arc_primary_color_2, 0, MAX_ARC_EFFECTS * sizeof(color)); - memset(&arc_secondary_color, 0, MAX_ARC_EFFECTS * sizeof(color)); - memset(&arc_type, 0, MAX_ARC_EFFECTS * sizeof(ubyte)); - memset(&arc_width, 0, MAX_ARC_EFFECTS * sizeof(float)); + memset(electrical_arcs, 0, MAX_ARC_EFFECTS * sizeof(model_electrical_arc)); } }; diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index d96bd05ede3..21cb51bbc22 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -5180,15 +5180,16 @@ void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_mode auto smi = &pmi->submodel[sub_model_num]; if ( smi->num_arcs < MAX_ARC_EFFECTS ) { - smi->arc_type[smi->num_arcs] = (ubyte)arc_type; - smi->arc_pts[smi->num_arcs][0] = *v1; - smi->arc_pts[smi->num_arcs][1] = *v2; + auto &new_arc = smi->electrical_arcs[smi->num_arcs]; + new_arc.type = static_cast(arc_type); + new_arc.endpoint_1 = *v1; + new_arc.endpoint_2 = *v2; if (arc_type == MARC_TYPE_SHIP || arc_type == MARC_TYPE_SCRIPTED) { - smi->arc_primary_color_1[smi->num_arcs] = *primary_color_1; - smi->arc_primary_color_2[smi->num_arcs] = *primary_color_2; - smi->arc_secondary_color[smi->num_arcs] = *secondary_color; - smi->arc_width[smi->num_arcs] = width; + new_arc.primary_color_1 = *primary_color_1; + new_arc.primary_color_2 = *primary_color_2; + new_arc.secondary_color = *secondary_color; + new_arc.width = width; } smi->num_arcs++; diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 2b60a60b2b3..dbcdf024622 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -826,8 +826,10 @@ void model_render_add_lightning(model_draw_list *scene, const model_render_param } for ( i = 0; i < smi->num_arcs; i++ ) { + auto &arc = smi->electrical_arcs[i]; + // pick a color based upon arc type - switch ( smi->arc_type[i] ) { + switch ( arc.type ) { // "normal", FreeSpace 1 style arcs case MARC_TYPE_DAMAGED: if ( Random::flip_coin() ) { @@ -853,14 +855,14 @@ void model_render_add_lightning(model_draw_list *scene, const model_render_param case MARC_TYPE_SCRIPTED: case MARC_TYPE_SHIP: if ( Random::flip_coin() ) { - primary = smi->arc_primary_color_1[i]; + primary = arc.primary_color_1; } else { - primary = smi->arc_primary_color_2[i]; + primary = arc.primary_color_2; } - secondary = smi->arc_secondary_color[i]; + secondary = arc.secondary_color; - width = smi->arc_width[i]; + width = arc.width; break; @@ -887,12 +889,12 @@ void model_render_add_lightning(model_draw_list *scene, const model_render_param break; default: - UNREACHABLE("Unknown arc type of %d found in model_render_add_lightning(), please contact an SCP coder!", smi->arc_type[i]); + UNREACHABLE("Unknown arc type of %d found in model_render_add_lightning(), please contact an SCP coder!", arc.type); } // render the actual arc segment if (width > 0.0f) - scene->add_arc(&smi->arc_pts[i][0], &smi->arc_pts[i][1], &primary, &secondary, width); + scene->add_arc(&arc.endpoint_1, &arc.endpoint_2, &primary, &secondary, width); } } diff --git a/code/object/object.cpp b/code/object/object.cpp index 7f9a138bb28..2d4b3cbb546 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -1298,18 +1298,16 @@ void obj_move_all_post(object *objp, float frametime) // Make any electrical arcs on ships cast light if (Arc_light) { if ( (Detail.lighting > 3) && (objp != Viewer_obj) ) { - int i; - ship *shipp; - shipp = &Ships[objp->instance]; + auto shipp = &Ships[objp->instance]; - for (i=0; iarc_timestamp[i].isValid() ) { + for (auto &arc: shipp->electrical_arcs) { + if ( arc.timestamp.isValid() ) { // Move arc endpoints into world coordinates vec3d tmp1, tmp2; - vm_vec_unrotate(&tmp1,&shipp->arc_pts[i][0],&objp->orient); + vm_vec_unrotate(&tmp1,&arc.endpoint_1,&objp->orient); vm_vec_add2(&tmp1,&objp->pos); - vm_vec_unrotate(&tmp2,&shipp->arc_pts[i][1],&objp->orient); + vm_vec_unrotate(&tmp2,&arc.endpoint_2,&objp->orient); vm_vec_add2(&tmp2,&objp->pos); light_add_point( &tmp1, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f); @@ -1395,19 +1393,17 @@ void obj_move_all_post(object *objp, float frametime) // Make any electrical arcs on debris cast light if (Arc_light) { if ( Detail.lighting > 3 ) { - int i; - debris *db; - db = &Debris[objp->instance]; + auto db = &Debris[objp->instance]; if (db->arc_frequency > 0) { - for (i=0; iarc_timestamp[i].isValid() ) { + for (auto &arc: db->electrical_arcs) { + if ( arc.timestamp.isValid() ) { // Move arc endpoints into world coordinates vec3d tmp1, tmp2; - vm_vec_unrotate(&tmp1,&db->arc_pts[i][0],&objp->orient); + vm_vec_unrotate(&tmp1,&arc.endpoint_1,&objp->orient); vm_vec_add2(&tmp1,&objp->pos); - vm_vec_unrotate(&tmp2,&db->arc_pts[i][1],&objp->orient); + vm_vec_unrotate(&tmp2,&arc.endpoint_2,&objp->orient); vm_vec_add2(&tmp2,&objp->pos); light_add_point( &tmp1, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f ); diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 78213008553..2e4f56d53c3 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -2695,24 +2695,24 @@ ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number auto shipp = &Ships[objh->objp()->instance]; // spawn the arc in the first unused slot - for (int i = 0; i < MAX_ARC_EFFECTS; i++) { - if (!shipp->arc_timestamp[i].isValid()) { - shipp->arc_timestamp[i] = _timestamp(fl2i(duration * MILLISECONDS_PER_SECOND)); + auto arc = ship_find_electrical_arc_slot(shipp); + if (arc) + { + arc->timestamp = _timestamp(fl2i(duration * MILLISECONDS_PER_SECOND)); - shipp->arc_pts[i][0] = *v1; - shipp->arc_pts[i][1] = *v2; + arc->endpoint_1 = *v1; + arc->endpoint_2 = *v2; - //Set the arc colors - shipp->arc_primary_color_1[i] = Arc_color_damage_p1; - shipp->arc_primary_color_2[i] = Arc_color_damage_p2; - shipp->arc_secondary_color[i] = Arc_color_damage_s1; + //Set the arc colors + arc->primary_color_1 = Arc_color_damage_p1; + arc->primary_color_2 = Arc_color_damage_p2; + arc->secondary_color = Arc_color_damage_s1; - shipp->arc_type[i] = MARC_TYPE_SCRIPTED; + arc->type = MARC_TYPE_SCRIPTED; - shipp->arc_width[i] = width; + arc->width = width; - return ade_set_args(L, "i", i + 1); // FS2 -> Lua - } + return ade_set_args(L, "i", static_cast(arc - shipp->electrical_arcs) + 1); // FS2 -> Lua } return ade_set_args(L, "i", 0); @@ -2737,7 +2737,7 @@ ADE_FUNC(DeleteElectricArc, l_Ship, "number index", index--; // Lua -> FS2 if (index >= 0 && index < MAX_ARC_EFFECTS) { - shipp->arc_timestamp[index] = TIMESTAMP::invalid(); + shipp->electrical_arcs[index].timestamp = TIMESTAMP::invalid(); } return ADE_RETURN_NIL; @@ -2766,11 +2766,12 @@ ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector sec index--; // Lua -> FS2 if (index >= 0 && index < MAX_ARC_EFFECTS) { - shipp->arc_pts[index][0] = *v1; - shipp->arc_pts[index][1] = *v2; + auto &arc = shipp->electrical_arcs[index]; + arc.endpoint_1 = *v1; + arc.endpoint_2 = *v2; if (args == 5) - shipp->arc_width[index] = width; + arc.width = width; } return ADE_RETURN_NIL; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 7db1e21c5d8..c989d7f53ea 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -6876,12 +6876,10 @@ void ship::clear() for (int i = 0; i < NUM_SUB_EXPL_HANDLES; i++) sub_expl_sound_handle[i] = sound_handle::invalid(); - memset(&arc_pts, 0, MAX_ARC_EFFECTS * 2 * sizeof(vec3d)); + memset(&electrical_arcs, 0, MAX_ARC_EFFECTS * sizeof(ship_electrical_arc)); for (int i = 0; i < MAX_ARC_EFFECTS; i++) - arc_timestamp[i] = TIMESTAMP::invalid(); - memset(&arc_type, 0, MAX_ARC_EFFECTS * sizeof(ubyte)); - memset(&arc_width, 0, MAX_ARC_EFFECTS * sizeof(float)); - arc_next_time = timestamp(-1); + electrical_arcs[i].timestamp = TIMESTAMP::invalid(); + arc_next_time = TIMESTAMP::invalid(); emp_intensity = -1.0f; emp_decr = 0.0f; @@ -7332,9 +7330,8 @@ static void ship_set(int ship_index, int objnum, int ship_type) shipp->max_shield_regen_per_second = sip->max_shield_regen_per_second; shipp->max_weapon_regen_per_second = sip->max_weapon_regen_per_second; - for (int i = 0; i < (int)sip->ship_passive_arcs.size(); i++) - shipp->passive_arc_next_times.push_back(timestamp(0)); - + for (size_t i = 0; i < sip->ship_passive_arcs.size(); i++) + shipp->passive_arc_next_times.push_back(TIMESTAMP::immediate()); } /** @@ -20972,18 +20969,9 @@ void ship_render(object* obj, model_draw_list* scene) // Only render electrical arcs if within 500m of the eye (for a 10m piece) if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f && !Rendering_to_shadow_map ) { - for ( int i = 0; i < MAX_ARC_EFFECTS; i++ ) { - if ( shipp->arc_timestamp[i].isValid() ) { - model_instance_add_arc(pm, - pmi, - -1, - &shipp->arc_pts[i][0], - &shipp->arc_pts[i][1], - shipp->arc_type[i], - &shipp->arc_primary_color_1[i], - &shipp->arc_primary_color_2[i], - &shipp->arc_secondary_color[i], - shipp->arc_width[i]); + for (auto &arc: shipp->electrical_arcs) { + if (arc.timestamp.isValid()) { + model_instance_add_arc(pm, pmi, -1, &arc.endpoint_1, &arc.endpoint_2, arc.type, &arc.primary_color_1, &arc.primary_color_2, &arc.secondary_color, arc.width); } } } @@ -21421,3 +21409,19 @@ int ship_check_visibility(const ship* viewed, ship* viewer) return ship_is_visible; } + +ship_electrical_arc *ship_find_electrical_arc_slot(ship *shipp) +{ + size_t i = 0; + for (auto &ii : shipp->electrical_arcs) + { + if (!ii.timestamp.isValid()) + break; + i++; + } + + if (i == MAX_ARC_EFFECTS) + return nullptr; + + return &shipp->electrical_arcs[i]; +} diff --git a/code/ship/ship.h b/code/ship/ship.h index 315701ffce8..cb16a4c1d64 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -536,6 +536,11 @@ struct reload_pct SCP_vector _buffer; }; +struct ship_electrical_arc : electrical_arc +{ + TIMESTAMP timestamp; // When this times out, the spark goes away. Invalid is not used +}; + // NOTE: Can't be treated as a struct anymore, since it has STL data structures in its object tree! class ship { @@ -723,15 +728,9 @@ class ship std::array sub_expl_sound_handle; // Stuff for showing electrical arcs on damaged ships - vec3d arc_pts[MAX_ARC_EFFECTS][2]; // The endpoints of each arc - TIMESTAMP arc_timestamp[MAX_ARC_EFFECTS]; // When this times out, the spark goes away. Invalid is not used - ubyte arc_type[MAX_ARC_EFFECTS]; // see MARC_TYPE_* defines in model.h - color arc_primary_color_1[MAX_ARC_EFFECTS]; - color arc_primary_color_2[MAX_ARC_EFFECTS]; - color arc_secondary_color[MAX_ARC_EFFECTS]; - float arc_width[MAX_ARC_EFFECTS]; - int arc_next_time; // When the next damage/emp arc will be created. - SCP_vector passive_arc_next_times; // When the next passive ship arc will be created. + ship_electrical_arc electrical_arcs[MAX_ARC_EFFECTS]; + TIMESTAMP arc_next_time; // When the next damage/emp arc will be created. + SCP_vector passive_arc_next_times; // When the next passive ship arc will be created. // emp missile stuff float emp_intensity; // <= 0.0f if no emp effect present @@ -2127,4 +2126,7 @@ bool ship_secondary_has_ammo(ship_weapon* swp, int bank_index); // Used to check if one ship can see the other on radar int ship_check_visibility(const ship* viewed, ship* viewer); +// Find the first available arc slot. +ship_electrical_arc *ship_find_electrical_arc_slot(ship *shipp); + #endif diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index 539414385ed..e7e40f752bd 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -2167,47 +2167,45 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) if (submodel_1 >= 0 && submodel_2 >= 0) { // spawn the arc in the first unused slot - for (int j = 0; j < MAX_ARC_EFFECTS; j++) { - if (!shipp->arc_timestamp[j].isValid()) { - shipp->arc_timestamp[j] = _timestamp(fl2i(arc_info->duration * MILLISECONDS_PER_SECOND)); - - vec3d v1, v2, offset; - // subtract away the submodel's offset, since these positions were in frame of ref of the whole ship - model_find_submodel_offset(&offset, pm, submodel_1); - v1 = arc_info->pos.first - offset; - model_find_submodel_offset(&offset, pm, submodel_2); - v2 = arc_info->pos.second - offset; - - model_instance_local_to_global_point(&v1, &v1, shipp->model_instance_num, submodel_1, &vmd_identity_matrix, &vmd_zero_vector); - shipp->arc_pts[j][0] = v1; - model_instance_local_to_global_point(&v2, &v2, shipp->model_instance_num, submodel_2, &vmd_identity_matrix, &vmd_zero_vector); - shipp->arc_pts[j][1] = v2; - - //Set the arc colors - shipp->arc_primary_color_1[j] = arc_info->primary_color_1; - shipp->arc_primary_color_2[j] = arc_info->primary_color_2; - shipp->arc_secondary_color[j] = arc_info->secondary_color; - - shipp->arc_type[j] = MARC_TYPE_SHIP; - - if (arc_info->width > 0.0f) { - shipp->arc_width[j] = arc_info->width; - } else { - // same width as other arc types in model_render_add_lightning - // try and scale the size a bit so that it looks equally well on smaller vessels - shipp->arc_width[j] = Arc_width_default_damage; - if (pm->rad < Arc_width_no_multiply_over_radius_damage) { - shipp->arc_width[j] *= (pm->rad * Arc_width_radius_multiplier_damage); - - if (shipp->arc_width[j] < Arc_width_minimum_damage) { - shipp->arc_width[j] = Arc_width_minimum_damage; - } + auto arc = ship_find_electrical_arc_slot(shipp); + if (arc) { + arc->timestamp = _timestamp(fl2i(arc_info->duration * MILLISECONDS_PER_SECOND)); + + vec3d v1, v2, offset; + // subtract away the submodel's offset, since these positions were in frame of ref of the whole ship + model_find_submodel_offset(&offset, pm, submodel_1); + v1 = arc_info->pos.first - offset; + model_find_submodel_offset(&offset, pm, submodel_2); + v2 = arc_info->pos.second - offset; + + model_instance_local_to_global_point(&v1, &v1, shipp->model_instance_num, submodel_1, &vmd_identity_matrix, &vmd_zero_vector); + arc->endpoint_1 = v1; + model_instance_local_to_global_point(&v2, &v2, shipp->model_instance_num, submodel_2, &vmd_identity_matrix, &vmd_zero_vector); + arc->endpoint_2 = v2; + + //Set the arc colors + arc->primary_color_1 = arc_info->primary_color_1; + arc->primary_color_2 = arc_info->primary_color_2; + arc->secondary_color = arc_info->secondary_color; + + arc->type = MARC_TYPE_SHIP; + + if (arc_info->width > 0.0f) { + arc->width = arc_info->width; + } else { + // same width as other arc types in model_render_add_lightning + // try and scale the size a bit so that it looks equally well on smaller vessels + arc->width = Arc_width_default_damage; + if (pm->rad < Arc_width_no_multiply_over_radius_damage) { + arc->width *= (pm->rad * Arc_width_radius_multiplier_damage); + + if (arc->width < Arc_width_minimum_damage) { + arc->width = Arc_width_minimum_damage; } } - - shipp->passive_arc_next_times[passive_arc_info_idx] = timestamp((int)(arc_info->frequency * 1000)); - break; } + + shipp->passive_arc_next_times[passive_arc_info_idx] = _timestamp(fl2i(arc_info->frequency * MILLISECONDS_PER_SECOND)); } } } @@ -2247,9 +2245,9 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } // Kill off old sparks - for (auto &arc_stamp : shipp->arc_timestamp) { - if (arc_stamp.isValid() && timestamp_elapsed(arc_stamp)) { - arc_stamp = TIMESTAMP::invalid(); + for (auto &arc: shipp->electrical_arcs) { + if (arc.timestamp.isValid() && timestamp_elapsed(arc.timestamp)) { + arc.timestamp = TIMESTAMP::invalid(); } } @@ -2258,7 +2256,7 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) return; } - if (!timestamp_valid(shipp->arc_next_time)) { + if (!shipp->arc_next_time.isValid()) { // start the next fireball up in the next 10 seconds or so... int freq; @@ -2272,12 +2270,12 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } // set the next arc time - shipp->arc_next_time = timestamp_rand(freq*2,freq*4); + shipp->arc_next_time = _timestamp_rand(freq*2,freq*4); } if ( timestamp_elapsed(shipp->arc_next_time) ) { - shipp->arc_next_time = timestamp(-1); // invalid, so it gets restarted next frame + shipp->arc_next_time = TIMESTAMP::invalid(); // invalid, so it gets restarted next frame int n, n_arcs = Random::next(1, 3); @@ -2327,22 +2325,23 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) // Create the arc effects int num_damage_arcs = 0; for (int i=0; iarc_timestamp[i].isValid() ) { - shipp->arc_timestamp[i] = _timestamp(lifetime); // live up to a second + auto arc = &shipp->electrical_arcs[i]; + if ( !arc->timestamp.isValid() ) { + arc->timestamp = _timestamp(lifetime); // live up to a second switch( n ) { case 0: - shipp->arc_pts[i][0] = v1; - shipp->arc_pts[i][1] = v2; + arc->endpoint_1 = v1; + arc->endpoint_2 = v2; break; case 1: - shipp->arc_pts[i][0] = v2; - shipp->arc_pts[i][1] = v3; + arc->endpoint_1 = v2; + arc->endpoint_2 = v3; break; case 2: - shipp->arc_pts[i][0] = v2; - shipp->arc_pts[i][1] = v4; + arc->endpoint_1 = v2; + arc->endpoint_2 = v4; break; default: @@ -2351,16 +2350,16 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) // determine what kind of arc to create if((shipp->emp_intensity > 0.0f) || (disrupted_arc)){ - shipp->arc_type[i] = MARC_TYPE_EMP; + arc->type = MARC_TYPE_EMP; } else { - shipp->arc_type[i] = MARC_TYPE_DAMAGED; + arc->type = MARC_TYPE_DAMAGED; } n++; num_damage_arcs++; if ( n == n_arcs || num_damage_arcs >= MAX_SHIP_DAMAGE_ARCS) break; // Don't need to create anymore - } else if (shipp->arc_type[i] == MARC_TYPE_DAMAGED || shipp->arc_type[i] == MARC_TYPE_EMP) { + } else if (arc->type == MARC_TYPE_DAMAGED || arc->type == MARC_TYPE_EMP) { num_damage_arcs ++; } @@ -2391,10 +2390,10 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } // maybe move arc points around - for (int i=0; ielectrical_arcs) { //Only move arc points around for Damaged or EMP type arcs - if (((shipp->arc_type[i] == MARC_TYPE_DAMAGED) || (shipp->arc_type[i] == MARC_TYPE_EMP)) && shipp->arc_timestamp[i].isValid()) { - if ( !timestamp_elapsed( shipp->arc_timestamp[i] ) ) { + if (((arc.type == MARC_TYPE_DAMAGED) || (arc.type == MARC_TYPE_EMP)) && arc.timestamp.isValid()) { + if (!timestamp_elapsed(arc.timestamp)) { // Maybe move a vertex.... 20% of the time maybe? int mr = Random::next(); if ( mr < Random::MAX_VALUE/5 ) { @@ -2403,9 +2402,9 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) vec3d static_one; if ( mr % 2 ) { - static_one = shipp->arc_pts[i][0]; + static_one = arc.endpoint_1; } else { - static_one = shipp->arc_pts[i][1]; + static_one = arc.endpoint_2; } // For large ships, cap the length to be 25% of max radius @@ -2423,7 +2422,10 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } } - shipp->arc_pts[i][mr % 2] = v1; + if (mr % 2 == 0) + arc.endpoint_1 = v1; + else + arc.endpoint_2 = v1; } } } From 76f9328bf63d73c05254e4efdf3c970968dbac8b Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 9 Sep 2024 00:22:36 -0400 Subject: [PATCH 2/6] fix scope of ship arc sound generation The code for generating spark sounds was incorrectly copied from debris to ships, dating all the way back to retail. The sound should be played once per generation event, not once for every electrical arc active on the ship. This fixes a bug that caused overlapping electrical arc sounds. --- code/debris/debris.cpp | 3 +-- code/ship/shipfx.cpp | 49 +++++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index 665fdee206b..255cedaf480 100644 --- a/code/debris/debris.cpp +++ b/code/debris/debris.cpp @@ -278,14 +278,13 @@ void debris_process_post(object * obj, float frame_time) arc->endpoint_1 = v2; arc->endpoint_2 = v3; break; - case 2: arc->endpoint_1 = v2; arc->endpoint_2 = v4; break; default: - Int3(); + UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n); } n++; diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index e7e40f752bd..e0190d2d043 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -2338,14 +2338,13 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) arc->endpoint_1 = v2; arc->endpoint_2 = v3; break; - case 2: arc->endpoint_1 = v2; arc->endpoint_2 = v4; break; default: - Int3(); + UNREACHABLE("Unhandled case %d for electrical arc creation in shipfx_do_lightning_arcs_frame()!", n); } // determine what kind of arc to create @@ -2362,30 +2361,30 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } else if (arc->type == MARC_TYPE_DAMAGED || arc->type == MARC_TYPE_EMP) { num_damage_arcs ++; } + } - // rotate v2 out of local coordinates into world. - // Use v2 since it is used in every bolt. See above switch(). - vec3d snd_pos; - vm_vec_unrotate(&snd_pos, &v2, &obj->orient); - vm_vec_add2(&snd_pos, &obj->pos ); - - //Play a sound effect - if ( lifetime > 750 ) { - // 1.00 second effect - snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_05), &snd_pos, &View_position, obj->radius ); - } else if ( lifetime > 500 ) { - // 0.75 second effect - snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_04), &snd_pos, &View_position, obj->radius ); - } else if ( lifetime > 250 ) { - // 0.50 second effect - snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_03), &snd_pos, &View_position, obj->radius ); - } else if ( lifetime > 100 ) { - // 0.25 second effect - snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_02), &snd_pos, &View_position, obj->radius ); - } else { - // 0.10 second effect - snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_01), &snd_pos, &View_position, obj->radius ); - } + // rotate v2 out of local coordinates into world. + // Use v2 since it is used in every bolt. See above switch(). + vec3d snd_pos; + vm_vec_unrotate(&snd_pos, &v2, &obj->orient); + vm_vec_add2(&snd_pos, &obj->pos ); + + //Play a sound effect + if ( lifetime > 750 ) { + // 1.00 second effect + snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_05), &snd_pos, &View_position, obj->radius ); + } else if ( lifetime > 500 ) { + // 0.75 second effect + snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_04), &snd_pos, &View_position, obj->radius ); + } else if ( lifetime > 250 ) { + // 0.50 second effect + snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_03), &snd_pos, &View_position, obj->radius ); + } else if ( lifetime > 100 ) { + // 0.25 second effect + snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_02), &snd_pos, &View_position, obj->radius ); + } else { + // 0.10 second effect + snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_01), &snd_pos, &View_position, obj->radius ); } } From 84f263ab83fdbc6f4ff6fdc66d68da63feb16667 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 9 Sep 2024 00:28:41 -0400 Subject: [PATCH 3/6] rename debris spark timestamps, per Asteroth --- code/debris/debris.cpp | 14 +++++++------- code/debris/debris.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index 255cedaf480..d0ffbba7da1 100644 --- a/code/debris/debris.cpp +++ b/code/debris/debris.cpp @@ -244,9 +244,9 @@ void debris_process_post(object * obj, float frame_time) return; // If arc_frequency <= 0, this piece has no arcs on it } - if ( !timestamp_elapsed(db->fire_timeout) && timestamp_elapsed(db->next_fireball)) { + if ( !timestamp_elapsed(db->arc_timeout) && timestamp_elapsed(db->arc_next_time)) { - db->next_fireball = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 ); + db->arc_next_time = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 ); db->arc_frequency += 100; if (db->is_hull) { @@ -576,7 +576,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ db->ship_info_index = parent_ship_class; db->team = team; db->ambient_sound = (sip == nullptr) ? gamesnd_id(-1) : sip->debris_ambient_sound; - db->fire_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false + db->arc_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false db->time_started = Missiontime; db->species = (sip == nullptr) ? -1 : sip->species; db->parent_alt_name = alt_type_index; @@ -597,7 +597,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ db->arc_frequency = 0; } - db->next_fireball = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later + db->arc_next_time = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later flagset default_flags; default_flags.set(Object::Object_Flags::Renders); @@ -647,12 +647,12 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ // limit the amount of time that fireballs appear // let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds. if (spark_timeout >= 0) { - db->fire_timeout = _timestamp(spark_timeout); + db->arc_timeout = _timestamp(spark_timeout); } else if (parent_objnum >= 0) { float t = 1000*Objects[parent_objnum].radius/3 + (fl2i(1000*3*Objects[parent_objnum].radius) == 0 ? 0 : Random::next(fl2i(1000*3*Objects[parent_objnum].radius))); - db->fire_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds + db->arc_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds } else { - db->fire_timeout = TIMESTAMP::immediate(); + db->arc_timeout = TIMESTAMP::immediate(); } if (parent_objnum >= 0 && Objects[parent_objnum].radius >= MIN_RADIUS_FOR_PERSISTENT_DEBRIS) { diff --git a/code/debris/debris.h b/code/debris/debris.h index ab03ed133f8..7d4fcd31113 100644 --- a/code/debris/debris.h +++ b/code/debris/debris.h @@ -49,10 +49,10 @@ typedef struct debris { int model_num; // What model this uses int model_instance_num; // What model instance this uses - needed for arcs int submodel_num; // What submodel this uses - TIMESTAMP next_fireball; // When to start a fireball + TIMESTAMP arc_next_time; // When the next damage/emp arc will be created. bool is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment int species; // What species this is from. -1 if don't care. - TIMESTAMP fire_timeout; // timestamp that holds time for fireballs to stop appearing + TIMESTAMP arc_timeout; // timestamp that holds time for arcs to stop appearing TIMESTAMP sound_delay; // timestamp to signal when sound should start fix time_started; // time when debris was created From cca46abe1a251a5e69275052ad5fc9f3da8cc806 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 9 Sep 2024 00:48:46 -0400 Subject: [PATCH 4/6] electrical arc upgrade, stage 2: make arrays dynamic --- code/debris/debris.cpp | 33 ++++++++++++++------------------ code/debris/debris.h | 4 ++-- code/model/model.h | 8 +------- code/model/modelinterp.cpp | 9 ++------- code/model/modelread.cpp | 26 ++++++++++++------------- code/model/modelrender.cpp | 31 ++++++++++++------------------ code/scripting/api/objs/ship.cpp | 10 +++++----- code/ship/ship.cpp | 14 ++++++++------ code/ship/ship.h | 6 +++--- code/ship/shipfx.cpp | 27 +++++++++++--------------- 10 files changed, 70 insertions(+), 98 deletions(-) diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index d0ffbba7da1..38aa6c55793 100644 --- a/code/debris/debris.cpp +++ b/code/debris/debris.cpp @@ -56,8 +56,8 @@ int Debris_num_submodels = 0; #define DEBRIS_INDEX(dp) (int)(dp-Debris.data()) -// Find the first available arc slot. -debris_electrical_arc *debris_find_electrical_arc_slot(debris *db); +// Find the first available arc slot. If none is available, and no_create is false, add one. +debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create); /** * Start the sequence of a piece of debris writhing in unholy agony!!! @@ -251,22 +251,19 @@ void debris_process_post(object * obj, float frame_time) if (db->is_hull) { - int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks + int n_arcs = Random::next(1, 3); // Create 1-3 sparks vec3d v1 = submodel_get_random_point(db->model_num, db->submodel_num); vec3d v2 = submodel_get_random_point(db->model_num, db->submodel_num); vec3d v3 = submodel_get_random_point(db->model_num, db->submodel_num); vec3d v4 = submodel_get_random_point(db->model_num, db->submodel_num); - n = 0; - int lifetime = Random::next(100, 1000); // Create the spark effects - for (int i=0; ielectrical_arcs[i]; - if ( !arc->timestamp.isValid() ) { - + for (int n = 0; n < n_arcs; n++) { + auto arc = debris_find_or_create_electrical_arc_slot(db, db->electrical_arcs.size() >= MAX_DEBRIS_ARCS); + if (arc) { arc->timestamp = _timestamp(lifetime); // live up to a second switch( n ) { @@ -286,10 +283,6 @@ void debris_process_post(object * obj, float frame_time) default: UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n); } - - n++; - if ( n == n_arcs ) - break; // Don't need to create anymore } } @@ -582,9 +575,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ db->parent_alt_name = alt_type_index; db->damage_mult = 1.0f; - for (int i=0; ielectrical_arcs[i].timestamp = TIMESTAMP::invalid(); - } + db->electrical_arcs.clear(); if ( db->is_hull ) { // Percent of debris pieces with arcs controlled via table (default 50%) @@ -1238,7 +1229,7 @@ void create_generic_debris(object* ship_objp, vec3d* pos, float min_num_debris, } } -debris_electrical_arc *debris_find_electrical_arc_slot(debris *db) +debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create) { size_t i = 0; for (auto& ii : db->electrical_arcs) @@ -1248,8 +1239,12 @@ debris_electrical_arc *debris_find_electrical_arc_slot(debris *db) i++; } - if (i == MAX_DEBRIS_ARCS) - return nullptr; + if (i == db->electrical_arcs.size()) + { + if (no_create) + return nullptr; + db->electrical_arcs.emplace_back(); + } return &db->electrical_arcs[i]; } diff --git a/code/debris/debris.h b/code/debris/debris.h index 7d4fcd31113..359dbe2aebc 100644 --- a/code/debris/debris.h +++ b/code/debris/debris.h @@ -20,7 +20,7 @@ class object; struct CFILE; class model_draw_list; -#define MAX_DEBRIS_ARCS 8 // Must be less than MAX_ARC_EFFECTS in model.h +#define MAX_DEBRIS_ARCS 8 FLAG_LIST(Debris_Flags) { Used, @@ -56,7 +56,7 @@ typedef struct debris { TIMESTAMP sound_delay; // timestamp to signal when sound should start fix time_started; // time when debris was created - debris_electrical_arc electrical_arcs[MAX_DEBRIS_ARCS]; + SCP_vector electrical_arcs; int arc_frequency; // Starts at 1000, gets bigger int parent_alt_name; diff --git a/code/model/model.h b/code/model/model.h index 91835f913ae..1b4e0d66b2d 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -131,17 +131,11 @@ struct submodel_instance vec3d canonical_offset = vmd_zero_vector; vec3d canonical_prev_offset = vmd_zero_vector; - int num_arcs = 0; // See model_add_arc for more info - model_electrical_arc electrical_arcs[MAX_ARC_EFFECTS]; + SCP_vector electrical_arcs; //SMI-Specific movement axis. Only valid in MOVEMENT_TYPE_TRIGGERED. vec3d rotation_axis = vmd_zero_vector; vec3d translation_axis = vmd_zero_vector; - - submodel_instance() - { - memset(electrical_arcs, 0, MAX_ARC_EFFECTS * sizeof(model_electrical_arc)); - } }; #define TM_BASE_TYPE 0 // the standard base map diff --git a/code/model/modelinterp.cpp b/code/model/modelinterp.cpp index a57dd743314..7a40fbfeff7 100644 --- a/code/model/modelinterp.cpp +++ b/code/model/modelinterp.cpp @@ -785,9 +785,7 @@ void model_draw_bay_paths_htl(int model_num) gr_set_cull(cull); } -static const int MAX_ARC_SEGMENT_POINTS = 50; -int Num_arc_segment_points = 0; -vec3d Arc_segment_points[MAX_ARC_SEGMENT_POINTS]; +SCP_vector Arc_segment_points; void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth ) { @@ -795,10 +793,7 @@ void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth ) const float scaler = 0.30f; if ( (d < scaler) || (depth > 4) ) { - // the real limit appears to be 33, so we should never hit this unless the code changes - Assert( Num_arc_segment_points < MAX_ARC_SEGMENT_POINTS ); - - memcpy( &Arc_segment_points[Num_arc_segment_points++], v2, sizeof(vec3d) ); + Arc_segment_points.push_back(*v2); } else { // divide in half vec3d tmp; diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index 21cb51bbc22..3bb1fe61bd3 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -5159,7 +5159,7 @@ void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi) Assert(pm->id == pmi->model_num); for (int i = 0; i < pm->n_models; ++i) { - pmi->submodel[i].num_arcs = 0; // Turn off any electric arcing effects + pmi->submodel[i].electrical_arcs.clear(); // Turn off any electric arcing effects } } @@ -5179,20 +5179,18 @@ void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_mode if ( sub_model_num >= pm->n_models ) return; auto smi = &pmi->submodel[sub_model_num]; - if ( smi->num_arcs < MAX_ARC_EFFECTS ) { - auto &new_arc = smi->electrical_arcs[smi->num_arcs]; - new_arc.type = static_cast(arc_type); - new_arc.endpoint_1 = *v1; - new_arc.endpoint_2 = *v2; - - if (arc_type == MARC_TYPE_SHIP || arc_type == MARC_TYPE_SCRIPTED) { - new_arc.primary_color_1 = *primary_color_1; - new_arc.primary_color_2 = *primary_color_2; - new_arc.secondary_color = *secondary_color; - new_arc.width = width; - } + smi->electrical_arcs.emplace_back(); + auto &new_arc = smi->electrical_arcs.back(); + + new_arc.type = static_cast(arc_type); + new_arc.endpoint_1 = *v1; + new_arc.endpoint_2 = *v2; - smi->num_arcs++; + if (arc_type == MARC_TYPE_SHIP || arc_type == MARC_TYPE_SCRIPTED) { + new_arc.primary_color_1 = *primary_color_1; + new_arc.primary_color_2 = *primary_color_2; + new_arc.secondary_color = *secondary_color; + new_arc.width = width; } } diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index dbcdf024622..601eb1cb0ac 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -40,9 +40,7 @@ extern int Model_polys; extern int tiling; extern float model_radius; -extern const int MAX_ARC_SEGMENT_POINTS; -extern int Num_arc_segment_points; -extern vec3d Arc_segment_points[]; +extern SCP_vector Arc_segment_points; extern bool Scene_framebuffer_in_frame; color Wireframe_color; @@ -810,12 +808,9 @@ model_draw_list::~model_draw_list() { void model_render_add_lightning(model_draw_list *scene, const model_render_params* interp, const polymodel *pm, const submodel_instance *smi ) { - int i; float width = 0.9f; color primary, secondary; - Assert( smi->num_arcs > 0 ); - if ( interp->get_model_flags() & MR_SHOW_OUTLINE_PRESET ) { return; } @@ -825,9 +820,7 @@ void model_render_add_lightning(model_draw_list *scene, const model_render_param return; } - for ( i = 0; i < smi->num_arcs; i++ ) { - auto &arc = smi->electrical_arcs[i]; - + for (auto &arc: smi->electrical_arcs) { // pick a color based upon arc type switch ( arc.type ) { // "normal", FreeSpace 1 style arcs @@ -1306,7 +1299,7 @@ void model_render_children_buffers(model_draw_list* scene, model_material *rende } } - if ( smi != nullptr && smi->num_arcs > 0 ) { + if ( smi != nullptr && !smi->electrical_arcs.empty() ) { model_render_add_lightning( scene, interp, pm, smi ); } @@ -1626,7 +1619,7 @@ void submodel_render_queue(const model_render_params *render_info, model_draw_li } } - if ( pmi && pmi->submodel[submodel_num].num_arcs > 0 ) { + if ( pmi && !pmi->submodel[submodel_num].electrical_arcs.empty() ) { model_render_add_lightning( scene, render_info, pm, &pmi->submodel[submodel_num] ); } @@ -2080,8 +2073,8 @@ void model_render_glow_points(const polymodel *pm, const polymodel_instance *pmi Assert( bank->points != nullptr ); int flick; - if (pmi != nullptr && pmi->submodel[pm->detail[0]].num_arcs > 0) { - flick = static_rand( timestamp() % 20 ) % (pmi->submodel[pm->detail[0]].num_arcs + j); //the more damage, the more arcs, the more likely the lights will fail + if (pmi != nullptr && !pmi->submodel[pm->detail[0]].electrical_arcs.empty()) { + flick = static_rand( timestamp() % 20 ) % (pmi->submodel[pm->detail[0]].electrical_arcs.size() + j); //the more damage, the more arcs, the more likely the lights will fail } else { flick = 1; } @@ -2516,10 +2509,10 @@ void model_render_insignias(const insignia_draw_data *insignia_data) void model_render_arc(const vec3d *v1, const vec3d *v2, const color *primary, const color *secondary, float arc_width) { - Num_arc_segment_points = 0; + Arc_segment_points.clear(); - // need need to add the first point - memcpy( &Arc_segment_points[Num_arc_segment_points++], v1, sizeof(vec3d) ); + // need to add the first point + Arc_segment_points.push_back(*v1); // this should fill in all of the middle, and the last, points interp_render_arc_segment(v1, v2, 0); @@ -2527,10 +2520,10 @@ void model_render_arc(const vec3d *v1, const vec3d *v2, const color *primary, co // use primary color for fist pass Assert( primary ); - g3_render_rod(primary, Num_arc_segment_points, Arc_segment_points, arc_width); + g3_render_rod(primary, static_cast(Arc_segment_points.size()), Arc_segment_points.data(), arc_width); if (secondary) { - g3_render_rod(secondary, Num_arc_segment_points, Arc_segment_points, arc_width * 0.33f); + g3_render_rod(secondary, static_cast(Arc_segment_points.size()), Arc_segment_points.data(), arc_width * 0.33f); } } @@ -2940,7 +2933,7 @@ void model_render_queue(const model_render_params* interp, model_draw_list* scen } else { model_render_buffers(scene, &rendering_material, interp, &pm->submodel[detail_model_num].buffer, pm, detail_model_num, detail_level, tmap_flags); - if ( pmi != nullptr && pmi->submodel[detail_model_num].num_arcs > 0 ) { + if ( pmi != nullptr && !pmi->submodel[detail_model_num].electrical_arcs.empty() ) { model_render_add_lightning( scene, interp, pm, &pmi->submodel[detail_model_num] ); } } diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 2e4f56d53c3..218ef99eb78 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -2694,8 +2694,8 @@ ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number auto shipp = &Ships[objh->objp()->instance]; - // spawn the arc in the first unused slot - auto arc = ship_find_electrical_arc_slot(shipp); + // spawn the arc in the first unused slot, or in a new slot if there are no unused ones + auto arc = ship_find_or_create_electrical_arc_slot(shipp, false); if (arc) { arc->timestamp = _timestamp(fl2i(duration * MILLISECONDS_PER_SECOND)); @@ -2712,7 +2712,7 @@ ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number arc->width = width; - return ade_set_args(L, "i", static_cast(arc - shipp->electrical_arcs) + 1); // FS2 -> Lua + return ade_set_args(L, "i", static_cast(arc - shipp->electrical_arcs.data()) + 1); // FS2 -> Lua } return ade_set_args(L, "i", 0); @@ -2735,7 +2735,7 @@ ADE_FUNC(DeleteElectricArc, l_Ship, "number index", auto shipp = &Ships[objh->objp()->instance]; index--; // Lua -> FS2 - if (index >= 0 && index < MAX_ARC_EFFECTS) + if (SCP_vector_inbounds(shipp->electrical_arcs, index)) { shipp->electrical_arcs[index].timestamp = TIMESTAMP::invalid(); } @@ -2764,7 +2764,7 @@ ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector sec auto shipp = &Ships[objh->objp()->instance]; index--; // Lua -> FS2 - if (index >= 0 && index < MAX_ARC_EFFECTS) + if (SCP_vector_inbounds(shipp->electrical_arcs, index)) { auto &arc = shipp->electrical_arcs[index]; arc.endpoint_1 = *v1; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index c989d7f53ea..49d92b1d877 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -6876,9 +6876,7 @@ void ship::clear() for (int i = 0; i < NUM_SUB_EXPL_HANDLES; i++) sub_expl_sound_handle[i] = sound_handle::invalid(); - memset(&electrical_arcs, 0, MAX_ARC_EFFECTS * sizeof(ship_electrical_arc)); - for (int i = 0; i < MAX_ARC_EFFECTS; i++) - electrical_arcs[i].timestamp = TIMESTAMP::invalid(); + electrical_arcs.clear(); arc_next_time = TIMESTAMP::invalid(); emp_intensity = -1.0f; @@ -21410,7 +21408,7 @@ int ship_check_visibility(const ship* viewed, ship* viewer) return ship_is_visible; } -ship_electrical_arc *ship_find_electrical_arc_slot(ship *shipp) +ship_electrical_arc *ship_find_or_create_electrical_arc_slot(ship *shipp, bool no_create) { size_t i = 0; for (auto &ii : shipp->electrical_arcs) @@ -21420,8 +21418,12 @@ ship_electrical_arc *ship_find_electrical_arc_slot(ship *shipp) i++; } - if (i == MAX_ARC_EFFECTS) - return nullptr; + if (i == shipp->electrical_arcs.size()) + { + if (no_create) + return nullptr; + shipp->electrical_arcs.emplace_back(); + } return &shipp->electrical_arcs[i]; } diff --git a/code/ship/ship.h b/code/ship/ship.h index cb16a4c1d64..5ce7a343df0 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -728,7 +728,7 @@ class ship std::array sub_expl_sound_handle; // Stuff for showing electrical arcs on damaged ships - ship_electrical_arc electrical_arcs[MAX_ARC_EFFECTS]; + SCP_vector electrical_arcs; TIMESTAMP arc_next_time; // When the next damage/emp arc will be created. SCP_vector passive_arc_next_times; // When the next passive ship arc will be created. @@ -2126,7 +2126,7 @@ bool ship_secondary_has_ammo(ship_weapon* swp, int bank_index); // Used to check if one ship can see the other on radar int ship_check_visibility(const ship* viewed, ship* viewer); -// Find the first available arc slot. -ship_electrical_arc *ship_find_electrical_arc_slot(ship *shipp); +// Find the first available arc slot. If none is available, and no_create is false, add one. +ship_electrical_arc *ship_find_or_create_electrical_arc_slot(ship *shipp, bool no_create); #endif diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index e0190d2d043..ec7e020466f 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -2166,8 +2166,8 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) if (skip) continue; if (submodel_1 >= 0 && submodel_2 >= 0) { - // spawn the arc in the first unused slot - auto arc = ship_find_electrical_arc_slot(shipp); + // spawn the arc in the first unused slot, or in a new slot if there are no unused ones + auto arc = ship_find_or_create_electrical_arc_slot(shipp, shipp->electrical_arcs.size() >= MAX_ARC_EFFECTS); if (arc) { arc->timestamp = _timestamp(fl2i(arc_info->duration * MILLISECONDS_PER_SECOND)); @@ -2277,7 +2277,7 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) shipp->arc_next_time = TIMESTAMP::invalid(); // invalid, so it gets restarted next frame - int n, n_arcs = Random::next(1, 3); + int n_arcs = Random::next(1, 3); vec3d v1 = submodel_get_random_point(model_num, -1); vec3d v2 = submodel_get_random_point(model_num, -1); @@ -2315,18 +2315,18 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } - n = 0; - float factor = 1.0f + 0.0025f*obj->radius; int a = (int) (factor*100.0f); int b = (int) (factor*1000.0f); int lifetime = Random::next(a, b); // Create the arc effects - int num_damage_arcs = 0; - for (int i=0; ielectrical_arcs[i]; - if ( !arc->timestamp.isValid() ) { + auto num_damage_arcs = std::count_if(shipp->electrical_arcs.begin(), shipp->electrical_arcs.end(), [](const ship_electrical_arc &arc) { + return arc.timestamp.isValid() && (arc.type == MARC_TYPE_DAMAGED || arc.type == MARC_TYPE_EMP); + }); + for (int n = 0; n < n_arcs && num_damage_arcs < MAX_SHIP_DAMAGE_ARCS; n++) { + auto arc = ship_find_or_create_electrical_arc_slot(shipp, false); + if (arc) { arc->timestamp = _timestamp(lifetime); // live up to a second switch( n ) { @@ -2353,16 +2353,11 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } else { arc->type = MARC_TYPE_DAMAGED; } - - n++; + num_damage_arcs++; - if ( n == n_arcs || num_damage_arcs >= MAX_SHIP_DAMAGE_ARCS) - break; // Don't need to create anymore - } else if (arc->type == MARC_TYPE_DAMAGED || arc->type == MARC_TYPE_EMP) { - num_damage_arcs ++; } } - + // rotate v2 out of local coordinates into world. // Use v2 since it is used in every bolt. See above switch(). vec3d snd_pos; From 6e48b2e4230b4e2d02a95f60d533a9820096daf5 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 9 Sep 2024 02:07:19 -0400 Subject: [PATCH 5/6] electrical arc upgrade, stage 3: add support for customizing segment depth and using persistent arc points --- code/debris/debris.cpp | 2 +- code/model/model.h | 6 ++-- code/model/modelinterp.cpp | 14 ++++---- code/model/modelread.cpp | 8 +++-- code/model/modelrender.cpp | 41 +++++++++++++-------- code/model/modelrender.h | 7 ++-- code/scripting/api/objs/ship.cpp | 61 ++++++++++++++++++++++++++++---- code/ship/ship.cpp | 2 +- code/ship/ship.h | 3 ++ code/ship/shipfx.cpp | 8 +++++ 10 files changed, 114 insertions(+), 38 deletions(-) diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index 38aa6c55793..6ab475f5464 100644 --- a/code/debris/debris.cpp +++ b/code/debris/debris.cpp @@ -1178,7 +1178,7 @@ void debris_render(object * obj, model_draw_list *scene) if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) { for (auto &arc: db->electrical_arcs) { if ( arc.timestamp.isValid() ) { - model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, MARC_TYPE_DAMAGED ); + model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, nullptr, MARC_TYPE_DAMAGED ); } } } diff --git a/code/model/model.h b/code/model/model.h index 1b4e0d66b2d..abe500bc944 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -94,10 +94,12 @@ struct electrical_arc vec3d endpoint_1; vec3d endpoint_2; ubyte type; // see MARC_TYPE_* defines + ubyte segment_depth; // number of times to divide the arc into segments }; struct model_electrical_arc : electrical_arc { + const SCP_vector *persistent_arc_points; }; // Data specific to a particular instance of a submodel. @@ -1204,8 +1206,8 @@ extern void model_set_up_techroom_instance(ship_info *sip, int model_instance_nu void model_replicate_submodel_instance(polymodel *pm, polymodel_instance *pmi, int submodel_num, flagset& flags); // Adds an electrical arcing effect to a submodel -void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi); -void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_model_num, vec3d *v1, vec3d *v2, int arc_type, color *primary_color_1 = nullptr, color *primary_color_2 = nullptr, color *secondary_color = nullptr, float width = 0.0f); +void model_instance_clear_arcs(const polymodel *pm, polymodel_instance *pmi); +void model_instance_add_arc(const polymodel *pm, polymodel_instance *pmi, int sub_model_num, const vec3d *v1, const vec3d *v2, const SCP_vector *persistent_arc_points, ubyte arc_type, const color *primary_color_1 = nullptr, const color *primary_color_2 = nullptr, const color *secondary_color = nullptr, float width = 0.0f, ubyte segment_depth = 4); // Gets two random points on the surface of a submodel extern vec3d submodel_get_random_point(int model_num, int submodel_num, int seed = -1); diff --git a/code/model/modelinterp.cpp b/code/model/modelinterp.cpp index 7a40fbfeff7..183ad84807a 100644 --- a/code/model/modelinterp.cpp +++ b/code/model/modelinterp.cpp @@ -785,15 +785,13 @@ void model_draw_bay_paths_htl(int model_num) gr_set_cull(cull); } -SCP_vector Arc_segment_points; - -void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth ) +void interp_generate_arc_segment(SCP_vector &arc_segment_points, const vec3d *v1, const vec3d *v2, ubyte depth_limit, ubyte depth) { float d = vm_vec_dist_quick( v1, v2 ); - const float scaler = 0.30f; + constexpr float scaler = 0.30f; - if ( (d < scaler) || (depth > 4) ) { - Arc_segment_points.push_back(*v2); + if ( (d < scaler) || (depth > depth_limit) ) { + arc_segment_points.push_back(*v2); } else { // divide in half vec3d tmp; @@ -804,8 +802,8 @@ void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth ) tmp.xyz.z += (frand() - 0.5f) * d * scaler; // add additional point - interp_render_arc_segment( v1, &tmp, depth+1 ); - interp_render_arc_segment( &tmp, v2, depth+1 ); + interp_generate_arc_segment( arc_segment_points, v1, &tmp, depth_limit, depth+1 ); + interp_generate_arc_segment( arc_segment_points, &tmp, v2, depth_limit, depth+1 ); } } diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index 3bb1fe61bd3..ac4e8c2f5e5 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -5154,7 +5154,7 @@ void model_do_intrinsic_motions(object *objp) } } -void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi) +void model_instance_clear_arcs(const polymodel *pm, polymodel_instance *pmi) { Assert(pm->id == pmi->model_num); @@ -5164,7 +5164,7 @@ void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi) } // Adds an electrical arcing effect to a submodel -void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_model_num, vec3d *v1, vec3d *v2, int arc_type, color *primary_color_1, color *primary_color_2, color *secondary_color, float width ) +void model_instance_add_arc(const polymodel *pm, polymodel_instance *pmi, int sub_model_num, const vec3d *v1, const vec3d *v2, const SCP_vector *persistent_arc_points, ubyte arc_type, const color *primary_color_1, const color *primary_color_2, const color *secondary_color, float width, ubyte segment_depth) { Assert(pm->id == pmi->model_num); @@ -5182,9 +5182,11 @@ void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_mode smi->electrical_arcs.emplace_back(); auto &new_arc = smi->electrical_arcs.back(); - new_arc.type = static_cast(arc_type); + new_arc.type = arc_type; new_arc.endpoint_1 = *v1; new_arc.endpoint_2 = *v2; + new_arc.persistent_arc_points = persistent_arc_points; + new_arc.segment_depth = segment_depth; if (arc_type == MARC_TYPE_SHIP || arc_type == MARC_TYPE_SCRIPTED) { new_arc.primary_color_1 = *primary_color_1; diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 601eb1cb0ac..598d640b1d0 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -40,12 +40,10 @@ extern int Model_polys; extern int tiling; extern float model_radius; -extern SCP_vector Arc_segment_points; - extern bool Scene_framebuffer_in_frame; color Wireframe_color; -extern void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth); +extern void interp_generate_arc_segment(SCP_vector &arc_segment_points, const vec3d *v1, const vec3d *v2, ubyte depth_limit, ubyte depth); int model_render_determine_elapsed_time(int objnum, uint64_t flags); @@ -444,7 +442,7 @@ void model_draw_list::add_submodel_to_batch(int model_num) TransformBufferHandler.set_model_transform(transform, model_num); } -void model_draw_list::add_arc(const vec3d *v1, const vec3d *v2, const color *primary, const color *secondary, float arc_width) +void model_draw_list::add_arc(const vec3d *v1, const vec3d *v2, const SCP_vector *persistent_arc_points, const color *primary, const color *secondary, float arc_width, ubyte segment_depth) { arc_effect new_arc; @@ -454,6 +452,8 @@ void model_draw_list::add_arc(const vec3d *v1, const vec3d *v2, const color *pri new_arc.primary = *primary; new_arc.secondary = *secondary; new_arc.width = arc_width; + new_arc.segment_depth = segment_depth; + new_arc.persistent_arc_points = persistent_arc_points; Arcs.push_back(new_arc); } @@ -623,7 +623,7 @@ void model_draw_list::render_arc(const arc_effect &arc) { g3_start_instance_matrix(&arc.transform); - model_render_arc(&arc.v1, &arc.v2, &arc.primary, &arc.secondary, arc.width); + model_render_arc(&arc.v1, &arc.v2, arc.persistent_arc_points, &arc.primary, &arc.secondary, arc.width, arc.segment_depth); g3_done_instance(true); } @@ -887,7 +887,7 @@ void model_render_add_lightning(model_draw_list *scene, const model_render_param // render the actual arc segment if (width > 0.0f) - scene->add_arc(&arc.endpoint_1, &arc.endpoint_2, &primary, &secondary, width); + scene->add_arc(&arc.endpoint_1, &arc.endpoint_2, arc.persistent_arc_points, &primary, &secondary, width, arc.segment_depth); } } @@ -2507,23 +2507,36 @@ void model_render_insignias(const insignia_draw_data *insignia_data) } } -void model_render_arc(const vec3d *v1, const vec3d *v2, const color *primary, const color *secondary, float arc_width) +SCP_vector Arc_segment_points; + +void model_render_arc(const vec3d *v1, const vec3d *v2, const SCP_vector *persistent_arc_points, const color *primary, const color *secondary, float arc_width, ubyte depth_limit) { - Arc_segment_points.clear(); + int size; + const vec3d *pvecs; + + if (persistent_arc_points) { + size = static_cast(persistent_arc_points->size()); + pvecs = persistent_arc_points->data(); + } else { + Arc_segment_points.clear(); + + // need to add the first point + Arc_segment_points.push_back(*v1); - // need to add the first point - Arc_segment_points.push_back(*v1); + // this should fill in all of the middle, and the last, points + interp_generate_arc_segment(Arc_segment_points, v1, v2, depth_limit, 0); - // this should fill in all of the middle, and the last, points - interp_render_arc_segment(v1, v2, 0); + size = static_cast(Arc_segment_points.size()); + pvecs = Arc_segment_points.data(); + } // use primary color for fist pass Assert( primary ); - g3_render_rod(primary, static_cast(Arc_segment_points.size()), Arc_segment_points.data(), arc_width); + g3_render_rod(primary, size, pvecs, arc_width); if (secondary) { - g3_render_rod(secondary, static_cast(Arc_segment_points.size()), Arc_segment_points.data(), arc_width * 0.33f); + g3_render_rod(secondary, size, pvecs, arc_width * 0.33f); } } diff --git a/code/model/modelrender.h b/code/model/modelrender.h index 4d9d55842a2..cbd99397c61 100644 --- a/code/model/modelrender.h +++ b/code/model/modelrender.h @@ -164,6 +164,9 @@ struct arc_effect color primary; color secondary; float width; + ubyte segment_depth; + + const SCP_vector *persistent_arc_points; }; struct insignia_draw_data @@ -281,7 +284,7 @@ class model_draw_list void pop_transform(); void set_scale(const vec3d *scale = NULL); - void add_arc(const vec3d *v1, const vec3d *v2, const color *primary, const color *secondary, float arc_width); + void add_arc(const vec3d *v1, const vec3d *v2, const SCP_vector *persistent_arc_points, const color *primary, const color *secondary, float arc_width, ubyte segment_depth); void render_arcs(); void add_insignia(const model_render_params *params, const polymodel *pm, int detail_level, int bitmap_num); @@ -308,7 +311,7 @@ void submodel_render_immediate(const model_render_params* render_info, const pol void submodel_render_queue(const model_render_params* render_info, model_draw_list* scene, const polymodel* pm, const polymodel_instance* pmi, int submodel_num, const matrix* orient, const vec3d* pos); void model_render_buffers(model_draw_list* scene, model_material* rendering_material, const model_render_params* interp, const vertex_buffer* buffer, const polymodel* pm, int mn, int detail_level, uint tmap_flags); bool model_render_check_detail_box(const vec3d* view_pos, const polymodel* pm, int submodel_num, uint64_t flags); -void model_render_arc(const vec3d* v1, const vec3d* v2, const color* primary, const color* secondary, float arc_width); +void model_render_arc(const vec3d* v1, const vec3d* v2, const SCP_vector *persistent_arc_points, const color* primary, const color* secondary, float arc_width, ubyte depth_limit); void model_render_insignias(const insignia_draw_data* insignia); void model_render_set_wireframe_color(const color* clr); bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int y2, float zoom, bool lighting, int class_idx, const matrix* orient, const SCP_string& pof_filename = "", float closeup_zoom = 0, const vec3d* closeup_pos = &vmd_zero_vector); diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 218ef99eb78..075d8375678 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -39,6 +39,7 @@ extern void ship_reset_disabled_physics(object *objp, int ship_class); extern bool sexp_check_flag_arrays(const char *flag_name, Object::Object_Flags &object_flag, Ship::Ship_Flags &ship_flags, Mission::Parse_Object_Flags &parse_obj_flag, AI::AI_Flags &ai_flag); extern void sexp_alter_ship_flag_helper(object_ship_wing_point_team &oswpt, bool future_ships, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags parse_obj_flag, AI::AI_Flags ai_flag, bool set_flag); +extern void interp_generate_arc_segment(SCP_vector &arc_segment_points, const vec3d *v1, const vec3d *v2, ubyte depth_limit, ubyte depth); namespace scripting { namespace api { @@ -2675,8 +2676,9 @@ ADE_FUNC(jettison, l_Ship, "number jettison_speed, [ship... dockee_ships /* All return jettison_helper(L, docker_objh, jettison_speed, 2); } -ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number duration, number width", - "Creates an electric arc on the ship between two points in the ship's reference frame, for the specified duration in seconds, and the specified width in meters.", +ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number duration, number width, [number segment_depth, boolean persistent_points]", + "Creates an electric arc on the ship between two points in the ship's reference frame, for the specified duration in seconds, and the specified width in meters. Optionally, " + "specify the segment depth (the number of times the spark is divided) and whether to generate a set of arc points that will persist from frame to frame.", "number", "The arc index if successful, 0 otherwise") { @@ -2685,8 +2687,10 @@ ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number vec3d* v2; float duration = 0.0f; float width = 0.0f; + int segment_depth = 4; + bool persistent_points = false; - if (!ade_get_args(L, "oooff", l_Ship.GetPtr(&objh), l_Vector.GetPtr(&v1), l_Vector.GetPtr(&v2), &duration, &width)) + if (!ade_get_args(L, "oooff|ib", l_Ship.GetPtr(&objh), l_Vector.GetPtr(&v1), l_Vector.GetPtr(&v2), &duration, &width, &segment_depth, &persistent_points)) return ade_set_error(L, "i", 0); if (!objh->isValid()) @@ -2711,6 +2715,19 @@ ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number arc->type = MARC_TYPE_SCRIPTED; arc->width = width; + arc->segment_depth = static_cast(segment_depth); + + // we might want to generate the arc points ahead of time, rather than every frame + if (persistent_points) + { + arc->persistent_arc_points.reset(new SCP_vector()); + + // need to add the first point + arc->persistent_arc_points->push_back(*v1); + + // this should fill in all of the middle, and the last, points + interp_generate_arc_segment(*arc->persistent_arc_points, v1, v2, static_cast(segment_depth), 1); // start at depth 1 for the benefit of Lua + } return ade_set_args(L, "i", static_cast(arc - shipp->electrical_arcs.data()) + 1); // FS2 -> Lua } @@ -2743,8 +2760,9 @@ ADE_FUNC(DeleteElectricArc, l_Ship, "number index", return ADE_RETURN_NIL; } -ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector secondPoint, [number width]", - "Sets the endpoints (in the ship's reference frame) and width of the specified electric arc on the ship, .", +ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector secondPoint, [number width, number segment_depth, boolean persistent_points]", + "Sets the endpoints (in the ship's reference frame), width, and segment depth of the specified electric arc on the ship, plus whether the arc has persistent points. " + "If this arc already had a collection of persistent points and it still does after this function is called, the points will be regenerated.", nullptr, nullptr) { @@ -2753,8 +2771,10 @@ ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector sec vec3d* v1; vec3d* v2; float width = 0.0f; + int segment_depth = 4; + bool persistent_points = false; - int args = ade_get_args(L, "oioo|f", l_Ship.GetPtr(&objh), &index, l_Vector.GetPtr(&v1), l_Vector.GetPtr(&v2), &width); + int args = ade_get_args(L, "oioo|fib", l_Ship.GetPtr(&objh), &index, l_Vector.GetPtr(&v1), l_Vector.GetPtr(&v2), &width, &segment_depth, &persistent_points); if (args < 4) return ADE_RETURN_NIL; @@ -2770,8 +2790,35 @@ ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector sec arc.endpoint_1 = *v1; arc.endpoint_2 = *v2; - if (args == 5) + if (args >= 5) arc.width = width; + if (args >= 6) + arc.segment_depth = static_cast(segment_depth); + if (args >= 7) + { + if (persistent_points) + { + if (!arc.persistent_arc_points) + arc.persistent_arc_points.reset(new SCP_vector()); + } + else + { + if (arc.persistent_arc_points) + arc.persistent_arc_points.reset(); + } + } + + // persistent points need to be regenerated when the arc is moved; they also need to be generated if we are adding them for the first time + if (arc.persistent_arc_points) + { + arc.persistent_arc_points->clear(); + + // need to add the first point + arc.persistent_arc_points->push_back(*v1); + + // this should fill in all of the middle, and the last, points + interp_generate_arc_segment(*arc.persistent_arc_points, v1, v2, static_cast(segment_depth), 1); // start at depth 1 for the benefit of Lua + } } return ADE_RETURN_NIL; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 49d92b1d877..7529517b85c 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -20969,7 +20969,7 @@ void ship_render(object* obj, model_draw_list* scene) if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f && !Rendering_to_shadow_map ) { for (auto &arc: shipp->electrical_arcs) { if (arc.timestamp.isValid()) { - model_instance_add_arc(pm, pmi, -1, &arc.endpoint_1, &arc.endpoint_2, arc.type, &arc.primary_color_1, &arc.primary_color_2, &arc.secondary_color, arc.width); + model_instance_add_arc(pm, pmi, -1, &arc.endpoint_1, &arc.endpoint_2, arc.persistent_arc_points.get(), arc.type, &arc.primary_color_1, &arc.primary_color_2, &arc.secondary_color, arc.width, arc.segment_depth); } } } diff --git a/code/ship/ship.h b/code/ship/ship.h index 5ce7a343df0..d1a4c02384f 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -539,6 +539,9 @@ struct reload_pct struct ship_electrical_arc : electrical_arc { TIMESTAMP timestamp; // When this times out, the spark goes away. Invalid is not used + + // if this vector exists, these points will be used instead of the ones generated on each frame by interp_generate_arc_segment() + std::unique_ptr> persistent_arc_points; }; // NOTE: Can't be treated as a struct anymore, since it has STL data structures in its object tree! diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index ec7e020466f..4528d733624 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -2205,6 +2205,10 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) } } + arc->segment_depth = 4; // previously hard-coded in interp_generate_arc_segment() + + arc->persistent_arc_points.reset(); // by default, no persistent points + shipp->passive_arc_next_times[passive_arc_info_idx] = _timestamp(fl2i(arc_info->frequency * MILLISECONDS_PER_SECOND)); } } @@ -2354,6 +2358,10 @@ void shipfx_do_lightning_arcs_frame( ship *shipp ) arc->type = MARC_TYPE_DAMAGED; } + arc->segment_depth = 4; // previously hard-coded in interp_generate_arc_segment() + + arc->persistent_arc_points.reset(); // by default, no persistent points + num_damage_arcs++; } } From fdc9c7c5ab3a662a714a46da9cf587f7c855181d Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Sep 2024 02:50:21 -0400 Subject: [PATCH 6/6] electrical arc upgrade, stage 4: add a new ModifyElectricArcPoints function with a handy valueToVec3d utility function --- code/scripting/api/objs/ship.cpp | 66 +++++++++++++++++++++++++++ code/scripting/lua/LuaUtil.cpp | 78 ++++++++++++++++++++++++++++++++ code/scripting/lua/LuaUtil.h | 8 ++++ 3 files changed, 152 insertions(+) diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 075d8375678..3a641ae83b3 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -36,6 +36,9 @@ #include "ship/shipfx.h" #include "ship/shiphit.h" +#include "scripting/lua/LuaException.h" +#include "scripting/lua/LuaUtil.h" + extern void ship_reset_disabled_physics(object *objp, int ship_class); extern bool sexp_check_flag_arrays(const char *flag_name, Object::Object_Flags &object_flag, Ship::Ship_Flags &ship_flags, Mission::Parse_Object_Flags &parse_obj_flag, AI::AI_Flags &ai_flag); extern void sexp_alter_ship_flag_helper(object_ship_wing_point_team &oswpt, bool future_ships, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags parse_obj_flag, AI::AI_Flags ai_flag, bool set_flag); @@ -2824,5 +2827,68 @@ ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector sec return ADE_RETURN_NIL; } +ADE_FUNC(ModifyElectricArcPoints, l_Ship, "number index, table points, [number width]", + "Sets the collection of persistent points to be used by this arc, as well as optionally the arc's width. " + "The table of points should consist of Vectors (e.g. created with ba.createVector()), arrays with three elements each, or tables with 'x'/'X', 'y'/'Y', and 'z'/'Z' pairs. There must be at least two points.", + nullptr, + nullptr) +{ + object_h* objh = nullptr; + int index; + luacpp::LuaTable luaPoints; + float width = 0.0f; + + int args = ade_get_args(L, "oit|f", l_Ship.GetPtr(&objh), &index, &luaPoints, &width); + if (args < 3) + return ADE_RETURN_NIL; + + if (!objh->isValid()) + return ADE_RETURN_NIL; + + auto shipp = &Ships[objh->objp()->instance]; + + index--; // Lua -> FS2 + if (SCP_vector_inbounds(shipp->electrical_arcs, index) && luaPoints.isValid()) + { + SCP_vector fsoPoints; + + // convert Lua points to FSO points + for (const auto &entry : luaPoints) + { + try + { + fsoPoints.push_back(luacpp::util::valueToVec3d(entry.second)); + } + catch (const luacpp::LuaException &e) + { + LuaError(L, "%s", e.what()); + return ADE_RETURN_NIL; + } + } + + if (fsoPoints.size() < 2) + { + LuaError(L, "Points table passed to ship:ModifyElectricArcPoints() has fewer than two points!"); + return ADE_RETURN_NIL; + } + + auto &arc = shipp->electrical_arcs[index]; + arc.endpoint_1 = fsoPoints.front(); + arc.endpoint_2 = fsoPoints.back(); + + // need to create the persistent point storage if it isn't set up yet + if (!arc.persistent_arc_points) + arc.persistent_arc_points.reset(new SCP_vector()); + + // assign all of our new points to the persistent point storage + arc.persistent_arc_points->operator=(std::move(fsoPoints)); + + if (args >= 4) + arc.width = width; + } + + return ADE_RETURN_NIL; +} + } } diff --git a/code/scripting/lua/LuaUtil.cpp b/code/scripting/lua/LuaUtil.cpp index 043cae53f67..1015a1e879c 100644 --- a/code/scripting/lua/LuaUtil.cpp +++ b/code/scripting/lua/LuaUtil.cpp @@ -1,6 +1,8 @@ #include "LuaUtil.h" +#include "scripting/api/objs/vecmath.h" + namespace { const char* mainStateRefName = "_lua_mainthread"; } @@ -62,5 +64,81 @@ lua_State* getMainThread(lua_State* L) return state; } +vec3d valueToVec3d(const LuaValue &luaValue) +{ + if (luaValue.is(luacpp::ValueType::TABLE)) + { + vec3d tablePoint = vmd_zero_vector; + int index = 0; + bool is_array = false; + bool is_table = false; + + // we have to go deeper + luacpp::LuaTable childTable; + childTable.setReference(luaValue.getReference()); + for (const auto &childEntry : childTable) + { + // note: this is pre-increment + if (index >= 3) + throw LuaException("Vec3d table has more than three entries!"); + + if (!childEntry.second.is(luacpp::ValueType::NUMBER)) + throw LuaException("Value in vec3d table is not a number!"); + + auto number = static_cast(childEntry.second.getValue()); + + if (childEntry.first.is(luacpp::ValueType::STRING)) + { + is_table = true; + if (is_array) + throw LuaException("Vec3d table has an unexpected format!"); + + const auto &str = childEntry.first.getValue(); + if (lcase_equal(str, "x")) + tablePoint.xyz.x = number; + else if (lcase_equal(str, "y")) + tablePoint.xyz.y = number; + else if (lcase_equal(str, "z")) + tablePoint.xyz.z = number; + else + throw LuaException("Vec3d table has an entry other than x/X, y/Y, or z/Z!"); + } + else + { + is_array = true; + if (is_table) + throw LuaException("Vec3d table has an unexpected format!"); + + tablePoint.a1d[index] = number; + } + + index++; + } + + // note: this is post-increment + if (index < 3) + throw LuaException("Vec3d table has fewer than three entries!"); + + return tablePoint; + } + else if (luaValue.is(luacpp::ValueType::USERDATA)) + { + try + { + vec3d v; + luaValue.getValue(scripting::api::l_Vector.Get(&v)); + return v; + } + catch (const luacpp::LuaException& /*e*/) + { + throw LuaException("Userdata in vec3d table is not a vector!"); + } + } + else + { + throw LuaException("Vec3d value is of an unhandled type!"); + } +} + } // namespace util } // namespace luacpp diff --git a/code/scripting/lua/LuaUtil.h b/code/scripting/lua/LuaUtil.h index 89b20c4e8f4..9b5af679f4e 100644 --- a/code/scripting/lua/LuaUtil.h +++ b/code/scripting/lua/LuaUtil.h @@ -91,6 +91,14 @@ void tableToList(LuaTable& table, Container& list) { } const char* getValueName(ValueType type); + +/** + * Processes a LuaValue and returns a vec3d. The LuaValue could be a Vector, an array with three elements, or a table with x/X, y/Y, and z/Z entries. + * + * Throws a LuaException if the conversion was not successful. + */ +vec3d valueToVec3d(const LuaValue &luaValue); + } }