FS2_Open
Open source remastering of the Freespace 2 engine
modelread.cpp
Go to the documentation of this file.
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 <string.h>
13 #include <ctype.h>
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 "freespace2/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 
41 {
42  {"no lighting", MR_NO_LIGHTING, 0},
43  {"transparent", MR_ALL_XPARENT, 0},
44  {"no Zbuffer", MR_NO_ZBUFFER, 0},
45  {"no cull", MR_NO_CULL, 0},
46  {"no glowmaps", MR_NO_GLOWMAPS, 0},
47  {"force clamp", MR_FORCE_CLAMP, 0},
48 };
49 
51 
52 #define MAX_SUBMODEL_COLLISION_ROT_ANGLE (PI / 6.0f) // max 30 degrees per frame
53 
54 // info for special polygon lists
55 
58 
60 
61 static int model_initted = 0;
62 extern int Cmdline_nohtl;
63 
64 #ifndef NDEBUG
65 CFILE *ss_fp = NULL; // file pointer used to dump subsystem information
66 char model_filename[_MAX_PATH]; // temp used to store filename
68 int ss_warning_shown = 0; // have we shown the warning dialog concerning the subsystems?
69 int Model_ram = 0; // How much RAM the models use total
70 #endif
71 
72 static uint Global_checksum = 0;
73 
74 // Anything less than this is considered incompatible.
75 #define PM_COMPATIBLE_VERSION 1900
76 
77 // Anything greater than or equal to PM_COMPATIBLE_VERSION and
78 // whose major version is less than or equal to this is considered
79 // compatible.
80 #define PM_OBJFILE_MAJOR_VERSION 30
81 
82 static int Model_signature = 0;
83 
88 
89 void model_set_subsys_path_nums(polymodel *pm, int n_subsystems, model_subsystem *subsystems);
91 
92 
93 // Goober5000 - see SUBSYSTEM_X in model.h
94 // NOTE: Each subsystem must match up with its #define, or there will be problems
96 {
97  "None",
98  "Engines",
99  "Turrets",
100  "Radar",
101  "Navigation",
102  "Communications",
103  "Weapons",
104  "Sensors",
105  "Solar panels",
106  "Gas collection",
107  "Activation",
108  "Unknown"
109 };
110 
111 
112 //WMC - For general compatibility stuff.
113 //Note that the order of the items in this list
114 //determine the order that they are tried in ai_goal_fixup_dockpoints
116 {
117  { "cargo", DOCK_TYPE_CARGO, 0 },
118  { "rearm", DOCK_TYPE_REARM, 0 },
119  { "generic", DOCK_TYPE_GENERIC, 0 }
120 };
121 
123 
125 
126 
127 // Goober5000 - reimplementation of Bobboau's $dumb_rotation feature in a way that works with the rest of the model instance system
128 // note: since these data types are only ever used in this file, they don't need to be in model.h
129 
131 {
132 public:
135 
136  submodel_intrinsic_rotation(int _submodel_num, float _turn_rate)
137  : submodel_num(_submodel_num)
138  {
139  memset(&submodel_info_1, 0, sizeof(submodel_info_1));
140  submodel_info_1.cur_turn_rate = _turn_rate;
141  submodel_info_1.desired_turn_rate = _turn_rate;
142  }
143 };
144 
146 {
147 public:
148  bool is_ship;
151 
152  intrinsic_rotation(bool _is_ship, int _model_instance_num)
153  : is_ship(_is_ship), model_instance_num(_model_instance_num), list()
154  {}
155 };
156 
158 
159 
160 // Free up a model, getting rid of all its memory
161 // With the basic page in system this can be called from outside of modelread.cpp
162 void model_unload(int modelnum, int force)
163 {
164  int i, j, num;
165 
166  if ( modelnum >= MAX_POLYGON_MODELS ) {
167  num = modelnum % MAX_POLYGON_MODELS;
168  } else {
169  num = modelnum;
170  }
171 
172  if ( (num < 0) || (num >= MAX_POLYGON_MODELS)) {
173  return;
174  }
175 
176  polymodel *pm = Polygon_models[num];
177 
178  if ( !pm ) {
179  return;
180  }
181 
182  Assert( pm->used_this_mission >= 0 );
183 
184  if (!force && (--pm->used_this_mission > 0))
185  return;
186 
187  mprintf(("Unloading model '%s' from slot '%i'\n", pm->filename, num));
188 
189  // so that the textures can be released
190  pm->used_this_mission = 0;
191 
192  // we want to call bm_release() from here rather than just bm_unload() in order
193  // to get the slots back so we set "release" to true.
194  model_page_out_textures(pm->id, true);
195 
196 #ifndef NDEBUG
197  Model_ram -= pm->ram_used;
198 #endif
199 
200  safe_kill(pm->ship_bay);
201 
202  if (pm->paths) {
203  for (i=0; i<pm->n_paths; i++ ) {
204  for (j=0; j<pm->paths[i].nverts; j++ ) {
205  if ( pm->paths[i].verts[j].turret_ids ) {
206  vm_free(pm->paths[i].verts[j].turret_ids);
207  }
208  }
209  if (pm->paths[i].verts) {
210  vm_free(pm->paths[i].verts);
211  }
212  }
213  vm_free(pm->paths);
214  }
215 
216  if ( pm->shield.verts ) {
217  vm_free( pm->shield.verts );
218  }
219 
220  if ( pm->shield.tris ) {
221  vm_free(pm->shield.tris);
222  }
223 
224  if ( pm->missile_banks ) {
225  vm_free(pm->missile_banks);
226  }
227 
228  if ( pm->docking_bays ) {
229  for (i=0; i<pm->n_docks; i++ ) {
230  if ( pm->docking_bays[i].splines ) {
231  vm_free( pm->docking_bays[i].splines );
232  }
233  }
234  vm_free(pm->docking_bays);
235  }
236 
237 
238  if ( pm->thrusters ) {
239  for (i = 0; i < pm->n_thrusters; i++) {
240  if (pm->thrusters[i].points)
241  vm_free(pm->thrusters[i].points);
242  }
243 
244  vm_free(pm->thrusters);
245  }
246 
247  if ( pm->glow_point_banks ) { // free the glows!!! -Bobboau
248  for (i = 0; i < pm->n_glow_point_banks; i++) {
249  if (pm->glow_point_banks[i].points)
251  }
252 
254  }
255 
256 #ifndef NDEBUG
257  if ( pm->debug_info ) {
258  vm_free(pm->debug_info);
259  }
260 #endif
261 
262  model_octant_free( pm );
263 
264  if (pm->submodel) {
265  for (i = 0; i < pm->n_models; i++) {
266  if ( !Cmdline_nohtl ) {
267  pm->submodel[i].buffer.clear();
268  }
269 
270  if ( pm->submodel[i].bsp_data ) {
271  vm_free(pm->submodel[i].bsp_data);
272  }
273 
274  if ( pm->submodel[i].collision_tree_index >= 0 ) {
276  }
277  }
278 
279  delete[] pm->submodel;
280  }
281 
282  if ( !Cmdline_nohtl ) {
284  }
285 
286  if ( pm->xc ) {
287  vm_free(pm->xc);
288  }
289 
290  if ( pm->lights ) {
291  vm_free(pm->lights);
292  }
293 
294  if ( pm->gun_banks ) {
295  vm_free(pm->gun_banks);
296  }
297 
298  if ( pm->shield_collision_tree ) {
300  }
301 
302  for (i = 0; i < MAX_MODEL_DETAIL_LEVELS; ++i) {
303  pm->detail_buffers[i].clear();
304  }
305 
306  // run through Ship_info and if the model has been loaded we'll need to reset the modelnum to -1.
307  for (auto it = Ship_info.begin(); it != Ship_info.end(); ++it) {
308  if ( pm->id == it->model_num ) {
309  it->model_num = -1;
310  }
311 
312  if ( pm->id == it->cockpit_model_num ) {
313  it->cockpit_model_num = -1;
314  }
315 
316  if ( pm->id == it->model_num_hud ) {
317  it->model_num_hud = -1;
318  }
319  }
320 
321  // need to reset weapon models as well
322  for (int k = 0; k < MAX_WEAPON_TYPES; ++k) {
323  if ( pm->id == Weapon_info[k].model_num ) {
324  Weapon_info[k].model_num = -1;
325  }
326  if ( pm->id == Weapon_info[k].external_model_num ) {
328  }
329  }
330 
331  pm->id = 0;
332  delete pm;
333 
334  Polygon_models[num] = NULL;
335 }
336 
338 {
339  int i;
340 
341  if ( !model_initted) {
342  model_init();
343  return;
344  }
345 
346  mprintf(( "Freeing all existing models...\n" ));
348 
349  for (i=0;i<MAX_POLYGON_MODELS;i++) {
350  // forcefully unload all loaded models (be careful with this)
351  model_unload(i, 1);
352  }
353 }
354 
356 {
357  size_t i;
358 
359  // free any outstanding model instances
360  for ( i = 0; i < Polygon_model_instances.size(); ++i ) {
361  if ( Polygon_model_instances[i] ) {
363  }
364  }
365 
366  // 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
367  extern int Nmodel_instance_num;
368  Nmodel_instance_num = -1;
369 
370  Polygon_model_instances.clear();
371 }
372 
374 {
375  int i;
376 
377  if ( !model_initted ) {
378  model_init();
379  return;
380  }
381 
382  mprintf(( "Starting model page in...\n" ));
383 
384  for (i=0; i<MAX_POLYGON_MODELS; i++) {
385  if (Polygon_models[i] != NULL)
386  Polygon_models[i]->used_this_mission = 0;
387  }
388 }
389 
391 {
392  int i;
393 
394  Assert( model_initted );
395 
396  mprintf(( "Stopping model page in...\n" ));
397 
398  for (i=0; i<MAX_POLYGON_MODELS; i++) {
399  if (Polygon_models[i] == NULL)
400  continue;
401 
402  if (Polygon_models[i]->used_this_mission)
403  continue;
404 
405  model_unload(i);
406  }
407 }
408 
410 {
411  int i;
412 
413  if ( model_initted ) {
414  Int3(); // Model_init shouldn't be called twice!
415  return;
416  }
417 
418 #ifndef NDEBUG
419  Model_ram = 0;
420 #endif
421 
422  for (i=0;i<MAX_POLYGON_MODELS;i++) {
423  Polygon_models[i] = NULL;
424  }
425 
426  atexit( model_free_all );
427  model_initted = 1;
428 }
429 
430 // routine to parse out values from a user property field of an object
431 void get_user_prop_value(char *buf, char *value)
432 {
433  char *p, *p1, c;
434 
435  p = buf;
436  while ( isspace(*p) || (*p == '=') ) // skip white space and equal sign
437  p++;
438  p1 = p;
439  while ( !iscntrl(*p1) )
440  p1++;
441  c = *p1;
442  *p1 = '\0';
443  strcpy(value, p);
444  *p1 = c;
445 }
446 
447 // routine to look for one of the specified user properties
448 // 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)
449 // returns the index of the property found, or -1 if not found
450 // NB: the first recognized option is returned, so if one option is a substring of another, put it later in the list!
451 int prop_string(char *props, char **p, int n_args, ...)
452 {
453  char *pos = nullptr;
454  va_list args;
455  int index = -1;
456 
457  va_start(args, n_args);
458 
459  for (int i = 0; i < n_args; ++i)
460  {
461  const char *option = va_arg(args, const char *);
462 
463  // look for our option in the props fields
464  if ((pos = strstr(props, option)) != nullptr)
465  {
466  // we found it
467  index = i;
468 
469  // so advance past the string and its following character
470  pos += strlen(option);
471  pos++;
472 
473  break;
474  }
475  }
476 
477  va_end(args);
478 
479  // if we have a p, assign *p
480  // (if nothing was found, *p will be nullptr)
481  if (p != nullptr)
482  *p = pos;
483 
484  return index;
485 }
486 
487 // syntactic sugar
488 int prop_string(char *props, char **p, const char *option0)
489 {
490  return prop_string(props, p, 1, option0);
491 }
492 int prop_string(char *props, char **p, const char *option0, const char *option1)
493 {
494  return prop_string(props, p, 2, option0, option1);
495 }
496 int prop_string(char *props, char **p, const char *option0, const char *option1, const char *option2)
497 {
498  return prop_string(props, p, 3, option0, option1, option2);
499 }
500 
501 // funciton to copy model data from one subsystem set to another subsystem set. This function
502 // is called when two ships use the same model data, but since the model only gets read in one time,
503 // the subsystem data is only present in one location. The ship code will call this routine to fix
504 // this situation by copying stuff from the source subsystem set to the dest subsystem set.
505 void model_copy_subsystems( int n_subsystems, model_subsystem *d_sp, model_subsystem *s_sp )
506 {
507  int i, j;
508  model_subsystem *source, *dest;
509 
510  for (i = 0; i < n_subsystems; i++ ) {
511  source = &s_sp[i];
512  for ( j = 0; j < n_subsystems; j++ ) {
513  dest = &d_sp[j];
514  if ( !subsystem_stricmp( source->subobj_name, dest->subobj_name) ) {
515  dest->flags |= (source->flags & MSS_MODEL_FLAG_MASK);
516  dest->flags2 |= (source->flags2 & MSS_MODEL_FLAG2_MASK);
517  dest->subobj_num = source->subobj_num;
518  dest->model_num = source->model_num;
519  dest->pnt = source->pnt;
520  dest->radius = source->radius;
521  dest->type = source->type;
522  dest->turn_rate = source->turn_rate;
523  dest->turret_gun_sobj = source->turret_gun_sobj;
524 
525  strcpy_s( dest->name, source->name );
526 
527  if ( dest->type == SUBSYSTEM_TURRET ) {
528  int nfp;
529 
530  dest->turret_fov = source->turret_fov;
532  dest->turret_norm = source->turret_norm;
533  dest->turret_matrix = source->turret_matrix;
534 
535  for (nfp = 0; nfp < dest->turret_num_firing_points; nfp++ )
536  dest->turret_firing_point[nfp] = source->turret_firing_point[nfp];
537 
538  if ( dest->flags & MSS_FLAG_CREWPOINT )
539  strcpy_s(dest->crewspot, source->crewspot);
540  }
541  break;
542  }
543  }
544  if ( j == n_subsystems )
545  Int3(); // get allender -- something is amiss with models
546 
547  }
548 }
549 
550 // routine to get/set subsystem information
551 static void set_subsystem_info(int model_num, model_subsystem *subsystemp, char *props, char *dname)
552 {
553  char *p;
554  char buf[64];
555  char lcdname[256];
556  int idx;
557 
558  if ( (p = strstr(props, "$name")) != NULL)
559  get_user_prop_value(p+5, subsystemp->name);
560  else
561  strcpy_s( subsystemp->name, dname );
562 
563  strcpy_s(lcdname, dname);
564  strlwr(lcdname);
565 
566  // check the name for its specific type
567  if ( strstr(lcdname, "engine") ) {
568  subsystemp->type = SUBSYSTEM_ENGINE;
569  } else if ( strstr(lcdname, "radar") ) {
570  subsystemp->type = SUBSYSTEM_RADAR;
571  } else if ( strstr(lcdname, "turret") ) {
572  float angle;
573 
574  subsystemp->type = SUBSYSTEM_TURRET;
575  if ( (p = strstr(props, "$fov")) != NULL )
576  get_user_prop_value(p+4, buf); // get the value of the fov
577  else
578  strcpy_s(buf,"180");
579  angle = fl_radians(atoi(buf))/2.0f;
580  subsystemp->turret_fov = cosf(angle);
581  subsystemp->turret_num_firing_points = 0;
582 
583  if ( (p = strstr(props, "$crewspot")) != NULL) {
584  subsystemp->flags |= MSS_FLAG_CREWPOINT;
585  get_user_prop_value(p+9, subsystemp->crewspot);
586  }
587 
588  } else if ( strstr(lcdname, "navigation") ) {
589  subsystemp->type = SUBSYSTEM_NAVIGATION;
590  } else if ( strstr(lcdname, "communication") ) {
591  subsystemp->type = SUBSYSTEM_COMMUNICATION;
592  } else if ( strstr(lcdname, "weapon") ) {
593  subsystemp->type = SUBSYSTEM_WEAPONS;
594  } else if ( strstr(lcdname, "sensor") ) {
595  subsystemp->type = SUBSYSTEM_SENSORS;
596  } else if ( strstr(lcdname, "solar") ) {
597  subsystemp->type = SUBSYSTEM_SOLAR;
598  } else if ( strstr(lcdname, "gas") ) {
599  subsystemp->type = SUBSYSTEM_GAS_COLLECT;
600  } else if ( strstr(lcdname, "activator") ) {
601  subsystemp->type = SUBSYSTEM_ACTIVATION;
602  } else { // If unrecognized type, set to unknown so artist can continue working...
603  subsystemp->type = SUBSYSTEM_UNKNOWN;
604  mprintf(("Subsystem '%s' on ship %s is not recognized as a common subsystem type\n", dname, model_get(model_num)->filename));
605  }
606 
607  if ( (strstr(props, "$triggered")) != NULL ) {
608  subsystemp->flags |= MSS_FLAG_ROTATES;
609  subsystemp->flags |= MSS_FLAG_TRIGGERED;
610  }
611 
612  // Dumb-Rotating subsystem
613  if (prop_string(props, nullptr, "$dumb_rotate") >= 0) {
614  // no special subsystem handling needed here, but make sure we didn't specify both methods
615  if (prop_string(props, nullptr, "$rotate") >= 0) {
616  Warning(LOCATION, "Subsystem '%s' on ship %s cannot have both rotation and dumb-rotation!", dname, model_get(model_num)->filename);
617  }
618  }
619  // Rotating subsystem
620  else if ((idx = prop_string(props, &p, "$rotate_time", "$rotate_rate", "$rotate")) >= 0) {
621  subsystemp->flags |= MSS_FLAG_ROTATES;
622 
623  // get value for (a) complete rotation (b) step (c) activation
624  get_user_prop_value(p, buf); // note: p points to the value since we used prop_string
625 
626  // for retail compatibility, $rotate means $rotate_time
627  float turn_rate;
628  if (idx == 0 || idx == 2) {
629  float turn_time = static_cast<float>(atof(buf));
630  if (turn_time == 0.0f) {
631  Warning(LOCATION, "Rotation has a turn time of 0 for subsystem '%s' on ship %s!", dname, model_get(model_num)->filename);
632  turn_rate = 1.0f;
633  } else {
634  turn_rate = PI2 / turn_time;
635  }
636  } else {
637  turn_rate = static_cast<float>(atof(buf));
638  }
639 
640  // CASE OF WEAPON ROTATION (primary only)
641  if ( (p = strstr(props, "$pbank")) != NULL) {
642  subsystemp->flags |= MSS_FLAG_ARTILLERY;
643 
644  // get which pbank should trigger rotation
645  get_user_prop_value(p+6, buf);
646  subsystemp->weapon_rotation_pbank = atoi(buf);
647  } // end of weapon rotation stuff
648 
649 
650  // *** determine how the subsys rotates ***
651 
652  // CASE OF STEPPED ROTATION
653  if ( (strstr(props, "$stepped")) != NULL) {
654 
655  subsystemp->stepped_rotation = new(stepped_rotation);
656  subsystemp->flags |= MSS_FLAG_STEPPED_ROTATE;
657 
658  // get number of steps
659  if ( (p = strstr(props, "$steps")) != NULL) {
660  get_user_prop_value(p+6, buf);
661  subsystemp->stepped_rotation->num_steps = atoi(buf);
662  } else {
663  subsystemp->stepped_rotation->num_steps = 8;
664  }
665 
666  // get pause time
667  if ( (p = strstr(props, "$t_paused")) != NULL) {
668  get_user_prop_value(p+9, buf);
669  subsystemp->stepped_rotation->t_pause = (float)atof(buf);
670  } else {
671  subsystemp->stepped_rotation->t_pause = 2.0f;
672  }
673 
674  // get transition time - time to go between steps
675  if ( (p = strstr(props, "$t_transit")) != NULL) {
676  get_user_prop_value(p+10, buf);
677  subsystemp->stepped_rotation->t_transit = (float)atof(buf);
678  } else {
679  subsystemp->stepped_rotation->t_transit = 2.0f;
680  }
681 
682  // get fraction of time spent in accel
683  if ( (p = strstr(props, "$fraction_accel")) != NULL) {
684  get_user_prop_value(p+15, buf);
685  subsystemp->stepped_rotation->fraction = (float)atof(buf);
686  Assert(subsystemp->stepped_rotation->fraction > 0 && subsystemp->stepped_rotation->fraction < 0.5);
687  } else {
688  subsystemp->stepped_rotation->fraction = 0.3f;
689  }
690 
691  int num_steps = subsystemp->stepped_rotation->num_steps;
692  float t_trans = subsystemp->stepped_rotation->t_transit;
693  float fraction = subsystemp->stepped_rotation->fraction;
694 
695  subsystemp->stepped_rotation->max_turn_accel = PI2 / (fraction*(1.0f - fraction) * num_steps * t_trans*t_trans);
696  subsystemp->stepped_rotation->max_turn_rate = PI2 / ((1.0f - fraction) * num_steps *t_trans);
697  }
698 
699  // CASE OF NORMAL CONTINUOUS ROTATION
700  else {
701  subsystemp->turn_rate = turn_rate;
702  }
703  }
704 }
705 
706 // used in collision code to check if submode rotates too far
708 {
709  vec3d diff;
710  vm_vec_sub(&diff, (vec3d*)&sii->angs, (vec3d*)&sii->prev_angs);
711 
712  // find the angle
713  float delta_angle = vm_vec_mag(&diff);
714 
715  // make sure we get the short way around
716  if (delta_angle > PI) {
717  delta_angle = (PI2 - delta_angle);
718  }
719 
720  return delta_angle;
721 }
722 
723 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 )
724 {
725  int i;
726  model_subsystem *subsystemp;
727 
728  if ( slist==NULL ) {
729 #ifndef NDEBUG
730  if (!ss_warning_shown) {
731  mprintf(("No subsystems found for model \"%s\".\n", model_get(model_num)->filename));
732  ss_warning_shown = 1;
733  }
734 #endif
735  return; // For TestCode, POFView, etc don't bother
736  }
737 
738  // try to find the name of the subsystem passed here on the list of subsystems currently on the
739  // ship. Assign the values only when the right subsystem is found
740 
741  for (i = 0; i < n_subsystems; i++ ) {
742  subsystemp = &slist[i];
743 
744 #ifndef NDEBUG
745  // Goober5000 - notify if there's a mismatch
746  if ( stricmp(subobj_name, subsystemp->subobj_name) && !subsystem_stricmp(subobj_name, subsystemp->subobj_name) )
747  {
748  nprintf(("Model", "NOTE: Subsystem \"%s\" in model \"%s\" is represented as \"%s\" in ships.tbl. This works fine in FSO v3.6 and up, "
749  "but is not compatible with FS2 retail.\n", subobj_name, model_get(model_num)->filename, subsystemp->subobj_name));
750 
751  }
752 #endif
753 
754  if (!subsystem_stricmp(subobj_name, subsystemp->subobj_name))
755  {
756  //commented by Goober5000 because this is also set when the table is parsed
757  //subsystemp->flags = 0;
758 
759  subsystemp->subobj_num = subobj_num;
760  subsystemp->turret_gun_sobj = -1;
761  subsystemp->model_num = model_num;
762  subsystemp->pnt = *pnt; // use the offset to get the center point of the subsystem
763  subsystemp->radius = rad;
764  set_subsystem_info(model_num, subsystemp, props, subobj_name);
765  strcpy_s(subsystemp->subobj_name, subobj_name); // copy the object name
766  return;
767  }
768  }
769 #ifndef NDEBUG
770  char bname[_MAX_FNAME];
771 
772  if ( !ss_warning_shown) {
773  _splitpath(model_filename, NULL, NULL, bname, NULL);
774  // Lets still give a comment about it and not just erase it
775  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 );
776  mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename));
777  ss_warning_shown = 1;
778  } else
779 #endif
780  mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename));
781 
782 #ifndef NDEBUG
783  if ( ss_fp ) {
784  _splitpath(model_filename, NULL, NULL, bname, NULL);
785  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));
786  char tmp_buffer[128];
787  sprintf(tmp_buffer, "$Subsystem:\t\t\t%s,1,0.0\n", subobj_name);
788  cfputs(tmp_buffer, ss_fp);
789  }
790 #endif
791 
792 }
793 
794 void print_family_tree( polymodel *obj, int modelnum, char * ident, int islast )
795 {
796  char temp[50];
797 
798  if ( modelnum < 0 ) return;
799  if (obj==NULL) return;
800 
801  if (ident[0] == '\0') {
802  mprintf(( " %s", obj->submodel[modelnum].name ));
803  sprintf( temp, " " );
804  } else if ( islast ) {
805  mprintf(( "%s:%s", ident, obj->submodel[modelnum].name ));
806  sprintf( temp, "%s ", ident );
807  } else {
808  mprintf(( "%s:%s", ident, obj->submodel[modelnum].name ));
809  sprintf( temp, "%s ", ident );
810  }
811 
812  mprintf(( "\n" ));
813 
814  int child = obj->submodel[modelnum].first_child;
815  while( child > -1 ) {
816  if ( obj->submodel[child].next_sibling < 0 )
817  print_family_tree( obj, child, temp,1 );
818  else
819  print_family_tree( obj, child, temp,0 );
820  child = obj->submodel[child].next_sibling;
821  }
822 }
823 
825 {
826  print_family_tree( obj, 0, "", 0 );
827  key_getch();
828 }
829 
831 {
832  int i;
833  for (i=0; i<obj->n_models; i++ ) {
834  obj->submodel[i].num_children = 0;
835  obj->submodel[i].first_child = -1;
836  obj->submodel[i].next_sibling = -1;
837  }
838 
839  for (i=0; i<obj->n_models; i++ ) {
840  int pn;
841  pn = obj->submodel[i].parent;
842  if ( pn > -1 ) {
843  obj->submodel[pn].num_children++;
844  int tmp = obj->submodel[pn].first_child;
845  obj->submodel[pn].first_child = i;
846  obj->submodel[i].next_sibling = tmp;
847  }
848  }
849 }
850 
852 {
853  if (Cmdline_nohtl || Is_standalone) {
854  return;
855  }
856 
857  int i;
858 
859  // initialize empty buffer
861 
862  if (pm->vertex_buffer_id < 0) {
863  Error(LOCATION, "Could not generate vertex buffer for '%s'!", pm->filename);
864  }
865 
866  // determine the size and configuration of each buffer segment
867  for (i = 0; i < pm->n_models; i++) {
869  }
870 
871  // figure out which vertices are transparent
872  for ( i = 0; i < pm->n_models; i++ ) {
873  if ( !pm->submodel[i].is_thruster ) {
875  }
876  }
877 
878  bool use_batched_rendering = true;
879 
880  if ( GLSL_version >= 130 && !Cmdline_no_batching ) {
881  uint stride = 0;
882 
883  // figure out if the vertex stride of this entire model matches. if not, turn off batched rendering for this model
884  for ( i = 0; i < pm->n_models; ++i ) {
885  if ( pm->submodel[i].buffer.model_list != NULL && pm->submodel[i].buffer.stride != stride) {
886  if ( stride == 0 ) {
887  stride = pm->submodel[i].buffer.stride;
888  } else {
889  use_batched_rendering = false;
890  break;
891  }
892  }
893  }
894  } else {
895  use_batched_rendering = false;
896  }
897 
898  // create another set of indexes for the detail buffers
899  if ( use_batched_rendering ) {
900  for ( i = 0; i < pm->n_detail_levels; i++ ) {
902  }
903  }
904 
905  // now actually fill the buffer with our info ...
906  for (i = 0; i < pm->n_models; i++) {
908 
909  // release temporary memory
910  pm->submodel[i].buffer.release();
912  }
913 
914  if ( use_batched_rendering ) {
915  // pack the merged index buffers to the vbo.
916  for ( i = 0; i < pm->n_detail_levels; ++i ) {
917  if ( pm->detail_buffers[i].model_list == NULL ) {
918  continue;
919  }
920 
922  pm->detail_buffers[i].release();
923  }
924 
925  pm->flags |= PM_FLAG_BATCHED;
926  }
927 
928  // ... and then finalize buffer
929  gr_pack_buffer(pm->vertex_buffer_id, NULL);
930 }
931 
932 // Goober5000
933 bool maybe_swap_mins_maxs(vec3d *mins, vec3d *maxs)
934 {
935  float temp;
936  bool swap_was_necessary = false;
937 
938  if (mins->xyz.x > maxs->xyz.x)
939  {
940  temp = mins->xyz.x;
941  mins->xyz.x = maxs->xyz.x;
942  maxs->xyz.x = temp;
943  swap_was_necessary = true;
944  }
945  if (mins->xyz.y > maxs->xyz.y)
946  {
947  temp = mins->xyz.y;
948  mins->xyz.y = maxs->xyz.y;
949  maxs->xyz.y = temp;
950  swap_was_necessary = true;
951  }
952  if (mins->xyz.z > maxs->xyz.z)
953  {
954  temp = mins->xyz.z;
955  mins->xyz.z = maxs->xyz.z;
956  maxs->xyz.z = temp;
957  swap_was_necessary = true;
958  }
959 
960 // This is a mini utility that prints out the proper hex string for the
961 // mins and maxs so that the POF file can be modified in a hex editor.
962 // Currently none of the major POF editors allow editing of bounding boxes.
963 #if 0
964  if (swap_was_necessary)
965  {
966  // use C hackery to convert float values to raw bytes
967  const int NUM_BYTES = 24;
968  typedef struct converter
969  {
970  union
971  {
972  struct
973  {
974  float min_x, min_y, min_z, max_x, max_y, max_z;
975  } _float;
976  ubyte _byte[NUM_BYTES];
977  };
978  } converter;
979 
980  // fill in the values
981  converter z;
982  z._float.min_x = mins->xyz.x;
983  z._float.min_y = mins->xyz.y;
984  z._float.min_z = mins->xyz.z;
985  z._float.max_x = maxs->xyz.x;
986  z._float.max_y = maxs->xyz.y;
987  z._float.max_z = maxs->xyz.z;
988 
989  // prep string
990  char hex_str[5];
991  char text[100 + (5 * NUM_BYTES)];
992  strcpy_s(text, "The following is the correct hex string for the minima and maxima:\n");
993 
994  // append hex values to the string
995  for (int i = 0; i < NUM_BYTES; i++)
996  {
997  sprintf(hex_str, "%02X ", z._byte[i]);
998  strcat_s(text, hex_str);
999  }
1000 
1001  // notify the user
1002  Warning(LOCATION, text);
1003  }
1004 #endif
1005 
1006  return swap_was_necessary;
1007 }
1008 
1009 void model_calc_bound_box( vec3d *box, vec3d *big_mn, vec3d *big_mx)
1010 {
1011  box[0].xyz.x = big_mn->xyz.x; box[0].xyz.y = big_mn->xyz.y; box[0].xyz.z = big_mn->xyz.z;
1012  box[1].xyz.x = big_mx->xyz.x; box[1].xyz.y = big_mn->xyz.y; box[1].xyz.z = big_mn->xyz.z;
1013  box[2].xyz.x = big_mx->xyz.x; box[2].xyz.y = big_mx->xyz.y; box[2].xyz.z = big_mn->xyz.z;
1014  box[3].xyz.x = big_mn->xyz.x; box[3].xyz.y = big_mx->xyz.y; box[3].xyz.z = big_mn->xyz.z;
1015 
1016 
1017  box[4].xyz.x = big_mn->xyz.x; box[4].xyz.y = big_mn->xyz.y; box[4].xyz.z = big_mx->xyz.z;
1018  box[5].xyz.x = big_mx->xyz.x; box[5].xyz.y = big_mn->xyz.y; box[5].xyz.z = big_mx->xyz.z;
1019  box[6].xyz.x = big_mx->xyz.x; box[6].xyz.y = big_mx->xyz.y; box[6].xyz.z = big_mx->xyz.z;
1020  box[7].xyz.x = big_mn->xyz.x; box[7].xyz.y = big_mx->xyz.y; box[7].xyz.z = big_mx->xyz.z;
1021 }
1022 
1023 
1024 void parse_triggers(int &n_trig, queued_animation **triggers, char *props);
1025 
1026 
1027 //reads a binary file containing a 3d model
1028 int read_model_file(polymodel * pm, char *filename, int n_subsystems, model_subsystem *subsystems, int ferror)
1029 {
1030  CFILE *fp;
1031  int version;
1032  int id, len, next_chunk;
1033  int i,j;
1034  vec3d temp_vec;
1035 
1036  fp = cfopen(filename,"rb");
1037 
1038  if (!fp) {
1039  if (ferror == 1) {
1040  Error( LOCATION, "Can't open model file <%s>", filename );
1041  } else if (ferror == 0) {
1042  Warning( LOCATION, "Can't open model file <%s>", filename );
1043  }
1044 
1045  return -1;
1046  }
1047 
1048  // generate checksum for the POF
1049  cfseek(fp, 0, SEEK_SET);
1050  cf_chksum_long(fp, &Global_checksum);
1051  cfseek(fp, 0, SEEK_SET);
1052 
1053 
1054  // code to get a filename to write out subsystem information for each model that
1055  // is read. This info is essentially debug stuff that is used to help get models
1056  // into the game quicker
1057 #if 0
1058  {
1059  char bname[_MAX_FNAME];
1060 
1061  _splitpath(filename, NULL, NULL, bname, NULL);
1062  sprintf(debug_name, "%s.subsystems", bname);
1063  ss_fp = cfopen(debug_name, "wb", CFILE_NORMAL, CF_TYPE_TABLES );
1064  if ( !ss_fp ) {
1065  mprintf(( "Can't open debug file for writing subsystems for %s\n", filename));
1066  } else {
1067  strcpy_s(model_filename, filename);
1068  ss_warning_shown = 0;
1069  }
1070  }
1071 #endif
1072 
1073  id = cfread_int(fp);
1074 
1075  if (id != POF_HEADER_ID)
1076  Error( LOCATION, "Bad ID in model file <%s>",filename);
1077 
1078  // Version is major*100+minor
1079  // So, major = version / 100;
1080  // minor = version % 100;
1081  version = cfread_int(fp);
1082 
1083  //Warning( LOCATION, "POF Version = %d", version );
1084 
1085  if (version < PM_COMPATIBLE_VERSION || (version/100) > PM_OBJFILE_MAJOR_VERSION) {
1086  Warning(LOCATION,"Bad version (%d) in model file <%s>",version,filename);
1087  return 0;
1088  }
1089 
1090  pm->version = version;
1091  Assert( strlen(filename) < FILESPEC_LENGTH );
1092  strcpy_s(pm->filename, filename);
1093 
1094  memset( &pm->view_positions, 0, sizeof(pm->view_positions) );
1095 
1096  // reset insignia counts
1097  pm->num_ins = 0;
1098 
1099  // reset glow points!! - Goober5000
1100  pm->n_glow_point_banks = 0;
1101 
1102  // reset SLDC
1103  pm->shield_collision_tree = NULL;
1104  pm->sldc_size = 0;
1105 
1106  id = cfread_int(fp);
1107  len = cfread_int(fp);
1108  next_chunk = cftell(fp) + len;
1109 
1110  while (!cfeof(fp)) {
1111 
1112 // mprintf(("Processing chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len));
1113 // key_getch();
1114 
1115  switch (id) {
1116 
1117  case ID_OHDR: { //Object header
1118  //vector v;
1119 
1120  //mprintf(0,"Got chunk OHDR, len=%d\n",len);
1121 
1122 #if defined( FREESPACE1_FORMAT )
1123  pm->n_models = cfread_int(fp);
1124 // mprintf(( "Num models = %d\n", pm->n_models ));
1125  pm->rad = cfread_float(fp);
1126  pm->flags = cfread_int(fp); // 1=Allow tiling
1127 #elif defined( FREESPACE2_FORMAT )
1128  pm->rad = cfread_float(fp);
1129  pm->flags = cfread_int(fp); // 1=Allow tiling
1130  pm->n_models = cfread_int(fp);
1131 // mprintf(( "Num models = %d\n", pm->n_models ));
1132 #endif
1133 
1134  // Check for unrealistic radii
1135  if ( pm->rad <= 0.1f )
1136  {
1137  Warning(LOCATION, "Model <%s> has a radius <= 0.1f\n", filename);
1138  }
1139 
1140  pm->submodel = new bsp_info[pm->n_models];
1141 
1142  //Assert(pm->n_models <= MAX_SUBMODELS);
1143 
1144  cfread_vector(&pm->mins,fp);
1145  cfread_vector(&pm->maxs,fp);
1146 
1147  // sanity first!
1148  if (maybe_swap_mins_maxs(&pm->mins, &pm->maxs)) {
1149  Warning(LOCATION, "Inverted bounding box on model '%s'! Swapping values to compensate.", pm->filename);
1150  }
1151  model_calc_bound_box(pm->bounding_box, &pm->mins, &pm->maxs);
1152 
1153  pm->n_detail_levels = cfread_int(fp);
1154  // mprintf(( "There are %d detail levels\n", pm->n_detail_levels ));
1155  for (i=0; i<pm->n_detail_levels;i++ ) {
1156  pm->detail[i] = cfread_int(fp);
1157  pm->detail_depth[i] = 0.0f;
1159  }
1160 
1161  pm->num_debris_objects = cfread_int(fp);
1163  // mprintf(( "There are %d debris objects\n", pm->num_debris_objects ));
1164  for (i=0; i<pm->num_debris_objects;i++ ) {
1165  pm->debris_objects[i] = cfread_int(fp);
1166  // mprintf(( "Debris object %d is model %d.\n", i, pm->debris_objects[i] ));
1167  }
1168 
1169  if ( pm->version >= 1903 ) {
1170 
1171  if ( pm->version >= 2009 ) {
1172 
1173  pm->mass = cfread_float(fp);
1174  cfread_vector( &pm->center_of_mass, fp );
1175  cfread_vector( &pm->moment_of_inertia.vec.rvec, fp );
1176  cfread_vector( &pm->moment_of_inertia.vec.uvec, fp );
1177  cfread_vector( &pm->moment_of_inertia.vec.fvec, fp );
1178 
1179  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)) {
1180  Warning(LOCATION, "Moment of inertia values for model %s are invalid. This has to be fixed.\n", pm->filename);
1181  Int3();
1182  }
1183  } else {
1184  // old code where mass wasn't based on area, so do the calculation manually
1185 
1186  float vol_mass = cfread_float(fp);
1187  // Attn: John Slagel: The following is better done in bspgen.
1188  // Convert volume (cubic) to surface area (quadratic) and scale so 100 -> 100
1189  float area_mass = (float) pow(vol_mass, 0.6667f) * 4.65f;
1190 
1191  pm->mass = area_mass;
1192  float mass_ratio = vol_mass / area_mass;
1193 
1194  cfread_vector( &pm->center_of_mass, fp );
1195  cfread_vector( &pm->moment_of_inertia.vec.rvec, fp );
1196  cfread_vector( &pm->moment_of_inertia.vec.uvec, fp );
1197  cfread_vector( &pm->moment_of_inertia.vec.fvec, fp );
1198 
1199  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)) {
1200  Warning(LOCATION, "Moment of inertia values for model %s are invalid. This has to be fixed.\n", pm->filename);
1201  Int3();
1202  }
1203 
1204  // John remove this with change to bspgen
1205  vm_vec_scale( &pm->moment_of_inertia.vec.rvec, mass_ratio );
1206  vm_vec_scale( &pm->moment_of_inertia.vec.uvec, mass_ratio );
1207  vm_vec_scale( &pm->moment_of_inertia.vec.fvec, mass_ratio );
1208  }
1209 
1210  // a custom MOI is only used for ships, but we should probably log it anyway
1211  if ( IS_VEC_NULL(&pm->moment_of_inertia.vec.rvec)
1212  && IS_VEC_NULL(&pm->moment_of_inertia.vec.uvec)
1213  && IS_VEC_NULL(&pm->moment_of_inertia.vec.fvec) )
1214  {
1215  mprintf(("Model %s has a null moment of inertia! (This is only a problem if the model is a ship.)\n", filename));
1216  }
1217 
1218  } else {
1219  pm->mass = 50.0f;
1220  vm_vec_zero( &pm->center_of_mass );
1222  vm_vec_scale(&pm->moment_of_inertia.vec.rvec, 0.001f);
1223  vm_vec_scale(&pm->moment_of_inertia.vec.uvec, 0.001f);
1224  vm_vec_scale(&pm->moment_of_inertia.vec.fvec, 0.001f);
1225  }
1226 
1227  // read in cross section info
1228  pm->xc = NULL;
1229  if ( pm->version >= 2014 ) {
1230  pm->num_xc = cfread_int(fp);
1231  if (pm->num_xc > 0) {
1232  pm->xc = (cross_section*) vm_malloc(pm->num_xc*sizeof(cross_section));
1233  for (i=0; i<pm->num_xc; i++) {
1234  pm->xc[i].z = cfread_float(fp);
1235  pm->xc[i].radius = cfread_float(fp);
1236  }
1237  }
1238  } else {
1239  pm->num_xc = 0;
1240  }
1241 
1242  if ( pm->version >= 2007 ) {
1243  pm->num_lights = cfread_int(fp);
1244  //mprintf(( "Found %d lights!\n", pm->num_lights ));
1245 
1246  if (pm->num_lights > 0) {
1247  pm->lights = (bsp_light *)vm_malloc( sizeof(bsp_light)*pm->num_lights );
1248  for (i=0; i<pm->num_lights; i++ ) {
1249  cfread_vector(&pm->lights[i].pos,fp);
1250  pm->lights[i].type = cfread_int(fp);
1251  pm->lights[i].value = 0.0f;
1252  }
1253  }
1254  } else {
1255  pm->num_lights = 0;
1256  pm->lights = NULL;
1257  }
1258 
1259  break;
1260  }
1261 
1262  case ID_SOBJ: { //Subobject header
1263  int n;
1264  char *p, props[MAX_PROP_LEN];
1265 // float d;
1266 
1267  //mprintf(0,"Got chunk SOBJ, len=%d\n",len);
1268 
1269  n = cfread_int(fp);
1270  //mprintf(("SOBJ IDed itself as %d", n));
1271 
1272  Assert(n < pm->n_models );
1273 
1274 #if defined( FREESPACE2_FORMAT )
1275  pm->submodel[n].rad = cfread_float(fp); //radius
1276 #endif
1277 
1278  pm->submodel[n].parent = cfread_int(fp);
1279 
1280 // cfread_vector(&pm->submodel[n].norm,fp);
1281 // d = cfread_float(fp);
1282 // cfread_vector(&pm->submodel[n].pnt,fp);
1283  cfread_vector(&pm->submodel[n].offset,fp);
1284 
1285 // 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 ));
1286 
1287 #if defined ( FREESPACE1_FORMAT )
1288  pm->submodel[n].rad = cfread_float(fp); //radius
1289 #endif
1290 
1291 // pm->submodel[n].tree_offset = cfread_int(fp); //offset
1292 // pm->submodel[n].data_offset = cfread_int(fp); //offset
1293 
1295 
1296  cfread_vector(&pm->submodel[n].min,fp);
1297  cfread_vector(&pm->submodel[n].max,fp);
1298 
1299  pm->submodel[n].name[0] = '\0';
1300 
1301  cfread_string_len(pm->submodel[n].name, MAX_NAME_LEN, fp); // get the name
1302  cfread_string_len(props, MAX_PROP_LEN, fp); // and the user properties
1303 
1304  // Check for unrealistic radii
1305  if ( pm->submodel[n].rad <= 0.1f )
1306  {
1307  Warning(LOCATION, "Submodel <%s> in model <%s> has a radius <= 0.1f\n", pm->submodel[n].name, filename);
1308  }
1309 
1310  // sanity first!
1311  if (maybe_swap_mins_maxs(&pm->submodel[n].min, &pm->submodel[n].max)) {
1312  Warning(LOCATION, "Inverted bounding box on submodel '%s' of model '%s'! Swapping values to compensate.", pm->submodel[n].name, pm->filename);
1313  }
1315 
1316  pm->submodel[n].movement_type = cfread_int(fp);
1317  pm->submodel[n].movement_axis = cfread_int(fp);
1318 
1319  // change turret movement type to MOVEMENT_TYPE_ROT_SPECIAL
1320  if ( strstr(pm->submodel[n].name, "turret") || strstr(pm->submodel[n].name, "gun") || strstr(pm->submodel[n].name, "cannon")) {
1322  pm->submodel[n].can_move = true;
1323  } else
1324 
1325  if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) {
1326  if (strstr(pm->submodel[n].name, "thruster")) {
1329  } else if(strstr(props, "$triggered")) {
1331  }
1332  }
1333 
1334  if ( ( p = strstr(props, "$look_at")) != NULL ) {
1336  get_user_prop_value(p+9, pm->submodel[n].look_at);
1337  pm->submodel[n].look_at_num = -2; // Set this to -2 to mark it as something we need to work out the correct subobject number for later, after all subobjects have been processed
1338 
1339  } else {
1340  pm->submodel[n].look_at_num = -1; // No look_at
1341  }
1342 
1343  // note, this should come BEFORE do_new_subsystem() for proper error handling (to avoid both rotating and dumb-rotating submodel)
1344  int idx = prop_string(props, &p, "$dumb_rotate_time", "$dumb_rotate_rate", "$dumb_rotate");
1345  if (idx >= 0) {
1348 
1349  // do this the same way as regular $rotate
1350  char buf[64];
1351  get_user_prop_value(p, buf);
1352 
1353  // for past SCP compatibility, $dumb_rotate means $dumb_rotate_rate
1354  float turn_rate;
1355  if (idx == 0) {
1356  float turn_time = static_cast<float>(atof(buf));
1357  if (turn_time == 0.0f) {
1358  Warning(LOCATION, "Dumb-Rotation has a turn time of 0 for subsystem '%s' on ship %s!", pm->submodel[n].name, pm->filename);
1359  turn_rate = 1.0f;
1360  } else {
1361  turn_rate = PI2 / turn_time;
1362  }
1363  } else {
1364  turn_rate = static_cast<float>(atof(buf));
1365  }
1366 
1367  pm->submodel[n].dumb_turn_rate = turn_rate;
1368  } else {
1369  pm->submodel[n].dumb_turn_rate = 0.0f;
1370  }
1371 
1372  // Sets can_move on submodels which are of a rotating type or which have such a parent somewhere down the hierarchy
1373  if ((pm->submodel[n].movement_type != MOVEMENT_TYPE_NONE)
1374  || strstr(props, "$triggered") || strstr(props, "$rotate") || strstr(props, "$gun_rotation")) {
1375  pm->submodel[n].can_move = true;
1376  } else if (pm->submodel[n].parent >= 0 && pm->submodel[pm->submodel[n].parent].can_move) {
1377  pm->submodel[n].can_move = true;
1378  }
1379 
1380  if ( pm->submodel[n].name[0] == '\0' ) {
1381  strcpy_s(pm->submodel[n].name, "unknown object name");
1382  }
1383 
1384  bool rotating_submodel_has_subsystem = !(pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT);
1385  if ( ( p = strstr(props, "$special"))!= NULL ) {
1386  char type[64];
1387 
1388  get_user_prop_value(p+9, type);
1389  if ( !stricmp(type, "subsystem") ) { // if we have a subsystem, put it into the list!
1390  do_new_subsystem( n_subsystems, subsystems, n, pm->submodel[n].rad, &pm->submodel[n].offset, props, pm->submodel[n].name, pm->id );
1391  rotating_submodel_has_subsystem = true;
1392  } else if ( !stricmp(type, "no_rotate") ) {
1393  // mark those submodels which should not rotate - ie, those with no subsystem
1396  } else {
1397  // if submodel rotates (via bspgen), then there is either a subsys or special=no_rotate
1399  }
1400  }
1401 
1402  // adding a warning if rotation is specified without movement axis.
1403  if (pm->submodel[n].movement_axis == MOVEMENT_AXIS_NONE) {
1404  if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) {
1405  Warning(LOCATION, "Rotation without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename);
1406  }
1408  Warning(LOCATION, "Intrinsic rotation (e.g. dumb-rotate) without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename);
1409  }
1410  }
1411 
1412 /* if ( strstr(props, "$nontargetable")!= NULL ) {
1413  pm->submodel[n].targetable = 0;
1414  }else{
1415  pm->submodel[n].targetable = 1;
1416  }
1417 */
1418 // pm->submodel[n].n_triggers = 0;
1419 // pm->submodel[n].triggers = NULL;
1420 
1421  //parse_triggers(pm->submodel[n].n_triggers, &pm->submodel[n].triggers, &props[0]);
1422 
1423  if (strstr(props, "$no_collisions") != NULL )
1424  pm->submodel[n].no_collisions = true;
1425  else
1426  pm->submodel[n].no_collisions = false;
1427 
1428  if (strstr(props, "$nocollide_this_only") != NULL )
1429  pm->submodel[n].nocollide_this_only = true;
1430  else
1431  pm->submodel[n].nocollide_this_only = false;
1432 
1433  if (strstr(props, "$collide_invisible") != NULL )
1434  pm->submodel[n].collide_invisible = true;
1435  else
1436  pm->submodel[n].collide_invisible = false;
1437 
1438  if (strstr(props, "$gun_rotation") != NULL)
1439  pm->submodel[n].gun_rotation = true;
1440  else
1441  pm->submodel[n].gun_rotation = false;
1442 
1443  if ( (p = strstr(props, "$lod0_name")) != NULL)
1444  get_user_prop_value(p+10, pm->submodel[n].lod_name);
1445 
1446  if (strstr(props, "$attach_thrusters") != NULL )
1447  pm->submodel[n].attach_thrusters = true;
1448  else
1449  pm->submodel[n].attach_thrusters = false;
1450 
1451  if ( (p = strstr(props, "$detail_box:")) != NULL ) {
1452  p += 12;
1453  while (*p == ' ') p++;
1454  pm->submodel[n].use_render_box = atoi(p);
1455 
1456  if ( (p = strstr(props, "$box_offset:")) != NULL ) {
1457  p += 12;
1458  while (*p == ' ') p++;
1459  pm->submodel[n].render_box_offset.xyz.x = (float)strtod(p, (char **)NULL);
1460  while (*p != ',') p++;
1461  pm->submodel[n].render_box_offset.xyz.y = (float)strtod(++p, (char **)NULL);
1462  while (*p != ',') p++;
1463  pm->submodel[n].render_box_offset.xyz.z = (float)strtod(++p, (char **)NULL);
1464 
1465  pm->submodel[n].use_render_box_offset = true;
1466  }
1467 
1468  if ( (p = strstr(props, "$box_min:")) != NULL ) {
1469  p += 9;
1470  while (*p == ' ') p++;
1471  pm->submodel[n].render_box_min.xyz.x = (float)strtod(p, (char **)NULL);
1472  while (*p != ',') p++;
1473  pm->submodel[n].render_box_min.xyz.y = (float)strtod(++p, (char **)NULL);
1474  while (*p != ',') p++;
1475  pm->submodel[n].render_box_min.xyz.z = (float)strtod(++p, (char **)NULL);
1476  } else {
1477  pm->submodel[n].render_box_min = pm->submodel[n].min;
1478  }
1479 
1480  if ( (p = strstr(props, "$box_max:")) != NULL ) {
1481  p += 9;
1482  while (*p == ' ') p++;
1483  pm->submodel[n].render_box_max.xyz.x = (float)strtod(p, (char **)NULL);
1484  while (*p != ',') p++;
1485  pm->submodel[n].render_box_max.xyz.y = (float)strtod(++p, (char **)NULL);
1486  while (*p != ',') p++;
1487  pm->submodel[n].render_box_max.xyz.z = (float)strtod(++p, (char **)NULL);
1488  } else {
1489  pm->submodel[n].render_box_max = pm->submodel[n].max;
1490  }
1491  }
1492 
1493  if ( (p = strstr(props, "$detail_sphere:")) != NULL ) {
1494  p += 15;
1495  while (*p == ' ') p++;
1496  pm->submodel[n].use_render_sphere = atoi(p);
1497 
1498  if ( (p = strstr(props, "$radius:")) != NULL ) {
1499  p += 8;
1500  while (*p == ' ') p++;
1501  pm->submodel[n].render_sphere_radius = (float)strtod(p, (char **)NULL);
1502  } else {
1504  }
1505 
1506  if ( (p = strstr(props, "$offset:")) != NULL ) {
1507  p += 8;
1508  while (*p == ' ') p++;
1509  pm->submodel[n].render_sphere_offset.xyz.x = (float)strtod(p, (char **)NULL);
1510  while (*p != ',') p++;
1511  pm->submodel[n].render_sphere_offset.xyz.y = (float)strtod(++p, (char **)NULL);
1512  while (*p != ',') p++;
1513  pm->submodel[n].render_sphere_offset.xyz.z = (float)strtod(++p, (char **)NULL);
1514 
1515  pm->submodel[n].use_render_sphere_offset = true;
1516  } else {
1518  }
1519  }
1520 
1521  // Added for new handling of turret orientation - KeldorKatarn
1522  matrix *orient = &pm->submodel[n].orientation;
1523 
1524  if ( (p = strstr(props, "$uvec:")) != NULL ) {
1525  p += 6;
1526 
1527  char *parsed_string = p;
1528 
1529  while (*parsed_string == ' ') {
1530  parsed_string++; // Skip spaces
1531  }
1532 
1533  orient->vec.uvec.xyz.x = (float)(strtod(parsed_string, (char **)NULL));
1534 
1535  // Find end of number
1536  parsed_string = strchr(parsed_string, ',');
1537  if (parsed_string == NULL) {
1538  Error( LOCATION,
1539  "Submodel '%s' of model '%s' has an improperly formatted $uvec: declaration in its properties."
1540  "\n\n$uvec: should be followed by 3 numbers separated with commas."
1541  "\n\nCouldn't find first comma (,)!",
1542  pm->submodel[n].name, filename);
1543  }
1544  parsed_string++;
1545 
1546  while (*parsed_string == ' ') {
1547  parsed_string++; // Skip spaces
1548  }
1549 
1550  orient->vec.uvec.xyz.y = (float)(strtod(parsed_string, (char **)NULL));
1551 
1552  // Find end of number
1553  parsed_string = strchr(parsed_string, ',');
1554  if (parsed_string == NULL) {
1555  Error( LOCATION,
1556  "Submodel '%s' of model '%s' has an improperly formatted $uvec: declaration in its properties."
1557  "\n\n$uvec: should be followed by 3 numbers separated with commas."
1558  "\n\nCouldn't find second comma (,)!",
1559  pm->submodel[n].name, filename);
1560  }
1561  parsed_string++;
1562 
1563  while (*parsed_string == ' ') {
1564  parsed_string++; // Skip spaces
1565  }
1566 
1567  orient->vec.uvec.xyz.z = (float)(strtod(parsed_string, (char **)NULL));
1568 
1569  if ( (p = strstr(props, "$fvec:")) != NULL ) {
1570  parsed_string = p + 6;
1571 
1572  while (*parsed_string == ' ') {
1573  parsed_string++; // Skip spaces
1574  }
1575 
1576  orient->vec.fvec.xyz.x = (float)(strtod(parsed_string, (char **)NULL));
1577 
1578  // Find end of number
1579  parsed_string = strchr(parsed_string, ',');
1580  if (parsed_string == NULL) {
1581  Error( LOCATION,
1582  "Submodel '%s' of model '%s' has an improperly formatted $fvec: declaration in its properties."
1583  "\n\n$fvec: should be followed by 3 numbers separated with commas."
1584  "\n\nCouldn't find first comma (,)!",
1585  pm->submodel[n].name, filename);
1586  }
1587  parsed_string++;
1588 
1589  while (*parsed_string == ' ') {
1590  parsed_string++; // Skip spaces
1591  }
1592 
1593  orient->vec.fvec.xyz.y = (float)(strtod(parsed_string, (char **)NULL));
1594 
1595  // Find end of number
1596  parsed_string = strchr(parsed_string, ',');
1597  if (parsed_string == NULL) {
1598  Error( LOCATION,
1599  "Submodel '%s' of model '%s' has an improperly formatted $fvec: declaration in its properties."
1600  "\n\n$fvec: should be followed by 3 numbers separated with commas."
1601  "\n\nCouldn't find second comma (,)!",
1602  pm->submodel[n].name, filename);
1603  }
1604  parsed_string++;
1605 
1606  while (*parsed_string == ' ') {
1607  parsed_string++; // Skip spaces
1608  }
1609 
1610  orient->vec.fvec.xyz.z = (float)(strtod(parsed_string, (char **)NULL));
1611 
1612  pm->submodel[n].force_turret_normal = true;
1613 
1614  vm_vec_normalize(&orient->vec.uvec);
1615  vm_vec_normalize(&orient->vec.fvec);
1616 
1617  vm_vec_cross(&orient->vec.rvec, &orient->vec.uvec, &orient->vec.fvec);
1618  vm_vec_cross(&orient->vec.fvec, &orient->vec.rvec, &orient->vec.uvec);
1619 
1620  vm_vec_normalize(&orient->vec.fvec);
1621  vm_vec_normalize(&orient->vec.rvec);
1622 
1623  vm_orthogonalize_matrix(orient);
1624  } else {
1625  int parent_num = pm->submodel[n].parent;
1626 
1627  if (parent_num > -1) {
1628  *orient = pm->submodel[parent_num].orientation;
1629  } else {
1630  *orient = vmd_identity_matrix;
1631  }
1632 
1633  Warning( LOCATION, "Improper custom orientation matrix for subsystem %s, you must define a up vector, then a forward vector", pm->submodel[n].name);
1634  }
1635  } else {
1636  int parent_num = pm->submodel[n].parent;
1637 
1638  if (parent_num > -1) {
1639  *orient = pm->submodel[parent_num].orientation;
1640  } else {
1641  *orient = vmd_identity_matrix;
1642  }
1643 
1644  if (strstr(props, "$fvec:") != NULL) {
1645  Warning( LOCATION, "Improper custom orientation matrix for subsystem %s, you must define a up vector, then a forward vector", pm->submodel[n].name);
1646  }
1647  }
1648 
1649  if ( !rotating_submodel_has_subsystem ) {
1650  nprintf(("Model", "Model %s: Rotating Submodel without subsystem: %s\n", pm->filename, pm->submodel[n].name));
1651 
1652  // mark those submodels which should not rotate - ie, those with no subsystem
1655  }
1656 
1657 
1658  pm->submodel[n].angs.p = 0.0f;
1659  pm->submodel[n].angs.b = 0.0f;
1660  pm->submodel[n].angs.h = 0.0f;
1661 
1662  {
1663  int nchunks = cfread_int( fp ); // Throw away nchunks
1664  if ( nchunks > 0 ) {
1665  Error( LOCATION, "Model '%s' is chunked. See John or Adam!\n", pm->filename );
1666  }
1667  }
1668  pm->submodel[n].bsp_data_size = cfread_int(fp);
1669  if ( pm->submodel[n].bsp_data_size > 0 ) {
1671  cfread(pm->submodel[n].bsp_data,1,pm->submodel[n].bsp_data_size,fp);
1672  swap_bsp_data( pm, pm->submodel[n].bsp_data );
1673  } else {
1674  pm->submodel[n].bsp_data = NULL;
1675  }
1676 
1677  if ( strstr( pm->submodel[n].name, "thruster") )
1678  pm->submodel[n].is_thruster=1;
1679  else
1680  pm->submodel[n].is_thruster=0;
1681 
1682  // Genghis: if we have a thruster and none of the collision
1683  // properties were provided, then set "nocollide_this_only".
1684  if (pm->submodel[n].is_thruster && !(pm->submodel[n].no_collisions) && !(pm->submodel[n].nocollide_this_only) && !(pm->submodel[n].collide_invisible) )
1685  {
1686  pm->submodel[n].nocollide_this_only = true;
1687  }
1688 
1689  if ( strstr( pm->submodel[n].name, "-destroyed") )
1690  pm->submodel[n].is_damaged=1;
1691  else
1692  pm->submodel[n].is_damaged=0;
1693 
1694  //mprintf(( "Submodel %d, name '%s', parent = %d\n", n, pm->submodel[n].name, pm->submodel[n].parent ));
1695  //key_getch();
1696 
1697  //mprintf(( "Submodel %d, tree offset %d\n", n, pm->submodel[n].tree_offset ));
1698  //mprintf(( "Submodel %d, data offset %d\n", n, pm->submodel[n].data_offset ));
1699  //key_getch();
1700 
1701  break;
1702 
1703  }
1704 
1705  case ID_SLDC: // kazan - Shield Collision tree
1706  {
1707  pm->sldc_size = cfread_int(fp);
1709  cfread(pm->shield_collision_tree,1,pm->sldc_size,fp);
1711  //mprintf(( "Shield Collision Tree, %d bytes in size\n", pm->sldc_size));
1712  }
1713  break;
1714 
1715  case ID_SHLD:
1716  {
1717  pm->shield.nverts = cfread_int( fp ); // get the number of vertices in the list
1718 
1719  if (pm->shield.nverts > 0) {
1720  pm->shield.verts = (shield_vertex *)vm_malloc(pm->shield.nverts * sizeof(shield_vertex) );
1721  Assert( pm->shield.verts );
1722  for ( i = 0; i < pm->shield.nverts; i++ ) { // read in the vertex list
1723  cfread_vector( &(pm->shield.verts[i].pos), fp );
1724  }
1725  }
1726 
1727  pm->shield.ntris = cfread_int( fp ); // get the number of triangles that compose the shield
1728 
1729  if (pm->shield.ntris > 0) {
1730  pm->shield.tris = (shield_tri *)vm_malloc(pm->shield.ntris * sizeof(shield_tri) );
1731  Assert( pm->shield.tris );
1732  for ( i = 0; i < pm->shield.ntris; i++ ) {
1733  cfread_vector( &temp_vec, fp );
1734  vm_vec_normalize_safe(&temp_vec);
1735  pm->shield.tris[i].norm = temp_vec;
1736  for ( j = 0; j < 3; j++ ) {
1737  pm->shield.tris[i].verts[j] = cfread_int( fp ); // read in the indices into the shield_vertex list
1738 #ifndef NDEBUG
1739  if (pm->shield.tris[i].verts[j] >= pm->shield.nverts) {
1740  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]);
1741  }
1742 #endif
1743  }
1744 
1745  for ( j = 0; j < 3; j++ ) {
1746  pm->shield.tris[i].neighbors[j] = cfread_int( fp ); // read in the neighbor indices -- indexes into tri list
1747 #ifndef NDEBUG
1748  if (pm->shield.tris[i].neighbors[j] >= pm->shield.ntris) {
1749  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]);
1750  }
1751 #endif
1752  }
1753  }
1754  }
1755  }
1756  break;
1757 
1758  case ID_GPNT:
1759  pm->n_guns = cfread_int(fp);
1760 
1761  if (pm->n_guns > 0) {
1762  pm->gun_banks = (w_bank *)vm_malloc(sizeof(w_bank) * pm->n_guns);
1763  Assert( pm->gun_banks != NULL );
1764 
1765  for (i = 0; i < pm->n_guns; i++ ) {
1766  w_bank *bank = &pm->gun_banks[i];
1767 
1768  bank->num_slots = cfread_int(fp);
1769  Assert ( bank->num_slots < MAX_SLOTS );
1770  for (j = 0; j < bank->num_slots; j++) {
1771  cfread_vector( &(bank->pnt[j]), fp );
1772  cfread_vector( &temp_vec, fp );
1773  vm_vec_normalize_safe(&temp_vec);
1774  bank->norm[j] = temp_vec;
1775  }
1776  }
1777  }
1778  break;
1779 
1780  case ID_MPNT:
1781  pm->n_missiles = cfread_int(fp);
1782 
1783  if (pm->n_missiles > 0) {
1784  pm->missile_banks = (w_bank *)vm_malloc(sizeof(w_bank) * pm->n_missiles);
1785  Assert( pm->missile_banks != NULL );
1786 
1787  for (i = 0; i < pm->n_missiles; i++ ) {
1788  w_bank *bank = &pm->missile_banks[i];
1789 
1790  bank->num_slots = cfread_int(fp);
1791  Assert ( bank->num_slots < MAX_SLOTS );
1792  for (j = 0; j < bank->num_slots; j++) {
1793  cfread_vector( &(bank->pnt[j]), fp );
1794  cfread_vector( &temp_vec, fp );
1795  vm_vec_normalize_safe(&temp_vec);
1796  bank->norm[j] = temp_vec;
1797  }
1798  }
1799  }
1800  break;
1801 
1802  case ID_DOCK: {
1803  char props[MAX_PROP_LEN];
1804 
1805  pm->n_docks = cfread_int(fp);
1806 
1807  if (pm->n_docks > 0) {
1808  pm->docking_bays = (dock_bay *)vm_malloc(sizeof(dock_bay) * pm->n_docks);
1809  Assert( pm->docking_bays != NULL );
1810 
1811  for (i = 0; i < pm->n_docks; i++ ) {
1812  char *p;
1813  dock_bay *bay = &pm->docking_bays[i];
1814 
1815  cfread_string_len( props, MAX_PROP_LEN, fp );
1816  if ( (p = strstr(props, "$name"))!= NULL ) {
1817  get_user_prop_value(p+5, bay->name);
1818 
1819  int length = strlen(bay->name);
1820  if ((length > 0) && is_white_space(bay->name[length-1])) {
1821  nprintf(("Model", "model '%s' has trailing whitespace on bay name '%s'; this will be trimmed\n", pm->filename, bay->name));
1823  }
1824  if (strlen(bay->name) == 0) {
1825  nprintf(("Model", "model '%s' has an empty name specified for docking point %d\n", pm->filename, i));
1826  }
1827  } else {
1828  nprintf(("Model", "model '%s' has no name specified for docking point %d\n", pm->filename, i));
1829  sprintf(bay->name, "<unnamed bay %c>", 'A' + i);
1830  }
1831 
1832  bay->num_spline_paths = cfread_int( fp );
1833  if ( bay->num_spline_paths > 0 ) {
1834  bay->splines = (int *)vm_malloc(sizeof(int) * bay->num_spline_paths);
1835  for ( j = 0; j < bay->num_spline_paths; j++ )
1836  bay->splines[j] = cfread_int(fp);
1837  } else {
1838  bay->splines = NULL;
1839  }
1840 
1841  // determine what this docking bay can be used for
1842  if ( !strnicmp(bay->name, "cargo", 5) )
1843  bay->type_flags = DOCK_TYPE_CARGO;
1844  else
1846 
1847  bay->num_slots = cfread_int(fp);
1848 
1849  if(bay->num_slots != 2) {
1850  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);
1851  }
1852 
1853  for (j = 0; j < bay->num_slots; j++) {
1854  cfread_vector( &(bay->pnt[j]), fp );
1855  cfread_vector( &(bay->norm[j]), fp );
1856  if(vm_vec_mag(&(bay->norm[j])) <= 0.0f) {
1857  Warning(LOCATION, "Model '%s' dock point '%s' has a null normal. ", filename, bay->name);
1858  }
1859  }
1860 
1861  if(vm_vec_same(&bay->pnt[0], &bay->pnt[1])) {
1862  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);
1863 
1864  // just move the second point over by some amount
1865  bay->pnt[1].xyz.z += 10.0f;
1866  }
1867 
1868  vec3d diff;
1869  vm_vec_normalized_dir(&diff, &bay->pnt[0], &bay->pnt[1]);
1870  float dot = vm_vec_dot(&diff, &bay->norm[0]);
1871  if(fl_abs(dot) > 0.99f) {
1872  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);
1873 
1874  // generate a simple rotation matrix in all three dimensions (though bank is probably not needed)
1875  angles a = { PI_2, PI_2, PI_2 };
1876  matrix m;
1877  vm_angles_2_matrix(&m, &a);
1878 
1879  // rotate the docking normal
1880  vec3d temp = bay->norm[0];
1881  vm_vec_rotate(&bay->norm[0], &temp, &m);
1882  }
1883  }
1884  }
1885  break;
1886  }
1887 
1888  case ID_GLOW: //start glow point reading -Bobboau
1889  {
1890  char props[MAX_PROP_LEN];
1891 
1892  int gpb_num = cfread_int(fp);
1893 
1894  pm->n_glow_point_banks = gpb_num;
1895  pm->glow_point_banks = NULL;
1896 
1897  if (gpb_num > 0)
1898  {
1899  pm->glow_point_banks = (glow_point_bank *) vm_malloc(sizeof(glow_point_bank) * gpb_num);
1900  Assert(pm->glow_point_banks != NULL);
1901  }
1902 
1903  for (int gpb = 0; gpb < gpb_num; gpb++)
1904  {
1905  glow_point_bank *bank = &pm->glow_point_banks[gpb];
1906 
1907  bank->is_on = true;
1908  bank->glow_timestamp = 0;
1909  bank->disp_time = cfread_int(fp);
1910  bank->on_time = cfread_int(fp);
1911  bank->off_time = cfread_int(fp);
1912  bank->submodel_parent = cfread_int(fp);
1913  bank->LOD = cfread_int(fp);
1914  bank->type = cfread_int(fp);
1915  bank->num_points = cfread_int(fp);
1916  bank->points = NULL;
1917 
1918  if (bank->num_points > 0)
1919  bank->points = (glow_point *) vm_malloc(sizeof(glow_point) * bank->num_points);
1920 
1921  //if((bank->off_time > 0) && (bank->disp_time > 0))
1922  //bank->is_on = false;
1923 
1924  cfread_string_len(props, MAX_PROP_LEN, fp);
1925  // look for $glow_texture=xxx
1926  int length = strlen(props);
1927 
1928  if (length > 0)
1929  {
1930  int base_length = strlen("$glow_texture=");
1931  Assert(strstr( (const char *)&props, "$glow_texture=") != NULL);
1932  Assert(length > base_length);
1933  char *glow_texture_name = props + base_length;
1934 
1935  if (glow_texture_name[0] == '$')
1936  glow_texture_name++;
1937 
1938  bank->glow_bitmap = bm_load(glow_texture_name);
1939 
1940  if (bank->glow_bitmap < 0)
1941  {
1942  Warning( LOCATION, "Couldn't open glowpoint texture '%s'\nreferenced by model '%s'\n", glow_texture_name, pm->filename);
1943  }
1944  else
1945  {
1946  nprintf(( "Model", "Glow point bank %i texture num is %d for '%s'\n", gpb, bank->glow_bitmap, pm->filename));
1947  }
1948 
1949  strcat(glow_texture_name, "-neb");
1950  bank->glow_neb_bitmap = bm_load(glow_texture_name);
1951 
1952  if (bank->glow_neb_bitmap < 0)
1953  {
1954  bank->glow_neb_bitmap = bank->glow_bitmap;
1955  nprintf(( "Model", "Glow point bank nebula texture not found for '%s', using normal glowpoint texture instead\n", pm->filename));
1956  // Error( LOCATION, "Couldn't open texture '%s'\nreferenced by model '%s'\n", glow_texture_name, pm->filename );
1957  }
1958  else
1959  {
1960  nprintf(( "Model", "Glow point bank %i nebula texture num is %d for '%s'\n", gpb, bank->glow_neb_bitmap, pm->filename));
1961  }
1962  }
1963  else
1964  {
1965  // niffiwan: no "props" string found - ensure we don't have a random texture assigned!
1966  bank->glow_bitmap = -1;
1967  bank->glow_neb_bitmap = -1;
1968  Warning( LOCATION, "No Glow point texture for bank '%d' referenced by model '%s'\n", gpb, pm->filename);
1969  }
1970 
1971  for (j = 0; j < bank->num_points; j++)
1972  {
1973  glow_point *p = &bank->points[j];
1974 
1975  cfread_vector(&(p->pnt), fp);
1976  cfread_vector( &temp_vec, fp );
1977  if (!IS_VEC_NULL_SQ_SAFE(&temp_vec))
1978  vm_vec_normalize(&temp_vec);
1979  else
1980  vm_vec_zero(&temp_vec);
1981  p->norm = temp_vec;
1982  p->radius = cfread_float( fp);
1983  }
1984  }
1985  break;
1986  }
1987 
1988  case ID_FUEL:
1989  char props[MAX_PROP_LEN];
1990  pm->n_thrusters = cfread_int(fp);
1991 
1992  if (pm->n_thrusters > 0) {
1993  pm->thrusters = (thruster_bank *)vm_malloc(sizeof(thruster_bank) * pm->n_thrusters);
1994  Assert( pm->thrusters != NULL );
1995 
1996  for (i = 0; i < pm->n_thrusters; i++ ) {
1997  thruster_bank *bank = &pm->thrusters[i];
1998 
1999  bank->num_points = cfread_int(fp);
2000  bank->points = NULL;
2001 
2002  if (bank->num_points > 0)
2003  bank->points = (glow_point *) vm_malloc(sizeof(glow_point) * bank->num_points);
2004 
2005  bank->obj_num = -1;
2006  bank->submodel_num = -1;
2007 
2008  if (pm->version < 2117) {
2009  bank->wash_info_pointer = NULL;
2010  } else {
2011  cfread_string_len( props, MAX_PROP_LEN, fp );
2012  // look for $engine_subsystem=xxx
2013  int length = strlen(props);
2014  if (length > 0) {
2015  int base_length = strlen("$engine_subsystem=");
2016  Assert( strstr( (const char *)&props, "$engine_subsystem=") != NULL );
2017  Assert( length > base_length );
2018  char *engine_subsys_name = props + base_length;
2019  if (engine_subsys_name[0] == '$') {
2020  engine_subsys_name++;
2021  }
2022 
2023  nprintf(("wash", "Ship %s with engine wash associated with subsys %s\n", filename, engine_subsys_name));
2024 
2025  // set wash_info_index to invalid
2026  int table_error = 1;
2027  bank->wash_info_pointer = NULL;
2028  for (int k=0; k<n_subsystems; k++) {
2029  if ( !subsystem_stricmp(subsystems[k].subobj_name, engine_subsys_name) ) {
2030  bank->submodel_num = subsystems[k].subobj_num;
2031 
2032  bank->wash_info_pointer = subsystems[k].engine_wash_pointer;
2033  if (bank->wash_info_pointer != NULL) {
2034  table_error = 0;
2035  }
2036  // also set what subsystem this is attached to but not if we only have one thruster bank
2037  // do this so that original :V: models still work like they used to
2038  if (pm->n_thrusters > 1) {
2039  bank->obj_num = k;
2040  }
2041  break;
2042  }
2043  }
2044 
2045  if ( (bank->wash_info_pointer == NULL) && (n_subsystems > 0) ) {
2046  if (table_error) {
2047  // Warning(LOCATION, "No engine wash table entry in ships.tbl for ship model %s", filename);
2048  } else {
2049  Warning(LOCATION, "Inconsistent model: Engine wash engine subsystem does not match any ship subsytem names for ship model %s", filename);
2050  }
2051  }
2052  } else {
2053  bank->wash_info_pointer = NULL;
2054  }
2055  }
2056 
2057  for (j = 0; j < bank->num_points; j++) {
2058  glow_point *p = &bank->points[j];
2059 
2060  cfread_vector( &(p->pnt), fp );
2061  cfread_vector( &temp_vec, fp );
2062  vm_vec_normalize_safe(&temp_vec);
2063  p->norm = temp_vec;
2064 
2065  if ( pm->version > 2004 ) {
2066  p->radius = cfread_float( fp );
2067  //mprintf(( "Rad = %.2f\n", rad ));
2068  } else {
2069  p->radius = 1.0f;
2070  }
2071  }
2072  //mprintf(( "Num slots = %d\n", bank->num_slots ));
2073  }
2074  }
2075  break;
2076 
2077  case ID_TGUN:
2078  case ID_TMIS: {
2079  int n_banks = cfread_int(fp); // Number of turrets
2080 
2081  for ( i = 0; i < n_banks; i++ ) {
2082  int parent; // The parent subobj of the turret (the gun base)
2083  int physical_parent; // The subobj that the firepoints are physically attached to (the gun barrel)
2084  int n_slots; // How many firepoints the turret has
2085  model_subsystem *subsystemp; // The actual turret subsystem
2086 
2087  parent = cfread_int( fp );
2088  physical_parent = cfread_int(fp);
2089 
2090  int snum=-1;
2091  if ( subsystems ) {
2092  for ( snum = 0; snum < n_subsystems; snum++ ) {
2093  subsystemp = &subsystems[snum];
2094 
2095  if ( parent == subsystemp->subobj_num ) {
2096  cfread_vector( &temp_vec, fp );
2097  vm_vec_normalize_safe(&temp_vec);
2098  subsystemp->turret_norm = temp_vec;
2099  vm_vector_2_matrix(&subsystemp->turret_matrix,&subsystemp->turret_norm,NULL,NULL);
2100 
2101  n_slots = cfread_int( fp );
2102  subsystemp->turret_gun_sobj = physical_parent;
2103  if(n_slots > MAX_TFP) {
2104  Warning(LOCATION, "Model %s has %i turret firing points on subsystem %s, maximum is %i", pm->filename, n_slots, subsystemp->name, MAX_TFP);
2105  }
2106 
2107  for (j = 0; j < n_slots; j++ ) {
2108  if(j < MAX_TFP)
2109  cfread_vector( &subsystemp->turret_firing_point[j], fp );
2110  else
2111  {
2112  vec3d bogus;
2113  cfread_vector(&bogus, fp);
2114  }
2115  }
2116  Assertion( n_slots > 0, "Turret %s in model %s has no firing points.\n", subsystemp->name, pm->filename);
2117 
2118  subsystemp->turret_num_firing_points = n_slots;
2119 
2120  break;
2121  }
2122  }
2123  }
2124 
2125  if ( (n_subsystems == 0) || (snum == n_subsystems) ) {
2126  vec3d bogus;
2127 
2128  nprintf(("Warning", "Turret submodel %i not found for turret %i in model %s\n", parent, i, pm->filename));
2129  cfread_vector( &bogus, fp );
2130  n_slots = cfread_int( fp );
2131  for (j = 0; j < n_slots; j++ )
2132  cfread_vector( &bogus, fp );
2133  }
2134  }
2135  break;
2136  }
2137 
2138  case ID_SPCL: {
2139  char name[MAX_NAME_LEN], props_spcl[MAX_PROP_LEN], *p;
2140  int n_specials;
2141  float radius;
2142  vec3d pnt;
2143 
2144  n_specials = cfread_int(fp); // get the number of special subobjects we have
2145  for (i = 0; i < n_specials; i++) {
2146 
2147  // get the next free object of the subobject list. Flag error if no more room
2148 
2149  cfread_string_len(name, MAX_NAME_LEN, fp); // get the name of this special polygon
2150 
2151  cfread_string_len(props_spcl, MAX_PROP_LEN, fp); // will definately have properties as well!
2152  cfread_vector( &pnt, fp );
2153  radius = cfread_float( fp );
2154 
2155  // check if $Split
2156  p = strstr(name, "$split");
2157  if (p != NULL) {
2158  pm->split_plane[pm->num_split_plane] = pnt.xyz.z;
2159  pm->num_split_plane++;
2161  } else if ( ( p = strstr(props_spcl, "$special"))!= NULL ) {
2162  char type[64];
2163 
2164  get_user_prop_value(p+9, type);
2165  if ( !stricmp(type, "subsystem") ) { // if we have a subsystem, put it into the list!
2166  do_new_subsystem( n_subsystems, subsystems, -1, radius, &pnt, props_spcl, &name[1], pm->id ); // skip the first '$' character of the name
2167  } else if ( !stricmp(type, "shieldpoint") ) {
2168  pm->shield_points.push_back(pnt);
2169  }
2170  } else if ( strstr(name, "$enginelarge") || strstr(name, "$enginehuge") ){
2171  do_new_subsystem( n_subsystems, subsystems, -1, radius, &pnt, props_spcl, &name[1], pm->id ); // skip the first '$' character of the name
2172  } else {
2173  nprintf(("Warning", "Unknown special object type %s while reading model %s\n", name, pm->filename));
2174  }
2175  }
2176  break;
2177  }
2178 
2179  case ID_TXTR: { //Texture filename list
2180  int n;
2181 // char name_buf[128];
2182 
2183  //mprintf(0,"Got chunk TXTR, len=%d\n",len);
2184 
2185 
2186  n = cfread_int(fp);
2187  pm->n_textures = n;
2188  // Don't overwrite memory!!
2190  //mprintf(0," num textures = %d\n",n);
2191  for (i=0; i<n; i++ )
2192  {
2193  char tmp_name[256];
2194  cfread_string_len(tmp_name,127,fp);
2195  model_load_texture(pm, i, tmp_name);
2196  //mprintf(0,"<%s>\n",name_buf);
2197  }
2198 
2199 
2200  break;
2201  }
2202 
2203 /* case ID_IDTA: //Interpreter data
2204  //mprintf(0,"Got chunk IDTA, len=%d\n",len);
2205 
2206  pm->model_data = (ubyte *)vm_malloc(len);
2207  pm->model_data_size = len;
2208  Assert(pm->model_data != NULL );
2209 
2210  cfread(pm->model_data,1,len,fp);
2211 
2212  break;
2213 */
2214 
2215  case ID_INFO: // don't need to do anything with info stuff
2216 
2217  #ifndef NDEBUG
2218  pm->debug_info_size = len;
2219  pm->debug_info = (char *)vm_malloc(pm->debug_info_size+1);
2220  Assert(pm->debug_info!=NULL);
2221  memset(pm->debug_info,0,len+1);
2222  cfread( pm->debug_info, 1, len, fp );
2223  #endif
2224  break;
2225 
2226  case ID_GRID:
2227  break;
2228 
2229  case ID_PATH:
2230  pm->n_paths = cfread_int( fp );
2231 
2232  if (pm->n_paths <= 0) {
2233  break;
2234  }
2235 
2236  pm->paths = (model_path *)vm_malloc(sizeof(model_path)*pm->n_paths);
2237  Assert( pm->paths != NULL );
2238 
2239  memset( pm->paths, 0, sizeof(model_path) * pm->n_paths );
2240 
2241  for (i=0; i<pm->n_paths; i++ ) {
2242  cfread_string_len(pm->paths[i].name , MAX_NAME_LEN-1, fp);
2243  if ( pm->version >= 2002 ) {
2244  // store the sub_model name number of the parent
2246  // get rid of leading '$' char in name
2247  if ( pm->paths[i].parent_name[0] == '$' ) {
2248  char tmpbuf[MAX_NAME_LEN];
2249  strcpy_s(tmpbuf, pm->paths[i].parent_name+1);
2250  strcpy_s(pm->paths[i].parent_name, tmpbuf);
2251  }
2252  // store the sub_model index (ie index into pm->submodel) of the parent
2253  pm->paths[i].parent_submodel = -1;
2254  for ( j = 0; j < pm->n_models; j++ ) {
2255  if ( !stricmp( pm->submodel[j].name, pm->paths[i].parent_name) ) {
2256  pm->paths[i].parent_submodel = j;
2257  }
2258  }
2259  } else {
2260  pm->paths[i].parent_name[0] = 0;
2261  pm->paths[i].parent_submodel = -1;
2262  }
2263 
2264  pm->paths[i].nverts = cfread_int( fp );
2265  pm->paths[i].verts = (mp_vert *)vm_malloc( sizeof(mp_vert) * pm->paths[i].nverts );
2266  pm->paths[i].goal = pm->paths[i].nverts - 1;
2267  pm->paths[i].type = MP_TYPE_UNUSED;
2268  pm->paths[i].value = 0;
2269  Assert(pm->paths[i].verts!=NULL);
2270  memset( pm->paths[i].verts, 0, sizeof(mp_vert) * pm->paths[i].nverts );
2271 
2272  for (j=0; j<pm->paths[i].nverts; j++ ) {
2273  cfread_vector(&pm->paths[i].verts[j].pos,fp );
2274  pm->paths[i].verts[j].radius = cfread_float( fp );
2275 
2276  { // version 1802 added turret stuff
2277  int nturrets, k;
2278 
2279  nturrets = cfread_int( fp );
2280  pm->paths[i].verts[j].nturrets = nturrets;
2281 
2282  if (nturrets > 0) {
2283  pm->paths[i].verts[j].turret_ids = (int *)vm_malloc( sizeof(int) * nturrets );
2284  for ( k = 0; k < nturrets; k++ )
2285  pm->paths[i].verts[j].turret_ids[k] = cfread_int( fp );
2286  }
2287  }
2288 
2289  }
2290  }
2291  break;
2292 
2293  case ID_EYE: // an eye position(s)
2294  {
2295  int num_eyes;
2296 
2297  // all eyes points are stored simply as vectors and their normals.
2298  // 0th element is used as usual player view position.
2299 
2300  num_eyes = cfread_int( fp );
2301  pm->n_view_positions = num_eyes;
2302  Assert ( num_eyes < MAX_EYES );
2303  for (i = 0; i < num_eyes; i++ ) {
2304  pm->view_positions[i].parent = cfread_int( fp );
2305  cfread_vector( &pm->view_positions[i].pnt, fp );
2306  cfread_vector( &pm->view_positions[i].norm, fp );
2307  }
2308  }
2309  break;
2310 
2311  case ID_INSG:
2312  int num_ins, num_verts, num_faces, idx, idx2, idx3;
2313 
2314  // get the # of insignias
2315  num_ins = cfread_int(fp);
2316  pm->num_ins = num_ins;
2317 
2318  // read in the insignias
2319  for(idx=0; idx<num_ins; idx++){
2320  // get the detail level
2321  pm->ins[idx].detail_level = cfread_int(fp);
2322  if (pm->ins[idx].detail_level < 0) {
2323  Warning(LOCATION, "Model '%s': insignia uses an invalid LOD (%i)\n", pm->filename, pm->ins[idx].detail_level);
2324  }
2325 
2326  // # of faces
2327  num_faces = cfread_int(fp);
2328  pm->ins[idx].num_faces = num_faces;
2329  Assert(num_faces <= MAX_INS_FACES);
2330 
2331  // # of vertices
2332  num_verts = cfread_int(fp);
2333  Assert(num_verts <= MAX_INS_VECS);
2334 
2335  // read in all the vertices
2336  for(idx2=0; idx2<num_verts; idx2++){
2337  cfread_vector(&pm->ins[idx].vecs[idx2], fp);
2338  }
2339 
2340  // read in world offset
2341  cfread_vector(&pm->ins[idx].offset, fp);
2342 
2343  // read in all the faces
2344  for(idx2=0; idx2<pm->ins[idx].num_faces; idx2++){
2345  // read in 3 vertices
2346  for(idx3=0; idx3<3; idx3++){
2347  pm->ins[idx].faces[idx2][idx3] = cfread_int(fp);
2348  pm->ins[idx].u[idx2][idx3] = cfread_float(fp);
2349  pm->ins[idx].v[idx2][idx3] = cfread_float(fp);
2350  }
2351  vec3d tempv;
2352 
2353  //get three points (rotated) and compute normal
2354 
2355  vm_vec_perp(&tempv,
2356  &pm->ins[idx].vecs[pm->ins[idx].faces[idx2][0]],
2357  &pm->ins[idx].vecs[pm->ins[idx].faces[idx2][1]],
2358  &pm->ins[idx].vecs[pm->ins[idx].faces[idx2][2]]);
2359 
2360  vm_vec_normalize_safe(&tempv);
2361 
2362  pm->ins[idx].norm[idx2] = tempv;
2363 // 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));
2364 
2365  }
2366  }
2367  break;
2368 
2369  // autocentering info
2370  case ID_ACEN:
2371  cfread_vector(&pm->autocenter, fp);
2372  pm->flags |= PM_FLAG_AUTOCEN;
2373  break;
2374 
2375  default:
2376  mprintf(("Unknown chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len));
2377  cfseek(fp,len,SEEK_CUR);
2378  break;
2379 
2380  }
2381  cfseek(fp,next_chunk,SEEK_SET);
2382 
2383  id = cfread_int(fp);
2384  len = cfread_int(fp);
2385  next_chunk = cftell(fp) + len;
2386 
2387  }
2388 
2389 #ifndef NDEBUG
2390  if ( ss_fp) {
2391  int size;
2392 
2393  cfclose(ss_fp);
2394  ss_fp = cfopen(debug_name, "rb");
2395  if ( ss_fp ) {
2396  size = cfilelength(ss_fp);
2397  cfclose(ss_fp);
2398  if ( size <= 0 ) {
2399  _unlink(debug_name);
2400  }
2401  }
2402  }
2403 #endif
2404 
2405  cfclose(fp);
2406 
2407  // mprintf(("Done processing chunks\n"));
2408  return 1;
2409 }
2410 
2411 //Goober
2412 void model_load_texture(polymodel *pm, int i, char *file)
2413 {
2414  // NOTE: it doesn't help to use more than MAX_FILENAME_LEN here as bmpman will use that restriction
2415  // we also have to make sure there is always a trailing NUL since overflow doesn't add it
2416  char tmp_name[MAX_FILENAME_LEN];
2417  strcpy_s(tmp_name, file);
2418  strlwr(tmp_name);
2419 
2420  texture_map *tmap = &pm->maps[i];
2421  tmap->Clear();
2422 
2423  //WMC - IMPORTANT!!
2424  //The Fred_running checks are there so that FRED will see those textures and put them in the
2425  //texture replacement box.
2426 
2427  // base maps ---------------------------------------------------------------
2428  texture_info *tbase = &tmap->textures[TM_BASE_TYPE];
2429  if (strstr(tmp_name, "thruster") || strstr(tmp_name, "invisible") || strstr(tmp_name, "warpmap"))
2430  {
2431  // Don't load textures for thruster animations or invisible textures
2432  // or warp models!-Bobboau
2433  tbase->clear();
2434  }
2435  else
2436  {
2437  // check if we should be transparent, include "-trans" but make sure to skip anything that might be "-transport"
2438  if ( (strstr(tmp_name, "-trans") && !strstr(tmp_name, "-transpo")) || strstr(tmp_name, "shockwave") || !strcmp(tmp_name, "nameplate") ) {
2439  tmap->is_transparent = true;
2440  }
2441 
2442  if (strstr(tmp_name, "-amb")) {
2443  tmap->is_ambient = true;
2444  }
2445 
2446  tbase->LoadTexture(tmp_name, pm->filename);
2447  if(tbase->GetTexture() < 0)
2448  Warning(LOCATION, "Couldn't open texture '%s'\nreferenced by model '%s'\n", tmp_name, pm->filename);
2449  }
2450  // -------------------------------------------------------------------------
2451 
2452  // glow maps ---------------------------------------------------------------
2453  texture_info *tglow = &tmap->textures[TM_GLOW_TYPE];
2454  if ( (!Cmdline_glow && !Fred_running) || (tbase->GetTexture() < 0))
2455  {
2456  tglow->clear();
2457  }
2458  else
2459  {
2460  strcpy_s(tmp_name, file);
2461  strcat_s(tmp_name, "-glow" );
2462  strlwr(tmp_name);
2463 
2464  tglow->LoadTexture(tmp_name, pm->filename);
2465  }
2466  // -------------------------------------------------------------------------
2467 
2468  // specular maps -----------------------------------------------------------
2469  texture_info *tspec = &tmap->textures[TM_SPECULAR_TYPE];
2470  if ( (!Cmdline_spec && !Fred_running) || (tbase->GetTexture() < 0))
2471  {
2472  tspec->clear();
2473  }
2474  else
2475  {
2476  strcpy_s(tmp_name, file);
2477  strcat_s(tmp_name, "-shine");
2478  strlwr(tmp_name);
2479 
2480  tspec->LoadTexture(tmp_name, pm->filename);
2481  }
2482  //tmap->spec_map.original_texture = tmap->spec_map.texture;
2483  // -------------------------------------------------------------------------
2484 
2485  // bump maps ---------------------------------------------------------------
2486  texture_info *tnorm = &tmap->textures[TM_NORMAL_TYPE];
2487  if ( (!Cmdline_normal && !Fred_running) || (tbase->GetTexture() < 0) ) {
2488  tnorm->clear();
2489  } else {
2490  strcpy_s(tmp_name, file);
2491  strcat_s(tmp_name, "-normal");
2492  strlwr(tmp_name);
2493 
2494  tnorm->LoadTexture(tmp_name, pm->filename);
2495  }
2496 
2497  // try to get a height map too
2498  texture_info *theight = &tmap->textures[TM_HEIGHT_TYPE];
2499  if ((!Cmdline_height && !Fred_running) || (tbase->GetTexture() < 0)) {
2500  theight->clear();
2501  } else {
2502  strcpy_s(tmp_name, file);
2503  strcat_s(tmp_name, "-height");
2504  strlwr(tmp_name);
2505 
2506  theight->LoadTexture(tmp_name, pm->filename);
2507  }
2508 
2509  // Utility map -------------------------------------------------------------
2510  texture_info *tmisc = &tmap->textures[TM_MISC_TYPE];
2511 
2512  strcpy_s(tmp_name, file);
2513  strcat_s(tmp_name, "-misc");
2514  strlwr(tmp_name);
2515 
2516  tmisc->LoadTexture(tmp_name, pm->filename);
2517 
2518  // -------------------------------------------------------------------------
2519 
2520  // See if we need to compile a new shader for this material
2521  int shader_flags = 0;
2522 
2523  if (tbase->GetTexture() > 0)
2524  shader_flags |= SDR_FLAG_MODEL_DIFFUSE_MAP;
2525  if (tglow->GetTexture() > 0 && Cmdline_glow)
2526  shader_flags |= SDR_FLAG_MODEL_GLOW_MAP;
2527  if (tspec->GetTexture() > 0 && Cmdline_spec)
2528  shader_flags |= SDR_FLAG_MODEL_SPEC_MAP;
2529  if (tnorm->GetTexture() > 0 && Cmdline_normal)
2530  shader_flags |= SDR_FLAG_MODEL_NORMAL_MAP;
2531  if (theight->GetTexture() > 0 && Cmdline_height)
2532  shader_flags |= SDR_FLAG_MODEL_HEIGHT_MAP;
2533  if (tspec->GetTexture() > 0 && Cmdline_env && Cmdline_spec) // No env maps without spec map
2534  shader_flags |= SDR_FLAG_MODEL_ENV_MAP;
2535  if (tmisc->GetTexture() > 0)
2536  shader_flags |= SDR_FLAG_MODEL_MISC_MAP;
2537 
2539 
2541  shader_flags |= SDR_FLAG_MODEL_CLIP;
2542 
2545 
2547  shader_flags |= SDR_FLAG_MODEL_DEFERRED;
2548 
2551 
2552  if( !Cmdline_no_batching && GLSL_version >= 130 ) {
2553  shader_flags &= ~SDR_FLAG_MODEL_DEFERRED;
2554  shader_flags |= SDR_FLAG_MODEL_TRANSFORM;
2555 
2558 
2561 
2562  shader_flags |= SDR_FLAG_MODEL_DEFERRED;
2563 
2566 
2569  }
2570 }
2571 
2572 //returns the number of this model
2573 int model_load(char *filename, int n_subsystems, model_subsystem *subsystems, int ferror, int duplicate)
2574 {
2575  int i, num, arc_idx;
2576  polymodel *pm = NULL;
2577 
2578  if ( !model_initted )
2579  model_init();
2580 
2581 #ifndef NDEBUG
2582  int ram_before = TotalRam;
2583 #endif
2584 
2585  num = -1;
2586 
2587  for (i=0; i< MAX_POLYGON_MODELS; i++) {
2588  if ( Polygon_models[i] ) {
2589  if (!stricmp(filename, Polygon_models[i]->filename) && !duplicate) {
2590  // Model already loaded; just return.
2591  Polygon_models[i]->used_this_mission++;
2592  return Polygon_models[i]->id;
2593  }
2594  } else if ( num == -1 ) {
2595  // This is the first empty slot
2596  num = i;
2597  }
2598  }
2599 
2600  // No empty slot
2601  if ( num == -1 ) {
2602  Error( LOCATION, "Too many models" );
2603  return -1;
2604  }
2605 
2606  mprintf(( "Loading model '%s' into slot '%i'\n", filename, num ));
2607 
2608  pm = new polymodel;
2609  Polygon_models[num] = pm;
2610 
2611  pm->n_paths = 0;
2612  pm->paths = NULL;
2613 
2614  uint org_sig = static_cast<uint>(Model_signature);
2615  if ( org_sig + MAX_POLYGON_MODELS > INT_MAX || org_sig + MAX_POLYGON_MODELS < org_sig ) {
2616  Model_signature = 0; // Overflow
2617  } else {
2618  Model_signature+=MAX_POLYGON_MODELS; // No overflow
2619  }
2620  Assert( (Model_signature % MAX_POLYGON_MODELS) == 0 );
2621  pm->id = Model_signature + num;
2622  Assert( (pm->id % MAX_POLYGON_MODELS) == num );
2623 
2624  extern int Parse_normal_problem_count;
2625  Parse_normal_problem_count = 0;
2626 
2627  pm->used_this_mission = 0;
2628 
2629 #ifndef NDEBUG
2630  char busy_text[60] = { '\0' };
2631 
2632  strcat_s( busy_text, "** ModelLoad: " );
2633  strcat_s( busy_text, filename );
2634  strcat_s( busy_text, " **" );
2635 
2636  game_busy(busy_text);
2637 #endif
2638 
2639  if (read_model_file(pm, filename, n_subsystems, subsystems, ferror) < 0) {
2640  if (pm != NULL) {
2641  delete pm;
2642  }
2643 
2644  Polygon_models[num] = NULL;
2645  return -1;
2646  }
2647 
2648  pm->used_this_mission++;
2649 
2650 #ifdef _DEBUG
2651  if(Fred_running && Parse_normal_problem_count > 0)
2652  {
2653  char buffer[100];
2654  sprintf(buffer,"Serious problem loading model %s, %d normals capped to zero",
2655  filename, Parse_normal_problem_count);
2656  MessageBox(NULL,buffer,"Error", MB_OK);
2657  }
2658 #endif
2659 
2660  //=============================
2661  // Find the destroyed replacement models
2662 
2663  // Set up the default values
2664  for (i=0; i<pm->n_models; i++ ) {
2665  pm->submodel[i].my_replacement = -1; // assume nothing replaces this
2666  pm->submodel[i].i_replace = -1; // assume this doesn't replaces anything
2667  }
2668 
2669  // Search for models that have destroyed versions
2670  for (i=0; i<pm->n_models; i++ ) {
2671  int j;
2672  char destroyed_name[128];
2673 
2674  strcpy_s( destroyed_name, pm->submodel[i].name );
2675  strcat_s( destroyed_name, "-destroyed" );
2676  for (j=0; j<pm->n_models; j++ ) {
2677  if ( !stricmp( pm->submodel[j].name, destroyed_name )) {
2678  pm->submodel[i].my_replacement = j;
2679  pm->submodel[j].i_replace = i;
2680  }
2681  }
2682 
2683  // Search for models with live debris
2684  // This debris comes from a destroyed subsystem when ship is still alive
2685  char live_debris_name[128];
2686 
2687  strcpy_s( live_debris_name, "debris-" );
2688  strcat_s( live_debris_name, pm->submodel[i].name );
2689 
2690  pm->submodel[i].num_live_debris = 0;
2691  for (j=0; j<pm->n_models; j++ ) {
2692  // check if current model name is substring of destroyed
2693  if ( strstr( pm->submodel[j].name, live_debris_name )) {
2694  mprintf(( "Found live debris model for '%s'\n", pm->submodel[i].name ));
2696  pm->submodel[i].live_debris[pm->submodel[i].num_live_debris++] = j;
2697  pm->submodel[j].is_live_debris = 1;
2698  }
2699  }
2700 
2701  }
2702 
2703  create_family_tree(pm);
2704 
2705  // maybe generate vertex buffers
2707 
2708  //==============================
2709  // Find all the lower detail versions of the hires model
2710  for (i=0; i<pm->n_models; i++ ) {
2711  int j, l1;
2712  bsp_info * sm1 = &pm->submodel[i];
2713 
2714  // set all arc types to be default
2715  for(arc_idx=0; arc_idx < MAX_ARC_EFFECTS; arc_idx++){
2716  sm1->arc_type[arc_idx] = MARC_TYPE_NORMAL;
2717  }
2718 
2719  sm1->num_details = 0;
2720  // If a backward compatibility LOD name is declared use it
2721  if (sm1->lod_name[0] != '\0') {
2722  l1=strlen(sm1->lod_name);
2723  }
2724  // otherwise use the name for LOD comparision
2725  else {
2726  l1 = strlen(sm1->name);
2727  }
2728 
2729  for (j=0; j<pm->num_debris_objects;j++ ) {
2730  if ( i == pm->debris_objects[j] ) {
2731  sm1->is_damaged = 1;
2732  }
2733  }
2734 
2735 
2736  for (j=0; j<MAX_MODEL_DETAIL_LEVELS; j++ ) {
2737  sm1->details[j] = -1;
2738  }
2739 
2740  for (j=0; j<pm->n_models; j++ ) {
2741  int k;
2742  bsp_info * sm2 = &pm->submodel[j];
2743 
2744  if ( i==j ) continue;
2745 
2746  // set all arc types to be default
2747  for(arc_idx=0; arc_idx < MAX_ARC_EFFECTS; arc_idx++){
2748  sm2->arc_type[arc_idx] = MARC_TYPE_NORMAL;
2749  }
2750 
2751  // if sm2 is a detail of sm1 and sm1 is a high detail, then add it to sm1's list
2752  if ((int)strlen(sm2->name)!=l1) continue;
2753 
2754  int ndiff = 0;
2755  int first_diff = 0;
2756  for ( k=0; k<l1; k++) {
2757  // If a backward compatibility LOD name is declared use it
2758  if (sm1->lod_name[0] != '\0') {
2759  if (sm1->lod_name[k] != sm2->name[k] ) {
2760  if (ndiff==0) first_diff = k;
2761  ndiff++;
2762  }
2763  }
2764  // otherwise do the standard LOD comparision
2765  else {
2766  if (sm1->name[k] != sm2->name[k] ) {
2767  if (ndiff==0) first_diff = k;
2768  ndiff++;
2769  }
2770  }
2771  }
2772  if (ndiff==1) { // They only differ by one character!
2773  int dl1, dl2;
2774  // If a backward compatibility LOD name is declared use it
2775  if (sm1->lod_name[0] != '\0') {
2776  dl1 = tolower(sm1->lod_name[first_diff]) - 'a';
2777  }
2778  // otherwise do the standard LOD comparision
2779  else {
2780  dl1 = tolower(sm1->name[first_diff]) - 'a';
2781  }
2782  dl2 = tolower(sm2->name[first_diff]) - 'a';
2783 
2784  // Handle LODs named "detail0/1/2/etc" too (as opposed to "detaila/b/c/etc")
2785  if (sm1->parent == -1 && sm2->parent == -1 && !sm1->is_damaged && !sm2->is_damaged && !sm1->is_live_debris && !sm2->is_live_debris) {
2786  dl2 = dl2 - dl1;
2787  dl1 = 0;
2788  }
2789 
2790  if ( (dl1<0) || (dl2<0) || (dl1>=MAX_MODEL_DETAIL_LEVELS) || (dl2>=MAX_MODEL_DETAIL_LEVELS) ) continue; // invalid detail levels
2791 
2792  if ( dl1 == 0 ) {
2793  dl2--; // Start from 1 up...
2794  if (dl2 >= sm1->num_details ) sm1->num_details = dl2+1;
2795  sm1->details[dl2] = j;
2796  mprintf(( "Submodel '%s' is detail level %d of '%s'\n", sm2->name, dl2 + 1, sm1->name ));
2797  }
2798  }
2799  }
2800 
2801  for (j=0; j<sm1->num_details; j++ ) {
2802  if ( sm1->details[j] == -1 ) {
2803  sm1->num_details = 0;
2804  }
2805  }
2806 
2807  }
2808 
2809 
2810  model_octant_create( pm );
2811 
2812  if ( !Cmdline_old_collision_sys ) {
2813  for ( i = 0; i < pm->n_models; ++i ) {
2816 
2817  model_collide_parse_bsp(tree, pm->submodel[i].bsp_data, pm->version);
2818  }
2819  }
2820 
2821  // Find the core_radius... the minimum of
2822  float rx, ry, rz;
2823  rx = fl_abs( pm->submodel[pm->detail[0]].max.xyz.x - pm->submodel[pm->detail[0]].min.xyz.x );
2824  ry = fl_abs( pm->submodel[pm->detail[0]].max.xyz.y - pm->submodel[pm->detail[0]].min.xyz.y );
2825  rz = fl_abs( pm->submodel[pm->detail[0]].max.xyz.z - pm->submodel[pm->detail[0]].min.xyz.z );
2826 
2827  pm->core_radius = MIN( rx, MIN(ry, rz) ) / 2.0f;
2828 
2829  for (i=0; i<pm->n_view_positions; i++ ) {
2830  if ( pm->view_positions[i].parent == pm->detail[0] ) {
2831  float d = vm_vec_mag( &pm->view_positions[i].pnt );
2832 
2833  d += 0.1f; // Make the eye 1/10th of a meter inside the sphere.
2834 
2835  if ( d > pm->core_radius ) {
2836  pm->core_radius = d;
2837  }
2838  }
2839  }
2840 
2841 #ifndef NDEBUG
2842  int ram_after = TotalRam;
2843 
2844  pm->ram_used = ram_after - ram_before;
2845  Model_ram += pm->ram_used;
2846 #endif
2847 
2848  // Goober5000 - originally done in ship_create for no apparent reason
2849  model_set_subsys_path_nums(pm, n_subsystems, subsystems);
2851 
2852  return pm->id;
2853 }
2854 
2855 int model_create_instance(bool is_ship, int model_num)
2856 {
2857  int i = 0;
2858  int open_slot = -1;
2859 
2860  // go through model instances and find an empty slot
2861  for ( i = 0; i < (int)Polygon_model_instances.size(); i++) {
2862  if ( !Polygon_model_instances[i] ) {
2863  open_slot = i;
2864  }
2865  }
2866 
2868  memset(pmi, 0, sizeof(polymodel_instance));
2869  pmi->model_num = model_num;
2870 
2871  // if not found, create a slot
2872  if ( open_slot < 0 ) {
2873  Polygon_model_instances.push_back( pmi );
2874  open_slot = Polygon_model_instances.size() - 1;
2875  } else {
2876  Polygon_model_instances[open_slot] = pmi;
2877  }
2878 
2879  polymodel *pm = model_get(model_num);
2880 
2882 
2883  for ( i = 0; i < pm->n_models; i++ ) {
2884  model_clear_submodel_instance( &pmi->submodel[i], &pm->submodel[i] );
2885  }
2886 
2887  // add intrinsic_rotation instances if this model is intrinsic-rotating
2888  if (pm->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
2889  intrinsic_rotation intrinsic_rotate(is_ship, open_slot);
2890 
2891  for (i = 0; i < pm->n_models; i++) {
2893  intrinsic_rotate.list.push_back(submodel_intrinsic_rotation(i, pm->submodel[i].dumb_turn_rate));
2894  }
2895  }
2896 
2897  if (intrinsic_rotate.list.empty()) {
2898  Assertion(!intrinsic_rotate.list.empty(), "This model has the PM_FLAG_HAS_INTRINSIC_ROTATE flag; why doesn't it have an intrinsic-rotating submodel?");
2899  } else {
2900  Intrinsic_rotations.push_back(intrinsic_rotate);
2901  }
2902  }
2903 
2904  return open_slot;
2905 }
2906 
2908 {
2909  Assert(model_instance_num >= 0);
2910  Assert(model_instance_num < (int)Polygon_model_instances.size());
2911  Assert(Polygon_model_instances[model_instance_num] != NULL);
2912 
2913  polymodel_instance *pmi = Polygon_model_instances[model_instance_num];
2914 
2915  if ( pmi->submodel ) {
2916  vm_free(pmi->submodel);
2917  }
2918 
2919  vm_free(pmi);
2920 
2921  Polygon_model_instances[model_instance_num] = NULL;
2922 
2923  // delete intrinsic rotations associated with this instance
2924  for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it) {
2925  if (intrinsic_it->model_instance_num == model_instance_num) {
2926  Intrinsic_rotations.erase(intrinsic_it);
2927  break;
2928  }
2929  }
2930 }
2931 
2932 // ensure that the subsys path is at least SUBSYS_PATH_DIST from the
2933 // second last to last point.
2935 {
2936  vec3d *v1, *v2, dir;
2937  float dist;
2938  int index_1, index_2;
2939 
2940  Assert( (path_num >= 0) && (path_num < pm->n_paths) );
2941 
2942  model_path *mp;
2943  mp = &pm->paths[path_num];
2944 
2945  Assert(mp != NULL);
2946  if (mp->nverts <= 1 ) {
2947  Error(LOCATION, "Subsystem Path (%s) Parent (%s) in model (%s) has less than 2 vertices/points!", mp->name, mp->parent_name, pm->filename);
2948  }
2949 
2950  index_1 = 1;
2951  index_2 = 0;
2952 
2953  v1 = &mp->verts[index_1].pos;
2954  v2 = &mp->verts[index_2].pos;
2955 
2956  dist = vm_vec_dist(v1, v2);
2957  if (dist < (SUBSYS_PATH_DIST - 10))
2958  {
2959  vm_vec_normalized_dir(&dir, v2, v1);
2960  vm_vec_scale_add(v2, v1, &dir, SUBSYS_PATH_DIST);
2961  }
2962 }
2963 
2964 // fill in the path_num field inside the model_subsystem struct. This is an index into
2965 // the pm->paths[] array, which is a path that provides a frontal approach to a subsystem
2966 // (used for attacking purposes)
2967 //
2968 // NOTE: path_num in model_subsystem has the follows the following convention:
2969 // > 0 => index into pm->paths[] for model that subsystem sits on
2970 // -1 => path is not yet determined (may or may not exist)
2971 // -2 => path doesn't yet exist for this subsystem
2972 void model_set_subsys_path_nums(polymodel *pm, int n_subsystems, model_subsystem *subsystems)
2973 {
2974  int i, j;
2975 
2976  for (i = 0; i < n_subsystems; i++)
2977  subsystems[i].path_num = -1;
2978 
2979  for (i = 0; i < n_subsystems; i++)
2980  {
2981  for (j = 0; j < pm->n_paths; j++)
2982  {
2983  if ( ((subsystems[i].subobj_num != -1) && (subsystems[i].subobj_num == pm->paths[j].parent_submodel)) ||
2984  (!subsystem_stricmp(subsystems[i].subobj_name, pm->paths[j].parent_name)) )
2985  {
2986  if (pm->n_paths > j)
2987  {
2988  subsystems[i].path_num = j;
2990 
2991  break;
2992  }
2993  }
2994  }
2995 
2996  // If a path num wasn't located, then set value to -2
2997  if (subsystems[i].path_num == -1)
2998  subsystems[i].path_num = -2;
2999  }
3000 }
3001 
3002 // Determine the path indices (indicies into pm->paths[]) for the paths used for approaching/departing
3003 // a fighter bay on a capital ship.
3005 {
3006  int i;
3007 
3008  if (pm->ship_bay != NULL)
3009  {
3010  vm_free(pm->ship_bay);
3011  pm->ship_bay = NULL;
3012  }
3013 
3014  /*
3015  // currently only capital ships have fighter bays
3016  if ( !(sip->flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) ) {
3017  return;
3018  }
3019  */
3020 
3021  // malloc out storage for the path information
3022  pm->ship_bay = (ship_bay *) vm_malloc(sizeof(ship_bay));
3023  Assert(pm->ship_bay != NULL);
3024 
3025  pm->ship_bay->num_paths = 0;
3026  // TODO: determine if zeroing out here is affecting any earlier initializations
3027  pm->ship_bay->arrive_flags = 0; // bitfield, set to 1 when that path number is reserved for an arrival
3028  pm->ship_bay->depart_flags = 0; // bitfield, set to 1 when that path number is reserved for a departure
3029 
3030 
3031  // iterate through the paths that exist in the polymodel, searching for $bayN pathnames
3032  bool too_many_paths = false;
3033  for (i = 0; i < pm->n_paths; i++)
3034  {
3035  if (!strnicmp(pm->paths[i].name, NOX("$bay"), 4))
3036  {
3037  int bay_num;
3038  char temp[3];
3039 
3040  strncpy(temp, pm->paths[i].name + 4, 2);
3041  temp[2] = 0;
3042  bay_num = atoi(temp);
3043 
3044  if (bay_num < 1 || bay_num > MAX_SHIP_BAY_PATHS)
3045  {
3046  if(bay_num > MAX_SHIP_BAY_PATHS)
3047  {
3048  too_many_paths = true;
3049  }
3050  if(bay_num < 1)
3051  {
3052  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);
3053  }
3054  continue;
3055  }
3056 
3057  pm->ship_bay->path_indexes[bay_num - 1] = i;
3058  pm->ship_bay->num_paths++;
3059  }
3060  }
3061  if(too_many_paths)
3062  {
3063  Warning(LOCATION, "Model '%s' has too many bay paths - max is %d", pm->filename, MAX_SHIP_BAY_PATHS);
3064  }
3065 }
3066 
3067 // Get "parent" submodel for live debris submodel
3068 int model_get_parent_submodel_for_live_debris( int model_num, int live_debris_model_num )
3069 {
3070  polymodel *pm = model_get(model_num);
3071 
3072  Assert(pm->submodel[live_debris_model_num].is_live_debris == 1);
3073 
3074  int mn;
3075  bsp_info *child;
3076 
3077  // Start with the high level of detail hull
3078  // Check all its children until we find the submodel to which the live debris belongs
3079  child = &pm->submodel[pm->detail[0]];
3080  mn = child->first_child;
3081 
3082  while (mn > 0) {
3083  child = &pm->submodel[mn];
3084 
3085  if (child->num_live_debris > 0) {
3086  // check all live debris submodels for the current child
3087  for (int idx=0; idx<child->num_live_debris; idx++) {
3088  if (child->live_debris[idx] == live_debris_model_num) {
3089  return mn;
3090  }
3091  }
3092  // DKA 5/26/99: can multiple live debris subsystems with each ship
3093  // NO LONGER TRUE Can only be 1 submodel with live debris
3094  // Error( LOCATION, "Could not find parent submodel for live debris. Possible model error");
3095  }
3096 
3097  // get next child
3098  mn = child->next_sibling;
3099  }
3100  Error( LOCATION, "Could not find parent submodel for live debris");
3101  return -1;
3102 }
3103 
3104 
3105 float model_get_radius( int modelnum )
3106 {
3107  polymodel *pm;
3108 
3109  pm = model_get(modelnum);
3110 
3111  return pm->rad;
3112 }
3113 
3114 float model_get_core_radius( int modelnum )
3115 {
3116  polymodel *pm;
3117 
3118  pm = model_get(modelnum);
3119 
3120  return pm->core_radius;
3121 }
3122 
3123 float submodel_get_radius( int modelnum, int submodelnum )
3124 {
3125  polymodel *pm;
3126 
3127  pm = model_get(modelnum);
3128 
3129  return pm->submodel[submodelnum].rad;
3130 }
3131 
3132 
3133 
3134 polymodel * model_get(int model_num)
3135 {
3136  if ( model_num < 0 ) {
3137  Warning(LOCATION, "Invalid model number %d requested. Please post the call stack where an SCP coder can see it.\n", model_num);
3138  return NULL;
3139  }
3140 
3141  int num = model_num % MAX_POLYGON_MODELS;
3142 
3143  Assertion( num >= 0, "Model id %d is invalid. Please backtrace and investigate.\n", num);
3144  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 );
3145  Assertion( Polygon_models[num], "No model with id %d found. Please backtrace and investigate.\n", num );
3146  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 );
3147 
3148  if (num < 0 || num > MAX_POLYGON_MODELS || !Polygon_models[num] || Polygon_models[num]->id != model_num)
3149  return NULL;
3150 
3151  return Polygon_models[num];
3152 }
3153 
3155 {
3156  Assert( model_instance_num >= 0 );
3157  Assert( model_instance_num < (int)Polygon_model_instances.size() );
3158  if ( model_instance_num < 0 || model_instance_num >= (int)Polygon_model_instances.size() ) {
3159  return NULL;
3160  }
3161 
3162  return Polygon_model_instances[model_instance_num];
3163 }
3164 
3165 // Returns zero is x1,y1,x2,y2 are valid
3166 // returns 1 for invalid model, 2 for point offscreen.
3167 // note that x1,y1,x2,y2 aren't clipped to 2d screen coordinates!
3168 int model_find_2d_bound_min(int model_num,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3169 {
3170  polymodel * po;
3171  int n_valid_pts;
3172  int i, x,y,min_x, min_y, max_x, max_y;
3173  int rval = 0;
3174 
3175  po = model_get(model_num);
3176 
3177  g3_start_instance_matrix(pos,orient,false);
3178 
3179  n_valid_pts = 0;
3180 
3181  int hull = po->detail[0];
3182 
3183  min_x = min_y = max_x = max_y = 0;
3184 
3185  for (i=0; i<8; i++ ) {
3186  vertex pt;
3187  ubyte flags;
3188 
3189  flags = g3_rotate_vertex(&pt,&po->submodel[hull].bounding_box[i]);
3190  if ( !(flags&CC_BEHIND) ) {
3191  g3_project_vertex(&pt);
3192 
3193  if (!(pt.flags & PF_OVERFLOW)) {
3194  x = fl2i(pt.screen.xyw.x);
3195  y = fl2i(pt.screen.xyw.y);
3196  if ( n_valid_pts == 0 ) {
3197  min_x = x;
3198  min_y = y;
3199  max_x = x;
3200  max_y = y;
3201  } else {
3202  if ( x < min_x ) min_x = x;
3203  if ( y < min_y ) min_y = y;
3204 
3205  if ( x > max_x ) max_x = x;
3206  if ( y > max_y ) max_y = y;
3207  }
3208  n_valid_pts++;
3209  }
3210  }
3211  }
3212 
3213  if ( n_valid_pts < 8 ) {
3214  rval = 2;
3215  }
3216 
3217  if (x1) *x1 = min_x;
3218  if (y1) *y1 = min_y;
3219 
3220  if (x2) *x2 = max_x;
3221  if (y2) *y2 = max_y;
3222 
3223  g3_done_instance(false);
3224 
3225  return rval;
3226 }
3227 
3228 
3229 // Returns zero is x1,y1,x2,y2 are valid
3230 // returns 1 for invalid model, 2 for point offscreen.
3231 // note that x1,y1,x2,y2 aren't clipped to 2d screen coordinates!
3232 int submodel_find_2d_bound_min(int model_num,int submodel, matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3233 {
3234  polymodel * po;
3235  int n_valid_pts;
3236  int i, x,y,min_x, min_y, max_x, max_y;
3237  bsp_info * sm;
3238 
3239  po = model_get(model_num);
3240  if ( (submodel < 0) || (submodel >= po->n_models ) ) return 1;
3241  sm = &po->submodel[submodel];
3242 
3243  g3_start_instance_matrix(pos,orient,false);
3244 
3245  n_valid_pts = 0;
3246 
3247  min_x = min_y = max_x = max_y = 0;
3248 
3249  for (i=0; i<8; i++ ) {
3250  vertex pt;
3251  ubyte flags;
3252 
3253  flags = g3_rotate_vertex(&pt,&sm->bounding_box[i]);
3254  if ( !(flags&CC_BEHIND) ) {
3255  g3_project_vertex(&pt);
3256 
3257  if (!(pt.flags & PF_OVERFLOW)) {
3258  x = fl2i(pt.screen.xyw.x);
3259  y = fl2i(pt.screen.xyw.y);
3260  if ( n_valid_pts == 0 ) {
3261  min_x = x;
3262  min_y = y;
3263  max_x = x;
3264  max_y = y;
3265  } else {
3266  if ( x < min_x ) min_x = x;
3267  if ( y < min_y ) min_y = y;
3268 
3269  if ( x > max_x ) max_x = x;
3270  if ( y > max_y ) max_y = y;
3271  }
3272  n_valid_pts++;
3273  }
3274  }
3275  }
3276 
3277  if ( n_valid_pts == 0 ) {
3278  return 2;
3279  }
3280 
3281  if (x1) *x1 = min_x;
3282  if (y1) *y1 = min_y;
3283 
3284  if (x2) *x2 = max_x;
3285  if (y2) *y2 = max_y;
3286 
3287  g3_done_instance(false);
3288 
3289  return 0;
3290 }
3291 
3292 
3301 int model_find_2d_bound(int model_num,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3302 {
3303  float t,w,h;
3304  vertex pnt;
3305  polymodel * po;
3306 
3307  po = model_get(model_num);
3308  float width = po->rad;
3309  float height = po->rad;
3310 
3311  g3_rotate_vertex(&pnt,pos);
3312 
3313  if ( pnt.flags & CC_BEHIND )
3314  return 2;
3315 
3316  if (!(pnt.flags&PF_PROJECTED))
3317  g3_project_vertex(&pnt);
3318 
3319  if (pnt.flags & PF_OVERFLOW)
3320  return 2;
3321 
3322  t = (width * Canv_w2)/pnt.world.xyz.z;
3323  w = t*Matrix_scale.xyz.x;
3324 
3325  t = (height*Canv_h2)/pnt.world.xyz.z;
3326  h = t*Matrix_scale.xyz.y;
3327 
3328  if (x1) *x1 = fl2i(pnt.screen.xyw.x - w);
3329  if (y1) *y1 = fl2i(pnt.screen.xyw.y - h);
3330 
3331  if (x2) *x2 = fl2i(pnt.screen.xyw.x + w);
3332  if (y2) *y2 = fl2i(pnt.screen.xyw.y + h);
3333 
3334  return 0;
3335 }
3336 
3345 int subobj_find_2d_bound(float radius ,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
3346 {
3347  float t,w,h;
3348  vertex pnt;
3349 
3350  float width = radius;
3351  float height = radius;
3352 
3353  g3_rotate_vertex(&pnt,pos);
3354 
3355  if ( pnt.flags & CC_BEHIND )
3356  return 2;
3357 
3358  if (!(pnt.flags&PF_PROJECTED))
3359  g3_project_vertex(&pnt);
3360 
3361  if (pnt.flags & PF_OVERFLOW)
3362  return 2;
3363 
3364  t = (width * Canv_w2)/pnt.world.xyz.z;
3365  w = t*Matrix_scale.xyz.x;
3366 
3367  t = (height*Canv_h2)/pnt.world.xyz.z;
3368  h = t*Matrix_scale.xyz.y;
3369 
3370  if (x1) *x1 = fl2i(pnt.screen.xyw.x - w);
3371  if (y1) *y1 = fl2i(pnt.screen.xyw.y - h);
3372 
3373  if (x2) *x2 = fl2i(pnt.screen.xyw.x + w);
3374  if (y2) *y2 = fl2i(pnt.screen.xyw.y + h);
3375 
3376  return 0;
3377 }
3378 
3379 
3380 // Given a vector that is in sub_model_num's frame of
3381 // reference, and given the object's orient and position,
3382 // return the vector in the model's frame of reference.
3383 void model_find_obj_dir(vec3d *w_vec, vec3d *m_vec, int model_num, int submodel_num, matrix *objorient)
3384 {
3385  vec3d tvec, vec;
3386  matrix m;
3387  int mn;
3388 
3389  polymodel *pm = model_get(model_num);
3390  vec = *m_vec;
3391  mn = submodel_num;
3392 
3393  // instance up the tree for this point
3394  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
3395  // By using this kind of computation, the rotational angles can always
3396  // be computed relative to the submodel itself, instead of relative
3397  // to the parent - KeldorKatarn
3398  matrix rotation_matrix = pm->submodel[mn].orientation;
3399  vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);
3400 
3401  matrix inv_orientation;
3402  vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
3403 
3404  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
3405 
3406  vm_vec_unrotate(&tvec, &vec, &m);
3407  vec = tvec;
3408 
3409  mn = pm->submodel[mn].parent;
3410  }
3411 
3412  // now instance for the entire object
3413  vm_vec_unrotate(w_vec, &vec, objorient);
3414 }
3415 
3417 {
3418  vec3d tvec, vec;
3419  matrix m;
3420  int mn;
3421 
3422  polymodel_instance *pmi = model_get_instance(model_instance_num);
3423  polymodel *pm = model_get(pmi->model_num);
3424  vec = *m_vec;
3425  mn = submodel_num;
3426 
3427  // instance up the tree for this point
3428  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
3429  // By using this kind of computation, the rotational angles can always
3430  // be computed relative to the submodel itself, instead of relative
3431  // to the parent - KeldorKatarn
3432  matrix rotation_matrix = pm->submodel[mn].orientation;
3433  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[mn].angs);
3434 
3435  matrix inv_orientation;
3436  vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
3437 
3438  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
3439 
3440  vm_vec_unrotate(&tvec, &vec, &m);
3441  vec = tvec;
3442 
3443  mn = pm->submodel[mn].parent;
3444  }
3445 
3446  // now instance for the entire object
3447  vm_vec_unrotate(w_vec, &vec, objorient);
3448 }
3449 
3450 
3451 // Given a rotating submodel, find the ship and world axes of rotation.
3452 void model_get_rotating_submodel_axis(vec3d *model_axis, vec3d *world_axis, int model_instance_num, int submodel_num, matrix *objorient)
3453 {
3454  polymodel_instance *pmi = model_get_instance(model_instance_num);
3455  polymodel *pm = model_get(pmi->model_num);
3456 
3457  bsp_info *sm = &pm->submodel[submodel_num];
3459 
3460  if (sm->movement_axis == MOVEMENT_AXIS_X) {
3461  vm_vec_make(model_axis, 1.0f, 0.0f, 0.0f);
3462  } else if (sm->movement_axis == MOVEMENT_AXIS_Y) {
3463  vm_vec_make(model_axis, 0.0f, 1.0f, 0.0f);
3464  } else {
3466  vm_vec_make(model_axis, 0.0f, 0.0f, 1.0f);
3467  }
3468 
3469  model_instance_find_obj_dir(world_axis, model_axis, model_instance_num, submodel_num, objorient);
3470 }
3471 
3472 
3473 // Does stepped rotation of a submodel
3475 {
3477 
3478  if ( psub->subobj_num < 0 ) return;
3479 
3480  polymodel *pm = model_get(psub->model_num);
3481  bsp_info *sm = &pm->submodel[psub->subobj_num];
3482 
3483  if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
3484 
3485  // get active rotation time this frame
3486  int end_stamp = timestamp();
3487  // just to make sure this issue wont pop up again... might cause odd jerking in some extremely odd situations
3488  // but given that those issues would require the timer to be reseted in any case it probably wont hurt
3489  float rotation_time;
3490  if ((end_stamp - sii->step_zero_timestamp) < 0) {
3491  sii->step_zero_timestamp = end_stamp;
3492  rotation_time = 0.0f;
3493  } else {
3494  rotation_time = 0.001f * (end_stamp - sii->step_zero_timestamp);
3495  }
3496  //Assert(rotation_time >= 0);
3497 
3498  // save last angles
3499  sii->prev_angs = sii->angs;
3500 
3501  // float pointer into struct to get angle (either p,b,h)
3502  float *ang_prev = NULL, *ang_next = NULL;
3503  switch( sm->movement_axis ) {
3504  case MOVEMENT_AXIS_X:
3505  ang_prev = &sii->prev_angs.p;
3506  ang_next = &sii->angs.p;
3507  break;
3508 
3509  case MOVEMENT_AXIS_Y:
3510  ang_prev = &sii->prev_angs.h;
3511  ang_next = &sii->angs.h;
3512  break;
3513 
3514  case MOVEMENT_AXIS_Z:
3515  ang_prev = &sii->prev_angs.b;
3516  ang_next = &sii->angs.b;
3517  break;
3518  }
3519 
3520  // just in case we got through that switch statement in error
3521  if ( (ang_prev == NULL) && (ang_next == NULL) )
3522  return;
3523 
3524  // angular displacement of one step
3525  float step_size = (PI2 / psub->stepped_rotation->num_steps);
3526 
3527  // get time to complete one step, including pause
3528  float step_time = psub->stepped_rotation->t_transit + psub->stepped_rotation->t_pause;
3529 
3530  // cur_step is step number relative to zero (0 - num_steps)
3531  // step_offset_time is TIME into current step
3532  float step_offset_time = (float)fmod(rotation_time, step_time);
3533  // subtract off fractional step part, round up (ie, 1.999999 -> 2)
3534  int cur_step = int( ((rotation_time - step_offset_time) / step_time) + 0.5f);
3535  // mprintf(("cur step %d\n", cur_step));
3536  // Assert(step_offset_time >= 0);
3537 
3538  if (cur_step >= psub->stepped_rotation->num_steps) {
3539  // I don;t know why, but removing this line makes it all good.
3540  // sii->step_zero_timestamp += int(1000.0f * (psub->stepped_rotation->num_steps * step_time) + 0.5f);
3541 
3542  // reset cur_step (use mod to handle physics/ai pause)
3543  cur_step = cur_step % psub->stepped_rotation->num_steps;
3544  }
3545 
3546  // get base angle
3547  *ang_next = cur_step * step_size;
3548 
3549  // determine which phase of rotation we're in
3550  float coast_start_time = psub->stepped_rotation->fraction * psub->stepped_rotation->t_transit;
3551  float decel_start_time = psub->stepped_rotation->t_transit * (1.0f - psub->stepped_rotation->fraction);
3552  float pause_start_time = psub->stepped_rotation->t_transit;
3553 
3554  float start_coast_angle = 0.5f * psub->stepped_rotation->max_turn_accel * coast_start_time * coast_start_time;
3555 
3556  if (step_offset_time < coast_start_time) {
3557  // do accel
3558  float accel_time = step_offset_time;
3559  *ang_next += 0.5f * psub->stepped_rotation->max_turn_accel * accel_time * accel_time;
3560  sii->cur_turn_rate = psub->stepped_rotation->max_turn_accel * accel_time;
3561  } else if (step_offset_time < decel_start_time) {
3562  // do coast
3563  float coast_time = step_offset_time - coast_start_time;
3564  *ang_next += start_coast_angle + psub->stepped_rotation->max_turn_rate * coast_time;
3566  } else if (step_offset_time < pause_start_time) {
3567  // do decel
3568  float time_to_pause = psub->stepped_rotation->t_transit - step_offset_time;
3569  *ang_next += (step_size - 0.5f * psub->stepped_rotation->max_turn_accel * time_to_pause * time_to_pause);
3570  sii->cur_turn_rate = psub->stepped_rotation->max_turn_rate * time_to_pause;
3571  } else {
3572  // do pause
3573  *ang_next += step_size;
3574  sii->cur_turn_rate = 0.0f;
3575  }
3576 }
3577 
3579 
3581 {
3582  bsp_info * sm;
3583 
3584  if ( mn < 0 ) {
3585  return;
3586  }
3587 
3588  sm = &pm->submodel[mn];
3589  angles *angs = &pm->submodel[mn].angs;
3590 
3591  if ( sm->movement_type != MOVEMENT_TYPE_LOOK_AT ) {
3592  return;
3593  }
3594 
3595  vec3d other, mp;
3596 
3597  int pmn = pm->id;
3598 
3599  // VA - Run this bit only once for each look_at enabled submodel, to correctly associate the name given in the $look_at: property with the number of that named subobject
3600  if (sm->look_at_num == -2) {
3601  // Search through submodels for the look_at target name
3602  for (int i = 0; i < pm->n_models; i++) {
3603  if (!strcmp(sm->look_at, pm->submodel[i].name)) {
3604  sm->look_at_num = i; // Found it
3605  nprintf(("Model", "NOTE: Matched $look_at: target <%s> with subobject id %d\n", sm->look_at, i));
3606  break;
3607  }
3608  }
3609 
3610  if (sm->look_at_num == -2) {
3611  Warning( LOCATION, "Invalid submodel name given in $look_at: property in model file <%s>. (%s looking for %s)\n", pm->filename, pm->submodel->name, sm->look_at );
3612  sm->look_at_num = -1; // Set to -1 to not break stuff
3613  }
3614  }
3615 
3618 
3619  if (!IS_MAT_NULL(&pm->submodel[mn].orientation)) {
3620  vm_vec_rotate(&mp, &other, &pm->submodel[mn].orientation);
3621  } else {
3622  mp = other;
3623  }
3624 
3625  vec3d d, l;
3626  model_find_submodel_offset(&d, pmn, mn);
3628  vm_vec_sub(&other, &l, &d);
3629 
3630  if (!IS_MAT_NULL(&pm->submodel[mn].orientation)) {
3631  vm_vec_rotate(&l, &other, &pm->submodel[mn].orientation);
3632  } else {
3633  l = other;
3634  }
3635 
3636  float *a;
3637  int axis;
3638 
3639  switch( sm->movement_axis ) {
3640  default:
3641  case MOVEMENT_AXIS_X:
3642  l.xyz.x = 0;
3643  mp.xyz.x = 0;
3644  a = &angs->p;
3645  axis = 0;
3646  break;
3647 
3648  case MOVEMENT_AXIS_Y:
3649  l.xyz.y = 0;
3650  mp.xyz.y = 0;
3651  a = &angs->h;
3652  axis = 1;
3653  break;
3654 
3655  case MOVEMENT_AXIS_Z:
3656  l.xyz.z = 0;
3657  mp.xyz.z = 0;
3658  a = &angs->b;
3659  axis = 2;
3660  break;
3661  }
3662 
3663  vm_vec_normalize(&mp);
3664  vm_vec_normalize(&l);
3665 
3666  vec3d c;
3667  vm_vec_cross(&c, &l, &mp);
3668  float dot=vm_vec_dot(&l,&mp);
3669  if (dot>=0.0f) {
3670  *a = asinf(c.a1d[axis]);
3671  } else {
3672  *a = PI-asinf(c.a1d[axis]);
3673  }
3674 
3675  if (*a > PI2 ) {
3676  *a -= PI2;
3677  } else { if (*a < 0.0f )
3678  *a += PI2;
3679  }
3680 
3681  for (int k=0; k<sm->num_details; k++ ) {
3682  pm->submodel[sm->details[k]].angs = *angs;
3683  }
3684 
3685 }
3686 
3687 // Rotates the angle of a submodel, when the submodel has a subsystem (which is almost always the case)
3689 {
3690  bsp_info * sm;
3691 
3692  if ( psub->subobj_num < 0 ) return;
3693 
3694  polymodel *pm = model_get(psub->model_num);
3695  sm = &pm->submodel[psub->subobj_num];
3696 
3697  if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
3698 
3699  submodel_rotate(sm, sii);
3700 }
3701 
3702 // Rotates the angle of a submodel. If the submodel has a subsystem, the execution flow should first go through the other
3703 // submodel_rotate function before this one. (This function is called directly in the case of dumb_rotation.)
3705 {
3706  // save last angles
3707  sii->prev_angs = sii->angs;
3708 
3709  // probably send in a calculated desired turn rate
3710  float diff = sii->desired_turn_rate - sii->cur_turn_rate;
3711 
3712  float final_turn_rate;
3713  if (diff > 0) {
3714  final_turn_rate = sii->cur_turn_rate + sii->turn_accel * flFrametime;
3715  if (final_turn_rate > sii->desired_turn_rate) {
3716  final_turn_rate = sii->desired_turn_rate;
3717  }
3718  } else if (diff < 0) {
3719  final_turn_rate = sii->cur_turn_rate - sii->turn_accel * flFrametime;
3720  if (final_turn_rate < sii->desired_turn_rate) {
3721  final_turn_rate = sii->desired_turn_rate;
3722  }
3723  } else {
3724  final_turn_rate = sii->desired_turn_rate;
3725  }
3726 
3727  float delta = (sii->cur_turn_rate + final_turn_rate) * 0.5f * flFrametime;
3728  sii->cur_turn_rate = final_turn_rate;
3729 
3730  // Apply rotation in the axis of movement
3731  // then normalize the angle angle so that we are within a valid range:
3732  // greater than or equal to 0
3733  // less than PI2
3734  switch( sm->movement_axis ) {
3735  case MOVEMENT_AXIS_X:
3736  sii->angs.p += delta;
3737 
3738  while (sii->angs.p > PI2)
3739  sii->angs.p -= PI2;
3740  while (sii->angs.p < 0.0f)
3741  sii->angs.p += PI2;
3742 
3743  break;
3744  case MOVEMENT_AXIS_Y:
3745  sii->angs.h += delta;
3746 
3747  while (sii->angs.h > PI2)
3748  sii->angs.h -= PI2;
3749  while (sii->angs.h < 0.0f)
3750  sii->angs.h += PI2;
3751 
3752  break;
3753  case MOVEMENT_AXIS_Z:
3754  sii->angs.b += delta;
3755 
3756  while (sii->angs.b > PI2)
3757  sii->angs.b -= PI2;
3758  while (sii->angs.b < 0.0f)
3759  sii->angs.b += PI2;
3760 
3761  break;
3762  }
3763 }
3764 /*
3765 void submodel_ai_rotate(model_subsystem *psub, submodel_instance_info *sii)
3766 {
3767  bsp_info * sm;
3768 
3769  if ( psub->subobj_num < 0 ) return;
3770  if(psub->ai_rotation.type = 0) return;
3771 
3772  polymodel *pm = model_get(psub->model_num);
3773  sm = &pm->submodel[psub->subobj_num];
3774 
3775  if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
3776 
3777 
3778  // save last angles
3779  sii->prev_angs = sii->angs;
3780 
3781  // probably send in a calculated desired turn rate
3782  float diff = sii->desired_turn_rate - sii->cur_turn_rate;
3783 
3784  float final_turn_rate;
3785  if (diff > 0) {
3786  final_turn_rate = sii->cur_turn_rate + sii->turn_accel * flFrametime;
3787  if (final_turn_rate > sii->desired_turn_rate) {
3788  final_turn_rate = sii->desired_turn_rate;
3789  }
3790  } else if (diff < 0) {
3791  final_turn_rate = sii->cur_turn_rate - sii->turn_accel * flFrametime;
3792  if (final_turn_rate < sii->desired_turn_rate) {
3793  final_turn_rate = sii->desired_turn_rate;
3794  }
3795  } else {
3796  final_turn_rate = sii->desired_turn_rate;
3797  }
3798 
3799  float delta = (sii->cur_turn_rate + final_turn_rate) * 0.5f * flFrametime;
3800  sii->cur_turn_rate = final_turn_rate;
3801 
3802 
3803  //float delta = psub->turn_rate * flFrametime;
3804 
3805  switch( sm->movement_axis ) {
3806  case MOVEMENT_AXIS_X:
3807  if (sii->angs.p + delta > psub->ai_rotation.max ){//if it will or has gone past it's max then set it to the max/min
3808  sii->angs.p = psub->ai_rotation.max;
3809  return;
3810  } else if(sii->angs.p + delta < psub->ai_rotation.min){
3811  sii->angs.p = psub->ai_rotation.min;
3812  return;
3813  }
3814  sii->angs.p += delta;
3815  if (sii->angs.p > PI2 )
3816  sii->angs.p -= PI2;
3817  else if (sii->angs.p < 0.0f )
3818  sii->angs.p += PI2;
3819  break;
3820  case MOVEMENT_AXIS_Y:
3821  sii->angs.h += delta;
3822  if (sii->angs.h > PI2 )
3823  sii->angs.h -= PI2;
3824  else if (sii->angs.h < 0.0f )
3825  sii->angs.h += PI2;
3826  break;
3827  case MOVEMENT_AXIS_Z:
3828  sii->angs.b += delta;
3829  if (sii->angs.b > PI2 )
3830  sii->angs.b -= PI2;
3831  else if (sii->angs.b < 0.0f )
3832  sii->angs.b += PI2;
3833  break;
3834  }
3835 }
3836 */
3837 
3838 
3839 //=========================================================================
3840 // Make a turret's correct orientation matrix. This should be done when
3841 // the model is read, but I wasn't sure at what point all the data that I
3842 // needed was read, so I just check a flag and call this routine when
3843 // I determine I need the correct matrix. In this code, you can't use
3844 // vm_vec_2_matrix or anything, since these turrets could be either
3845 // right handed or left handed.
3846 void model_make_turret_matrix(int model_num, model_subsystem * turret )
3847 {
3848  polymodel * pm;
3849  vec3d fvec, uvec, rvec;
3850 
3851  pm = model_get(model_num);
3852  bsp_info * gun = &pm->submodel[turret->turret_gun_sobj];
3853  bsp_info * base = &pm->submodel[turret->subobj_num];
3854  float offset_base_h = 0.0f;
3855  float offset_barrel_h = 0.0f;
3856 #ifdef WMC_SIDE_TURRETS
3857  offset_base_h = -PI_2;
3858  offset_barrel_h = -PI_2;
3859 #endif
3860 
3861  if (base->force_turret_normal == true)
3862  turret->turret_norm = base->orientation.vec.uvec;
3863 
3864  model_clear_instance(model_num);
3865  base->angs.h = offset_base_h;
3866  gun->angs.h = offset_barrel_h;
3867  model_find_world_dir(&fvec, &turret->turret_norm, model_num, turret->turret_gun_sobj, &vmd_identity_matrix);
3868 
3869  base->angs.h = -PI_2 + offset_base_h;
3870  gun->angs.p = -PI_2;
3871  gun->angs.h = offset_barrel_h;
3872  model_find_world_dir(&rvec, &turret->turret_norm, model_num, turret->turret_gun_sobj, &vmd_identity_matrix);
3873 
3874  base->angs.h = 0.0f + offset_base_h;
3875  gun->angs.p = -PI_2;
3876  gun->angs.h = offset_barrel_h;
3877  model_find_world_dir(&uvec, &turret->turret_norm, model_num, turret->turret_gun_sobj, &vmd_identity_matrix);
3878 
3879  vm_vec_normalize(&fvec);
3880  vm_vec_normalize(&rvec);
3881  vm_vec_normalize(&uvec);
3882 
3883  turret->turret_matrix.vec.fvec = fvec;
3884  turret->turret_matrix.vec.rvec = rvec;
3885  turret->turret_matrix.vec.uvec = uvec;
3886 
3887 // vm_vector_2_matrix(&turret->turret_matrix,&turret->turret_norm,NULL,NULL);
3888 
3889  // HACK!! WARNING!!!
3890  // I'm doing nothing to verify that this matrix is orthogonal!!
3891  // In other words, there's no guarantee that the vectors are 90 degrees
3892  // from each other.
3893  // I'm not doing this because I don't know how to do it without ruining
3894  // the handedness of the matrix... however, I'm not too worried about
3895  // this because I am creating these 3 vectors by making them 90 degrees
3896  // apart, so this should be close enough. I think this will start
3897  // causing weird errors when we view from turrets. -John
3898  turret->flags |= MSS_FLAG_TURRET_MATRIX;
3899 }
3900 
3901 // Tries to move joints so that the turret points to the point dst.
3902 // turret1 is the angles of the turret, turret2 is the angles of the gun from turret
3903 // Returns 1 if rotated gun, 0 if no gun to rotate (rotation handled by AI)
3904 int model_rotate_gun(int model_num, model_subsystem *turret, matrix *orient, angles *base_angles, angles *gun_angles, vec3d *pos, vec3d *dst, int obj_idx, bool reset)
3905 {
3906  polymodel * pm;
3907  object *objp = &Objects[obj_idx];
3908  ship *shipp = &Ships[objp->instance];
3909  ship_subsys *ss = ship_get_subsys(shipp, turret->subobj_name);
3910 
3911  pm = model_get(model_num);
3912  bsp_info * gun = &pm->submodel[turret->turret_gun_sobj];
3913  bsp_info * base = &pm->submodel[turret->subobj_num];
3914  bool limited_base_rotation = false;
3915 
3916  // Check for a valid turret
3917  Assert( turret->turret_num_firing_points > 0 );
3918  // Check for a valid subsystem
3919  Assert( ss != NULL );
3920 
3921  //This should not happen
3922  if ( base == gun ) {
3923  return 0;
3924  }
3925 
3926  // Build the correct turret matrix if there isn't already one
3927  if ( !(turret->flags & MSS_FLAG_TURRET_MATRIX) )
3928  model_make_turret_matrix(model_num, turret );
3929 
3930  Assert( turret->flags & MSS_FLAG_TURRET_MATRIX);
3931 // Assert( gun->movement_axis == MOVEMENT_AXIS_X ); // Gun must be able to change pitch
3932 // Assert( base->movement_axis == MOVEMENT_AXIS_Z ); // Parent must be able to change heading
3933 
3934  //------------
3935  // rotate the dest point into the turret gun normal's frame of
3936  // reference, but not using the turret's angles.
3937  // Call this vector of_dst
3938  vec3d of_dst;
3939  matrix world_to_turret_matrix; // converts world coordinates to turret's FOR
3940  vec3d world_to_turret_translate; // converts world coordinates to turret's FOR
3941  vec3d tempv;
3942 
3943  vm_vec_unrotate( &tempv, &base->offset, orient);
3944  vm_vec_add( &world_to_turret_translate, pos, &tempv );
3945 
3946  if (turret->flags & MSS_FLAG_TURRET_ALT_MATH)
3947  world_to_turret_matrix = ss->world_to_turret_matrix;
3948  else
3949  vm_matrix_x_matrix( &world_to_turret_matrix, orient, &turret->turret_matrix );
3950 
3951  vm_vec_sub( &tempv, dst, &world_to_turret_translate );
3952  vm_vec_rotate( &of_dst, &tempv, &world_to_turret_matrix );
3953 
3954  vm_vec_normalize(&of_dst);
3955 
3956  //------------
3957  // Find the heading and pitch that the gun needs to turn to
3958  // by extracting them from the of_dst vector.
3959  // Call this the desired_angles
3960  angles desired_angles;
3961 // vm_extract_angles_vector(&desired_angles, &of_dst);
3962 
3963  if (reset == false) {
3964  desired_angles.p = acosf(of_dst.xyz.z);
3965  desired_angles.h = PI - atan2_safe(of_dst.xyz.x, of_dst.xyz.y);
3966  desired_angles.b = 0.0f;
3967  } else {
3968  desired_angles.p = 0.0f;
3969  desired_angles.h = 0.0f;
3970  desired_angles.b = 0.0f;
3971  if (turret->n_triggers > 0) {
3972  int i;
3973  for (i = 0; i<turret->n_triggers; i++) {
3974  desired_angles.p = turret->triggers[i].angle.xyz.x;
3975  desired_angles.h = turret->triggers[i].angle.xyz.y;
3976  }
3977  }
3978  }
3979 
3980  if (turret->flags & MSS_FLAG_TURRET_ALT_MATH)
3981  limited_base_rotation = true;
3982 
3983  // mprintf(( "Z = %.1f, atan= %.1f\n", of_dst.xyz.z, desired_angles.p ));
3984 
3985  //------------
3986  // Gradually turn the turret towards the desired angles
3987  float step_size = turret->turret_turning_rate * flFrametime;
3988  float base_delta, gun_delta;
3989 
3990  if (reset == true)
3991  step_size /= 3.0f;
3992  else
3994 
3995  // reset these two
3996  ss->base_rotation_rate_pct = 0.0f;
3997  ss->gun_rotation_rate_pct = 0.0f;
3998 
3999  base_delta = vm_interp_angle(&base_angles->h, desired_angles.h, step_size, limited_base_rotation);
4000  gun_delta = vm_interp_angle(&gun_angles->p, desired_angles.p, step_size);
4001 
4002  if (turret->turret_base_rotation_snd != -1)
4003  {
4004  if (step_size > 0)
4005  {
4006  base_delta = (float) (fabs(base_delta)) / step_size;
4007  if (base_delta > 1.0f)
4008  base_delta = 1.0f;
4009  ss->base_rotation_rate_pct = base_delta;
4010  }
4011  }
4012 
4013  if (turret->turret_gun_rotation_snd != -1)
4014  {
4015  if (step_size > 0)
4016  {
4017  gun_delta = (float) (fabs(gun_delta)) / step_size;
4018  if (gun_delta > 1.0f)
4019  gun_delta = 1.0f;
4020  ss->gun_rotation_rate_pct = gun_delta;
4021  }
4022  }
4023 
4024 // base_angles->h -= step_size*(key_down_timef(KEY_1)-key_down_timef(KEY_2) );
4025 // gun_angles->p += step_size*(key_down_timef(KEY_3)-key_down_timef(KEY_4) );
4026 
4027  if (turret->flags & MSS_FLAG_FIRE_ON_TARGET)
4028  {
4029  base_delta = vm_delta_from_interp_angle( base_angles->h, desired_angles.h );
4030  gun_delta = vm_delta_from_interp_angle( gun_angles->p, desired_angles.p );
4031  ss->points_to_target = sqrt( pow(base_delta,2) + pow(gun_delta,2));
4032  }
4033 
4034  return 1;
4035 
4036 }
4037 
4038 
4039 // Goober5000
4040 // For a submodel, return its overall offset from the main model.
4041 void model_find_submodel_offset(vec3d *outpnt, int model_num, int sub_model_num)
4042 {
4043  int mn;
4044  polymodel *pm = model_get(model_num);
4045 
4046  vm_vec_zero(outpnt);
4047  mn = sub_model_num;
4048 
4049  //instance up the tree for this point
4050  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4051  vm_vec_add2(outpnt, &pm->submodel[mn].offset);
4052 
4053  mn = pm->submodel[mn].parent;
4054  }
4055 }
4056 
4058  if (pm->submodel[sn].parent != -1) {
4060  }
4061 
4062  vm_vec_sub2(v, &pm->submodel[sn].offset);
4063  vec3d t = *v;
4064 
4065  matrix a;
4066  vm_angles_2_matrix(&a, &pm->submodel[sn].angs);
4067  if (!IS_MAT_NULL(&pm->submodel[sn].orientation)) {
4068  matrix inv, f;
4069  vm_copy_transpose(&inv, &pm->submodel[sn].orientation);
4070  vm_matrix_x_matrix(&f, &a, &inv);
4071  vm_matrix_x_matrix(&a, &pm->submodel[sn].orientation, &f);
4072  }
4073  vm_vec_rotate(v, &t, &a);
4074 }
4075 
4076 // just like below, exept it actualy does what it says it does
4078 {
4079  vec3d tempv1, tempv2;
4080 
4081  // get into ship RF
4082  vm_vec_sub(&tempv1, world_pt, pos);
4083  vm_vec_rotate(&tempv2, &tempv1, orient);
4084 
4085  if (pm->submodel[submodel_num].parent == -1) {
4086  *out = tempv2;
4087  return;
4088  }
4089 
4090  //vec3d os = ZERO_VECTOR;
4091  // put into submodel RF
4092  make_submodel_world_matrix(pm,submodel_num, &tempv2);
4093  *out = tempv2;
4094 }
4095 
4096 // Given a point (pnt) that is in submodel_num's frame of
4097 // reference, and given the object's orient and position,
4098 // return the point in 3-space in outpnt.
4099 void model_find_world_point(vec3d *outpnt, vec3d *mpnt, int model_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
4100 {
4101  vec3d pnt;
4102  vec3d tpnt;
4103  matrix m;
4104  int mn;
4105  polymodel *pm = model_get(model_num);
4106 
4107  pnt = *mpnt;
4108  mn = submodel_num;
4109 
4110  //instance up the tree for this point
4111  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4112  // By using this kind of computation, the rotational angles can always
4113  // be computed relative to the submodel itself, instead of relative
4114  // to the parent - KeldorKatarn
4115  matrix rotation_matrix = pm->submodel[mn].orientation;
4116  vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);
4117 
4118  matrix inv_orientation;
4119  vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
4120 
4121  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
4122 
4123  vm_vec_unrotate(&tpnt, &pnt, &m);
4124 
4125  vm_vec_add(&pnt, &tpnt, &pm->submodel[mn].offset);
4126 
4127  mn = pm->submodel[mn].parent;
4128  }
4129 
4130  //now instance for the entire object
4131  vm_vec_unrotate(outpnt,&pnt,objorient);
4132  vm_vec_add2(outpnt,objpos);
4133 }
4134 
4135 void model_instance_find_world_point(vec3d *outpnt, vec3d *mpnt, int model_instance_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
4136 {
4137  vec3d pnt;
4138  vec3d tpnt;
4139  matrix m;
4140  int mn;
4141  polymodel_instance *pmi = model_get_instance(model_instance_num);
4142  polymodel *pm = model_get(pmi->model_num);
4143 
4144  pnt = *mpnt;
4145  mn = submodel_num;
4146 
4147  //instance up the tree for this point
4148  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4149  // By using this kind of computation, the rotational angles can always
4150  // be computed relative to the submodel itself, instead of relative
4151  // to the parent - KeldorKatarn
4152  matrix rotation_matrix = pm->submodel[mn].orientation;
4153  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[mn].angs);
4154 
4155  matrix inv_orientation;
4156  vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
4157 
4158  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
4159 
4160  vm_vec_unrotate(&tpnt, &pnt, &m);
4161 
4162  vm_vec_add(&pnt, &tpnt, &pm->submodel[mn].offset);
4163 
4164  mn = pm->submodel[mn].parent;
4165  }
4166 
4167  //now instance for the entire object
4168  vm_vec_unrotate(outpnt,&pnt,objorient);
4169  vm_vec_add2(outpnt,objpos);
4170 }
4171 
4172 // Given a point in the world RF, find the corresponding point in the model RF.
4173 // This is special purpose code, specific for model collision.
4174 // NOTE - this code ASSUMES submodel is 1 level down from hull (detail[0])
4175 //
4176 // out - point in model RF
4177 // world_pt - point in world RF
4178 // pm - polygon model
4179 // submodel_num - submodel in whose RF we're trying to find the corresponding world point
4180 // orient - orient matrix of ship
4181 // pos - pos vector of ship
4182 void world_find_model_point(vec3d *out, vec3d *world_pt, const polymodel *pm, int submodel_num, const matrix *orient, const vec3d *pos)
4183 {
4184  Assert( (pm->submodel[submodel_num].parent == pm->detail[0]) || (pm->submodel[submodel_num].parent == -1) );
4185 
4186  vec3d tempv1, tempv2;
4187  matrix m;
4188 
4189  // get into ship RF
4190  vm_vec_sub(&tempv1, world_pt, pos);
4191  vm_vec_rotate(&tempv2, &tempv1, orient);
4192 
4193  if (pm->submodel[submodel_num].parent == -1) {
4194  *out = tempv2;
4195  return;
4196  }
4197 
4198  // put into submodel RF
4199  vm_vec_sub2(&tempv2, &pm->submodel[submodel_num].offset);
4200 
4201  // By using this kind of computation, the rotational angles can always
4202  // be computed relative to the submodel itself, instead of relative
4203  // to the parent - KeldorKatarn
4204  matrix rotation_matrix = pm->submodel[submodel_num].orientation;
4205  vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[submodel_num].angs);
4206 
4207  matrix inv_orientation;
4208  vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
4209 
4210  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
4211 
4212  vm_vec_rotate(out, &tempv2, &m);
4213 }
4214 
4215 void world_find_model_instance_point(vec3d *out, vec3d *world_pt, const polymodel_instance *pmi, int submodel_num, const matrix *orient, const vec3d *pos)
4216 {
4217  polymodel *pm = model_get(pmi->model_num);
4218 
4219  Assert( (pm->submodel[submodel_num].parent == pm->detail[0]) || (pm->submodel[submodel_num].parent == -1) );
4220 
4221  vec3d tempv1, tempv2;
4222  matrix m;
4223 
4224  // get into ship RF
4225  vm_vec_sub(&tempv1, world_pt, pos);
4226  vm_vec_rotate(&tempv2, &tempv1, orient);
4227 
4228  if (pm->submodel[submodel_num].parent == -1) {
4229  *out = tempv2;
4230  return;
4231  }
4232 
4233  // put into submodel RF
4234  vm_vec_sub2(&tempv2, &pm->submodel[submodel_num].offset);
4235 
4236  // By using this kind of computation, the rotational angles can always
4237  // be computed relative to the submodel itself, instead of relative
4238  // to the parent - KeldorKatarn
4239  matrix rotation_matrix = pm->submodel[submodel_num].orientation;
4240  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[submodel_num].angs);
4241 
4242  matrix inv_orientation;
4243  vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
4244 
4245  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
4246 
4247  vm_vec_rotate(out, &tempv2, &m);
4248 }
4249 
4259 {
4260  vm_vec_zero(outpnt);
4261  matrix submodel_instance_matrix, rotation_matrix, inv_orientation;
4262 
4263  polymodel_instance *pmi = model_get_instance(model_instance_num);
4264  polymodel *pm = model_get(pmi->model_num);
4265 
4266  int mn = submodel_num;
4267  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4268  vec3d offset = pm->submodel[mn].offset;
4269 
4270  int parent_mn = pm->submodel[mn].parent;
4271 
4272  if (pm->submodel[parent_mn].can_move) {
4273  rotation_matrix = pm->submodel[parent_mn].orientation;
4274  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[parent_mn].angs);
4275 
4276  vm_copy_transpose(&inv_orientation, &pm->submodel[parent_mn].orientation);
4277 
4278  vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
4279 
4280  vec3d tvec = offset;
4281  vm_vec_unrotate(&offset, &tvec, &submodel_instance_matrix);
4282  }
4283 
4284  vm_vec_add2(outpnt, &offset);
4285 
4286  mn = parent_mn;
4287  }
4288 }
4289 
4302 void find_submodel_instance_point_normal(vec3d *outpnt, vec3d *outnorm, int model_instance_num, int submodel_num, const vec3d *submodel_pnt, const vec3d *submodel_norm)
4303 {
4304  *outnorm = *submodel_norm;
4305  vm_vec_zero(outpnt);
4306  matrix submodel_instance_matrix, rotation_matrix, inv_orientation;
4307 
4308  polymodel_instance *pmi = model_get_instance(model_instance_num);
4309  polymodel *pm = model_get(pmi->model_num);
4310 
4311  int mn = submodel_num;
4312  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4313  vec3d offset = pm->submodel[mn].offset;
4314 
4315  if ( mn == submodel_num) {
4316  vec3d submodel_pnt_offset = *submodel_pnt;
4317 
4318  rotation_matrix = pm->submodel[submodel_num].orientation;
4319  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[submodel_num].angs);
4320 
4321  vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
4322 
4323  vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
4324 
4325  vec3d tvec = submodel_pnt_offset;
4326  vm_vec_unrotate(&submodel_pnt_offset, &tvec, &submodel_instance_matrix);
4327 
4328  vec3d tnorm = *outnorm;
4329  vm_vec_unrotate(outnorm, &tnorm, &submodel_instance_matrix);
4330 
4331  vm_vec_add2(&offset, &submodel_pnt_offset);
4332  }
4333 
4334  int parent_model_num = pm->submodel[mn].parent;
4335 
4336  rotation_matrix = pm->submodel[parent_model_num].orientation;
4337  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[parent_model_num].angs);
4338 
4339  vm_copy_transpose(&inv_orientation, &pm->submodel[parent_model_num].orientation);
4340 
4341  vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
4342 
4343  vec3d tvec = offset;
4344  vm_vec_unrotate(&offset, &tvec, &submodel_instance_matrix);
4345 
4346  vec3d tnorm = *outnorm;
4347  vm_vec_unrotate(outnorm, &tnorm, &submodel_instance_matrix);
4348 
4349  vm_vec_add2(outpnt, &offset);
4350 
4351  mn = parent_model_num;
4352  }
4353 }
4354 
4370 void find_submodel_instance_point_orient(vec3d *outpnt, matrix *outorient, int model_instance_num, int submodel_num, const vec3d *submodel_pnt, const matrix *submodel_orient)
4371 {
4372  *outorient = *submodel_orient;
4373  vm_vec_zero(outpnt);
4374  matrix submodel_instance_matrix, rotation_matrix, inv_orientation;
4375 
4376  polymodel_instance *pmi = model_get_instance(model_instance_num);
4377  polymodel *pm = model_get(pmi->model_num);
4378 
4379  int mn = submodel_num;
4380  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4381  vec3d offset = pm->submodel[mn].offset;
4382 
4383  if ( mn == submodel_num) {
4384  vec3d submodel_pnt_offset = *submodel_pnt;
4385 
4386  rotation_matrix = pm->submodel[submodel_num].orientation;
4387  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[submodel_num].angs);
4388 
4389  vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
4390 
4391  vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
4392 
4393  vec3d tvec = submodel_pnt_offset;
4394  vm_vec_unrotate(&submodel_pnt_offset, &tvec, &submodel_instance_matrix);
4395 
4396  matrix tnorm = *outorient;
4397  vm_matrix_x_matrix(outorient, &tnorm, &submodel_instance_matrix);
4398 
4399  vm_vec_add2(&offset, &submodel_pnt_offset);
4400  }
4401 
4402  int parent_model_num = pm->submodel[mn].parent;
4403 
4404  rotation_matrix = pm->submodel[parent_model_num].orientation;
4405  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[parent_model_num].angs);
4406 
4407  vm_copy_transpose(&inv_orientation, &pm->submodel[parent_model_num].orientation);
4408 
4409  vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
4410 
4411  vec3d tvec = offset;
4412  vm_vec_unrotate(&offset, &tvec, &submodel_instance_matrix);
4413 
4414  matrix tnorm = *outorient;
4415  vm_matrix_x_matrix(outorient, &tnorm, &submodel_instance_matrix);
4416 
4417  vm_vec_add2(outpnt, &offset);
4418 
4419  mn = parent_model_num;
4420  }
4421 }
4422 
4431 void find_submodel_instance_world_point(vec3d *outpnt, int model_instance_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
4432 {
4433  vec3d loc_pnt;
4434 
4435  find_submodel_instance_point(&loc_pnt, model_instance_num, submodel_num);
4436 
4437  vm_vec_unrotate(outpnt, &loc_pnt, objorient);
4438  vm_vec_add2(outpnt, objpos);
4439 }
4440 
4441 // Verify rotating submodel has corresponding ship subsystem -- info in which to store rotation angle
4443 {
4444  model_subsystem *psub;
4445  ship_subsys *pss;
4446 
4447  int found = 0;
4448 
4449  // Go through all subsystems and look for submodel
4450  // the subsystems that need it.
4451  for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
4452  psub = pss->system_info;
4453  if (psub->subobj_num == submodel) {
4454  found = 1;
4455  break;
4456  }
4457  }
4458 
4459  return found;
4460 }
4461 
4462 /*
4463  * Get all submodel indexes that satisfy the following:
4464  * 1) Have the rotating or intrinsic-rotating movement type
4465  * 2) Are currently rotating (i.e. actually moving and not part of the superstructure due to being destroyed or replaced)
4466  * 3) Are not rotating too far for collision detection (c.f. MAX_SUBMODEL_COLLISION_ROT_ANGLE)
4467  */
4469 {
4470  Assert(objp->type == OBJ_SHIP || objp->type == OBJ_WEAPON || objp->type == OBJ_ASTEROID);
4471 
4472  int model_instance_num;
4473  int model_num;
4474  if (objp->type == OBJ_SHIP) {
4475  model_instance_num = Ships[objp->instance].model_instance_num;
4476  model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
4477  }
4478  else if (objp->type == OBJ_WEAPON) {
4479  model_instance_num = Weapons[objp->instance].model_instance_num;
4480  if (model_instance_num < 0) {
4481  return;
4482  }
4484  }
4485  else if (objp->type == OBJ_ASTEROID) {
4486  model_instance_num = Asteroids[objp->instance].model_instance_num;
4487  if (model_instance_num < 0) {
4488  return;
4489  }
4490  model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
4491  }
4492  else {
4493  return;
4494  }
4495 
4496  polymodel *pm = model_get(model_num);
4497  bsp_info *child_submodel = &pm->submodel[pm->detail[0]];
4498 
4499  if(child_submodel->no_collisions) { // if detail0 has $no_collision set dont check childs
4500  return;
4501  }
4502 
4503  polymodel_instance *pmi = model_get_instance(model_instance_num);
4504 
4505  int i = child_submodel->first_child;
4506  while ( i >= 0 ) {
4507  child_submodel = &pm->submodel[i];
4508 
4509  // Don't check it or its children if it is destroyed or it is a replacement (non-moving)
4510  if ( !child_submodel->blown_off && (child_submodel->i_replace == -1) && !child_submodel->no_collisions && !child_submodel->nocollide_this_only) {
4511 
4512  // Only look for submodels that rotate or intrinsic-rotate
4513  if (child_submodel->movement_type == MOVEMENT_TYPE_ROT || child_submodel->movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
4514 
4515  // check submodel rotation is less than max allowed.
4516  submodel_instance_info *sii = pmi->submodel[i].sii;
4517 
4518  // If there is no instance info yet then ignore this submodel
4519  if (sii != nullptr) {
4520 
4521  // found the correct submodel instance - now check delta rotation angle not too large
4522  float delta_angle = get_submodel_delta_angle(sii);
4523  if (delta_angle < MAX_SUBMODEL_COLLISION_ROT_ANGLE) {
4524  submodel_vector->push_back(i);
4525  }
4526  }
4527  }
4528  }
4529  i = child_submodel->next_sibling;
4530  }
4531 }
4532 
4534 {
4535  if ( pm->submodel[mn].buffer.model_list != NULL ) {
4536  submodel_vector.push_back(mn);
4537  }
4538 
4539  int i = pm->submodel[mn].first_child;
4540 
4541  while ( i >= 0 ) {
4542  model_get_submodel_tree_list(submodel_vector, pm, i);
4543 
4544  i = pm->submodel[i].next_sibling;
4545  }
4546 }
4547 
4548 // Given a direction (pnt) that is in submodel_num's frame of
4549 // reference, and given the object's orient and position,
4550 // return the point in 3-space in outpnt.
4551 void model_find_world_dir(vec3d *out_dir, vec3d *in_dir, int model_num, int submodel_num, const matrix *objorient)
4552 {
4553  vec3d pnt;
4554  vec3d tpnt;
4555  matrix m;
4556  int mn;
4557  polymodel *pm = model_get(model_num);
4558 
4559  pnt = *in_dir;
4560  mn = submodel_num;
4561 
4562  //instance up the tree for this point
4563  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4564  // By using this kind of computation, the rotational angles can always
4565  // be computed relative to the submodel itself, instead of relative
4566  // to the parent - KeldorKatarn
4567  matrix rotation_matrix = pm->submodel[mn].orientation;
4568  vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);
4569 
4570  matrix inv_orientation;
4571  vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
4572 
4573  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
4574 
4575  vm_vec_unrotate(&tpnt, &pnt, &m);
4576  pnt = tpnt;
4577 
4578  mn = pm->submodel[mn].parent;
4579  }
4580 
4581  //now instance for the entire object
4582  vm_vec_unrotate(out_dir,&pnt,objorient);
4583 }
4584 
4585 // the same as above - just taking model instance data into account
4586 // model_find_world_dir
4588 {
4589  vec3d pnt;
4590  vec3d tpnt;
4591  matrix m;
4592  int mn;
4593  polymodel_instance *pmi = model_get_instance(model_instance_num);
4594  polymodel *pm = model_get(pmi->model_num);
4595 
4596  pnt = *in_dir;
4597  mn = submodel_num;
4598 
4599  //instance up the tree for this point
4600  while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
4601  // By using this kind of computation, the rotational angles can always
4602  // be computed relative to the submodel itself, instead of relative
4603  // to the parent - KeldorKatarn
4604  matrix rotation_matrix = pm->submodel[mn].orientation;
4605  vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[mn].angs);
4606 
4607  matrix inv_orientation;
4608  vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
4609 
4610  vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
4611 
4612  vm_vec_unrotate(&tpnt, &pnt, &m);
4613  pnt = tpnt;
4614 
4615  mn = pm->submodel[mn].parent;
4616  }
4617 
4618  //now instance for the entire object
4619  vm_vec_unrotate(out_dir,&pnt,objorient);
4620 }
4621 
4622 
4623 // Clears all the submodel instances stored in a model to their defaults.
4624 void model_clear_instance(int model_num)
4625 {
4626  polymodel * pm;
4627  int i;
4628 
4629  pm = model_get(model_num);
4630 
4631  pm->gun_submodel_rotation = 0.0f;
4632  // reset textures to original ones
4633  for (i=0; i<pm->n_textures; i++ ) {
4634  pm->maps[i].ResetToOriginal();
4635  }
4636 
4637  for (i=0; i<pm->n_models; i++ ) {
4638  bsp_info *sm = &pm->submodel[i];
4639 
4640  if ( pm->submodel[i].is_damaged ) {
4641  sm->blown_off = 1;
4642  } else {
4643  sm->blown_off = 0;
4644  }
4645  sm->angs.p = 0.0f;
4646  sm->angs.b = 0.0f;
4647  sm->angs.h = 0.0f;
4648 
4649  sm->num_arcs = 0; // Turn off any electric arcing effects
4650  }
4651 
4652  for (i=0; i<pm->num_lights; i++ ) {
4653  pm->lights[i].value = 0.0f;
4654  }
4655 
4657 
4658 // if ( keyd_pressed[KEY_1] ) pm->lights[0].value = 1.0f/255.0f;
4659 // if ( keyd_pressed[KEY_2] ) pm->lights[1].value = 1.0f/255.0f;
4660 // if ( keyd_pressed[KEY_3] ) pm->lights[2].value = 1.0f/255.0f;
4661 // if ( keyd_pressed[KEY_4] ) pm->lights[3].value = 1.0f/255.0f;
4662 // if ( keyd_pressed[KEY_5] ) pm->lights[4].value = 1.0f/255.0f;
4663 // if ( keyd_pressed[KEY_6] ) pm->lights[5].value = 1.0f/255.0f;
4664 
4665 
4666 }
4667 
4668 // initialization during ship set
4670 {
4671  sii->blown_off = 0;
4672  sii->angs.p = 0.0f;
4673  sii->angs.b = 0.0f;
4674  sii->angs.h = 0.0f;
4675  sii->prev_angs.p = 0.0f;
4676  sii->prev_angs.b = 0.0f;
4677  sii->prev_angs.h = 0.0f;
4678 
4679  sii->cur_turn_rate = 0.0f;
4680  sii->desired_turn_rate = 0.0f;
4681  sii->turn_accel = 0.0f;
4682 }
4683 
4685 {
4686  sm_instance->angs.p = 0.0f;
4687  sm_instance->angs.b = 0.0f;
4688  sm_instance->angs.h = 0.0f;
4689 
4690  sm_instance->blown_off = sm->is_damaged ? true : false;
4691 
4692  sm_instance->collision_checked = false;
4693  sm_instance->sii = NULL;
4694 }
4695 
4697 {
4698  int i;
4699  polymodel_instance *pmi = model_get_instance(model_instance_num);
4700  polymodel *pm = model_get(pmi->model_num);
4701 
4702  for ( i = 0; i < pm->n_models; i++ ) {
4703  model_clear_submodel_instance(&pmi->submodel[i], &pm->submodel[i]);
4704  }
4705 }
4706 
4707 // initialization during ship set
4708 void model_set_instance_info(submodel_instance_info *sii, float turn_rate, float turn_accel)
4709 {
4710  sii->blown_off = 0;
4711  sii->angs.p = 0.0f;
4712  sii->angs.b = 0.0f;
4713  sii->angs.h = 0.0f;
4714  sii->prev_angs.p = 0.0f;
4715  sii->prev_angs.b = 0.0f;
4716  sii->prev_angs.h = 0.0f;
4717 
4718  sii->cur_turn_rate = turn_rate * 0.0f;
4719  sii->desired_turn_rate = turn_rate;
4720  sii->turn_accel = turn_accel;
4721  sii->axis_set = 0;
4722  sii->step_zero_timestamp = timestamp();
4723 }
4724 
4725 // Sets the submodel instance data in a submodel (for all detail levels)
4726 void model_set_instance(int model_num, int sub_model_num, submodel_instance_info *sii, int flags)
4727 {
4728  int i;
4729  polymodel * pm;
4730 
4731  pm = model_get(model_num);
4732 
4733  Assert( sub_model_num >= 0 );
4734  Assert( sub_model_num < pm->n_models );
4735 
4736  if ( sub_model_num < 0 ) return;
4737  if ( sub_model_num >= pm->n_models ) return;
4738  bsp_info *sm = &pm->submodel[sub_model_num];
4739 
4740  if (flags & SSF_NO_DISAPPEAR) {
4741  sm->blown_off = 0;
4742  } else {
4743  // Set the "blown out" flags
4744  sm->blown_off = sii->blown_off;
4745  }
4746 
4747  if ( (sm->blown_off) && (!(flags & SSF_NO_REPLACE)) ) {
4748  if ( sm->my_replacement > -1 ) {
4749  pm->submodel[sm->my_replacement].blown_off = 0;
4750  pm->submodel[sm->my_replacement].angs = sii->angs;
4751  }
4752  } else {
4753  // If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
4754  // the replacement model from being drawn by marking it as having been blown off
4755  if ( sm->my_replacement > -1 && sm->my_replacement != sub_model_num) {
4756  pm->submodel[sm->my_replacement].blown_off = 1;
4757  }
4758  }
4759 
4760  // Set the angles
4761  sm->angs = sii->angs;
4762 
4763  // For all the detail levels of this submodel, set them also.
4764  for (i=0; i<sm->num_details; i++ ) {
4765  model_set_instance(model_num, sm->details[i], sii, flags );
4766  }
4767 }
4768 
4769 // Sets the submodel instance data in a submodel (for all detail levels)
4770 // Techroom version uses two floats of setting rotation angles (for turrets)
4771 // instead of using larger but largely unused structures for storing the same data
4772 void model_set_instance_techroom(int model_num, int sub_model_num, float angle_1, float angle_2)
4773 {
4774  polymodel * pm;
4775 
4776  pm = model_get(model_num);
4777 
4778  Assert( sub_model_num >= 0 );
4779  Assert( sub_model_num < pm->n_models );
4780 
4781  if ( sub_model_num < 0 ) return;
4782  if ( sub_model_num >= pm->n_models ) return;
4783  bsp_info *sm = &pm->submodel[sub_model_num];
4784 
4785  // If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
4786  // the replacement model from being drawn by marking it as having been blown off
4787  if ( sm->my_replacement > -1 && sm->my_replacement != sub_model_num) {
4788  pm->submodel[sm->my_replacement].blown_off = 1;
4789  }
4790 
4791  // Set the angles
4792  sm->angs.p = angle_1;
4793  sm->angs.h = angle_2;
4794 }
4795 
4797 {
4798  int i;
4799  polymodel *pm;
4800  polymodel_instance *pmi;
4801 
4802  pmi = model_get_instance(model_instance_num);
4803  pm = model_get(pmi->model_num);
4804 
4805  Assertion(sub_model_num >= 0 && sub_model_num < pm->n_models,
4806  "Sub model number (%d) which should be updated is out of range! Must be between 0 and %d. This happend on model %s.",
4807  sub_model_num, pm->n_models - 1, pm->filename);
4808 
4809  if ( sub_model_num < 0 ) return;
4810  if ( sub_model_num >= pm->n_models ) return;
4811 
4812  submodel_instance *smi = &pmi->submodel[sub_model_num];
4813  bsp_info *sm = &pm->submodel[sub_model_num];
4814 
4815  // Set the "blown out" flags
4816  if ( flags & SSF_NO_DISAPPEAR ) {
4817  smi->blown_off = false;
4818  } else {
4819  smi->blown_off = sii->blown_off ? true : false;
4820  }
4821 
4822  if ( smi->blown_off && !(flags & SSF_NO_REPLACE) ) {
4823  if ( sm->my_replacement > -1 ) {
4824  pmi->submodel[sm->my_replacement].blown_off = false;
4825  pmi->submodel[sm->my_replacement].angs = sii->angs;
4826  pmi->submodel[sm->my_replacement].prev_angs = sii->prev_angs;
4827  }
4828  } else {
4829  // If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
4830  // the replacement model from being drawn by marking it as having been blown off
4831  if ( sm->my_replacement > -1 && sm->my_replacement != sub_model_num) {
4832  pmi->submodel[sm->my_replacement].blown_off = true;
4833  }
4834  }
4835 
4836  // Set the angles
4837  smi->angs = sii->angs;
4838  smi->prev_angs = sii->prev_angs;
4839  smi->sii = sii;
4840 
4841  // For all the detail levels of this submodel, set them also.
4842  for (i=0; i<sm->num_details; i++ ) {
4843  model_update_instance(model_instance_num, sm->details[i], sii, flags );
4844  }
4845 }
4846 
4848 {
4850  Assert(pmi != nullptr);
4851 
4852  // Handle all submodels which have $dumb_rotate
4853  for (auto submodel_it = ir->list.begin(); submodel_it != ir->list.end(); ++submodel_it)
4854  {
4855  polymodel *pm = model_get(pmi->model_num);
4856  Assert(pm != nullptr);
4857  bsp_info *sm = &pm->submodel[submodel_it->submodel_num];
4858 
4859  // First, calculate the angles for the rotation
4860  submodel_rotate(sm, &submodel_it->submodel_info_1);
4861 
4862  // Now actually rotate the submodel instance
4863  // (Since this is an intrinsic rotation, we have no associated subsystem, so pass 0 for subsystem flags.)
4864  model_update_instance(ir->model_instance_num, submodel_it->submodel_num, &submodel_it->submodel_info_1, 0);
4865  }
4866 }
4867 
4868 // 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
4869 // 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,
4870 // they are both handled here.
4871 //
4872 // 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
4873 // 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
4874 // 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
4875 // or damage sparks that aren't attached to models.)
4876 //
4877 // -- Goober5000