diff --git a/code/asteroid/asteroid.cpp b/code/asteroid/asteroid.cpp index 54c03cf25a9..dec4d56b2f1 100644 --- a/code/asteroid/asteroid.cpp +++ b/code/asteroid/asteroid.cpp @@ -329,6 +329,11 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi } asp->objnum = objnum; + asp->model_instance_num = -1; + + if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_DUMB_ROTATE) { + asp->model_instance_num = model_create_instance(false, asip->model_num[asteroid_subtype]); + } // Add to Asteroid_used_list asteroid_obj_list_add(objnum); @@ -828,10 +833,12 @@ void asteroid_delete( object * obj ) asp = &Asteroids[num]; - Assert( Num_asteroids >= 0 ); + if (asp->model_instance_num >= 0) + model_delete_instance(asp->model_instance_num); asp->flags = 0; Num_asteroids--; + Assert(Num_asteroids >= 0); asteroid_obj_list_remove( obj ); } @@ -930,7 +937,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos if ( asteroid_hit_info == NULL ) { // asteroid weapon collision Assert( other_obj->type == OBJ_WEAPON ); - mc.model_instance_num = -1; + mc.model_instance_num = Asteroids[num].model_instance_num; mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].model_num[asteroid_subtype]; // Fill in the model to check model_clear_instance( mc.model_num ); mc.orient = &pasteroid->orient; // The object's orient @@ -1107,7 +1114,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos } else { // Asteroid is heavier obj - mc.model_instance_num = -1; + mc.model_instance_num = Asteroids[num].model_instance_num; mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].model_num[asteroid_subtype]; // Fill in the model to check model_clear_instance( mc.model_num ); mc.orient = &pasteroid->orient; // The object's orient diff --git a/code/asteroid/asteroid.h b/code/asteroid/asteroid.h index d3c64ad100e..d02939c7849 100644 --- a/code/asteroid/asteroid.h +++ b/code/asteroid/asteroid.h @@ -98,6 +98,7 @@ class asteroid_info typedef struct asteroid { int flags; int objnum; + int model_instance_num; int asteroid_type; // 0..MAX_DEBRIS_TYPES int asteroid_subtype; // Index in asteroid_info for modelnum and modelp int check_for_wrap; // timestamp to check for asteroid wrapping around field diff --git a/code/fred2/management.cpp b/code/fred2/management.cpp index 09e17ee75c7..183b4b91f63 100644 --- a/code/fred2/management.cpp +++ b/code/fred2/management.cpp @@ -135,6 +135,7 @@ int query_ship_name_duplicate(int ship); char *reg_read_string( char *section, char *name, char *default_value ); extern int Nmodel_num; +extern int Nmodel_instance_num; extern matrix Nmodel_orient; extern int Nmodel_bitmap; @@ -955,6 +956,7 @@ void clear_mission() Nmodel_flags = DEFAULT_NMODEL_FLAGS; Nmodel_num = -1; + Nmodel_instance_num = -1; vm_set_identity(&Nmodel_orient); Nmodel_bitmap = -1; diff --git a/code/model/model.h b/code/model/model.h index 50745e89f0f..8319e950ab1 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -35,6 +35,7 @@ extern int model_render_flags_size; #define MOVEMENT_TYPE_ROT_SPECIAL 2 // for turrets only #define MOVEMENT_TYPE_TRIGGERED 3 //triggered rotation #define MOVEMENT_TYPE_LOOK_AT 4 // the subobject is always looking at a 'look at' subobject, as best it can - Bobboau +#define MOVEMENT_TYPE_DUMB_ROTATE 5 // DA 11/13/98 Reordered to account for difference between max and game @@ -121,19 +122,18 @@ typedef struct polymodel_instance { #define MSS_FLAG_NO_SS_TARGETING (1 << 16) // toggles the subsystem targeting for the turret #define MSS_FLAG_TURRET_RESET_IDLE (1 << 17) // makes turret reset to their initial position if the target is out of field of view #define MSS_FLAG_TURRET_ALT_MATH (1 << 18) // tells the game to use additional calculations should turret have a defined y fov -#define MSS_FLAG_DUM_ROTATES (1 << 19) // Bobboau -#define MSS_FLAG_CARRY_SHOCKWAVE (1 << 20) // subsystem - even with 'carry no damage' flag - will carry shockwave damage to the hull -#define MSS_FLAG_ALLOW_LANDING (1 << 21) // This subsystem can be landed on -#define MSS_FLAG_FOV_EDGE_CHECK (1 << 22) // Tells the game to use better FOV edge checking with this turret -#define MSS_FLAG_FOV_REQUIRED (1 << 23) // Tells game not to allow this turret to attempt targeting objects out of FOV -#define MSS_FLAG_NO_REPLACE (1 << 24) // set the subsys not to draw replacement ('destroyed') model -#define MSS_FLAG_NO_LIVE_DEBRIS (1 << 25) // sets the subsys not to release live debris -#define MSS_FLAG_IGNORE_IF_DEAD (1 << 26) // tells homing missiles to ignore the subsys if its dead and home on to hull instead of earlier subsys pos -#define MSS_FLAG_ALLOW_VANISHING (1 << 27) // allows subsystem to vanish (prevents explosions & sounds effects from being played) -#define MSS_FLAG_DAMAGE_AS_HULL (1 << 28) // applies armor damage to subsystem instead of subsystem damage - FUBAR -#define MSS_FLAG_TURRET_LOCKED (1 << 29) // Turret starts locked by default - Sushi -#define MSS_FLAG_NO_AGGREGATE (1 << 30) // Don't include with aggregate subsystem types - Goober5000 -#define MSS_FLAG_TURRET_ANIM_WAIT (1 << 31) // Turret won't fire until animation is complete - Sushi +#define MSS_FLAG_CARRY_SHOCKWAVE (1 << 19) // subsystem - even with 'carry no damage' flag - will carry shockwave damage to the hull +#define MSS_FLAG_ALLOW_LANDING (1 << 20) // This subsystem can be landed on +#define MSS_FLAG_FOV_EDGE_CHECK (1 << 21) // Tells the game to use better FOV edge checking with this turret +#define MSS_FLAG_FOV_REQUIRED (1 << 22) // Tells game not to allow this turret to attempt targeting objects out of FOV +#define MSS_FLAG_NO_REPLACE (1 << 23) // set the subsys not to draw replacement ('destroyed') model +#define MSS_FLAG_NO_LIVE_DEBRIS (1 << 24) // sets the subsys not to release live debris +#define MSS_FLAG_IGNORE_IF_DEAD (1 << 25) // tells homing missiles to ignore the subsys if its dead and home on to hull instead of earlier subsys pos +#define MSS_FLAG_ALLOW_VANISHING (1 << 26) // allows subsystem to vanish (prevents explosions & sounds effects from being played) +#define MSS_FLAG_DAMAGE_AS_HULL (1 << 27) // applies armor damage to subsystem instead of subsystem damage - FUBAR +#define MSS_FLAG_TURRET_LOCKED (1 << 28) // Turret starts locked by default - Sushi +#define MSS_FLAG_NO_AGGREGATE (1 << 29) // Don't include with aggregate subsystem types - Goober5000 +#define MSS_FLAG_TURRET_ANIM_WAIT (1 << 30) // Turret won't fire until animation is complete - Sushi #define MSS_FLAG2_PLAYER_TURRET_SOUND (1 << 0) #define MSS_FLAG2_TURRET_ONLY_TARGET_IF_CAN_FIRE (1 << 1) // Turrets only target things they're allowed to shoot at (e.g. if check-hull fails, won't keep targeting) @@ -300,7 +300,7 @@ class bsp_info public: bsp_info() : movement_type(-1), movement_axis(0), can_move(false), bsp_data_size(0), bsp_data(NULL), collision_tree_index(-1), - rad(0.0f), blown_off(0), my_replacement(-1), i_replace(-1), is_live_debris(0), num_live_debris(0), sii(NULL), + rad(0.0f), blown_off(0), my_replacement(-1), i_replace(-1), is_live_debris(0), num_live_debris(0), is_thruster(0), is_damaged(0), parent(-1), num_children(0), first_child(-1), next_sibling(-1), num_details(0), num_arcs(0), outline_buffer(NULL), n_verts_outline(0), render_sphere_radius(0.0f), use_render_box(0), use_render_box_offset(false), use_render_sphere(0), use_render_sphere_offset(false), gun_rotation(false), no_collisions(false), @@ -352,8 +352,6 @@ class bsp_info int num_live_debris; // num live debris models assocaiated with a submodel int live_debris[MAX_LIVE_DEBRIS]; // array of live debris submodels for a submodel - submodel_instance_info *sii; // stuff needed for collision from rotations - int is_thruster; int is_damaged; @@ -623,6 +621,7 @@ typedef struct insignia { #define PM_FLAG_AUTOCEN (1<<1) // contains autocentering info #define PM_FLAG_TRANS_BUFFER (1<<2) // render transparency buffer #define PM_FLAG_BATCHED (1<<3) // this model can be batch rendered +#define PM_FLAG_HAS_DUMB_ROTATE (1<<4) // whether this model has a dumb-rotate submodel somewhere // Goober5000 class texture_info @@ -831,7 +830,7 @@ void model_instance_free_all(); // Loads a model from disk and returns the model number it loaded into. int model_load(char *filename, int n_subsystems, model_subsystem *subsystems, int ferror = 1, int duplicate = 0); -int model_create_instance(int model_num); +int model_create_instance(bool is_ship, int model_num); void model_delete_instance(int model_instance_num); // Goober5000 @@ -998,7 +997,8 @@ extern void model_make_turret_matrix(int model_num, model_subsystem * turret ); // Rotates the angle of a submodel. Use this so the right unlocked axis // gets stuffed. -extern void submodel_rotate(model_subsystem *psub, submodel_instance_info * sii); +extern void submodel_rotate(model_subsystem *psub, submodel_instance_info *sii); +extern void submodel_rotate(bsp_info *sm, submodel_instance_info *sii); // Rotates the angle of a submodel. Use this so the right unlocked axis // gets stuffed. Does this for stepped rotations @@ -1054,8 +1054,8 @@ void model_set_instance_info(submodel_instance_info *sii, float turn_rate, float extern void model_clear_instance_info(submodel_instance_info * sii); // Sets the submodel instance data in a submodel -extern void model_set_instance(int model_num, int sub_model_num, submodel_instance_info * sii, int flags = 0 ); -extern void model_set_instance_techroom(int model_num, int sub_model_num, float angle_1, float angle_2 ); +extern void model_set_instance(int model_num, int sub_model_num, submodel_instance_info *sii, int flags = 0); +extern void model_set_instance_techroom(int model_num, int sub_model_num, float angle_1, float angle_2); void model_update_instance(int model_instance_num, int sub_model_num, submodel_instance_info *sii, int flags); void model_instance_dumb_rotation(int model_instance_num); @@ -1370,7 +1370,7 @@ void model_finish_cloak(int full_cloak); void model_do_look_at(int model_num); //Bobboau -void model_do_dumb_rotation(int modelnum); //Bobboau +void model_do_dumb_rotations(int model_instance_num = -1); int model_should_render_engine_glow(int objnum, int bank_obj); diff --git a/code/model/modelinterp.cpp b/code/model/modelinterp.cpp index 289bebcd320..1ec98f4e556 100644 --- a/code/model/modelinterp.cpp +++ b/code/model/modelinterp.cpp @@ -2011,8 +2011,6 @@ void model_render_DEPRECATED(int model_num, matrix *orient, vec3d * pos, uint fl polymodel *pm = model_get(model_num); - model_do_dumb_rotation(model_num); - if (flags & MR_FORCE_CLAMP) gr_set_texture_addressing(TMAP_ADDRESS_CLAMP); diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index 01124ed0abc..d9b5ba3bc51 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -19,6 +19,7 @@ #define MODEL_LIB +#include "asteroid/asteroid.h" #include "bmpman/bmpman.h" #include "cfile/cfile.h" #include "cmdline/cmdline.h" @@ -34,6 +35,7 @@ #include "parse/parselo.h" #include "render/3dinternal.h" #include "ship/ship.h" +#include "weapon/weapon.h" flag_def_list model_render_flags[] = { @@ -64,7 +66,6 @@ CFILE *ss_fp = NULL; // file pointer used to dump subsystem information char model_filename[_MAX_PATH]; // temp used to store filename char debug_name[_MAX_PATH]; int ss_warning_shown = 0; // have we shown the warning dialog concerning the subsystems? -char Global_filename[256]; int Model_ram = 0; // How much RAM the models use total #endif @@ -122,6 +123,40 @@ int Num_dock_type_names = sizeof(Dock_type_names) / sizeof(flag_def_list); SCP_vector glowpoint_bank_overrides; + +// Goober5000 - reimplementation of Bobboau's $dumb_rotation feature in a way that works with the rest of the model instance system +// note: since these data types are only ever used in this file, they don't need to be in model.h + +class submodel_dumb_rotation +{ +public: + int submodel_num; + submodel_instance_info submodel_info_1; + + submodel_dumb_rotation(int _submodel_num, float _turn_rate) + : submodel_num(_submodel_num) + { + memset(&submodel_info_1, 0, sizeof(submodel_info_1)); + submodel_info_1.cur_turn_rate = _turn_rate; + submodel_info_1.desired_turn_rate = _turn_rate; + } +}; + +class dumb_rotation +{ +public: + bool is_ship; + int model_instance_num; + SCP_vector list; + + dumb_rotation(bool _is_ship, int _model_instance_num) + : is_ship(_is_ship), model_instance_num(_model_instance_num), list() + {} +}; + +SCP_vector Dumb_rotations; + + // Free up a model, getting rid of all its memory // With the basic page in system this can be called from outside of modelread.cpp void model_unload(int modelnum, int force) @@ -444,7 +479,7 @@ void model_copy_subsystems( int n_subsystems, model_subsystem *d_sp, model_subsy } // routine to get/set subsystem information -static void set_subsystem_info( model_subsystem *subsystemp, char *props, char *dname ) +static void set_subsystem_info(int model_num, model_subsystem *subsystemp, char *props, char *dname) { char *p; char buf[64]; @@ -496,16 +531,23 @@ static void set_subsystem_info( model_subsystem *subsystemp, char *props, char * subsystemp->type = SUBSYSTEM_ACTIVATION; } else { // If unrecognized type, set to unknown so artist can continue working... subsystemp->type = SUBSYSTEM_UNKNOWN; - mprintf(("Potential problem found: Unrecognized subsystem type '%s', believed to be in ship %s\n", dname, Global_filename)); + mprintf(("Subsystem '%s' on ship %s is not recognized as a common subsystem type\n", dname, model_get(model_num)->filename)); } - if ( (strstr(props, "$triggered:")) != NULL ) { + if ( (strstr(props, "$triggered")) != NULL ) { subsystemp->flags |= MSS_FLAG_ROTATES; subsystemp->flags |= MSS_FLAG_TRIGGERED; } + // Dumb-Rotating subsystem + if ((p = strstr(props, "$dumb_rotate")) != NULL) { + // no special subsystem handling needed here, but make sure we didn't specify both methods + if (strstr(props, "$rotate") != NULL) { + Warning(LOCATION, "Subsystem '%s' on ship %s cannot have both rotation and dumb-rotation!", dname, model_get(model_num)->filename); + } + } // Rotating subsystem - if ( (p = strstr(props, "$rotate")) != NULL) { + else if ((p = strstr(props, "$rotate")) != NULL) { subsystemp->flags |= MSS_FLAG_ROTATES; // get time for (a) complete rotation (b) step (c) activation @@ -637,7 +679,7 @@ void do_new_subsystem( int n_subsystems, model_subsystem *slist, int subobj_num, subsystemp->model_num = model_num; subsystemp->pnt = *pnt; // use the offset to get the center point of the subsystem subsystemp->radius = rad; - set_subsystem_info( subsystemp, props, subobj_name); + set_subsystem_info(model_num, subsystemp, props, subobj_name); strcpy_s(subsystemp->subobj_name, subobj_name); // copy the object name return; } @@ -909,10 +951,6 @@ int read_model_file(polymodel * pm, char *filename, int n_subsystems, model_subs int i,j; vec3d temp_vec; -#ifndef NDEBUG - strcpy_s(Global_filename, filename); -#endif - // little test code i used in fred2 //char pwd[128]; //getcwd(pwd, 128); @@ -1210,20 +1248,12 @@ int read_model_file(polymodel * pm, char *filename, int n_subsystems, model_subs if (strstr(pm->submodel[n].name, "thruster")) { pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE; pm->submodel[n].movement_axis = MOVEMENT_AXIS_NONE; - }else if(strstr(props, "$triggered:")){ + } else if(strstr(props, "$triggered")) { pm->submodel[n].movement_type = MOVEMENT_TYPE_TRIGGERED; } } - // Sets can_move on submodels which are of a rotating type or which have such a parent somewhere down the hierarchy - if ( (pm->submodel[n].movement_type != MOVEMENT_TYPE_NONE) - || strstr(props, "$triggered:") || strstr(props, "$rotate") || strstr(props, "$dumb_rotate:") || strstr(props, "$gun_rotation:") || strstr(props, "$gun_rotation") ) { - pm->submodel[n].can_move = true; - } else if (pm->submodel[n].parent > -1 && pm->submodel[pm->submodel[n].parent].can_move) { - pm->submodel[n].can_move = true; - } - - if ( ( p = strstr(props, "$look_at:")) != NULL ) { + if ( ( p = strstr(props, "$look_at")) != NULL ) { pm->submodel[n].movement_type = MOVEMENT_TYPE_LOOK_AT; get_user_prop_value(p+9, pm->submodel[n].look_at); pm->submodel[n].look_at_num = -2; // Set this to -2 to mark it as something we need to work out the correct subobject number for later, after all subobjects have been processed @@ -1232,13 +1262,24 @@ int read_model_file(polymodel * pm, char *filename, int n_subsystems, model_subs pm->submodel[n].look_at_num = -1; // No look_at } - if ( ( p = strstr(props, "$dumb_rotate:") ) != NULL ) { - pm->submodel[n].movement_type = MSS_FLAG_DUM_ROTATES; + // note, this should come BEFORE do_new_subsystem() for proper error handling (to avoid both rotating and dumb-rotating submodel) + if ( ( p = strstr(props, "$dumb_rotate") ) != NULL ) { + pm->submodel[n].movement_type = MOVEMENT_TYPE_DUMB_ROTATE; pm->submodel[n].dumb_turn_rate = (float)atof(p+13); + + pm->flags |= PM_FLAG_HAS_DUMB_ROTATE; } else { pm->submodel[n].dumb_turn_rate = 0.0f; } + // Sets can_move on submodels which are of a rotating type or which have such a parent somewhere down the hierarchy + if ((pm->submodel[n].movement_type != MOVEMENT_TYPE_NONE) + || strstr(props, "$triggered") || strstr(props, "$rotate") || strstr(props, "$gun_rotation")) { + pm->submodel[n].can_move = true; + } else if (pm->submodel[n].parent >= 0 && pm->submodel[pm->submodel[n].parent].can_move) { + pm->submodel[n].can_move = true; + } + if ( pm->submodel[n].name[0] == '\0' ) { strcpy_s(pm->submodel[n].name, "unknown object name"); } @@ -1262,8 +1303,13 @@ int read_model_file(polymodel * pm, char *filename, int n_subsystems, model_subs } // adding a warning if rotation is specified without movement axis. - if ((pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) && (pm->submodel[n].movement_axis == MOVEMENT_AXIS_NONE)){ - Warning(LOCATION, "Rotation without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename); + if (pm->submodel[n].movement_axis == MOVEMENT_AXIS_NONE) { + if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) { + Warning(LOCATION, "Rotation without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename); + } + else if (pm->submodel[n].movement_type == MOVEMENT_TYPE_DUMB_ROTATE) { + Warning(LOCATION, "Dumb rotation without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename); + } } /* if ( strstr(props, "$nontargetable")!= NULL ) { @@ -2709,7 +2755,7 @@ int model_load(char *filename, int n_subsystems, model_subsystem *subsystems, in return pm->id; } -int model_create_instance(int model_num) +int model_create_instance(bool is_ship, int model_num) { int i = 0; int open_slot = -1; @@ -2723,6 +2769,7 @@ int model_create_instance(int model_num) polymodel_instance *pmi = (polymodel_instance*)vm_malloc(sizeof(polymodel_instance)); memset(pmi, 0, sizeof(polymodel_instance)); + pmi->model_num = model_num; // if not found, create a slot if ( open_slot < 0 ) { @@ -2740,7 +2787,22 @@ int model_create_instance(int model_num) model_clear_submodel_instance( &pmi->submodel[i], &pm->submodel[i] ); } - pmi->model_num = model_num; + // add dumb_rotate instances if this model is dumb-rotating + if (pm->flags & PM_FLAG_HAS_DUMB_ROTATE) { + dumb_rotation dumb_rot(is_ship, open_slot); + + for (i = 0; i < pm->n_models; i++) { + if (pm->submodel[i].movement_type == MOVEMENT_TYPE_DUMB_ROTATE) { + dumb_rot.list.push_back(submodel_dumb_rotation(i, pm->submodel[i].dumb_turn_rate)); + } + } + + if (dumb_rot.list.empty()) { + Assertion(!dumb_rot.list.empty(), "This model has the HAS_DUMB_ROTATE flag; why doesn't it have a dumb-rotating submodel?"); + } else { + Dumb_rotations.push_back(dumb_rot); + } + } return open_slot; } @@ -2760,6 +2822,14 @@ void model_delete_instance(int model_instance_num) vm_free(pmi); Polygon_model_instances[model_instance_num] = NULL; + + // delete dumb rotations associated with this instance + for (auto dumb_it = Dumb_rotations.begin(); dumb_it != Dumb_rotations.end(); ++dumb_it) { + if (dumb_it->model_instance_num == model_instance_num) { + Dumb_rotations.erase(dumb_it); + break; + } + } } // ensure that the subsys path is at least SUBSYS_PATH_DIST from the @@ -3327,7 +3397,7 @@ void model_get_rotating_submodel_axis(vec3d *model_axis, vec3d *world_axis, int polymodel *pm = model_get(modelnum); bsp_info *sm = &pm->submodel[submodel_num]; - Assert(sm->movement_type == MOVEMENT_TYPE_ROT); + Assert(sm->movement_type == MOVEMENT_TYPE_ROT || sm->movement_type == MOVEMENT_TYPE_DUMB_ROTATE); if (sm->movement_axis == MOVEMENT_AXIS_X) { vm_vec_make(model_axis, 1.0f, 0.0f, 0.0f); @@ -3556,8 +3626,7 @@ void submodel_look_at(polymodel *pm, int mn) } -// Rotates the angle of a submodel. Use this so the right unlocked axis -// gets stuffed. +// Rotates the angle of a submodel, when the submodel has a subsystem (which is almost always the case) void submodel_rotate(model_subsystem *psub, submodel_instance_info *sii) { bsp_info * sm; @@ -3569,6 +3638,13 @@ void submodel_rotate(model_subsystem *psub, submodel_instance_info *sii) if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return; + submodel_rotate(sm, sii); +} + +// Rotates the angle of a submodel. If the submodel has a subsystem, the execution flow should first go through the other +// submodel_rotate function before this one. (This function is called directly in the case of dumb_rotation.) +void submodel_rotate(bsp_info *sm, submodel_instance_info *sii) +{ // save last angles sii->prev_angs = sii->angs; @@ -4329,21 +4405,49 @@ int rotating_submodel_has_ship_subsys(int submodel, ship *shipp) return found; } +/* + * Get all submodel indexes that satisfy the following: + * 1) Have the rotating or dumb-rotating movement type + * 2) Are currently rotating (i.e. actually moving and not part of the superstructure due to being destroyed or replaced) + * 3) Are not rotating too far for collision detection (c.f. MAX_SUBMODEL_COLLISION_ROT_ANGLE) + */ void model_get_rotating_submodel_list(SCP_vector *submodel_vector, object *objp) { - Assert(objp->type == OBJ_SHIP); - - // Check if not currently rotating - then treat as part of superstructure. - int modelnum = Ship_info[Ships[objp->instance].ship_info_index].model_num; - polymodel *pm = model_get(modelnum); - bsp_info *child_submodel; + Assert(objp->type == OBJ_SHIP || objp->type == OBJ_WEAPON || objp->type == OBJ_ASTEROID); - child_submodel = &pm->submodel[pm->detail[0]]; + int model_instance_num; + int model_num; + if (objp->type == OBJ_SHIP) { + model_instance_num = Ships[objp->instance].model_instance_num; + model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; + } + else if (objp->type == OBJ_WEAPON) { + model_instance_num = Weapons[objp->instance].model_instance_num; + if (model_instance_num < 0) { + return; + } + model_num = Weapon_info[Weapons[objp->instance].weapon_info_index].model_num; + } + else if (objp->type == OBJ_ASTEROID) { + model_instance_num = Asteroids[objp->instance].model_instance_num; + if (model_instance_num < 0) { + return; + } + model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype]; + } + else { + return; + } + + polymodel *pm = model_get(model_num); + bsp_info *child_submodel = &pm->submodel[pm->detail[0]]; if(child_submodel->no_collisions) { // if detail0 has $no_collision set dont check childs return; } + polymodel_instance *pmi = model_get_instance(model_instance_num); + int i = child_submodel->first_child; while ( i >= 0 ) { child_submodel = &pm->submodel[i]; @@ -4351,45 +4455,21 @@ void model_get_rotating_submodel_list(SCP_vector *submodel_vector, object * // Don't check it or its children if it is destroyed or it is a replacement (non-moving) if ( !child_submodel->blown_off && (child_submodel->i_replace == -1) && !child_submodel->no_collisions && !child_submodel->nocollide_this_only) { - // Only look for submodels that rotate - if (child_submodel->movement_type == MOVEMENT_TYPE_ROT) { + // Only look for submodels that rotate or dumb-rotate + if (child_submodel->movement_type == MOVEMENT_TYPE_ROT || child_submodel->movement_type == MOVEMENT_TYPE_DUMB_ROTATE) { - // find ship subsys and check submodel rotation is less than max allowed. - ship *pship = &Ships[objp->instance]; - ship_subsys *subsys; + // check submodel rotation is less than max allowed. + submodel_instance_info *sii = pmi->submodel[i].sii; - for ( subsys = GET_FIRST(&pship->subsys_list); subsys !=END_OF_LIST(&pship->subsys_list); subsys = GET_NEXT(subsys) ) { - Assert(subsys->system_info->model_num == modelnum); - if (i == subsys->system_info->subobj_num) { - // found the correct subsystem - now check delta rotation angle not too large - float delta_angle = get_submodel_delta_angle(&subsys->submodel_info_1); - if (delta_angle < MAX_SUBMODEL_COLLISION_ROT_ANGLE) { - submodel_vector->push_back(i); - } - break; - } + // found the correct submodel instance - now check delta rotation angle not too large + float delta_angle = get_submodel_delta_angle(sii); + if (delta_angle < MAX_SUBMODEL_COLLISION_ROT_ANGLE) { + submodel_vector->push_back(i); } } } i = child_submodel->next_sibling; } - - // error checking -//#define MODEL_CHECK -#ifdef MODEL_CHECK - ship *pship = &Ships[objp->instance]; - for (size_t idx=0; idxsize(); idx++) { - int valid = rotating_submodel_has_ship_subsys(submodel_vector[idx], pship); -// Assert( valid ); - if ( !valid ) { - - Warning( LOCATION, "Ship %s has rotating submodel [%s] without ship subsystem\n", pship->ship_name, pm->submodel[submodel_vector[idx]].name ); - pm->submodel[submodel_vector[idx]].movement_type &= ~MOVEMENT_TYPE_ROT; - submodel_vector->erase(submodel_vector->begin()+i); - } - } -#endif - } void model_get_submodel_tree_list(SCP_vector &submodel_vector, polymodel* pm, int mn) @@ -4508,9 +4588,6 @@ void model_clear_instance(int model_num) sm->angs.b = 0.0f; sm->angs.h = 0.0f; - // set pointer to other ship subsystem info [turn rate, accel, moment, axis, ...] - sm->sii = NULL; - sm->num_arcs = 0; // Turn off any electric arcing effects } @@ -4587,10 +4664,8 @@ void model_set_instance_info(submodel_instance_info *sii, float turn_rate, float sii->step_zero_timestamp = timestamp(); } - - // Sets the submodel instance data in a submodel (for all detail levels) -void model_set_instance(int model_num, int sub_model_num, submodel_instance_info * sii, int flags) +void model_set_instance(int model_num, int sub_model_num, submodel_instance_info *sii, int flags) { int i; polymodel * pm; @@ -4615,7 +4690,6 @@ void model_set_instance(int model_num, int sub_model_num, submodel_instance_info if ( sm->my_replacement > -1 ) { pm->submodel[sm->my_replacement].blown_off = 0; pm->submodel[sm->my_replacement].angs = sii->angs; - pm->submodel[sm->my_replacement].sii = sii; } } else { // If submodel isn't yet blown off and has a -destroyed replacement model, we prevent @@ -4627,7 +4701,6 @@ void model_set_instance(int model_num, int sub_model_num, submodel_instance_info // Set the angles sm->angs = sii->angs; - sm->sii = sii; // For all the detail levels of this submodel, set them also. for (i=0; inum_details; i++ ) { @@ -4713,94 +4786,74 @@ void model_update_instance(int model_instance_num, int sub_model_num, submodel_i } } -void model_instance_dumb_rotation_sub(polymodel_instance * pmi, polymodel *pm, int mn) +void model_do_dumb_rotations_sub(dumb_rotation *dr) { - while ( mn >= 0 ) { + polymodel_instance *pmi = model_get_instance(dr->model_instance_num); + Assert(pmi != nullptr); - bsp_info * sm = &pm->submodel[mn]; - submodel_instance *smi = &pmi->submodel[mn]; - - if ( sm->movement_type == MSS_FLAG_DUM_ROTATES ){ - float *ang; - int axis = sm->movement_axis; - switch ( axis ) { - case MOVEMENT_AXIS_X: - ang = &smi->angs.p; - break; - case MOVEMENT_AXIS_Z: - ang = &smi->angs.b; - break; - default: - case MOVEMENT_AXIS_Y: - ang = &smi->angs.h; - break; - } - *ang = sm->dumb_turn_rate * float(timestamp())/1000.0f; - *ang = ((*ang/(PI*2.0f))-float(int(*ang/(PI*2.0f))))*(PI*2.0f); - //this keeps ang from getting bigger than 2PI - } + // Handle all submodels which have $dumb_rotate + for (auto sub_it = dr->list.begin(); sub_it != dr->list.end(); ++sub_it) + { + polymodel *pm = model_get(pmi->model_num); + bsp_info *sm = &pm->submodel[sub_it->submodel_num]; + Assert(pm != nullptr && sm != nullptr); - if ( pm->submodel[mn].first_child > -1 ) - model_instance_dumb_rotation_sub(pmi, pm, pm->submodel[mn].first_child); + // First, calculate the angles for the rotation + submodel_rotate(sm, &sub_it->submodel_info_1); - mn = pm->submodel[mn].next_sibling; + // Now actually rotate the submodel instance + // (Since this is a dumb rotation, we have no associated subsystem, so pass 0 for subsystem flags.) + model_update_instance(dr->model_instance_num, sub_it->submodel_num, &sub_it->submodel_info_1, 0); } } -void model_instance_dumb_rotation(int model_instance_num) +// Handle the $dumb_rotate rotations for either a) a single ship model; or b) all non-ship models. The reason for the two cases is that ship_model_update_instance will +// be called for each ship via obj_move_all_post, but we also need to handle non-ship models once obj_move_all_post exits. Since the two processes are almost identical, +// they are both handled here. +// +// This function is quite a bit different than Bobboau's old model_do_dumb_rotation function. Whereas Bobboau used the brute-force technique of navigating through +// each model hierarchy as it was rendered, this function should be seen as a version of obj_move_all_post, but for models rather than objects. In fact, the only reason +// for the special ship case is that the ship dumb rotations kind of need to be handled where all the other ship rotations are. (Unless you want inconsistent collisions +// or damage sparks that aren't attached to models.) +// +// -- Goober5000 +void model_do_dumb_rotations(int model_instance_num) { - polymodel *pm; - polymodel_instance *pmi; - - pmi = model_get_instance(model_instance_num); - pm = model_get(pmi->model_num); - int mn = pm->detail[0]; + // we are handling a specific ship + if (model_instance_num >= 0) + { + for (auto dumb_it = Dumb_rotations.begin(); dumb_it != Dumb_rotations.end(); ++dumb_it) + { + if (dumb_it->model_instance_num == model_instance_num) + { + Assertion(dumb_it->is_ship, "This code path is only for ship dumb_rotations! See the comments associated with the model_do_dumb_rotations function!"); - model_instance_dumb_rotation_sub(pmi, pm, mn); -} + // we're just doing one ship, and in ship_model_update_instance, that ship's angles were already set to zero -void model_do_children_dumb_rotation(polymodel * pm, int mn) -{ - while ( mn >= 0 ) { + // Now update the angles in the submodels + model_do_dumb_rotations_sub(&(*dumb_it)); - bsp_info * sm = &pm->submodel[mn]; - - if ( sm->movement_type == MSS_FLAG_DUM_ROTATES ) { - float *ang; - int axis = sm->movement_axis; - switch(axis) { - case MOVEMENT_AXIS_X: - ang = &sm->angs.p; - break; - case MOVEMENT_AXIS_Z: - ang = &sm->angs.b; - break; - default: - case MOVEMENT_AXIS_Y: - ang = &sm->angs.h; - break; + // once we've handled this one ship, we're done + break; } - - *ang = sm->dumb_turn_rate * float(timestamp())/1000.0f; - *ang = ((*ang/(PI*2.0f))-float(int(*ang/(PI*2.0f))))*(PI*2.0f); - //this keeps ang from getting bigger than 2PI } + } + // we are handling all non-ships + else + { + for (auto dumb_it = Dumb_rotations.begin(); dumb_it != Dumb_rotations.end(); ++dumb_it) + { + if (!dumb_it->is_ship) + { + // Just as in ship_model_update_instance: first clear all the angles in the model to zero + model_clear_submodel_instances(dumb_it->model_instance_num); - if (pm->submodel[mn].first_child >-1) { - model_do_children_dumb_rotation(pm, pm->submodel[mn].first_child); + // Now update the angles in the submodels + model_do_dumb_rotations_sub(&(*dumb_it)); + } } - - mn = pm->submodel[mn].next_sibling; } } -void model_do_dumb_rotation(int pn){ - polymodel * pm; - - pm = model_get(pn); - int mn = pm->detail[0]; - - model_do_children_dumb_rotation(pm,mn); -} void model_do_children_look_at(polymodel * pm, int mn) { @@ -4829,7 +4882,7 @@ void model_init_submodel_axis_pt(submodel_instance_info *sii, int model_num, int vec3d p1, v1, p2, v2, int1; polymodel *pm = model_get(model_num); - Assert(pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_ROT); + Assert(pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_ROT || pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_DUMB_ROTATE); Assert(sii); mpoint1 = NULL; diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index b4e07fc5663..a38aa669640 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -9,6 +9,7 @@ #include +#include "asteroid/asteroid.h" #include "cmdline/cmdline.h" #include "gamesequence/gamesequence.h" #include "graphics/gropengldraw.h" @@ -23,6 +24,7 @@ #include "render/3dinternal.h" #include "ship/ship.h" #include "ship/shipfx.h" +#include "weapon/weapon.h" extern int Model_texturing; extern int Model_polys; @@ -2686,8 +2688,6 @@ void model_render_queue(model_render_params *interp, draw_list *scene, int model polymodel *pm = model_get(model_num); polymodel_instance *pmi = NULL; - model_do_dumb_rotation(model_num); - float light_factor = model_render_determine_light_factor(interp, pos, model_flags); if ( light_factor < (1.0f/32.0f) ) { @@ -2731,7 +2731,20 @@ void model_render_queue(model_render_params *interp, draw_list *scene, int model shipp = &Ships[objp->instance]; pmi = model_get_instance(shipp->model_instance_num); } + else if (pm->flags & PM_FLAG_HAS_DUMB_ROTATE) { + if (objp->type == OBJ_ASTEROID) + pmi = model_get_instance(Asteroids[objp->instance].model_instance_num); + else if (objp->type == OBJ_WEAPON) + pmi = model_get_instance(Weapons[objp->instance].model_instance_num); + else + Warning(LOCATION, "Unsupported object type %d for rendering dumb-rotate submodels!", objp->type); + } } + + // is this a skybox with a rotating submodel? + extern int Nmodel_num, Nmodel_instance_num; + if (model_num == Nmodel_num && Nmodel_instance_num >= 0) + pmi = model_get_instance(Nmodel_instance_num); // Set the flags we will pass to the tmapper uint tmap_flags = TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB; diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index 25195b1a594..7158fc344ef 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -601,6 +601,8 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ pmi = model_get_instance(model_instance_num); } else if (heavy->type == OBJ_ASTEROID) { pm = Asteroid_info[Asteroids[heavy->instance].asteroid_type].modelp[Asteroids[heavy->instance].asteroid_subtype]; + model_instance_num = Asteroids[heavy->instance].model_instance_num; + pmi = model_get_instance(model_instance_num); } else if (heavy->type == OBJ_DEBRIS) { pm = model_get(Debris[heavy->instance].model_num); } else { diff --git a/code/object/object.cpp b/code/object/object.cpp index 8f1bde4722b..2412658e7d8 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -1454,6 +1454,11 @@ void obj_move_all(float frametime) Script_system.RemHookVars(2, "User", "Target"); } + // Now that we've moved all the objects, move all the models that use dumb-rotate. We do that here because we already handled the + // ship models in obj_move_all_post, and this is more or less conceptually close enough to move the rest. (Originally all models + // were dumb-rotated here, but there are collision-related reasons for ship rotations to happen where they do, even dumb ones.) + model_do_dumb_rotations(); + // After all objects have been moved, move all docked objects. objp = GET_FIRST(&obj_used_list); while( objp !=END_OF_LIST(&obj_used_list) ) { diff --git a/code/parse/lua.cpp b/code/parse/lua.cpp index 74cdc7e53ac..8d27f0175d3 100644 --- a/code/parse/lua.cpp +++ b/code/parse/lua.cpp @@ -5034,6 +5034,10 @@ ADE_FUNC(checkRayCollision, l_Object, "vector Start Point, vector End Point, [bo if (obj->type == OBJ_SHIP) { model_instance_num = Ships[obj->instance].model_instance_num; + } else if (obj->type == OBJ_WEAPON) { + model_instance_num = Weapons[obj->instance].model_instance_num; + } else if (obj->type == OBJ_ASTEROID) { + model_instance_num = Asteroids[obj->instance].model_instance_num; } mc_info hull_check; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 3b1616c35d8..0287748ecc8 100755 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -9867,7 +9867,7 @@ int ship_create(matrix *orient, vec3d *pos, int ship_type, char *ship_name) model_anim_set_initial_states(shipp); - shipp->model_instance_num = model_create_instance(sip->model_num); + shipp->model_instance_num = model_create_instance(true, sip->model_num); shipp->time_created = Missiontime; @@ -10297,7 +10297,7 @@ void change_ship_type(int n, int ship_type, int by_sexp) ship_assign_sound(sp); // create new model instance data - sp->model_instance_num = model_create_instance(sip->model_num); + sp->model_instance_num = model_create_instance(true, sip->model_num); // Valathil - Reinitialize collision checks if ( Cmdline_old_collision_sys ) { @@ -13045,7 +13045,6 @@ void ship_model_start(object *objp) model_set_instance(model_num, psub->turret_gun_sobj, &pss->submodel_info_2, pss->flags ); } } - model_do_dumb_rotation(model_num); } /** @@ -13081,6 +13080,7 @@ void ship_model_update_instance(object *objp) // Then, clear all the angles in the model to zero model_clear_submodel_instances(model_instance_num); + // Handle subsystem rotations for this ship for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) { psub = pss->system_info; switch (psub->type) { @@ -13109,8 +13109,8 @@ void ship_model_update_instance(object *objp) } } - model_instance_dumb_rotation(model_instance_num); - + // Handle dumb rotations for this ship + model_do_dumb_rotations(model_instance_num); // preprocess subobject orientations for collision detection model_collide_preprocess(&objp->orient, model_instance_num); diff --git a/code/starfield/starfield.cpp b/code/starfield/starfield.cpp index 9db440b98c7..5d26048bdc4 100644 --- a/code/starfield/starfield.cpp +++ b/code/starfield/starfield.cpp @@ -164,6 +164,7 @@ int stars_debris_loaded = 0; // 0 = not loaded, 1 = normal vclips, 2 = nebula vc // background data int Stars_background_inited = 0; // if we're inited int Nmodel_num = -1; // model num +int Nmodel_instance_num = -1; // model instance num matrix Nmodel_orient = IDENTITY_MATRIX; // model orientation int Nmodel_flags = DEFAULT_NMODEL_FLAGS; // model flags int Nmodel_bitmap = -1; // model texture @@ -2180,6 +2181,11 @@ void stars_set_background_model(char *model_name, char *texture_name, int flags) Nmodel_num = -1; } + if (Nmodel_instance_num >= 0) { + model_delete_instance(Nmodel_instance_num); + Nmodel_instance_num = -1; + } + Nmodel_flags = flags; if ( (model_name == NULL) || (*model_name == '\0') ) @@ -2188,8 +2194,13 @@ void stars_set_background_model(char *model_name, char *texture_name, int flags) Nmodel_num = model_load(model_name, 0, NULL, -1); Nmodel_bitmap = bm_load(texture_name); - if (Nmodel_num >= 0) + if (Nmodel_num >= 0) { model_page_in_textures(Nmodel_num); + + if (model_get(Nmodel_num)->flags & PM_FLAG_HAS_DUMB_ROTATE) { + Nmodel_instance_num = model_create_instance(false, Nmodel_num); + } + } } // call this to set a specific orientation for the background diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index 8bd7dea00ae..50471472b70 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -163,6 +163,7 @@ extern int Num_weapon_subtypes; typedef struct weapon { int weapon_info_index; // index into weapon_info array int objnum; // object number for this weapon + int model_instance_num; // model instance number, if we have any dumb-rotating submodels int team; // The team of the ship that fired this int species; // The species of the ship that fired thisz float lifeleft; // life left on this weapon diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 872df11a207..219eb018c19 100755 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -3856,9 +3856,10 @@ void weapon_delete(object *obj) } if (wp->hud_in_flight_snd_sig >= 0 && snd_is_playing(wp->hud_in_flight_snd_sig)) - { snd_stop(wp->hud_in_flight_snd_sig); - } + + if (wp->model_instance_num >= 0) + model_delete_instance(wp->model_instance_num); wp->objnum = -1; Num_weapons--; @@ -5351,6 +5352,7 @@ int weapon_create( vec3d * pos, matrix * porient, int weapon_type, int parent_ob wp->start_pos = *pos; wp->objnum = objnum; + wp->model_instance_num = -1; wp->homing_object = &obj_used_list; // Assume not homing on anything. wp->homing_subsys = NULL; wp->creation_time = Missiontime; @@ -5465,7 +5467,15 @@ int weapon_create( vec3d * pos, matrix * porient, int weapon_type, int parent_ob } if ( wip->render_type == WRT_POF ) { + // this should have been checked above, but let's be extra sure + Assert(wip->model_num >= 0); + objp->radius = model_get_radius(wip->model_num); + + // if we dumb-rotate, make sure we have a model instance + if (model_get(wip->model_num)->flags & PM_FLAG_HAS_DUMB_ROTATE) { + wp->model_instance_num = model_create_instance(false, wip->model_num); + } } else if ( wip->render_type == WRT_LASER ) { objp->radius = wip->laser_head_radius; }