Files
fs2open.github.com/code/object/objectdock.cpp
T
Goober5000andClaude Opus 4.8 bed228d264 avoid undefined behavior from UNREACHABLE on reachable code paths (#7555)
* avoid undefined behavior from UNREACHABLE on reachable code paths

UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.

Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.

A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.

Where the converted site was a simple guard of the form
    if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
    Assertion(!x, ...);
    if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* addendum

After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible.  UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error".  Also fix a few other sites and change a couple to Warnings.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-09 11:20:25 +09:00

1101 lines
35 KiB
C++

/*
* Created by Ian "Goober5000" Warfield for the FreeSpace2 Source Code Project.
* You may not sell or otherwise commercially exploit the source or things you
* create based on the source.
*/
#include "math/bitarray.h"
#include "math/vecmat.h"
#include "mission/missionparse.h"
#include "object/object.h"
#include "object/objectdock.h"
#include "ship/ship.h"
// helper prototypes
void dock_evaluate_tree(object *objp, dock_function_info *infop, void (*function)(object *, dock_function_info *), ubyte *visited_bitstring);
void dock_move_docked_children_tree(object *objp, object *parent_objp);
void dock_count_total_docked_objects_helper(object *objp, dock_function_info *infop);
void dock_check_find_docked_object_helper(object *objp, dock_function_info *infop);
void dock_calc_docked_mins_maxs_helper(object *objp, dock_function_info *infop);
void dock_calc_docked_center_of_mass_helper(object *objp, dock_function_info *infop);
void dock_calc_total_docked_mass_helper(object *objp, dock_function_info *infop);
void dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper(object *objp, dock_function_info *infop);
void dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper(object *objp, dock_function_info *infop);
void dock_find_max_speed_helper(object *objp, dock_function_info *infop);
void dock_find_max_fspeed_helper(object *objp, dock_function_info *infop);
void dock_calc_total_moi_helper(object* objp, dock_function_info* infop);
// management prototypes
bool dock_check_assume_hub();
object *dock_get_hub(object *objp);
void dock_add_instance(object *objp, int dockpoint, object *other_objp);
void dock_remove_instance(object *objp, object *other_objp);
dock_instance *dock_find_instance(object *objp, object *other_objp);
dock_instance *dock_find_instance(object *objp, int dockpoint);
int dock_count_instances(object *objp);
object *dock_get_first_docked_object(object *objp)
{
Assert(objp != NULL);
// are we docked?
if (!object_is_docked(objp))
return NULL;
return objp->dock_list->docked_objp;
}
bool dock_check_docked_one_on_one(object *objp)
{
Assert(objp != NULL);
// we must be docked
if (!object_is_docked(objp))
return false;
// our dock list must contain only one object
if (objp->dock_list->next != NULL)
return false;
// the other guy's dock list must contain only one object
if (dock_get_first_docked_object(objp)->dock_list->next != NULL)
return false;
// debug check to make sure that we're docked to each other
Assert(objp == dock_get_first_docked_object(objp)->dock_list->docked_objp);
// success
return true;
}
int dock_count_direct_docked_objects(object *objp)
{
Assert(objp != NULL);
return dock_count_instances(objp);
}
int dock_count_total_docked_objects(object *objp)
{
Assert(objp != NULL);
dock_function_info dfi;
dock_evaluate_all_docked_objects(objp, &dfi, dock_count_total_docked_objects_helper);
return dfi.maintained_variables.int_value;
}
bool dock_check_find_direct_docked_object(object *objp, object *other_objp)
{
Assert(objp != NULL);
Assert(other_objp != NULL);
return (dock_find_instance(objp, other_objp) != NULL);
}
bool dock_check_find_docked_object(object *objp, object *other_objp)
{
Assert(objp != nullptr);
Assert(objp->signature > 0);
Assert(other_objp != nullptr);
Assert(other_objp->signature > 0);
if (!(objp != nullptr && objp->signature > 0))
return false;
if (!(other_objp != nullptr && other_objp->signature > 0))
return false;
dock_function_info dfi;
dfi.parameter_variables.objp_value = other_objp;
dock_evaluate_all_docked_objects(objp, &dfi, dock_check_find_docked_object_helper);
return dfi.maintained_variables.bool_value;
}
object *dock_find_object_at_dockpoint(object *objp, int dockpoint)
{
Assert(objp != NULL);
dock_instance *result = dock_find_instance(objp, dockpoint);
if (result == NULL)
return NULL;
else
return result->docked_objp;
}
int dock_find_dockpoint_used_by_object(object *objp, object *other_objp)
{
Assert(objp != NULL);
Assert(other_objp != NULL);
dock_instance *result = dock_find_instance(objp, other_objp);
if (result == NULL)
return -1;
else
return result->dockpoint_used;
}
/**
* Get the offset of the actual center of the docked ship models for the purposes of warping (which may not be the specified center).
* Note, these are LOCAL coordinates in relation to objp, not world coordinates.
* See also ship_class_get_actual_center() in ship.cpp
*/
void dock_calc_docked_actual_center(vec3d *dest, object *objp)
{
Assert(dest != nullptr);
Assert(objp != nullptr);
vec3d overall_mins, overall_maxs;
dock_calc_docked_extents(&overall_mins, &overall_maxs, objp);
// c.f. ship_class_get_actual_center() in ship.cpp
dest->xyz.x = (overall_maxs.xyz.x + overall_mins.xyz.x) * 0.5f;
dest->xyz.y = (overall_maxs.xyz.y + overall_mins.xyz.y) * 0.5f;
dest->xyz.z = (overall_maxs.xyz.z + overall_mins.xyz.z) * 0.5f;
}
/**
* Get the mins and maxs of the entire assembly of docked ship models.
* Note, these are LOCAL coordinates in relation to objp, not world coordinates.
*/
void dock_calc_docked_extents(vec3d *mins, vec3d *maxs, object *objp)
{
Assert(mins != nullptr);
Assert(maxs != nullptr);
Assert(objp != nullptr);
*mins = vmd_zero_vector;
*maxs = vmd_zero_vector;
// Let's calculate all mins/maxes in relation to the orientation of the main object
// (which is expected to be the dock leader, but this technically isn't required).
// Since the vast majority of dockpoints are aligned with an axis, this should
// yield a much better fit of our docked bounding box.
dock_function_info dfi;
dfi.parameter_variables.objp_value = objp; // the reference object for our bounding box orientation
dfi.maintained_variables.vecp_value = mins; // mins
dfi.maintained_variables.vecp_value2 = maxs; // maxs
dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_docked_mins_maxs_helper);
}
float dock_calc_docked_center_of_mass(vec3d *dest, object *objp)
{
Assertion(dest != nullptr, "dock_calc_docked_center_of_mass, invalid dest");
Assertion(objp != nullptr, "dock_calc_docked_center_of_mass, invalid objp");
vm_vec_zero(dest);
dock_function_info dfi;
dfi.maintained_variables.vecp_value = dest;
dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_docked_center_of_mass_helper);
// overall center of mass = weighted sum of centers of mass divided by total mass
float total_mass = dfi.maintained_variables.float_value;
vm_vec_scale(dest, (1.0f / total_mass));
return total_mass;
}
float dock_calc_total_docked_mass(object *objp)
{
Assertion(objp != nullptr, "dock_calc_total_docked_mass, invalid argument");
dock_function_info dfi;
dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_total_docked_mass_helper);
return dfi.maintained_variables.float_value;
}
float dock_calc_max_cross_sectional_radius_perpendicular_to_axis(object *objp, axis_type axis)
{
Assert(objp != NULL);
vec3d local_line_end;
vec3d *world_line_start, world_line_end;
dock_function_info dfi;
// to calculate the cross-sectional radius, we need a line that will be perpendicular to the cross-section
// the first endpoint is simply the position of the object
world_line_start = &objp->pos;
// the second endpoint extends in the axis direction
vm_vec_zero(&local_line_end);
switch(axis)
{
case X_AXIS:
local_line_end.xyz.x = 1.0f;
break;
case Y_AXIS:
local_line_end.xyz.y = 1.0f;
break;
case Z_AXIS:
local_line_end.xyz.z = 1.0f;
break;
default:
Int3();
return 0.0f;
}
// move the endpoint to go through the axis of the actual object
vm_vec_unrotate(&world_line_end, &local_line_end, &objp->orient);
vm_vec_add2(&world_line_end, &objp->pos);
// now we have a unit vector starting at the object's position and pointing along the chosen axis
// (although the length doesn't matter, as it's calculated as an endless line)
// now determine the cross-sectional radius
// set parameters and call function for the radius squared
dfi.parameter_variables.vecp_value = world_line_start;
dfi.parameter_variables.vecp_value2 = &world_line_end;
dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper);
// the radius is the square root of our result
return fl_sqrt(dfi.maintained_variables.float_value);
}
float dock_calc_max_semilatus_rectum_parallel_to_axis(object *objp, axis_type axis)
{
Assert(objp != NULL);
vec3d local_line_end;
vec3d *world_line_start, world_line_end;
dock_function_info dfi;
// to calculate the semilatus rectum, we need a directrix that will be parallel to the axis
// the first endpoint is simply the position of the object
world_line_start = &objp->pos;
// the second endpoint extends in the axis direction
vm_vec_zero(&local_line_end);
switch(axis)
{
case X_AXIS:
local_line_end.xyz.x = 1.0f;
break;
case Y_AXIS:
local_line_end.xyz.y = 1.0f;
break;
case Z_AXIS:
local_line_end.xyz.z = 1.0f;
break;
default:
Int3();
return 0.0f;
}
// move the endpoint to go through the axis of the actual object
vm_vec_unrotate(&world_line_end, &local_line_end, &objp->orient);
vm_vec_add2(&world_line_end, &objp->pos);
// now we have a unit vector starting at the object's position and pointing along the chosen axis
// (although the length doesn't matter, as it's calculated as an endless line)
// now determine the semilatus rectum
// set parameters and call function for the semilatus rectum squared
dfi.parameter_variables.vecp_value = world_line_start;
dfi.parameter_variables.vecp_value2 = &world_line_end;
dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper);
// the semilatus rectum is the square root of our result
return fl_sqrt(dfi.maintained_variables.float_value);
}
float dock_calc_docked_fspeed(object *objp)
{
Assert(objp != NULL);
// *sigh*... the docked fspeed is simply the max fspeed of all docked objects
dock_function_info dfi;
dock_evaluate_all_docked_objects(objp, &dfi, dock_find_max_fspeed_helper);
return dfi.maintained_variables.float_value;
}
float dock_calc_docked_speed(object *objp)
{
Assert(objp != NULL);
// ditto with speed
dock_function_info dfi;
dock_evaluate_all_docked_objects(objp, &dfi, dock_find_max_speed_helper);
return dfi.maintained_variables.float_value;
}
// Calculates the total moi (NOT INVERTED) of a docked assembly
// dest => output matrix
// objp => one of the objects in the assembly
// center => center of mass of the assembly in world coords ( use dock_calc_docked_center_of_mass to find it )
// Returns whether or not was successful (in case some or all of the matrices were uninvertable or too close to it)
// If not successful, dest will have NaN or infinity, use at your own risk!
bool dock_calc_total_moi(matrix* dest, object* objp, vec3d *center)
{
Assertion((dest != nullptr) && (objp != nullptr) && (center != nullptr), "dock_calc_total_moi invalid argument(s)");
*dest = vmd_zero_matrix;
dock_function_info dfi;
dfi.parameter_variables.vecp_value = center;
dfi.maintained_variables.matrix_value = dest;
dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_total_moi_helper);
return is_valid_matrix(dest);
}
// This ship is the only ship NOT moved by docking AI to keep everyone together
// All the other ships in the tree will update based on this one
// Since this is based on current speed don't expect it to remain consistent between frames
object* dock_find_dock_root(object *objp)
{
Assertion(objp != nullptr, "dock_find_dock_root invalid argument");
dock_function_info dfi;
object* fastest_objp;
dfi.maintained_variables.objp_value = nullptr;
// find the object with the highest speed
dock_evaluate_all_docked_objects(objp, &dfi, dock_find_max_speed_helper);
fastest_objp = dfi.maintained_variables.objp_value;
// if we have no max speed, just use the given one
if (fastest_objp == nullptr)
fastest_objp = objp;
return fastest_objp;
}
void dock_calculate_and_apply_whack_docked_object(const vec3d* impulse, const vec3d* world_hit_pos, object* objp)
{
Assertion((objp != nullptr) && (impulse != nullptr) && (world_hit_pos != nullptr),
"dock_whack_docked_object invalid argument(s)");
// Detect null vector.
if (whack_below_limit(impulse))
return;
// calc overall world center-of-mass of all ships
vec3d world_center_of_mass;
float total_mass = dock_calc_docked_center_of_mass(&world_center_of_mass, objp);
vec3d hit_pos;
// the new hitpos is the vector from world center-of-mass to world hitpos
vm_vec_sub(&hit_pos, world_hit_pos, &world_center_of_mass);
matrix moi, inv_moi;
// calculate the effective inverse MOI for the docked composite object about its center of mass
if (dock_calc_total_moi(&moi, objp, &world_center_of_mass)) {
vm_inverse_matrix(&inv_moi, &moi);
}
else { // Just in case anything funky happened (usually due to some of the input matrices being non-invertable or too close to it)
inv_moi = vmd_zero_matrix;
}
// calculate the angular_impulse about the center of mass in world coords
vec3d angular_impulse;
vm_vec_cross(&angular_impulse, &hit_pos, impulse);
// calculate the change in rotvel caused by the whack in world coords
vec3d delta_rotvel;
vm_vec_rotate(&delta_rotvel, &angular_impulse, &inv_moi);
// get the total change in vel for the entire docked assembly
vec3d center_mass_delta_vel = *impulse * (1.0f / total_mass);
// get the root of the dock tree, so that updating this velocity will update the rest of the tree
object* root_objp;
root_objp = dock_find_dock_root(objp);
vec3d local_delta_rotvel;
// translate the rotvel change into the root's frame
vm_vec_rotate(&local_delta_rotvel, &delta_rotvel, &root_objp->orient);
// compute the root's linear vel as vel = center mass vel + world frame rotvel x relative pos
vec3d root_delta_vel;
vec3d rel_pos;
vm_vec_sub(&rel_pos, &root_objp->pos, &world_center_of_mass);
vm_vec_cross(&root_delta_vel, &delta_rotvel, &rel_pos);
vm_vec_add2(&root_delta_vel, &center_mass_delta_vel);
// whack it
physics_apply_whack(vm_vec_mag(impulse),
&root_objp->phys_info,
&local_delta_rotvel,
&root_delta_vel,
&root_objp->orient);
}
// functions to deal with all docked ships anywhere
// ---------------------------------------------------------------------------------------------------------------
// universal two functions
// -----------------------
// evaluate a certain function for all docked objects
// The_Force:: If changed please change in QtFred's ShipInitialStatusDialogModel also
void dock_evaluate_all_docked_objects(object *objp, dock_function_info *infop, void (*function)(object *, dock_function_info *))
{
Assertion((objp != nullptr) && (infop != nullptr) && (function != nullptr),
"dock_evaluate_all_docked_objects, invalid argument(s)");
// not docked?
if (!object_is_docked(objp))
{
// call the function for just the one object
function(objp, infop);
return;
}
// we only have two objects docked
if (dock_check_docked_one_on_one(objp))
{
// call the function for the first object, and return if instructed
function(objp, infop);
if (infop->early_return_condition) return;
// call the function for the second object, and return if instructed
function(objp->dock_list->docked_objp, infop);
if (infop->early_return_condition) return;
}
// we have multiple objects docked and we're treating them as a hub
else if (dock_check_assume_hub())
{
// get the hub
object *hub_objp = dock_get_hub(objp);
// call the function for the hub, and return if instructed
function(hub_objp, infop);
if (infop->early_return_condition) return;
// iterate through all docked objects
for (dock_instance *ptr = hub_objp->dock_list; ptr != NULL; ptr = ptr->next)
{
// call the function for this object, and return if instructed
function(ptr->docked_objp, infop);
if (infop->early_return_condition) return;
}
}
// we have multiple objects docked and we must treat them as a tree
else
{
// create a bit array to mark the objects we check
ubyte *visited_bitstring = (ubyte *) vm_malloc(calculate_num_bytes(MAX_OBJECTS));
// clear it
memset(visited_bitstring, 0, calculate_num_bytes(MAX_OBJECTS));
// start evaluating the tree
dock_evaluate_tree(objp, infop, function, visited_bitstring);
// destroy the bit array
vm_free(visited_bitstring);
visited_bitstring = NULL;
}
}
void dock_evaluate_tree(object *objp, dock_function_info *infop, void (*function)(object *, dock_function_info *), ubyte *visited_bitstring)
{
// make sure we haven't visited this object already
if (get_bit(visited_bitstring, OBJ_INDEX(objp)))
return;
// mark as visited
set_bit(visited_bitstring, OBJ_INDEX(objp));
// call the function for this object, and return if instructed
function(objp, infop);
if (infop->early_return_condition) return;
// iterate through all docked objects
for (dock_instance *ptr = objp->dock_list; ptr != NULL; ptr = ptr->next)
{
// start another tree with the docked object as the root, and return if instructed
dock_evaluate_tree(ptr->docked_objp, infop, function, visited_bitstring);
if (infop->early_return_condition) return;
}
}
// special-case functions
// ----------------------
void dock_move_docked_objects(object *objp)
{
Assert(objp != NULL);
if ((objp->type != OBJ_SHIP) && (objp->type != OBJ_START))
return;
if (!object_is_docked(objp))
return;
// has this object (by extension, this group of docked objects) been handled already?
if (objp->flags[Object::Object_Flags::Docked_already_handled])
return;
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
dock_function_info dfi;
object *fastest_objp;
// in FRED, objp is the object everyone moves with
if (Fred_running)
{
fastest_objp = objp;
}
else
{
fastest_objp = dock_find_dock_root(objp);;
}
// start a tree with that object as the parent... do NOT use the überfunction for this,
// because we must use a tree for the parent ancestry to work correctly
// we don't need a bit array because OF_DOCKED_ALREADY_HANDLED takes care of it
// and must persist for the entire game frame
// start evaluating the tree, starting with the fastest object having no parent
dock_move_docked_children_tree(fastest_objp, NULL);
}
void dock_move_docked_children_tree(object *objp, object *parent_objp)
{
// has this object been handled already?
if (objp->flags[Object::Object_Flags::Docked_already_handled])
return;
// mark as handled
objp->flags.set(Object::Object_Flags::Docked_already_handled);
// if parent_objp exists
if (parent_objp != NULL)
{
// move this object to align with it
obj_move_one_docked_object(objp, parent_objp);
}
// iterate through all docked objects
for (dock_instance *ptr = objp->dock_list; ptr != NULL; ptr = ptr->next)
{
// start another tree with the docked object as the root and this object as the parent
dock_move_docked_children_tree(ptr->docked_objp, objp);
}
}
// helper functions
// ----------------
void dock_count_total_docked_objects_helper(object * /*objp*/, dock_function_info *infop)
{
// increment count
infop->maintained_variables.int_value++;
}
void dock_check_find_docked_object_helper(object *objp, dock_function_info *infop)
{
// if object found, set to true and break
if (infop->parameter_variables.objp_value == objp)
{
infop->maintained_variables.bool_value = true;
infop->early_return_condition = true;
}
}
void dock_calc_docked_mins_maxs_helper(object *objp, dock_function_info *infop)
{
vec3d parent_relative_mins, parent_relative_maxs;
// find the model used by this object
auto pm = object_get_model(objp);
Assert(pm);
// special case: we are already in the correct frame of reference
if (objp == infop->parameter_variables.objp_value)
{
parent_relative_mins = pm->mins;
parent_relative_maxs = pm->maxs;
}
// we are not the parent object and need to do some gymnastics
else
{
// get mins and maxs in world coordinates
vec3d world_mins, world_maxs;
vm_vec_unrotate(&world_mins, &pm->mins, &objp->orient);
vm_vec_add2(&world_mins, &objp->pos);
vm_vec_unrotate(&world_maxs, &pm->maxs, &objp->orient);
vm_vec_add2(&world_maxs, &objp->pos);
// now adjust them to be local to the parent
vec3d temp_mins, temp_maxs;
vm_vec_sub(&temp_mins, &world_mins, &infop->parameter_variables.objp_value->pos);
vm_vec_rotate(&parent_relative_mins, &temp_mins, &infop->parameter_variables.objp_value->orient);
vm_vec_sub(&temp_maxs, &world_maxs, &infop->parameter_variables.objp_value->pos);
vm_vec_rotate(&parent_relative_maxs, &temp_maxs, &infop->parameter_variables.objp_value->orient);
}
// We test both points for both cases because they may have been flipped around. However, X is still comparable to X, Y to Y, Z to Z.
// test for overall min
for (int i = 0; i < 3; ++i)
{
if (parent_relative_mins.a1d[i] < infop->maintained_variables.vecp_value->a1d[i])
infop->maintained_variables.vecp_value->a1d[i] = parent_relative_mins.a1d[i];
if (parent_relative_maxs.a1d[i] < infop->maintained_variables.vecp_value->a1d[i])
infop->maintained_variables.vecp_value->a1d[i] = parent_relative_maxs.a1d[i];
}
// test for overall max
for (int i = 0; i < 3; ++i)
{
if (parent_relative_mins.a1d[i] > infop->maintained_variables.vecp_value2->a1d[i])
infop->maintained_variables.vecp_value2->a1d[i] = parent_relative_mins.a1d[i];
if (parent_relative_maxs.a1d[i] > infop->maintained_variables.vecp_value2->a1d[i])
infop->maintained_variables.vecp_value2->a1d[i] = parent_relative_maxs.a1d[i];
}
}
void dock_calc_docked_center_of_mass_helper(object *objp, dock_function_info *infop)
{
// add weighted object position and add mass
vm_vec_scale_add2(infop->maintained_variables.vecp_value, &objp->pos, objp->phys_info.mass);
infop->maintained_variables.float_value += objp->phys_info.mass;
}
void dock_calc_total_docked_mass_helper(object *objp, dock_function_info *infop)
{
// add mass
infop->maintained_variables.float_value += objp->phys_info.mass;
}
// What we're doing here is finding the distances between each extent of the object and the line, and then taking the
// maximum distance as the cross-sectional radius. We're actually maintaining the square of the distance rather than
// the actual distance, as it's faster to calculate and it gives the same result in a greater-than or less-than
// comparison. When we're done calculating everything for all objects (i.e. when we return to the parent function)
// we take the square root of the final value.
void dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper(object *objp, dock_function_info *infop)
{
vec3d world_point, local_point[6], nearest;
polymodel *pm;
int i;
float dist_squared;
// line parameters
vec3d *line_start = infop->parameter_variables.vecp_value;
vec3d *line_end = infop->parameter_variables.vecp_value2;
// We must find world coordinates for each of the six endpoints on the three axes of the object. I looked up
// which axis is front/back, left/right, and up/down, as well as which endpoint is which. It doesn't really
// matter, though, as all we need are the distances.
// grab our model
Assert(objp->type == OBJ_SHIP);
pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
// set up the points we want to check
memset(local_point, 0, sizeof(vec3d) * 6);
local_point[0].xyz.x = pm->maxs.xyz.x; // right point (max x)
local_point[1].xyz.x = pm->mins.xyz.x; // left point (min x)
local_point[2].xyz.y = pm->maxs.xyz.y; // top point (max y)
local_point[3].xyz.y = pm->mins.xyz.y; // bottom point (min y)
local_point[4].xyz.z = pm->maxs.xyz.z; // front point (max z)
local_point[5].xyz.z = pm->mins.xyz.z; // rear point (min z)
// check points
for (i = 0; i < 6; i++)
{
// calculate position of point
vm_vec_unrotate(&world_point, &local_point[i], &objp->orient);
vm_vec_add2(&world_point, &objp->pos);
// calculate square of distance to line
vm_vec_dist_squared_to_line(&world_point, line_start, line_end, &nearest, &dist_squared);
// update with farthest distance squared
if (dist_squared > infop->maintained_variables.float_value)
infop->maintained_variables.float_value = dist_squared;
}
}
// What we're doing here is projecting each object extent onto the directrix, calculating the distance between the
// projected point and the origin, and then taking the maximum distance as the semilatus rectum. We're actually
// maintaining the square of the distance rather than the actual distance, as it's faster to calculate and it gives
// the same result in a greater-than or less-than comparison. When we're done calculating everything for all
// objects (i.e. when we return to the parent function) we take the square root of the final value.
void dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper(object *objp, dock_function_info *infop)
{
vec3d world_point, local_point[6], nearest;
polymodel *pm;
int i;
float temp, dist_squared;
// line parameters
vec3d *line_start = infop->parameter_variables.vecp_value;
vec3d *line_end = infop->parameter_variables.vecp_value2;
// We must find world coordinates for each of the six endpoints on the three axes of the object. I looked up
// which axis is front/back, left/right, and up/down, as well as which endpoint is which. It doesn't really
// matter, though, as all we need are the distances.
// grab our model
Assert(objp->type == OBJ_SHIP);
pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
// set up the points we want to check
memset(local_point, 0, sizeof(vec3d) * 6);
local_point[0].xyz.x = pm->maxs.xyz.x; // right point (max x)
local_point[1].xyz.x = pm->mins.xyz.x; // left point (min x)
local_point[2].xyz.y = pm->maxs.xyz.y; // top point (max y)
local_point[3].xyz.y = pm->mins.xyz.y; // bottom point (min y)
local_point[4].xyz.z = pm->maxs.xyz.z; // front point (max z)
local_point[5].xyz.z = pm->mins.xyz.z; // rear point (min z)
// check points
for (i = 0; i < 6; i++)
{
// calculate position of point
vm_vec_unrotate(&world_point, &local_point[i], &objp->orient);
vm_vec_add2(&world_point, &objp->pos);
// find the nearest point along the line
vm_vec_dist_squared_to_line(&world_point, line_start, line_end, &nearest, &temp);
// find the distance squared between the origin of the line and the point on the line
dist_squared = vm_vec_dist_squared(line_start, &nearest);
// update with farthest distance squared
if (dist_squared > infop->maintained_variables.float_value)
infop->maintained_variables.float_value = dist_squared;
}
}
void dock_find_max_fspeed_helper(object *objp, dock_function_info *infop)
{
// check our fspeed against the running maximum
if (objp->phys_info.fspeed > infop->maintained_variables.float_value)
{
infop->maintained_variables.float_value = objp->phys_info.fspeed;
infop->maintained_variables.objp_value = objp;
}
}
void dock_find_max_speed_helper(object *objp, dock_function_info *infop)
{
// check our speed against the running maximum
if (objp->phys_info.speed > infop->maintained_variables.float_value)
{
infop->maintained_variables.float_value = objp->phys_info.speed;
infop->maintained_variables.objp_value = objp;
}
}
void object_set_arriving_stage1_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
{
if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
Ships[objp->instance].flags.set(Ship::Ship_Flags::Arriving_stage_1_dock_follower);
}
void object_remove_arriving_stage1_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
{
if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
Ships[objp->instance].flags.remove(Ship::Ship_Flags::Arriving_stage_1_dock_follower);
}
void object_set_arriving_stage2_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
{
if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
Ships[objp->instance].flags.set(Ship::Ship_Flags::Arriving_stage_2_dock_follower);
}
void object_remove_arriving_stage2_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
{
if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
Ships[objp->instance].flags.remove(Ship::Ship_Flags::Arriving_stage_2_dock_follower);
}
void dock_find_dock_leader_helper(object *objp, dock_function_info *infop)
{
if (Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
{
infop->maintained_variables.objp_value = objp;
infop->early_return_condition = true;
}
}
void dock_calc_total_moi_helper(object* objp, dock_function_info* infop)
{
matrix local_moi, unorient, temp, world_moi;
// The MOI for a compound object is simply the sum of the MOI's of the parts, but
// they all have to be with respect to the same point (the center of mass, in this case).
// So for each part:
// We invert the inverse MOI to get an MOI in the local frame
if (!vm_inverse_matrix(&local_moi, &objp->phys_info.I_body_inv)) {
// This is done on purpose to indicate a zero inv_moi
infop->maintained_variables.matrix_value->a1d[0] = NAN;
return;
}
// We calculate the inverse of the orientation matrix (which is also the transpose)
vm_copy_transpose(&unorient, &objp->orient);
// We calculate the world space MOI using (world MOI) = O^-1 * (local MOI) * O
// where O is the orientation matrix (which translates between local space and world space).
// Note that because FS stores orientation matrices transposed, objp->orient is O^-1 in this formula.
vm_matrix_x_matrix(&temp, &objp->orient, &local_moi);
vm_matrix_x_matrix(&world_moi, &temp, &unorient);
// The world space MOI just calculated is about the center of mass of the part,
// so we need to translate it to the center of mass of the whole assembly.
// To do this we add a term corresponding to the MOI of a point mass whose position
// is the position of the part relative to the center of mass
vec3d* center = infop->parameter_variables.vecp_value;
vec3d pos = objp->pos - *center;
physics_add_point_mass_moi(&world_moi, objp->phys_info.mass, &pos);
// Finally we add the translated world space MOI for the part to the accumulated sum
*infop->maintained_variables.matrix_value += world_moi;
}
// ---------------------------------------------------------------------------------------------------------------
// end of über code block ----------------------------------------------------------------------------------------
// dock management functions -------------------------------------------------------------------------------------
void dock_dock_objects(object *objp1, int dockpoint1, object *objp2, int dockpoint2)
{
Assert(objp1 != NULL);
Assert(objp2 != NULL);
#ifndef NDEBUG
if ((dock_find_instance(objp1, objp2) != NULL) || (dock_find_instance(objp2, objp1) != NULL))
{
Error(LOCATION, "Trying to dock an object that's already docked!\n");
}
if ((dock_find_instance(objp1, dockpoint1) != NULL) || (dock_find_instance(objp2, dockpoint2) != NULL))
{
Error(LOCATION, "Trying to dock to a dockpoint that's in use!\n");
}
#endif
// put objects on each others' dock lists
dock_add_instance(objp1, dockpoint1, objp2);
dock_add_instance(objp2, dockpoint2, objp1);
}
void dock_undock_objects(object *objp1, object *objp2)
{
Assert(objp1 != NULL);
Assert(objp2 != NULL);
// remove objects from each others' dock lists
dock_remove_instance(objp1, objp2);
dock_remove_instance(objp2, objp1);
}
void dock_undock_all(object *objp)
{
Assert(objp != NULL);
while (object_is_docked(objp))
{
object* dockee = dock_get_first_docked_object(objp);
dock_undock_objects(objp, dockee);
}
}
// dock list functions -------------------------------------------------------------------------------------------
bool dock_check_assume_hub()
{
// There are several ways of handling ships docking to other ships. Level 1, the simplest, is the one-docker, one-dockee
// model used in retail FS2. Level 2 is the hub model, where we stipulate that any given set of docked ships
// includes one ship to which all other ships are docked. No ship except for the hub ship can be docked to more than
// one ship. Level 3 is the daisy-chain model, where you can string ships along and make a rooted tree.
//
// The new code can handle level 3 ship formations, but it requires more overhead than level 2 or level 1. (Whether
// the additional overhead is significant or not has not been determined.) In the vast majority of cases, level 3
// is not needed. So this function is provided to allow the code to optimize itself for level 2, should level 1
// evaluation fail.
// Determining this with a mission flag is a rather brittle design, and in any case this is not likely to be a large
// resource sink. Until a general-purpose tree detection routine is written, this function will always return false.
return false;
}
object *dock_get_hub(object *objp)
{
Assert(dock_check_assume_hub() && object_is_docked(objp));
// if our dock list contains only one object, it must be the hub
if (objp->dock_list->next == NULL)
{
return dock_get_first_docked_object(objp);
}
// otherwise we are the hub
else
{
return objp;
}
}
void dock_add_instance(object *objp, int dockpoint, object *other_objp)
{
dock_instance *item;
// create item
item = (dock_instance *) vm_malloc(sizeof(dock_instance));
item->dockpoint_used = dockpoint;
item->docked_objp = other_objp;
// prepend item to existing list
item->next = objp->dock_list;
objp->dock_list = item;
}
void dock_remove_instance(object *objp, object *other_objp)
{
int found = 0;
dock_instance *prev_ptr, *ptr;
prev_ptr = NULL;
ptr = objp->dock_list;
// iterate until item found
while (ptr != NULL)
{
// if found, exit loop
if (ptr->docked_objp == other_objp)
{
found = 1;
break;
}
// iterate
prev_ptr = ptr;
ptr = ptr->next;
}
// delete if found
if (found)
{
// special case... found at beginning of list
if (prev_ptr == NULL)
{
objp->dock_list = ptr->next;
}
// normal case
else
{
prev_ptr->next = ptr->next;
}
// delete it
vm_free(ptr);
}
else
{
// Trigger an assertion, we can recover from this one, thankfully.
Assertion(false, "Tried to undock an object that isn't docked!\n");
}
}
// just free the list without worrying about undocking anything
void dock_free_dock_list(object *objp)
{
Assert(objp != NULL);
while (objp->dock_list != NULL)
{
dock_instance *ptr = objp->dock_list;
objp->dock_list = ptr->next;
vm_free(ptr);
}
}
dock_instance *dock_find_instance(object *objp, object *other_objp)
{
dock_instance *ptr = objp->dock_list;
// iterate until item found
while (ptr != NULL)
{
// if found, return it
if (ptr->docked_objp == other_objp)
return ptr;
// iterate
ptr = ptr->next;
}
// not found
return NULL;
}
dock_instance *dock_find_instance(object *objp, int dockpoint)
{
dock_instance *ptr = objp->dock_list;
// iterate until item found
while (ptr != NULL)
{
// if found, return it
if (ptr->dockpoint_used == dockpoint)
return ptr;
// iterate
ptr = ptr->next;
}
// not found
return NULL;
}
int dock_count_instances(object *objp)
{
int total_count = 0;
// count all instances in the list
dock_instance *ptr = objp->dock_list;
while (ptr != NULL)
{
// incrememnt for this object
total_count++;
// iterate
ptr = ptr->next;
}
// done
return total_count;
}