Skip to content

Commit 11a33fe

Browse files
authored
Merge pull request #6347 from Goober5000/electrical_arc_enhancements
Enhancements to the FSO electrical arc system
2 parents 821e48d + c5d5a93 commit 11a33fe

File tree

14 files changed

+536
-297
lines changed

14 files changed

+536
-297
lines changed

code/debris/debris.cpp

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ particle::ParticleEffectHandle Debris_hit_particle;
5959

6060
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
6161

62+
// Find the first available arc slot. If none is available, and no_create is false, add one.
63+
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create);
6264

6365
/**
6466
* Start the sequence of a piece of debris writhing in unholy agony!!!
@@ -266,56 +268,48 @@ void debris_process_post(object * obj, float frame_time)
266268
return; // If arc_frequency <= 0, this piece has no arcs on it
267269
}
268270

269-
if ( !timestamp_elapsed(db->fire_timeout) && timestamp_elapsed(db->next_fireball)) {
271+
if ( !timestamp_elapsed(db->arc_timeout) && timestamp_elapsed(db->arc_next_time)) {
270272

271-
db->next_fireball = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
273+
db->arc_next_time = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
272274
db->arc_frequency += 100;
273275

274276
if (db->is_hull) {
275277

276-
int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks
278+
int n_arcs = Random::next(1, 3); // Create 1-3 sparks
277279

278280
vec3d v1 = submodel_get_random_point(db->model_num, db->submodel_num);
279281
vec3d v2 = submodel_get_random_point(db->model_num, db->submodel_num);
280282
vec3d v3 = submodel_get_random_point(db->model_num, db->submodel_num);
281283
vec3d v4 = submodel_get_random_point(db->model_num, db->submodel_num);
282284

283-
n = 0;
284-
285285
int lifetime = Random::next(100, 1000);
286286

287287
// Create the spark effects
288-
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
289-
if ( !db->arc_timestamp[i].isValid() ) {
290-
291-
db->arc_timestamp[i] = _timestamp(lifetime); // live up to a second
288+
for (int n = 0; n < n_arcs; n++) {
289+
auto arc = debris_find_or_create_electrical_arc_slot(db, db->electrical_arcs.size() >= MAX_DEBRIS_ARCS);
290+
if (arc) {
291+
arc->timestamp = _timestamp(lifetime); // live up to a second
292292

293293
switch( n ) {
294294
case 0:
295-
db->arc_pts[i][0] = v1;
296-
db->arc_pts[i][1] = v2;
295+
arc->endpoint_1 = v1;
296+
arc->endpoint_2 = v2;
297297
break;
298298
case 1:
299-
db->arc_pts[i][0] = v2;
300-
db->arc_pts[i][1] = v3;
299+
arc->endpoint_1 = v2;
300+
arc->endpoint_2 = v3;
301301
break;
302-
303302
case 2:
304-
db->arc_pts[i][0] = v2;
305-
db->arc_pts[i][1] = v4;
303+
arc->endpoint_1 = v2;
304+
arc->endpoint_2 = v4;
306305
break;
307306

308307
default:
309-
Int3();
308+
UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n);
310309
}
311-
312-
n++;
313-
if ( n == n_arcs )
314-
break; // Don't need to create anymore
315310
}
316311
}
317312

318-
319313
// rotate v2 out of local coordinates into world.
320314
// Use v2 since it is used in every bolt. See above switch().
321315
vec3d snd_pos;
@@ -342,16 +336,20 @@ void debris_process_post(object * obj, float frame_time)
342336
}
343337
}
344338

345-
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
346-
if ( db->arc_timestamp[i].isValid() ) {
347-
if ( timestamp_elapsed( db->arc_timestamp[i] ) ) {
339+
for (auto &arc: db->electrical_arcs) {
340+
if (arc.timestamp.isValid()) {
341+
if (timestamp_elapsed(arc.timestamp)) {
348342
// Kill off the spark
349-
db->arc_timestamp[i] = TIMESTAMP::invalid();
343+
arc.timestamp = TIMESTAMP::invalid();
350344
} else {
351345
// Maybe move a vertex.... 20% of the time maybe?
352346
int mr = Random::next();
353347
if ( mr < Random::MAX_VALUE/5 ) {
354-
db->arc_pts[i][mr % 2] = submodel_get_random_point(db->model_num, db->submodel_num);
348+
auto pt = submodel_get_random_point(db->model_num, db->submodel_num);
349+
if (mr % 2 == 0)
350+
arc.endpoint_1 = pt;
351+
else
352+
arc.endpoint_2 = pt;
355353
}
356354
}
357355
}
@@ -595,15 +593,13 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
595593
db->ship_info_index = parent_ship_class;
596594
db->team = team;
597595
db->ambient_sound = (sip == nullptr) ? gamesnd_id(-1) : sip->debris_ambient_sound;
598-
db->fire_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false
596+
db->arc_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false
599597
db->time_started = Missiontime;
600598
db->species = (sip == nullptr) ? -1 : sip->species;
601599
db->parent_alt_name = alt_type_index;
602600
db->damage_mult = 1.0f;
603601

604-
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) { // NOLINT
605-
db->arc_timestamp[i] = TIMESTAMP::invalid();
606-
}
602+
db->electrical_arcs.clear();
607603

608604
if ( db->is_hull ) {
609605
// Percent of debris pieces with arcs controlled via table (default 50%)
@@ -616,7 +612,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
616612
db->arc_frequency = 0;
617613
}
618614

619-
db->next_fireball = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later
615+
db->arc_next_time = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later
620616

621617
flagset<Object::Object_Flags> default_flags;
622618
default_flags.set(Object::Object_Flags::Renders);
@@ -677,12 +673,12 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
677673
// limit the amount of time that fireballs appear
678674
// let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds.
679675
if (spark_timeout >= 0) {
680-
db->fire_timeout = _timestamp(spark_timeout);
676+
db->arc_timeout = _timestamp(spark_timeout);
681677
} else if (parent_objnum >= 0) {
682678
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)));
683-
db->fire_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
679+
db->arc_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
684680
} else {
685-
db->fire_timeout = TIMESTAMP::immediate();
681+
db->arc_timeout = TIMESTAMP::immediate();
686682
}
687683

688684
if (parent_objnum >= 0 && Objects[parent_objnum].radius >= MIN_RADIUS_FOR_PERSISTENT_DEBRIS) {
@@ -1168,7 +1164,7 @@ void calc_debris_physics_properties( physics_info *pi, vec3d *mins, vec3d *maxs,
11681164
*/
11691165
void debris_render(object * obj, model_draw_list *scene)
11701166
{
1171-
int i, num, swapped;
1167+
int num, swapped;
11721168
debris *db;
11731169

11741170
swapped = -1;
@@ -1203,9 +1199,9 @@ void debris_render(object * obj, model_draw_list *scene)
12031199

12041200
// Only render electrical arcs if within 500m of the eye (for a 10m piece)
12051201
if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) {
1206-
for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
1207-
if ( db->arc_timestamp[i].isValid() ) {
1208-
model_instance_add_arc( pm, pmi, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_DAMAGED );
1202+
for (auto &arc: db->electrical_arcs) {
1203+
if ( arc.timestamp.isValid() ) {
1204+
model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, nullptr, MARC_TYPE_DAMAGED );
12091205
}
12101206
}
12111207
}
@@ -1256,3 +1252,23 @@ void create_generic_debris(object* ship_objp, const vec3d* pos, float min_num_de
12561252
debris_create(ship_objp, model_num, -1, &create_pos, pos, 0, speed_mult);
12571253
}
12581254
}
1255+
1256+
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create)
1257+
{
1258+
size_t i = 0;
1259+
for (auto& ii : db->electrical_arcs)
1260+
{
1261+
if (!ii.timestamp.isValid())
1262+
break;
1263+
i++;
1264+
}
1265+
1266+
if (i == db->electrical_arcs.size())
1267+
{
1268+
if (no_create)
1269+
return nullptr;
1270+
db->electrical_arcs.emplace_back();
1271+
}
1272+
1273+
return &db->electrical_arcs[i];
1274+
}

