1 /*
2  * Created by Ian "Goober5000" Warfield for the FreeSpace2 Source Code Project.
3  * You may not sell or otherwise commercially exploit the source or things you
4  * create based on the source.
5  */
6 
7 
8 
9 #include "math/bitarray.h"
10 #include "math/vecmat.h"
11 #include "mission/missionparse.h"
12 #include "object/object.h"
13 #include "object/objectdock.h"
14 #include "ship/ship.h"
15 
16 
17 
18 
19 // helper prototypes
20 
21 void dock_evaluate_tree(object *objp, dock_function_info *infop, void (*function)(object *, dock_function_info *), ubyte *visited_bitstring);
22 void dock_move_docked_children_tree(object *objp, object *parent_objp);
23 void dock_count_total_docked_objects_helper(object *objp, dock_function_info *infop);
24 void dock_check_find_docked_object_helper(object *objp, dock_function_info *infop);
25 void dock_calc_docked_mins_maxs_helper(object *objp, dock_function_info *infop);
26 void dock_calc_docked_center_of_mass_helper(object *objp, dock_function_info *infop);
27 void dock_calc_total_docked_mass_helper(object *objp, dock_function_info *infop);
28 void dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper(object *objp, dock_function_info *infop);
29 void dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper(object *objp, dock_function_info *infop);
30 void dock_find_max_speed_helper(object *objp, dock_function_info *infop);
31 void dock_find_max_fspeed_helper(object *objp, dock_function_info *infop);
32 void dock_calc_total_moi_helper(object* objp, dock_function_info* infop);
33 
34 // management prototypes
35 
36 bool dock_check_assume_hub();
37 object *dock_get_hub(object *objp);
38 
39 void dock_add_instance(object *objp, int dockpoint, object *other_objp);
40 void dock_remove_instance(object *objp, object *other_objp);
41 dock_instance *dock_find_instance(object *objp, object *other_objp);
42 dock_instance *dock_find_instance(object *objp, int dockpoint);
43 int dock_count_instances(object *objp);
44 
45 
46 
dock_get_first_docked_object(object * objp)47 object *dock_get_first_docked_object(object *objp)
48 {
49 	Assert(objp != NULL);
50 
51 	// are we docked?
52 	if (!object_is_docked(objp))
53 		return NULL;
54 
55 	return objp->dock_list->docked_objp;
56 }
57 
dock_check_docked_one_on_one(object * objp)58 bool dock_check_docked_one_on_one(object *objp)
59 {
60 	Assert(objp != NULL);
61 
62 	// we must be docked
63 	if (!object_is_docked(objp))
64 		return false;
65 
66 	// our dock list must contain only one object
67 	if (objp->dock_list->next != NULL)
68 		return false;
69 
70 	// the other guy's dock list must contain only one object
71 	if (dock_get_first_docked_object(objp)->dock_list->next != NULL)
72 		return false;
73 
74 	// debug check to make sure that we're docked to each other
75 	Assert(objp == dock_get_first_docked_object(objp)->dock_list->docked_objp);
76 
77 	// success
78 	return true;
79 }
80 
dock_count_direct_docked_objects(object * objp)81 int dock_count_direct_docked_objects(object *objp)
82 {
83 	Assert(objp != NULL);
84 	return dock_count_instances(objp);
85 }
86 
dock_count_total_docked_objects(object * objp)87 int dock_count_total_docked_objects(object *objp)
88 {
89 	Assert(objp != NULL);
90 
91 	dock_function_info dfi;
92 
93 	dock_evaluate_all_docked_objects(objp, &dfi, dock_count_total_docked_objects_helper);
94 
95 	return dfi.maintained_variables.int_value;
96 }
97 
dock_check_find_direct_docked_object(object * objp,object * other_objp)98 bool dock_check_find_direct_docked_object(object *objp, object *other_objp)
99 {
100 	Assert(objp != NULL);
101 	Assert(other_objp != NULL);
102 
103 	return (dock_find_instance(objp, other_objp) != NULL);
104 }
105 
dock_check_find_docked_object(object * objp,object * other_objp)106 bool dock_check_find_docked_object(object *objp, object *other_objp)
107 {
108 	Assert(objp != nullptr);
109 	Assert(objp->signature > 0);
110 	Assert(other_objp != nullptr);
111 	Assert(other_objp->signature > 0);
112 
113 
114 	if (!(objp != nullptr && objp->signature > 0))
115 		return false;
116 	if (!(other_objp != nullptr && other_objp->signature > 0))
117 		return false;
118 
119 	dock_function_info dfi;
120 	dfi.parameter_variables.objp_value = other_objp;
121 
122 	dock_evaluate_all_docked_objects(objp, &dfi, dock_check_find_docked_object_helper);
123 
124 	return dfi.maintained_variables.bool_value;
125 }
126 
dock_find_object_at_dockpoint(object * objp,int dockpoint)127 object *dock_find_object_at_dockpoint(object *objp, int dockpoint)
128 {
129 	Assert(objp != NULL);
130 
131 	dock_instance *result = dock_find_instance(objp, dockpoint);
132 
133 	if (result == NULL)
134 		return NULL;
135 	else
136 		return result->docked_objp;
137 }
138 
dock_find_dockpoint_used_by_object(object * objp,object * other_objp)139 int dock_find_dockpoint_used_by_object(object *objp, object *other_objp)
140 {
141 	Assert(objp != NULL);
142 	Assert(other_objp != NULL);
143 
144 	dock_instance *result = dock_find_instance(objp, other_objp);
145 
146 	if (result == NULL)
147 		return -1;
148 	else
149 		return result->dockpoint_used;
150 }
151 
152 /**
153  * Get the offset of the actual center of the docked ship models for the purposes of warping (which may not be the specified center).
154  * Note, these are LOCAL coordinates in relation to objp, not world coordinates.
155  * See also ship_class_get_actual_center() in ship.cpp
156  */
dock_calc_docked_actual_center(vec3d * dest,object * objp)157 void dock_calc_docked_actual_center(vec3d *dest, object *objp)
158 {
159 	Assert(dest != nullptr);
160 	Assert(objp != nullptr);
161 
162 	vec3d overall_mins, overall_maxs;
163 	dock_calc_docked_extents(&overall_mins, &overall_maxs, objp);
164 
165 	// c.f. ship_class_get_actual_center() in ship.cpp
166 	dest->xyz.x = (overall_maxs.xyz.x + overall_mins.xyz.x) * 0.5f;
167 	dest->xyz.y = (overall_maxs.xyz.y + overall_mins.xyz.y) * 0.5f;
168 	dest->xyz.z = (overall_maxs.xyz.z + overall_mins.xyz.z) * 0.5f;
169 }
170 
171 /**
172 * Get the mins and maxs of the entire assembly of docked ship models.
173 * Note, these are LOCAL coordinates in relation to objp, not world coordinates.
174 */
dock_calc_docked_extents(vec3d * mins,vec3d * maxs,object * objp)175 void dock_calc_docked_extents(vec3d *mins, vec3d *maxs, object *objp)
176 {
177 	Assert(mins != nullptr);
178 	Assert(maxs != nullptr);
179 	Assert(objp != nullptr);
180 
181 	*mins = vmd_zero_vector;
182 	*maxs = vmd_zero_vector;
183 
184 	// Let's calculate all mins/maxes in relation to the orientation of the main object
185 	// (which is expected to be the dock leader, but this technically isn't required).
186 	// Since the vast majority of dockpoints are aligned with an axis, this should
187 	// yield a much better fit of our docked bounding box.
188 
189 	dock_function_info dfi;
190 	dfi.parameter_variables.objp_value = objp;		// the reference object for our bounding box orientation
191 	dfi.maintained_variables.vecp_value = mins;		// mins
192 	dfi.maintained_variables.vecp_value2 = maxs;	// maxs
193 
194 	dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_docked_mins_maxs_helper);
195 }
196 
dock_calc_docked_center_of_mass(vec3d * dest,object * objp)197 float dock_calc_docked_center_of_mass(vec3d *dest, object *objp)
198 {
199 	Assertion(dest != nullptr, "dock_calc_docked_center_of_mass, invalid dest");
200 	Assertion(objp != nullptr, "dock_calc_docked_center_of_mass, invalid objp");
201 
202 	vm_vec_zero(dest);
203 
204 	dock_function_info dfi;
205 	dfi.maintained_variables.vecp_value = dest;
206 
207 	dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_docked_center_of_mass_helper);
208 
209 	// overall center of mass = weighted sum of centers of mass divided by total mass
210 	float total_mass = dfi.maintained_variables.float_value;
211 	vm_vec_scale(dest, (1.0f / total_mass));
212 	return total_mass;
213 }
214 
dock_calc_total_docked_mass(object * objp)215 float dock_calc_total_docked_mass(object *objp)
216 {
217 	Assertion(objp != nullptr, "dock_calc_total_docked_mass, invalid argument");
218 
219 	dock_function_info dfi;
220 
221 	dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_total_docked_mass_helper);
222 
223 	return dfi.maintained_variables.float_value;
224 }
225 
dock_calc_max_cross_sectional_radius_perpendicular_to_axis(object * objp,axis_type axis)226 float dock_calc_max_cross_sectional_radius_perpendicular_to_axis(object *objp, axis_type axis)
227 {
228 	Assert(objp != NULL);
229 
230 	vec3d local_line_end;
231 	vec3d *world_line_start, world_line_end;
232 	dock_function_info dfi;
233 
234 	// to calculate the cross-sectional radius, we need a line that will be perpendicular to the cross-section
235 
236 	// the first endpoint is simply the position of the object
237 	world_line_start = &objp->pos;
238 
239 	// the second endpoint extends in the axis direction
240 	vm_vec_zero(&local_line_end);
241 	switch(axis)
242 	{
243 		case X_AXIS:
244 			local_line_end.xyz.x = 1.0f;
245 			break;
246 
247 		case Y_AXIS:
248 			local_line_end.xyz.y = 1.0f;
249 			break;
250 
251 		case Z_AXIS:
252 			local_line_end.xyz.z = 1.0f;
253 			break;
254 
255 		default:
256 			Int3();
257 			return 0.0f;
258 	}
259 
260 	// move the endpoint to go through the axis of the actual object
261 	vm_vec_unrotate(&world_line_end, &local_line_end, &objp->orient);
262 	vm_vec_add2(&world_line_end, &objp->pos);
263 
264 	// now we have a unit vector starting at the object's position and pointing along the chosen axis
265 	// (although the length doesn't matter, as it's calculated as an endless line)
266 
267 	// now determine the cross-sectional radius
268 
269 	// set parameters and call function for the radius squared
270 	dfi.parameter_variables.vecp_value = world_line_start;
271 	dfi.parameter_variables.vecp_value2 = &world_line_end;
272 	dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper);
273 
274 	// the radius is the square root of our result
275 	return fl_sqrt(dfi.maintained_variables.float_value);
276 }
277 
dock_calc_max_semilatus_rectum_parallel_to_axis(object * objp,axis_type axis)278 float dock_calc_max_semilatus_rectum_parallel_to_axis(object *objp, axis_type axis)
279 {
280 	Assert(objp != NULL);
281 
282 	vec3d local_line_end;
283 	vec3d *world_line_start, world_line_end;
284 	dock_function_info dfi;
285 
286 	// to calculate the semilatus rectum, we need a directrix that will be parallel to the axis
287 
288 	// the first endpoint is simply the position of the object
289 	world_line_start = &objp->pos;
290 
291 	// the second endpoint extends in the axis direction
292 	vm_vec_zero(&local_line_end);
293 	switch(axis)
294 	{
295 		case X_AXIS:
296 			local_line_end.xyz.x = 1.0f;
297 			break;
298 
299 		case Y_AXIS:
300 			local_line_end.xyz.y = 1.0f;
301 			break;
302 
303 		case Z_AXIS:
304 			local_line_end.xyz.z = 1.0f;
305 			break;
306 
307 		default:
308 			Int3();
309 			return 0.0f;
310 	}
311 
312 	// move the endpoint to go through the axis of the actual object
313 	vm_vec_unrotate(&world_line_end, &local_line_end, &objp->orient);
314 	vm_vec_add2(&world_line_end, &objp->pos);
315 
316 	// now we have a unit vector starting at the object's position and pointing along the chosen axis
317 	// (although the length doesn't matter, as it's calculated as an endless line)
318 
319 	// now determine the semilatus rectum
320 
321 	// set parameters and call function for the semilatus rectum squared
322 	dfi.parameter_variables.vecp_value = world_line_start;
323 	dfi.parameter_variables.vecp_value2 = &world_line_end;
324 	dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper);
325 
326 	// the semilatus rectum is the square root of our result
327 	return fl_sqrt(dfi.maintained_variables.float_value);
328 }
329 
dock_calc_docked_fspeed(object * objp)330 float dock_calc_docked_fspeed(object *objp)
331 {
332 	Assert(objp != NULL);
333 
334 	// *sigh*... the docked fspeed is simply the max fspeed of all docked objects
335 	dock_function_info dfi;
336 	dock_evaluate_all_docked_objects(objp, &dfi, dock_find_max_fspeed_helper);
337 	return dfi.maintained_variables.float_value;
338 }
339 
dock_calc_docked_speed(object * objp)340 float dock_calc_docked_speed(object *objp)
341 {
342 	Assert(objp != NULL);
343 
344 	// ditto with speed
345 	dock_function_info dfi;
346 	dock_evaluate_all_docked_objects(objp, &dfi, dock_find_max_speed_helper);
347 	return dfi.maintained_variables.float_value;
348 }
349 
350 // Calculates the total moi (NOT INVERTED) of a docked assembly
351 //		dest		=>		output matrix
352 //		objp		=>		one of the objects in the assembly
353 //		center 		=>		center of mass of the assembly in world coords ( use dock_calc_docked_center_of_mass to find it )
354 // Returns whether or not was successful (in case some or all of the matrices were uninvertable or too close to it)
355 // If not successful, dest will have NaN or infinity, use at your own risk!
dock_calc_total_moi(matrix * dest,object * objp,vec3d * center)356 bool dock_calc_total_moi(matrix* dest, object* objp, vec3d *center)
357 {
358 	Assertion((dest != nullptr) && (objp != nullptr) && (center != nullptr), "dock_calc_total_moi invalid argument(s)");
359 
360 	*dest = vmd_zero_matrix;
361 
362 	dock_function_info dfi;
363 	dfi.parameter_variables.vecp_value = center;
364 	dfi.maintained_variables.matrix_value = dest;
365 
366 	dock_evaluate_all_docked_objects(objp, &dfi, dock_calc_total_moi_helper);
367 
368 	return is_valid_matrix(dest);
369 }
370 
371 // This ship is the only ship NOT moved by docking AI to keep everyone together
372 // All the other ships in the tree will update based on this one
373 // Since this is based on current speed don't expect it to remain consistent between frames
dock_find_dock_root(object * objp)374 object* dock_find_dock_root(object *objp)
375 {
376 	Assertion(objp != nullptr, "dock_find_dock_root invalid argument");
377 
378 	dock_function_info dfi;
379 	object* fastest_objp;
380 
381 	dfi.maintained_variables.objp_value = nullptr;
382 
383 	// find the object with the highest speed
384 	dock_evaluate_all_docked_objects(objp, &dfi, dock_find_max_speed_helper);
385 	fastest_objp = dfi.maintained_variables.objp_value;
386 
387 	// if we have no max speed, just use the given one
388 	if (fastest_objp == nullptr)
389 		fastest_objp = objp;
390 
391 	return fastest_objp;
392 }
393 
dock_calculate_and_apply_whack_docked_object(vec3d * impulse,const vec3d * world_hit_pos,object * objp)394 void dock_calculate_and_apply_whack_docked_object(vec3d* impulse, const vec3d* world_hit_pos, object* objp)
395 {
396 	Assertion((objp != nullptr) && (impulse != nullptr) && (world_hit_pos != nullptr),
397 		"dock_whack_docked_object invalid argument(s)");
398 
399 	//	Detect null vector.
400 	if (whack_below_limit(impulse))
401 		return;
402 
403 	// calc overall world center-of-mass of all ships
404 	vec3d world_center_of_mass;
405 	float total_mass = dock_calc_docked_center_of_mass(&world_center_of_mass, objp);
406 
407 	vec3d hit_pos;
408 	// the new hitpos is the vector from world center-of-mass to world hitpos
409 	vm_vec_sub(&hit_pos, world_hit_pos, &world_center_of_mass);
410 
411 	matrix moi, inv_moi;
412 	// calculate the effective inverse MOI for the docked composite object about its center of mass
413 	if (dock_calc_total_moi(&moi, objp, &world_center_of_mass)) {
414 		vm_inverse_matrix(&inv_moi, &moi);
415 	}
416 	else { // Just in case anything funky happened (usually due to some of the input matrices being non-invertable or too close to it)
417 		inv_moi = vmd_zero_matrix;
418 	}
419 
420 	// calculate the angular_impulse about the center of mass in world coords
421 	vec3d angular_impulse;
422 	vm_vec_cross(&angular_impulse, &hit_pos, impulse);
423 
424 	// calculate the change in rotvel caused by the whack in world coords
425 	vec3d delta_rotvel;
426 	vm_vec_rotate(&delta_rotvel, &angular_impulse, &inv_moi);
427 
428 	// get the total change in vel for the entire docked assembly
429 	vec3d center_mass_delta_vel = *impulse * (1.0f / total_mass);
430 
431 	// get the root of the dock tree, so that updating this velocity will update the rest of the tree
432 	object* root_objp;
433 	root_objp = dock_find_dock_root(objp);
434 
435 	vec3d local_delta_rotvel;
436 
437 	// translate the rotvel change into the root's frame
438 	vm_vec_rotate(&local_delta_rotvel, &delta_rotvel, &root_objp->orient);
439 
440 	// compute the root's linear vel as vel = center mass vel + world frame rotvel x relative pos
441 	vec3d root_delta_vel;
442 	vec3d rel_pos;
443 	vm_vec_sub(&rel_pos, &root_objp->pos, &world_center_of_mass);
444 	vm_vec_cross(&root_delta_vel, &delta_rotvel, &rel_pos);
445 	vm_vec_add2(&root_delta_vel, &center_mass_delta_vel);
446 
447 	// whack it
448 	physics_apply_whack(vm_vec_mag(impulse),
449 		&root_objp->phys_info,
450 		&local_delta_rotvel,
451 		&root_delta_vel,
452 		&root_objp->orient);
453 
454 }
455 
456 
457 // functions to deal with all docked ships anywhere
458 // ---------------------------------------------------------------------------------------------------------------
459 
460 // universal two functions
461 // -----------------------
462 
463 // evaluate a certain function for all docked objects
dock_evaluate_all_docked_objects(object * objp,dock_function_info * infop,void (* function)(object *,dock_function_info *))464 void dock_evaluate_all_docked_objects(object *objp, dock_function_info *infop, void (*function)(object *, dock_function_info *))
465 {
466 	Assertion((objp != nullptr) && (infop != nullptr) && (function != nullptr),
467 		"dock_evaluate_all_docked_objects, invalid argument(s)");
468 
469 	// not docked?
470 	if (!object_is_docked(objp))
471 	{
472 		// call the function for just the one object
473 		function(objp, infop);
474 		return;
475 	}
476 
477 	// we only have two objects docked
478 	if (dock_check_docked_one_on_one(objp))
479 	{
480 		// call the function for the first object, and return if instructed
481 		function(objp, infop);
482 		if (infop->early_return_condition) return;
483 
484 		// call the function for the second object, and return if instructed
485 		function(objp->dock_list->docked_objp, infop);
486 		if (infop->early_return_condition) return;
487 	}
488 
489 	// we have multiple objects docked and we're treating them as a hub
490 	else if (dock_check_assume_hub())
491 	{
492 		// get the hub
493 		object *hub_objp = dock_get_hub(objp);
494 
495 		// call the function for the hub, and return if instructed
496 		function(hub_objp, infop);
497 		if (infop->early_return_condition) return;
498 
499 		// iterate through all docked objects
500 		for (dock_instance *ptr = hub_objp->dock_list; ptr != NULL; ptr = ptr->next)
501 		{
502 			// call the function for this object, and return if instructed
503 			function(ptr->docked_objp, infop);
504 			if (infop->early_return_condition) return;
505 		}
506 	}
507 
508 	// we have multiple objects docked and we must treat them as a tree
509 	else
510 	{
511 		// create a bit array to mark the objects we check
512 		ubyte *visited_bitstring = (ubyte *) vm_malloc(calculate_num_bytes(MAX_OBJECTS));
513 
514 		// clear it
515 		memset(visited_bitstring, 0, calculate_num_bytes(MAX_OBJECTS));
516 
517 		// start evaluating the tree
518 		dock_evaluate_tree(objp, infop, function, visited_bitstring);
519 
520 		// destroy the bit array
521 		vm_free(visited_bitstring);
522 		visited_bitstring = NULL;
523 	}
524 }
525 
dock_evaluate_tree(object * objp,dock_function_info * infop,void (* function)(object *,dock_function_info *),ubyte * visited_bitstring)526 void dock_evaluate_tree(object *objp, dock_function_info *infop, void (*function)(object *, dock_function_info *), ubyte *visited_bitstring)
527 {
528 	// make sure we haven't visited this object already
529 	if (get_bit(visited_bitstring, OBJ_INDEX(objp)))
530 		return;
531 
532 	// mark as visited
533 	set_bit(visited_bitstring, OBJ_INDEX(objp));
534 
535 	// call the function for this object, and return if instructed
536 	function(objp, infop);
537 	if (infop->early_return_condition) return;
538 
539 	// iterate through all docked objects
540 	for (dock_instance *ptr = objp->dock_list; ptr != NULL; ptr = ptr->next)
541 	{
542 		// start another tree with the docked object as the root, and return if instructed
543 		dock_evaluate_tree(ptr->docked_objp, infop, function, visited_bitstring);
544 		if (infop->early_return_condition) return;
545 	}
546 }
547 
548 // special-case functions
549 // ----------------------
550 
dock_move_docked_objects(object * objp)551 void dock_move_docked_objects(object *objp)
552 {
553 	Assert(objp != NULL);
554 
555 	if ((objp->type != OBJ_SHIP) && (objp->type != OBJ_START))
556 		return;
557 
558 	if (!object_is_docked(objp))
559 		return;
560 
561 	// has this object (by extension, this group of docked objects) been handled already?
562 	if (objp->flags[Object::Object_Flags::Docked_already_handled])
563 		return;
564 
565 	Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
566 
567 	dock_function_info dfi;
568 	object *fastest_objp;
569 
570 	// in FRED, objp is the object everyone moves with
571 	if (Fred_running)
572 	{
573 		fastest_objp = objp;
574 	}
575 	else
576 	{
577 		fastest_objp = dock_find_dock_root(objp);;
578 	}
579 
580 	// start a tree with that object as the parent... do NOT use the überfunction for this,
581 	// because we must use a tree for the parent ancestry to work correctly
582 
583 	// we don't need a bit array because OF_DOCKED_ALREADY_HANDLED takes care of it
584 	// and must persist for the entire game frame
585 
586 	// start evaluating the tree, starting with the fastest object having no parent
587 	dock_move_docked_children_tree(fastest_objp, NULL);
588 }
589 
dock_move_docked_children_tree(object * objp,object * parent_objp)590 void dock_move_docked_children_tree(object *objp, object *parent_objp)
591 {
592 	// has this object been handled already?
593 	if (objp->flags[Object::Object_Flags::Docked_already_handled])
594 		return;
595 
596 	// mark as handled
597     objp->flags.set(Object::Object_Flags::Docked_already_handled);
598 
599 	// if parent_objp exists
600 	if (parent_objp != NULL)
601 	{
602 		// move this object to align with it
603 		obj_move_one_docked_object(objp, parent_objp);
604 	}
605 
606 	// iterate through all docked objects
607 	for (dock_instance *ptr = objp->dock_list; ptr != NULL; ptr = ptr->next)
608 	{
609 		// start another tree with the docked object as the root and this object as the parent
610 		dock_move_docked_children_tree(ptr->docked_objp, objp);
611 	}
612 }
613 
614 
615 // helper functions
616 // ----------------
617 
dock_count_total_docked_objects_helper(object *,dock_function_info * infop)618 void dock_count_total_docked_objects_helper(object * /*objp*/, dock_function_info *infop)
619 {
620 	// increment count
621 	infop->maintained_variables.int_value++;
622 }
623 
dock_check_find_docked_object_helper(object * objp,dock_function_info * infop)624 void dock_check_find_docked_object_helper(object *objp, dock_function_info *infop)
625 {
626 	// if object found, set to true and break
627 	if (infop->parameter_variables.objp_value == objp)
628 	{
629 		infop->maintained_variables.bool_value = true;
630 		infop->early_return_condition = true;
631 	}
632 }
633 
dock_calc_docked_mins_maxs_helper(object * objp,dock_function_info * infop)634 void dock_calc_docked_mins_maxs_helper(object *objp, dock_function_info *infop)
635 {
636 	polymodel *pm;
637 	vec3d parent_relative_mins, parent_relative_maxs;
638 
639 	// find the model used by this object
640 	int modelnum = object_get_model(objp);
641 	Assert(modelnum >= 0);
642 	pm = model_get(modelnum);
643 
644 	// special case: we are already in the correct frame of reference
645 	if (objp == infop->parameter_variables.objp_value)
646 	{
647 		parent_relative_mins = pm->mins;
648 		parent_relative_maxs = pm->maxs;
649 	}
650 	// we are not the parent object and need to do some gymnastics
651 	else
652 	{
653 		// get mins and maxs in world coordinates
654 		vec3d world_mins, world_maxs;
655 		vm_vec_unrotate(&world_mins, &pm->mins, &objp->orient);
656 		vm_vec_add2(&world_mins, &objp->pos);
657 		vm_vec_unrotate(&world_maxs, &pm->maxs, &objp->orient);
658 		vm_vec_add2(&world_maxs, &objp->pos);
659 
660 		// now adjust them to be local to the parent
661 		vec3d temp_mins, temp_maxs;
662 		vm_vec_sub(&temp_mins, &world_mins, &infop->parameter_variables.objp_value->pos);
663 		vm_vec_rotate(&parent_relative_mins, &temp_mins, &infop->parameter_variables.objp_value->orient);
664 		vm_vec_sub(&temp_maxs, &world_maxs, &infop->parameter_variables.objp_value->pos);
665 		vm_vec_rotate(&parent_relative_maxs, &temp_maxs, &infop->parameter_variables.objp_value->orient);
666 	}
667 
668 	// 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.
669 
670 	// test for overall min
671 	for (int i = 0; i < 3; ++i)
672 	{
673 		if (parent_relative_mins.a1d[i] < infop->maintained_variables.vecp_value->a1d[i])
674 			infop->maintained_variables.vecp_value->a1d[i] = parent_relative_mins.a1d[i];
675 		if (parent_relative_maxs.a1d[i] < infop->maintained_variables.vecp_value->a1d[i])
676 			infop->maintained_variables.vecp_value->a1d[i] = parent_relative_maxs.a1d[i];
677 	}
678 
679 	// test for overall max
680 	for (int i = 0; i < 3; ++i)
681 	{
682 		if (parent_relative_mins.a1d[i] > infop->maintained_variables.vecp_value2->a1d[i])
683 			infop->maintained_variables.vecp_value2->a1d[i] = parent_relative_mins.a1d[i];
684 		if (parent_relative_maxs.a1d[i] > infop->maintained_variables.vecp_value2->a1d[i])
685 			infop->maintained_variables.vecp_value2->a1d[i] = parent_relative_maxs.a1d[i];
686 	}
687 }
688 
dock_calc_docked_center_of_mass_helper(object * objp,dock_function_info * infop)689 void dock_calc_docked_center_of_mass_helper(object *objp, dock_function_info *infop)
690 {
691 	// add weighted object position and add mass
692 	vm_vec_scale_add2(infop->maintained_variables.vecp_value, &objp->pos, objp->phys_info.mass);
693 	infop->maintained_variables.float_value += objp->phys_info.mass;
694 }
695 
dock_calc_total_docked_mass_helper(object * objp,dock_function_info * infop)696 void dock_calc_total_docked_mass_helper(object *objp, dock_function_info *infop)
697 {
698 	// add mass
699 	infop->maintained_variables.float_value += objp->phys_info.mass;
700 }
701 
702 // What we're doing here is finding the distances between each extent of the object and the line, and then taking the
703 // maximum distance as the cross-sectional radius.  We're actually maintaining the square of the distance rather than
704 // the actual distance, as it's faster to calculate and it gives the same result in a greater-than or less-than
705 // comparison.  When we're done calculating everything for all objects (i.e. when we return to the parent function)
706 // we take the square root of the final value.
dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper(object * objp,dock_function_info * infop)707 void dock_calc_max_cross_sectional_radius_squared_perpendicular_to_line_helper(object *objp, dock_function_info *infop)
708 {
709 	vec3d world_point, local_point[6], nearest;
710 	polymodel *pm;
711 	int i;
712 	float dist_squared;
713 
714 	// line parameters
715 	vec3d *line_start = infop->parameter_variables.vecp_value;
716 	vec3d *line_end = infop->parameter_variables.vecp_value2;
717 
718 	// We must find world coordinates for each of the six endpoints on the three axes of the object.  I looked up
719 	// which axis is front/back, left/right, and up/down, as well as which endpoint is which.  It doesn't really
720 	// matter, though, as all we need are the distances.
721 
722 	// grab our model
723 	Assert(objp->type == OBJ_SHIP);
724 	pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
725 
726 	// set up the points we want to check
727 	memset(local_point, 0, sizeof(vec3d) * 6);
728 	local_point[0].xyz.x = pm->maxs.xyz.x;	// right point (max x)
729 	local_point[1].xyz.x = pm->mins.xyz.x;	// left point (min x)
730 	local_point[2].xyz.y = pm->maxs.xyz.y;	// top point (max y)
731 	local_point[3].xyz.y = pm->mins.xyz.y;	// bottom point (min y)
732 	local_point[4].xyz.z = pm->maxs.xyz.z;	// front point (max z)
733 	local_point[5].xyz.z = pm->mins.xyz.z;	// rear point (min z)
734 
735 	// check points
736 	for (i = 0; i < 6; i++)
737 	{
738 		// calculate position of point
739 		vm_vec_unrotate(&world_point, &local_point[i], &objp->orient);
740 		vm_vec_add2(&world_point, &objp->pos);
741 
742 		// calculate square of distance to line
743 		vm_vec_dist_squared_to_line(&world_point, line_start, line_end, &nearest, &dist_squared);
744 
745 		// update with farthest distance squared
746 		if (dist_squared > infop->maintained_variables.float_value)
747 			infop->maintained_variables.float_value = dist_squared;
748 	}
749 }
750 
751 // What we're doing here is projecting each object extent onto the directrix, calculating the distance between the
752 // projected point and the origin, and then taking the maximum distance as the semilatus rectum.  We're actually
753 // maintaining the square of the distance rather than the actual distance, as it's faster to calculate and it gives
754 // the same result in a greater-than or less-than comparison.  When we're done calculating everything for all
755 // objects (i.e. when we return to the parent function) we take the square root of the final value.
dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper(object * objp,dock_function_info * infop)756 void dock_calc_max_semilatus_rectum_squared_parallel_to_directrix_helper(object *objp, dock_function_info *infop)
757 {
758 	vec3d world_point, local_point[6], nearest;
759 	polymodel *pm;
760 	int i;
761 	float temp, dist_squared;
762 
763 	// line parameters
764 	vec3d *line_start = infop->parameter_variables.vecp_value;
765 	vec3d *line_end = infop->parameter_variables.vecp_value2;
766 
767 	// We must find world coordinates for each of the six endpoints on the three axes of the object.  I looked up
768 	// which axis is front/back, left/right, and up/down, as well as which endpoint is which.  It doesn't really
769 	// matter, though, as all we need are the distances.
770 
771 	// grab our model
772 	Assert(objp->type == OBJ_SHIP);
773 	pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
774 
775 	// set up the points we want to check
776 	memset(local_point, 0, sizeof(vec3d) * 6);
777 	local_point[0].xyz.x = pm->maxs.xyz.x;	// right point (max x)
778 	local_point[1].xyz.x = pm->mins.xyz.x;	// left point (min x)
779 	local_point[2].xyz.y = pm->maxs.xyz.y;	// top point (max y)
780 	local_point[3].xyz.y = pm->mins.xyz.y;	// bottom point (min y)
781 	local_point[4].xyz.z = pm->maxs.xyz.z;	// front point (max z)
782 	local_point[5].xyz.z = pm->mins.xyz.z;	// rear point (min z)
783 
784 	// check points
785 	for (i = 0; i < 6; i++)
786 	{
787 		// calculate position of point
788 		vm_vec_unrotate(&world_point, &local_point[i], &objp->orient);
789 		vm_vec_add2(&world_point, &objp->pos);
790 
791 		// find the nearest point along the line
792 		vm_vec_dist_squared_to_line(&world_point, line_start, line_end, &nearest, &temp);
793 
794 		// find the distance squared between the origin of the line and the point on the line
795 		dist_squared = vm_vec_dist_squared(line_start, &nearest);
796 
797 		// update with farthest distance squared
798 		if (dist_squared > infop->maintained_variables.float_value)
799 			infop->maintained_variables.float_value = dist_squared;
800 	}
801 }
802 
dock_find_max_fspeed_helper(object * objp,dock_function_info * infop)803 void dock_find_max_fspeed_helper(object *objp, dock_function_info *infop)
804 {
805 	// check our fspeed against the running maximum
806 	if (objp->phys_info.fspeed > infop->maintained_variables.float_value)
807 	{
808 		infop->maintained_variables.float_value = objp->phys_info.fspeed;
809 		infop->maintained_variables.objp_value = objp;
810 	}
811 }
812 
dock_find_max_speed_helper(object * objp,dock_function_info * infop)813 void dock_find_max_speed_helper(object *objp, dock_function_info *infop)
814 {
815 	// check our speed against the running maximum
816 	if (objp->phys_info.speed > infop->maintained_variables.float_value)
817 	{
818 		infop->maintained_variables.float_value = objp->phys_info.speed;
819 		infop->maintained_variables.objp_value = objp;
820 	}
821 }
822 
object_set_arriving_stage1_ndl_flag_helper(object * objp,dock_function_info *)823 void object_set_arriving_stage1_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
824 {
825 	if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
826 		Ships[objp->instance].flags.set(Ship::Ship_Flags::Arriving_stage_1_dock_follower);
827 }
828 
object_remove_arriving_stage1_ndl_flag_helper(object * objp,dock_function_info *)829 void object_remove_arriving_stage1_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
830 {
831 	if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
832 		Ships[objp->instance].flags.remove(Ship::Ship_Flags::Arriving_stage_1_dock_follower);
833 }
834 
object_set_arriving_stage2_ndl_flag_helper(object * objp,dock_function_info *)835 void object_set_arriving_stage2_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
836 {
837 	if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
838 		Ships[objp->instance].flags.set(Ship::Ship_Flags::Arriving_stage_2_dock_follower);
839 }
840 
object_remove_arriving_stage2_ndl_flag_helper(object * objp,dock_function_info *)841 void object_remove_arriving_stage2_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ )
842 {
843 	if (! Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader])
844 		Ships[objp->instance].flags.remove(Ship::Ship_Flags::Arriving_stage_2_dock_follower);
845 }
846 
dock_calc_total_moi_helper(object * objp,dock_function_info * infop)847 void dock_calc_total_moi_helper(object* objp, dock_function_info* infop)
848 {
849 	matrix local_moi, unorient, temp, world_moi;
850 	// The MOI for a compound object is simply the sum of the MOI's of the parts, but
851 	// they all have to be with respect to the same point (the center of mass, in this case).
852 	// So for each part:
853 
854 	// We invert the inverse MOI to get an MOI in the local frame
855 	if (!vm_inverse_matrix(&local_moi, &objp->phys_info.I_body_inv)) {
856 		// This is done on purpose to indicate a zero inv_moi
857 		infop->maintained_variables.matrix_value->a1d[0] = NAN;
858 		return;
859 	}
860 
861 	// We calculate the inverse of the orientation matrix (which is also the transpose)
862 	vm_copy_transpose(&unorient, &objp->orient);
863 
864 	// We calculate the world space MOI using (world MOI) = O^-1 * (local MOI) * O
865 	// where O is the orientation matrix (which translates between local space and world space).
866 	// Note that because FS stores orientation matrices transposed, objp->orient is O^-1 in this formula.
867 	vm_matrix_x_matrix(&temp, &objp->orient, &local_moi);
868 	vm_matrix_x_matrix(&world_moi, &temp, &unorient);
869 
870 	// The world space MOI just calculated is about the center of mass of the part,
871 	// so we need to translate it to the center of mass of the whole assembly.
872 	// To do this we add a term corresponding to the MOI of a point mass whose position
873 	// is the position of the part relative to the center of mass
874 	vec3d* center = infop->parameter_variables.vecp_value;
875 	vec3d pos = objp->pos - *center;
876 	physics_add_point_mass_moi(&world_moi, objp->phys_info.mass, &pos);
877 
878 	// Finally we add the translated world space MOI for the part to the accumulated sum
879 	*infop->maintained_variables.matrix_value += world_moi;
880 }
881 
882 // ---------------------------------------------------------------------------------------------------------------
883 // end of über code block ----------------------------------------------------------------------------------------
884 
885 // dock management functions -------------------------------------------------------------------------------------
dock_dock_objects(object * objp1,int dockpoint1,object * objp2,int dockpoint2)886 void dock_dock_objects(object *objp1, int dockpoint1, object *objp2, int dockpoint2)
887 {
888 	Assert(objp1 != NULL);
889 	Assert(objp2 != NULL);
890 
891 #ifndef NDEBUG
892 	if ((dock_find_instance(objp1, objp2) != NULL) || (dock_find_instance(objp2, objp1) != NULL))
893 	{
894 		Error(LOCATION, "Trying to dock an object that's already docked!\n");
895 	}
896 
897 	if ((dock_find_instance(objp1, dockpoint1) != NULL) || (dock_find_instance(objp2, dockpoint2) != NULL))
898 	{
899 		Error(LOCATION, "Trying to dock to a dockpoint that's in use!\n");
900 	}
901 #endif
902 
903 	// put objects on each others' dock lists
904 	dock_add_instance(objp1, dockpoint1, objp2);
905 	dock_add_instance(objp2, dockpoint2, objp1);
906 }
907 
dock_undock_objects(object * objp1,object * objp2)908 void dock_undock_objects(object *objp1, object *objp2)
909 {
910 	Assert(objp1 != NULL);
911 	Assert(objp2 != NULL);
912 
913 	// remove objects from each others' dock lists
914 	dock_remove_instance(objp1, objp2);
915 	dock_remove_instance(objp2, objp1);
916 }
917 
dock_undock_all(object * objp)918 void dock_undock_all(object *objp)
919 {
920 	Assert(objp != NULL);
921 
922 	while (object_is_docked(objp))
923 	{
924 		object* dockee = dock_get_first_docked_object(objp);
925 
926 		dock_undock_objects(objp, dockee);
927 	}
928 }
929 
930 // dock list functions -------------------------------------------------------------------------------------------
dock_check_assume_hub()931 bool dock_check_assume_hub()
932 {
933 	// There are several ways of handling ships docking to other ships.  Level 1, the simplest, is the one-docker, one-dockee
934 	// model used in retail FS2.  Level 2 is the hub model, where we stipulate that any given set of docked ships
935 	// includes one ship to which all other ships are docked.  No ship except for the hub ship can be docked to more than
936 	// one ship.  Level 3 is the daisy-chain model, where you can string ships along and make a rooted tree.
937 	//
938 	// The new code can handle level 3 ship formations, but it requires more overhead than level 2 or level 1.  (Whether
939 	// the additional overhead is significant or not has not been determined.)  In the vast majority of cases, level 3
940 	// is not needed.  So this function is provided to allow the code to optimize itself for level 2, should level 1
941 	// evaluation fail.
942 
943 	// Assume level 2 optimization unless the mission specifies level 3.
944 	return !(The_mission.flags[Mission::Mission_Flags::Allow_dock_trees]);
945 }
946 
dock_get_hub(object * objp)947 object *dock_get_hub(object *objp)
948 {
949 	Assert(dock_check_assume_hub() && object_is_docked(objp));
950 
951 	// if our dock list contains only one object, it must be the hub
952 	if (objp->dock_list->next == NULL)
953 	{
954 		return dock_get_first_docked_object(objp);
955 	}
956 	// otherwise we are the hub
957 	else
958 	{
959 		return objp;
960 	}
961 }
962 
dock_add_instance(object * objp,int dockpoint,object * other_objp)963 void dock_add_instance(object *objp, int dockpoint, object *other_objp)
964 {
965 	dock_instance *item;
966 
967 	// create item
968 	item = (dock_instance *) vm_malloc(sizeof(dock_instance));
969 	item->dockpoint_used = dockpoint;
970 	item->docked_objp = other_objp;
971 
972 	// prepend item to existing list
973 	item->next = objp->dock_list;
974 	objp->dock_list = item;
975 }
976 
dock_remove_instance(object * objp,object * other_objp)977 void dock_remove_instance(object *objp, object *other_objp)
978 {
979 	int found = 0;
980 	dock_instance *prev_ptr, *ptr;
981 
982 	prev_ptr = NULL;
983 	ptr = objp->dock_list;
984 
985 	// iterate until item found
986 	while (ptr != NULL)
987 	{
988 		// if found, exit loop
989 		if (ptr->docked_objp == other_objp)
990 		{
991 			found = 1;
992 			break;
993 		}
994 
995 		// iterate
996 		prev_ptr = ptr;
997 		ptr = ptr->next;
998 	}
999 
1000 	// delete if found
1001 	if (found)
1002 	{
1003 		// special case... found at beginning of list
1004 		if (prev_ptr == NULL)
1005 		{
1006 			objp->dock_list = ptr->next;
1007 		}
1008 		// normal case
1009 		else
1010 		{
1011 			prev_ptr->next = ptr->next;
1012 		}
1013 
1014 		// delete it
1015 		vm_free(ptr);
1016 	}
1017 	else
1018 	{
1019 		// Trigger an assertion, we can recover from this one, thankfully.
1020 		UNREACHABLE("Tried to undock an object that isn't docked!\n");
1021 	}
1022 }
1023 
1024 // just free the list without worrying about undocking anything
dock_free_dock_list(object * objp)1025 void dock_free_dock_list(object *objp)
1026 {
1027 	Assert(objp != NULL);
1028 
1029 	while (objp->dock_list != NULL)
1030 	{
1031 		dock_instance *ptr = objp->dock_list;
1032 		objp->dock_list = ptr->next;
1033 		vm_free(ptr);
1034 	}
1035 }
1036 
dock_find_instance(object * objp,object * other_objp)1037 dock_instance *dock_find_instance(object *objp, object *other_objp)
1038 {
1039 	dock_instance *ptr = objp->dock_list;
1040 
1041 	// iterate until item found
1042 	while (ptr != NULL)
1043 	{
1044 		// if found, return it
1045 		if (ptr->docked_objp == other_objp)
1046 			return ptr;
1047 
1048 		// iterate
1049 		ptr = ptr->next;
1050 	}
1051 
1052 	// not found
1053 	return NULL;
1054 }
1055 
dock_find_instance(object * objp,int dockpoint)1056 dock_instance *dock_find_instance(object *objp, int dockpoint)
1057 {
1058 	dock_instance *ptr = objp->dock_list;
1059 
1060 	// iterate until item found
1061 	while (ptr != NULL)
1062 	{
1063 		// if found, return it
1064 		if (ptr->dockpoint_used == dockpoint)
1065 			return ptr;
1066 
1067 		// iterate
1068 		ptr = ptr->next;
1069 	}
1070 
1071 	// not found
1072 	return NULL;
1073 }
1074 
dock_count_instances(object * objp)1075 int dock_count_instances(object *objp)
1076 {
1077 	int total_count = 0;
1078 
1079 	// count all instances in the list
1080 	dock_instance *ptr = objp->dock_list;
1081 	while (ptr != NULL)
1082 	{
1083 		// incrememnt for this object
1084 		total_count++;
1085 
1086 		// iterate
1087 		ptr = ptr->next;
1088 	}
1089 
1090 	// done
1091 	return total_count;
1092 }
1093