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( ¢er->xyz.x );
4917 center->xyz.y = INTEL_FLOAT( ¢er->xyz.y );
4918 center->xyz.z = INTEL_FLOAT( ¢er->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( ¢er_point );
4940
4941 for (i=0;i<nv;i++) {
4942 vm_vec_add2( ¢er_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( ¢er_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( ¢er->xyz.x );
4978 center->xyz.y = INTEL_FLOAT( ¢er->xyz.y );
4979 center->xyz.z = INTEL_FLOAT( ¢er->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( ¢er_point );
4995
4996 for (i=0;i<nv;i++) {
4997 vm_vec_add2( ¢er_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( ¢er_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( ¢er->xyz.x );
5042 center->xyz.y = INTEL_FLOAT( ¢er->xyz.y );
5043 center->xyz.z = INTEL_FLOAT( ¢er->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 }