1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 #include <cstring>
13 #include <cctype>
14 #ifdef _WIN32
15 #include <io.h>
16 #include <direct.h>
17 #include <windows.h>
18 #endif
19 
20 #define MODEL_LIB
21 
22 #include "asteroid/asteroid.h"
23 #include "bmpman/bmpman.h"
24 #include "cfile/cfile.h"
25 #include "cmdline/cmdline.h"
26 #include "freespace.h"		// For flFrameTime
27 #include "gamesnd/gamesnd.h"
28 #include "globalincs/linklist.h"
29 #include "io/key.h"
30 #include "io/timer.h"
31 #include "math/fvi.h"
32 #include "math/vecmat.h"
33 #include "model/model.h"
34 #include "model/modelsinc.h"
35 #include "parse/parselo.h"
36 #include "render/3dinternal.h"
37 #include "ship/ship.h"
38 #include "weapon/weapon.h"
39 #include "tracing/tracing.h"
40 
41 #include <algorithm>
42 
43 flag_def_list model_render_flags[] =
44 {
45 	{"no lighting",		MR_NO_LIGHTING,     0},
46 	{"transparent",		MR_ALL_XPARENT,     0},
47 	{"no Zbuffer",		MR_NO_ZBUFFER,      0},
48 	{"no cull",			MR_NO_CULL,         0},
49 	{"no glowmaps",		MR_NO_GLOWMAPS,     0},
50 	{"force clamp",		MR_FORCE_CLAMP,     0},
51 };
52 
53 int model_render_flags_size = sizeof(model_render_flags)/sizeof(flag_def_list);
54 
55 #define MAX_SUBMODEL_COLLISION_ROT_ANGLE (PI / 6.0f)	// max 30 degrees per frame
56 
57 // info for special polygon lists
58 
59 polymodel *Polygon_models[MAX_POLYGON_MODELS];
60 SCP_vector<polymodel_instance*> Polygon_model_instances;
61 
62 SCP_vector<bsp_collision_tree> Bsp_collision_tree_list;
63 
64 static int model_initted = 0;
65 
66 #ifndef NDEBUG
67 CFILE *ss_fp = NULL;			// file pointer used to dump subsystem information
68 char  model_filename[_MAX_PATH];		// temp used to store filename
69 char	debug_name[_MAX_PATH];
70 int ss_warning_shown = 0;		// have we shown the warning dialog concerning the subsystems?
71 #endif
72 
73 static uint Global_checksum = 0;
74 
75 // Anything less than this is considered incompatible.
76 #define PM_COMPATIBLE_VERSION 1900
77 
78 // Anything greater than or equal to PM_COMPATIBLE_VERSION and
79 // whose major version is less than or equal to this is considered
80 // compatible.
81 #define PM_OBJFILE_MAJOR_VERSION 30
82 
83 // 22.01 adds support for external weapon model angle offsets
84 // 22.00 fixes the POF byte alignment and introduces the SLC2 chunk
85 //
86 // 21.18 adds support for external weapon model angle offsets
87 // 21.17 adds support for engine thruster banks linked to specific engine subsystems
88 // FreeSpace 2 shipped at POF version 21.17
89 // Descent: FreeSpace shipped at POF version 20.14
90 // See also https://wiki.hard-light.net/index.php/POF_data_structure
91 #define PM_LATEST_ALIGNED_VERSION	2201
92 #define PM_LATEST_LEGACY_VERSION	2118
93 #define PM_FIRST_ALIGNED_VERSION	2200
94 
95 static int Model_signature = 0;
96 
97 void interp_configure_vertex_buffers(polymodel*, int);
98 void interp_pack_vertex_buffers(polymodel* pm, int mn);
99 void interp_create_detail_index_buffer(polymodel *pm, int detail);
100 void interp_create_transparency_index_buffer(polymodel *pm, int detail_num);
101 void model_interp_process_shield_mesh(polymodel * pm);
102 
103 void model_set_subsys_path_nums(polymodel *pm, int n_subsystems, model_subsystem *subsystems);
104 void model_set_bay_path_nums(polymodel *pm);
105 
106 uint align_bsp_data(ubyte* bsp_in, ubyte* bsp_out, uint bsp_size);
107 uint convert_sldc_to_slc2(ubyte* sldc, ubyte* slc2, uint tree_size);
108 
109 
110 // Goober5000 - see SUBSYSTEM_X in model.h
111 // NOTE: Each subsystem must match up with its #define, or there will be problems
112 const char *Subsystem_types[SUBSYSTEM_MAX] =
113 {
114 	"None",
115 	"Engines",
116 	"Turrets",
117 	"Radar",
118 	"Navigation",
119 	"Communications",
120 	"Weapons",
121 	"Sensors",
122 	"Solar panels",
123 	"Gas collection",
124 	"Activation",
125 	"Unknown"
126 };
127 
128 
129 //WMC - For general compatibility stuff.
130 //Note that the order of the items in this list
131 //determine the order that they are tried in ai_goal_fixup_dockpoints
132 flag_def_list Dock_type_names[] =
133 {
134 	{ "cargo",		DOCK_TYPE_CARGO,	0 },
135 	{ "rearm",		DOCK_TYPE_REARM,	0 },
136 	{ "generic",	DOCK_TYPE_GENERIC,	0 }
137 };
138 
139 int Num_dock_type_names = sizeof(Dock_type_names) / sizeof(flag_def_list);
140 
141 SCP_vector<glow_point_bank_override> glowpoint_bank_overrides;
142 
143 
144 // Goober5000 - reimplementation of Bobboau's $dumb_rotation and $look_at features in a way that works with the rest of the model instance system
145 // note: since these data types are only ever used in this file, they don't need to be in model.h
146 
147 class intrinsic_rotation
148 {
149 public:
150 	bool is_ship;
151 	int model_instance_num;
152 	SCP_vector<int> submodel_list;
153 
intrinsic_rotation(bool _is_ship,int _model_instance_num)154 	intrinsic_rotation(bool _is_ship, int _model_instance_num)
155 		: is_ship(_is_ship), model_instance_num(_model_instance_num)
156 	{}
157 
add_submodel(int _submodel_num,submodel_instance * _submodel_instance_1,float _turn_rate)158 	void add_submodel(int _submodel_num, submodel_instance *_submodel_instance_1, float _turn_rate)
159 	{
160 		submodel_list.push_back(_submodel_num);
161 		_submodel_instance_1->current_turn_rate = _turn_rate;
162 		_submodel_instance_1->desired_turn_rate = _turn_rate;
163 	}
164 };
165 
166 SCP_vector<intrinsic_rotation> Intrinsic_rotations;
167 
168 
169 // Free up a model, getting rid of all its memory
170 // With the basic page in system this can be called from outside of modelread.cpp
model_unload(int modelnum,int force)171 void model_unload(int modelnum, int force)
172 {
173 	int i, j, num;
174 
175 	if ( modelnum >= MAX_POLYGON_MODELS ) {
176 		num = modelnum % MAX_POLYGON_MODELS;
177 	} else {
178 		num = modelnum;
179 	}
180 
181 	if ( (num < 0) || (num >= MAX_POLYGON_MODELS))	{
182 		return;
183 	}
184 
185 	polymodel *pm = Polygon_models[num];
186 
187 	if ( !pm )	{
188 		return;
189 	}
190 
191 	Assert( pm->used_this_mission >= 0 );
192 
193 	if (!force && (--pm->used_this_mission > 0))
194 		return;
195 
196 	mprintf(("Unloading model '%s' from slot '%i'\n", pm->filename, num));
197 
198 	// so that the textures can be released
199 	pm->used_this_mission = 0;
200 
201 	// we want to call bm_release() from here rather than just bm_unload() in order
202 	// to get the slots back so we set "release" to true.
203 	model_page_out_textures(pm->id, true);
204 
205 	safe_kill(pm->ship_bay);
206 
207 	if (pm->paths)	{
208 		for (i=0; i<pm->n_paths; i++ )	{
209 			for (j=0; j<pm->paths[i].nverts; j++ )	{
210 				if ( pm->paths[i].verts[j].turret_ids )	{
211 					vm_free(pm->paths[i].verts[j].turret_ids);
212 				}
213 			}
214 			if (pm->paths[i].verts)	{
215 				vm_free(pm->paths[i].verts);
216 			}
217 		}
218 		vm_free(pm->paths);
219 	}
220 
221 	if ( pm->shield.verts )	{
222 		vm_free( pm->shield.verts );
223 	}
224 
225 	if ( pm->shield.tris )	{
226 		vm_free(pm->shield.tris);
227 	}
228 
229 	if (pm->gun_banks) {	// NOLINT
230 		delete[] pm->gun_banks;
231 	}
232 
233 	if (pm->missile_banks) {	// NOLINT
234 		delete[] pm->missile_banks;
235 	}
236 
237 	if ( pm->docking_bays )	{
238 		for (i=0; i<pm->n_docks; i++ )	{
239 			if ( pm->docking_bays[i].splines )	{
240 				vm_free( pm->docking_bays[i].splines );
241 			}
242 		}
243 		vm_free(pm->docking_bays);
244 	}
245 
246 
247 	if ( pm->thrusters ) {
248 		for (i = 0; i < pm->n_thrusters; i++) {
249 			if (pm->thrusters[i].points)
250 				vm_free(pm->thrusters[i].points);
251 		}
252 
253 		vm_free(pm->thrusters);
254 	}
255 
256 	if ( pm->glow_point_banks )	{ // free the glows!!! -Bobboau
257 		for (i = 0; i < pm->n_glow_point_banks; i++) {
258 			if (pm->glow_point_banks[i].points)
259 				vm_free(pm->glow_point_banks[i].points);
260 		}
261 
262 		vm_free(pm->glow_point_banks);
263 	}
264 
265 #ifndef NDEBUG
266 	if ( pm->debug_info )	{
267 		vm_free(pm->debug_info);
268 	}
269 #endif
270 
271 	model_octant_free( pm );
272 
273 	if (pm->submodel) {
274 		for (i = 0; i < pm->n_models; i++) {
275 			pm->submodel[i].buffer.clear();
276 
277 			if ( pm->submodel[i].bsp_data )	{
278 				vm_free(pm->submodel[i].bsp_data);
279 			}
280 
281 			if ( pm->submodel[i].collision_tree_index >= 0 ) {
282 				model_remove_bsp_collision_tree(pm->submodel[i].collision_tree_index);
283 			}
284 
285 			if ( pm->submodel[i].outline_buffer != nullptr ) {
286 				vm_free(pm->submodel[i].outline_buffer);
287 				pm->submodel[i].outline_buffer = nullptr;
288 			}
289 		}
290 
291 		delete[] pm->submodel;
292 	}
293 
294 	if ( pm->xc ) {
295 		vm_free(pm->xc);
296 	}
297 
298 	if ( pm->lights )	{
299 		vm_free(pm->lights);
300 	}
301 
302 	if ( pm->shield_collision_tree ) {
303 		vm_free(pm->shield_collision_tree);
304 	}
305 
306 	if (pm->shield.buffer_id.isValid()) {
307 		gr_delete_buffer(pm->shield.buffer_id);
308 		pm->shield.buffer_id = gr_buffer_handle::invalid();
309 		pm->shield.buffer_n_verts = 0;
310 	}
311 
312 	if (pm->vert_source.Vbuffer_handle.isValid()) {
313 		gr_heap_deallocate(GpuHeap::ModelVertex, pm->vert_source.Vertex_offset);
314 		pm->vert_source.Vbuffer_handle = gr_buffer_handle::invalid();
315 
316 		pm->vert_source.Vertex_offset = 0;
317 		pm->vert_source.Base_vertex_offset = 0;
318 	}
319 
320 	if ( pm->vert_source.Vertex_list != NULL ) {
321 		vm_free(pm->vert_source.Vertex_list);
322 		pm->vert_source.Vertex_list = NULL;
323 	}
324 
325 	if (pm->vert_source.Ibuffer_handle.isValid()) {
326 		gr_heap_deallocate(GpuHeap::ModelIndex, pm->vert_source.Index_offset);
327 
328 		pm->vert_source.Ibuffer_handle = gr_buffer_handle::invalid();
329 		pm->vert_source.Index_offset = 0;
330 	}
331 
332 	if ( pm->vert_source.Index_list != NULL ) {
333 		vm_free(pm->vert_source.Index_list);
334 		pm->vert_source.Index_list = NULL;
335 	}
336 
337 	pm->vert_source.Vertex_list_size = 0;
338 	pm->vert_source.Index_list_size = 0;
339 
340 	for (i = 0; i < MAX_MODEL_DETAIL_LEVELS; ++i) {
341 		pm->detail_buffers[i].clear();
342 	}
343 
344 	// run through Ship_info and if the model has been loaded we'll need to reset the modelnum to -1.
345 	for (auto &si : Ship_info) {
346 		if ( pm->id == si.model_num ) {
347 			si.model_num = -1;
348 		}
349 
350 		if ( pm->id == si.cockpit_model_num ) {
351 			si.cockpit_model_num = -1;
352 		}
353 
354 		if ( pm->id == si.model_num_hud ) {
355 			si.model_num_hud = -1;
356 		}
357 	}
358 
359 	// need to reset weapon models as well
360 	for (auto &wi: Weapon_info) {
361 		if ( pm->id == wi.model_num ) {
362 			wi.model_num = -1;
363 		}
364 		if ( pm->id == wi.external_model_num ) {
365 			wi.external_model_num = -1;
366 		}
367 	}
368 
369 	pm->id = 0;
370 	delete pm;
371 
372 	Polygon_models[num] = NULL;
373 }
374 
model_free_all()375 void model_free_all()
376 {
377 	int i;
378 
379 	if ( !model_initted)	{
380 		model_init();
381 		return;
382 	}
383 
384 	mprintf(( "Freeing all existing models...\n" ));
385 	model_instance_free_all();
386 
387 	for (i=0;i<MAX_POLYGON_MODELS;i++) {
388 		// forcefully unload all loaded models (be careful with this)
389 		model_unload(i, 1);
390 	}
391 }
392 
model_instance_free_all()393 void model_instance_free_all()
394 {
395 	size_t i;
396 
397 	// free any outstanding model instances
398 	for ( i = 0; i < Polygon_model_instances.size(); ++i ) {
399 		if ( Polygon_model_instances[i] ) {
400 			model_delete_instance((int)i);
401 		}
402 	}
403 
404 	// clear skybox model instance if we have one; it is not an object and therefore has no <object>_delete function which would remove the instance
405 	extern int Nmodel_instance_num;
406 	Nmodel_instance_num = -1;
407 
408 	Polygon_model_instances.clear();
409 }
410 
model_page_in_start()411 void model_page_in_start()
412 {
413 	int i;
414 
415 	if ( !model_initted ) {
416 		model_init();
417 		return;
418 	}
419 
420 	mprintf(( "Starting model page in...\n" ));
421 
422 	for (i=0; i<MAX_POLYGON_MODELS; i++) {
423 		if (Polygon_models[i] != NULL)
424 			Polygon_models[i]->used_this_mission = 0;
425 	}
426 }
427 
model_page_in_stop()428 void model_page_in_stop()
429 {
430 	int i;
431 
432 	Assert( model_initted );
433 
434 	mprintf(( "Stopping model page in...\n" ));
435 
436 	for (i=0; i<MAX_POLYGON_MODELS; i++) {
437 		if (Polygon_models[i] == NULL)
438 			continue;
439 
440 		if (Polygon_models[i]->used_this_mission)
441 			continue;
442 
443 		model_unload(i);
444 	}
445 }
446 
model_init()447 void model_init()
448 {
449 	int i;
450 
451 	if ( model_initted )		{
452 		Int3();		// Model_init shouldn't be called twice!
453 		return;
454 	}
455 
456 	for (i=0;i<MAX_POLYGON_MODELS;i++) {
457 		Polygon_models[i] = NULL;
458 	}
459 
460 	model_initted = 1;
461 }
462 
463 // routine to parse out values from a user property field of an object
get_user_prop_value(char * buf,char * value)464 void get_user_prop_value(char *buf, char *value)
465 {
466 	char *p, *p1, c;
467 
468 	p = buf;
469 	while ( isspace(*p) || (*p == '=') )		// skip white space and equal sign
470 		p++;
471 	p1 = p;
472 	while ( !iscntrl(*p1) )
473 		p1++;
474 	c = *p1;
475 	*p1 = '\0';
476 	strcpy(value, p);
477 	*p1 = c;
478 }
479 
480 // routine to parse out a vec3d from a user property field of an object
get_user_vec3d_value(char * buf,vec3d * value,bool require_brackets,char * submodel_name,char * filename)481 bool get_user_vec3d_value(char *buf, vec3d *value, bool require_brackets, char* submodel_name, char* filename)
482 {
483 	float f1, f2, f3;
484 	char closing_bracket = '\0';
485 	bool success = false;
486 
487 	pause_parse();
488 	Mp = buf;
489 	snprintf(Current_filename, sizeof(Current_filename), "submodel %s on %s", submodel_name, filename);
490 
491 	// Check if there's a missing line break before the next "$".
492 	char end_separator = '\0';
493 	char* end_pos = buf;
494 	while (!iscntrl(*end_pos) && *end_pos != '$')
495 		end_pos++;
496 
497 	// We found a $ before the next line break, remember it and replace it with a line break.
498 	if (*end_pos == '$') {
499 		end_separator = *end_pos;
500 		*end_pos = '\n';
501 	}
502 
503 	// Note that we can't simply return from within this block
504 	// because we always need to call unpause_parse before we
505 	// leave the function.  A one-iteration loop with break
506 	// statements allows the code to jump to the end.  Alternatively,
507 	// goto could have been used.
508 	do {
509 		// skip white space and equal sign or colon
510 		while (isspace(*Mp) || (*Mp == '=') || (*Mp == ':'))
511 			Mp++;
512 
513 		if (require_brackets)
514 		{
515 			// look for vector bracket
516 			if (*Mp == '{')
517 				closing_bracket = '}';
518 			else if (*Mp == '[')
519 				closing_bracket = ']';
520 			else
521 				break;
522 		}
523 
524 		// get comma-separated floats
525 		if (stuff_float(&f1, true) != 2)
526 			break;
527 		if (stuff_float(&f2, true) != 2)
528 			break;
529 		if (stuff_float(&f3, true) != 2)
530 			break;
531 
532 		if (require_brackets)
533 		{
534 			ignore_white_space();
535 
536 			if (*Mp != closing_bracket)
537 				break;
538 		}
539 
540 		value->xyz = { f1, f2, f3 };
541 		success = true;
542 	} while (false);
543 
544 	if (end_separator != '\0') {
545 		// Revert the character replacement we did at the start
546 		*end_pos = end_separator;
547 	}
548 
549 	unpause_parse();
550 	return success;
551 }
552 
553 // routine to look for one of the specified user properties
554 // if p is not null, sets p to the next character AFTER the string and a space/equals/colon (not the beginning of the string, as strstr would)
555 // returns the index of the property found, or -1 if not found
556 // NB: the first recognized option is returned, so if one option is a substring of another, put it later in the list!
prop_string(char * props,char ** p,int n_args,...)557 int prop_string(char *props, char **p, int n_args, ...)
558 {
559 	char *pos = nullptr;
560 	va_list args;
561 	int index = -1;
562 
563 	va_start(args, n_args);
564 
565 	for (int i = 0; i < n_args; ++i)
566 	{
567 		const char *option = va_arg(args, const char *);
568 
569 		// look for our option in the props fields
570 		if ((pos = strstr(props, option)) != nullptr)
571 		{
572 			// we found it
573 			index = i;
574 
575 			// so advance past the string and its following character
576 			pos += strlen(option);
577 			pos++;
578 
579 			break;
580 		}
581 	}
582 
583 	va_end(args);
584 
585 	// if we have a p, assign *p
586 	// (if nothing was found, *p will be nullptr)
587 	if (p != nullptr)
588 		*p = pos;
589 
590 	return index;
591 }
592 
593 // syntactic sugar
prop_string(char * props,char ** p,const char * option0)594 int prop_string(char *props, char **p, const char *option0)
595 {
596 	return prop_string(props, p, 1, option0);
597 }
prop_string(char * props,char ** p,const char * option0,const char * option1)598 int prop_string(char *props, char **p, const char *option0, const char *option1)
599 {
600 	return prop_string(props, p, 2, option0, option1);
601 }
prop_string(char * props,char ** p,const char * option0,const char * option1,const char * option2)602 int prop_string(char *props, char **p, const char *option0, const char *option1, const char *option2)
603 {
604 	return prop_string(props, p, 3, option0, option1, option2);
605 }
606 
607 const Model::Subsystem_Flags carry_flags[] = { Model::Subsystem_Flags::Crewpoint, Model::Subsystem_Flags::Rotates, Model::Subsystem_Flags::Triggered, Model::Subsystem_Flags::Artillery, Model::Subsystem_Flags::Stepped_rotate };
608 // funciton to copy model data from one subsystem set to another subsystem set.  This function
609 // is called when two ships use the same model data, but since the model only gets read in one time,
610 // the subsystem data is only present in one location.  The ship code will call this routine to fix
611 // this situation by copying stuff from the source subsystem set to the dest subsystem set.
model_copy_subsystems(int n_subsystems,model_subsystem * d_sp,model_subsystem * s_sp)612 void model_copy_subsystems( int n_subsystems, model_subsystem *d_sp, model_subsystem *s_sp )
613 {
614 	int i, j;
615 	model_subsystem *source, *dest;
616 
617 	for (i = 0; i < n_subsystems; i++ ) {
618 		source = &s_sp[i];
619 		for ( j = 0; j < n_subsystems; j++ ) {
620 			dest = &d_sp[j];
621 			if ( !subsystem_stricmp( source->subobj_name, dest->subobj_name) ) {
622 				for (auto const &flag : carry_flags) {
623 					if (source->flags[flag])
624 						dest->flags.set(flag);
625 				}
626 
627 				dest->subobj_num = source->subobj_num;
628 				dest->model_num = source->model_num;
629 				dest->pnt = source->pnt;
630 				dest->radius = source->radius;
631 				dest->type = source->type;
632 				dest->turn_rate = source->turn_rate;
633 				dest->turret_gun_sobj = source->turret_gun_sobj;
634 
635                 strcpy_s(dest->name, source->name);
636 
637 				if ( dest->type == SUBSYSTEM_TURRET ) {
638 					int nfp;
639 
640 					dest->turret_fov = source->turret_fov;
641 					dest->turret_num_firing_points = source->turret_num_firing_points;
642 					dest->turret_norm = source->turret_norm;
643 
644 					for (nfp = 0; nfp < dest->turret_num_firing_points; nfp++ )
645 						dest->turret_firing_point[nfp] = source->turret_firing_point[nfp];
646 
647 					if ( dest->flags[Model::Subsystem_Flags::Crewpoint] )
648 						strcpy_s(dest->crewspot, source->crewspot);
649 				}
650 				break;
651 			}
652 		}
653 		if ( j == n_subsystems )
654 			Int3();							// get allender -- something is amiss with models
655 
656 	}
657 }
658 
659 // routine to get/set subsystem information
set_subsystem_info(int model_num,model_subsystem * subsystemp,char * props,char * dname)660 static void set_subsystem_info(int model_num, model_subsystem *subsystemp, char *props, char *dname)
661 {
662 	char *p;
663 	char buf[64];
664 	char	lcdname[256];
665 	int		idx;
666 
667 	if ( (p = strstr(props, "$name")) != NULL)
668 		get_user_prop_value(p+5, subsystemp->name);
669 	else
670 		strcpy_s(subsystemp->name, dname);
671 
672 	strcpy_s(lcdname, dname);
673 	strlwr(lcdname);
674 
675 	// check the name for its specific type
676 	if ( strstr(lcdname, "engine") ) {
677 		subsystemp->type = SUBSYSTEM_ENGINE;
678 	} else if ( strstr(lcdname, "radar") ) {
679 		subsystemp->type = SUBSYSTEM_RADAR;
680 	} else if ( strstr(lcdname, "turret") ) {
681 		float angle;
682 
683 		subsystemp->type = SUBSYSTEM_TURRET;
684 		if ( (p = strstr(props, "$fov")) != NULL )
685 			get_user_prop_value(p+4, buf);			// get the value of the fov
686 		else
687 			strcpy_s(buf,"180");
688 		angle = fl_radians(atoi(buf))/2.0f;
689 		subsystemp->turret_fov = cosf(angle);
690 		subsystemp->turret_num_firing_points = 0;
691 
692 		if ( (p = strstr(props, "$crewspot")) != NULL) {
693             subsystemp->flags.set(Model::Subsystem_Flags::Crewpoint);
694 			get_user_prop_value(p+9, subsystemp->crewspot);
695 		}
696 
697 	} else if ( strstr(lcdname, "navigation") ) {
698 		subsystemp->type = SUBSYSTEM_NAVIGATION;
699 	} else if ( strstr(lcdname, "communication") )  {
700 		subsystemp->type = SUBSYSTEM_COMMUNICATION;
701 	} else if ( strstr(lcdname, "weapon") ) {
702 		subsystemp->type = SUBSYSTEM_WEAPONS;
703 	} else if ( strstr(lcdname, "sensor") ) {
704 		subsystemp->type = SUBSYSTEM_SENSORS;
705 	} else if ( strstr(lcdname, "solar") ) {
706 		subsystemp->type = SUBSYSTEM_SOLAR;
707 	} else if ( strstr(lcdname, "gas") ) {
708 		subsystemp->type = SUBSYSTEM_GAS_COLLECT;
709 	} else if ( strstr(lcdname, "activator") ) {
710 		subsystemp->type = SUBSYSTEM_ACTIVATION;
711 	}  else { // If unrecognized type, set to unknown so artist can continue working...
712 		subsystemp->type = SUBSYSTEM_UNKNOWN;
713 		mprintf(("Subsystem '%s' on ship %s is not recognized as a common subsystem type\n", dname, model_get(model_num)->filename));
714 	}
715 
716 	if ( (strstr(props, "$triggered")) != NULL ) {
717         subsystemp->flags.set(Model::Subsystem_Flags::Rotates);
718         subsystemp->flags.set(Model::Subsystem_Flags::Triggered);
719 	}
720 
721 	// Dumb-Rotating subsystem
722 	if (prop_string(props, nullptr, "$dumb_rotate") >= 0) {
723 		// no special subsystem handling needed here, but make sure we didn't specify both methods
724 		if (prop_string(props, nullptr, "$rotate") >= 0) {
725 			Warning(LOCATION, "Subsystem '%s' on ship %s cannot have both rotation and dumb-rotation!", dname, model_get(model_num)->filename);
726 		}
727 	}
728 	// Look-At subsystem
729 	else if ((p = strstr(props, "$look_at")) != nullptr) {
730 		// no special subsystem handling needed here, but make sure we didn't specify both methods
731 		if (prop_string(props, nullptr, "$rotate") >= 0) {
732 			Warning(LOCATION, "Subsystem '%s' on ship %s cannot have both rotation and look-at!", dname, model_get(model_num)->filename);
733 		}
734 	}
735 	// Rotating subsystem
736 	else if ((idx = prop_string(props, &p, "$rotate_time", "$rotate_rate", "$rotate")) >= 0) {
737         subsystemp->flags.set(Model::Subsystem_Flags::Rotates);
738 
739 		// get value for (a) complete rotation (b) step (c) activation
740 		get_user_prop_value(p, buf);	// note: p points to the value since we used prop_string
741 
742 		// for retail compatibility, $rotate means $rotate_time
743 		float turn_rate;
744 		if (idx == 0 || idx == 2) {
745 			float turn_time = static_cast<float>(atof(buf));
746 			if (turn_time == 0.0f) {
747 				Warning(LOCATION, "Rotation has a turn time of 0 for subsystem '%s' on ship %s!", dname, model_get(model_num)->filename);
748 				turn_rate = 1.0f;
749 			} else {
750 				turn_rate = PI2 / turn_time;
751 			}
752 		} else {
753 			turn_rate = static_cast<float>(atof(buf));
754 		}
755 
756 		// CASE OF WEAPON ROTATION (primary only)
757 		if ( (p = strstr(props, "$pbank")) != NULL)	{
758             subsystemp->flags.set(Model::Subsystem_Flags::Artillery);
759 
760 			// get which pbank should trigger rotation
761 			get_user_prop_value(p+6, buf);
762 			subsystemp->weapon_rotation_pbank = atoi(buf);
763 		} // end of weapon rotation stuff
764 
765 
766 		// *** determine how the subsys rotates ***
767 
768 		// CASE OF STEPPED ROTATION
769 		if ( (strstr(props, "$stepped")) != NULL) {
770 
771 			subsystemp->stepped_rotation = new stepped_rotation;
772             subsystemp->flags.set(Model::Subsystem_Flags::Stepped_rotate);
773 
774 			// get number of steps
775 			if ( (p = strstr(props, "$steps")) != NULL) {
776 				get_user_prop_value(p+6, buf);
777 			   subsystemp->stepped_rotation->num_steps = atoi(buf);
778 			 } else {
779 			    subsystemp->stepped_rotation->num_steps = 8;
780 			 }
781 
782 			// get pause time
783 			if ( (p = strstr(props, "$t_paused")) != NULL) {
784 				get_user_prop_value(p+9, buf);
785 			   subsystemp->stepped_rotation->t_pause = (float)atof(buf);
786 			 } else {
787 			    subsystemp->stepped_rotation->t_pause = 2.0f;
788 			 }
789 
790 			// get transition time - time to go between steps
791 			if ( (p = strstr(props, "$t_transit")) != NULL) {
792 				get_user_prop_value(p+10, buf);
793 			    subsystemp->stepped_rotation->t_transit = (float)atof(buf);
794 			} else {
795 			    subsystemp->stepped_rotation->t_transit = 2.0f;
796 			}
797 
798 			// get fraction of time spent in accel
799 			if ( (p = strstr(props, "$fraction_accel")) != NULL) {
800 				get_user_prop_value(p+15, buf);
801 			    subsystemp->stepped_rotation->fraction = (float)atof(buf);
802 			   Assert(subsystemp->stepped_rotation->fraction > 0 && subsystemp->stepped_rotation->fraction < 0.5);
803 			} else {
804 			    subsystemp->stepped_rotation->fraction = 0.3f;
805 			}
806 
807 			int num_steps = subsystemp->stepped_rotation->num_steps;
808 			float t_trans = subsystemp->stepped_rotation->t_transit;
809 			float fraction = subsystemp->stepped_rotation->fraction;
810 
811 			subsystemp->stepped_rotation->max_turn_accel = PI2 / (fraction*(1.0f - fraction) * num_steps * t_trans*t_trans);
812 			subsystemp->stepped_rotation->max_turn_rate =  PI2 / ((1.0f - fraction) * num_steps *t_trans);
813 		}
814 
815 		// CASE OF NORMAL CONTINUOUS ROTATION
816 		else {
817 			subsystemp->turn_rate = turn_rate;
818 		}
819 	}
820 }
821 
822 // used in collision code to check if submodel rotates too far
get_submodel_delta_angle(submodel_instance * smi)823 float get_submodel_delta_angle(submodel_instance *smi)
824 {
825 	// find the angle
826 	float delta_angle = smi->cur_angle - smi->prev_angle;
827 
828 	// make sure we get the short way around
829 	if (delta_angle > PI) {
830 		delta_angle = (PI2 - delta_angle);
831 	}
832 
833 	return delta_angle;
834 }
835 
do_new_subsystem(int n_subsystems,model_subsystem * slist,int subobj_num,float rad,vec3d * pnt,char * props,char * subobj_name,int model_num)836 void do_new_subsystem( int n_subsystems, model_subsystem *slist, int subobj_num, float rad, vec3d *pnt, char *props, char *subobj_name, int model_num )
837 {
838 	int i;
839 	model_subsystem *subsystemp;
840 
841 	if ( slist==NULL ) {
842 #ifndef NDEBUG
843 		if (!ss_warning_shown) {
844 			mprintf(("No subsystems found for model \"%s\".\n", model_get(model_num)->filename));
845 			ss_warning_shown = 1;
846 		}
847 #endif
848 		return;			// For TestCode, POFView, etc don't bother
849 	}
850 
851 	// try to find the name of the subsystem passed here on the list of subsystems currently on the
852 	// ship.  Assign the values only when the right subsystem is found
853 
854 	for (i = 0; i < n_subsystems; i++ ) {
855 		subsystemp = &slist[i];
856 
857 #ifndef NDEBUG
858 		// Goober5000 - notify if there's a mismatch
859 		if ( stricmp(subobj_name, subsystemp->subobj_name) != 0 && !subsystem_stricmp(subobj_name, subsystemp->subobj_name) )
860 		{
861 			nprintf(("Model", "NOTE: Subsystem \"%s\" in model \"%s\" is represented as \"%s\" in ships.tbl.  This works fine in FSO v3.6 and up, "
862 				"but is not compatible with FS2 retail.\n", subobj_name, model_get(model_num)->filename, subsystemp->subobj_name));
863 
864 		}
865 #endif
866 
867 		if (!subsystem_stricmp(subobj_name, subsystemp->subobj_name))
868 		{
869 			//commented by Goober5000 because this is also set when the table is parsed
870 			//subsystemp->flags = 0;
871 
872 			subsystemp->subobj_num = subobj_num;
873 			subsystemp->turret_gun_sobj = -1;
874 			subsystemp->model_num = model_num;
875 			subsystemp->pnt = *pnt;				// use the offset to get the center point of the subsystem
876 			subsystemp->radius = rad;
877 			set_subsystem_info(model_num, subsystemp, props, subobj_name);
878 			strcpy_s(subsystemp->subobj_name, subobj_name);						// copy the object name
879 			return;
880 		}
881 	}
882 #ifndef NDEBUG
883 	char bname[_MAX_FNAME];
884 
885 	if ( !ss_warning_shown) {
886 		_splitpath(model_filename, NULL, NULL, bname, NULL);
887 		// Lets still give a comment about it and not just erase it
888 		Warning(LOCATION,"Not all subsystems in model \"%s\" have a record in ships.tbl.\nThis can cause game to crash.\n\nList of subsystems not found from table is in log file.\n", model_get(model_num)->filename );
889 		mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename));
890 		ss_warning_shown = 1;
891 	} else
892 #endif
893 		mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename));
894 
895 #ifndef NDEBUG
896 	if ( ss_fp )	{
897 		_splitpath(model_filename, NULL, NULL, bname, NULL);
898 		mprintf(("A subsystem was found in model %s that does not have a record in ships.tbl.\nA list of subsystems for this ship will be dumped to:\n\ndata%stables%s%s.subsystems for inclusion\ninto ships.tbl.\n", model_filename, DIR_SEPARATOR_STR, DIR_SEPARATOR_STR, bname));
899 		char tmp_buffer[128];
900 		sprintf(tmp_buffer, "$Subsystem:\t\t\t%s,1,0.0\n", subobj_name);
901 		cfputs(tmp_buffer, ss_fp);
902 	}
903 #endif
904 
905 }
906 
print_family_tree(polymodel * obj,int modelnum,const char * ident,int islast)907 void print_family_tree( polymodel *obj, int modelnum, const char * ident, int islast )
908 {
909 	char temp[50];
910 
911 	if ( modelnum < 0 ) return;
912 	if (obj==NULL) return;
913 
914 	if (ident[0] == '\0')	{
915 		mprintf(( " %s", obj->submodel[modelnum].name ));
916 		sprintf( temp, " " );
917 	} else if ( islast ) 	{
918 		mprintf(( "%s:%s", ident, obj->submodel[modelnum].name ));
919 		sprintf( temp, "%s  ", ident );
920 	} else {
921 		mprintf(( "%s:%s", ident, obj->submodel[modelnum].name ));
922 		sprintf( temp, "%s ", ident );
923 	}
924 
925 	mprintf(( "\n" ));
926 
927 	int child = obj->submodel[modelnum].first_child;
928 	while( child > -1 )	{
929 		if ( obj->submodel[child].next_sibling < 0 )
930 			print_family_tree( obj, child, temp,1 );
931 		else
932 			print_family_tree( obj, child, temp,0 );
933 		child = obj->submodel[child].next_sibling;
934 	}
935 }
936 
dump_object_tree(polymodel * obj)937 void dump_object_tree(polymodel *obj)
938 {
939 	print_family_tree( obj, 0, "", 0 );
940 	key_getch();
941 }
942 
create_family_tree(polymodel * obj)943 void create_family_tree(polymodel *obj)
944 {
945 	int i;
946 	for (i=0; i<obj->n_models; i++ )	{
947 		obj->submodel[i].num_children = 0;
948 		obj->submodel[i].first_child = -1;
949 		obj->submodel[i].next_sibling = -1;
950 	}
951 
952 	for (i=0; i<obj->n_models; i++ )	{
953 		int pn;
954 		pn = obj->submodel[i].parent;
955 		if ( pn > -1 )	{
956 			obj->submodel[pn].num_children++;
957 			int tmp = obj->submodel[pn].first_child;
958 			obj->submodel[pn].first_child = i;
959 			obj->submodel[i].next_sibling = tmp;
960 		}
961 	}
962 }
963 
create_vertex_buffer(polymodel * pm)964 void create_vertex_buffer(polymodel *pm)
965 {
966 	if (Is_standalone) {
967 		return;
968 	}
969 
970 	TRACE_SCOPE(tracing::ModelCreateVertexBuffers);
971 
972 	int i;
973 
974 	// determine the size and configuration of each buffer segment
975 	for (i = 0; i < pm->n_models; i++) {
976 		interp_configure_vertex_buffers(pm, i);
977 	}
978 
979 	// figure out which vertices are transparent
980 	for ( i = 0; i < pm->n_models; i++ ) {
981 		if ( !pm->submodel[i].is_thruster ) {
982 			interp_create_transparency_index_buffer(pm, i);
983 		}
984 	}
985 	clear_bm_lookup_cache();
986 
987 	size_t stride = 0;
988 	// Determine the global stride of this model (should be the same for every submodel)
989 	for ( i = 0; i < pm->n_models; ++i ) {
990 		if (pm->submodel[i].buffer.model_list != nullptr && pm->submodel[i].buffer.stride != stride) {
991 			Assertion(stride == 0, "Submodel %d of model %s has a stride of "
992 				SIZE_T_ARG
993 				" while the rest of the model has a vertex stride of "
994 				SIZE_T_ARG
995 				"!", i, pm->filename, pm->submodel[i].buffer.stride, stride);
996 
997 			stride = pm->submodel[i].buffer.stride;
998 		}
999 	}
1000 
1001 	// create another set of indexes for the detail buffers
1002 	for ( i = 0; i < pm->n_detail_levels; i++ )	{
1003 		interp_create_detail_index_buffer(pm, i);
1004 	}
1005 
1006 	// now actually fill the buffer with our info ...
1007 	for (i = 0; i < pm->n_models; i++) {
1008 		interp_pack_vertex_buffers(pm, i);
1009 
1010 		// release temporary memory
1011 		pm->submodel[i].buffer.release();
1012 		pm->submodel[i].trans_buffer.release();
1013 	}
1014 
1015 	// pack the merged index buffers to the vbo.
1016 	for ( i = 0; i < pm->n_detail_levels; ++i ) {
1017 		if ( pm->detail_buffers[i].model_list == NULL ) {
1018 			continue;
1019 		}
1020 
1021 		model_interp_pack_buffer(&pm->vert_source, &pm->detail_buffers[i]);
1022 		pm->detail_buffers[i].release();
1023 	}
1024 
1025 	pm->flags |= PM_FLAG_BATCHED;
1026 
1027 	// ... and then finalize buffer
1028 	model_interp_submit_buffers(&pm->vert_source, stride);
1029 
1030 	model_interp_process_shield_mesh(pm);
1031 }
1032 
1033 // Goober5000
maybe_swap_mins_maxs(vec3d * mins,vec3d * maxs)1034 bool maybe_swap_mins_maxs(vec3d *mins, vec3d *maxs)
1035 {
1036 	float temp;
1037 	bool swap_was_necessary = false;
1038 
1039 	if (mins->xyz.x > maxs->xyz.x)
1040 	{
1041 		temp = mins->xyz.x;
1042 		mins->xyz.x = maxs->xyz.x;
1043 		maxs->xyz.x = temp;
1044 		swap_was_necessary = true;
1045 	}
1046 	if (mins->xyz.y > maxs->xyz.y)
1047 	{
1048 		temp = mins->xyz.y;
1049 		mins->xyz.y = maxs->xyz.y;
1050 		maxs->xyz.y = temp;
1051 		swap_was_necessary = true;
1052 	}
1053 	if (mins->xyz.z > maxs->xyz.z)
1054 	{
1055 		temp = mins->xyz.z;
1056 		mins->xyz.z = maxs->xyz.z;
1057 		maxs->xyz.z = temp;
1058 		swap_was_necessary = true;
1059 	}
1060 
1061 // This is a mini utility that prints out the proper hex string for the
1062 // mins and maxs so that the POF file can be modified in a hex editor.
1063 // Currently none of the major POF editors allow editing of bounding boxes.
1064 #if 0
1065 	if (swap_was_necessary)
1066 	{
1067 		// use C hackery to convert float values to raw bytes
1068 		const int NUM_BYTES = 24;
1069 		typedef struct converter
1070 		{
1071 			union
1072 			{
1073 				struct
1074 				{
1075 					float min_x, min_y, min_z, max_x, max_y, max_z;
1076 				} _float;
1077 				ubyte _byte[NUM_BYTES];
1078 			};
1079 		} converter;
1080 
1081 		// fill in the values
1082 		converter z;
1083 		z._float.min_x = mins->xyz.x;
1084 		z._float.min_y = mins->xyz.y;
1085 		z._float.min_z = mins->xyz.z;
1086 		z._float.max_x = maxs->xyz.x;
1087 		z._float.max_y = maxs->xyz.y;
1088 		z._float.max_z = maxs->xyz.z;
1089 
1090 		// prep string
1091 		char hex_str[5];
1092 		char text[100 + (5 * NUM_BYTES)];
1093 		strcpy_s(text, "The following is the correct hex string for the minima and maxima:\n");
1094 
1095 		// append hex values to the string
1096 		for (int i = 0; i < NUM_BYTES; i++)
1097 		{
1098 			sprintf(hex_str, "%02X ", z._byte[i]);
1099 			strcat_s(text, hex_str);
1100 		}
1101 
1102 		// notify the user
1103 		Warning(LOCATION, text);
1104 	}
1105 #endif
1106 
1107 	return swap_was_necessary;
1108 }
1109 
model_calc_bound_box(vec3d * box,vec3d * big_mn,vec3d * big_mx)1110 void model_calc_bound_box( vec3d *box, vec3d *big_mn, vec3d *big_mx)
1111 {
1112 	box[0].xyz.x = big_mn->xyz.x; box[0].xyz.y = big_mn->xyz.y; box[0].xyz.z = big_mn->xyz.z;
1113 	box[1].xyz.x = big_mx->xyz.x; box[1].xyz.y = big_mn->xyz.y; box[1].xyz.z = big_mn->xyz.z;
1114 	box[2].xyz.x = big_mx->xyz.x; box[2].xyz.y = big_mx->xyz.y; box[2].xyz.z = big_mn->xyz.z;
1115 	box[3].xyz.x = big_mn->xyz.x; box[3].xyz.y = big_mx->xyz.y; box[3].xyz.z = big_mn->xyz.z;
1116 
1117 
1118 	box[4].xyz.x = big_mn->xyz.x; box[4].xyz.y = big_mn->xyz.y; box[4].xyz.z = big_mx->xyz.z;
1119 	box[5].xyz.x = big_mx->xyz.x; box[5].xyz.y = big_mn->xyz.y; box[5].xyz.z = big_mx->xyz.z;
1120 	box[6].xyz.x = big_mx->xyz.x; box[6].xyz.y = big_mx->xyz.y; box[6].xyz.z = big_mx->xyz.z;
1121 	box[7].xyz.x = big_mn->xyz.x; box[7].xyz.y = big_mx->xyz.y; box[7].xyz.z = big_mx->xyz.z;
1122 }
1123 
1124 
model_maybe_adjust_movement_axis(bsp_info * sm)1125 void model_maybe_adjust_movement_axis(bsp_info *sm)
1126 {
1127 	// if we have a FOR, we need to transform the movement axis and make it a non-standard one
1128 	if (!vm_matrix_equal(sm->frame_of_reference, vmd_identity_matrix) && (sm->movement_type != MOVEMENT_TYPE_NONE) && (sm->movement_axis_id != MOVEMENT_AXIS_NONE)) {
1129 		vec3d new_axis;
1130 		vm_vec_unrotate(&new_axis, &sm->movement_axis, &sm->frame_of_reference);
1131 		sm->movement_axis = new_axis;
1132 		sm->movement_axis_id = MOVEMENT_AXIS_OTHER;
1133 	}
1134 }
1135 
1136 
1137 //reads a binary file containing a 3d model
read_model_file(polymodel * pm,const char * filename,int n_subsystems,model_subsystem * subsystems,int ferror)1138 int read_model_file(polymodel * pm, const char *filename, int n_subsystems, model_subsystem *subsystems, int ferror)
1139 {
1140 	CFILE *fp;
1141 	int version;
1142 	int id, len, next_chunk;
1143 	int i,j;
1144 	vec3d temp_vec;
1145 
1146 	fp = cfopen(filename,"rb");
1147 
1148 	if (!fp) {
1149 		if (ferror == 1) {
1150 			Error( LOCATION, "Can't open model file <%s>", filename );
1151 		} else if (ferror == 0) {
1152 			Warning( LOCATION, "Can't open model file <%s>", filename );
1153 		}
1154 
1155 		return -1;
1156 	}
1157 
1158 	TRACE_SCOPE(tracing::ReadModelFile);
1159 
1160 	// generate checksum for the POF
1161 	cfseek(fp, 0, SEEK_SET);
1162 	cf_chksum_long(fp, &Global_checksum);
1163 	cfseek(fp, 0, SEEK_SET);
1164 
1165 
1166 	// code to get a filename to write out subsystem information for each model that
1167 	// is read.  This info is essentially debug stuff that is used to help get models
1168 	// into the game quicker
1169 #if 0
1170 	{
1171 		char bname[_MAX_FNAME];
1172 
1173 		_splitpath(filename, NULL, NULL, bname, NULL);
1174 		sprintf(debug_name, "%s.subsystems", bname);
1175 		ss_fp = cfopen(debug_name, "wb", CFILE_NORMAL, CF_TYPE_TABLES );
1176 		if ( !ss_fp )	{
1177 			mprintf(( "Can't open debug file for writing subsystems for %s\n", filename));
1178 		} else {
1179 			strcpy_s(model_filename, filename);
1180 			ss_warning_shown = 0;
1181 		}
1182 	}
1183 #endif
1184 
1185 	id = cfread_int(fp);
1186 
1187 	if (id != POF_HEADER_ID)
1188 		Error( LOCATION, "Bad ID in model file <%s>",filename);
1189 
1190 	// Version is major*100+minor
1191 	// So, major = version / 100;
1192 	//     minor = version % 100;
1193 	version = cfread_int(fp);
1194 
1195 	//Warning( LOCATION, "POF Version = %d", version );
1196 
1197 	if (version < PM_COMPATIBLE_VERSION || (version/100) > PM_OBJFILE_MAJOR_VERSION)	{
1198 		Warning(LOCATION,"Bad version (%d) in model file <%s>",version,filename);
1199 		return 0;
1200 	}
1201 	if ((version > PM_LATEST_LEGACY_VERSION && version < PM_FIRST_ALIGNED_VERSION) || (version > PM_LATEST_ALIGNED_VERSION)) {
1202 		Warning(LOCATION, "Model file %s is version %d, but the latest supported version on this build of FSO is %d.  The model may not work correctly.", filename, version, version >= PM_FIRST_ALIGNED_VERSION ? PM_LATEST_ALIGNED_VERSION : PM_LATEST_LEGACY_VERSION);
1203 	}
1204 
1205 	pm->version = version;
1206 	Assert( strlen(filename) < FILESPEC_LENGTH );
1207 	strcpy_s(pm->filename, filename);
1208 
1209 	memset( &pm->view_positions, 0, sizeof(pm->view_positions) );
1210 
1211 	// reset insignia counts
1212 	pm->num_ins = 0;
1213 
1214 	// reset glow points!! - Goober5000
1215 	pm->n_glow_point_banks = 0;
1216 
1217 	// reset SLDC
1218 	pm->shield_collision_tree = NULL;
1219 	pm->sldc_size = 0;
1220 
1221 	id = cfread_int(fp);
1222 	len = cfread_int(fp);
1223 	next_chunk = cftell(fp) + len;
1224 
1225 	// keep track of any look_at submodels we might notice
1226 	SCP_vector<SCP_string> look_at_submodel_names;
1227 
1228 	while (!cfeof(fp)) {
1229 
1230 //		mprintf(("Processing chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len));
1231 //		key_getch();
1232 
1233 		switch (id) {
1234 
1235 			case ID_OHDR: {		//Object header
1236 				//vector v;
1237 
1238 				//mprintf(0,"Got chunk OHDR, len=%d\n",len);
1239 
1240 #if defined( FREESPACE1_FORMAT )
1241 				pm->n_models = cfread_int(fp);
1242 //				mprintf(( "Num models = %d\n", pm->n_models ));
1243 				pm->rad = cfread_float(fp);
1244 				pm->flags = cfread_int(fp);	// 1=Allow tiling
1245 #elif defined( FREESPACE2_FORMAT )
1246 				pm->rad = cfread_float(fp);
1247 				pm->flags = cfread_int(fp);	// 1=Allow tiling
1248 				pm->n_models = cfread_int(fp);
1249 //				mprintf(( "Num models = %d\n", pm->n_models ));
1250 #endif
1251                 Assertion(pm->n_models >= 1, "Models without any submodels are not supported!");
1252 
1253 				// Check for unrealistic radii
1254 				if ( pm->rad <= 0.1f )
1255 				{
1256 					Warning(LOCATION, "Model <%s> has a radius <= 0.1f\n", filename);
1257 				}
1258 
1259 				pm->submodel = new bsp_info[pm->n_models];
1260 
1261 				//Assert(pm->n_models <= MAX_SUBMODELS);
1262 
1263 				cfread_vector(&pm->mins,fp);
1264 				cfread_vector(&pm->maxs,fp);
1265 
1266 				// sanity first!
1267 				if (maybe_swap_mins_maxs(&pm->mins, &pm->maxs)) {
1268 					Warning(LOCATION, "Inverted bounding box on model '%s'!  Swapping values to compensate.", pm->filename);
1269 				}
1270 				model_calc_bound_box(pm->bounding_box, &pm->mins, &pm->maxs);
1271 
1272 				pm->n_detail_levels = cfread_int(fp);
1273 			//	mprintf(( "There are %d detail levels\n", pm->n_detail_levels ));
1274 				for (i=0; i<pm->n_detail_levels;i++ )	{
1275 					pm->detail[i] = cfread_int(fp);
1276 					pm->detail_depth[i] = 0.0f;
1277 			///		mprintf(( "Detail level %d is model %d.\n", i, pm->detail[i] ));
1278 				}
1279 
1280 				pm->num_debris_objects = cfread_int(fp);
1281 			    if (pm->num_debris_objects > MAX_DEBRIS_OBJECTS) {
1282 				    Error(LOCATION,
1283 				          "Model %s specified that it contains %d debris objects but only %d are supported by the "
1284 				          "engine.",
1285 				          filename, pm->num_debris_objects, MAX_DEBRIS_OBJECTS);
1286 			    }
1287 				// mprintf(( "There are %d debris objects\n", pm->num_debris_objects ));
1288 				for (i=0; i<pm->num_debris_objects;i++ )	{
1289 					pm->debris_objects[i] = cfread_int(fp);
1290 					// mprintf(( "Debris object %d is model %d.\n", i, pm->debris_objects[i] ));
1291 				}
1292 
1293 				if ( pm->version >= 1903 )	{
1294 
1295 					if ( pm->version >= 2009 )	{
1296 
1297 						pm->mass = cfread_float(fp);
1298 						cfread_vector( &pm->center_of_mass, fp );
1299 						cfread_vector( &pm->moment_of_inertia.vec.rvec, fp );
1300 						cfread_vector( &pm->moment_of_inertia.vec.uvec, fp );
1301 						cfread_vector( &pm->moment_of_inertia.vec.fvec, fp );
1302 
1303 						if(!is_valid_vec(&pm->moment_of_inertia.vec.rvec) || !is_valid_vec(&pm->moment_of_inertia.vec.uvec) || !is_valid_vec(&pm->moment_of_inertia.vec.fvec)) {
1304 							Warning(LOCATION, "Moment of inertia values for model %s are invalid. This has to be fixed.\n", pm->filename);
1305 							Int3();
1306 						}
1307 					} else {
1308 						// old code where mass wasn't based on area, so do the calculation manually
1309 
1310 						float vol_mass = cfread_float(fp);
1311 						//	Attn: John Slagel:  The following is better done in bspgen.
1312 						// Convert volume (cubic) to surface area (quadratic) and scale so 100 -> 100
1313 						float area_mass = (float) pow(vol_mass, 0.6667f) * 4.65f;
1314 
1315 						pm->mass = area_mass;
1316 						float mass_ratio = vol_mass / area_mass;
1317 
1318 						cfread_vector( &pm->center_of_mass, fp );
1319 						cfread_vector( &pm->moment_of_inertia.vec.rvec, fp );
1320 						cfread_vector( &pm->moment_of_inertia.vec.uvec, fp );
1321 						cfread_vector( &pm->moment_of_inertia.vec.fvec, fp );
1322 
1323 						if(!is_valid_vec(&pm->moment_of_inertia.vec.rvec) || !is_valid_vec(&pm->moment_of_inertia.vec.uvec) || !is_valid_vec(&pm->moment_of_inertia.vec.fvec)) {
1324 							Warning(LOCATION, "Moment of inertia values for model %s are invalid. This has to be fixed.\n", pm->filename);
1325 							Int3();
1326 						}
1327 
1328 						// John remove this with change to bspgen
1329 						vm_vec_scale( &pm->moment_of_inertia.vec.rvec, mass_ratio );
1330 						vm_vec_scale( &pm->moment_of_inertia.vec.uvec, mass_ratio );
1331 						vm_vec_scale( &pm->moment_of_inertia.vec.fvec, mass_ratio );
1332 					}
1333 
1334 					// a custom MOI is only used for ships, but we should probably log it anyway
1335 					if ( IS_VEC_NULL(&pm->moment_of_inertia.vec.rvec)
1336 						&& IS_VEC_NULL(&pm->moment_of_inertia.vec.uvec)
1337 						&& IS_VEC_NULL(&pm->moment_of_inertia.vec.fvec) )
1338 					{
1339 						mprintf(("Model %s has a null moment of inertia!  (This is only a problem if the model is a ship.)\n", filename));
1340 					}
1341 
1342 				} else {
1343 					pm->mass = 50.0f;
1344 					vm_vec_zero( &pm->center_of_mass );
1345 					vm_set_identity( &pm->moment_of_inertia );
1346 					vm_vec_scale(&pm->moment_of_inertia.vec.rvec, 0.001f);
1347 					vm_vec_scale(&pm->moment_of_inertia.vec.uvec, 0.001f);
1348 					vm_vec_scale(&pm->moment_of_inertia.vec.fvec, 0.001f);
1349 				}
1350 
1351 				// read in cross section info
1352 				pm->xc = NULL;
1353 				if ( pm->version >= 2014 ) {
1354 					pm->num_xc = cfread_int(fp);
1355 					if (pm->num_xc > 0) {
1356 						pm->xc = (cross_section*) vm_malloc(pm->num_xc*sizeof(cross_section));
1357 						for (i=0; i<pm->num_xc; i++) {
1358 							pm->xc[i].z = cfread_float(fp);
1359 							pm->xc[i].radius = cfread_float(fp);
1360 						}
1361 					}
1362 				} else {
1363 					pm->num_xc = 0;
1364 				}
1365 
1366 				if ( pm->version >= 2007 )	{
1367 					pm->num_lights = cfread_int(fp);
1368 					//mprintf(( "Found %d lights!\n", pm->num_lights ));
1369 
1370 					if (pm->num_lights > 0) {
1371 						pm->lights = (bsp_light *)vm_malloc( sizeof(bsp_light)*pm->num_lights );
1372 						for (i=0; i<pm->num_lights; i++ )	{
1373 							cfread_vector(&pm->lights[i].pos,fp);
1374 							pm->lights[i].type = cfread_int(fp);
1375 						}
1376 					}
1377 				} else {
1378 					pm->num_lights = 0;
1379 					pm->lights = NULL;
1380 				}
1381 
1382 				break;
1383 			}
1384 
1385 			case ID_SOBJ: {		//Subobject header
1386 				int n, parent;
1387 				char *p, props[MAX_PROP_LEN];
1388 //				float d;
1389 
1390 				//mprintf(0,"Got chunk SOBJ, len=%d\n",len);
1391 
1392 				n = cfread_int(fp);
1393 				//mprintf(("SOBJ IDed itself as %d\n", n));
1394 
1395 				Assert(n < pm->n_models );
1396 
1397 #if defined( FREESPACE2_FORMAT )
1398 				pm->submodel[n].rad = cfread_float(fp);		//radius
1399 #endif
1400 
1401 				parent = cfread_int(fp);
1402 				pm->submodel[n].parent = parent;
1403 
1404 //				cfread_vector(&pm->submodel[n].norm,fp);
1405 //				d = cfread_float(fp);
1406 //				cfread_vector(&pm->submodel[n].pnt,fp);
1407 				cfread_vector(&pm->submodel[n].offset,fp);
1408 
1409 //			mprintf(( "Subobj %d, offs = %.1f, %.1f, %.1f\n", n, pm->submodel[n].offset.xyz.x, pm->submodel[n].offset.xyz.y, pm->submodel[n].offset.xyz.z ));
1410 
1411 #if defined ( FREESPACE1_FORMAT )
1412 				pm->submodel[n].rad = cfread_float(fp);		//radius
1413 #endif
1414 
1415 //				pm->submodel[n].tree_offset = cfread_int(fp);	//offset
1416 //				pm->submodel[n].data_offset = cfread_int(fp);	//offset
1417 
1418 				cfread_vector(&pm->submodel[n].geometric_center,fp);
1419 
1420 				cfread_vector(&pm->submodel[n].min,fp);
1421 				cfread_vector(&pm->submodel[n].max,fp);
1422 
1423 				pm->submodel[n].name[0] = '\0';
1424 
1425 				cfread_string_len(pm->submodel[n].name, MAX_NAME_LEN, fp);		// get the name
1426 				cfread_string_len(props, MAX_PROP_LEN, fp);			// and the user properties
1427 
1428 				// Check for unrealistic radii
1429 				if ( pm->submodel[n].rad <= 0.1f )
1430 				{
1431 					Warning(LOCATION, "Submodel <%s> in model <%s> has a radius <= 0.1f\n", pm->submodel[n].name, filename);
1432 				}
1433 
1434 				// sanity first!
1435 				if (maybe_swap_mins_maxs(&pm->submodel[n].min, &pm->submodel[n].max)) {
1436 					Warning(LOCATION, "Inverted bounding box on submodel '%s' of model '%s'!  Swapping values to compensate.", pm->submodel[n].name, pm->filename);
1437 				}
1438 				model_calc_bound_box(pm->submodel[n].bounding_box, &pm->submodel[n].min, &pm->submodel[n].max);
1439 
1440 				// ---------- submodel movement ----------
1441 
1442 				pm->submodel[n].movement_type = cfread_int(fp);
1443 				pm->submodel[n].movement_axis_id = cfread_int(fp);
1444 
1445 				// change turret movement type to MOVEMENT_TYPE_ROT_SPECIAL
1446 				if ( stristr(pm->submodel[n].name, "turret") || ((parent >= 0) && (pm->submodel[parent].movement_type == MOVEMENT_TYPE_ROT_SPECIAL)) ) {
1447 					pm->submodel[n].movement_type = MOVEMENT_TYPE_ROT_SPECIAL;
1448 				} else if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) {
1449 					if (stristr(pm->submodel[n].name, "thruster")) {
1450 						pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
1451 					} else if(strstr(props, "$triggered")) {
1452 						pm->submodel[n].movement_type = MOVEMENT_TYPE_TRIGGERED;
1453 					}
1454 				}
1455 
1456 				// determine rotation axis
1457 				// (the axis is a vector from 0,0,0 to the point specified)
1458 				// note: the standard axis point definitions are copied from Volition code originally in model_init_submodel_axis_pt
1459 				if (pm->submodel[n].movement_axis_id == MOVEMENT_AXIS_X) {
1460 					pm->submodel[n].movement_axis = vmd_x_vector;
1461 				}
1462 				else if (pm->submodel[n].movement_axis_id == MOVEMENT_AXIS_Y) {
1463 					pm->submodel[n].movement_axis = vmd_y_vector;
1464 				}
1465 				else if (pm->submodel[n].movement_axis_id == MOVEMENT_AXIS_Z) {
1466 					pm->submodel[n].movement_axis = vmd_z_vector;
1467 				}
1468 				else if (pm->submodel[n].movement_axis_id == MOVEMENT_AXIS_OTHER) {
1469 					if ((p = strstr(props, "$rotation_axis")) != nullptr) {
1470 						if (get_user_vec3d_value(p + 20, &pm->submodel[n].movement_axis, true, pm->submodel[n].name, pm->filename)) {
1471 							vm_vec_normalize(&pm->submodel[n].movement_axis);
1472 						} else {
1473 							Warning(LOCATION, "Failed to parse $rotation_axis on subsystem '%s' on ship %s!", pm->submodel[n].name, pm->filename);
1474 							pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
1475 						}
1476 					} else {
1477 						Warning(LOCATION, "A $rotation_axis was not specified for subsystem '%s' on ship %s!", pm->submodel[n].name, pm->filename);
1478 						pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
1479 					}
1480 				}
1481 
1482 				// note, this should come BEFORE do_new_subsystem() for proper error handling (to avoid both rotating and look-at submodel)
1483 				if ((p = strstr(props, "$look_at")) != nullptr) {
1484 					pm->submodel[n].movement_type = MOVEMENT_TYPE_INTRINSIC_ROTATE;
1485 
1486 					// we need to work out the correct subobject number later, after all subobjects have been processed
1487 					pm->submodel[n].look_at_submodel = static_cast<int>(look_at_submodel_names.size());
1488 
1489 					char submodel_name[MAX_NAME_LEN];
1490 					get_user_prop_value(p + 9, submodel_name);
1491 					look_at_submodel_names.push_back(submodel_name);
1492 				} else {
1493 					pm->submodel[n].look_at_submodel = -1; // No look_at
1494 				}
1495 
1496 				// optional extra property for look_at
1497 				if ((p = strstr(props, "$look_at_offset")) != nullptr) {
1498 					auto offset = (float)atof(p + 16);
1499 
1500 					// model property is specified in degrees, so convert it
1501 					offset = fl_radians(offset);
1502 
1503 					// check range (the angle is now in radians)
1504 					if (offset < -PI2 || offset > PI2) {
1505 						Warning(LOCATION, "Submodel '%s' of model '%s' has a look_at_offset that is outside the range of -360 to 360!", pm->submodel[n].name, pm->filename);
1506 						offset = -1.0f;
1507 					}
1508 					// make the angle positive, since negative angles will be set at first look_at call
1509 					else if (offset < 0.0f) {
1510 						offset += PI2;
1511 					}
1512 
1513 					pm->submodel[n].look_at_offset = offset;
1514 				} else {
1515 					pm->submodel[n].look_at_offset = -1.0f;
1516 				}
1517 
1518 				// note, this should come BEFORE do_new_subsystem() for proper error handling (to avoid both rotating and dumb-rotating submodel)
1519 				int idx = prop_string(props, &p, "$dumb_rotate_time", "$dumb_rotate_rate", "$dumb_rotate");
1520 				if (idx >= 0) {
1521 					pm->submodel[n].movement_type = MOVEMENT_TYPE_INTRINSIC_ROTATE;
1522 
1523 					// do this the same way as regular $rotate
1524 					char buf[64];
1525 					get_user_prop_value(p, buf);
1526 
1527 					// for past SCP compatibility, $dumb_rotate means $dumb_rotate_rate
1528 					float turn_rate;
1529 					if (idx == 0) {
1530 						float turn_time = static_cast<float>(atof(buf));
1531 						if (turn_time == 0.0f) {
1532 							Warning(LOCATION, "Dumb-Rotation has a turn time of 0 for subsystem '%s' on ship %s!", pm->submodel[n].name, pm->filename);
1533 							turn_rate = 1.0f;
1534 						} else {
1535 							turn_rate = PI2 / turn_time;
1536 						}
1537 					} else {
1538 						turn_rate = static_cast<float>(atof(buf));
1539 					}
1540 
1541 					pm->submodel[n].dumb_turn_rate = turn_rate;
1542 				} else {
1543 					pm->submodel[n].dumb_turn_rate = 0.0f;
1544 				}
1545 
1546 				if ( pm->submodel[n].name[0] == '\0' ) {
1547 					strcpy_s(pm->submodel[n].name, "unknown object name");
1548 				}
1549 
1550 				if ( ( p = strstr(props, "$special"))!= NULL ) {
1551 					char type[64];
1552 
1553 					get_user_prop_value(p+9, type);
1554 					if ( !stricmp(type, "subsystem") ) {	// if we have a subsystem, put it into the list!
1555 						do_new_subsystem( n_subsystems, subsystems, n, pm->submodel[n].rad, &pm->submodel[n].offset, props, pm->submodel[n].name, pm->id );
1556 					} else if ( !stricmp(type, "no_rotate") ) {
1557 						// mark those submodels which should not rotate - ie, those with no subsystem
1558 						pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
1559 					} else {
1560 						// if submodel rotates (via bspgen), then there is either a subsys or special=no_rotate
1561 						Assert( pm->submodel[n].movement_type != MOVEMENT_TYPE_ROT );
1562 					}
1563 				}
1564 
1565 				// ---------- done with submodel movement (except for gun_rotation and sanity checks) ----------
1566 
1567 				if (strstr(props, "$no_collisions") != NULL )
1568 					pm->submodel[n].no_collisions = true;
1569 				else
1570 					pm->submodel[n].no_collisions = false;
1571 
1572 				if (strstr(props, "$nocollide_this_only") != NULL )
1573 					pm->submodel[n].nocollide_this_only = true;
1574 				else
1575 					pm->submodel[n].nocollide_this_only = false;
1576 
1577 				if (strstr(props, "$collide_invisible") != NULL )
1578 					pm->submodel[n].collide_invisible = true;
1579 				else
1580 					pm->submodel[n].collide_invisible = false;
1581 
1582 				if (strstr(props, "$gun_rotation") != nullptr) {
1583 					pm->submodel[n].gun_rotation = true;
1584 					pm->submodel[n].can_move = true;		// this is something of a special case because it's rotating without "rotating"
1585 				} else
1586 					pm->submodel[n].gun_rotation = false;
1587 
1588 				if ( (p = strstr(props, "$lod0_name")) != NULL)
1589 					get_user_prop_value(p+10, pm->submodel[n].lod_name);
1590 
1591 				if (strstr(props, "$attach_thrusters") != NULL )
1592 					pm->submodel[n].attach_thrusters = true;
1593 				else
1594 					pm->submodel[n].attach_thrusters = false;
1595 
1596 				if ( (p = strstr(props, "$detail_box:")) != NULL ) {
1597 					p += 12;
1598 					while (*p == ' ') p++;
1599 					pm->submodel[n].use_render_box = atoi(p);
1600 
1601 					if ( (p = strstr(props, "$box_offset:")) != NULL ) {
1602 						p += 12;
1603 						while (*p == ' ') p++;
1604 						pm->submodel[n].render_box_offset.xyz.x = (float)strtod(p, (char **)NULL);
1605 						while (*p != ',') p++;
1606 						pm->submodel[n].render_box_offset.xyz.y = (float)strtod(++p, (char **)NULL);
1607 						while (*p != ',') p++;
1608 						pm->submodel[n].render_box_offset.xyz.z = (float)strtod(++p, (char **)NULL);
1609 
1610 						pm->submodel[n].use_render_box_offset = true;
1611 					}
1612 
1613 					if ( (p = strstr(props, "$box_min:")) != NULL ) {
1614 						p += 9;
1615 						while (*p == ' ') p++;
1616 						pm->submodel[n].render_box_min.xyz.x = (float)strtod(p, (char **)NULL);
1617 						while (*p != ',') p++;
1618 						pm->submodel[n].render_box_min.xyz.y = (float)strtod(++p, (char **)NULL);
1619 						while (*p != ',') p++;
1620 						pm->submodel[n].render_box_min.xyz.z = (float)strtod(++p, (char **)NULL);
1621 					} else {
1622 						pm->submodel[n].render_box_min = pm->submodel[n].min;
1623 					}
1624 
1625 					if ( (p = strstr(props, "$box_max:")) != NULL ) {
1626 						p += 9;
1627 						while (*p == ' ') p++;
1628 						pm->submodel[n].render_box_max.xyz.x = (float)strtod(p, (char **)NULL);
1629 						while (*p != ',') p++;
1630 						pm->submodel[n].render_box_max.xyz.y = (float)strtod(++p, (char **)NULL);
1631 						while (*p != ',') p++;
1632 						pm->submodel[n].render_box_max.xyz.z = (float)strtod(++p, (char **)NULL);
1633 					} else {
1634 						pm->submodel[n].render_box_max = pm->submodel[n].max;
1635 					}
1636 
1637 					if ( (p = strstr(props, "$do_not_scale_distances")) != nullptr ) {
1638 						p += 23;
1639 						pm->submodel[n].do_not_scale_detail_distances = true;
1640 					}
1641 				}
1642 
1643 				if ( (p = strstr(props, "$detail_sphere:")) != NULL ) {
1644 					p += 15;
1645 					while (*p == ' ') p++;
1646 					pm->submodel[n].use_render_sphere = atoi(p);
1647 
1648 					if ( (p = strstr(props, "$radius:")) != NULL ) {
1649 						p += 8;
1650 						while (*p == ' ') p++;
1651 						pm->submodel[n].render_sphere_radius = (float)strtod(p, (char **)NULL);
1652 					} else {
1653 						pm->submodel[n].render_sphere_radius = pm->submodel[n].rad;
1654 					}
1655 
1656 					if ( (p = strstr(props, "$offset:")) != NULL ) {
1657 						p += 8;
1658 						while (*p == ' ') p++;
1659 						pm->submodel[n].render_sphere_offset.xyz.x = (float)strtod(p, (char **)NULL);
1660 						while (*p != ',') p++;
1661 						pm->submodel[n].render_sphere_offset.xyz.y = (float)strtod(++p, (char **)NULL);
1662 						while (*p != ',') p++;
1663 						pm->submodel[n].render_sphere_offset.xyz.z = (float)strtod(++p, (char **)NULL);
1664 
1665 						pm->submodel[n].use_render_sphere_offset = true;
1666 					} else {
1667 						pm->submodel[n].render_sphere_offset = vmd_zero_vector;
1668 					}
1669 
1670 					if ( (p = strstr(props, "$do_not_scale_distances")) != nullptr ) {
1671 						p += 23;
1672 						pm->submodel[n].do_not_scale_detail_distances = true;
1673 					}
1674 				}
1675 
1676 				// KeldorKatarn, with modifications
1677 				if ( (p = strstr(props, "$uvec")) != nullptr ) {
1678 					matrix submodel_orient;
1679 
1680 					if (get_user_vec3d_value(p + 5, &submodel_orient.vec.uvec, false, pm->submodel[n].name, pm->filename)) {
1681 
1682 						if ((p = strstr(props, "$fvec")) != nullptr) {
1683 
1684 							if (get_user_vec3d_value(p + 5, &submodel_orient.vec.fvec, false, pm->submodel[n].name, pm->filename)) {
1685 
1686 								vm_vec_normalize(&submodel_orient.vec.uvec);
1687 								vm_vec_normalize(&submodel_orient.vec.fvec);
1688 
1689 								vm_vec_cross(&submodel_orient.vec.rvec, &submodel_orient.vec.uvec, &submodel_orient.vec.fvec);
1690 								vm_vec_cross(&submodel_orient.vec.fvec, &submodel_orient.vec.rvec, &submodel_orient.vec.uvec);
1691 
1692 								vm_vec_normalize(&submodel_orient.vec.fvec);
1693 								vm_vec_normalize(&submodel_orient.vec.rvec);
1694 
1695 								vm_orthogonalize_matrix(&submodel_orient);
1696 
1697 								pm->submodel[n].frame_of_reference = submodel_orient;
1698 
1699 							} else {
1700 								Warning(LOCATION,
1701 									"Submodel '%s' of model '%s' has an improperly formatted $fvec declaration in its properties."
1702 									"\n\n$fvec should be followed by 3 numbers separated with commas.",
1703 									pm->submodel[n].name, filename);
1704 							}
1705 						} else {
1706 							Warning(LOCATION, "Improper custom orientation matrix for subsystem %s; you must define both an up vector and a forward vector", pm->submodel[n].name);
1707 						}
1708 					} else {
1709 						Warning(LOCATION,
1710 							"Submodel '%s' of model '%s' has an improperly formatted $uvec declaration in its properties."
1711 							"\n\n$uvec should be followed by 3 numbers separated with commas.",
1712 							pm->submodel[n].name, filename);
1713 					}
1714 				} else {
1715 					if (parent >= 0) {
1716 						pm->submodel[n].frame_of_reference = pm->submodel[parent].frame_of_reference;
1717 					} else {
1718 						pm->submodel[n].frame_of_reference = vmd_identity_matrix;
1719 					}
1720 				}
1721 
1722 				// ---------- submodel rotation sanity checks ----------
1723 
1724 				// make sure this is a validly normalized axis
1725 				if (vm_vec_mag(&pm->submodel[n].movement_axis) < 0.999f || vm_vec_mag(&pm->submodel[n].movement_axis) > 1.001f) {
1726 					pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
1727 				}
1728 
1729 				// maybe use the FOR to manipulate the rotation axis
1730 				// (do this before the compatibility check below to prevent doing it twice)
1731 				model_maybe_adjust_movement_axis(&pm->submodel[n]);
1732 
1733 				// important compatibility check: if there are multipart turrets without rotation axes defined, define them
1734 				// also, some of the retail models got the axes wrong, so fix those :-/
1735 				// what this boils down to is that we must force turret axes for submodels with frame_of_reference defined
1736 				//		and also for turrets which don't have their axes set to "other"
1737 				if (parent >= 0 && stristr(pm->submodel[parent].name, "turret"))
1738 				{
1739 					auto base = &pm->submodel[parent];
1740 					auto gun = &pm->submodel[n];
1741 
1742 					if (!vm_matrix_equal(base->frame_of_reference, vmd_identity_matrix)
1743 						|| (base->movement_axis_id != MOVEMENT_AXIS_OTHER))
1744 					{
1745 						base->movement_axis_id = MOVEMENT_AXIS_Y;
1746 						base->movement_axis = vmd_y_vector;
1747 						base->movement_type = MOVEMENT_TYPE_ROT_SPECIAL;
1748 						model_maybe_adjust_movement_axis(base);
1749 					}
1750 
1751 					if (!vm_matrix_equal(gun->frame_of_reference, vmd_identity_matrix)
1752 						|| (gun->movement_axis_id != MOVEMENT_AXIS_OTHER))
1753 					{
1754 						gun->movement_axis_id = MOVEMENT_AXIS_X;
1755 						gun->movement_axis = vmd_x_vector;
1756 						gun->movement_type = MOVEMENT_TYPE_ROT_SPECIAL;
1757 						model_maybe_adjust_movement_axis(gun);
1758 					}
1759 				}
1760 
1761 				// adding a warning if rotation is specified without movement axis.
1762 				if (pm->submodel[n].movement_axis_id == MOVEMENT_AXIS_NONE) {
1763 					if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) {
1764 						Warning(LOCATION, "Rotation without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename);
1765 					}
1766 					else if (pm->submodel[n].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
1767 						Warning(LOCATION, "Intrinsic rotation (e.g. dumb-rotate or look-at) without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename);
1768 					}
1769 					pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
1770 				}
1771 
1772 				// clear the axis if the submodel doesn't move
1773 				// (don't clear can_move because of gun_rotation)
1774 				if (pm->submodel[n].movement_type == MOVEMENT_TYPE_NONE) {
1775 					pm->submodel[n].movement_axis_id = MOVEMENT_AXIS_NONE;
1776 					pm->submodel[n].movement_axis = vmd_zero_vector;
1777 				}
1778 
1779 				// Set the can_move field on submodels which are of a rotating type or which have such a parent somewhere down the hierarchy
1780 				if (pm->submodel[n].movement_type != MOVEMENT_TYPE_NONE) {
1781 					pm->submodel[n].can_move = true;
1782 				} else if (pm->submodel[n].parent >= 0 && pm->submodel[pm->submodel[n].parent].can_move) {
1783 					pm->submodel[n].can_move = true;
1784 				}
1785 
1786 				// ---------- done submodel rotation sanity checks ----------
1787 
1788 
1789 				{
1790 					int nchunks = cfread_int( fp );		// Throw away nchunks
1791 					if ( nchunks > 0 )	{
1792 						Error( LOCATION, "Model '%s' is chunked.  See John or Adam!\n", pm->filename );
1793 					}
1794 				}
1795 
1796 				//ShivanSpS - if pof version is 2200 or higher load bsp_data as it is, otherwise, align it
1797 				if (pm->version >= 2200)
1798 				{
1799 					pm->submodel[n].bsp_data_size = cfread_int(fp);
1800 					if (pm->submodel[n].bsp_data_size > 0) {
1801 						pm->submodel[n].bsp_data = (ubyte*)vm_malloc(pm->submodel[n].bsp_data_size);
1802 						cfread(pm->submodel[n].bsp_data, 1, pm->submodel[n].bsp_data_size, fp);
1803 						swap_bsp_data(pm, pm->submodel[n].bsp_data);
1804 					}
1805 					else {
1806 						pm->submodel[n].bsp_data = nullptr;
1807 					}
1808 				}
1809 				else
1810 				{
1811 					pm->submodel[n].bsp_data_size = cfread_int(fp);
1812 					if (pm->submodel[n].bsp_data_size > 0) {
1813 						//mprintf(("BSP_Data is being aligned.\n"));
1814 
1815 						std::unique_ptr<ubyte[]> bsp_in(new ubyte[pm->submodel[n].bsp_data_size]);
1816 						std::unique_ptr<ubyte[]> bsp_out(new ubyte[pm->submodel[n].bsp_data_size * 2]);
1817 
1818 						cfread(bsp_in.get(), 1, pm->submodel[n].bsp_data_size, fp);
1819 
1820 						//mprintf(("BSP_Data was %d bytes in size\n", pm->submodel[n].bsp_data_size));
1821 						pm->submodel[n].bsp_data_size = align_bsp_data(bsp_in.get(), bsp_out.get(), pm->submodel[n].bsp_data_size);
1822 						//mprintf(("BSP_Data now is %d bytes in size\n", pm->submodel[n].bsp_data_size));
1823 
1824 						pm->submodel[n].bsp_data = (ubyte*)vm_malloc(pm->submodel[n].bsp_data_size);
1825 						memcpy(pm->submodel[n].bsp_data, bsp_out.get(), pm->submodel[n].bsp_data_size);
1826 						swap_bsp_data(pm, pm->submodel[n].bsp_data);
1827 					}
1828 					else {
1829 						pm->submodel[n].bsp_data = nullptr;
1830 					}
1831 				}
1832 
1833 				pm->submodel[n].is_thruster = (stristr(pm->submodel[n].name, "thruster") != nullptr);
1834 
1835 				// Genghis: if we have a thruster and none of the collision
1836 				// properties were provided, then set "nocollide_this_only".
1837 				if (pm->submodel[n].is_thruster && !(pm->submodel[n].no_collisions) && !(pm->submodel[n].nocollide_this_only) && !(pm->submodel[n].collide_invisible) )
1838 				{
1839 					pm->submodel[n].nocollide_this_only = true;
1840 				}
1841 
1842 				pm->submodel[n].is_damaged = (strstr(pm->submodel[n].name, "-destroyed") != nullptr);
1843 
1844 				break;
1845 			}
1846 
1847 			case ID_SLDC: // kazan - Shield Collision tree
1848 			{   //ShivanSpS - if pof version is 2200 or higher ignore SLDC, otherwise convert it to slc2.
1849 				if (pm->version < 2200) {
1850 					//mprintf(("SLDC data is being converted to SLC2.\n"));
1851 					pm->sldc_size = cfread_int(fp);
1852 
1853 					std::unique_ptr<ubyte[]> sldc_tree(new ubyte[pm->sldc_size]);
1854 					std::unique_ptr<ubyte[]> slc2_tree(new ubyte[pm->sldc_size * 2]);
1855 
1856 					cfread(sldc_tree.get(), 1, pm->sldc_size, fp);
1857 					//mprintf(("SLDC Shield Collision Tree was %d bytes in size\n", pm->sldc_size));
1858 					pm->sldc_size = convert_sldc_to_slc2(sldc_tree.get(), slc2_tree.get(), pm->sldc_size);
1859 					//mprintf(("SLC2 Shield Collision Tree is %d bytes in size\n", pm->sldc_size));
1860 					pm->shield_collision_tree = (ubyte*)vm_malloc(pm->sldc_size); //sldc_size is slc2 size, reused variable
1861 					memcpy(pm->shield_collision_tree, slc2_tree.get(), pm->sldc_size);
1862 					swap_sldc_data(pm->shield_collision_tree);
1863 				}
1864 			}
1865 			break;
1866 
1867 			case ID_SLC2: // ShivanSpS -Newer version of the SLDC Shield Collision tree, only pof version 2200.
1868 			{
1869 				if (pm->version >= 2200) {
1870 					pm->sldc_size = cfread_int(fp);
1871 					pm->shield_collision_tree = (ubyte*)vm_malloc(pm->sldc_size);
1872 					cfread(pm->shield_collision_tree, 1, pm->sldc_size, fp);
1873 					swap_sldc_data(pm->shield_collision_tree);
1874 					//mprintf(( "SLC2 Shield Collision Tree, %d bytes in size\n", pm->sldc_size));
1875 				}
1876 			}
1877 			break;
1878 
1879 			case ID_SHLD:
1880 				{
1881 					pm->shield.nverts = cfread_int( fp );		// get the number of vertices in the list
1882 
1883 					if (pm->shield.nverts > 0) {
1884 						pm->shield.verts = (shield_vertex *)vm_malloc(pm->shield.nverts * sizeof(shield_vertex) );
1885 						Assert( pm->shield.verts );
1886 						for ( i = 0; i < pm->shield.nverts; i++ ) {						// read in the vertex list
1887 							cfread_vector( &(pm->shield.verts[i].pos), fp );
1888 						}
1889 					}
1890 
1891 					pm->shield.ntris = cfread_int( fp );		// get the number of triangles that compose the shield
1892 
1893 					if (pm->shield.ntris > 0) {
1894 						pm->shield.tris = (shield_tri *)vm_malloc(pm->shield.ntris * sizeof(shield_tri) );
1895 						Assert( pm->shield.tris );
1896 						for ( i = 0; i < pm->shield.ntris; i++ ) {
1897 							cfread_vector( &temp_vec, fp );
1898 							vm_vec_normalize_safe(&temp_vec);
1899 							pm->shield.tris[i].norm = temp_vec;
1900 							for ( j = 0; j < 3; j++ ) {
1901 								pm->shield.tris[i].verts[j] = cfread_int( fp );		// read in the indices into the shield_vertex list
1902 #ifndef NDEBUG
1903 								if (pm->shield.tris[i].verts[j] >= pm->shield.nverts) {
1904 									Error(LOCATION, "Ship %s has a bogus shield mesh.\nOnly %i vertices, index %i found.\n", filename, pm->shield.nverts, pm->shield.tris[i].verts[j]);
1905 								}
1906 #endif
1907 							}
1908 
1909 							for ( j = 0; j < 3; j++ ) {
1910 								pm->shield.tris[i].neighbors[j] = cfread_int( fp );	// read in the neighbor indices -- indexes into tri list
1911 #ifndef NDEBUG
1912 								if (pm->shield.tris[i].neighbors[j] >= pm->shield.ntris) {
1913 									Error(LOCATION, "Ship %s has a bogus shield mesh.\nOnly %i triangles, index %i found.\n", filename, pm->shield.ntris, pm->shield.tris[i].neighbors[j]);
1914 								}
1915 #endif
1916 							}
1917 						}
1918 					}
1919 				}
1920 				break;
1921 
1922 			// guns and missiles use almost exactly the same code
1923 			case ID_GPNT:
1924 			case ID_MPNT:
1925 			{
1926 				int n_weps = cfread_int(fp);
1927 				w_bank *wep_banks = nullptr;
1928 
1929 				if (n_weps > 0)
1930 				{
1931 					wep_banks = new w_bank[n_weps];
1932 					for (i = 0; i < n_weps; ++i)
1933 					{
1934 						w_bank *bank = &wep_banks[i];
1935 
1936 						bank->num_slots = cfread_int(fp);
1937 						if (bank->num_slots > 0)
1938 						{
1939 							bank->pnt = new vec3d[bank->num_slots];
1940 							bank->norm = new vec3d[bank->num_slots];
1941 							bank->external_model_angle_offset = new float[bank->num_slots];
1942 
1943 							for (j = 0; j < bank->num_slots; ++j)
1944 							{
1945 								cfread_vector(&(bank->pnt[j]), fp);
1946 								cfread_vector(&temp_vec, fp);
1947 								vm_vec_normalize_safe(&temp_vec);
1948 								bank->norm[j] = temp_vec;
1949 
1950 								// angle offsets are a new POF feature
1951 								if ((pm->version >= 2118 && pm->version < PM_FIRST_ALIGNED_VERSION) || (pm->version >= 2201))
1952 									bank->external_model_angle_offset[j] = fl_radians(cfread_float(fp));
1953 								else
1954 									bank->external_model_angle_offset[j] = 0.0f;
1955 							}
1956 						}
1957 					}
1958 				}
1959 
1960 				if (id == ID_GPNT)
1961 				{
1962 					pm->n_guns = n_weps;
1963 					pm->gun_banks = wep_banks;
1964 				}
1965 				else
1966 				{
1967 					pm->n_missiles = n_weps;
1968 					pm->missile_banks = wep_banks;
1969 				}
1970 				break;
1971 			}
1972 
1973 			case ID_DOCK: {
1974 				char props[MAX_PROP_LEN];
1975 
1976 				pm->n_docks = cfread_int(fp);
1977 
1978 				if (pm->n_docks > 0) {
1979 					pm->docking_bays = (dock_bay *)vm_malloc(sizeof(dock_bay) * pm->n_docks);
1980 					Assert( pm->docking_bays != NULL );
1981 
1982 					for (i = 0; i < pm->n_docks; i++ ) {
1983 						char *p;
1984 						dock_bay *bay = &pm->docking_bays[i];
1985 
1986 						cfread_string_len( props, MAX_PROP_LEN, fp );
1987 						if ( (p = strstr(props, "$name"))!= NULL ) {
1988 							get_user_prop_value(p+5, bay->name);
1989 
1990 							auto length = strlen(bay->name);
1991 							if ((length > 0) && is_white_space(bay->name[length-1])) {
1992 								nprintf(("Model", "model '%s' has trailing whitespace on bay name '%s'; this will be trimmed\n", pm->filename, bay->name));
1993 								drop_trailing_white_space(bay->name);
1994 							}
1995 							if (strlen(bay->name) == 0) {
1996 								nprintf(("Model", "model '%s' has an empty name specified for docking point %d\n", pm->filename, i));
1997 							}
1998 						} else {
1999 							nprintf(("Model", "model '%s' has no name specified for docking point %d\n", pm->filename, i));
2000 							sprintf(bay->name, "<unnamed bay %c>", 'A' + i);
2001 						}
2002 
2003 #ifndef NDEBUG
2004 						// check for duplicates
2005 						// (we just warn here and take no action, because even some retail models have duplicate dockpoints)
2006 						for (j = 0; j < i; j++) {
2007 							if (stricmp(bay->name, pm->docking_bays[j].name) == 0) {
2008 								Warning(LOCATION, "Duplicate docking bay name '%s' found on model '%s'!", bay->name, pm->filename);
2009 							}
2010 						}
2011 #endif
2012 
2013 						bay->num_spline_paths = cfread_int( fp );
2014 						if ( bay->num_spline_paths > 0 ) {
2015 							bay->splines = (int *)vm_malloc(sizeof(int) * bay->num_spline_paths);
2016 							for ( j = 0; j < bay->num_spline_paths; j++ )
2017 								bay->splines[j] = cfread_int(fp);
2018 						} else {
2019 							bay->splines = NULL;
2020 						}
2021 
2022 						// determine what this docking bay can be used for
2023 						if ( !strnicmp(bay->name, "cargo", 5) )
2024 							bay->type_flags = DOCK_TYPE_CARGO;
2025 						else
2026 							bay->type_flags = (DOCK_TYPE_REARM | DOCK_TYPE_GENERIC);
2027 
2028 						bay->num_slots = cfread_int(fp);
2029 
2030 						if(bay->num_slots != 2) {
2031 							Warning(LOCATION, "Model '%s' has %d slots in dock point '%s'; models must have exactly %d slots per dock point.", filename, bay->num_slots, bay->name, 2);
2032 						}
2033 
2034 						for (j = 0; j < bay->num_slots; j++) {
2035 							cfread_vector( &(bay->pnt[j]), fp );
2036 							cfread_vector( &(bay->norm[j]), fp );
2037 							if(vm_vec_mag(&(bay->norm[j])) <= 0.0f) {
2038 								Warning(LOCATION, "Model '%s' dock point '%s' has a null normal. ", filename, bay->name);
2039 							}
2040 						}
2041 
2042 						if(vm_vec_same(&bay->pnt[0], &bay->pnt[1])) {
2043 							Warning(LOCATION, "Model '%s' has two identical docking slot positions on docking port '%s'. This is not allowed.  A new second slot position will be generated.", filename, bay->name);
2044 
2045 							// just move the second point over by some amount
2046 							bay->pnt[1].xyz.z += 10.0f;
2047 						}
2048 
2049 						vec3d diff;
2050 						vm_vec_normalized_dir(&diff, &bay->pnt[0], &bay->pnt[1]);
2051 						float dot = vm_vec_dot(&diff, &bay->norm[0]);
2052 						if(fl_abs(dot) > 0.99f) {
2053 							Warning(LOCATION, "Model '%s', docking port '%s' has docking slot positions that lie on the same axis as the docking normal.  This will cause a NULL VEC crash when docked to another ship.  A new docking normal will be generated.", filename, bay->name);
2054 
2055 							// generate a simple rotation matrix in all three dimensions (though bank is probably not needed)
2056 							angles a = { PI_2, PI_2, PI_2 };
2057 							matrix m;
2058 							vm_angles_2_matrix(&m, &a);
2059 
2060 							// rotate the docking normal
2061 							vec3d temp = bay->norm[0];
2062 							vm_vec_rotate(&bay->norm[0], &temp, &m);
2063 						}
2064 					}
2065 				}
2066 				break;
2067 			}
2068 
2069 			case ID_GLOW:					//start glow point reading -Bobboau
2070 			{
2071 				char props[MAX_PROP_LEN];
2072 
2073 				int gpb_num = cfread_int(fp);
2074 
2075 				pm->n_glow_point_banks = gpb_num;
2076 				pm->glow_point_banks = NULL;
2077 
2078 				if (gpb_num > 0)
2079 				{
2080 					pm->glow_point_banks = (glow_point_bank *) vm_malloc(sizeof(glow_point_bank) * gpb_num);
2081 					Assert(pm->glow_point_banks != NULL);
2082 				}
2083 
2084 				for (int gpb = 0; gpb < gpb_num; gpb++)
2085 				{
2086 					glow_point_bank *bank = &pm->glow_point_banks[gpb];
2087 
2088 					bank->is_on = true;
2089 					bank->glow_timestamp = 0;
2090 					bank->disp_time = cfread_int(fp);
2091 					bank->on_time = cfread_int(fp);
2092 					bank->off_time = cfread_int(fp);
2093 					bank->submodel_parent = cfread_int(fp);
2094 					bank->LOD = cfread_int(fp);
2095 					bank->type = cfread_int(fp);
2096 					bank->num_points = cfread_int(fp);
2097 					bank->points = NULL;
2098 					bank->glow_bitmap = -1;
2099 					bank->glow_neb_bitmap = -1;
2100 
2101 					if (bank->num_points > 0)
2102 						bank->points = (glow_point *) vm_malloc(sizeof(glow_point) * bank->num_points);
2103 
2104 					//if((bank->off_time > 0) && (bank->disp_time > 0))
2105 						//bank->is_on = false;
2106 
2107 					cfread_string_len(props, MAX_PROP_LEN, fp);
2108 					// look for $glow_texture=xxx
2109 					auto length = strlen(props);
2110 
2111 					if (length > 0)
2112 					{
2113 						auto base_length = strlen("$glow_texture=");
2114 						char *glow_texture_start = strstr(props, "$glow_texture=");
2115 						if ( (glow_texture_start != nullptr) && (strlen(glow_texture_start + base_length) > 0) ) {
2116 							char *glow_texture_name = glow_texture_start + base_length;
2117 
2118 							if (glow_texture_name[0] == '$')
2119 								glow_texture_name++;
2120 
2121 							bank->glow_bitmap = bm_load(glow_texture_name);
2122 
2123 							if (bank->glow_bitmap < 0)
2124 							{
2125 								Warning( LOCATION, "Couldn't open glowpoint texture '%s'\nreferenced by model '%s'\n", glow_texture_name, pm->filename);
2126 							}
2127 							else
2128 							{
2129 								nprintf(( "Model", "Glow point bank %i texture num is %d for '%s'\n", gpb, bank->glow_bitmap, pm->filename));
2130 							}
2131 
2132 							strcat(glow_texture_name, "-neb");
2133 							bank->glow_neb_bitmap = bm_load(glow_texture_name);
2134 
2135 							if (bank->glow_neb_bitmap < 0)
2136 							{
2137 								bank->glow_neb_bitmap = bank->glow_bitmap;
2138 								nprintf(( "Model", "Glow point bank nebula texture not found for '%s', using normal glowpoint texture instead\n", pm->filename));
2139 							//	Error( LOCATION, "Couldn't open texture '%s'\nreferenced by model '%s'\n", glow_texture_name, pm->filename );
2140 							}
2141 							else
2142 							{
2143 								nprintf(( "Model", "Glow point bank %i nebula texture num is %d for '%s'\n", gpb, bank->glow_neb_bitmap, pm->filename));
2144 							}
2145 						} else {
2146 							Warning( LOCATION, "No glow point texture for bank '%d' referenced by model '%s'\n", gpb, pm->filename);
2147 						}
2148 					}
2149 					else
2150 					{
2151 						Warning( LOCATION, "No glow point texture for bank '%d' referenced by model '%s'\n", gpb, pm->filename);
2152 					}
2153 
2154 					for (j = 0; j < bank->num_points; j++)
2155 					{
2156 						glow_point *p = &bank->points[j];
2157 
2158 						cfread_vector(&(p->pnt), fp);
2159 						cfread_vector( &temp_vec, fp );
2160 						if (!IS_VEC_NULL_SQ_SAFE(&temp_vec))
2161 							vm_vec_normalize(&temp_vec);
2162 						else
2163 							vm_vec_zero(&temp_vec);
2164 						p->norm = temp_vec;
2165 						p->radius = cfread_float( fp);
2166 					}
2167 				}
2168 				break;
2169 			 }
2170 
2171 			case ID_FUEL:
2172 				char props[MAX_PROP_LEN];
2173 				pm->n_thrusters = cfread_int(fp);
2174 
2175 				if (pm->n_thrusters > 0) {
2176 					pm->thrusters = (thruster_bank *)vm_malloc(sizeof(thruster_bank) * pm->n_thrusters);
2177 					Assert( pm->thrusters != NULL );
2178 
2179 					for (i = 0; i < pm->n_thrusters; i++ ) {
2180 						thruster_bank *bank = &pm->thrusters[i];
2181 
2182 						bank->num_points = cfread_int(fp);
2183 						bank->points = NULL;
2184 
2185 						if (bank->num_points > 0)
2186 							bank->points = (glow_point *) vm_malloc(sizeof(glow_point) * bank->num_points);
2187 
2188 						bank->obj_num = -1;
2189 						bank->submodel_num = -1;
2190 						bank->wash_info_pointer = nullptr;
2191 
2192 						if (pm->version >= 2117) {
2193 							cfread_string_len( props, MAX_PROP_LEN, fp );
2194 							// look for $engine_subsystem=xxx
2195 							auto length = strlen(props);
2196 							if (length > 0) {
2197 								auto base_length = strlen("$engine_subsystem=");
2198 								char *engine_subsys_start = strstr(props, "$engine_subsystem=");
2199 								if ( (engine_subsys_start != nullptr) && (strlen(engine_subsys_start + base_length) > 0) ) {
2200 									char *engine_subsys_name = engine_subsys_start + base_length;
2201 									if (engine_subsys_name[0] == '$') {
2202 										engine_subsys_name++;
2203 									}
2204 
2205 									nprintf(("wash", "Ship %s with engine wash associated with subsys %s\n", filename, engine_subsys_name));
2206 
2207 									// start off assuming the subsys is invalid
2208 									int table_error = 1;
2209 									for (int k=0; k<n_subsystems; k++) {
2210 										if ( !subsystem_stricmp(subsystems[k].subobj_name, engine_subsys_name) ) {
2211 											bank->submodel_num = subsystems[k].subobj_num;
2212 
2213 											bank->wash_info_pointer = subsystems[k].engine_wash_pointer;
2214 											if (bank->wash_info_pointer != nullptr) {
2215 												table_error = 0;
2216 											}
2217 											// also set what subsystem this is attached to but not if we only have one thruster bank
2218 											// do this so that original :V: models still work like they used to
2219 											if (pm->n_thrusters > 1) {
2220 												bank->obj_num = k;
2221 											}
2222 											break;
2223 										}
2224 									}
2225 
2226 									if ( (bank->wash_info_pointer == nullptr) && (n_subsystems > 0) ) {
2227 										if (table_error) {
2228 										//	Warning(LOCATION, "No engine wash table entry in ships.tbl for ship model %s", filename);
2229 										} else {
2230 											Warning(LOCATION, "Inconsistent model: Engine wash engine subsystem does not match any ship subsytem names for ship model %s", filename);
2231 										}
2232 									}
2233 								}
2234 							}
2235 						}
2236 
2237 						for (j = 0; j < bank->num_points; j++) {
2238 							glow_point *p = &bank->points[j];
2239 
2240 							cfread_vector( &(p->pnt), fp );
2241 							cfread_vector( &temp_vec, fp );
2242 							vm_vec_normalize_safe(&temp_vec);
2243 							p->norm = temp_vec;
2244 
2245 							if ( pm->version > 2004 )	{
2246 								p->radius = cfread_float( fp );
2247 								//mprintf(( "Rad = %.2f\n", rad ));
2248 							} else {
2249 								p->radius = 1.0f;
2250 							}
2251 						}
2252 						//mprintf(( "Num slots = %d\n", bank->num_slots ));
2253 					}
2254 				}
2255 				break;
2256 
2257 			case ID_TGUN:
2258 			case ID_TMIS: {
2259 				int n_banks = cfread_int(fp);			// Number of turrets
2260 
2261 				for ( i = 0; i < n_banks; i++ ) {
2262 					int parent;							// The parent subobj of the turret (the gun base)
2263 					int physical_parent;				// The subobj that the firepoints are physically attached to (the gun barrel)
2264 					int n_slots;						// How many firepoints the turret has
2265 					model_subsystem *subsystemp;		// The actual turret subsystem
2266 
2267 					parent = cfread_int( fp );
2268 					physical_parent = cfread_int(fp);
2269 
2270 					int snum=-1;
2271 					if ( subsystems ) {
2272 						for ( snum = 0; snum < n_subsystems; snum++ ) {
2273 							subsystemp = &subsystems[snum];
2274 
2275 							if ( parent == subsystemp->subobj_num ) {
2276 								cfread_vector( &temp_vec, fp );
2277 								vm_vec_normalize_safe(&temp_vec);
2278 								subsystemp->turret_norm = temp_vec;
2279 
2280 								n_slots = cfread_int( fp );
2281 								subsystemp->turret_gun_sobj = physical_parent;
2282 								if(n_slots > MAX_TFP) {
2283 									Warning(LOCATION, "Model %s has %i turret firing points on subsystem %s, maximum is %i", pm->filename, n_slots, subsystemp->name, MAX_TFP);
2284 								}
2285 
2286 								for (j = 0; j < n_slots; j++ )	{
2287 									if(j < MAX_TFP)
2288 										cfread_vector( &subsystemp->turret_firing_point[j], fp );
2289 									else
2290 									{
2291 										vec3d bogus;
2292 										cfread_vector(&bogus, fp);
2293 									}
2294 								}
2295 								Assertion( n_slots > 0, "Turret %s in model %s has no firing points.\n", subsystemp->name, pm->filename);
2296 
2297 								subsystemp->turret_num_firing_points = n_slots;
2298 
2299 								break;
2300 							}
2301 						}
2302 					}
2303 
2304 					if ( (n_subsystems == 0) || (snum == n_subsystems) ) {
2305 						vec3d bogus;
2306 
2307 						nprintf(("Warning", "Turret submodel %i not found for turret %i in model %s\n", parent, i, pm->filename));
2308 						cfread_vector( &bogus, fp );
2309 						n_slots = cfread_int( fp );
2310 						for (j = 0; j < n_slots; j++ )
2311 							cfread_vector( &bogus, fp );
2312 					}
2313 				}
2314 				break;
2315 			}
2316 
2317 			case ID_SPCL: {
2318 				char name[MAX_NAME_LEN], props_spcl[MAX_PROP_LEN], *p;
2319 				int n_specials;
2320 				float radius;
2321 				vec3d pnt;
2322 
2323 				n_specials = cfread_int(fp);		// get the number of special subobjects we have
2324 				for (i = 0; i < n_specials; i++) {
2325 
2326 					// get the next free object of the subobject list.  Flag error if no more room
2327 
2328 					cfread_string_len(name, MAX_NAME_LEN, fp);			// get the name of this special polygon
2329 
2330 					cfread_string_len(props_spcl, MAX_PROP_LEN, fp);		// will definately have properties as well!
2331 					cfread_vector( &pnt, fp );
2332 					radius = cfread_float( fp );
2333 
2334 					// check if $Split
2335 					p = strstr(name, "$split");
2336 					if (p != NULL) {
2337 						pm->split_plane[pm->num_split_plane] = pnt.xyz.z;
2338 						pm->num_split_plane++;
2339 						Assert(pm->num_split_plane <= MAX_SPLIT_PLANE);
2340 					} else if ( ( p = strstr(props_spcl, "$special"))!= NULL ) {
2341 						char type[64];
2342 
2343 						get_user_prop_value(p+9, type);
2344 						if ( !stricmp(type, "subsystem") ) {	// if we have a subsystem, put it into the list!
2345 							do_new_subsystem( n_subsystems, subsystems, -1, radius, &pnt, props_spcl, &name[1], pm->id );		// skip the first '$' character of the name
2346 						} else if ( !stricmp(type, "shieldpoint") ) {
2347 							pm->shield_points.push_back(pnt);
2348 						}
2349 					} else if ( strstr(name, "$enginelarge") || strstr(name, "$enginehuge") ){
2350 						do_new_subsystem( n_subsystems, subsystems, -1, radius, &pnt, props_spcl, &name[1], pm->id );		// skip the first '$' character of the name
2351 					} else {
2352 						nprintf(("Warning", "Unknown special object type %s while reading model %s\n", name, pm->filename));
2353 					}
2354 				}
2355 				break;
2356 			}
2357 
2358 			case ID_TXTR: {		//Texture filename list
2359 				int n;
2360 //				char name_buf[128];
2361 
2362 				//mprintf(0,"Got chunk TXTR, len=%d\n",len);
2363 
2364 
2365 				n = cfread_int(fp);
2366 				pm->n_textures = n;
2367 				// Don't overwrite memory!!
2368 				Verify(pm->n_textures <= MAX_MODEL_TEXTURES);
2369 				//mprintf(0,"  num textures = %d\n",n);
2370 				for (i=0; i<n; i++ )
2371 				{
2372 					char tmp_name[256];
2373 					cfread_string_len(tmp_name,127,fp);
2374 					model_load_texture(pm, i, tmp_name);
2375 					//mprintf(0,"<%s>\n",name_buf);
2376 				}
2377 
2378 
2379 				break;
2380 			}
2381 
2382 /*			case ID_IDTA:		//Interpreter data
2383 				//mprintf(0,"Got chunk IDTA, len=%d\n",len);
2384 
2385 				pm->model_data = (ubyte *)vm_malloc(len);
2386 				pm->model_data_size = len;
2387 				Assert(pm->model_data != NULL );
2388 
2389 				cfread(pm->model_data,1,len,fp);
2390 
2391 				break;
2392 */
2393 
2394 			case ID_INFO:		// don't need to do anything with info stuff
2395 
2396 				#ifndef NDEBUG
2397 					pm->debug_info_size = len;
2398 					pm->debug_info = (char *)vm_malloc(pm->debug_info_size+1);
2399 					Assert(pm->debug_info!=NULL);
2400 					memset(pm->debug_info,0,len+1);
2401 					cfread( pm->debug_info, 1, len, fp );
2402 				#endif
2403 				break;
2404 
2405 			case ID_GRID:
2406 				break;
2407 
2408 			case ID_PATH:
2409 				pm->n_paths = cfread_int( fp );
2410 
2411 				if (pm->n_paths <= 0) {
2412 					break;
2413 				}
2414 
2415 				pm->paths = (model_path *)vm_malloc(sizeof(model_path)*pm->n_paths);
2416 				Assert( pm->paths != NULL );
2417 
2418 				memset( pm->paths, 0, sizeof(model_path) * pm->n_paths );
2419 
2420 				for (i=0; i<pm->n_paths; i++ )	{
2421 					cfread_string_len(pm->paths[i].name, MAX_NAME_LEN-1, fp);
2422 
2423 					// check for reused path names... not fatal, but maybe problematic
2424 					for (j = 0; j < i; j++) {
2425 						if (!stricmp(pm->paths[i].name, pm->paths[j].name)) {
2426 							Warning(LOCATION, "Path '%s' in model %s has a name that is not unique!", pm->paths[i].name, pm->filename);
2427 						}
2428 					}
2429 
2430 					if ( pm->version >= 2002 ) {
2431 						// store the sub_model name number of the parent
2432 						cfread_string_len(pm->paths[i].parent_name , MAX_NAME_LEN-1, fp);
2433 						// get rid of leading '$' char in name
2434 						if ( pm->paths[i].parent_name[0] == '$' ) {
2435 							char tmpbuf[MAX_NAME_LEN];
2436 							strcpy_s(tmpbuf, pm->paths[i].parent_name+1);
2437 							strcpy_s(pm->paths[i].parent_name, tmpbuf);
2438 						}
2439 						// store the sub_model index (ie index into pm->submodel) of the parent
2440 						pm->paths[i].parent_submodel = -1;
2441 						for ( j = 0; j < pm->n_models; j++ ) {
2442 							if ( !stricmp( pm->submodel[j].name, pm->paths[i].parent_name) ) {
2443 								pm->paths[i].parent_submodel = j;
2444 							}
2445 						}
2446 					} else {
2447 						pm->paths[i].parent_name[0] = 0;
2448 						pm->paths[i].parent_submodel = -1;
2449 					}
2450 
2451 					pm->paths[i].nverts = cfread_int( fp );
2452 					pm->paths[i].verts = (mp_vert *)vm_malloc( sizeof(mp_vert) * pm->paths[i].nverts );
2453 					pm->paths[i].goal = pm->paths[i].nverts - 1;
2454 					pm->paths[i].type = MP_TYPE_UNUSED;
2455 					pm->paths[i].value = 0;
2456 					Assert(pm->paths[i].verts!=NULL);
2457 					memset( pm->paths[i].verts, 0, sizeof(mp_vert) * pm->paths[i].nverts );
2458 
2459 					for (j=0; j<pm->paths[i].nverts; j++ )	{
2460 						cfread_vector(&pm->paths[i].verts[j].pos,fp );
2461 						pm->paths[i].verts[j].radius = cfread_float( fp );
2462 
2463 						{					// version 1802 added turret stuff
2464 							int nturrets, k;
2465 
2466 							nturrets = cfread_int( fp );
2467 							pm->paths[i].verts[j].nturrets = nturrets;
2468 
2469 							if (nturrets > 0) {
2470 								pm->paths[i].verts[j].turret_ids = (int *)vm_malloc( sizeof(int) * nturrets );
2471 								for ( k = 0; k < nturrets; k++ )
2472 									pm->paths[i].verts[j].turret_ids[k] = cfread_int( fp );
2473 							}
2474 						}
2475 
2476 					}
2477 				}
2478 				break;
2479 
2480 			case ID_EYE:					// an eye position(s)
2481 				{
2482 					int num_eyes;
2483 
2484 					// all eyes points are stored simply as vectors and their normals.
2485 					// 0th element is used as usual player view position.
2486 
2487 					num_eyes = cfread_int( fp );
2488 					pm->n_view_positions = num_eyes;
2489 					Assert ( num_eyes < MAX_EYES );
2490 					for (i = 0; i < num_eyes; i++ ) {
2491 						pm->view_positions[i].parent = cfread_int( fp );
2492 						cfread_vector( &pm->view_positions[i].pnt, fp );
2493 						cfread_vector( &pm->view_positions[i].norm, fp );
2494 					}
2495 				}
2496 				break;
2497 
2498 			case ID_INSG:
2499 				int num_ins, num_verts, num_faces, idx, idx2, idx3;
2500 
2501 				// get the # of insignias
2502 				num_ins = cfread_int(fp);
2503 				pm->num_ins = num_ins;
2504 
2505 				// read in the insignias
2506 				for(idx=0; idx<num_ins; idx++){
2507 					// get the detail level
2508 					pm->ins[idx].detail_level = cfread_int(fp);
2509 					if (pm->ins[idx].detail_level < 0) {
2510 						Warning(LOCATION, "Model '%s': insignia uses an invalid LOD (%i)\n", pm->filename, pm->ins[idx].detail_level);
2511 					}
2512 
2513 					// # of faces
2514 					num_faces = cfread_int(fp);
2515 					pm->ins[idx].num_faces = num_faces;
2516 					Assert(num_faces <= MAX_INS_FACES);
2517 
2518 					// # of vertices
2519 					num_verts = cfread_int(fp);
2520 					Assert(num_verts <= MAX_INS_VECS);
2521 
2522 					// read in all the vertices
2523 					for(idx2=0; idx2<num_verts; idx2++){
2524 						cfread_vector(&pm->ins[idx].vecs[idx2], fp);
2525 					}
2526 
2527 					// read in world offset
2528 					cfread_vector(&pm->ins[idx].offset, fp);
2529 
2530 					// read in all the faces
2531 					for(idx2=0; idx2<pm->ins[idx].num_faces; idx2++){
2532 						// read in 3 vertices
2533 						for(idx3=0; idx3<3; idx3++){
2534 							pm->ins[idx].faces[idx2][idx3] = cfread_int(fp);
2535 							pm->ins[idx].u[idx2][idx3] = cfread_float(fp);
2536 							pm->ins[idx].v[idx2][idx3] = cfread_float(fp);
2537 						}
2538 						vec3d tempv;
2539 
2540 						//get three points (rotated) and compute normal
2541 
2542 						vm_vec_perp(&tempv,
2543 							&pm->ins[idx].vecs[pm->ins[idx].faces[idx2][0]],
2544 							&pm->ins[idx].vecs[pm->ins[idx].faces[idx2][1]],
2545 							&pm->ins[idx].vecs[pm->ins[idx].faces[idx2][2]]);
2546 
2547 						vm_vec_normalize_safe(&tempv);
2548 
2549 						pm->ins[idx].norm[idx2] = tempv;
2550 //						mprintf(("insignorm %.2f %.2f %.2f\n",pm->ins[idx].norm[idx2].xyz.x, pm->ins[idx].norm[idx2].xyz.y, pm->ins[idx].norm[idx2].xyz.z));
2551 
2552 					}
2553 				}
2554 				break;
2555 
2556 			// autocentering info
2557 			case ID_ACEN:
2558 				cfread_vector(&pm->autocenter, fp);
2559 				pm->flags |= PM_FLAG_AUTOCEN;
2560 				break;
2561 
2562 			default:
2563 				mprintf(("Unknown chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len));
2564 				cfseek(fp,len,SEEK_CUR);
2565 				break;
2566 
2567 		}
2568 		cfseek(fp,next_chunk,SEEK_SET);
2569 
2570 		id = cfread_int(fp);
2571 		len = cfread_int(fp);
2572 		next_chunk = cftell(fp) + len;
2573 	}
2574 
2575 	// Now that we've processed all the chunks, resolve the look_at submodels if we have any
2576 	if (!look_at_submodel_names.empty()) {
2577 		for (i = 0; i < pm->n_models; i++) {
2578 			if (pm->submodel[i].look_at_submodel >= 0) {
2579 				const char *submodel_name = look_at_submodel_names[pm->submodel[i].look_at_submodel].c_str();
2580 
2581 				// search for this submodel name among all submodels
2582 				for (j = 0; j < pm->n_models; j++) {
2583 					if (!stricmp(submodel_name, pm->submodel[j].name)) {
2584 						nprintf(("Model", "NOTE: Matched %s %s $look_at: target %s with subobject id %d\n", pm->filename, pm->submodel[i].name, submodel_name, j));
2585 
2586 						// set the correct submodel reference, and set the char* to null as a found-flag
2587 						pm->submodel[i].look_at_submodel = j;
2588 						submodel_name = nullptr;
2589 						break;
2590 					}
2591 				}
2592 
2593 				// certain old models specify the submodel number, so let's maintain compatibilty
2594 				if (submodel_name != nullptr && can_construe_as_integer(submodel_name)) {
2595 					pm->submodel[i].look_at_submodel = atoi(submodel_name);
2596 					submodel_name = nullptr;
2597 				}
2598 
2599 				// did we fail to find it?
2600 				if (submodel_name != nullptr) {
2601 					Warning(LOCATION, "Unable to match %s %s $look_at: target %s with a submodel!\n", pm->filename, pm->submodel[i].name, submodel_name);
2602 					pm->submodel[i].look_at_submodel = -1;
2603 					pm->submodel[i].movement_type = MOVEMENT_TYPE_NONE;
2604 				}
2605 				// are we navel-gazing?
2606 				else if (pm->submodel[i].look_at_submodel == i) {
2607 					Warning(LOCATION, "Matched %s %s $look_at: target with its own submodel!  Submodel cannot look at itself!\n", pm->filename, pm->submodel[i].name);
2608 					pm->submodel[i].look_at_submodel = -1;
2609 					pm->submodel[i].movement_type = MOVEMENT_TYPE_NONE;
2610 				}
2611 			}
2612 		}
2613 	}
2614 
2615 	// And now look through all the submodels and set the model flag if any are intrinsic-rotating
2616 	for (i = 0; i < pm->n_models; i++) {
2617 		if (pm->submodel[i].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
2618 			pm->flags |= PM_FLAG_HAS_INTRINSIC_ROTATE;
2619 			break;
2620 		}
2621 	}
2622 
2623 #ifndef NDEBUG
2624 	if ( ss_fp) {
2625 		int size;
2626 
2627 		cfclose(ss_fp);
2628 		ss_fp = cfopen(debug_name, "rb");
2629 		if ( ss_fp )	{
2630 			size = cfilelength(ss_fp);
2631 			cfclose(ss_fp);
2632 			if ( size <= 0 )	{
2633 				_unlink(debug_name);
2634 			}
2635 		}
2636 	}
2637 #endif
2638 
2639 	cfclose(fp);
2640 
2641 	// mprintf(("Done processing chunks\n"));
2642 	return 1;
2643 }
2644 
2645 //Goober
model_load_texture(polymodel * pm,int i,char * file)2646 void model_load_texture(polymodel *pm, int i, char *file)
2647 {
2648 	// NOTE: it doesn't help to use more than MAX_FILENAME_LEN here as bmpman will use that restriction
2649 	//       we also have to make sure there is always a trailing NUL since overflow doesn't add it
2650 	char tmp_name[MAX_FILENAME_LEN];
2651 	strcpy_s(tmp_name, file);
2652 	strlwr(tmp_name);
2653 
2654 	texture_map *tmap = &pm->maps[i];
2655 	tmap->Clear();
2656 
2657 	//WMC - IMPORTANT!!
2658 	//The Fred_running checks are there so that FRED will see those textures and put them in the
2659 	//texture replacement box.
2660 
2661 	// base maps ---------------------------------------------------------------
2662 	texture_info *tbase = &tmap->textures[TM_BASE_TYPE];
2663 
2664 	if (strstr(tmp_name, "thruster") || strstr(tmp_name, "invisible") || strstr(tmp_name, "warpmap"))
2665 	{
2666 		// Don't load textures for thruster animations or invisible textures
2667 		// or warp models!-Bobboau
2668 		tbase->clear();
2669 	}
2670 	else
2671 	{
2672 		// check if we should be transparent, include "-trans" but make sure to skip anything that might be "-transport"
2673 		if ( (strstr(tmp_name, "-trans") && !strstr(tmp_name, "-transpo")) || strstr(tmp_name, "shockwave") || !strcmp(tmp_name, "nameplate") ) {
2674 			tmap->is_transparent = true;
2675 		}
2676 
2677 		if (strstr(tmp_name, "-amb")) {
2678 			tmap->is_ambient = true;
2679 		}
2680 
2681 		tbase->LoadTexture(tmp_name, pm->filename);
2682 
2683 		if ( tbase->GetTexture() < 0 ) {
2684 			Warning(LOCATION, "Couldn't open texture '%s'\nreferenced by model '%s'\n", tmp_name, pm->filename);
2685 		}
2686 	}
2687 	// -------------------------------------------------------------------------
2688 
2689 	// glow maps ---------------------------------------------------------------
2690 	texture_info *tglow = &tmap->textures[TM_GLOW_TYPE];
2691 	if ( (!Cmdline_glow && !Fred_running) || (tbase->GetTexture() < 0))
2692 	{
2693 		tglow->clear();
2694 	}
2695 	else
2696 	{
2697 		strcpy_s(tmp_name, file);
2698 		strcat_s(tmp_name, "-glow" );
2699 		strlwr(tmp_name);
2700 
2701 		tglow->LoadTexture(tmp_name, pm->filename);
2702 	}
2703 	// -------------------------------------------------------------------------
2704 
2705 	// specular maps -----------------------------------------------------------
2706 	texture_info *tspec = &tmap->textures[TM_SPECULAR_TYPE];
2707 	texture_info *tspecgloss = &tmap->textures[TM_SPEC_GLOSS_TYPE];
2708 	if ( (!Cmdline_spec && !Fred_running) || (tbase->GetTexture() < 0))
2709 	{
2710 		tspec->clear();
2711 		tspecgloss->clear();
2712 	}
2713 	else
2714 	{
2715 		// look for reflectance map
2716 		strcpy_s(tmp_name, file);
2717 		strcat_s(tmp_name, "-reflect");
2718 		strlwr(tmp_name);
2719 
2720 		tspecgloss->LoadTexture(tmp_name, pm->filename);
2721 
2722 		// look for a legacy shine map as well
2723 		strcpy_s(tmp_name, file);
2724 		strcat_s(tmp_name, "-shine");
2725 		strlwr(tmp_name);
2726 
2727 		tspec->LoadTexture(tmp_name, pm->filename);
2728 	}
2729 	//tmap->spec_map.original_texture = tmap->spec_map.texture;
2730 	// -------------------------------------------------------------------------
2731 
2732 	// bump maps ---------------------------------------------------------------
2733 	texture_info *tnorm = &tmap->textures[TM_NORMAL_TYPE];
2734 	if ( (!Cmdline_normal && !Fred_running) || (tbase->GetTexture() < 0) ) {
2735 		tnorm->clear();
2736 	} else {
2737 		strcpy_s(tmp_name, file);
2738 		strcat_s(tmp_name, "-normal");
2739 		strlwr(tmp_name);
2740 
2741 		tnorm->LoadTexture(tmp_name, pm->filename);
2742 	}
2743 
2744 	// try to get a height map too
2745 	texture_info *theight = &tmap->textures[TM_HEIGHT_TYPE];
2746 	if ((!Cmdline_height && !Fred_running) || (tbase->GetTexture() < 0)) {
2747 		theight->clear();
2748 	} else {
2749 		strcpy_s(tmp_name, file);
2750 		strcat_s(tmp_name, "-height");
2751 		strlwr(tmp_name);
2752 
2753 		theight->LoadTexture(tmp_name, pm->filename);
2754 	}
2755 
2756 	// ambient occlusion maps
2757 	texture_info *tambient = &tmap->textures[TM_AMBIENT_TYPE];
2758 
2759 	strcpy_s(tmp_name, file);
2760 	strcat_s(tmp_name, "-ao");
2761 	strlwr(tmp_name);
2762 
2763 	tambient->LoadTexture(tmp_name, pm->filename);
2764 
2765 	// Utility map -------------------------------------------------------------
2766 	texture_info *tmisc = &tmap->textures[TM_MISC_TYPE];
2767 
2768 	strcpy_s(tmp_name, file);
2769 	strcat_s(tmp_name, "-misc");
2770 	strlwr(tmp_name);
2771 
2772 	tmisc->LoadTexture(tmp_name, pm->filename);
2773 
2774 	// -------------------------------------------------------------------------
2775 
2776 	// See if we need to compile a new shader for this material
2777 	int shader_flags = 0;
2778 
2779 	if (tbase->GetTexture() > 0)
2780 		shader_flags |= SDR_FLAG_MODEL_DIFFUSE_MAP;
2781 	if (tglow->GetTexture() > 0 && Cmdline_glow)
2782 		shader_flags |= SDR_FLAG_MODEL_GLOW_MAP;
2783 	if ((tspec->GetTexture() > 0 || tspecgloss->GetTexture() > 0) && Cmdline_spec)
2784 		shader_flags |= SDR_FLAG_MODEL_SPEC_MAP;
2785 	if (tnorm->GetTexture() > 0 && Cmdline_normal)
2786 		shader_flags |= SDR_FLAG_MODEL_NORMAL_MAP;
2787 	if (theight->GetTexture() > 0 && Cmdline_height)
2788 		shader_flags |= SDR_FLAG_MODEL_HEIGHT_MAP;
2789 	if ((tspec->GetTexture() > 0 || tspecgloss->GetTexture() > 0) && Cmdline_env && Cmdline_spec) // No env maps without spec map
2790 		shader_flags |= SDR_FLAG_MODEL_ENV_MAP;
2791 	if (tmisc->GetTexture() > 0)
2792 		shader_flags |= SDR_FLAG_MODEL_MISC_MAP;
2793 	if (tambient->GetTexture() >0)
2794 		shader_flags |= SDR_FLAG_MODEL_AMBIENT_MAP;
2795 
2796 	gr_maybe_create_shader(SDR_TYPE_MODEL, SDR_FLAG_MODEL_SHADOW_MAP);
2797 
2798 	shader_flags |= SDR_FLAG_MODEL_CLIP;
2799 
2800 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED);
2801 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED | SDR_FLAG_MODEL_FOG);
2802 
2803 	shader_flags |= SDR_FLAG_MODEL_DEFERRED;
2804 
2805 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT);
2806 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_FOG);
2807 
2808 	shader_flags &= ~SDR_FLAG_MODEL_DEFERRED;
2809 	shader_flags |= SDR_FLAG_MODEL_TRANSFORM;
2810 
2811 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED);
2812 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED | SDR_FLAG_MODEL_FOG);
2813 
2814 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT);
2815 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_FOG);
2816 
2817 	shader_flags |= SDR_FLAG_MODEL_DEFERRED;
2818 
2819 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED);
2820 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED | SDR_FLAG_MODEL_FOG);
2821 
2822 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT);
2823 	gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_FOG);
2824 }
2825 
2826 //returns the number of this model
model_load(const char * filename,int n_subsystems,model_subsystem * subsystems,int ferror,int duplicate)2827 int model_load(const  char *filename, int n_subsystems, model_subsystem *subsystems, int ferror, int duplicate)
2828 {
2829 	int i, num;
2830 	polymodel *pm = NULL;
2831 
2832 	if ( !model_initted )
2833 		model_init();
2834 
2835 	num = -1;
2836 
2837 	for (i=0; i< MAX_POLYGON_MODELS; i++)	{
2838 		if ( Polygon_models[i] )	{
2839 			if (!stricmp(filename, Polygon_models[i]->filename) && !duplicate)		{
2840 				// Model already loaded; just return.
2841 				Polygon_models[i]->used_this_mission++;
2842 				return Polygon_models[i]->id;
2843 			}
2844 		} else if ( num == -1 )	{
2845 			// This is the first empty slot
2846 			num = i;
2847 		}
2848 	}
2849 
2850 	// No empty slot
2851 	if ( num == -1 )	{
2852 		Error( LOCATION, "Too many models" );
2853 		return -1;
2854 	}
2855 
2856 	TRACE_SCOPE(tracing::LoadModelFile);
2857 
2858 	mprintf(( "Loading model '%s' into slot '%i'\n", filename, num ));
2859 
2860 	pm = new polymodel;
2861 	Polygon_models[num] = pm;
2862 
2863 	pm->n_paths = 0;
2864 	pm->paths = NULL;
2865 
2866 	uint org_sig = static_cast<uint>(Model_signature);
2867 	if ( org_sig + MAX_POLYGON_MODELS > INT_MAX || org_sig + MAX_POLYGON_MODELS < org_sig )	{
2868 		Model_signature = 0; // Overflow
2869 	} else {
2870 		Model_signature+=MAX_POLYGON_MODELS; // No overflow
2871 	}
2872 	Assert( (Model_signature % MAX_POLYGON_MODELS) == 0 );
2873 	pm->id = Model_signature + num;
2874 	Assert( (pm->id % MAX_POLYGON_MODELS) == num );
2875 
2876 	extern int Parse_normal_problem_count;
2877 	Parse_normal_problem_count = 0;
2878 
2879 	pm->used_this_mission = 0;
2880 
2881 #ifndef NDEBUG
2882 	char busy_text[60] = { '\0' };
2883 
2884 	strcat_s( busy_text, "** ModelLoad: " );
2885 	strcat_s( busy_text, filename );
2886 	strcat_s( busy_text, " **" );
2887 
2888 	game_busy(busy_text);
2889 #endif
2890 
2891 	if (read_model_file(pm, filename, n_subsystems, subsystems, ferror) < 0)	{
2892 		if (pm != NULL) {
2893 			delete pm;
2894 		}
2895 
2896 		Polygon_models[num] = NULL;
2897 		return -1;
2898 	}
2899 
2900 	pm->used_this_mission++;
2901 
2902 #ifdef _DEBUG
2903 	if(Fred_running && Parse_normal_problem_count > 0)
2904 	{
2905 		char buffer[100];
2906 		sprintf(buffer,"Serious problem loading model %s, %d normals capped to zero",
2907 			filename, Parse_normal_problem_count);
2908 		os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, buffer);
2909 	}
2910 #endif
2911 
2912 	//=============================
2913 	// Find the destroyed replacement models
2914 
2915 	// Set up the default values
2916 	for (i=0; i<pm->n_models; i++ )	{
2917 		pm->submodel[i].my_replacement = -1;	// assume nothing replaces this
2918 		pm->submodel[i].i_replace = -1;		// assume this doesn't replaces anything
2919 	}
2920 
2921 	// Search for models that have destroyed versions
2922 	for (i=0; i<pm->n_models; i++ )	{
2923 		int j;
2924 		char destroyed_name[128];
2925 
2926 		strcpy_s( destroyed_name, pm->submodel[i].name );
2927 		strcat_s( destroyed_name, "-destroyed" );
2928 		for (j=0; j<pm->n_models; j++ )	{
2929 			if ( !stricmp( pm->submodel[j].name, destroyed_name ))	{
2930 				pm->submodel[i].my_replacement = j;
2931 				pm->submodel[j].i_replace = i;
2932 			}
2933 		}
2934 
2935 		// Search for models with live debris
2936 		// This debris comes from a destroyed subsystem when ship is still alive
2937 		char live_debris_name[128];
2938 
2939 		strcpy_s( live_debris_name, "debris-" );
2940 		strcat_s( live_debris_name, pm->submodel[i].name );
2941 
2942 		pm->submodel[i].num_live_debris = 0;
2943 		for (j=0; j<pm->n_models; j++ ) {
2944 			// check if current model name is substring of destroyed
2945 			if ( strstr( pm->submodel[j].name, live_debris_name ))	{
2946 				mprintf(( "Found live debris model for '%s'\n", pm->submodel[i].name ));
2947 				Assert(pm->submodel[i].num_live_debris < MAX_LIVE_DEBRIS);
2948 				pm->submodel[i].live_debris[pm->submodel[i].num_live_debris++] = j;
2949 				pm->submodel[j].is_live_debris = 1;
2950 
2951 				// make sure live debris doesn't have a parent
2952 				pm->submodel[j].parent = -1;
2953 			}
2954 		}
2955 
2956 	}
2957 
2958 	create_family_tree(pm);
2959 
2960 	// maybe generate vertex buffers
2961 	create_vertex_buffer(pm);
2962 
2963 	//==============================
2964 	// Find all the lower detail versions of the hires model
2965 	for (i=0; i<pm->n_models; i++ )	{
2966 		int j;
2967 		size_t l1;
2968 		bsp_info * sm1 = &pm->submodel[i];
2969 
2970 		sm1->num_details = 0;
2971 		// If a backward compatibility LOD name is declared use it
2972 		if (sm1->lod_name[0] != '\0') {
2973 			l1=strlen(sm1->lod_name);
2974 		}
2975 		// otherwise use the name for LOD comparision
2976 		else {
2977 			l1 = strlen(sm1->name);
2978 		}
2979 
2980 		for (j=0; j<pm->num_debris_objects;j++ )	{
2981 			if ( i == pm->debris_objects[j] )	{
2982 				sm1->is_damaged = true;
2983 			}
2984 		}
2985 
2986 
2987 		for (j=0; j<MAX_MODEL_DETAIL_LEVELS; j++ )	{
2988 			sm1->details[j] = -1;
2989 		}
2990 
2991 		for (j=0; j<pm->n_models; j++ )	{
2992 			bsp_info * sm2 = &pm->submodel[j];
2993 
2994 			if ( i==j ) continue;
2995 
2996 			// if sm2 is a detail of sm1 and sm1 is a high detail, then add it to sm1's list
2997 			if (strlen(sm2->name)!=l1) continue;
2998 
2999 			int ndiff = 0;
3000 			size_t first_diff = 0;
3001 			for ( size_t k=0; k<l1; k++)	{
3002 				// If a backward compatibility LOD name is declared use it
3003 				if (sm1->lod_name[0] != '\0') {
3004 					if (sm1->lod_name[k] != sm2->name[k] )	{
3005 						if (ndiff==0) first_diff = k;
3006 						ndiff++;
3007 					}
3008 				}
3009 				// otherwise do the standard LOD comparision
3010 				else {
3011 					if (sm1->name[k] != sm2->name[k] )	{
3012 						if (ndiff==0) first_diff = k;
3013 						ndiff++;
3014 					}
3015 				}
3016 			}
3017 			if (ndiff==1)	{		// They only differ by one character!
3018 				int dl1, dl2;
3019 				// If a backward compatibility LOD name is declared use it
3020 				if (sm1->lod_name[0] != '\0') {
3021 					dl1 = SCP_tolower(sm1->lod_name[first_diff]) - 'a';
3022 				}
3023 				// otherwise do the standard LOD comparision
3024 				else {
3025 					dl1 = SCP_tolower(sm1->name[first_diff]) - 'a';
3026 				}
3027 				dl2 = SCP_tolower(sm2->name[first_diff]) - 'a';
3028 
3029 				// Handle LODs named "detail0/1/2/etc" too (as opposed to "detaila/b/c/etc")
3030 				if (sm1->parent == -1 && sm2->parent == -1 && !sm1->is_damaged && !sm2->is_damaged && !sm1->is_live_debris && !sm2->is_live_debris) {
3031 					dl2 = dl2 - dl1;
3032 					dl1 = 0;
3033 				}
3034 
3035 				if ( (dl1<0) || (dl2<0) || (dl1>=MAX_MODEL_DETAIL_LEVELS) || (dl2>=MAX_MODEL_DETAIL_LEVELS) ) continue;	// invalid detail levels
3036 
3037 				if ( dl1 == 0 )	{
3038 					dl2--;	// Start from 1 up...
3039 					if (dl2 >= sm1->num_details ) sm1->num_details = dl2+1;
3040 					sm1->details[dl2] = j;
3041   				    mprintf(( "Submodel '%s' is detail level %d of '%s'\n", sm2->name, dl2 + 1, sm1->name ));
3042 				}
3043 			}
3044 		}
3045 
3046 		for (j=0; j<sm1->num_details; j++ )	{
3047 			if ( sm1->details[j] == -1 )	{
3048 				sm1->num_details = 0;
3049 			}
3050 		}
3051 
3052 	}
3053 
3054 
3055 	model_octant_create( pm );
3056 
3057 	TRACE_SCOPE(tracing::ModelParseAllBSPTrees);
3058 
3059 	for (i = 0; i < pm->n_models; ++i) {
3060 		if (!(pm->submodel[i].nocollide_this_only || pm->submodel[i].no_collisions)) {
3061 			pm->submodel[i].collision_tree_index = model_create_bsp_collision_tree();
3062 			bsp_collision_tree* tree             = model_get_bsp_collision_tree(pm->submodel[i].collision_tree_index);
3063 			model_collide_parse_bsp(tree, pm->submodel[i].bsp_data, pm->version);
3064 		}
3065 	}
3066 
3067 	// Find the core_radius... the minimum of
3068 	float rx, ry, rz;
3069 	rx = fl_abs( pm->submodel[pm->detail[0]].max.xyz.x - pm->submodel[pm->detail[0]].min.xyz.x );
3070 	ry = fl_abs( pm->submodel[pm->detail[0]].max.xyz.y - pm->submodel[pm->detail[0]].min.xyz.y );
3071 	rz = fl_abs( pm->submodel[pm->detail[0]].max.xyz.z - pm->submodel[pm->detail[0]].min.xyz.z );
3072 
3073 	pm->core_radius = MIN( rx, MIN(ry, rz) ) / 2.0f;
3074 
3075 	for (i=0; i<pm->n_view_positions; i++ )	{
3076 		if ( pm->view_positions[i].parent == pm->detail[0] )	{
3077 			float d = vm_vec_mag( &pm->view_positions[i].pnt );
3078 
3079 			d += 0.1f;		// Make the eye 1/10th of a meter inside the sphere.
3080 
3081 			if ( d > pm->core_radius )	{
3082 				pm->core_radius = d;
3083 			}
3084 		}
3085 	}
3086 
3087 	// Goober5000 - originally done in ship_create for no apparent reason
3088 	model_set_subsys_path_nums(pm, n_subsystems, subsystems);
3089 	model_set_bay_path_nums(pm);
3090 
3091 	return pm->id;
3092 }
3093 
model_create_instance(bool is_ship,int model_num)3094 int model_create_instance(bool is_ship, int model_num)
3095 {
3096 	int i = 0;
3097 	int open_slot = -1;
3098 
3099 	// go through model instances and find an empty slot
3100 	for ( i = 0; i < (int)Polygon_model_instances.size(); i++) {
3101 		if ( !Polygon_model_instances[i] ) {
3102 			open_slot = i;
3103 		}
3104 	}
3105 
3106 	auto pmi = new polymodel_instance;
3107 
3108 	pmi->model_num = model_num;
3109 
3110 	// if not found, create a slot
3111 	if ( open_slot < 0 ) {
3112 		Polygon_model_instances.push_back( pmi );
3113 		open_slot = (int)(Polygon_model_instances.size() - 1);
3114 	} else {
3115 		Polygon_model_instances[open_slot] = pmi;
3116 	}
3117 	pmi->id = open_slot;
3118 
3119 	polymodel *pm = model_get(model_num);
3120 
3121 	if (pm->n_models > 0)
3122 		pmi->submodel = new submodel_instance[pm->n_models];
3123 
3124 	// add intrinsic_rotation instances if this model is intrinsic-rotating
3125 	if (pm->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
3126 		intrinsic_rotation intrinsic_rotate(is_ship, open_slot);
3127 
3128 		for (i = 0; i < pm->n_models; i++) {
3129 			if (pm->submodel[i].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
3130 				// note: dumb_turn_rate will be 0.0f for look_at
3131 				intrinsic_rotate.add_submodel(i, &pmi->submodel[i], pm->submodel[i].dumb_turn_rate);
3132 			}
3133 		}
3134 
3135 		if (intrinsic_rotate.submodel_list.empty()) {
3136 			Assertion(!intrinsic_rotate.submodel_list.empty(), "This model has the PM_FLAG_HAS_INTRINSIC_ROTATE flag; why doesn't it have an intrinsic-rotating submodel?");
3137 		} else {
3138 			Intrinsic_rotations.push_back(intrinsic_rotate);
3139 		}
3140 	}
3141 
3142 	return open_slot;
3143 }
3144 
model_delete_instance(int model_instance_num)3145 void model_delete_instance(int model_instance_num)
3146 {
3147 	Assert(model_instance_num >= 0);
3148 	Assert(model_instance_num < (int)Polygon_model_instances.size());
3149 	Assert(Polygon_model_instances[model_instance_num] != nullptr);
3150 
3151 	polymodel_instance *pmi = Polygon_model_instances[model_instance_num];
3152 
3153 	if ( pmi->submodel ) {
3154 		delete[] pmi->submodel;
3155 		pmi->submodel = nullptr;
3156 	}
3157 
3158 	delete pmi;
3159 
3160 	Polygon_model_instances[model_instance_num] = nullptr;
3161 
3162 	// delete intrinsic rotations associated with this instance
3163 	for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it) {
3164 		if (intrinsic_it->model_instance_num == model_instance_num) {
3165 			Intrinsic_rotations.erase(intrinsic_it);
3166 			break;
3167 		}
3168 	}
3169 }
3170 
3171 // ensure that the subsys path is at least SUBSYS_PATH_DIST from the
3172 // second last to last point.
model_maybe_fixup_subsys_path(polymodel * pm,int path_num)3173 void model_maybe_fixup_subsys_path(polymodel *pm, int path_num)
3174 {
3175 	vec3d	*v1, *v2, dir;
3176 	float	dist;
3177 	int		index_1, index_2;
3178 
3179 	Assert( (path_num >= 0) && (path_num < pm->n_paths) );
3180 
3181 	model_path *mp;
3182 	mp = &pm->paths[path_num];
3183 
3184 	Assert(mp != NULL);
3185 	if (mp->nverts <= 1 ) {
3186 		Error(LOCATION, "Subsystem Path (%s) Parent (%s) in model (%s) has less than 2 vertices/points!", mp->name, mp->parent_name, pm->filename);
3187 	}
3188 
3189 	index_1 = 1;
3190 	index_2 = 0;
3191 
3192 	v1 = &mp->verts[index_1].pos;
3193 	v2 = &mp->verts[index_2].pos;
3194 
3195 	dist = vm_vec_dist(v1, v2);
3196 	if (dist < (SUBSYS_PATH_DIST - 10))
3197 	{
3198 		vm_vec_normalized_dir(&dir, v2, v1);
3199 		vm_vec_scale_add(v2, v1, &dir, SUBSYS_PATH_DIST);
3200 	}
3201 }
3202 
3203 // fill in the path_num field inside the model_subsystem struct.  This is an index into
3204 // the pm->paths[] array, which is a path that provides a frontal approach to a subsystem
3205 // (used for attacking purposes)
3206 //
3207 // NOTE: path_num in model_subsystem has the follows the following convention:
3208 //			> 0	=> index into pm->paths[] for model that subsystem sits on
3209 //			-1		=> path is not yet determined (may or may not exist)
3210 //			-2		=> path doesn't yet exist for this subsystem
model_set_subsys_path_nums(polymodel * pm,int n_subsystems,model_subsystem * subsystems)3211 void model_set_subsys_path_nums(polymodel *pm, int n_subsystems, model_subsystem *subsystems)
3212 {
3213 	int i, j;
3214 
3215 	for (i = 0; i < n_subsystems; i++)
3216 		subsystems[i].path_num = -1;
3217 
3218 	for (i = 0; i < n_subsystems; i++)
3219 	{
3220 		for (j = 0; j < pm->n_paths; j++)
3221 		{
3222 			if ( ((subsystems[i].subobj_num != -1) && (subsystems[i].subobj_num == pm->paths[j].parent_submodel)) ||
3223 				(!subsystem_stricmp(subsystems[i].subobj_name, pm->paths[j].parent_name)) )
3224 			{
3225 				if (pm->n_paths > j)
3226 				{
3227 					subsystems[i].path_num = j;
3228 					model_maybe_fixup_subsys_path(pm, j);
3229 
3230 					break;
3231 				}
3232 			}
3233 		}
3234 
3235 		// If a path num wasn't located, then set value to -2
3236 		if (subsystems[i].path_num == -1)
3237 			subsystems[i].path_num = -2;
3238 	}
3239 }
3240 
3241 // Determine the path indices (indicies into pm->paths[]) for the paths used for approaching/departing
3242 // a fighter bay on a capital ship.
model_set_bay_path_nums(polymodel * pm)3243 void model_set_bay_path_nums(polymodel *pm)
3244 {
3245 	int i;
3246 
3247 	if (pm->ship_bay != NULL)
3248 	{
3249 		vm_free(pm->ship_bay);
3250 		pm->ship_bay = NULL;
3251 	}
3252 
3253 	/*
3254 	// currently only capital ships have fighter bays
3255 	if ( !(sip->is_big_or_huge()) ) {
3256 		return;
3257 	}
3258 	*/
3259 
3260 	// malloc out storage for the path information
3261 	pm->ship_bay = (ship_bay *) vm_malloc(sizeof(ship_bay));
3262 	Assert(pm->ship_bay != NULL);
3263 
3264 	pm->ship_bay->num_paths = 0;
3265 	// TODO: determine if zeroing out here is affecting any earlier initializations
3266 	pm->ship_bay->arrive_flags = 0;	// bitfield, set to 1 when that path number is reserved for an arrival
3267 	pm->ship_bay->depart_flags = 0;	// bitfield, set to 1 when that path number is reserved for a departure
3268 
3269 	// sanity part 1
3270 	memset(pm->ship_bay->path_indexes, -1, MAX_SHIP_BAY_PATHS * sizeof(int));
3271 
3272 	// iterate through the paths that exist in the polymodel, searching for $bayN pathnames
3273 	bool too_many_paths = false;
3274 	for (i = 0; i < pm->n_paths; i++)
3275 	{
3276 		if (!strnicmp(pm->paths[i].name, NOX("$bay"), 4))
3277 		{
3278 			int bay_num;
3279 			char temp[3];
3280 
3281 			strncpy(temp, pm->paths[i].name + 4, 2);
3282 			temp[2] = 0;
3283 			bay_num = atoi(temp);
3284 
3285 			if (bay_num < 1 || bay_num > MAX_SHIP_BAY_PATHS)
3286 			{
3287 				if(bay_num > MAX_SHIP_BAY_PATHS)
3288 				{
3289 					too_many_paths = true;
3290 				}
3291 				if(bay_num < 1)
3292 				{
3293 					Warning(LOCATION, "Model '%s' bay path '%s' index '%d' has an invalid bay number of %d", pm->filename, pm->paths[i].name, i, bay_num);
3294 				}
3295 				continue;
3296 			}
3297 
3298 			pm->ship_bay->path_indexes[bay_num - 1] = i;
3299 			pm->ship_bay->num_paths++;
3300 		}
3301 	}
3302 	if(too_many_paths)
3303 	{
3304 		Warning(LOCATION, "Model '%s' has too many bay paths - max is %d", pm->filename, MAX_SHIP_BAY_PATHS);
3305 	}
3306 
3307 	// sanity part 2
3308 	for (i = 0; i < pm->ship_bay->num_paths; i++)
3309 	{
3310 		if (pm->ship_bay->path_indexes[i] < 0)
3311 		{
3312 			Warning(LOCATION, "Model '%s' does not have a '$bay%.2d' path specified!  A total of %d bay paths were counted.  Either there is a gap in the path sequence, or a path has a duplicate name.", pm->filename, i + 1, pm->ship_bay->num_paths);
3313 			pm->ship_bay->path_indexes[i] = 0;	// avoid crashes
3314 		}
3315 	}
3316 }
3317 
3318 // Get "parent" submodel for live debris submodel
model_get_parent_submodel_for_live_debris(int model_num,int live_debris_model_num)3319 int model_get_parent_submodel_for_live_debris( int model_num, int live_debris_model_num )
3320 {
3321 	polymodel *pm = model_get(model_num);
3322 
3323 	Assert(pm->submodel[live_debris_model_num].is_live_debris == 1);
3324 
3325 	int mn;
3326 	bsp_info *child;
3327 
3328 	// Start with the high level of detail hull
3329 	// Check all its children until we find the submodel to which the live debris belongs
3330 	child = &pm->submodel[pm->detail[0]];
3331 	mn = child->first_child;
3332 
3333 	while (mn > 0) {
3334 		child = &pm->submodel[mn];
3335 
3336 		if (child->num_live_debris > 0) {
3337 			// check all live debris submodels for the current child
3338 			for (int idx=0; idx<child->num_live_debris; idx++) {
3339 				if (child->live_debris[idx] == live_debris_model_num) {
3340 					return mn;
3341 				}
3342 			}
3343 			// DKA 5/26/99: can multiple live debris subsystems with each ship
3344 			// NO LONGER TRUE Can only be 1 submodel with live debris
3345 			// Error( LOCATION, "Could not find parent submodel for live debris.  Possible model error");
3346 		}
3347 
3348 		// get next child
3349 		mn = child->next_sibling;
3350 	}
3351 	Error( LOCATION, "Could not find parent submodel for live debris");
3352 	return -1;
3353 }
3354 
3355 
model_get_radius(int modelnum)3356 float model_get_radius( int modelnum )
3357 {
3358 	polymodel *pm;
3359 
3360 	pm = model_get(modelnum);
3361 
3362 	return pm->rad;
3363 }
3364 
model_get_core_radius(int modelnum)3365 float model_get_core_radius( int modelnum )
3366 {
3367 	polymodel *pm;
3368 
3369 	pm = model_get(modelnum);
3370 
3371 	return pm->core_radius;
3372 }
3373 
submodel_get_radius(int modelnum,int submodelnum)3374 float submodel_get_radius( int modelnum, int submodelnum )
3375 {
3376 	polymodel *pm;
3377 
3378 	pm = model_get(modelnum);
3379 
3380 	return pm->submodel[submodelnum].rad;
3381 }
3382 
3383 
3384 
model_get(int model_num)3385 polymodel * model_get(int model_num)
3386 {
3387 	if ( model_num < 0 ) {
3388 		Warning(LOCATION, "Invalid model number %d requested. Please post the call stack where an SCP coder can see it.\n", model_num);
3389 		return NULL;
3390 	}
3391 
3392 	int num = model_num % MAX_POLYGON_MODELS;
3393 
3394 	Assertion( num >= 0, "Model id %d is invalid. Please backtrace and investigate.\n", num);
3395 	Assertion( num < MAX_POLYGON_MODELS, "Model id %d is larger than MAX_POLYGON_MODELS (%d). This is impossible, thus we have to conclude that math as we know it has ceased to work.\n", num, MAX_POLYGON_MODELS );
3396 	Assertion( Polygon_models[num], "No model with id %d found. Please backtrace and investigate.\n", num );
3397 	Assertion( Polygon_models[num]->id == model_num, "Index collision between model %s and requested model %d. Please backtrace and investigate.\n", Polygon_models[num]->filename, model_num );
3398 
3399 	if (num < 0 || num > MAX_POLYGON_MODELS || !Polygon_models[num] || Polygon_models[num]->id != model_num)
3400 		return NULL;
3401 
3402 	return Polygon_models[num];
3403 }
3404 
model_get_instance(int model_instance_num)3405 polymodel_instance* model_get_instance(int model_instance_num)
3406 {
3407 	Assert( model_instance_num >= 0 );
3408 	Assert( model_instance_num < (int)Polygon_model_instances.size() );
3409 	if ( model_instance_num < 0 || model_instance_num >= (int)Polygon_model_instances.size() ) {
3410 		return NULL;
3411 	}
3412 
3413 	return Polygon_model_instances[model_instance_num];
3414 }
3415 
3416 // Returns zero is x1,y1,x2,y2 are valid
3417 // returns 1 for invalid model, 2 for point offscreen.
3418 // note that x1,y1,x2,y2 aren't clipped to 2d screen coordinates!
model_find_2d_bound_min(int model_num,matrix * orient,vec3d * pos,int * x1,int * y1,int * x2,int * y2)3419 int model_find_2d_bound_min(int model_num,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3420 {
3421 	polymodel * po;
3422 	int n_valid_pts;
3423 	int i, x,y,min_x, min_y, max_x, max_y;
3424 	int rval = 0;
3425 
3426 	po = model_get(model_num);
3427 
3428 	g3_start_instance_matrix(pos,orient,false);
3429 
3430 	n_valid_pts = 0;
3431 
3432 	int hull = po->detail[0];
3433 
3434 	min_x = min_y = max_x = max_y = 0;
3435 
3436 	for (i=0; i<8; i++ )	{
3437 		vertex pt;
3438 		ubyte flags;
3439 
3440 		flags = g3_rotate_vertex(&pt,&po->submodel[hull].bounding_box[i]);
3441 		if ( !(flags&CC_BEHIND) ) {
3442 			g3_project_vertex(&pt);
3443 
3444 			if (!(pt.flags & PF_OVERFLOW)) {
3445 				x = fl2i(pt.screen.xyw.x);
3446 				y = fl2i(pt.screen.xyw.y);
3447 				if ( n_valid_pts == 0 )	{
3448 					min_x = x;
3449 					min_y = y;
3450 					max_x = x;
3451 					max_y = y;
3452 				} else {
3453 					if ( x < min_x ) min_x = x;
3454 					if ( y < min_y ) min_y = y;
3455 
3456 					if ( x > max_x ) max_x = x;
3457 					if ( y > max_y ) max_y = y;
3458 				}
3459 				n_valid_pts++;
3460 			}
3461 		}
3462 	}
3463 
3464 	if ( n_valid_pts < 8 )	{
3465 		rval = 2;
3466 	}
3467 
3468 	if (x1) *x1 = min_x;
3469 	if (y1) *y1 = min_y;
3470 
3471 	if (x2) *x2 = max_x;
3472 	if (y2) *y2 = max_y;
3473 
3474 	g3_done_instance(false);
3475 
3476 	return rval;
3477 }
3478 
3479 
3480 // Returns zero is x1,y1,x2,y2 are valid
3481 // returns 1 for invalid model, 2 for point offscreen.
3482 // note that x1,y1,x2,y2 aren't clipped to 2d screen coordinates!
submodel_find_2d_bound_min(int model_num,int submodel,matrix * orient,vec3d * pos,int * x1,int * y1,int * x2,int * y2)3483 int submodel_find_2d_bound_min(int model_num,int submodel, matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3484 {
3485 	polymodel * po;
3486 	int n_valid_pts;
3487 	int i, x,y,min_x, min_y, max_x, max_y;
3488 	bsp_info * sm;
3489 
3490 	po = model_get(model_num);
3491 	if ( (submodel < 0) || (submodel >= po->n_models ) ) return 1;
3492 	sm = &po->submodel[submodel];
3493 
3494 	g3_start_instance_matrix(pos,orient,false);
3495 
3496 	n_valid_pts = 0;
3497 
3498 	min_x = min_y = max_x = max_y = 0;
3499 
3500 	for (i=0; i<8; i++ )	{
3501 		vertex pt;
3502 		ubyte flags;
3503 
3504 		flags = g3_rotate_vertex(&pt,&sm->bounding_box[i]);
3505 		if ( !(flags&CC_BEHIND) ) {
3506 			g3_project_vertex(&pt);
3507 
3508 			if (!(pt.flags & PF_OVERFLOW)) {
3509 				x = fl2i(pt.screen.xyw.x);
3510 				y = fl2i(pt.screen.xyw.y);
3511 				if ( n_valid_pts == 0 )	{
3512 					min_x = x;
3513 					min_y = y;
3514 					max_x = x;
3515 					max_y = y;
3516 				} else {
3517 					if ( x < min_x ) min_x = x;
3518 					if ( y < min_y ) min_y = y;
3519 
3520 					if ( x > max_x ) max_x = x;
3521 					if ( y > max_y ) max_y = y;
3522 				}
3523 				n_valid_pts++;
3524 			}
3525 		}
3526 	}
3527 
3528 	if ( n_valid_pts == 0 )	{
3529 		return 2;
3530 	}
3531 
3532 	if (x1) *x1 = min_x;
3533 	if (y1) *y1 = min_y;
3534 
3535 	if (x2) *x2 = max_x;
3536 	if (y2) *y2 = max_y;
3537 
3538 	g3_done_instance(false);
3539 
3540 	return 0;
3541 }
3542 
3543 /**
3544  * Find 2D bound for sub object
3545  *
3546  * Note that x1,y1,x2,y2 aren't clipped to 2D screen coordinates.
3547  *
3548  * Calculates the focal length of the camera, and uses the law of similar
3549  * triangles to project the subsystem's radius to the screen.
3550  *
3551  * @return zero if x1,y1,x2,y2 are valid
3552  * @return 2 for point offscreen
3553  */
subobj_find_2d_bound(float radius,matrix *,vec3d * pos,int * x1,int * y1,int * x2,int * y2)3554 int subobj_find_2d_bound(float radius ,matrix * /*orient*/, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3555 {
3556 	float w,h,focal_length;
3557 	vertex pnt;
3558 
3559 	g3_rotate_vertex(&pnt,pos);
3560 
3561 	if ( pnt.flags & CC_BEHIND )
3562 		return 2;
3563 
3564 	if (!(pnt.flags&PF_PROJECTED))
3565 		g3_project_vertex(&pnt);
3566 
3567 	if (pnt.flags & PF_OVERFLOW)
3568 		return 2;
3569 
3570 	focal_length = Canv_h2 * Matrix_scale.xyz.y;
3571 	h = radius * focal_length / pnt.world.xyz.z;
3572 
3573 	w = h;
3574 
3575 	if (x1) *x1 = fl2i(pnt.screen.xyw.x - w);
3576 	if (y1) *y1 = fl2i(pnt.screen.xyw.y - h);
3577 
3578 	if (x2) *x2 = fl2i(pnt.screen.xyw.x + w);
3579 	if (y2) *y2 = fl2i(pnt.screen.xyw.y + h);
3580 
3581 	return 0;
3582 }
3583 
3584 
3585 // Given a rotating submodel, find the local and world axes of rotation.
model_get_rotating_submodel_axis(vec3d * model_axis,vec3d * world_axis,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,matrix * objorient)3586 void model_get_rotating_submodel_axis(vec3d *model_axis, vec3d *world_axis, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, matrix *objorient)
3587 {
3588 	Assert(pm->id == pmi->model_num);
3589 	bsp_info *sm = &pm->submodel[submodel_num];
3590 	Assert(sm->movement_type == MOVEMENT_TYPE_ROT || sm->movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE);
3591 
3592 	*model_axis = sm->movement_axis;
3593 	model_instance_find_world_dir(world_axis, model_axis, pm, pmi, submodel_num, objorient);
3594 }
3595 
3596 // Normalize the submodel angle and convert float angle to angles struct
submodel_canonicalize(bsp_info * sm,submodel_instance * smi,bool clamp)3597 void submodel_canonicalize(bsp_info *sm, submodel_instance *smi, bool clamp)
3598 {
3599 	if (clamp)
3600 	{
3601 		// normalize the angle so that we are within a valid range:
3602 		//  greater than or equal to 0
3603 		//  less than PI2
3604 		while (smi->cur_angle > PI2)
3605 			smi->cur_angle -= PI2;
3606 		while (smi->cur_angle < 0.0f)
3607 			smi->cur_angle += PI2;
3608 	}
3609 
3610 	// get the matrix and the angles
3611 	switch (sm->movement_axis_id)
3612 	{
3613 		case MOVEMENT_AXIS_X:
3614 		{
3615 			angles angs = vmd_zero_angles;
3616 			angs.p = smi->cur_angle;
3617 			vm_angles_2_matrix(&smi->canonical_orient, &angs);
3618 			break;
3619 		}
3620 
3621 		case MOVEMENT_AXIS_Y:
3622 		{
3623 			angles angs = vmd_zero_angles;
3624 			angs.h = smi->cur_angle;
3625 			vm_angles_2_matrix(&smi->canonical_orient, &angs);
3626 			break;
3627 		}
3628 
3629 		case MOVEMENT_AXIS_Z:
3630 		{
3631 			angles angs = vmd_zero_angles;
3632 			angs.b = smi->cur_angle;
3633 			vm_angles_2_matrix(&smi->canonical_orient, &angs);
3634 			break;
3635 		}
3636 
3637 		default:
3638 			vm_quaternion_rotate(&smi->canonical_orient, smi->cur_angle, &sm->movement_axis);
3639 			break;
3640 	}
3641 }
3642 
3643 // Does stepped rotation of a submodel
submodel_stepped_rotate(model_subsystem * psub,submodel_instance * smi)3644 void submodel_stepped_rotate(model_subsystem *psub, submodel_instance *smi)
3645 {
3646 	Assert(psub->flags[Model::Subsystem_Flags::Stepped_rotate]);
3647 
3648 	if ( psub->subobj_num < 0 ) return;
3649 
3650 	polymodel *pm = model_get(psub->model_num);
3651 	bsp_info *sm = &pm->submodel[psub->subobj_num];
3652 
3653 	if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
3654 
3655 	// get active rotation time this frame
3656 	int end_stamp = timestamp();
3657 	// just to make sure this issue wont pop up again... might cause odd jerking in some extremely odd situations
3658 	// but given that those issues would require the timer to be reseted in any case it probably wont hurt
3659 	float rotation_time;
3660 	if ((end_stamp - smi->step_zero_timestamp) < 0) {
3661 		smi->step_zero_timestamp = end_stamp;
3662 		rotation_time = 0.0f;
3663 	} else {
3664 		rotation_time = 0.001f * (end_stamp - smi->step_zero_timestamp);
3665 	}
3666 	//Assert(rotation_time >= 0);
3667 
3668 	// save last angles
3669 	smi->prev_angle = smi->cur_angle;
3670 	smi->canonical_prev_orient = smi->canonical_orient;
3671 
3672 	// angular displacement of one step
3673 	float step_size = (PI2 / psub->stepped_rotation->num_steps);
3674 
3675 	// get time to complete one step, including pause
3676 	float step_time = psub->stepped_rotation->t_transit + psub->stepped_rotation->t_pause;
3677 
3678 	// cur_step is step number relative to zero (0 - num_steps)
3679 	// step_offset_time is TIME into current step
3680 	float step_offset_time = (float)fmod(rotation_time, step_time);
3681 	// subtract off fractional step part, round up  (ie, 1.999999 -> 2)
3682 	int cur_step = (int)std::lround((rotation_time - step_offset_time) / step_time);
3683 	// mprintf(("cur step %d\n", cur_step));
3684 	// Assert(step_offset_time >= 0);
3685 
3686 	if (cur_step >= psub->stepped_rotation->num_steps) {
3687 		// I don;t know why, but removing this line makes it all good.
3688 		// sii->step_zero_timestamp += int(1000.0f * (psub->stepped_rotation->num_steps * step_time) + 0.5f);
3689 
3690 		// reset cur_step (use mod to handle physics/ai pause)
3691 		cur_step = cur_step % psub->stepped_rotation->num_steps;
3692 	}
3693 
3694 	// get base angle
3695 	smi->cur_angle = cur_step * step_size;
3696 
3697 	// determine which phase of rotation we're in
3698 	float coast_start_time = psub->stepped_rotation->fraction * psub->stepped_rotation->t_transit;
3699 	float decel_start_time = psub->stepped_rotation->t_transit * (1.0f - psub->stepped_rotation->fraction);
3700 	float pause_start_time = psub->stepped_rotation->t_transit;
3701 
3702 	float start_coast_angle = 0.5f * psub->stepped_rotation->max_turn_accel * coast_start_time * coast_start_time;
3703 
3704 	if (step_offset_time < coast_start_time) {
3705 		// do accel
3706 		float accel_time = step_offset_time;
3707 		smi->cur_angle += 0.5f * psub->stepped_rotation->max_turn_accel * accel_time * accel_time;
3708 		smi->current_turn_rate = psub->stepped_rotation->max_turn_accel * accel_time;
3709 	} else if (step_offset_time < decel_start_time) {
3710 		// do coast
3711 		float coast_time = step_offset_time - coast_start_time;
3712 		smi->cur_angle += start_coast_angle + psub->stepped_rotation->max_turn_rate * coast_time;
3713 		smi->current_turn_rate = psub->stepped_rotation->max_turn_rate;
3714 	} else if (step_offset_time < pause_start_time) {
3715 		// do decel
3716 		float time_to_pause = psub->stepped_rotation->t_transit - step_offset_time;
3717 		smi->cur_angle += (step_size - 0.5f * psub->stepped_rotation->max_turn_accel * time_to_pause * time_to_pause);
3718 		smi->current_turn_rate = psub->stepped_rotation->max_turn_rate * time_to_pause;
3719 	} else {
3720 		// do pause
3721 		smi->cur_angle += step_size;
3722 		smi->current_turn_rate = 0.0f;
3723 	}
3724 
3725 	submodel_canonicalize(sm, smi, true);
3726 }
3727 
3728 // Instantly rotate a submodel (around its axis of rotation) so that it is oriented toward its look_at_submodel.
3729 // Uses the same pointing logic as in model_rotate_gun
submodel_look_at(polymodel * pm,polymodel_instance * pmi,int submodel_num)3730 void submodel_look_at(polymodel *pm, polymodel_instance *pmi, int submodel_num)
3731 {
3732 	vec3d world_axis, world_pos, dst, planar_dst, dir, rotated_vec;
3733 
3734 	auto sm = &pm->submodel[submodel_num];
3735 	auto smi = &pmi->submodel[submodel_num];
3736 
3737 	Assert(sm->movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE);
3738 	Assert(sm->look_at_submodel >= 0);
3739 
3740 	// save last angles
3741 	smi->prev_angle = smi->cur_angle;
3742 	smi->canonical_prev_orient = smi->canonical_orient;
3743 
3744 	//------------
3745 	// Calculate the destination point in world coordinates
3746 	model_instance_find_world_point(&dst, &vmd_zero_vector, pm, pmi, sm->look_at_submodel, &vmd_identity_matrix, &vmd_zero_vector);
3747 
3748 	//------------
3749 	// Project the destination point onto the submodel base plane
3750 	model_instance_find_world_dir(&world_axis, &sm->movement_axis, pm, pmi, sm->parent, &vmd_identity_matrix);
3751 	model_instance_find_world_point(&world_pos, &vmd_zero_vector, pm, pmi, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
3752 
3753 	vm_project_point_onto_plane(&planar_dst, &dst, &world_axis, &world_pos);
3754 
3755 	//------------
3756 	// Calculate angle to rotate towards projected point
3757 	model_instance_find_world_dir(&rotated_vec, &sm->frame_of_reference.vec.fvec, pm, pmi, sm->parent, &vmd_identity_matrix);
3758 	vm_vec_sub(&dir, &planar_dst, &world_pos);
3759 	vm_vec_normalize(&dir);
3760 	smi->cur_angle = vm_vec_delta_ang_norm(&rotated_vec, &dir, &world_axis);
3761 
3762 	// apply an offset to the angle, since the direction we look at may be different than the default orientation!
3763 	// if we have not specified an offset in the POF, assume that the very first time we call submodel_look_at, the submodel is pointing in the correct direction
3764 	if (sm->look_at_offset < 0.0f)
3765 	{
3766 		sm->look_at_offset = -(smi->cur_angle);
3767 
3768 		// ensure the offset is in the proper range (see submodel_canonicalize)
3769 		while (sm->look_at_offset > PI2)
3770 			sm->look_at_offset -= PI2;
3771 		while (sm->look_at_offset < 0.0f)
3772 			sm->look_at_offset += PI2;
3773 	}
3774 	smi->cur_angle += sm->look_at_offset;
3775 
3776 	// calculate turn rate
3777 	// (try to avoid a one-frame dramatic spike in the turn rate if the angle passes 0.0 or PI2)
3778 	if (abs(smi->cur_angle - smi->prev_angle) < PI)
3779 		smi->current_turn_rate = smi->desired_turn_rate = (smi->cur_angle - smi->prev_angle) / flFrametime;
3780 
3781 	// and now set the other submodel fields
3782 	submodel_canonicalize(sm, smi, true);
3783 }
3784 
3785 // Rotates the angle of a submodel, when the submodel has a subsystem (which is almost always the case)
submodel_rotate(model_subsystem * psub,submodel_instance * smi)3786 void submodel_rotate(model_subsystem *psub, submodel_instance *smi)
3787 {
3788 	bsp_info * sm;
3789 
3790 	if ( psub->subobj_num < 0 ) return;
3791 
3792 	polymodel *pm = model_get(psub->model_num);
3793 	sm = &pm->submodel[psub->subobj_num];
3794 
3795 	if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
3796 
3797 	submodel_rotate(sm, smi);
3798 }
3799 
3800 // Rotates the angle of a submodel.  If the submodel has a subsystem, the execution flow should first go through the other
3801 // submodel_rotate function before this one.  (This function is called directly in the case of dumb_rotation.)
submodel_rotate(bsp_info * sm,submodel_instance * smi)3802 void submodel_rotate(bsp_info *sm, submodel_instance *smi)
3803 {
3804 	// save last angles
3805 	smi->prev_angle = smi->cur_angle;
3806 	smi->canonical_prev_orient = smi->canonical_orient;
3807 
3808 	// probably send in a calculated desired turn rate
3809 	float diff = smi->desired_turn_rate - smi->current_turn_rate;
3810 
3811 	float final_turn_rate;
3812 	if (diff > 0) {
3813 		final_turn_rate = smi->current_turn_rate + smi->turn_accel * flFrametime;
3814 		if (final_turn_rate > smi->desired_turn_rate) {
3815 			final_turn_rate = smi->desired_turn_rate;
3816 		}
3817 	} else if (diff < 0) {
3818 		final_turn_rate = smi->current_turn_rate - smi->turn_accel * flFrametime;
3819 		if (final_turn_rate < smi->desired_turn_rate) {
3820 			final_turn_rate = smi->desired_turn_rate;
3821 		}
3822 	} else {
3823 		final_turn_rate = smi->desired_turn_rate;
3824 	}
3825 
3826 	float delta = (smi->current_turn_rate + final_turn_rate) * 0.5f * flFrametime;
3827 	smi->current_turn_rate = final_turn_rate;
3828 
3829 	// Apply rotation in the axis of movement
3830 	smi->cur_angle += delta;
3831 
3832 	submodel_canonicalize(sm, smi, true);
3833 }
3834 
3835 // Tries to move joints so that the turret points to the point dst.
3836 // turret1 is the angles of the turret, turret2 is the angles of the gun from turret
3837 //	Returns 1 if rotated gun, 0 if no gun to rotate (rotation handled by AI)
model_rotate_gun(object * objp,polymodel * pm,polymodel_instance * pmi,ship_subsys * ss,vec3d * dst,bool reset)3838 int model_rotate_gun(object *objp, polymodel *pm, polymodel_instance *pmi, ship_subsys *ss, vec3d *dst, bool reset)
3839 {
3840 	model_subsystem *turret = ss->system_info;
3841 
3842 	// This should not happen
3843 	if ( turret->turret_gun_sobj < 0 || turret->subobj_num == turret->turret_gun_sobj ) {
3844 		return 0;
3845 	}
3846 
3847 	auto base_sm = &pm->submodel[turret->subobj_num];
3848 	auto gun_sm = &pm->submodel[turret->turret_gun_sobj];
3849 
3850 	auto base_smi = &pmi->submodel[turret->subobj_num];
3851 	auto gun_smi = &pmi->submodel[turret->turret_gun_sobj];
3852 
3853 	bool limited_base_rotation = false;
3854 
3855 	// Check for a valid turret
3856 	Assert( turret->turret_num_firing_points > 0 );
3857 	// Check for a valid subsystem
3858 	Assert( ss != NULL );
3859 
3860 	// Find the heading and pitch that the gun needs to turn to
3861 	float desired_base_angle, desired_gun_angle;
3862 
3863 	if (!reset) {
3864 		vec3d world_axis, world_pos, planar_dst, dir, rotated_vec;
3865 		matrix save_base_orient;
3866 
3867 		// NOTE: this code assumes that the turret's fvec is where the base should point and the uvec is where the gun should point
3868 
3869 		//------------
3870 		// Project the destination point onto the turret base plane
3871 		model_instance_find_world_dir(&world_axis, &base_sm->movement_axis, pm, pmi, base_sm->parent, &objp->orient);
3872 		model_instance_find_world_point(&world_pos, &vmd_zero_vector, pm, pmi, turret->subobj_num, &objp->orient, &objp->pos);
3873 
3874 		vm_project_point_onto_plane(&planar_dst, dst, &world_axis, &world_pos);
3875 
3876 		//------------
3877 		// Calculate base angle to rotate towards projected point
3878 		model_instance_find_world_dir(&rotated_vec, &base_sm->frame_of_reference.vec.fvec, pm, pmi, base_sm->parent, &objp->orient);
3879 		vm_vec_sub(&dir, &planar_dst, &world_pos);
3880 		vm_vec_normalize(&dir);
3881 		desired_base_angle = vm_vec_delta_ang_norm(&rotated_vec, &dir, &world_axis);
3882 
3883 		//------------
3884 		// Pretend the base is pointing directly at the target
3885 		save_base_orient = base_smi->canonical_orient;
3886 		vm_quaternion_rotate(&base_smi->canonical_orient, desired_base_angle, &base_sm->movement_axis);
3887 
3888 		//------------
3889 		// Project the destination point onto the turret gun plane with the base in the desired orientation
3890 		// NOTE: the rotation axis is given in the model's reference frame, so it needs to be rotated when the base is rotated
3891 		model_instance_find_world_dir(&world_axis, &gun_sm->movement_axis, pm, pmi, gun_sm->parent, &objp->orient);
3892 		model_instance_find_world_point(&world_pos, &vmd_zero_vector, pm, pmi, turret->turret_gun_sobj, &objp->orient, &objp->pos);
3893 
3894 		vm_project_point_onto_plane(&planar_dst, dst, &world_axis, &world_pos);
3895 
3896 		//------------
3897 		// Calculate gun angle to rotate towards projected point
3898 		model_instance_find_world_dir(&rotated_vec, &gun_sm->frame_of_reference.vec.uvec, pm, pmi, gun_sm->parent, &objp->orient);
3899 		vm_vec_sub(&dir, &planar_dst, &world_pos);
3900 		vm_vec_normalize(&dir);
3901 		desired_gun_angle = vm_vec_delta_ang_norm(&rotated_vec, &dir, &world_axis);
3902 		// for ventral turrets without custom matrixes
3903 		if (vm_vec_dot(&gun_sm->frame_of_reference.vec.uvec, &turret->turret_norm) < 0.0f) {
3904 			desired_gun_angle = PI + desired_gun_angle;
3905 		}
3906 
3907 		//------------
3908 		// Restore the base
3909 		base_smi->canonical_orient = save_base_orient;
3910 
3911 	} else {
3912 		desired_base_angle = base_smi->turret_idle_angle;
3913 		desired_gun_angle = 0.0f;
3914 
3915 		if ((turret->subobj_num != turret->turret_gun_sobj)) {
3916 			desired_gun_angle = gun_smi->turret_idle_angle;
3917 		}
3918 	}
3919 
3920 	if (turret->flags[Model::Subsystem_Flags::Turret_restricted_fov])
3921 		limited_base_rotation = true;
3922 
3923 	//------------
3924 	// Gradually turn the turret towards the desired angles
3925 	float step_size = turret->turret_turning_rate * flFrametime;
3926 	float base_delta, gun_delta;
3927 
3928 	if (reset)
3929 		step_size /= 3.0f;
3930 	else
3931 		ss->rotation_timestamp = timestamp(turret->turret_reset_delay);
3932 
3933 	base_delta = vm_interp_angle(&base_smi->cur_angle, desired_base_angle, step_size, limited_base_rotation);
3934 	gun_delta = vm_interp_angle(&gun_smi->cur_angle, desired_gun_angle, step_size);
3935 
3936 	submodel_canonicalize(base_sm, base_smi, true);
3937 	submodel_canonicalize(gun_sm, gun_smi, true);
3938 
3939 	//------------
3940 	// Set fields for turret rotation sounds
3941 
3942 	ss->base_rotation_rate_pct = 0.0f;
3943 	ss->gun_rotation_rate_pct = 0.0f;
3944 
3945 	if (turret->turret_base_rotation_snd.isValid())
3946 	{
3947 		if (step_size > 0)
3948 		{
3949 			base_delta = (float) (fabs(base_delta)) / step_size;
3950 			if (base_delta > 1.0f)
3951 				base_delta = 1.0f;
3952 			ss->base_rotation_rate_pct = base_delta;
3953 		}
3954 	}
3955 
3956 	if (turret->turret_gun_rotation_snd.isValid())
3957 	{
3958 		if (step_size > 0)
3959 		{
3960 			gun_delta = (float) (fabs(gun_delta)) / step_size;
3961 			if (gun_delta > 1.0f)
3962 				gun_delta = 1.0f;
3963 			ss->gun_rotation_rate_pct = gun_delta;
3964 		}
3965 	}
3966 
3967 //	base_angles->h -= step_size*(key_down_timef(KEY_1)-key_down_timef(KEY_2) );
3968 //	gun_angles->p += step_size*(key_down_timef(KEY_3)-key_down_timef(KEY_4) );
3969 
3970 	if (turret->flags[Model::Subsystem_Flags::Fire_on_target])
3971 	{
3972 		base_delta = vm_delta_from_interp_angle(base_smi->cur_angle, desired_base_angle);
3973 		gun_delta = vm_delta_from_interp_angle(gun_smi->cur_angle, desired_gun_angle);
3974 		ss->points_to_target = sqrt((base_delta*base_delta) + (gun_delta*gun_delta));
3975 	}
3976 
3977 	return 1;
3978 }
3979 
3980 
3981 // Goober5000
3982 // For a submodel, return its overall offset from the main model.
model_find_submodel_offset(vec3d * outpnt,const polymodel * pm,int submodel_num)3983 void model_find_submodel_offset(vec3d *outpnt, const polymodel *pm, int submodel_num)
3984 {
3985 	vm_vec_zero(outpnt);
3986 	int mn = submodel_num;
3987 
3988 	//instance up the tree for this point
3989 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
3990 		vm_vec_add2(outpnt, &pm->submodel[mn].offset);
3991 
3992 		mn = pm->submodel[mn].parent;
3993 	}
3994 }
3995 
model_find_world_point(vec3d * outpnt,vec3d * mpnt,int model_num,int submodel_num,const matrix * objorient,const vec3d * objpos)3996 void model_find_world_point(vec3d *outpnt, vec3d *mpnt, int model_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
3997 {
3998 	return model_find_world_point(outpnt, mpnt, model_get(model_num), submodel_num, objorient, objpos);
3999 }
4000 
4001 // Given a point (pnt) that is in submodel_num's frame of
4002 // reference, and given the object's orient and position,
4003 // return the point in 3-space in outpnt.
model_find_world_point(vec3d * outpnt,vec3d * mpnt,const polymodel * pm,int submodel_num,const matrix * objorient,const vec3d * objpos)4004 void model_find_world_point(vec3d *outpnt, vec3d *mpnt, const polymodel *pm, int submodel_num, const matrix *objorient, const vec3d *objpos)
4005 {
4006 	vec3d pnt;
4007 	int mn;
4008 
4009 	pnt = *mpnt;
4010 	mn = submodel_num;
4011 
4012 	//instance up the tree for this point
4013 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4014 		// the angles in non-instanced models are always zero, so no need to rotate
4015 		vm_vec_add2(&pnt, &pm->submodel[mn].offset);
4016 
4017 		mn = pm->submodel[mn].parent;
4018 	}
4019 
4020 	//now instance for the entire object
4021 	vm_vec_unrotate(outpnt,&pnt,objorient);
4022 	vm_vec_add2(outpnt,objpos);
4023 }
4024 
model_instance_find_world_point(vec3d * outpnt,vec3d * mpnt,int model_instance_num,int submodel_num,const matrix * objorient,const vec3d * objpos)4025 void model_instance_find_world_point(vec3d *outpnt, vec3d *mpnt, int model_instance_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
4026 {
4027 	auto pmi = model_get_instance(model_instance_num);
4028 	auto pm = model_get(pmi->model_num);
4029 	return model_instance_find_world_point(outpnt, mpnt, pm, pmi, submodel_num, objorient, objpos);
4030 }
4031 
model_instance_find_world_point(vec3d * outpnt,vec3d * mpnt,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,const matrix * objorient,const vec3d * objpos)4032 void model_instance_find_world_point(vec3d *outpnt, vec3d *mpnt, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *objorient, const vec3d *objpos)
4033 {
4034 	vec3d pnt;
4035 	vec3d tpnt;
4036 	int mn;
4037 	Assert(pm->id == pmi->model_num);
4038 
4039 	pnt = *mpnt;
4040 	mn = submodel_num;
4041 
4042 	//instance up the tree for this point
4043 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4044 		vm_vec_unrotate(&tpnt, &pnt, &pmi->submodel[mn].canonical_orient);
4045 		vm_vec_add(&pnt, &tpnt, &pm->submodel[mn].offset);
4046 
4047 		mn = pm->submodel[mn].parent;
4048 	}
4049 
4050 	//now instance for the entire object
4051 	vm_vec_unrotate(outpnt,&pnt,objorient);
4052 	vm_vec_add2(outpnt,objpos);
4053 }
4054 
world_find_model_instance_point(vec3d * out,vec3d * world_pt,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,const matrix * orient,const vec3d * pos)4055 void world_find_model_instance_point(vec3d *out, vec3d *world_pt, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *orient, const vec3d *pos)
4056 {
4057 	Assert(pm->id == pmi->model_num);
4058 	Assert( (pm->submodel[submodel_num].parent == pm->detail[0]) || (pm->submodel[submodel_num].parent == -1) );
4059 
4060 	vec3d tempv1, tempv2;
4061 
4062 	// get into ship RF
4063 	vm_vec_sub(&tempv1, world_pt, pos);
4064 	vm_vec_rotate(&tempv2, &tempv1, orient);
4065 
4066 	if (pm->submodel[submodel_num].parent == -1) {
4067 		*out  = tempv2;
4068 		return;
4069 	}
4070 
4071 	// put into submodel RF
4072 	vm_vec_sub2(&tempv2, &pm->submodel[submodel_num].offset);
4073 	vm_vec_rotate(out, &tempv2, &pmi->submodel[submodel_num].canonical_orient);
4074 }
4075 
4076 /**
4077  * Finds the current location of a submodel (in the ship's frame of reference),
4078  * taking into account the rotations of any parent submodels it might have.
4079  *
4080  * @param *outpnt Output point
4081  * @param model_instance_num Index into Polygon_model_instances
4082  * @param submodel_num The number of the submodel we're interested in
4083  */
find_submodel_instance_point(vec3d * outpnt,const polymodel * pm,const polymodel_instance * pmi,int submodel_num)4084 void find_submodel_instance_point(vec3d *outpnt, const polymodel *pm, const polymodel_instance *pmi, int submodel_num)
4085 {
4086 	Assert(pm->id == pmi->model_num);
4087 	vm_vec_zero(outpnt);
4088 
4089 	int mn = submodel_num;
4090 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4091 		vec3d offset = pm->submodel[mn].offset;
4092 
4093 		int parent_mn = pm->submodel[mn].parent;
4094 
4095 		if (pm->submodel[parent_mn].can_move) {
4096 			vec3d tvec = offset;
4097 			vm_vec_unrotate(&offset, &tvec, &pmi->submodel[parent_mn].canonical_orient);
4098 		}
4099 
4100 		vm_vec_add2(outpnt, &offset);
4101 
4102 		mn = parent_mn;
4103 	}
4104 }
4105 
4106 /**
4107  * Finds the current location and rotation (in the ship's frame of reference) of
4108  * a submodel point, taking into account the rotations of the submodel and any
4109  * parent submodels it might have.
4110  *
4111  * @param *outpnt Output point
4112  * @param *outnorm Output normal
4113  * @param model_instance_num Index into Polygon_model_instances
4114  * @param submodel_num The number of the submodel we're interested in
4115  * @param *submodel_pnt The point which's current position we want, in the submodel's frame of reference
4116  * @param *submodel_norm The normal which's current direction we want, in the ship's frame of reference
4117  */
find_submodel_instance_point_normal(vec3d * outpnt,vec3d * outnorm,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,const vec3d * submodel_pnt,const vec3d * submodel_norm)4118 void find_submodel_instance_point_normal(vec3d *outpnt, vec3d *outnorm, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const vec3d *submodel_pnt, const vec3d *submodel_norm)
4119 {
4120 	Assert(pm->id == pmi->model_num);
4121 	*outnorm = *submodel_norm;
4122 	vm_vec_zero(outpnt);
4123 
4124 	int mn = submodel_num;
4125 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4126 		vec3d offset = pm->submodel[mn].offset;
4127 
4128 		if ( mn == submodel_num) {
4129 			vec3d submodel_pnt_offset = *submodel_pnt;
4130 
4131 			vec3d tvec = submodel_pnt_offset;
4132 			vm_vec_unrotate(&submodel_pnt_offset, &tvec, &pmi->submodel[mn].canonical_orient);
4133 
4134 			vec3d tnorm = *outnorm;
4135 			vm_vec_unrotate(outnorm, &tnorm, &pmi->submodel[mn].canonical_orient);
4136 
4137 			vm_vec_add2(&offset, &submodel_pnt_offset);
4138 		}
4139 
4140 		int parent_mn = pm->submodel[mn].parent;
4141 
4142 		vec3d tvec = offset;
4143 		vm_vec_unrotate(&offset, &tvec, &pmi->submodel[parent_mn].canonical_orient);
4144 
4145 		vec3d tnorm = *outnorm;
4146 		vm_vec_unrotate(outnorm, &tnorm, &pmi->submodel[parent_mn].canonical_orient);
4147 
4148 		vm_vec_add2(outpnt, &offset);
4149 
4150 		mn = parent_mn;
4151 	}
4152 }
4153 
4154 /**
4155  * Same as find_submodel_instance_point_normal, except that this takes and
4156  * returns matrices instead of normals.
4157  *
4158  * Finds the current location and rotation (in the ship's frame of reference) of
4159  * a submodel point, taking into account the rotations of the submodel and any
4160  * parent submodels it might have.
4161  *
4162  * @param *outpnt Output point
4163  * @param *outorient Output matrix
4164  * @param model_instance_num Index into Polygon_model_instances
4165  * @param submodel_num The number of the submodel we're interested in
4166  * @param *submodel_pnt The point which's current position we want, in the submodel's frame of reference
4167  * @param *submodel_orient The local matrix which's current orientation in the ship's frame of reference we want
4168  */
find_submodel_instance_point_orient(vec3d * outpnt,matrix * outorient,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,const vec3d * submodel_pnt,const matrix * submodel_orient)4169 void find_submodel_instance_point_orient(vec3d *outpnt, matrix *outorient, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const vec3d *submodel_pnt, const matrix *submodel_orient)
4170 {
4171 	Assert(pm->id == pmi->model_num);
4172 	*outorient = *submodel_orient;
4173 	vm_vec_zero(outpnt);
4174 
4175 	int mn = submodel_num;
4176 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4177 		vec3d offset = pm->submodel[mn].offset;
4178 
4179 		if ( mn == submodel_num) {
4180 			vec3d submodel_pnt_offset = *submodel_pnt;
4181 
4182 			vec3d tvec = submodel_pnt_offset;
4183 			vm_vec_unrotate(&submodel_pnt_offset, &tvec, &pmi->submodel[mn].canonical_orient);
4184 
4185 			matrix tnorm = *outorient;
4186 			vm_matrix_x_matrix(outorient, &tnorm, &pmi->submodel[mn].canonical_orient);
4187 
4188 			vm_vec_add2(&offset, &submodel_pnt_offset);
4189 		}
4190 
4191 		int parent_mn = pm->submodel[mn].parent;
4192 
4193 		vec3d tvec = offset;
4194 		vm_vec_unrotate(&offset, &tvec, &pmi->submodel[parent_mn].canonical_orient);
4195 
4196 		matrix tnorm = *outorient;
4197 		vm_matrix_x_matrix(outorient, &tnorm, &pmi->submodel[parent_mn].canonical_orient);
4198 
4199 		vm_vec_add2(outpnt, &offset);
4200 
4201 		mn = parent_mn;
4202 	}
4203 }
4204 
4205 /**
4206  * Finds the current world location of a submodel, taking into account the
4207  * rotations of any parent submodels it might have.
4208  *
4209  * @param *outpnt Output point
4210  * @param model_instance_num Index into Polygon_model_instances
4211  * @param submodel_num The number of the submodel we're interested in
4212  */
find_submodel_instance_world_point(vec3d * outpnt,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,const matrix * objorient,const vec3d * objpos)4213 void find_submodel_instance_world_point(vec3d *outpnt, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *objorient, const vec3d *objpos)
4214 {
4215 	vec3d loc_pnt;
4216 	Assert(pm->id == pmi->model_num);
4217 
4218 	find_submodel_instance_point(&loc_pnt, pm, pmi, submodel_num);
4219 
4220 	vm_vec_unrotate(outpnt, &loc_pnt, objorient);
4221 	vm_vec_add2(outpnt, objpos);
4222 }
4223 
4224 // Verify rotating submodel has corresponding ship subsystem -- info in which to store rotation angle
rotating_submodel_has_ship_subsys(int submodel,ship * shipp)4225 int rotating_submodel_has_ship_subsys(int submodel, ship *shipp)
4226 {
4227 	model_subsystem	*psub;
4228 	ship_subsys			*pss;
4229 
4230 	int found = 0;
4231 
4232 	// Go through all subsystems and look for submodel
4233 	// the subsystems that need it.
4234 	for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
4235 		psub = pss->system_info;
4236 		if (psub->subobj_num == submodel) {
4237 			found = 1;
4238 			break;
4239 		}
4240 	}
4241 
4242 	return found;
4243 }
4244 
4245 /*
4246  * Get all submodel indexes that satisfy the following:
4247  * 1) Have the rotating or intrinsic-rotating movement type
4248  * 2) Are currently rotating (i.e. actually moving and not part of the superstructure due to being destroyed or replaced)
4249  * 3) Are not rotating too far for collision detection (c.f. MAX_SUBMODEL_COLLISION_ROT_ANGLE)
4250  */
model_get_rotating_submodel_list(SCP_vector<int> * submodel_vector,object * objp)4251 void model_get_rotating_submodel_list(SCP_vector<int> *submodel_vector, object *objp)
4252 {
4253 	Assert(objp->type == OBJ_SHIP || objp->type == OBJ_WEAPON || objp->type == OBJ_ASTEROID);
4254 
4255 	int model_instance_num;
4256 	int model_num;
4257 	if (objp->type == OBJ_SHIP) {
4258 		model_instance_num = Ships[objp->instance].model_instance_num;
4259 		model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
4260 	}
4261 	else if (objp->type == OBJ_WEAPON) {
4262 		model_instance_num = Weapons[objp->instance].model_instance_num;
4263 		if (model_instance_num < 0) {
4264 			return;
4265 		}
4266 		model_num = Weapon_info[Weapons[objp->instance].weapon_info_index].model_num;
4267 	}
4268 	else if (objp->type == OBJ_ASTEROID) {
4269 		model_instance_num = Asteroids[objp->instance].model_instance_num;
4270 		if (model_instance_num < 0) {
4271 			return;
4272 		}
4273 		model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
4274 	}
4275 	else {
4276 		return;
4277 	}
4278 
4279 	polymodel *pm = model_get(model_num);
4280 	bsp_info *child_submodel = &pm->submodel[pm->detail[0]];
4281 
4282 	if(child_submodel->no_collisions) { // if detail0 has $no_collision set dont check childs
4283 		return;
4284 	}
4285 
4286 	polymodel_instance *pmi = model_get_instance(model_instance_num);
4287 	submodel_instance *child_submodel_instance;
4288 
4289 	int i = child_submodel->first_child;
4290 	while ( i >= 0 )	{
4291 		child_submodel = &pm->submodel[i];
4292 		child_submodel_instance = &pmi->submodel[i];
4293 
4294 		// Don't check it or its children if it is destroyed or it is a replacement (non-moving)
4295 		if ( !child_submodel_instance->blown_off && (child_submodel->i_replace == -1) && !child_submodel->no_collisions && !child_submodel->nocollide_this_only)	{
4296 
4297 			// Only look for submodels that rotate or intrinsic-rotate
4298 			if (child_submodel->movement_type == MOVEMENT_TYPE_ROT || child_submodel->movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
4299 
4300 				// check submodel rotation is less than max allowed.
4301 				float delta_angle = get_submodel_delta_angle(child_submodel_instance);
4302 				if (delta_angle < MAX_SUBMODEL_COLLISION_ROT_ANGLE) {
4303 					submodel_vector->push_back(i);
4304 				}
4305 			}
4306 		}
4307 		i = child_submodel->next_sibling;
4308 	}
4309 }
4310 
model_get_submodel_tree_list(SCP_vector<int> & submodel_vector,polymodel * pm,int mn)4311 void model_get_submodel_tree_list(SCP_vector<int> &submodel_vector, polymodel* pm, int mn)
4312 {
4313 	if ( pm->submodel[mn].buffer.model_list != NULL ) {
4314 		submodel_vector.push_back(mn);
4315 	}
4316 
4317 	int i = pm->submodel[mn].first_child;
4318 
4319 	while ( i >= 0 ) {
4320 		model_get_submodel_tree_list(submodel_vector, pm, i);
4321 
4322 		i = pm->submodel[i].next_sibling;
4323 	}
4324 }
4325 
model_find_world_dir(vec3d * out_dir,const vec3d * in_dir,int model_num,int submodel_num,const matrix * objorient)4326 void model_find_world_dir(vec3d *out_dir, const vec3d *in_dir, int model_num, int submodel_num, const matrix *objorient)
4327 {
4328 	model_find_world_dir(out_dir, in_dir, model_get(model_num), submodel_num, objorient);
4329 }
4330 
4331 // Given a direction (pnt) that is in submodel_num's frame of
4332 // reference, and given the object's orient and position,
4333 // return the point in 3-space in outpnt.
model_find_world_dir(vec3d * out_dir,const vec3d * in_dir,const polymodel * pm,int submodel_num,const matrix * objorient)4334 void model_find_world_dir(vec3d *out_dir, const vec3d *in_dir, const polymodel *pm, int submodel_num, const matrix *objorient)
4335 {
4336 	SCP_UNUSED(pm);
4337 	SCP_UNUSED(submodel_num);
4338 
4339 	//now instance for the entire object
4340 	vm_vec_unrotate(out_dir, in_dir, objorient);
4341 }
4342 
4343 // the same as model_find_world_dir - just taking model instance data into account
model_instance_find_world_dir(vec3d * out_dir,const vec3d * in_dir,int model_instance_num,int submodel_num,const matrix * objorient,bool use_submodel_parent)4344 void model_instance_find_world_dir(vec3d *out_dir, const vec3d *in_dir, int model_instance_num, int submodel_num, const matrix *objorient, bool use_submodel_parent)
4345 {
4346 	auto pmi = model_get_instance(model_instance_num);
4347 	auto pm = model_get(pmi->model_num);
4348 	model_instance_find_world_dir(out_dir, in_dir, pm, pmi, use_submodel_parent ? pm->submodel[submodel_num].parent : submodel_num, objorient);
4349 }
4350 
model_instance_find_world_dir(vec3d * out_dir,const vec3d * in_dir,const polymodel * pm,const polymodel_instance * pmi,int submodel_num,const matrix * objorient)4351 void model_instance_find_world_dir(vec3d *out_dir, const vec3d *in_dir, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *objorient)
4352 {
4353 	vec3d pnt;
4354 	vec3d tpnt;
4355 	int mn;
4356 	Assert(pm->id == pmi->model_num);
4357 
4358 	pnt = *in_dir;
4359 	mn = submodel_num;
4360 
4361 	// instance up the tree for this point
4362 	while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4363 		vm_vec_unrotate(&tpnt, &pnt, &pmi->submodel[mn].canonical_orient);
4364 		pnt = tpnt;
4365 
4366 		mn = pm->submodel[mn].parent;
4367 	}
4368 
4369 	// now instance for the entire object
4370 	vm_vec_unrotate(out_dir, &pnt, objorient);
4371 }
4372 
4373 
4374 // Clears all the submodel instances stored in a model to their defaults.
model_clear_instance(int model_num)4375 void model_clear_instance(int model_num)
4376 {
4377 	// ---- stuff that should be moved into model instances at some point
4378 	int i;
4379 	auto pm = model_get(model_num);
4380 
4381 	// reset textures to original ones
4382 	for (i=0; i<pm->n_textures; i++ )	{
4383 		pm->maps[i].ResetToOriginal();
4384 	}
4385 	// ---- end of stuff that should be moved into model instances at some point
4386 
4387 	interp_clear_instance();
4388 }
4389 
model_set_submodel_turn_info(submodel_instance * smi,float turn_rate,float turn_accel)4390 void model_set_submodel_turn_info(submodel_instance *smi, float turn_rate, float turn_accel)
4391 {
4392 	smi->current_turn_rate = 0.0f;
4393 	smi->desired_turn_rate = turn_rate;
4394 	smi->turn_accel = turn_accel;
4395 }
4396 
4397 // Sets the submodel instance data when a tech room model instance is created.
4398 // This only needs to be done at creation, not every frame.
model_set_up_techroom_instance(ship_info * sip,int model_instance_num)4399 void model_set_up_techroom_instance(ship_info *sip, int model_instance_num)
4400 {
4401 	auto pmi = model_get_instance(model_instance_num);
4402 	auto pm = model_get(pmi->model_num);
4403 	flagset<Ship::Subsystem_Flags> empty;
4404 
4405 	for (int i = 0; i < sip->n_subsystems; ++i)
4406 	{
4407 		model_subsystem *msp = &sip->subsystems[i];
4408 
4409 		const auto& initialAnims = sip->animations.animationSet[{animation::ModelAnimationTriggerType::Initial, animation::ModelAnimationSet::SUBTYPE_DEFAULT}];
4410 
4411 		for (const auto& initialAnim : initialAnims) {
4412 			initialAnim.second->start(pmi, false, true);
4413 		}
4414 
4415 		if (msp->subobj_num >= 0)
4416 			model_update_instance(pm, pmi, msp->subobj_num, empty);
4417 
4418 		if (msp->turret_gun_sobj >= 0)
4419 			model_update_instance(pm, pmi, msp->turret_gun_sobj, empty);
4420 	}
4421 }
4422 
model_update_instance(int model_instance_num,int submodel_num,flagset<Ship::Subsystem_Flags> & flags)4423 void model_update_instance(int model_instance_num, int submodel_num, flagset<Ship::Subsystem_Flags>& flags)
4424 {
4425 	auto pmi = model_get_instance(model_instance_num);
4426 	auto pm = model_get(pmi->model_num);
4427 	model_update_instance(pm, pmi, submodel_num, flags);
4428 }
4429 
4430 /*
4431  * This function handles copying submodel instance information to other submodel instances as appropriate.  The copy_from parameter is used for
4432  * copying data to other LODs, and is only specified from within model_update_instance itself.  The "public" function header omits this parameter.
4433  */
model_update_instance(polymodel * pm,polymodel_instance * pmi,const submodel_instance * copy_from,int submodel_num,flagset<Ship::Subsystem_Flags> & flags)4434 void model_update_instance(polymodel *pm, polymodel_instance *pmi, const submodel_instance *copy_from, int submodel_num, flagset<Ship::Subsystem_Flags>& flags)
4435 {
4436 	Assert(pm->id == pmi->model_num);
4437 
4438 	Assertion(submodel_num >= 0 && submodel_num < pm->n_models,
4439 		"Submodel number (%d) which should be updated is out of range! Must be between 0 and %d. This happened on model %s.",
4440 		submodel_num, pm->n_models - 1, pm->filename);
4441 
4442 	if ( submodel_num < 0 ) return;
4443 	if ( submodel_num >= pm->n_models ) return;
4444 
4445 	submodel_instance *smi = &pmi->submodel[submodel_num];
4446 	bsp_info *sm = &pm->submodel[submodel_num];
4447 
4448 	// Set the "blown out" flags.
4449 	if ( flags[Ship::Subsystem_Flags::No_disappear] ) {
4450 		smi->blown_off = false;
4451 	} else if ( copy_from ) {
4452 		smi->blown_off = copy_from->blown_off;
4453 	}
4454 
4455 	if ( smi->blown_off )	{
4456 		if ( sm->my_replacement >= 0 && !(flags[Ship::Subsystem_Flags::No_replace]) ) {
4457 			auto r_smi = &pmi->submodel[sm->my_replacement];
4458 			r_smi->blown_off = false;
4459 			if ( copy_from ) {
4460 				r_smi->cur_angle = copy_from->cur_angle;
4461 				r_smi->canonical_orient = copy_from->canonical_orient;
4462 				r_smi->canonical_prev_orient = copy_from->canonical_prev_orient;
4463 			} else {
4464 				r_smi->cur_angle = smi->cur_angle;
4465 				r_smi->canonical_orient = smi->canonical_orient;
4466 				r_smi->canonical_prev_orient = smi->canonical_prev_orient;
4467 			}
4468 		}
4469 	} else {
4470 		// If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
4471 		// the replacement model from being drawn by marking it as having been blown off
4472 		if ( sm->my_replacement >= 0 && sm->my_replacement != submodel_num)	{
4473 			auto r_smi = &pmi->submodel[sm->my_replacement];
4474 			r_smi->blown_off = true;
4475 		}
4476 	}
4477 
4478 	// Set the angles.
4479 	if ( copy_from ) {
4480 		smi->cur_angle = copy_from->cur_angle;
4481 		smi->canonical_orient = copy_from->canonical_orient;
4482 		smi->canonical_prev_orient = copy_from->canonical_prev_orient;
4483 	}
4484 
4485 	// For all the detail levels of this submodel, set them also.
4486 	for ( int i=0; i<sm->num_details; i++ )	{
4487 		model_update_instance( pm, pmi, smi, sm->details[i], flags );
4488 	}
4489 }
4490 
model_update_instance(polymodel * pm,polymodel_instance * pmi,int submodel_num,flagset<Ship::Subsystem_Flags> & flags)4491 void model_update_instance(polymodel *pm, polymodel_instance *pmi, int submodel_num, flagset<Ship::Subsystem_Flags>& flags)
4492 {
4493 	model_update_instance(pm, pmi, nullptr, submodel_num, flags);
4494 }
4495 
model_do_intrinsic_rotations_sub(intrinsic_rotation * ir)4496 void model_do_intrinsic_rotations_sub(intrinsic_rotation *ir)
4497 {
4498 	polymodel_instance *pmi = model_get_instance(ir->model_instance_num);
4499 	Assert(pmi != nullptr);
4500 	polymodel *pm = model_get(pmi->model_num);
4501 	Assert(pm != nullptr);
4502 	flagset<Ship::Subsystem_Flags> empty;
4503 
4504 	// Handle all submodels which have intrinsic rotation
4505 	for (auto submodel_num: ir->submodel_list)
4506 	{
4507 		// First, calculate the angles for the rotation
4508 		if (pm->submodel[submodel_num].look_at_submodel >= 0)
4509 			submodel_look_at(pm, pmi, submodel_num);
4510 		else
4511 			submodel_rotate(&pm->submodel[submodel_num], &pmi->submodel[submodel_num]);
4512 
4513 		// Now actually rotate the submodel instance
4514 		// (Since this is an intrinsic rotation, we have no associated subsystem, so pass 0 for subsystem flags.)
4515 		model_update_instance(pm, pmi, submodel_num, empty);
4516 	}
4517 }
4518 
4519 // Handle the intrinsic rotations for either a) a single ship model; or b) all non-ship models.  The reason for the two cases is that ship_model_update_instance will
4520 // be called for each ship via obj_move_all_post, but we also need to handle non-ship models once obj_move_all_post exits.  Since the two processes are almost identical,
4521 // they are both handled here.
4522 //
4523 // This function is quite a bit different than Bobboau's old model_do_dumb_rotation function.  Whereas Bobboau used the brute-force technique of navigating through
4524 // each model hierarchy as it was rendered, this function should be seen as a version of obj_move_all_post, but for models rather than objects.  In fact, the only reason
4525 // for the special ship case is that the ship intrinsic rotations kind of need to be handled where all the other ship rotations are.  (Unless you want inconsistent collisions
4526 // or damage sparks that aren't attached to models.)
4527 //
4528 // -- Goober5000
model_do_intrinsic_rotations(int model_instance_num)4529 void model_do_intrinsic_rotations(int model_instance_num)
4530 {
4531 	// we are handling a specific ship
4532 	if (model_instance_num >= 0)
4533 	{
4534 		for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it)
4535 		{
4536 			if (intrinsic_it->model_instance_num == model_instance_num)
4537 			{
4538 				Assertion(intrinsic_it->is_ship, "This code path is only for ship rotations!  See the comments associated with the model_do_intrinsic_rotations function!");
4539 
4540 				// we're just doing one ship, and in ship_model_update_instance, that ship's angles were already set to zero
4541 
4542 				// Now update the angles in the submodels
4543 				model_do_intrinsic_rotations_sub(&(*intrinsic_it));
4544 
4545 				// once we've handled this one ship, we're done
4546 				break;
4547 			}
4548 		}
4549 	}
4550 	// we are handling all non-ships
4551 	else
4552 	{
4553 		for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it)
4554 		{
4555 			if (!intrinsic_it->is_ship)
4556 			{
4557 				// update the angles in the submodels
4558 				model_do_intrinsic_rotations_sub(&(*intrinsic_it));
4559 			}
4560 		}
4561 	}
4562 }
4563 
4564 // Finds a point on the rotation axis of a submodel, used in collision, generally find rotational velocity
model_init_submodel_axis_pt(polymodel * pm,polymodel_instance * pmi,int submodel_num)4565 void model_init_submodel_axis_pt(polymodel *pm, polymodel_instance *pmi, int submodel_num)
4566 {
4567 	vec3d mpoint1, mpoint2;
4568 	vec3d p1, v1, p2, v2, int1;
4569 	Assert(pm->id == pmi->model_num);
4570 
4571 	Assert(pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_ROT || pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE);
4572 	submodel_instance *smi = &pmi->submodel[submodel_num];
4573 
4574 	auto axis = &pm->submodel[submodel_num].movement_axis;
4575 
4576 	// find 2 fixed points in submodel RF
4577 	// these will be rotated to about the axis an angle of 0 and PI and we'll find the intersection of the
4578 	// two lines to find a point on the axis
4579 
4580 	// since the movement axis is now arbitrary, we can't simply pick points on the other two axes;
4581 	// we need to generate some suitably orthogonal points
4582 
4583 	// first find the standard vector that's the most orthongonal-ish
4584 	vec3d *stdaxis;
4585 	float dotx = fl_abs(vm_vec_dot(axis, &vmd_x_vector));
4586 	float doty = fl_abs(vm_vec_dot(axis, &vmd_y_vector));
4587 	float dotz = fl_abs(vm_vec_dot(axis, &vmd_z_vector));
4588 	if (dotx < doty) {
4589 		if (dotx < dotz) {
4590 			stdaxis = &vmd_x_vector;
4591 		} else {
4592 			stdaxis = &vmd_z_vector;
4593 		}
4594 	} else {
4595 		if (doty < dotz) {
4596 			stdaxis = &vmd_y_vector;
4597 		} else {
4598 			stdaxis = &vmd_z_vector;
4599 		}
4600 	}
4601 
4602 	// now find a vector perpendicular to the axis
4603 	vm_vec_cross(&mpoint1, axis, stdaxis);
4604 
4605 	// now find another vector perpendicular to the axis and the first perpendicular vector
4606 	vm_vec_cross(&mpoint2, axis, &mpoint1);
4607 
4608 	// copy submodel angs
4609 	float save_angle = smi->cur_angle;
4610 	matrix save_orient = smi->canonical_orient;
4611 
4612 	// find two points rotated into model RF when angs set to 0
4613 	smi->cur_angle = 0.0f;
4614 	submodel_canonicalize(&pm->submodel[submodel_num], smi, false);
4615 	model_instance_find_world_point(&p1, &mpoint1, pm, pmi, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
4616 	model_instance_find_world_point(&p2, &mpoint2, pm, pmi, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
4617 
4618 	// find two points rotated into model RF when angs set to PI
4619 	smi->cur_angle = PI;
4620 	submodel_canonicalize(&pm->submodel[submodel_num], smi, false);
4621 	model_instance_find_world_point(&v1, &mpoint1, pm, pmi, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
4622 	model_instance_find_world_point(&v2, &mpoint2, pm, pmi, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
4623 
4624 	// reset submodel angs
4625 	smi->cur_angle = save_angle;
4626 	smi->canonical_orient = save_orient;
4627 
4628 	// find direction vectors of the two lines
4629 	vm_vec_sub2(&v1, &p1);
4630 	vm_vec_sub2(&v2, &p2);
4631 
4632 	// find the intersection of the two lines
4633 	float s, t;
4634 	fvi_two_lines_in_3space(&p1, &v1, &p2, &v2, &s, &t);
4635 
4636 	// find the actual intersection points
4637 	vm_vec_scale_add(&int1, &p1, &v1, s);
4638 
4639 	// set flag to init
4640 	smi->point_on_axis = int1;
4641 	smi->axis_set = true;
4642 }
4643 
model_instance_clear_arcs(polymodel * pm,polymodel_instance * pmi)4644 void model_instance_clear_arcs(polymodel *pm, polymodel_instance *pmi)
4645 {
4646 	Assert(pm->id == pmi->model_num);
4647 
4648 	for (int i = 0; i < pm->n_models; ++i) {
4649 		pmi->submodel[i].num_arcs = 0;		// Turn off any electric arcing effects
4650 	}
4651 }
4652 
4653 // Adds an electrical arcing effect to a submodel
model_instance_add_arc(polymodel * pm,polymodel_instance * pmi,int sub_model_num,vec3d * v1,vec3d * v2,int arc_type)4654 void model_instance_add_arc(polymodel *pm, polymodel_instance *pmi, int sub_model_num, vec3d *v1, vec3d *v2, int arc_type )
4655 {
4656 	Assert(pm->id == pmi->model_num);
4657 
4658 	if ( sub_model_num == -1 )	{
4659 		sub_model_num = pm->detail[0];
4660 	}
4661 
4662 	Assert( sub_model_num >= 0 );
4663 	Assert( sub_model_num < pm->n_models );
4664 
4665 	if ( sub_model_num < 0 ) return;
4666 	if ( sub_model_num >= pm->n_models ) return;
4667 	auto smi = &pmi->submodel[sub_model_num];
4668 
4669 	if ( smi->num_arcs < MAX_ARC_EFFECTS )	{
4670 		smi->arc_type[smi->num_arcs] = (ubyte)arc_type;
4671 		smi->arc_pts[smi->num_arcs][0] = *v1;
4672 		smi->arc_pts[smi->num_arcs][1] = *v2;
4673 		smi->num_arcs++;
4674 	}
4675 }
4676 
4677 // function to return an index into the docking_bays array which matches the criteria passed
4678 // to this function.  dock_type is one of the DOCK_TYPE_XXX defines in model.h
4679 // Goober5000 - now finds more than one dockpoint of this type
model_find_dock_index(int modelnum,int dock_type,int index_to_start_at)4680 int model_find_dock_index(int modelnum, int dock_type, int index_to_start_at)
4681 {
4682 	int i;
4683 	polymodel *pm;
4684 
4685 	// get model and make sure it has dockpoints
4686 	pm = model_get(modelnum);
4687 	if ( pm->n_docks <= 0 )
4688 		return -1;
4689 
4690 	// look for a dockpoint of this type
4691 	for (i = index_to_start_at; i < pm->n_docks; i++ )
4692 	{
4693 		if ( dock_type & pm->docking_bays[i].type_flags )
4694 			return i;
4695 	}
4696 
4697 	// if we get here, type wasn't found -- return -1 and hope for the best
4698 	return -1;
4699 }
4700 
4701 // function to return an index into the docking_bays array which matches the string passed
4702 // Fred uses strings to identify docking positions.  This function also accepts generic strings
4703 // so that a desginer doesn't have to know exact names if building a mission from hand.
model_find_dock_name_index(int modelnum,const char * name)4704 int model_find_dock_name_index(int modelnum, const char* name)
4705 {
4706 	int i;
4707 	polymodel *pm;
4708 
4709 	// get model and make sure it has dockpoints
4710 	pm = model_get(modelnum);
4711 	if ( pm->n_docks <= 0 )
4712 		return -1;
4713 
4714 	// check the generic names and call previous function to find first dock point of
4715 	// the specified type
4716 	for(i = 0; i < Num_dock_type_names; i++)
4717 	{
4718 		if(!stricmp(name, Dock_type_names[i].name)) {
4719 			return model_find_dock_index(modelnum, Dock_type_names[i].def);
4720 		}
4721 	}
4722 	/*
4723 	if ( !stricmp(name, "cargo") )
4724 		return model_find_dock_index( modelnum, DOCK_TYPE_CARGO );
4725 	else if (!stricmp( name, "rearm") )
4726 		return model_find_dock_index( modelnum, DOCK_TYPE_REARM );
4727 	else if (!stricmp( name, "generic") )
4728 		return model_find_dock_index( modelnum, DOCK_TYPE_GENERIC );
4729 	*/
4730 
4731 	// look for a dockpoint with this name
4732 	for (i = 0; i < pm->n_docks; i++ )
4733 	{
4734 		if ( !stricmp(pm->docking_bays[i].name, name) )
4735 			return i;
4736 	}
4737 
4738 	// if the bay does not have a name in the model, the model loading code
4739 	// will assign it a default name... check for that here
4740 	if (!strnicmp(name, "<unnamed bay ", 13))
4741 	{
4742 		int index = (name[13] - 'A');
4743 		if (index >= 0 && index < pm->n_docks)
4744 			return index;
4745 	}
4746 
4747 	// if we get here, name wasn't found -- return -1 and hope for the best
4748 	return -1;
4749 }
4750 
4751 // returns the actual name of a docking point on a model, needed by Fred.
model_get_dock_name(int modelnum,int index)4752 char *model_get_dock_name(int modelnum, int index)
4753 {
4754 	polymodel *pm;
4755 
4756 	pm = model_get(modelnum);
4757 	Assert((index >= 0) && (index < pm->n_docks));
4758 	return pm->docking_bays[index].name;
4759 }
4760 
model_get_num_dock_points(int modelnum)4761 int model_get_num_dock_points(int modelnum)
4762 {
4763 	polymodel *pm;
4764 
4765 	pm = model_get(modelnum);
4766 	return pm->n_docks;
4767 }
4768 
model_get_dock_index_type(int modelnum,int index)4769 int model_get_dock_index_type(int modelnum, int index)
4770 {
4771 	polymodel *pm = model_get(modelnum);
4772 
4773 	return pm->docking_bays[index].type_flags;
4774 }
4775 
4776 // get all the different docking point types on a model
model_get_dock_types(int modelnum)4777 int model_get_dock_types(int modelnum)
4778 {
4779 	int i, type = 0;
4780 	polymodel *pm;
4781 
4782 	pm = model_get(modelnum);
4783 	for (i=0; i<pm->n_docks; i++)
4784 		type |= pm->docking_bays[i].type_flags;
4785 
4786 	return type;
4787 }
4788 
4789 // Goober5000
4790 // returns index in [0, MAX_SHIP_BAY_PATHS)
model_find_bay_path(int modelnum,char * bay_path_name)4791 int model_find_bay_path(int modelnum, char *bay_path_name)
4792 {
4793 	int i;
4794 	polymodel *pm = model_get(modelnum);
4795 
4796 	if (pm->ship_bay == NULL)
4797 		return -1;
4798 
4799 	if (pm->ship_bay->num_paths <= 0)
4800 		return -1;
4801 
4802 	for (i = 0; i < pm->ship_bay->num_paths; i++)
4803 	{
4804 		if (!stricmp(pm->paths[pm->ship_bay->path_indexes[i]].name, bay_path_name))
4805 			return i;
4806 	}
4807 
4808 	return -1;
4809 }
4810 
model_create_bsp_collision_tree()4811 int model_create_bsp_collision_tree()
4812 {
4813 	// first find an open slot
4814 	size_t i;
4815 	bool slot_found = false;
4816 
4817 	for ( i = 0; i < Bsp_collision_tree_list.size(); ++i ) {
4818 		if ( !Bsp_collision_tree_list[i].used ) {
4819 			slot_found = true;
4820 			break;
4821 		}
4822 	}
4823 
4824 	if ( slot_found ) {
4825 		Bsp_collision_tree_list[i].used = true;
4826 
4827 		return (int)i;
4828 	}
4829 
4830 	bsp_collision_tree tree;
4831 
4832 	tree.used = true;
4833 	Bsp_collision_tree_list.push_back(tree);
4834 
4835 	return (int)(Bsp_collision_tree_list.size() - 1);
4836 }
4837 
model_get_bsp_collision_tree(int tree_index)4838 bsp_collision_tree *model_get_bsp_collision_tree(int tree_index)
4839 {
4840 	Assert(tree_index >= 0);
4841 	Assert((uint) tree_index < Bsp_collision_tree_list.size());
4842 
4843 	return &Bsp_collision_tree_list[tree_index];
4844 }
4845 
model_remove_bsp_collision_tree(int tree_index)4846 void model_remove_bsp_collision_tree(int tree_index)
4847 {
4848 	Bsp_collision_tree_list[tree_index].used = false;
4849 
4850 	if ( Bsp_collision_tree_list[tree_index].node_list ) {
4851 		vm_free(Bsp_collision_tree_list[tree_index].node_list);
4852 	}
4853 
4854 	if ( Bsp_collision_tree_list[tree_index].leaf_list ) {
4855 		vm_free(Bsp_collision_tree_list[tree_index].leaf_list);
4856 	}
4857 
4858 	if ( Bsp_collision_tree_list[tree_index].point_list ) {
4859 		vm_free( Bsp_collision_tree_list[tree_index].point_list );
4860 	}
4861 
4862 	if ( Bsp_collision_tree_list[tree_index].vert_list ) {
4863 		vm_free( Bsp_collision_tree_list[tree_index].vert_list);
4864 	}
4865 }
4866 
4867 #if BYTE_ORDER == BIG_ENDIAN
4868 
4869 // tigital -
swap_bsp_defpoints(ubyte * p)4870 void swap_bsp_defpoints(ubyte * p)
4871 {
4872 	int n, i;
4873 	int nverts = INTEL_INT( w(p+8) );		//tigital
4874 	int offset = INTEL_INT( w(p+16) );
4875 	int n_norms = INTEL_INT( w(p+12) );
4876 
4877 	w(p+8) = nverts;
4878 	w(p+16) = offset;
4879 	w(p+12) = n_norms;
4880 
4881 	ubyte * normcount = p+20;
4882 	vec3d *src = vp(p+offset);
4883 
4884 	model_allocate_interp_data(nverts, n_norms);
4885 
4886 	for (n=0; n<nverts; n++ )	{
4887 		src->xyz.x = INTEL_FLOAT( &src->xyz.x );		//tigital
4888 		src->xyz.y = INTEL_FLOAT( &src->xyz.y );
4889 		src->xyz.z = INTEL_FLOAT( &src->xyz.z );
4890 
4891 		Interp_verts[n] = src;
4892 		src++;	//tigital
4893 
4894 		for (i=0; i<normcount[n]; i++){
4895 			src->xyz.x = INTEL_FLOAT( &src->xyz.x );		//tigital
4896 			src->xyz.y = INTEL_FLOAT( &src->xyz.y );
4897 			src->xyz.z = INTEL_FLOAT( &src->xyz.z );
4898 			src++;
4899 		}
4900 	}
4901 }
4902 
swap_bsp_tmappoly(polymodel * pm,ubyte * p)4903 void swap_bsp_tmappoly( polymodel * pm, ubyte * p )
4904 {
4905 	int i, nv;
4906 	model_tmap_vert *verts;
4907 	vec3d * normal = vp(p+8);	//tigital
4908 	vec3d * center = vp(p+20);
4909 	float radius = INTEL_FLOAT( &fl(p+32) );
4910 
4911 	fl(p+32) = radius;
4912 
4913 	normal->xyz.x = INTEL_FLOAT( &normal->xyz.x );
4914 	normal->xyz.y = INTEL_FLOAT( &normal->xyz.y );
4915 	normal->xyz.z = INTEL_FLOAT( &normal->xyz.z );
4916 	center->xyz.x = INTEL_FLOAT( &center->xyz.x );
4917 	center->xyz.y = INTEL_FLOAT( &center->xyz.y );
4918 	center->xyz.z = INTEL_FLOAT( &center->xyz.z );
4919 
4920 	nv = INTEL_INT( w(p+36));		//tigital
4921 		w(p+36) = nv;
4922 
4923 	int tmap_num = INTEL_INT( w(p+40) );	//tigital
4924 		w(p+40) = tmap_num;
4925 
4926 	if ( nv < 0 ) return;
4927 
4928 	verts = (model_tmap_vert *)(p+44);
4929 	for (i=0;i<nv;i++){
4930 		verts[i].vertnum = INTEL_SHORT( verts[i].vertnum );
4931 		verts[i].normnum = INTEL_SHORT( verts[i].normnum );
4932 		verts[i].u = INTEL_FLOAT( &verts[i].u );
4933 		verts[i].v = INTEL_FLOAT( &verts[i].v );
4934 	}
4935 
4936 	if ( pm->version < 2003 )	{
4937 		// Set the "normal_point" part of field to be the center of the polygon
4938 		vec3d center_point;
4939 		vm_vec_zero( &center_point );
4940 
4941 		for (i=0;i<nv;i++)	{
4942 			vm_vec_add2( &center_point, Interp_verts[verts[i].vertnum] );
4943 		}
4944 
4945 		center_point.xyz.x /= nv;
4946 		center_point.xyz.y /= nv;
4947 		center_point.xyz.z /= nv;
4948 
4949 		*vp(p+20) = center_point;
4950 
4951 		float rad = 0.0f;
4952 
4953 		for (i=0;i<nv;i++)	{
4954 			float dist = vm_vec_dist( &center_point, Interp_verts[verts[i].vertnum] );
4955 			if ( dist > rad )	{
4956 				rad = dist;
4957 			}
4958 		}
4959 		fl(p+32) = rad;
4960 	}
4961 }
4962 
swap_bsp_flatpoly(polymodel * pm,ubyte * p)4963 void swap_bsp_flatpoly( polymodel * pm, ubyte * p )
4964 {
4965 	int i, nv;
4966 	short *verts;
4967 	vec3d * normal = vp(p+8);	//tigital
4968 	vec3d * center = vp(p+20);
4969 
4970 	float radius = INTEL_FLOAT( &fl(p+32) );
4971 
4972 	fl(p+32) = radius;
4973 
4974 	normal->xyz.x = INTEL_FLOAT( &normal->xyz.x );
4975 	normal->xyz.y = INTEL_FLOAT( &normal->xyz.y );
4976 	normal->xyz.z = INTEL_FLOAT( &normal->xyz.z );
4977 	center->xyz.x = INTEL_FLOAT( &center->xyz.x );
4978 	center->xyz.y = INTEL_FLOAT( &center->xyz.y );
4979 	center->xyz.z = INTEL_FLOAT( &center->xyz.z );
4980 
4981 	nv = INTEL_INT( w(p+36));		//tigital
4982 		w(p+36) = nv;
4983 
4984 	if ( nv < 0 ) return;
4985 
4986 	verts = (short *)(p+44);
4987 	for (i=0; i<nv*2; i++){
4988 		verts[i] = INTEL_SHORT( verts[i] );
4989 	}
4990 
4991 	if ( pm->version < 2003 )	{
4992 		// Set the "normal_point" part of field to be the center of the polygon
4993 		vec3d center_point;
4994 		vm_vec_zero( &center_point );
4995 
4996 		for (i=0;i<nv;i++)	{
4997 			vm_vec_add2( &center_point, Interp_verts[verts[i*2]] );
4998 		}
4999 
5000 		center_point.xyz.x /= nv;
5001 		center_point.xyz.y /= nv;
5002 		center_point.xyz.z /= nv;
5003 
5004 		*vp(p+20) = center_point;
5005 
5006 		float rad = 0.0f;
5007 
5008 		for (i=0;i<nv;i++)	{
5009 			float dist = vm_vec_dist( &center_point, Interp_verts[verts[i*2]] );
5010 			if ( dist > rad )	{
5011 				rad = dist;
5012 			}
5013 		}
5014 		fl(p+32) = rad;
5015 	}
5016 }
5017 
swap_bsp_sortnorms(polymodel * pm,ubyte * p)5018 void swap_bsp_sortnorms( polymodel * pm, ubyte * p )
5019 {
5020 	int frontlist = INTEL_INT( w(p+36) );	//tigital
5021 	int backlist = INTEL_INT( w(p+40) );
5022 	int prelist = INTEL_INT( w(p+44) );
5023 	int postlist = INTEL_INT( w(p+48) );
5024 	int onlist = INTEL_INT( w(p+52) );
5025 
5026 	w(p+36) = frontlist;
5027 	w(p+40) = backlist;
5028 	w(p+44) = prelist;
5029 	w(p+48) = postlist;
5030 	w(p+52) = onlist;
5031 
5032 	vec3d * normal = vp(p+8);	//tigital
5033 	vec3d * center = vp(p+20);
5034 	int  tmp = INTEL_INT( w(p+32) );
5035 
5036 	w(p+32) = tmp;
5037 
5038 	normal->xyz.x = INTEL_FLOAT( &normal->xyz.x );
5039 	normal->xyz.y = INTEL_FLOAT( &normal->xyz.y );
5040 	normal->xyz.z = INTEL_FLOAT( &normal->xyz.z );
5041 	center->xyz.x = INTEL_FLOAT( &center->xyz.x );
5042 	center->xyz.y = INTEL_FLOAT( &center->xyz.y );
5043 	center->xyz.z = INTEL_FLOAT( &center->xyz.z );
5044 
5045 	vec3d * bmin = vp(p+56);	//tigital
5046 	vec3d * bmax = vp(p+68);
5047 
5048 	bmin->xyz.x = INTEL_FLOAT( &bmin->xyz.x );
5049 	bmin->xyz.y = INTEL_FLOAT( &bmin->xyz.y );
5050 	bmin->xyz.z = INTEL_FLOAT( &bmin->xyz.z );
5051 	bmax->xyz.x = INTEL_FLOAT( &bmax->xyz.x );
5052 	bmax->xyz.y = INTEL_FLOAT( &bmax->xyz.y );
5053 	bmax->xyz.z = INTEL_FLOAT( &bmax->xyz.z );
5054 
5055 	if (prelist) swap_bsp_data(pm,p+prelist);
5056 	if (backlist) swap_bsp_data(pm,p+backlist);
5057 	if (onlist) swap_bsp_data(pm,p+onlist);
5058 	if (frontlist) swap_bsp_data(pm,p+frontlist);
5059 	if (postlist) swap_bsp_data(pm,p+postlist);
5060 }
5061 #endif // BIG_ENDIAN
5062 
swap_bsp_data(polymodel * pm,void * model_ptr)5063 void swap_bsp_data( polymodel * pm, void * model_ptr )
5064 {
5065 #if BYTE_ORDER == BIG_ENDIAN
5066 	ubyte *p = (ubyte *)model_ptr;
5067 	int chunk_type, chunk_size;
5068 	vec3d * min;
5069 	vec3d * max;
5070 
5071 	chunk_type = INTEL_INT( w(p) );	//tigital
5072 	chunk_size = INTEL_INT( w(p+4) );
5073 	w(p) = chunk_type;
5074 	w(p+4) = chunk_size;
5075 
5076 	while (chunk_type != OP_EOF) {
5077 		switch (chunk_type) {
5078 			case OP_EOF:
5079 				return;
5080 			case OP_DEFPOINTS:
5081 				swap_bsp_defpoints(p);
5082 				break;
5083 			case OP_FLATPOLY:
5084 				swap_bsp_flatpoly(pm, p);
5085 				break;
5086 			case OP_TMAPPOLY:
5087 				swap_bsp_tmappoly(pm, p);
5088 				break;
5089 			case OP_SORTNORM:
5090 				swap_bsp_sortnorms(pm, p);
5091 				break;
5092 			case OP_BOUNDBOX:
5093 				min = vp(p+8);
5094 				max = vp(p+20);
5095 				min->xyz.x = INTEL_FLOAT( &min->xyz.x );
5096 				min->xyz.y = INTEL_FLOAT( &min->xyz.y );
5097 				min->xyz.z = INTEL_FLOAT( &min->xyz.z );
5098 				max->xyz.x = INTEL_FLOAT( &max->xyz.x );
5099 				max->xyz.y = INTEL_FLOAT( &max->xyz.y );
5100 				max->xyz.z = INTEL_FLOAT( &max->xyz.z );
5101 				break;
5102 			default:
5103 				mprintf(( "Bad chunk type %d, len=%d in modelread:swap_bsp_data\n", chunk_type, chunk_size ));
5104 				Int3();		// Bad chunk type!
5105 			return;
5106 		}
5107 
5108 		p += chunk_size;
5109 		chunk_type = INTEL_INT( w(p));	//tigital
5110 		chunk_size = INTEL_INT( w(p+4) );
5111 		w(p) = chunk_type;
5112 		w(p+4) = chunk_size;
5113 	}
5114 
5115 	return;
5116 #else
5117 (void)pm;
5118 (void)model_ptr;
5119 #endif
5120 }
5121 
swap_sldc_data(ubyte * buffer)5122 void swap_sldc_data(ubyte* buffer)
5123 {
5124 	//ShivanSpS - Changed type char for a type int for SLC2
5125 #if BYTE_ORDER == BIG_ENDIAN
5126 	int* type_p = (int*)(buffer);
5127 	int* size_p = (int*)(buffer + 4);
5128 	*size_p = INTEL_INT(*size_p);
5129 	*type_p = INTEL_INT(*type_p);
5130 
5131 	// split and polygons
5132 	vec3d* minbox_p = (vec3d*)(buffer + 8);
5133 	vec3d* maxbox_p = (vec3d*)(buffer + 20);
5134 
5135 	minbox_p->xyz.x = INTEL_FLOAT(&minbox_p->xyz.x);
5136 	minbox_p->xyz.y = INTEL_FLOAT(&minbox_p->xyz.y);
5137 	minbox_p->xyz.z = INTEL_FLOAT(&minbox_p->xyz.z);
5138 
5139 	maxbox_p->xyz.x = INTEL_FLOAT(&maxbox_p->xyz.x);
5140 	maxbox_p->xyz.y = INTEL_FLOAT(&maxbox_p->xyz.y);
5141 	maxbox_p->xyz.z = INTEL_FLOAT(&maxbox_p->xyz.z);
5142 
5143 
5144 	// split
5145 	unsigned int* front_offset_p = (unsigned int*)(buffer + 32);
5146 	unsigned int* back_offset_p = (unsigned int*)(buffer + 36);
5147 
5148 	// polygons
5149 	unsigned int* num_polygons_p = (unsigned int*)(buffer + 32);
5150 
5151 	unsigned int* shld_polys = (unsigned int*)(buffer + 36);
5152 
5153 	if (*type_p == 0) // SPLIT
5154 	{
5155 		*front_offset_p = INTEL_INT(*front_offset_p);
5156 		*back_offset_p = INTEL_INT(*back_offset_p);
5157 	}
5158 	else
5159 	{
5160 		*num_polygons_p = INTEL_INT(*num_polygons_p);
5161 		for (unsigned int i = 0; i < *num_polygons_p; i++)
5162 		{
5163 			shld_polys[i] = INTEL_INT(shld_polys[i]);
5164 		}
5165 	}
5166 #else
5167 	(void)buffer;
5168 #endif
5169 }
5170 
glowpoint_override_defaults(glow_point_bank_override * gpo)5171 void glowpoint_override_defaults(glow_point_bank_override *gpo)
5172 {
5173 	gpo->name[0] = 0;
5174 	gpo->type = 0;
5175 	gpo->on_time = 0;
5176 	gpo->off_time = 0;
5177 	gpo->disp_time = 0;
5178 	gpo->glow_bitmap = -1;
5179 	gpo->glow_neb_bitmap = -1;
5180 	gpo->is_on = true;
5181 	gpo->type_override = false;
5182 	gpo->on_time_override = false;
5183 	gpo->off_time_override = false;
5184 	gpo->disp_time_override = false;
5185 	gpo->glow_bitmap_override = false;
5186 	gpo->pulse_period_override = false;
5187 	gpo->pulse_type = 0;
5188 	gpo->pulse_period = 0;
5189 	gpo->pulse_amplitude = 1.0f;
5190 	gpo->pulse_bias = 0.0f;
5191 	gpo->pulse_exponent = 1.0f;
5192 	gpo->is_lightsource = false;
5193 	gpo->radius_multi = 15.0f;
5194 	gpo->light_color = vmd_zero_vector;
5195 	gpo->light_mix_color = vmd_zero_vector;
5196 	gpo->lightcone = false;
5197 	gpo->cone_angle = 90.0f;
5198 	gpo->cone_direction = vmd_zero_vector;
5199 	gpo->dualcone = false;
5200 	gpo->rotating = false;
5201 	gpo->rotation_axis = vmd_zero_vector;
5202 	gpo->rotation_speed = 0.0f;
5203 }
5204 
get_glowpoint_bank_override_by_name(const char * name)5205 SCP_vector<glow_point_bank_override>::iterator get_glowpoint_bank_override_by_name(const char* name)
5206 {
5207 	SCP_vector<glow_point_bank_override>::iterator gpo = glowpoint_bank_overrides.begin();
5208 	for(;gpo != glowpoint_bank_overrides.end(); ++gpo)	{
5209 		if(!strcmp(gpo->name, name))	{
5210 			return gpo;
5211 		}
5212 	}
5213 	return glowpoint_bank_overrides.end();
5214 }
5215 
parse_glowpoint_table(const char * filename)5216 void parse_glowpoint_table(const char *filename)
5217 {
5218 	try {
5219 		if (cf_exists_full(filename, CF_TYPE_TABLES))
5220 			read_file_text(filename, CF_TYPE_TABLES);
5221 		else
5222 			return;
5223 
5224 		reset_parse();
5225 
5226 		if (!optional_string("#Glowpoint overrides")) {
5227 			return;
5228 		}
5229 
5230 		while (!required_string_either("$Name:", "#End")) {
5231 			glow_point_bank_override gpo;
5232 			glowpoint_override_defaults(&gpo);
5233 
5234 			bool replace = false;
5235 			bool skip = false;
5236 
5237 			required_string("$Name:");
5238 			stuff_string(gpo.name, F_NAME, NAME_LENGTH);
5239 
5240 			if (optional_string("+nocreate")) {
5241 				if (Parsing_modular_table) {
5242 					replace = true;
5243 				}
5244 				else {
5245 					mprintf(("+nocreate specified in non-modular glowpoint table.\n"));
5246 				}
5247 			}
5248 
5249 			if (optional_string("$On:")) {
5250 				stuff_boolean(&gpo.is_on);
5251 			}
5252 
5253 			if (optional_string("$Displacement time:")) {
5254 				stuff_int(&gpo.disp_time);
5255 				gpo.disp_time_override = true;
5256 			}
5257 
5258 			if (optional_string("$On time:")) {
5259 				stuff_int(&gpo.on_time);
5260 				gpo.on_time_override = true;
5261 			}
5262 
5263 			if (optional_string("$Off time:")) {
5264 				stuff_int(&gpo.off_time);
5265 				gpo.off_time_override = true;
5266 			}
5267 
5268 			if (optional_string("$Texture:")) {
5269 				char glow_texture_name[32];
5270 				stuff_string(glow_texture_name, F_NAME, NAME_LENGTH);
5271 
5272 				gpo.glow_bitmap_override = true;
5273 
5274 				if (stricmp(glow_texture_name, "none") != 0) {
5275 					gpo.glow_bitmap = bm_load(glow_texture_name);
5276 
5277 					if (gpo.glow_bitmap < 0)
5278 					{
5279 						Warning(LOCATION, "Couldn't open texture '%s'\nreferenced by glowpoint preset '%s'\n", glow_texture_name, gpo.name);
5280 					}
5281 					else
5282 					{
5283 						nprintf(("Model", "Glowpoint preset %s texture num is %d\n", gpo.name, gpo.glow_bitmap));
5284 					}
5285 
5286 					char glow_texture_neb_name[256];
5287 					strncpy(glow_texture_neb_name, glow_texture_name, 256);
5288 					strcat(glow_texture_neb_name, "-neb");
5289 					gpo.glow_neb_bitmap = bm_load(glow_texture_neb_name);
5290 
5291 					if (gpo.glow_neb_bitmap < 0)
5292 					{
5293 						gpo.glow_neb_bitmap = gpo.glow_bitmap;
5294 						nprintf(("Model", "Glowpoint preset nebula texture not found for '%s', using normal glowpoint texture instead\n", gpo.name));
5295 					}
5296 					else
5297 					{
5298 						nprintf(("Model", "Glowpoint preset %s nebula texture num is %d\n", gpo.name, gpo.glow_neb_bitmap));
5299 					}
5300 				}
5301 				else {
5302 					gpo.glow_bitmap_override = true;
5303 				}
5304 			}
5305 
5306 			if (optional_string("$Type:")) {
5307 				stuff_int(&gpo.type);
5308 				gpo.type_override = true;
5309 			}
5310 
5311 			if (optional_string("$Pulse type:")) {
5312 				char pulsetype[33];
5313 				stuff_string(pulsetype, F_NAME, NAME_LENGTH);
5314 				if (!stricmp(pulsetype, "sine")) {
5315 					gpo.pulse_type = PULSE_SIN;
5316 				}
5317 				else if (!stricmp(pulsetype, "cosine")) {
5318 					gpo.pulse_type = PULSE_COS;
5319 				}
5320 				else if (!stricmp(pulsetype, "triangle")) {
5321 					gpo.pulse_type = PULSE_TRI;
5322 				}
5323 				else if (!stricmp(pulsetype, "shiftedtriangle")) {
5324 					gpo.pulse_type = PULSE_SHIFTTRI;
5325 				}
5326 			}
5327 
5328 			if (optional_string("$Pulse period:")) {
5329 				stuff_int(&gpo.pulse_period);
5330 				gpo.pulse_period_override = true;
5331 			}
5332 
5333 			if (optional_string("$Pulse amplitude:")) {
5334 				stuff_float(&gpo.pulse_amplitude);
5335 			}
5336 
5337 			if (optional_string("$Pulse bias:")) {
5338 				stuff_float(&gpo.pulse_bias);
5339 			}
5340 
5341 			if (optional_string("$Pulse exponent:")) {
5342 				stuff_float(&gpo.pulse_exponent);
5343 			}
5344 
5345 			if (optional_string("+light")) {
5346 				gpo.is_lightsource = true;
5347 
5348 				if (optional_string("$Light radius multiplier:")) {
5349 					stuff_float(&gpo.radius_multi);
5350 				}
5351 
5352 				required_string("$Light color:");
5353 				int temp;
5354 				stuff_int(&temp);
5355 				gpo.light_color.xyz.x = temp / 255.0f;
5356 				stuff_int(&temp);
5357 				gpo.light_color.xyz.y = temp / 255.0f;
5358 				stuff_int(&temp);
5359 				gpo.light_color.xyz.z = temp / 255.0f;
5360 
5361 				if (optional_string("$Light mix color:")) {
5362 					stuff_int(&temp);
5363 					gpo.light_mix_color.xyz.x = temp / 255.0f;
5364 					stuff_int(&temp);
5365 					gpo.light_mix_color.xyz.y = temp / 255.0f;
5366 					stuff_int(&temp);
5367 					gpo.light_mix_color.xyz.z = temp / 255.0f;
5368 				}
5369 
5370 				if (optional_string("+lightcone")) {
5371 					gpo.lightcone = true;
5372 
5373 					if (optional_string("$Cone angle:")) {
5374 						stuff_float(&gpo.cone_angle);
5375 						gpo.cone_inner_angle = cosf((gpo.cone_angle - ((gpo.cone_angle < 20.0f) ? gpo.cone_angle*0.5f : 20.0f)) / 180.0f * PI);
5376 						gpo.cone_angle = cosf(gpo.cone_angle / 180.0f * PI);
5377 					}
5378 
5379 					required_string("$Cone direction:");
5380 					stuff_float_list(gpo.cone_direction.a1d, 3);
5381 					if (vm_vec_mag_quick(&gpo.cone_direction) != 0.0f) {
5382 						vm_vec_normalize(&gpo.cone_direction);
5383 					}
5384 					else {
5385 						Warning(LOCATION, "Null vector specified in cone direction for glowpoint override %s. Discarding preset.", gpo.name);
5386 						skip = true;
5387 					}
5388 					if (optional_string("+dualcone")) {
5389 						gpo.dualcone = true;
5390 					}
5391 
5392 					if (optional_string("+rotating")) {
5393 						gpo.rotating = true;
5394 						required_string("$Rotation axis:");
5395 						stuff_float_list(gpo.rotation_axis.a1d, 3);
5396 						if (vm_vec_mag_quick(&gpo.rotation_axis) != 0.0f) {
5397 							vm_vec_normalize(&gpo.rotation_axis);
5398 						}
5399 						else {
5400 							Warning(LOCATION, "Null vector specified in rotation axis for glowpoint override %s. Discarding preset.", gpo.name);
5401 							skip = true;
5402 						}
5403 						required_string("$Rotation speed:");
5404 						stuff_float(&gpo.rotation_speed);
5405 					}
5406 				}
5407 			}
5408 			if (!skip) {
5409 				SCP_vector<glow_point_bank_override>::iterator gpoi = get_glowpoint_bank_override_by_name(gpo.name);
5410 				if (gpoi == glowpoint_bank_overrides.end()) {
5411 					if (!replace) {
5412 						glowpoint_bank_overrides.push_back(gpo);
5413 					}
5414 				}
5415 				else {
5416 					if (!replace) {
5417 						Warning(LOCATION, "+nocreate not specified for glowpoint override that already exists. Discarding duplicate entry: %s", gpo.name);
5418 					}
5419 					else {
5420 						glowpoint_bank_overrides.erase(gpoi);
5421 						glowpoint_bank_overrides.push_back(gpo);
5422 					}
5423 				}
5424 			}
5425 
5426 		}
5427 		required_string("#End");
5428 	} catch (const parse::ParseException& e) {
5429 		mprintf(("Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
5430 		return;
5431 	}
5432 }
5433 
glowpoint_init()5434 void glowpoint_init()
5435 {
5436 	glowpoint_bank_overrides.clear();
5437 	parse_glowpoint_table("glowpoints.tbl");
5438 	parse_modular_table(NOX("*-gpo.tbm"), parse_glowpoint_table);
5439 }
5440 
reset()5441 void model_subsystem::reset()
5442 {
5443     flags.reset();
5444     memset(name, 0, sizeof(name));
5445     memset(subobj_name, 0, sizeof(alt_dmg_sub_name));
5446     memset(alt_sub_name, 0, sizeof(alt_sub_name));
5447     memset(alt_dmg_sub_name, 0, sizeof(alt_dmg_sub_name));
5448     subobj_num = 0;
5449     model_num = 0;
5450     type = 0;
5451     pnt.xyz.x = pnt.xyz.y = pnt.xyz.z = 0.0f;
5452     radius = 0;
5453 
5454     max_subsys_strength = 0;
5455     armor_type_idx = 0;
5456 
5457     memset(crewspot, 0, sizeof(crewspot));
5458     turret_norm.xyz.x = turret_norm.xyz.y = turret_norm.xyz.z = 0.0f;
5459 
5460     turret_fov = 0;
5461     turret_max_fov = 0;
5462     turret_base_fov = 0;
5463     turret_num_firing_points = 0;
5464     for (auto it = std::begin(turret_firing_point); it != std::end(turret_firing_point); ++it)
5465         it->xyz.x = it->xyz.y = it->xyz.z = 0.0f;
5466     turret_gun_sobj = 0;
5467     turret_turning_rate = 0;
5468     turret_base_rotation_snd = gamesnd_id();
5469     turret_base_rotation_snd_mult = 0;
5470     turret_gun_rotation_snd = gamesnd_id();
5471     turret_gun_rotation_snd_mult = 0;
5472 
5473     alive_snd = gamesnd_id();
5474     dead_snd = gamesnd_id();
5475     rotation_snd = gamesnd_id();
5476 
5477     engine_wash_pointer = NULL;
5478     turn_rate = 0;
5479     weapon_rotation_pbank = 0;
5480     stepped_rotation = NULL;
5481 
5482     awacs_intensity = 0.0f;
5483     awacs_radius = 0.0f;
5484 
5485     for (auto it = std::begin(primary_banks); it != std::end(primary_banks); ++it)
5486         *it = 0;
5487     for (auto it = std::begin(primary_bank_capacity); it != std::end(primary_bank_capacity); ++it)
5488         *it = 0;
5489     for (auto it = std::begin(secondary_banks); it != std::end(secondary_banks); ++it)
5490         *it = 0;
5491     for (auto it = std::begin(secondary_bank_capacity); it != std::end(secondary_bank_capacity); ++it)
5492         *it = 0;
5493 
5494     path_num = 0;
5495 
5496     n_triggers = 0;
5497     triggers = NULL;
5498 
5499     turret_reset_delay = 0;
5500 
5501     for (auto it = std::begin(target_priority); it != std::end(target_priority); ++it)
5502         *it = 0;
5503 
5504     num_target_priorities = 0;
5505 
5506     optimum_range = 0;
5507     favor_current_facing = 0;
5508 
5509     turret_rof_scaler = 0;
5510 
5511     turret_max_bomb_ownage = 0;
5512     turret_max_target_ownage = 0;
5513 
5514 	beam_warmdown_program = actions::ProgramSet();
5515 }
5516 
model_subsystem()5517 model_subsystem::model_subsystem() {
5518 	reset();
5519 }
5520 
convert_sldc_to_slc2(ubyte * sldc,ubyte * slc2,uint tree_size)5521 uint convert_sldc_to_slc2(ubyte* sldc, ubyte* slc2, uint tree_size)
5522 {
5523 	//ShivanSpS SLDC must be converted to SLC2 in order to be used by shield collision system
5524 	//Convert SLDC to SLC2
5525 	uint node_size, node_type_int, new_tree_size = 0, count = 0;
5526 	char node_type_char;
5527 
5528 	//Process the SLDC tree to the end
5529 	while (count < tree_size) {
5530 		//Save Node type and size
5531 		memcpy(&node_type_char, sldc, 1);
5532 		memcpy(&node_size, sldc + 1, 4);
5533 
5534 		//Convert Node type to int
5535 		node_type_int = (int)node_type_char;
5536 
5537 		//Copy the node type and new node size, move pointers
5538 		memcpy(slc2, &node_type_int, 4);
5539 		node_size += 3;
5540 		memcpy(slc2 + 4, &node_size, 4);
5541 		node_size -= 3;
5542 		slc2 += 8;
5543 		sldc += 5;
5544 
5545 
5546 		//Copy Vectors
5547 		memcpy(slc2, sldc, 24);
5548 		slc2 += 24;
5549 		sldc += 24;
5550 
5551 		if (node_type_char == 0) {
5552 			//Front and back offsets must be adjusted
5553 			uint front, back, newback = 0;
5554 			ubyte* p;
5555 
5556 			p = sldc - 29;
5557 			memcpy(&back, p + 33, 4);
5558 
5559 			//I need to find the new distance to back.
5560 			while (p < sldc + back - 29) {
5561 				uint ns;
5562 				memcpy(&ns, p + 1, 4);
5563 				p += ns;
5564 				newback += ns + 3;
5565 
5566 			}
5567 			//Copy offsets
5568 			front = node_size + 3;
5569 			memcpy(slc2, &front, 4); //Front is always this node size+3;
5570 			memcpy(slc2 + 4, &newback, 4);
5571 
5572 			slc2 += 8;
5573 			sldc += 8;
5574 		}
5575 		else {
5576 			//Copy the remaining data on the node
5577 			memcpy(slc2, sldc, node_size - 29);
5578 
5579 			//Move pointers
5580 			slc2 += node_size - 29;
5581 			sldc += node_size - 29;
5582 		}
5583 		//Count the new tree size and move the counter
5584 		count += node_size;
5585 		new_tree_size += node_size + 3;
5586 	}
5587 
5588 	//return the SLC2 tree size
5589 	return new_tree_size;
5590 }
5591 
align_bsp_data(ubyte * bsp_in,ubyte * bsp_out,uint bsp_size)5592 uint align_bsp_data(ubyte* bsp_in, ubyte* bsp_out, uint bsp_size)
5593 {
5594 	//ShivanSpS
5595 	ubyte* end;
5596 	uint copied = 0;
5597 	end = bsp_in + bsp_size;
5598 
5599 	uint bsp_chunk_type, bsp_chunk_size;
5600 	do {
5601 		//Read Chunk type and size
5602 		memcpy(&bsp_chunk_type, bsp_in, 4);
5603 		memcpy(&bsp_chunk_size, bsp_in + 4, 4);
5604 
5605 		//Chunk type 0 is EOF, but the size is read as 0, it needs to be adjusted
5606 		if (bsp_chunk_type == 0)
5607 			bsp_chunk_size = 4;
5608 
5609 		//mprintf(("|%d | %d|\n",bsp_chunk_type,bsp_chunk_size));
5610 
5611 		//DEFPOINTS is the only bsp data chunk that could be unaligned
5612 		if (bsp_chunk_type == 1) {
5613 			//if the size is not divisible by 4 align it, otherwise copy it.
5614 			if ((bsp_chunk_size % 4) != 0) {
5615 				//mprintf(("BSP DEFPOINTS DATA ALIGNED.\n"));
5616 				//Get the new size
5617 				uint newsize = bsp_chunk_size + 4 - (bsp_chunk_size % 4);
5618 				//Copy the entire chunk to dest
5619 				memcpy(bsp_out, bsp_in, bsp_chunk_size);
5620 				//Write the new chunk size on dest
5621 				memcpy(bsp_out + 4, &newsize, 4);
5622 				//The the position of vertex data
5623 				uint vertex_offset;
5624 				memcpy(&vertex_offset, bsp_in + 16, 4);
5625 				//Move vertex data to the back of the chunk
5626 				memmove(bsp_out + vertex_offset + (newsize - bsp_chunk_size), bsp_out + vertex_offset, bsp_chunk_size - vertex_offset);
5627 				vertex_offset += (newsize - bsp_chunk_size);
5628 				//Write new vertex offset
5629 				memcpy(bsp_out + 16, &vertex_offset, 4);
5630 				//Move pointers
5631 				bsp_in += bsp_chunk_size;
5632 				bsp_out += newsize;
5633 				copied += newsize;
5634 			}
5635 			else {
5636 				//if aligned just copy it
5637 				memcpy(bsp_out, bsp_in, bsp_chunk_size);
5638 				bsp_in += bsp_chunk_size;
5639 				bsp_out += bsp_chunk_size;
5640 				copied += bsp_chunk_size;
5641 			}
5642 		}
5643 		else {
5644 			//If the chunk is not a defpoint just copy it
5645 			memcpy(bsp_out, bsp_in, bsp_chunk_size);
5646 			bsp_in += bsp_chunk_size;
5647 			bsp_out += bsp_chunk_size;
5648 			copied += bsp_chunk_size;
5649 		}
5650 	} while (bsp_in < end);
5651 
5652 	//Returns the size of the aligned bsp_data
5653 	return copied;
5654 }