Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements to the FSO electrical arc system #6347

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 55 additions & 39 deletions code/debris/debris.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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!!!
Expand Down Expand Up @@ -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; i<MAX_DEBRIS_ARCS; ++i) {
if ( !db->arc_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;
Expand All @@ -318,16 +312,20 @@ void debris_process_post(object * obj, float frame_time)
}
}

for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
if ( db->arc_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;
}
}
}
Expand Down Expand Up @@ -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; i<MAX_DEBRIS_ARCS; ++i) { // NOLINT
db->arc_timestamp[i] = TIMESTAMP::invalid();
}
db->electrical_arcs.clear();

if ( db->is_hull ) {
// Percent of debris pieces with arcs controlled via table (default 50%)
Expand All @@ -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<Object::Object_Flags> default_flags;
default_flags.set(Object::Object_Flags::Renders);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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; i<MAX_DEBRIS_ARCS; i++ ) {
if ( db->arc_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 );
}
}
}
Expand Down Expand Up @@ -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];
}
15 changes: 10 additions & 5 deletions code/debris/debris.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Debris_Flags> flags; // See DEBRIS_??? defines
Expand All @@ -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<debris_electrical_arc> electrical_arcs;
int arc_frequency; // Starts at 1000, gets bigger

int parent_alt_name;
Expand Down
49 changes: 24 additions & 25 deletions code/model/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<vec3d> *persistent_arc_points;
};

// Data specific to a particular instance of a submodel.
struct submodel_instance
{
Expand Down Expand Up @@ -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<model_electrical_arc> 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
Expand Down Expand Up @@ -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<Ship::Subsystem_Flags>& 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<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);

// 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);
Expand Down
19 changes: 6 additions & 13 deletions code/model/modelinterp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<vec3d> &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;
Expand All @@ -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 );
}
}

Expand Down
29 changes: 15 additions & 14 deletions code/model/modelread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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)
{
Assert(pm->id == pmi->model_num);

Expand All @@ -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;
}
}

Expand Down
Loading
Loading