text: /*
   * 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(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)
  {
  	polymodel *pm;
  	vec3d parent_relative_mins, parent_relative_maxs;
  
  	// find the model used by this object
  	int modelnum = object_get_model(objp);
  	Assert(modelnum >= 0);
  	pm = model_get(modelnum);
  
  	// 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_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.
  		UNREACHABLE("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;
  }
  