code/debris/debris.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class object;
2020
struct CFILE;
2121
class model_draw_list;
2222

23-
#define MAX_DEBRIS_ARCS 8 // Must be less than MAX_ARC_EFFECTS in model.h
23+
#define MAX_DEBRIS_ARCS 8
2424

2525
FLAG_LIST(Debris_Flags) {
2626
Used,
@@ -30,6 +30,12 @@ FLAG_LIST(Debris_Flags) {
3030
NUM_VALUES
3131
};
3232

33+
struct debris_electrical_arc
34+
{
35+
vec3d endpoint_1;
36+
vec3d endpoint_2;
37+
TIMESTAMP timestamp; // When this times out, the spark goes away. Invalid is not used
38+
};
3339

3440
typedef struct debris {
3541
flagset<Debris_Flags> flags; // See DEBRIS_??? defines
@@ -43,15 +49,14 @@ typedef struct debris {
4349
int model_num; // What model this uses
4450
int model_instance_num; // What model instance this uses - needed for arcs
4551
int submodel_num; // What submodel this uses
46-
TIMESTAMP next_fireball; // When to start a fireball
52+
TIMESTAMP arc_next_time; // When the next damage/emp arc will be created.
4753
bool is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment
4854
int species; // What species this is from. -1 if don't care.
49-
TIMESTAMP fire_timeout; // timestamp that holds time for fireballs to stop appearing
55+
TIMESTAMP arc_timeout; // timestamp that holds time for arcs to stop appearing
5056
TIMESTAMP sound_delay; // timestamp to signal when sound should start
5157
fix time_started; // time when debris was created
5258

53-
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
54-
TIMESTAMP arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. Invalid is not used
59+
SCP_vector<debris_electrical_arc> electrical_arcs;
5560
int arc_frequency; // Starts at 1000, gets bigger
5661

5762
int parent_alt_name;

code/model/model.h

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ extern const char *Subsystem_types[SUBSYSTEM_MAX];
9696

9797
#define MAX_SPLIT_PLANE 5 // number of artist specified split planes (used in big ship explosions)
9898

99+
// Electrical Arc Effect Info
100+
// Sets a spark for this submodel between vertex v1 and v2
101+
struct electrical_arc
102+
{
103+
color primary_color_1;
104+
color primary_color_2;
105+
color secondary_color;
106+
float width; // only used for MARC_TYPE_SHIP and MARC_TYPE_SCRIPTED
107+
vec3d endpoint_1;
108+
vec3d endpoint_2;
109+
ubyte type; // see MARC_TYPE_* defines
110+
ubyte segment_depth; // number of times to divide the arc into segments
111+
};
112+
113+
struct model_electrical_arc : electrical_arc
114+
{
115+
const SCP_vector<vec3d> *persistent_arc_points;
116+
};
117+
99118
// Data specific to a particular instance of a submodel.
100119
struct submodel_instance
101120
{
@@ -127,31 +146,11 @@ struct submodel_instance
127146
vec3d canonical_offset = vmd_zero_vector;
128147
vec3d canonical_prev_offset = vmd_zero_vector;
129148

130-
// --- these fields used to be in bsp_info ---
131-
132-
// Electrical Arc Effect Info
133-
// Sets a spark for this submodel between vertex v1 and v2
134-
int num_arcs = 0; // See model_add_arc for more info
135-
color arc_primary_color_1[MAX_ARC_EFFECTS];
136-
color arc_primary_color_2[MAX_ARC_EFFECTS];
137-
color arc_secondary_color[MAX_ARC_EFFECTS];
138-
float arc_width[MAX_ARC_EFFECTS]; // only used for MARC_TYPE_SHIP and MARC_TYPE_SCRIPTED
139-
vec3d arc_pts[MAX_ARC_EFFECTS][2];
140-
ubyte arc_type[MAX_ARC_EFFECTS]; // see MARC_TYPE_* defines
149+
SCP_vector<model_electrical_arc> electrical_arcs;
141150

142151
//SMI-Specific movement axis. Only valid in MOVEMENT_TYPE_TRIGGERED.
143-
vec3d rotation_axis;
144-
vec3d translation_axis;
145-
146-
submodel_instance()
147-
{
148-
memset(&arc_pts, 0, MAX_ARC_EFFECTS * 2 * sizeof(vec3d));
149-
memset(&arc_primary_color_1, 0, MAX_ARC_EFFECTS * sizeof(color));
150-
memset(&arc_primary_color_2, 0, MAX_ARC_EFFECTS * sizeof(color));
151-
memset(&arc_secondary_color, 0, MAX_ARC_EFFECTS * sizeof(color));
152-
memset(&arc_type, 0, MAX_ARC_EFFECTS * sizeof(ubyte));
153-
memset(&arc_width, 0, MAX_ARC_EFFECTS * sizeof(float));
154-
}
152+
vec3d rotation_axis = vmd_zero_vector;
153+
vec3d translation_axis = vmd_zero_vector;
155154
};
156155

157156
#define TM_BASE_TYPE 0 // the standard base map
@@ -1220,8 +1219,8 @@ extern void model_set_up_techroom_instance(ship_info *sip, int model_instance_nu
12201219
void model_replicate_submodel_instance(polymodel *pm, polymodel_instance *pmi, int submodel_num, flagset<Ship::Subsystem_Flags>& flags);
12211220

12221221
// Adds an electrical arcing effect to a submodel
1223-
void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi);
1224-
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);
1222+
void model_instance_clear_arcs(const polymodel *pm, polymodel_instance *pmi);
1223+
void model_instance_add_arc(const polymodel *pm, polymodel_instance *pmi, int sub_model_num, const vec3d *v1, const vec3d *v2, const SCP_vector<vec3d> *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);
12251224

12261225
// Gets two random points on the surface of a submodel
12271226
extern vec3d submodel_get_random_point(int model_num, int submodel_num, int seed = -1);

code/model/modelinterp.cpp

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -785,20 +785,13 @@ void model_draw_bay_paths_htl(int model_num)
785785
gr_set_cull(cull);
786786
}
787787

788-
static const int MAX_ARC_SEGMENT_POINTS = 50;
789-
int Num_arc_segment_points = 0;
790-
vec3d Arc_segment_points[MAX_ARC_SEGMENT_POINTS];
791-
792-
void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth )
788+
void interp_generate_arc_segment(SCP_vector<vec3d> &arc_segment_points, const vec3d *v1, const vec3d *v2, ubyte depth_limit, ubyte depth)
793789
{
794790
float d = vm_vec_dist_quick( v1, v2 );
795-
const float scaler = 0.30f;
796-
797-
if ( (d < scaler) || (depth > 4) ) {
798-
// the real limit appears to be 33, so we should never hit this unless the code changes
799-
Assert( Num_arc_segment_points < MAX_ARC_SEGMENT_POINTS );
791+
constexpr float scaler = 0.30f;
800792

801-
memcpy( &Arc_segment_points[Num_arc_segment_points++], v2, sizeof(vec3d) );
793+
if ( (d < scaler) || (depth > depth_limit) ) {
794+
arc_segment_points.push_back(*v2);
802795
} else {
803796
// divide in half
804797
vec3d tmp;
@@ -809,8 +802,8 @@ void interp_render_arc_segment(const vec3d *v1, const vec3d *v2, int depth )
809802
tmp.xyz.z += (frand() - 0.5f) * d * scaler;
810803

811804
// add additional point
812-
interp_render_arc_segment( v1, &tmp, depth+1 );
813-
interp_render_arc_segment( &tmp, v2, depth+1 );
805+
interp_generate_arc_segment( arc_segment_points, v1, &tmp, depth_limit, depth+1 );
806+
interp_generate_arc_segment( arc_segment_points, &tmp, v2, depth_limit, depth+1 );
814807
}
815808
}
816809

code/model/modelread.cpp

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5195,17 +5195,17 @@ void model_do_intrinsic_motions(object *objp)
51955195
}
51965196
}
51975197

