#include "prop.h" #include "asteroid/asteroid.h" #include "debris/debris.h" #include "freespace.h" #include "model/model.h" #include "model/modelreplace.h" #include "network/multiutil.h" #include "parse/parselo.h" #include "prop/prop_flags.h" #include "render/3d.h" #include "ship/shipfx.h" #include "object/objcollide.h" #include "tracing/Monitor.h" MONITOR(NumPropsRend) bool Props_inited = false; SCP_vector Prop_info; SCP_vector> Props; SCP_vector Prop_categories; static SCP_vector Removed_props; flag_def_list_new Prop_flags[] = { { "no_collide", Prop::Info_Flags::No_collide, true, false }, { "no_fred", Prop::Info_Flags::No_fred, true, false }, { "no lighting", Prop::Info_Flags::No_lighting, true, false } //{ "no impact debris", Prop::Info_Flags::No_impact_debris, true, false }, }; //const size_t Num_prop_flags = sizeof(Prop_flags) / sizeof(flag_def_list_new); /** * Return the index of Prop_info[].name that is *token. */ int prop_info_lookup(const char* token) { Assertion(token != nullptr, "NULL token passed to prop_info_lookup"); for (auto it = Prop_info.cbegin(); it != Prop_info.cend(); ++it) if (!stricmp(token, it->name.c_str())) return (int)std::distance(Prop_info.cbegin(), it); return -1; } int prop_name_lookup(const char *name) { Assertion(name != nullptr, "NULL name passed to prop_name_lookup"); for (size_t i=0; i < Props.size(); i++){ auto p = Props[i] ? &Props[i].value() : nullptr; if (p == nullptr) { continue; } if (p->objnum >= 0){ if (Objects[p->objnum].type == OBJ_PROP) { if (!stricmp(name, p->prop_name)) { return static_cast(i); } } } } // couldn't find it return -1; } int find_prop_empty_slot() { for (size_t i = 0; i < Props.size(); i++) { if (!Props[i].has_value()) { return static_cast(i); } } return -1; } prop* prop_id_lookup(int id) { Assertion(id >= 0 && id < static_cast(Props.size()) && Props[id].has_value(), "Could not find prop for id %d", id); if (id < 0 || id >= static_cast(Props.size()) || !Props[id].has_value()) { return nullptr; } return &Props[id].value(); } int prop_category_id_lookup(const SCP_string& category) { for (size_t i = 0; i < Prop_categories.size(); i++) { if (!stricmp(category.c_str(), Prop_categories[i].name.c_str())) { return static_cast(i); } } return -1; } prop_category* prop_get_category(int index) { Assertion(SCP_vector_inbounds(Prop_categories, index), "Invalid category index %d", index); if (!SCP_vector_inbounds(Prop_categories, index)) { return nullptr; } return &Prop_categories[index]; } void parse_prop_table(const char* filename) { read_file_text(filename, CF_TYPE_TABLES); reset_parse(); if (optional_string("#PROP CATEGORIES")) { while (optional_string("$Name:")) { prop_category pc; stuff_string(pc.name, F_NAME); required_string("+Color:"); int rgb[3]; stuff_int_list(rgb, 3, ParseLookupType::RAW_INTEGER_TYPE); gr_init_color(&pc.list_color, rgb[0], rgb[1], rgb[2]); // Replace existing category if name matches (case-insensitive) auto existing = std::find_if(Prop_categories.begin(), Prop_categories.end(), [&pc](const prop_category& existing_pc) { return !stricmp(existing_pc.name.c_str(), pc.name.c_str()); }); if (existing != Prop_categories.end()) { *existing = pc; // Replace } else { Prop_categories.push_back(std::move(pc)); // Add new } } } required_string("#PROPS"); while (optional_string("$Name:")) { char fname[NAME_LENGTH]; prop_info* pip = nullptr; bool first_time = false; bool create_if_not_found = true; bool remove_prop = false; stuff_string(fname, F_NAME, NAME_LENGTH); if (optional_string("+nocreate")) { if (!Parsing_modular_table) { Warning(LOCATION, "+nocreate flag used for prop in non-modular table"); } create_if_not_found = false; } // check first if this is on the remove blacklist auto it = std::find(Removed_props.begin(), Removed_props.end(), fname); if (it != Removed_props.end()) { remove_prop = true; } // we might have a remove tag if (optional_string("+remove")) { if (!Parsing_modular_table) { Warning(LOCATION, "+remove flag used for prop in non-modular table"); } if (!remove_prop) { Removed_props.emplace_back(fname); remove_prop = true; } } // Check if prop exists already int prop_id = prop_info_lookup(fname); // maybe remove it if (remove_prop) { if (prop_id >= 0) { mprintf(("Removing previously parsed prop '%s'\n", fname)); Prop_info.erase(Prop_info.begin() + prop_id); } if (!skip_to_start_of_string_either("$Name:", "#End")) { error_display(1, "Missing [#End] or [$Name] after prop class %s", fname); } continue; } // an entry for this prop exists if (prop_id >= 0) { pip = &Prop_info[prop_id]; if (!Parsing_modular_table) { Warning(LOCATION, "Error: Prop name %s already exists in %s. All prop class names must be unique.", fname, filename); if (!skip_to_start_of_string_either("$Name:", "#End")) { error_display(1, "Missing [#End] or [$Name] after duplicate prop class %s", fname); } continue; } } // an entry does not exist else { // Don't create prop if it has +nocreate and is in a modular table. if (!create_if_not_found && Parsing_modular_table) { if (!skip_to_start_of_string_either("$Name:", "#End")) { error_display(1, "Missing [#End] or [$Name] after prop class %s", fname); } continue; } Prop_info.emplace_back(prop_info()); pip = &Prop_info.back(); first_time = true; pip->name = fname; pip->category_index = -1; } if (optional_string("$POF file:")) { char temp[32]; stuff_string(temp, F_NAME, MAX_FILENAME_LEN); bool valid = true; // if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it if (Parsing_modular_table) { if (VALID_FNAME(pip->pof_file)) { if (!model_exists(temp)) { valid = false; } } } if (valid) { pip->pof_file = temp; } else { WarningEx(LOCATION, "Prop %s\nPOF file \"%s\" invalid!", pip->name.c_str(), temp); } } if (optional_string("$Closeup_pos:")) { stuff_vec3d(&pip->closeup_pos); } else if (first_time && VALID_FNAME(pip->pof_file)) { // Calculate from the model file. This is inefficient, but whatever int model_idx = model_load(pip->pof_file.c_str()); polymodel* pm = model_get(model_idx); // Go through, find best pip->closeup_pos.xyz.z = fabsf(pm->maxs.xyz.z); float temp = fabsf(pm->mins.xyz.z); if (temp > pip->closeup_pos.xyz.z) pip->closeup_pos.xyz.z = temp; // Now multiply by 2 pip->closeup_pos.xyz.z *= -2.0f; // We're done with the model. model_unload(model_idx); } if (optional_string("$Closeup_zoom:")) { stuff_float(&pip->closeup_zoom); if (pip->closeup_zoom <= 0.0f) { mprintf(("Warning! Prop '%s' has a $Closeup_zoom value that is less than or equal to 0 (%f). Setting " "to default value.\n", pip->name.c_str(), pip->closeup_zoom)); pip->closeup_zoom = 0.5f; } } if(optional_string("$Detail distance:")) { pip->num_detail_levels = (int)stuff_int_list(pip->detail_distance, MAX_PROP_DETAIL_LEVELS, ParseLookupType::RAW_INTEGER_TYPE); } if (optional_string("$Category:")) { SCP_string cat; stuff_string(cat, F_NAME); pip->category_index = prop_category_id_lookup(cat); } if (optional_string("$Flags:")) { SCP_vector prop_strings; stuff_string_list(prop_strings); for (const auto& flag : prop_strings) { // get ship type from ship flags const char* cur_flag = flag.c_str(); bool flag_found = false; // check various ship flags for (auto& pflag : Prop_flags) { if (!stricmp(pflag.name, cur_flag)) { flag_found = true; if (!pflag.in_use) { Warning(LOCATION, "Use of '%s' flag for Prop Class '%s' - this flag is no longer needed.", pflag.name, pip->name.c_str()); } else { pip->flags.set(pflag.def); } break; } } // catch typos or deprecations if (!stricmp(cur_flag, "no-collide") || !stricmp(cur_flag, "no_collide")) { flag_found = true; pip->flags.set(Prop::Info_Flags::No_collide); } if (!flag_found) { Warning(LOCATION, "Bogus string in prop flags: %s\n", cur_flag); } } } if (optional_string("$Custom data:")) { parse_string_map(pip->custom_data, "$end_custom_data", "+Val:"); } if (optional_string("$Custom Strings")) { while (optional_string("$Name:")) { custom_string cs; // The name of the string stuff_string(cs.name, F_NAME); // Arbitrary string value used for grouping strings together required_string("+Value:"); stuff_string(cs.value, F_NAME); // The string text itself required_string("+String:"); stuff_string(cs.text, F_MULTITEXT); pip->custom_strings.push_back(std::move(cs)); } required_string("$end_custom_strings"); } } required_string("#END"); } void post_process_props() { constexpr auto UnknownCategory = "Unknown Category"; bool create_unknown_category = false; // check for missing data for (auto& pi : Prop_info) { if (!VALID_FNAME(pi.pof_file)) { Warning(LOCATION, "Prop '%s' has no POF file specified!", pi.name.c_str()); } if (!SCP_vector_inbounds(Prop_categories, pi.category_index)) { Warning(LOCATION, "Prop '%s' has no or invalid category specified!", pi.name.c_str()); pi.category_index = -1; // Just to be safe, though it should already be -1 by default create_unknown_category = true; continue; } } if (create_unknown_category) { prop_category pc; pc.name = UnknownCategory; gr_init_color(&pc.list_color, 128, 128, 128); Prop_categories.push_back(std::move(pc)); } // Sort props by category order from Prop_categories, preserving internal order std::stable_sort(Prop_info.begin(), Prop_info.end(), [](const prop_info& a, const prop_info& b) { // Get index of a's category in Prop_categories auto a_it = std::find_if(Prop_categories.begin(), Prop_categories.end(), [&](const prop_category& cat) { return !stricmp(prop_get_category(a.category_index)->name.c_str(), cat.name.c_str()); }); int a_index = (a_it != Prop_categories.end()) ? static_cast(std::distance(Prop_categories.begin(), a_it)) : INT_MAX; // Get index of b's category in Prop_categories auto b_it = std::find_if(Prop_categories.begin(), Prop_categories.end(), [&](const prop_category& cat) { return !stricmp(prop_get_category(b.category_index)->name.c_str(), cat.name.c_str()); }); int b_index = (b_it != Prop_categories.end()) ? static_cast(std::distance(Prop_categories.begin(), b_it)) : INT_MAX; // Sort by category index return a_index < b_index; }); } void prop_init() { // first parse the default table if (cf_exists_full("props.tbl", CF_TYPE_TABLES)) { parse_prop_table("props.tbl"); } // parse any modular tables parse_modular_table("*-prp.tbm", parse_prop_table); post_process_props(); Props_inited = true; } /** * Returns object index of prop. * @return -1 means failed. */ int prop_create(const matrix* orient, const vec3d* pos, int prop_type, const char* name) { prop_info* pip; prop* propp; Assertion((prop_type >= 0) && (prop_type < static_cast(Prop_info.size())), "Invalid prop_type %d passed to prop_create() (expected value in the range 0-%d)\n", prop_type, static_cast(Prop_info.size()) - 1); pip = &(Prop_info[prop_type]); int new_id = find_prop_empty_slot(); if (new_id < 0) { Props.emplace_back(prop()); new_id = static_cast(Props.size()) - 1; } else { Props[new_id] = prop(); } propp = prop_id_lookup(new_id); Assertion(propp != nullptr, "Could not create prop!"); propp->prop_info_index = prop_type; if ((name == nullptr) || (prop_name_lookup(name) >= 0)) { // regular name, regular suffix char base_name[NAME_LENGTH]; char suffix[NAME_LENGTH]; strcpy_s(base_name, Prop_info[prop_type].name.c_str()); sprintf(suffix, NOX(" %d"), new_id); // start building name strcpy_s(propp->prop_name, base_name); // if generated name will be longer than allowable name, truncate the class section of the name by the overflow int char_overflow = static_cast(strlen(base_name) + strlen(suffix)) - (NAME_LENGTH - 1); if (char_overflow > 0) { propp->prop_name[strlen(base_name) - static_cast(char_overflow)] = '\0'; } // complete building the name by adding suffix number strcat_s(propp->prop_name, suffix); } else { strcpy_s(propp->prop_name, name); } if (!VALID_FNAME(pip->pof_file)) { Error(LOCATION, "Cannot create prop %s; pof file is not valid", pip->name.c_str()); return -1; } pip->model_num = model_load(pip->pof_file.c_str()); polymodel* pm = model_get(pip->model_num); if (pip->num_detail_levels != pm->n_detail_levels) { if (!Is_standalone) { // just log to file for standalone servers Warning(LOCATION, "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", pip->name.c_str(), pip->num_detail_levels, pm->n_detail_levels); } else { nprintf(("Warning", "For prop '%s', detail level mismatch. Table has %d, POF has %d.", pip->name.c_str(), pip->num_detail_levels, pm->n_detail_levels)); } } for (int i = 0; i < pm->n_detail_levels; i++) pm->detail_depth[i] = (i < pip->num_detail_levels) ? i2fl(pip->detail_distance[i]) : 0.0f; flagset default_prop_object_flags; default_prop_object_flags.set(Object::Object_Flags::Renders); default_prop_object_flags.set(Object::Object_Flags::Physics); default_prop_object_flags.set(Object::Object_Flags::Immobile); default_prop_object_flags.set(Object::Object_Flags::Collides, !pip->flags[Prop::Info_Flags::No_collide]); int objnum = obj_create(OBJ_PROP, -1, new_id, orient, pos, model_get_radius(pip->model_num), default_prop_object_flags); Assert(objnum >= 0); propp->objnum = objnum; propp->model_instance_num = model_create_instance(objnum, pip->model_num); object* objp = &Objects[objnum]; objp->hull_strength = 0.0f; objp->phys_info.max_vel = vmd_zero_vector; objp->phys_info.desired_vel = vmd_zero_vector; objp->phys_info.forward_accel_time_const = 1.0; objp->phys_info.flags = PF_CONST_VEL; // For now use the asteroid signature for props as requested by Cyborg. if (Game_mode & GM_MULTIPLAYER) { objp->net_signature = multi_assign_network_signature(MULTI_SIG_ASTEROID); } // this is a little 'dangerous', values like this can cause the physics to do wierd things // but they are the most 'honest', and will help to indicate when the physics is trying to use these values in ways they shouldn't objp->phys_info.mass = INFINITY; objp->phys_info.I_body_inv = vmd_zero_matrix; // allocate memory for keeping glow point bank status (enabled/disabled) { bool val = true; // default value, enabled if (pm->n_glow_point_banks) propp->glow_point_bank_active.resize(pm->n_glow_point_banks, val); // set any default off banks to off for (int bank = 0; bank < pm->n_glow_point_banks; bank++) { glow_point_bank_override* gpo = nullptr; auto gpoi = pip->glowpoint_bank_override_map.find(bank); if (gpoi != pip->glowpoint_bank_override_map.end()) { gpo = &glowpoint_bank_overrides[gpoi->second]; } if (gpo) { if (gpo->default_off) { propp->glow_point_bank_active[bank] = false; } } } } //animation::anim_set_initial_states(propp); // Start up stracking for this prop in multi. //if (Game_mode & (GM_MULTIPLAYER)) { //multi_rollback_ship_record_add_ship(objnum); //} // Set time when prop is created propp->create_time = timer_get_milliseconds(); propp->time_created = Missiontime; return objnum; } void prop_delete(object* obj) { int num = obj->instance; int objnum = OBJ_INDEX(obj); Assertion(Props[num].has_value(), "Props[%d] is already nullopt!", num); prop& propp = *Props[num]; Assertion(propp.objnum == objnum, "Prop object id does not match passed object!"); propp.objnum = -1; //animation::ModelAnimationSet::stopAnimations(model_get_instance(propp->model_instance_num)); // glow point banks propp.glow_point_bank_active.clear(); model_delete_instance(propp.model_instance_num); propp.model_instance_num = -1; // Leave the slot empty for the duration of the scene // The Props array will be compacted at the end of the level Props[num] = std::nullopt; } /** * Change the prop model for a prop to that for prop class 'prop_type' * * @param n index of prop in ::Props[] array * @param prop_type prop class (index into ::Prop_info vector) */ static void prop_model_change(int n, int prop_type) { prop* sp = prop_id_lookup(n); prop_info* sip = &(Prop_info[prop_type]); object* objp = &Objects[sp->objnum]; //polymodel_instance* pmi = model_get_instance(sp->model_instance_num); // get new model sip->model_num = model_load(sip->pof_file.c_str()); polymodel* pm = model_get(sip->model_num); Objects[sp->objnum].radius = model_get_radius(pm->id); // page in nondims in game if ( !Fred_running ) model_page_in_textures(sip->model_num, prop_type); // allocate memory for keeping glow point bank status (enabled/disabled) { bool val = true; // default value, enabled // clear out any old gpb's first, then add new ones if needed sp->glow_point_bank_active.clear(); if (pm->n_glow_point_banks) sp->glow_point_bank_active.resize( pm->n_glow_point_banks, val ); // set any default off banks to off for (int bank = 0; bank < pm->n_glow_point_banks; bank++) { glow_point_bank_override* gpo = nullptr; auto gpoi = sip->glowpoint_bank_override_map.find(bank); if (gpoi != sip->glowpoint_bank_override_map.end()) { gpo = &glowpoint_bank_overrides[gpoi->second]; } if (gpo) { if (gpo->default_off) { sp->glow_point_bank_active[bank] = false; } } } } if ( sip->num_detail_levels != pm->n_detail_levels ) { if ( !Is_standalone ) { // just log to file for standalone servers Warning(LOCATION, "For prop '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name.c_str(), sip->num_detail_levels, pm->n_detail_levels ); } else { nprintf(("Warning", "For prop '%s', detail level mismatch. Table has %d, POF has %d.", sip->name.c_str(), sip->num_detail_levels, pm->n_detail_levels )); } } for (int i=0; in_detail_levels; i++ ) pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f; // reset texture animations //sp->base_texture_anim_timestamp = _timestamp(); model_delete_instance(sp->model_instance_num); // create new model instance data // note: this is needed for both subsystem stuff and submodel animation stuff sp->model_instance_num = model_create_instance(OBJ_INDEX(objp), sip->model_num); //pmi = model_get_instance(sp->model_instance_num); } /** * Change the prop class on a prop, and changing all required information * for consistency * * @param n index of prop in ::Props[] array * @param prop_type prop class (index into ::Prop_info vector) * @param by_sexp SEXP reference */ void change_prop_type(int n, int prop_type) { prop* sp = prop_id_lookup(n); // do a quick out if we're already using the new prop class if (sp->prop_info_index == prop_type) return; int objnum = sp->objnum; prop_info* pip = &(Prop_info[prop_type]); prop_info* pip_orig = &Prop_info[sp->prop_info_index]; object* objp = &Objects[objnum]; // point to new prop data prop_model_change(n, prop_type); sp->prop_info_index = prop_type; // check class-specific flags if (pip->flags[Prop::Info_Flags::No_collide]) // changing TO a no-collision prop class obj_set_flags(objp, objp->flags - Object::Object_Flags::Collides); else if (pip_orig->flags[Prop::Info_Flags::No_collide]) // changing FROM a no-collision prop class obj_set_flags(objp, objp->flags + Object::Object_Flags::Collides); } void prop_render(object* obj, model_draw_list* scene) { int num = obj->instance; prop* propp = prop_id_lookup(num); prop_info* pip = &Prop_info[propp->prop_info_index]; MONITOR_INC(NumPropsRend, 1); model_render_params render_info; auto pmi = model_get_instance(propp->model_instance_num); auto pm = model_get(pmi->model_num); model_clear_instance(pip->model_num); model_instance_clear_arcs(pm, pmi); uint64_t render_flags = MR_NORMAL; if (propp->flags[Prop::Prop_Flags::Render_with_alpha_mult]) { render_info.set_alpha_mult(propp->alpha_mult); } if (pip->flags[Prop::Info_Flags::No_lighting]) { render_flags |= MR_NO_LIGHTING; } if (propp->flags[Prop::Prop_Flags::Glowmaps_disabled]) { render_flags |= MR_NO_GLOWMAPS; } if (propp->flags[Prop::Prop_Flags::Draw_as_wireframe]) { render_flags |= MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING; render_info.set_color(Wireframe_color); } if (propp->flags[Prop::Prop_Flags::Render_full_detail]) { render_flags |= MR_FULL_DETAIL; } if (propp->flags[Prop::Prop_Flags::Render_without_light]) { render_flags |= MR_NO_LIGHTING; } uint debug_flags = render_info.get_debug_flags(); if (propp->flags[Prop::Prop_Flags::Render_without_diffuse]) { debug_flags |= MR_DEBUG_NO_DIFFUSE; } if (propp->flags[Prop::Prop_Flags::Render_without_glowmap]) { debug_flags |= MR_DEBUG_NO_GLOW; } if (propp->flags[Prop::Prop_Flags::Render_without_normalmap]) { debug_flags |= MR_DEBUG_NO_NORMAL; } if (propp->flags[Prop::Prop_Flags::Render_without_ambientmap]) { debug_flags |= MR_DEBUG_NO_AMBIENT; } if (propp->flags[Prop::Prop_Flags::Render_without_specmap]) { debug_flags |= MR_DEBUG_NO_SPEC; } if (propp->flags[Prop::Prop_Flags::Render_without_reflectmap]) { debug_flags |= MR_DEBUG_NO_REFLECT; } render_info.set_flags(render_flags); render_info.set_debug_flags(debug_flags); render_info.set_object_number(OBJ_INDEX(obj)); render_info.set_replacement_textures(pmi->texture_replace); model_render_queue(&render_info, scene, pip->model_num, &obj->orient, &obj->pos); } // Draft. Props vector uses std::optional to allow for empty slots for deleted props so the indices of the remaining // props do not change. In long FRED sessions without saving and loading, this can lead to a lot of empty slots. However, // saving and loading the level will naturally compact the props vector by way of clearing and re-adding props. // EDIT: Now that creating props will use empty prop slots, this may not be needed. Keeping it here for posterity. /*void compact_props_vector() { SCP_vector> new_props; SCP_unordered_map index_remap; for (size_t i = 0; i < Props.size(); ++i) { if (Props[i].has_value()) { index_remap[static_cast(i)] = static_cast(new_props.size()); new_props.push_back(std::move(Props[i])); } } Props = std::move(new_props); // Remap object instances for (object* obj = GET_FIRST(&obj_used_list); obj != END_OF_LIST(&obj_used_list); obj = GET_NEXT(obj)) { if (obj->type == OBJ_PROP && obj->instance >= 0) { auto it = index_remap.find(obj->instance); Assertion(it != index_remap.end(), "Object is pointing to invalid prop index %d! Get a coder!", obj->instance); if (it != index_remap.end()) { obj->instance = it->second; } } } }*/ void props_level_init() { Props.clear(); } void props_level_close() { // Clear all props and empty prop slots. Note that this can happen either before or after objects are cleaned up. Props.clear(); } // handles prop-ship/debris/asteroid/weapon collisions int prop_check_collision(object* prop_obj, object* other_obj, vec3d* hitpos, collision_info_struct* prop_hit_info) { mc_info mc; Assertion(prop_obj->type == OBJ_PROP, "Object is not a prop!"); int num = prop_obj->instance; prop* propp = prop_id_lookup(num); Assertion(propp->objnum == OBJ_INDEX(prop_obj), "Prop object id does not match passed object!"); prop_info* prinfo = &Prop_info[propp->prop_info_index]; // debris_hit_info nullptr - so debris-weapon collision if (prop_hit_info == nullptr) { // debris weapon collision Assert(other_obj->type == OBJ_WEAPON); mc.model_instance_num = propp->model_instance_num; mc.model_num = prinfo->model_num; // Fill in the model to check mc.orient = &prop_obj->orient; // The object's orient mc.pos = &prop_obj->pos; // The object's position mc.p0 = &other_obj->last_pos; // Point 1 of ray to check mc.p1 = &other_obj->pos; // Point 2 of ray to check mc.flags = MC_CHECK_MODEL; if (model_collide(&mc)) { *hitpos = mc.hit_point_world; } weapon* wp = &Weapons[other_obj->instance]; wp->collisionInfo = new mc_info; // The weapon will free this memory later *wp->collisionInfo = mc; return mc.num_hits; } object* heavy_obj = prop_hit_info->heavy; object* light_obj = prop_hit_info->light; vec3d zero; vm_vec_zero(&zero); vec3d p0 = light_obj->last_pos - heavy_obj->last_pos; vec3d p1 = light_obj->pos - heavy_obj->pos; mc.pos = &zero; // The object's position mc.p0 = &p0; // Point 1 of ray to check mc.p1 = &p1; // Point 2 of ray to check // find the light object's position in the heavy object's reference frame at last frame and also in this frame. vec3d p0_temp, p0_rotated; // Collision detection from rotation enabled if at rotation is less than 30 degree in frame // This should account for all ships if (heavy_obj->type == OBJ_SHIP && (vm_vec_mag_squared(&heavy_obj->phys_info.rotvel) * flFrametime * flFrametime) < (PI * PI / 36)) { // collide_rotate calculate (1) start position and (2) relative velocity prop_hit_info->collide_rotate = true; vm_vec_rotate(&p0_temp, &p0, &heavy_obj->last_orient); vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy_obj->orient); mc.p0 = &p0_rotated; // Point 1 of ray to check vm_vec_sub(&prop_hit_info->light_rel_vel, &p1, &p0_rotated); vm_vec_scale(&prop_hit_info->light_rel_vel, 1 / flFrametime); } else { prop_hit_info->collide_rotate = false; vm_vec_sub(&prop_hit_info->light_rel_vel, &light_obj->phys_info.vel, &heavy_obj->phys_info.vel); } int mc_ret_val = 0; if (prop_hit_info->heavy == other_obj && other_obj->type == OBJ_SHIP) { // ship is heavier, so prop is sphere. Check sphere collision against ship poly model mc.model_instance_num = Ships[other_obj->instance].model_instance_num; mc.model_num = Ship_info[Ships[other_obj->instance].ship_info_index].model_num; // Fill in the model to check mc.orient = &other_obj->orient; // The object's orient mc.radius = model_get_core_radius(prinfo->model_num); mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); // copy important data int orig_flags = mc.flags; // make a copy of start end positions of sphere in big ship RF vec3d orig_p0 = *mc.p0; vec3d orig_p1 = *mc.p1; // first test against the sphere - if this fails then don't do any submodel tests mc.flags = MC_ONLY_SPHERE | MC_CHECK_SPHERELINE; if (model_collide(&mc)) { // Set earliest hit time prop_hit_info->hit_time = FLT_MAX; auto pmi = model_get_instance(Ships[heavy_obj->instance].model_instance_num); auto pm = model_get(pmi->model_num); // Do collision the cool new way if (prop_hit_info->collide_rotate) { // We collide with the sphere, find the list of moving submodels and test one at a time SCP_vector submodel_vector; model_get_moving_submodel_list(submodel_vector, heavy_obj); // turn off all moving submodels, collide against only 1 at a time. mc.collision_checked.assign(pm->n_models, 0); for (auto submodel : submodel_vector) { mc.collision_checked[submodel] = true; } // Only check single submodel now, since children of moving submodels are handled as moving as well mc.flags = orig_flags | MC_SUBMODEL; if (Ship_info[Ships[other_obj->instance].ship_info_index].collision_lod > -1) { mc.lod = Ship_info[Ships[other_obj->instance].ship_info_index].collision_lod; } // check each submodel in turn for (auto submodel : submodel_vector) { // turn on just one submodel for collision test mc.collision_checked[submodel] = false; // find the start and end positions of the sphere in submodel RF model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true); model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos); mc.p0 = &p0; mc.p1 = &p1; mc.orient = &vmd_identity_matrix; mc.submodel_num = submodel; if (model_collide(&mc)) { if (mc.hit_dist < prop_hit_info->hit_time) { mc_ret_val = 1; // set up debris_hit_info common set_hit_struct_info(prop_hit_info, &mc, true); model_instance_local_to_global_point(&prop_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero); // set up debris_hit_info for rotating submodel if (!prop_hit_info->edge_hit) { model_instance_local_to_global_dir(&prop_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient); } // find position in submodel RF of light object at collison vec3d diff = *mc.p1 - *mc.p0; vec3d int_light_pos = *mc.p0 + diff * mc.hit_dist; model_instance_local_to_global_point(&prop_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero); } } // Don't look at this submodel again mc.collision_checked[submodel] = true; } // Clear collision_checked before base model pass so it auto-inits fresh mc.collision_checked.clear(); } // Now complete base model collision checks that do not take into account rotating submodels. mc.flags = orig_flags; mc.p0 = &orig_p0; mc.p1 = &orig_p1; mc.orient = &heavy_obj->orient; // usual ship_ship collision test if (model_collide(&mc)) { // check if this is the earliest hit if (mc.hit_dist < prop_hit_info->hit_time) { mc_ret_val = 1; set_hit_struct_info(prop_hit_info, &mc, false); // get collision normal if not edge hit if (!prop_hit_info->edge_hit) { model_instance_local_to_global_dir(&prop_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient); } // find position in submodel RF of light object at collison vec3d diff = *mc.p1 - *mc.p0; prop_hit_info->light_collision_cm_pos = *mc.p0 + diff * mc.hit_dist; } } } } else { // any case OTHER than big ship small prop, simpler code if (prop_obj == light_obj) { mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); int instance = heavy_obj->instance; // fill the appropriate model data if (heavy_obj->type == OBJ_DEBRIS) { mc.model_instance_num = -1; mc.model_num = Debris[instance].model_num; mc.submodel_num = Debris[instance].submodel_num; mc.flags |= MC_SUBMODEL; } else if (heavy_obj->type == OBJ_ASTEROID) { asteroid* astp = &Asteroids[instance]; mc.model_instance_num = astp->model_instance_num; mc.model_num = Asteroid_info[astp->asteroid_type].subtypes[astp->asteroid_subtype].model_number; mc.submodel_num = -1; } mc.orient = &heavy_obj->orient; // The object's orient mc.radius = model_get_core_radius(prinfo->model_num); mc_ret_val = model_collide(&mc); if (mc_ret_val) { set_hit_struct_info(prop_hit_info, &mc, false); // set normal if not edge hit if (!prop_hit_info->edge_hit) { vm_vec_unrotate(&prop_hit_info->collision_normal, &mc.hit_normal, &heavy_obj->orient); } // find position in submodel RF of light object at collison vec3d diff = *mc.p1 - *mc.p0; prop_hit_info->light_collision_cm_pos = *mc.p0 + diff * mc.hit_dist; } } else { // prop is the heavy object mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); //int instance = heavy_obj->instance; // fill the appropriate model data mc.model_instance_num = propp->model_instance_num; mc.model_num = prinfo->model_num; mc.orient = &heavy_obj->orient; switch (light_obj->type) { case OBJ_SHIP: mc.radius = model_get_core_radius(Ship_info[Ships[light_obj->instance].ship_info_index].model_num); break; case OBJ_ASTEROID: // FALLTHROUGH case OBJ_DEBRIS: mc.radius = light_obj->radius; break; default: UNREACHABLE("Unknown object type %d in prop_check_collision", light_obj->type); }; mc_ret_val = model_collide(&mc); if (mc_ret_val) { set_hit_struct_info(prop_hit_info, &mc, false); // set normal if not edge hit if (!prop_hit_info->edge_hit) { vm_vec_unrotate(&prop_hit_info->collision_normal, &mc.hit_normal, &heavy_obj->orient); } // find position in submodel RF of light object at collison vec3d diff = *mc.p1 - *mc.p0; prop_hit_info->light_collision_cm_pos = *mc.p0 + diff * mc.hit_dist; } } } if (mc_ret_val && other_obj->type == OBJ_SHIP) { WarpEffect* warp_effect = nullptr; ship* shipp = &Ships[other_obj->instance]; // this is extremely confusing but mc.hit_point_world isn't actually in world coords // everything above was calculated relative to the heavy's position vec3d actual_world_hit_pos = mc.hit_point_world + heavy_obj->pos; if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr)) warp_effect = shipp->warpin_effect.get(); else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr)) warp_effect = shipp->warpout_effect.get(); if (warp_effect != nullptr && point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect)) mc_ret_val = 0; } if (mc_ret_val) { // SET PHYSICS PARAMETERS // already have (hitpos - heavy) and light_cm_pos // get r_heavy and r_light prop_hit_info->r_heavy = prop_hit_info->hit_pos; prop_hit_info->r_light = prop_hit_info->hit_pos - prop_hit_info->light_collision_cm_pos; // set normal for edge hit if (prop_hit_info->edge_hit) { vm_vec_copy_normalize(&prop_hit_info->collision_normal, &prop_hit_info->r_light); vm_vec_negate(&prop_hit_info->collision_normal); } // get world hitpos vm_vec_add(hitpos, &prop_hit_info->heavy->pos, &prop_hit_info->r_heavy); return 1; } else { // no hit return 0; } }