diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index 64320568b9c..6ab475f5464 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. 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!!! @@ -242,56 +244,48 @@ 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) { - 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; iarc_timestamp[i].isValid() ) { - - db->arc_timestamp[i] = _timestamp(lifetime); // live up to a second + 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 ) { 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(); + 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 } } - // 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 +312,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; } } } @@ -571,15 +569,13 @@ 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; db->damage_mult = 1.0f; - for (int i=0; iarc_timestamp[i] = TIMESTAMP::invalid(); - } + db->electrical_arcs.clear(); if ( db->is_hull ) { // Percent of debris pieces with arcs controlled via table (default 50%) @@ -592,7 +588,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); @@ -642,12 +638,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) { @@ -1145,7 +1141,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 +1176,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, nullptr, MARC_TYPE_DAMAGED ); } } } @@ -1232,3 +1228,23 @@ 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_or_create_electrical_arc_slot(debris *db, bool no_create) +{ + size_t i = 0; + for (auto& ii : db->electrical_arcs) + { + if (!ii.timestamp.isValid()) + break; + i++; + } + + 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 7753fb7077b..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, @@ -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 @@ -43,15 +49,14 @@ 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 - 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 + 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 9474e5a0f2e..abe500bc944 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -83,6 +83,25 @@ 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 + 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. struct submodel_instance { @@ -114,31 +133,11 @@ 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 + SCP_vector electrical_arcs; //SMI-Specific movement axis. Only valid in MOVEMENT_TYPE_TRIGGERED. - vec3d rotation_axis; - vec3d translation_axis; - - 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)); - } + vec3d rotation_axis = vmd_zero_vector; + vec3d translation_axis = vmd_zero_vector; }; #define TM_BASE_TYPE 0 // the standard base map @@ -1207,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 a57dd743314..183ad84807a 100644 --- a/code/model/modelinterp.cpp +++ b/code/model/modelinterp.cpp @@ -785,20 +785,13 @@ 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]; - -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; - - 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 ); + constexpr float scaler = 0.30f; - memcpy( &Arc_segment_points[Num_arc_segment_points++], v2, sizeof(vec3d) ); + if ( (d < scaler) || (depth > depth_limit) ) { + arc_segment_points.push_back(*v2); } else { // divide in half vec3d tmp; @@ -809,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 d96bd05ede3..ac4e8c2f5e5 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -5154,17 +5154,17 @@ 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); 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 } } // 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); @@ -5179,19 +5179,20 @@ 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 ) { - 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; + smi->electrical_arcs.emplace_back(); + auto &new_arc = smi->electrical_arcs.back(); - 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.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; - 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 2b60a60b2b3..598d640b1d0 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -40,14 +40,10 @@ 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 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); @@ -446,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; @@ -456,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); } @@ -625,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); } @@ -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,9 @@ void model_render_add_lightning(model_draw_list *scene, const model_render_param return; } - for ( i = 0; i < smi->num_arcs; i++ ) { + for (auto &arc: smi->electrical_arcs) { // 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 +848,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 +882,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, arc.persistent_arc_points, &primary, &secondary, width, arc.segment_depth); } } @@ -1304,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 ); } @@ -1624,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] ); } @@ -2078,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; } @@ -2512,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) { - Num_arc_segment_points = 0; + int size; + const vec3d *pvecs; - // need need to add the first point - memcpy( &Arc_segment_points[Num_arc_segment_points++], v1, sizeof(vec3d) ); + 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); - // this should fill in all of the middle, and the last, points - interp_render_arc_segment(v1, v2, 0); + // this should fill in all of the middle, and the last, points + interp_generate_arc_segment(Arc_segment_points, v1, v2, depth_limit, 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, Num_arc_segment_points, Arc_segment_points, arc_width); + g3_render_rod(primary, size, pvecs, arc_width); if (secondary) { - g3_render_rod(secondary, Num_arc_segment_points, Arc_segment_points, arc_width * 0.33f); + g3_render_rod(secondary, size, pvecs, arc_width * 0.33f); } } @@ -2938,7 +2946,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/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/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..3a641ae83b3 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -36,9 +36,13 @@ #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); +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 +2679,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 +2690,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()) @@ -2694,25 +2701,38 @@ 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)); + // 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)); + + arc->endpoint_1 = *v1; + arc->endpoint_2 = *v2; - shipp->arc_pts[i][0] = *v1; - shipp->arc_pts[i][1] = *v2; + //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; - //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; + arc->type = MARC_TYPE_SCRIPTED; - shipp->arc_type[i] = MARC_TYPE_SCRIPTED; + arc->width = width; + arc->segment_depth = static_cast(segment_depth); - shipp->arc_width[i] = width; + // 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()); - return ade_set_args(L, "i", i + 1); // FS2 -> Lua + // 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 } return ade_set_args(L, "i", 0); @@ -2735,16 +2755,17 @@ 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->arc_timestamp[index] = TIMESTAMP::invalid(); + shipp->electrical_arcs[index].timestamp = TIMESTAMP::invalid(); } 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 +2774,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; @@ -2764,13 +2787,104 @@ 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; + arc.endpoint_2 = *v2; + + 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; +} + +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()) { - shipp->arc_pts[index][0] = *v1; - shipp->arc_pts[index][1] = *v2; + 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 == 5) - shipp->arc_width[index] = width; + 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); + } } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 7db1e21c5d8..7529517b85c 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -6876,12 +6876,8 @@ 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)); - 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.clear(); + arc_next_time = TIMESTAMP::invalid(); emp_intensity = -1.0f; emp_decr = 0.0f; @@ -7332,9 +7328,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 +20967,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.persistent_arc_points.get(), arc.type, &arc.primary_color_1, &arc.primary_color_2, &arc.secondary_color, arc.width, arc.segment_depth); } } } @@ -21421,3 +21407,23 @@ int ship_check_visibility(const ship* viewed, ship* viewer) return ship_is_visible; } + +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) + { + if (!ii.timestamp.isValid()) + break; + i++; + } + + 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 315701ffce8..d1a4c02384f 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -536,6 +536,14 @@ 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 + + // 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! class ship { @@ -723,15 +731,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. + 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. // emp missile stuff float emp_intensity; // <= 0.0f if no emp effect present @@ -2127,4 +2129,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. 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 539414385ed..4528d733624 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -2166,48 +2166,50 @@ 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 - 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; - } + // 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)); + + 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; } + + 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)); } } } @@ -2247,9 +2249,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 +2260,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,14 +2274,14 @@ 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); + 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); @@ -2317,84 +2319,83 @@ 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; iarc_timestamp[i].isValid() ) { - shipp->arc_timestamp[i] = _timestamp(lifetime); // live up to a second + 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 ) { 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: - Int3(); + UNREACHABLE("Unhandled case %d for electrical arc creation in shipfx_do_lightning_arcs_frame()!", n); } // 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++; + + 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++; - 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) { - 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 ); + } } // 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 +2404,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 +2424,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; } } }