5198-
void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi)
5198+
void model_instance_clear_arcs(const polymodel *pm, polymodel_instance *pmi)
51995199
{
52005200
Assert(pm->id == pmi->model_num);
52015201

52025202
for (int i = 0; i < pm->n_models; ++i) {
5203-
pmi->submodel[i].num_arcs = 0; // Turn off any electric arcing effects
5203+
pmi->submodel[i].electrical_arcs.clear(); // Turn off any electric arcing effects
52045204
}
52055205
}
52065206

52075207
// Adds an electrical arcing effect to a submodel
5208-
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 )
5208+
void model_instance_add_arc(const polymodel *pm, polymodel_instance *pmi, int sub_model_num, const vec3d *v1, const vec3d *v2, const SCP_vector<vec3d> *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)
52095209
{
52105210
Assert(pm->id == pmi->model_num);
52115211

@@ -5220,19 +5220,20 @@ void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_mode
52205220
if ( sub_model_num >= pm->n_models ) return;
52215221
auto smi = &pmi->submodel[sub_model_num];
52225222

5223-
if ( smi->num_arcs < MAX_ARC_EFFECTS ) {
5224-
smi->arc_type[smi->num_arcs] = (ubyte)arc_type;
5225-
smi->arc_pts[smi->num_arcs][0] = *v1;
5226-
smi->arc_pts[smi->num_arcs][1] = *v2;
5223+
smi->electrical_arcs.emplace_back();
5224+
auto &new_arc = smi->electrical_arcs.back();
52275225

5228-
if (arc_type == MARC_TYPE_SHIP || arc_type == MARC_TYPE_SCRIPTED) {
5229-
smi->arc_primary_color_1[smi->num_arcs] = *primary_color_1;
5230-
smi->arc_primary_color_2[smi->num_arcs] = *primary_color_2;
5231-
smi->arc_secondary_color[smi->num_arcs] = *secondary_color;
5232-
smi->arc_width[smi->num_arcs] = width;
5233-
}
5226+
new_arc.type = arc_type;
5227+
new_arc.endpoint_1 = *v1;
5228+
new_arc.endpoint_2 = *v2;
5229+
new_arc.persistent_arc_points = persistent_arc_points;
5230+
new_arc.segment_depth = segment_depth;
52345231

5235-
smi->num_arcs++;
5232+
if (arc_type == MARC_TYPE_SHIP || arc_type == MARC_TYPE_SCRIPTED) {
5233+
new_arc.primary_color_1 = *primary_color_1;
5234+
new_arc.primary_color_2 = *primary_color_2;
5235+
new_arc.secondary_color = *secondary_color;
5236+
new_arc.width = width;
52365237
}
52375238
}
52385239

0 commit comments

Comments
 (0)