mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 04:31:34 -10:00
This makes it easier to determine what takes the most time while loading the mission (loading models) and how we could optimize that load time (optimize model loading). This is not a bug fix but it also shouldn't cause any regressions since there are no actual code changes so this could be merged before the release of 3.8.
5907 lines
173 KiB
C++
5907 lines
173 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#define MODEL_LIB
|
|
|
|
#include "asteroid/asteroid.h"
|
|
#include "bmpman/bmpman.h"
|
|
#include "cfile/cfile.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "freespace.h" // For flFrameTime
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "io/key.h"
|
|
#include "io/timer.h"
|
|
#include "math/fvi.h"
|
|
#include "math/vecmat.h"
|
|
#include "model/model.h"
|
|
#include "model/modelsinc.h"
|
|
#include "parse/parselo.h"
|
|
#include "render/3dinternal.h"
|
|
#include "ship/ship.h"
|
|
#include "weapon/weapon.h"
|
|
#include "tracing/tracing.h"
|
|
|
|
#include <algorithm>
|
|
|
|
flag_def_list model_render_flags[] =
|
|
{
|
|
{"no lighting", MR_NO_LIGHTING, 0},
|
|
{"transparent", MR_ALL_XPARENT, 0},
|
|
{"no Zbuffer", MR_NO_ZBUFFER, 0},
|
|
{"no cull", MR_NO_CULL, 0},
|
|
{"no glowmaps", MR_NO_GLOWMAPS, 0},
|
|
{"force clamp", MR_FORCE_CLAMP, 0},
|
|
};
|
|
|
|
int model_render_flags_size = sizeof(model_render_flags)/sizeof(flag_def_list);
|
|
|
|
#define MAX_SUBMODEL_COLLISION_ROT_ANGLE (PI / 6.0f) // max 30 degrees per frame
|
|
|
|
// info for special polygon lists
|
|
|
|
polymodel *Polygon_models[MAX_POLYGON_MODELS];
|
|
SCP_vector<polymodel_instance*> Polygon_model_instances;
|
|
|
|
SCP_vector<bsp_collision_tree> Bsp_collision_tree_list;
|
|
|
|
static int model_initted = 0;
|
|
|
|
#ifndef NDEBUG
|
|
CFILE *ss_fp = NULL; // file pointer used to dump subsystem information
|
|
char model_filename[_MAX_PATH]; // temp used to store filename
|
|
char debug_name[_MAX_PATH];
|
|
int ss_warning_shown = 0; // have we shown the warning dialog concerning the subsystems?
|
|
#endif
|
|
|
|
static uint Global_checksum = 0;
|
|
|
|
// Anything less than this is considered incompatible.
|
|
#define PM_COMPATIBLE_VERSION 1900
|
|
|
|
// Anything greater than or equal to PM_COMPATIBLE_VERSION and
|
|
// whose major version is less than or equal to this is considered
|
|
// compatible.
|
|
#define PM_OBJFILE_MAJOR_VERSION 30
|
|
|
|
static int Model_signature = 0;
|
|
|
|
void interp_configure_vertex_buffers(polymodel*, int);
|
|
void interp_pack_vertex_buffers(polymodel* pm, int mn);
|
|
void interp_create_detail_index_buffer(polymodel *pm, int detail);
|
|
void interp_create_transparency_index_buffer(polymodel *pm, int detail_num);
|
|
void model_interp_process_shield_mesh(polymodel * pm);
|
|
|
|
void model_set_subsys_path_nums(polymodel *pm, int n_subsystems, model_subsystem *subsystems);
|
|
void model_set_bay_path_nums(polymodel *pm);
|
|
|
|
|
|
// Goober5000 - see SUBSYSTEM_X in model.h
|
|
// NOTE: Each subsystem must match up with its #define, or there will be problems
|
|
const char *Subsystem_types[SUBSYSTEM_MAX] =
|
|
{
|
|
"None",
|
|
"Engines",
|
|
"Turrets",
|
|
"Radar",
|
|
"Navigation",
|
|
"Communications",
|
|
"Weapons",
|
|
"Sensors",
|
|
"Solar panels",
|
|
"Gas collection",
|
|
"Activation",
|
|
"Unknown"
|
|
};
|
|
|
|
|
|
//WMC - For general compatibility stuff.
|
|
//Note that the order of the items in this list
|
|
//determine the order that they are tried in ai_goal_fixup_dockpoints
|
|
flag_def_list Dock_type_names[] =
|
|
{
|
|
{ "cargo", DOCK_TYPE_CARGO, 0 },
|
|
{ "rearm", DOCK_TYPE_REARM, 0 },
|
|
{ "generic", DOCK_TYPE_GENERIC, 0 }
|
|
};
|
|
|
|
int Num_dock_type_names = sizeof(Dock_type_names) / sizeof(flag_def_list);
|
|
|
|
SCP_vector<glow_point_bank_override> glowpoint_bank_overrides;
|
|
|
|
|
|
// Goober5000 - reimplementation of Bobboau's $dumb_rotation feature in a way that works with the rest of the model instance system
|
|
// note: since these data types are only ever used in this file, they don't need to be in model.h
|
|
|
|
class submodel_intrinsic_rotation
|
|
{
|
|
public:
|
|
int submodel_num;
|
|
submodel_instance_info submodel_info_1;
|
|
|
|
submodel_intrinsic_rotation(int _submodel_num, float _turn_rate)
|
|
: submodel_num(_submodel_num)
|
|
{
|
|
memset(&submodel_info_1, 0, sizeof(submodel_info_1));
|
|
submodel_info_1.cur_turn_rate = _turn_rate;
|
|
submodel_info_1.desired_turn_rate = _turn_rate;
|
|
}
|
|
};
|
|
|
|
class intrinsic_rotation
|
|
{
|
|
public:
|
|
bool is_ship;
|
|
int model_instance_num;
|
|
SCP_vector<submodel_intrinsic_rotation> list;
|
|
|
|
intrinsic_rotation(bool _is_ship, int _model_instance_num)
|
|
: is_ship(_is_ship), model_instance_num(_model_instance_num), list()
|
|
{}
|
|
};
|
|
|
|
SCP_vector<intrinsic_rotation> Intrinsic_rotations;
|
|
|
|
|
|
// Free up a model, getting rid of all its memory
|
|
// With the basic page in system this can be called from outside of modelread.cpp
|
|
void model_unload(int modelnum, int force)
|
|
{
|
|
int i, j, num;
|
|
|
|
if ( modelnum >= MAX_POLYGON_MODELS ) {
|
|
num = modelnum % MAX_POLYGON_MODELS;
|
|
} else {
|
|
num = modelnum;
|
|
}
|
|
|
|
if ( (num < 0) || (num >= MAX_POLYGON_MODELS)) {
|
|
return;
|
|
}
|
|
|
|
polymodel *pm = Polygon_models[num];
|
|
|
|
if ( !pm ) {
|
|
return;
|
|
}
|
|
|
|
Assert( pm->used_this_mission >= 0 );
|
|
|
|
if (!force && (--pm->used_this_mission > 0))
|
|
return;
|
|
|
|
mprintf(("Unloading model '%s' from slot '%i'\n", pm->filename, num));
|
|
|
|
// so that the textures can be released
|
|
pm->used_this_mission = 0;
|
|
|
|
// we want to call bm_release() from here rather than just bm_unload() in order
|
|
// to get the slots back so we set "release" to true.
|
|
model_page_out_textures(pm->id, true);
|
|
|
|
safe_kill(pm->ship_bay);
|
|
|
|
if (pm->paths) {
|
|
for (i=0; i<pm->n_paths; i++ ) {
|
|
for (j=0; j<pm->paths[i].nverts; j++ ) {
|
|
if ( pm->paths[i].verts[j].turret_ids ) {
|
|
vm_free(pm->paths[i].verts[j].turret_ids);
|
|
}
|
|
}
|
|
if (pm->paths[i].verts) {
|
|
vm_free(pm->paths[i].verts);
|
|
}
|
|
}
|
|
vm_free(pm->paths);
|
|
}
|
|
|
|
if ( pm->shield.verts ) {
|
|
vm_free( pm->shield.verts );
|
|
}
|
|
|
|
if ( pm->shield.tris ) {
|
|
vm_free(pm->shield.tris);
|
|
}
|
|
|
|
if ( pm->missile_banks ) {
|
|
vm_free(pm->missile_banks);
|
|
}
|
|
|
|
if ( pm->docking_bays ) {
|
|
for (i=0; i<pm->n_docks; i++ ) {
|
|
if ( pm->docking_bays[i].splines ) {
|
|
vm_free( pm->docking_bays[i].splines );
|
|
}
|
|
}
|
|
vm_free(pm->docking_bays);
|
|
}
|
|
|
|
|
|
if ( pm->thrusters ) {
|
|
for (i = 0; i < pm->n_thrusters; i++) {
|
|
if (pm->thrusters[i].points)
|
|
vm_free(pm->thrusters[i].points);
|
|
}
|
|
|
|
vm_free(pm->thrusters);
|
|
}
|
|
|
|
if ( pm->glow_point_banks ) { // free the glows!!! -Bobboau
|
|
for (i = 0; i < pm->n_glow_point_banks; i++) {
|
|
if (pm->glow_point_banks[i].points)
|
|
vm_free(pm->glow_point_banks[i].points);
|
|
}
|
|
|
|
vm_free(pm->glow_point_banks);
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
if ( pm->debug_info ) {
|
|
vm_free(pm->debug_info);
|
|
}
|
|
#endif
|
|
|
|
model_octant_free( pm );
|
|
|
|
if (pm->submodel) {
|
|
for (i = 0; i < pm->n_models; i++) {
|
|
pm->submodel[i].buffer.clear();
|
|
|
|
if ( pm->submodel[i].bsp_data ) {
|
|
vm_free(pm->submodel[i].bsp_data);
|
|
}
|
|
|
|
if ( pm->submodel[i].collision_tree_index >= 0 ) {
|
|
model_remove_bsp_collision_tree(pm->submodel[i].collision_tree_index);
|
|
}
|
|
|
|
if ( pm->submodel[i].outline_buffer != nullptr ) {
|
|
vm_free(pm->submodel[i].outline_buffer);
|
|
pm->submodel[i].outline_buffer = nullptr;
|
|
}
|
|
}
|
|
|
|
delete[] pm->submodel;
|
|
}
|
|
|
|
if ( pm->xc ) {
|
|
vm_free(pm->xc);
|
|
}
|
|
|
|
if ( pm->lights ) {
|
|
vm_free(pm->lights);
|
|
}
|
|
|
|
if ( pm->gun_banks ) {
|
|
vm_free(pm->gun_banks);
|
|
}
|
|
|
|
if ( pm->shield_collision_tree ) {
|
|
vm_free(pm->shield_collision_tree);
|
|
}
|
|
|
|
if ( pm->shield.buffer_id > -1 ) {
|
|
gr_delete_buffer(pm->shield.buffer_id);
|
|
pm->shield.buffer_id = -1;
|
|
pm->shield.buffer_n_verts = 0;
|
|
}
|
|
|
|
if ( pm->vert_source.Vbuffer_handle > -1 ) {
|
|
gr_delete_buffer(pm->vert_source.Vbuffer_handle);
|
|
pm->vert_source.Vbuffer_handle = -1;
|
|
}
|
|
|
|
if ( pm->vert_source.Vertex_list != NULL ) {
|
|
vm_free(pm->vert_source.Vertex_list);
|
|
pm->vert_source.Vertex_list = NULL;
|
|
}
|
|
|
|
if ( pm->vert_source.Ibuffer_handle > -1 ) {
|
|
gr_delete_buffer(pm->vert_source.Ibuffer_handle);
|
|
pm->vert_source.Ibuffer_handle = -1;
|
|
}
|
|
|
|
if ( pm->vert_source.Index_list != NULL ) {
|
|
vm_free(pm->vert_source.Index_list);
|
|
pm->vert_source.Index_list = NULL;
|
|
}
|
|
|
|
pm->vert_source.Vertex_list_size = 0;
|
|
pm->vert_source.Index_list_size = 0;
|
|
|
|
for (i = 0; i < MAX_MODEL_DETAIL_LEVELS; ++i) {
|
|
pm->detail_buffers[i].clear();
|
|
}
|
|
|
|
// run through Ship_info and if the model has been loaded we'll need to reset the modelnum to -1.
|
|
for (auto it = Ship_info.begin(); it != Ship_info.end(); ++it) {
|
|
if ( pm->id == it->model_num ) {
|
|
it->model_num = -1;
|
|
}
|
|
|
|
if ( pm->id == it->cockpit_model_num ) {
|
|
it->cockpit_model_num = -1;
|
|
}
|
|
|
|
if ( pm->id == it->model_num_hud ) {
|
|
it->model_num_hud = -1;
|
|
}
|
|
}
|
|
|
|
// need to reset weapon models as well
|
|
for (int k = 0; k < MAX_WEAPON_TYPES; ++k) {
|
|
if ( pm->id == Weapon_info[k].model_num ) {
|
|
Weapon_info[k].model_num = -1;
|
|
}
|
|
if ( pm->id == Weapon_info[k].external_model_num ) {
|
|
Weapon_info[k].external_model_num = -1;
|
|
}
|
|
}
|
|
|
|
pm->id = 0;
|
|
delete pm;
|
|
|
|
Polygon_models[num] = NULL;
|
|
}
|
|
|
|
void model_free_all()
|
|
{
|
|
int i;
|
|
|
|
if ( !model_initted) {
|
|
model_init();
|
|
return;
|
|
}
|
|
|
|
mprintf(( "Freeing all existing models...\n" ));
|
|
model_instance_free_all();
|
|
|
|
for (i=0;i<MAX_POLYGON_MODELS;i++) {
|
|
// forcefully unload all loaded models (be careful with this)
|
|
model_unload(i, 1);
|
|
}
|
|
}
|
|
|
|
void model_instance_free_all()
|
|
{
|
|
size_t i;
|
|
|
|
// free any outstanding model instances
|
|
for ( i = 0; i < Polygon_model_instances.size(); ++i ) {
|
|
if ( Polygon_model_instances[i] ) {
|
|
model_delete_instance((int)i);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
extern int Nmodel_instance_num;
|
|
Nmodel_instance_num = -1;
|
|
|
|
Polygon_model_instances.clear();
|
|
}
|
|
|
|
void model_page_in_start()
|
|
{
|
|
int i;
|
|
|
|
if ( !model_initted ) {
|
|
model_init();
|
|
return;
|
|
}
|
|
|
|
mprintf(( "Starting model page in...\n" ));
|
|
|
|
for (i=0; i<MAX_POLYGON_MODELS; i++) {
|
|
if (Polygon_models[i] != NULL)
|
|
Polygon_models[i]->used_this_mission = 0;
|
|
}
|
|
}
|
|
|
|
void model_page_in_stop()
|
|
{
|
|
int i;
|
|
|
|
Assert( model_initted );
|
|
|
|
mprintf(( "Stopping model page in...\n" ));
|
|
|
|
for (i=0; i<MAX_POLYGON_MODELS; i++) {
|
|
if (Polygon_models[i] == NULL)
|
|
continue;
|
|
|
|
if (Polygon_models[i]->used_this_mission)
|
|
continue;
|
|
|
|
model_unload(i);
|
|
}
|
|
}
|
|
|
|
void model_init()
|
|
{
|
|
int i;
|
|
|
|
if ( model_initted ) {
|
|
Int3(); // Model_init shouldn't be called twice!
|
|
return;
|
|
}
|
|
|
|
for (i=0;i<MAX_POLYGON_MODELS;i++) {
|
|
Polygon_models[i] = NULL;
|
|
}
|
|
|
|
model_initted = 1;
|
|
}
|
|
|
|
// routine to parse out values from a user property field of an object
|
|
void get_user_prop_value(char *buf, char *value)
|
|
{
|
|
char *p, *p1, c;
|
|
|
|
p = buf;
|
|
while ( isspace(*p) || (*p == '=') ) // skip white space and equal sign
|
|
p++;
|
|
p1 = p;
|
|
while ( !iscntrl(*p1) )
|
|
p1++;
|
|
c = *p1;
|
|
*p1 = '\0';
|
|
strcpy(value, p);
|
|
*p1 = c;
|
|
}
|
|
|
|
// routine to look for one of the specified user properties
|
|
// 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)
|
|
// returns the index of the property found, or -1 if not found
|
|
// NB: the first recognized option is returned, so if one option is a substring of another, put it later in the list!
|
|
int prop_string(char *props, char **p, int n_args, ...)
|
|
{
|
|
char *pos = nullptr;
|
|
va_list args;
|
|
int index = -1;
|
|
|
|
va_start(args, n_args);
|
|
|
|
for (int i = 0; i < n_args; ++i)
|
|
{
|
|
const char *option = va_arg(args, const char *);
|
|
|
|
// look for our option in the props fields
|
|
if ((pos = strstr(props, option)) != nullptr)
|
|
{
|
|
// we found it
|
|
index = i;
|
|
|
|
// so advance past the string and its following character
|
|
pos += strlen(option);
|
|
pos++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
// if we have a p, assign *p
|
|
// (if nothing was found, *p will be nullptr)
|
|
if (p != nullptr)
|
|
*p = pos;
|
|
|
|
return index;
|
|
}
|
|
|
|
// syntactic sugar
|
|
int prop_string(char *props, char **p, const char *option0)
|
|
{
|
|
return prop_string(props, p, 1, option0);
|
|
}
|
|
int prop_string(char *props, char **p, const char *option0, const char *option1)
|
|
{
|
|
return prop_string(props, p, 2, option0, option1);
|
|
}
|
|
int prop_string(char *props, char **p, const char *option0, const char *option1, const char *option2)
|
|
{
|
|
return prop_string(props, p, 3, option0, option1, option2);
|
|
}
|
|
|
|
const Model::Subsystem_Flags carry_flags[] = { Model::Subsystem_Flags::Crewpoint, Model::Subsystem_Flags::Rotates, Model::Subsystem_Flags::Triggered, Model::Subsystem_Flags::Artillery, Model::Subsystem_Flags::Stepped_rotate };
|
|
// funciton to copy model data from one subsystem set to another subsystem set. This function
|
|
// is called when two ships use the same model data, but since the model only gets read in one time,
|
|
// the subsystem data is only present in one location. The ship code will call this routine to fix
|
|
// this situation by copying stuff from the source subsystem set to the dest subsystem set.
|
|
void model_copy_subsystems( int n_subsystems, model_subsystem *d_sp, model_subsystem *s_sp )
|
|
{
|
|
int i, j;
|
|
model_subsystem *source, *dest;
|
|
|
|
for (i = 0; i < n_subsystems; i++ ) {
|
|
source = &s_sp[i];
|
|
for ( j = 0; j < n_subsystems; j++ ) {
|
|
dest = &d_sp[j];
|
|
if ( !subsystem_stricmp( source->subobj_name, dest->subobj_name) ) {
|
|
for (auto const &flag : carry_flags) {
|
|
if (source->flags[flag])
|
|
dest->flags.set(flag);
|
|
}
|
|
|
|
dest->subobj_num = source->subobj_num;
|
|
dest->model_num = source->model_num;
|
|
dest->pnt = source->pnt;
|
|
dest->radius = source->radius;
|
|
dest->type = source->type;
|
|
dest->turn_rate = source->turn_rate;
|
|
dest->turret_gun_sobj = source->turret_gun_sobj;
|
|
|
|
strcpy_s(dest->name, source->name);
|
|
|
|
if ( dest->type == SUBSYSTEM_TURRET ) {
|
|
int nfp;
|
|
|
|
dest->turret_fov = source->turret_fov;
|
|
dest->turret_num_firing_points = source->turret_num_firing_points;
|
|
dest->turret_norm = source->turret_norm;
|
|
dest->turret_matrix = source->turret_matrix;
|
|
|
|
for (nfp = 0; nfp < dest->turret_num_firing_points; nfp++ )
|
|
dest->turret_firing_point[nfp] = source->turret_firing_point[nfp];
|
|
|
|
if ( dest->flags[Model::Subsystem_Flags::Crewpoint] )
|
|
strcpy_s(dest->crewspot, source->crewspot);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if ( j == n_subsystems )
|
|
Int3(); // get allender -- something is amiss with models
|
|
|
|
}
|
|
}
|
|
|
|
// routine to get/set subsystem information
|
|
static void set_subsystem_info(int model_num, model_subsystem *subsystemp, char *props, char *dname)
|
|
{
|
|
char *p;
|
|
char buf[64];
|
|
char lcdname[256];
|
|
int idx;
|
|
|
|
if ( (p = strstr(props, "$name")) != NULL)
|
|
get_user_prop_value(p+5, subsystemp->name);
|
|
else
|
|
strcpy_s(subsystemp->name, dname);
|
|
|
|
strcpy_s(lcdname, dname);
|
|
strlwr(lcdname);
|
|
|
|
// check the name for its specific type
|
|
if ( strstr(lcdname, "engine") ) {
|
|
subsystemp->type = SUBSYSTEM_ENGINE;
|
|
} else if ( strstr(lcdname, "radar") ) {
|
|
subsystemp->type = SUBSYSTEM_RADAR;
|
|
} else if ( strstr(lcdname, "turret") ) {
|
|
float angle;
|
|
|
|
subsystemp->type = SUBSYSTEM_TURRET;
|
|
if ( (p = strstr(props, "$fov")) != NULL )
|
|
get_user_prop_value(p+4, buf); // get the value of the fov
|
|
else
|
|
strcpy_s(buf,"180");
|
|
angle = fl_radians(atoi(buf))/2.0f;
|
|
subsystemp->turret_fov = cosf(angle);
|
|
subsystemp->turret_num_firing_points = 0;
|
|
|
|
if ( (p = strstr(props, "$crewspot")) != NULL) {
|
|
subsystemp->flags.set(Model::Subsystem_Flags::Crewpoint);
|
|
get_user_prop_value(p+9, subsystemp->crewspot);
|
|
}
|
|
|
|
} else if ( strstr(lcdname, "navigation") ) {
|
|
subsystemp->type = SUBSYSTEM_NAVIGATION;
|
|
} else if ( strstr(lcdname, "communication") ) {
|
|
subsystemp->type = SUBSYSTEM_COMMUNICATION;
|
|
} else if ( strstr(lcdname, "weapon") ) {
|
|
subsystemp->type = SUBSYSTEM_WEAPONS;
|
|
} else if ( strstr(lcdname, "sensor") ) {
|
|
subsystemp->type = SUBSYSTEM_SENSORS;
|
|
} else if ( strstr(lcdname, "solar") ) {
|
|
subsystemp->type = SUBSYSTEM_SOLAR;
|
|
} else if ( strstr(lcdname, "gas") ) {
|
|
subsystemp->type = SUBSYSTEM_GAS_COLLECT;
|
|
} else if ( strstr(lcdname, "activator") ) {
|
|
subsystemp->type = SUBSYSTEM_ACTIVATION;
|
|
} else { // If unrecognized type, set to unknown so artist can continue working...
|
|
subsystemp->type = SUBSYSTEM_UNKNOWN;
|
|
mprintf(("Subsystem '%s' on ship %s is not recognized as a common subsystem type\n", dname, model_get(model_num)->filename));
|
|
}
|
|
|
|
if ( (strstr(props, "$triggered")) != NULL ) {
|
|
subsystemp->flags.set(Model::Subsystem_Flags::Rotates);
|
|
subsystemp->flags.set(Model::Subsystem_Flags::Triggered);
|
|
}
|
|
|
|
// Dumb-Rotating subsystem
|
|
if (prop_string(props, nullptr, "$dumb_rotate") >= 0) {
|
|
// no special subsystem handling needed here, but make sure we didn't specify both methods
|
|
if (prop_string(props, nullptr, "$rotate") >= 0) {
|
|
Warning(LOCATION, "Subsystem '%s' on ship %s cannot have both rotation and dumb-rotation!", dname, model_get(model_num)->filename);
|
|
}
|
|
}
|
|
// Rotating subsystem
|
|
else if ((idx = prop_string(props, &p, "$rotate_time", "$rotate_rate", "$rotate")) >= 0) {
|
|
subsystemp->flags.set(Model::Subsystem_Flags::Rotates);
|
|
|
|
// get value for (a) complete rotation (b) step (c) activation
|
|
get_user_prop_value(p, buf); // note: p points to the value since we used prop_string
|
|
|
|
// for retail compatibility, $rotate means $rotate_time
|
|
float turn_rate;
|
|
if (idx == 0 || idx == 2) {
|
|
float turn_time = static_cast<float>(atof(buf));
|
|
if (turn_time == 0.0f) {
|
|
Warning(LOCATION, "Rotation has a turn time of 0 for subsystem '%s' on ship %s!", dname, model_get(model_num)->filename);
|
|
turn_rate = 1.0f;
|
|
} else {
|
|
turn_rate = PI2 / turn_time;
|
|
}
|
|
} else {
|
|
turn_rate = static_cast<float>(atof(buf));
|
|
}
|
|
|
|
// CASE OF WEAPON ROTATION (primary only)
|
|
if ( (p = strstr(props, "$pbank")) != NULL) {
|
|
subsystemp->flags.set(Model::Subsystem_Flags::Artillery);
|
|
|
|
// get which pbank should trigger rotation
|
|
get_user_prop_value(p+6, buf);
|
|
subsystemp->weapon_rotation_pbank = atoi(buf);
|
|
} // end of weapon rotation stuff
|
|
|
|
|
|
// *** determine how the subsys rotates ***
|
|
|
|
// CASE OF STEPPED ROTATION
|
|
if ( (strstr(props, "$stepped")) != NULL) {
|
|
|
|
subsystemp->stepped_rotation = new stepped_rotation;
|
|
subsystemp->flags.set(Model::Subsystem_Flags::Stepped_rotate);
|
|
|
|
// get number of steps
|
|
if ( (p = strstr(props, "$steps")) != NULL) {
|
|
get_user_prop_value(p+6, buf);
|
|
subsystemp->stepped_rotation->num_steps = atoi(buf);
|
|
} else {
|
|
subsystemp->stepped_rotation->num_steps = 8;
|
|
}
|
|
|
|
// get pause time
|
|
if ( (p = strstr(props, "$t_paused")) != NULL) {
|
|
get_user_prop_value(p+9, buf);
|
|
subsystemp->stepped_rotation->t_pause = (float)atof(buf);
|
|
} else {
|
|
subsystemp->stepped_rotation->t_pause = 2.0f;
|
|
}
|
|
|
|
// get transition time - time to go between steps
|
|
if ( (p = strstr(props, "$t_transit")) != NULL) {
|
|
get_user_prop_value(p+10, buf);
|
|
subsystemp->stepped_rotation->t_transit = (float)atof(buf);
|
|
} else {
|
|
subsystemp->stepped_rotation->t_transit = 2.0f;
|
|
}
|
|
|
|
// get fraction of time spent in accel
|
|
if ( (p = strstr(props, "$fraction_accel")) != NULL) {
|
|
get_user_prop_value(p+15, buf);
|
|
subsystemp->stepped_rotation->fraction = (float)atof(buf);
|
|
Assert(subsystemp->stepped_rotation->fraction > 0 && subsystemp->stepped_rotation->fraction < 0.5);
|
|
} else {
|
|
subsystemp->stepped_rotation->fraction = 0.3f;
|
|
}
|
|
|
|
int num_steps = subsystemp->stepped_rotation->num_steps;
|
|
float t_trans = subsystemp->stepped_rotation->t_transit;
|
|
float fraction = subsystemp->stepped_rotation->fraction;
|
|
|
|
subsystemp->stepped_rotation->max_turn_accel = PI2 / (fraction*(1.0f - fraction) * num_steps * t_trans*t_trans);
|
|
subsystemp->stepped_rotation->max_turn_rate = PI2 / ((1.0f - fraction) * num_steps *t_trans);
|
|
}
|
|
|
|
// CASE OF NORMAL CONTINUOUS ROTATION
|
|
else {
|
|
subsystemp->turn_rate = turn_rate;
|
|
}
|
|
}
|
|
}
|
|
|
|
// used in collision code to check if submode rotates too far
|
|
float get_submodel_delta_angle(submodel_instance_info *sii)
|
|
{
|
|
vec3d diff;
|
|
vm_vec_sub(&diff, (vec3d*)&sii->angs, (vec3d*)&sii->prev_angs);
|
|
|
|
// find the angle
|
|
float delta_angle = vm_vec_mag(&diff);
|
|
|
|
// make sure we get the short way around
|
|
if (delta_angle > PI) {
|
|
delta_angle = (PI2 - delta_angle);
|
|
}
|
|
|
|
return delta_angle;
|
|
}
|
|
|
|
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 )
|
|
{
|
|
int i;
|
|
model_subsystem *subsystemp;
|
|
|
|
if ( slist==NULL ) {
|
|
#ifndef NDEBUG
|
|
if (!ss_warning_shown) {
|
|
mprintf(("No subsystems found for model \"%s\".\n", model_get(model_num)->filename));
|
|
ss_warning_shown = 1;
|
|
}
|
|
#endif
|
|
return; // For TestCode, POFView, etc don't bother
|
|
}
|
|
|
|
// try to find the name of the subsystem passed here on the list of subsystems currently on the
|
|
// ship. Assign the values only when the right subsystem is found
|
|
|
|
for (i = 0; i < n_subsystems; i++ ) {
|
|
subsystemp = &slist[i];
|
|
|
|
#ifndef NDEBUG
|
|
// Goober5000 - notify if there's a mismatch
|
|
if ( stricmp(subobj_name, subsystemp->subobj_name) && !subsystem_stricmp(subobj_name, subsystemp->subobj_name) )
|
|
{
|
|
nprintf(("Model", "NOTE: Subsystem \"%s\" in model \"%s\" is represented as \"%s\" in ships.tbl. This works fine in FSO v3.6 and up, "
|
|
"but is not compatible with FS2 retail.\n", subobj_name, model_get(model_num)->filename, subsystemp->subobj_name));
|
|
|
|
}
|
|
#endif
|
|
|
|
if (!subsystem_stricmp(subobj_name, subsystemp->subobj_name))
|
|
{
|
|
//commented by Goober5000 because this is also set when the table is parsed
|
|
//subsystemp->flags = 0;
|
|
|
|
subsystemp->subobj_num = subobj_num;
|
|
subsystemp->turret_gun_sobj = -1;
|
|
subsystemp->model_num = model_num;
|
|
subsystemp->pnt = *pnt; // use the offset to get the center point of the subsystem
|
|
subsystemp->radius = rad;
|
|
set_subsystem_info(model_num, subsystemp, props, subobj_name);
|
|
strcpy_s(subsystemp->subobj_name, subobj_name); // copy the object name
|
|
return;
|
|
}
|
|
}
|
|
#ifndef NDEBUG
|
|
char bname[_MAX_FNAME];
|
|
|
|
if ( !ss_warning_shown) {
|
|
_splitpath(model_filename, NULL, NULL, bname, NULL);
|
|
// Lets still give a comment about it and not just erase it
|
|
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 );
|
|
mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename));
|
|
ss_warning_shown = 1;
|
|
} else
|
|
#endif
|
|
mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename));
|
|
|
|
#ifndef NDEBUG
|
|
if ( ss_fp ) {
|
|
_splitpath(model_filename, NULL, NULL, bname, NULL);
|
|
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));
|
|
char tmp_buffer[128];
|
|
sprintf(tmp_buffer, "$Subsystem:\t\t\t%s,1,0.0\n", subobj_name);
|
|
cfputs(tmp_buffer, ss_fp);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void print_family_tree( polymodel *obj, int modelnum, const char * ident, int islast )
|
|
{
|
|
char temp[50];
|
|
|
|
if ( modelnum < 0 ) return;
|
|
if (obj==NULL) return;
|
|
|
|
if (ident[0] == '\0') {
|
|
mprintf(( " %s", obj->submodel[modelnum].name ));
|
|
sprintf( temp, " " );
|
|
} else if ( islast ) {
|
|
mprintf(( "%s:%s", ident, obj->submodel[modelnum].name ));
|
|
sprintf( temp, "%s ", ident );
|
|
} else {
|
|
mprintf(( "%s:%s", ident, obj->submodel[modelnum].name ));
|
|
sprintf( temp, "%s ", ident );
|
|
}
|
|
|
|
mprintf(( "\n" ));
|
|
|
|
int child = obj->submodel[modelnum].first_child;
|
|
while( child > -1 ) {
|
|
if ( obj->submodel[child].next_sibling < 0 )
|
|
print_family_tree( obj, child, temp,1 );
|
|
else
|
|
print_family_tree( obj, child, temp,0 );
|
|
child = obj->submodel[child].next_sibling;
|
|
}
|
|
}
|
|
|
|
void dump_object_tree(polymodel *obj)
|
|
{
|
|
print_family_tree( obj, 0, "", 0 );
|
|
key_getch();
|
|
}
|
|
|
|
void create_family_tree(polymodel *obj)
|
|
{
|
|
int i;
|
|
for (i=0; i<obj->n_models; i++ ) {
|
|
obj->submodel[i].num_children = 0;
|
|
obj->submodel[i].first_child = -1;
|
|
obj->submodel[i].next_sibling = -1;
|
|
}
|
|
|
|
for (i=0; i<obj->n_models; i++ ) {
|
|
int pn;
|
|
pn = obj->submodel[i].parent;
|
|
if ( pn > -1 ) {
|
|
obj->submodel[pn].num_children++;
|
|
int tmp = obj->submodel[pn].first_child;
|
|
obj->submodel[pn].first_child = i;
|
|
obj->submodel[i].next_sibling = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
void create_vertex_buffer(polymodel *pm)
|
|
{
|
|
if (Is_standalone) {
|
|
return;
|
|
}
|
|
|
|
TRACE_SCOPE(tracing::ModelCreateVertexBuffers);
|
|
|
|
int i;
|
|
|
|
// determine the size and configuration of each buffer segment
|
|
for (i = 0; i < pm->n_models; i++) {
|
|
interp_configure_vertex_buffers(pm, i);
|
|
}
|
|
|
|
// figure out which vertices are transparent
|
|
for ( i = 0; i < pm->n_models; i++ ) {
|
|
if ( !pm->submodel[i].is_thruster ) {
|
|
interp_create_transparency_index_buffer(pm, i);
|
|
}
|
|
}
|
|
|
|
bool use_batched_rendering = true;
|
|
|
|
if ( !Cmdline_no_batching ) {
|
|
size_t stride = 0;
|
|
|
|
// figure out if the vertex stride of this entire model matches. if not, turn off batched rendering for this model
|
|
for ( i = 0; i < pm->n_models; ++i ) {
|
|
if ( pm->submodel[i].buffer.model_list != NULL && pm->submodel[i].buffer.stride != stride) {
|
|
if ( stride == 0 ) {
|
|
stride = pm->submodel[i].buffer.stride;
|
|
} else {
|
|
use_batched_rendering = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
use_batched_rendering = false;
|
|
}
|
|
|
|
// create another set of indexes for the detail buffers
|
|
if ( use_batched_rendering ) {
|
|
for ( i = 0; i < pm->n_detail_levels; i++ ) {
|
|
interp_create_detail_index_buffer(pm, i);
|
|
}
|
|
}
|
|
|
|
// now actually fill the buffer with our info ...
|
|
for (i = 0; i < pm->n_models; i++) {
|
|
interp_pack_vertex_buffers(pm, i);
|
|
|
|
// release temporary memory
|
|
pm->submodel[i].buffer.release();
|
|
pm->submodel[i].trans_buffer.release();
|
|
}
|
|
|
|
if ( use_batched_rendering ) {
|
|
// pack the merged index buffers to the vbo.
|
|
for ( i = 0; i < pm->n_detail_levels; ++i ) {
|
|
if ( pm->detail_buffers[i].model_list == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
model_interp_pack_buffer(&pm->vert_source, &pm->detail_buffers[i]);
|
|
pm->detail_buffers[i].release();
|
|
}
|
|
|
|
pm->flags |= PM_FLAG_BATCHED;
|
|
}
|
|
|
|
// ... and then finalize buffer
|
|
model_interp_pack_buffer(&pm->vert_source, NULL);
|
|
|
|
model_interp_process_shield_mesh(pm);
|
|
}
|
|
|
|
// Goober5000
|
|
bool maybe_swap_mins_maxs(vec3d *mins, vec3d *maxs)
|
|
{
|
|
float temp;
|
|
bool swap_was_necessary = false;
|
|
|
|
if (mins->xyz.x > maxs->xyz.x)
|
|
{
|
|
temp = mins->xyz.x;
|
|
mins->xyz.x = maxs->xyz.x;
|
|
maxs->xyz.x = temp;
|
|
swap_was_necessary = true;
|
|
}
|
|
if (mins->xyz.y > maxs->xyz.y)
|
|
{
|
|
temp = mins->xyz.y;
|
|
mins->xyz.y = maxs->xyz.y;
|
|
maxs->xyz.y = temp;
|
|
swap_was_necessary = true;
|
|
}
|
|
if (mins->xyz.z > maxs->xyz.z)
|
|
{
|
|
temp = mins->xyz.z;
|
|
mins->xyz.z = maxs->xyz.z;
|
|
maxs->xyz.z = temp;
|
|
swap_was_necessary = true;
|
|
}
|
|
|
|
// This is a mini utility that prints out the proper hex string for the
|
|
// mins and maxs so that the POF file can be modified in a hex editor.
|
|
// Currently none of the major POF editors allow editing of bounding boxes.
|
|
#if 0
|
|
if (swap_was_necessary)
|
|
{
|
|
// use C hackery to convert float values to raw bytes
|
|
const int NUM_BYTES = 24;
|
|
typedef struct converter
|
|
{
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
float min_x, min_y, min_z, max_x, max_y, max_z;
|
|
} _float;
|
|
ubyte _byte[NUM_BYTES];
|
|
};
|
|
} converter;
|
|
|
|
// fill in the values
|
|
converter z;
|
|
z._float.min_x = mins->xyz.x;
|
|
z._float.min_y = mins->xyz.y;
|
|
z._float.min_z = mins->xyz.z;
|
|
z._float.max_x = maxs->xyz.x;
|
|
z._float.max_y = maxs->xyz.y;
|
|
z._float.max_z = maxs->xyz.z;
|
|
|
|
// prep string
|
|
char hex_str[5];
|
|
char text[100 + (5 * NUM_BYTES)];
|
|
strcpy_s(text, "The following is the correct hex string for the minima and maxima:\n");
|
|
|
|
// append hex values to the string
|
|
for (int i = 0; i < NUM_BYTES; i++)
|
|
{
|
|
sprintf(hex_str, "%02X ", z._byte[i]);
|
|
strcat_s(text, hex_str);
|
|
}
|
|
|
|
// notify the user
|
|
Warning(LOCATION, text);
|
|
}
|
|
#endif
|
|
|
|
return swap_was_necessary;
|
|
}
|
|
|
|
void model_calc_bound_box( vec3d *box, vec3d *big_mn, vec3d *big_mx)
|
|
{
|
|
box[0].xyz.x = big_mn->xyz.x; box[0].xyz.y = big_mn->xyz.y; box[0].xyz.z = big_mn->xyz.z;
|
|
box[1].xyz.x = big_mx->xyz.x; box[1].xyz.y = big_mn->xyz.y; box[1].xyz.z = big_mn->xyz.z;
|
|
box[2].xyz.x = big_mx->xyz.x; box[2].xyz.y = big_mx->xyz.y; box[2].xyz.z = big_mn->xyz.z;
|
|
box[3].xyz.x = big_mn->xyz.x; box[3].xyz.y = big_mx->xyz.y; box[3].xyz.z = big_mn->xyz.z;
|
|
|
|
|
|
box[4].xyz.x = big_mn->xyz.x; box[4].xyz.y = big_mn->xyz.y; box[4].xyz.z = big_mx->xyz.z;
|
|
box[5].xyz.x = big_mx->xyz.x; box[5].xyz.y = big_mn->xyz.y; box[5].xyz.z = big_mx->xyz.z;
|
|
box[6].xyz.x = big_mx->xyz.x; box[6].xyz.y = big_mx->xyz.y; box[6].xyz.z = big_mx->xyz.z;
|
|
box[7].xyz.x = big_mn->xyz.x; box[7].xyz.y = big_mx->xyz.y; box[7].xyz.z = big_mx->xyz.z;
|
|
}
|
|
|
|
|
|
void parse_triggers(int &n_trig, queued_animation **triggers, char *props);
|
|
|
|
|
|
//reads a binary file containing a 3d model
|
|
int read_model_file(polymodel * pm, const char *filename, int n_subsystems, model_subsystem *subsystems, int ferror)
|
|
{
|
|
CFILE *fp;
|
|
int version;
|
|
int id, len, next_chunk;
|
|
int i,j;
|
|
vec3d temp_vec;
|
|
|
|
fp = cfopen(filename,"rb");
|
|
|
|
if (!fp) {
|
|
if (ferror == 1) {
|
|
Error( LOCATION, "Can't open model file <%s>", filename );
|
|
} else if (ferror == 0) {
|
|
Warning( LOCATION, "Can't open model file <%s>", filename );
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
TRACE_SCOPE(tracing::ReadModelFile);
|
|
|
|
// generate checksum for the POF
|
|
cfseek(fp, 0, SEEK_SET);
|
|
cf_chksum_long(fp, &Global_checksum);
|
|
cfseek(fp, 0, SEEK_SET);
|
|
|
|
|
|
// code to get a filename to write out subsystem information for each model that
|
|
// is read. This info is essentially debug stuff that is used to help get models
|
|
// into the game quicker
|
|
#if 0
|
|
{
|
|
char bname[_MAX_FNAME];
|
|
|
|
_splitpath(filename, NULL, NULL, bname, NULL);
|
|
sprintf(debug_name, "%s.subsystems", bname);
|
|
ss_fp = cfopen(debug_name, "wb", CFILE_NORMAL, CF_TYPE_TABLES );
|
|
if ( !ss_fp ) {
|
|
mprintf(( "Can't open debug file for writing subsystems for %s\n", filename));
|
|
} else {
|
|
strcpy_s(model_filename, filename);
|
|
ss_warning_shown = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
id = cfread_int(fp);
|
|
|
|
if (id != POF_HEADER_ID)
|
|
Error( LOCATION, "Bad ID in model file <%s>",filename);
|
|
|
|
// Version is major*100+minor
|
|
// So, major = version / 100;
|
|
// minor = version % 100;
|
|
version = cfread_int(fp);
|
|
|
|
//Warning( LOCATION, "POF Version = %d", version );
|
|
|
|
if (version < PM_COMPATIBLE_VERSION || (version/100) > PM_OBJFILE_MAJOR_VERSION) {
|
|
Warning(LOCATION,"Bad version (%d) in model file <%s>",version,filename);
|
|
return 0;
|
|
}
|
|
|
|
pm->version = version;
|
|
Assert( strlen(filename) < FILESPEC_LENGTH );
|
|
strcpy_s(pm->filename, filename);
|
|
|
|
memset( &pm->view_positions, 0, sizeof(pm->view_positions) );
|
|
|
|
// reset insignia counts
|
|
pm->num_ins = 0;
|
|
|
|
// reset glow points!! - Goober5000
|
|
pm->n_glow_point_banks = 0;
|
|
|
|
// reset SLDC
|
|
pm->shield_collision_tree = NULL;
|
|
pm->sldc_size = 0;
|
|
|
|
id = cfread_int(fp);
|
|
len = cfread_int(fp);
|
|
next_chunk = cftell(fp) + len;
|
|
|
|
while (!cfeof(fp)) {
|
|
|
|
// mprintf(("Processing chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len));
|
|
// key_getch();
|
|
|
|
switch (id) {
|
|
|
|
case ID_OHDR: { //Object header
|
|
//vector v;
|
|
|
|
//mprintf(0,"Got chunk OHDR, len=%d\n",len);
|
|
|
|
#if defined( FREESPACE1_FORMAT )
|
|
pm->n_models = cfread_int(fp);
|
|
// mprintf(( "Num models = %d\n", pm->n_models ));
|
|
pm->rad = cfread_float(fp);
|
|
pm->flags = cfread_int(fp); // 1=Allow tiling
|
|
#elif defined( FREESPACE2_FORMAT )
|
|
pm->rad = cfread_float(fp);
|
|
pm->flags = cfread_int(fp); // 1=Allow tiling
|
|
pm->n_models = cfread_int(fp);
|
|
// mprintf(( "Num models = %d\n", pm->n_models ));
|
|
#endif
|
|
|
|
// Check for unrealistic radii
|
|
if ( pm->rad <= 0.1f )
|
|
{
|
|
Warning(LOCATION, "Model <%s> has a radius <= 0.1f\n", filename);
|
|
}
|
|
|
|
pm->submodel = new bsp_info[pm->n_models];
|
|
|
|
//Assert(pm->n_models <= MAX_SUBMODELS);
|
|
|
|
cfread_vector(&pm->mins,fp);
|
|
cfread_vector(&pm->maxs,fp);
|
|
|
|
// sanity first!
|
|
if (maybe_swap_mins_maxs(&pm->mins, &pm->maxs)) {
|
|
Warning(LOCATION, "Inverted bounding box on model '%s'! Swapping values to compensate.", pm->filename);
|
|
}
|
|
model_calc_bound_box(pm->bounding_box, &pm->mins, &pm->maxs);
|
|
|
|
pm->n_detail_levels = cfread_int(fp);
|
|
// mprintf(( "There are %d detail levels\n", pm->n_detail_levels ));
|
|
for (i=0; i<pm->n_detail_levels;i++ ) {
|
|
pm->detail[i] = cfread_int(fp);
|
|
pm->detail_depth[i] = 0.0f;
|
|
/// mprintf(( "Detail level %d is model %d.\n", i, pm->detail[i] ));
|
|
}
|
|
|
|
pm->num_debris_objects = cfread_int(fp);
|
|
Assert( pm->num_debris_objects <= MAX_DEBRIS_OBJECTS );
|
|
// mprintf(( "There are %d debris objects\n", pm->num_debris_objects ));
|
|
for (i=0; i<pm->num_debris_objects;i++ ) {
|
|
pm->debris_objects[i] = cfread_int(fp);
|
|
// mprintf(( "Debris object %d is model %d.\n", i, pm->debris_objects[i] ));
|
|
}
|
|
|
|
if ( pm->version >= 1903 ) {
|
|
|
|
if ( pm->version >= 2009 ) {
|
|
|
|
pm->mass = cfread_float(fp);
|
|
cfread_vector( &pm->center_of_mass, fp );
|
|
cfread_vector( &pm->moment_of_inertia.vec.rvec, fp );
|
|
cfread_vector( &pm->moment_of_inertia.vec.uvec, fp );
|
|
cfread_vector( &pm->moment_of_inertia.vec.fvec, fp );
|
|
|
|
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)) {
|
|
Warning(LOCATION, "Moment of inertia values for model %s are invalid. This has to be fixed.\n", pm->filename);
|
|
Int3();
|
|
}
|
|
} else {
|
|
// old code where mass wasn't based on area, so do the calculation manually
|
|
|
|
float vol_mass = cfread_float(fp);
|
|
// Attn: John Slagel: The following is better done in bspgen.
|
|
// Convert volume (cubic) to surface area (quadratic) and scale so 100 -> 100
|
|
float area_mass = (float) pow(vol_mass, 0.6667f) * 4.65f;
|
|
|
|
pm->mass = area_mass;
|
|
float mass_ratio = vol_mass / area_mass;
|
|
|
|
cfread_vector( &pm->center_of_mass, fp );
|
|
cfread_vector( &pm->moment_of_inertia.vec.rvec, fp );
|
|
cfread_vector( &pm->moment_of_inertia.vec.uvec, fp );
|
|
cfread_vector( &pm->moment_of_inertia.vec.fvec, fp );
|
|
|
|
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)) {
|
|
Warning(LOCATION, "Moment of inertia values for model %s are invalid. This has to be fixed.\n", pm->filename);
|
|
Int3();
|
|
}
|
|
|
|
// John remove this with change to bspgen
|
|
vm_vec_scale( &pm->moment_of_inertia.vec.rvec, mass_ratio );
|
|
vm_vec_scale( &pm->moment_of_inertia.vec.uvec, mass_ratio );
|
|
vm_vec_scale( &pm->moment_of_inertia.vec.fvec, mass_ratio );
|
|
}
|
|
|
|
// a custom MOI is only used for ships, but we should probably log it anyway
|
|
if ( IS_VEC_NULL(&pm->moment_of_inertia.vec.rvec)
|
|
&& IS_VEC_NULL(&pm->moment_of_inertia.vec.uvec)
|
|
&& IS_VEC_NULL(&pm->moment_of_inertia.vec.fvec) )
|
|
{
|
|
mprintf(("Model %s has a null moment of inertia! (This is only a problem if the model is a ship.)\n", filename));
|
|
}
|
|
|
|
} else {
|
|
pm->mass = 50.0f;
|
|
vm_vec_zero( &pm->center_of_mass );
|
|
vm_set_identity( &pm->moment_of_inertia );
|
|
vm_vec_scale(&pm->moment_of_inertia.vec.rvec, 0.001f);
|
|
vm_vec_scale(&pm->moment_of_inertia.vec.uvec, 0.001f);
|
|
vm_vec_scale(&pm->moment_of_inertia.vec.fvec, 0.001f);
|
|
}
|
|
|
|
// read in cross section info
|
|
pm->xc = NULL;
|
|
if ( pm->version >= 2014 ) {
|
|
pm->num_xc = cfread_int(fp);
|
|
if (pm->num_xc > 0) {
|
|
pm->xc = (cross_section*) vm_malloc(pm->num_xc*sizeof(cross_section));
|
|
for (i=0; i<pm->num_xc; i++) {
|
|
pm->xc[i].z = cfread_float(fp);
|
|
pm->xc[i].radius = cfread_float(fp);
|
|
}
|
|
}
|
|
} else {
|
|
pm->num_xc = 0;
|
|
}
|
|
|
|
if ( pm->version >= 2007 ) {
|
|
pm->num_lights = cfread_int(fp);
|
|
//mprintf(( "Found %d lights!\n", pm->num_lights ));
|
|
|
|
if (pm->num_lights > 0) {
|
|
pm->lights = (bsp_light *)vm_malloc( sizeof(bsp_light)*pm->num_lights );
|
|
for (i=0; i<pm->num_lights; i++ ) {
|
|
cfread_vector(&pm->lights[i].pos,fp);
|
|
pm->lights[i].type = cfread_int(fp);
|
|
pm->lights[i].value = 0.0f;
|
|
}
|
|
}
|
|
} else {
|
|
pm->num_lights = 0;
|
|
pm->lights = NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ID_SOBJ: { //Subobject header
|
|
int n;
|
|
char *p, props[MAX_PROP_LEN];
|
|
// float d;
|
|
|
|
//mprintf(0,"Got chunk SOBJ, len=%d\n",len);
|
|
|
|
n = cfread_int(fp);
|
|
//mprintf(("SOBJ IDed itself as %d", n));
|
|
|
|
Assert(n < pm->n_models );
|
|
|
|
#if defined( FREESPACE2_FORMAT )
|
|
pm->submodel[n].rad = cfread_float(fp); //radius
|
|
#endif
|
|
|
|
pm->submodel[n].parent = cfread_int(fp);
|
|
|
|
// cfread_vector(&pm->submodel[n].norm,fp);
|
|
// d = cfread_float(fp);
|
|
// cfread_vector(&pm->submodel[n].pnt,fp);
|
|
cfread_vector(&pm->submodel[n].offset,fp);
|
|
|
|
// 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 ));
|
|
|
|
#if defined ( FREESPACE1_FORMAT )
|
|
pm->submodel[n].rad = cfread_float(fp); //radius
|
|
#endif
|
|
|
|
// pm->submodel[n].tree_offset = cfread_int(fp); //offset
|
|
// pm->submodel[n].data_offset = cfread_int(fp); //offset
|
|
|
|
cfread_vector(&pm->submodel[n].geometric_center,fp);
|
|
|
|
cfread_vector(&pm->submodel[n].min,fp);
|
|
cfread_vector(&pm->submodel[n].max,fp);
|
|
|
|
pm->submodel[n].name[0] = '\0';
|
|
|
|
cfread_string_len(pm->submodel[n].name, MAX_NAME_LEN, fp); // get the name
|
|
cfread_string_len(props, MAX_PROP_LEN, fp); // and the user properties
|
|
|
|
// Check for unrealistic radii
|
|
if ( pm->submodel[n].rad <= 0.1f )
|
|
{
|
|
Warning(LOCATION, "Submodel <%s> in model <%s> has a radius <= 0.1f\n", pm->submodel[n].name, filename);
|
|
}
|
|
|
|
// sanity first!
|
|
if (maybe_swap_mins_maxs(&pm->submodel[n].min, &pm->submodel[n].max)) {
|
|
Warning(LOCATION, "Inverted bounding box on submodel '%s' of model '%s'! Swapping values to compensate.", pm->submodel[n].name, pm->filename);
|
|
}
|
|
model_calc_bound_box(pm->submodel[n].bounding_box, &pm->submodel[n].min, &pm->submodel[n].max);
|
|
|
|
pm->submodel[n].movement_type = cfread_int(fp);
|
|
pm->submodel[n].movement_axis = cfread_int(fp);
|
|
|
|
// change turret movement type to MOVEMENT_TYPE_ROT_SPECIAL
|
|
if ( strstr(pm->submodel[n].name, "turret") || strstr(pm->submodel[n].name, "gun") || strstr(pm->submodel[n].name, "cannon")) {
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_ROT_SPECIAL;
|
|
pm->submodel[n].can_move = true;
|
|
} else
|
|
|
|
if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) {
|
|
if (strstr(pm->submodel[n].name, "thruster")) {
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
|
|
pm->submodel[n].movement_axis = MOVEMENT_AXIS_NONE;
|
|
} else if(strstr(props, "$triggered")) {
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_TRIGGERED;
|
|
}
|
|
}
|
|
|
|
if ( ( p = strstr(props, "$look_at")) != NULL ) {
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_LOOK_AT;
|
|
get_user_prop_value(p+9, pm->submodel[n].look_at);
|
|
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
|
|
|
|
} else {
|
|
pm->submodel[n].look_at_num = -1; // No look_at
|
|
}
|
|
|
|
// note, this should come BEFORE do_new_subsystem() for proper error handling (to avoid both rotating and dumb-rotating submodel)
|
|
int idx = prop_string(props, &p, "$dumb_rotate_time", "$dumb_rotate_rate", "$dumb_rotate");
|
|
if (idx >= 0) {
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_INTRINSIC_ROTATE;
|
|
pm->flags |= PM_FLAG_HAS_INTRINSIC_ROTATE;
|
|
|
|
// do this the same way as regular $rotate
|
|
char buf[64];
|
|
get_user_prop_value(p, buf);
|
|
|
|
// for past SCP compatibility, $dumb_rotate means $dumb_rotate_rate
|
|
float turn_rate;
|
|
if (idx == 0) {
|
|
float turn_time = static_cast<float>(atof(buf));
|
|
if (turn_time == 0.0f) {
|
|
Warning(LOCATION, "Dumb-Rotation has a turn time of 0 for subsystem '%s' on ship %s!", pm->submodel[n].name, pm->filename);
|
|
turn_rate = 1.0f;
|
|
} else {
|
|
turn_rate = PI2 / turn_time;
|
|
}
|
|
} else {
|
|
turn_rate = static_cast<float>(atof(buf));
|
|
}
|
|
|
|
pm->submodel[n].dumb_turn_rate = turn_rate;
|
|
} else {
|
|
pm->submodel[n].dumb_turn_rate = 0.0f;
|
|
}
|
|
|
|
// Sets can_move on submodels which are of a rotating type or which have such a parent somewhere down the hierarchy
|
|
if ((pm->submodel[n].movement_type != MOVEMENT_TYPE_NONE)
|
|
|| strstr(props, "$triggered") || strstr(props, "$rotate") || strstr(props, "$gun_rotation")) {
|
|
pm->submodel[n].can_move = true;
|
|
} else if (pm->submodel[n].parent >= 0 && pm->submodel[pm->submodel[n].parent].can_move) {
|
|
pm->submodel[n].can_move = true;
|
|
}
|
|
|
|
if ( pm->submodel[n].name[0] == '\0' ) {
|
|
strcpy_s(pm->submodel[n].name, "unknown object name");
|
|
}
|
|
|
|
bool rotating_submodel_has_subsystem = !(pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT);
|
|
if ( ( p = strstr(props, "$special"))!= NULL ) {
|
|
char type[64];
|
|
|
|
get_user_prop_value(p+9, type);
|
|
if ( !stricmp(type, "subsystem") ) { // if we have a subsystem, put it into the list!
|
|
do_new_subsystem( n_subsystems, subsystems, n, pm->submodel[n].rad, &pm->submodel[n].offset, props, pm->submodel[n].name, pm->id );
|
|
rotating_submodel_has_subsystem = true;
|
|
} else if ( !stricmp(type, "no_rotate") ) {
|
|
// mark those submodels which should not rotate - ie, those with no subsystem
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
|
|
pm->submodel[n].movement_axis = MOVEMENT_AXIS_NONE;
|
|
} else {
|
|
// if submodel rotates (via bspgen), then there is either a subsys or special=no_rotate
|
|
Assert( pm->submodel[n].movement_type != MOVEMENT_TYPE_ROT );
|
|
}
|
|
}
|
|
|
|
// adding a warning if rotation is specified without movement axis.
|
|
if (pm->submodel[n].movement_axis == MOVEMENT_AXIS_NONE) {
|
|
if (pm->submodel[n].movement_type == MOVEMENT_TYPE_ROT) {
|
|
Warning(LOCATION, "Rotation without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename);
|
|
}
|
|
else if (pm->submodel[n].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
|
|
Warning(LOCATION, "Intrinsic rotation (e.g. dumb-rotate) without rotation axis defined on submodel '%s' of model '%s'!", pm->submodel[n].name, pm->filename);
|
|
}
|
|
}
|
|
|
|
/* if ( strstr(props, "$nontargetable")!= NULL ) {
|
|
pm->submodel[n].targetable = 0;
|
|
}else{
|
|
pm->submodel[n].targetable = 1;
|
|
}
|
|
*/
|
|
// pm->submodel[n].n_triggers = 0;
|
|
// pm->submodel[n].triggers = NULL;
|
|
|
|
//parse_triggers(pm->submodel[n].n_triggers, &pm->submodel[n].triggers, &props[0]);
|
|
|
|
if (strstr(props, "$no_collisions") != NULL )
|
|
pm->submodel[n].no_collisions = true;
|
|
else
|
|
pm->submodel[n].no_collisions = false;
|
|
|
|
if (strstr(props, "$nocollide_this_only") != NULL )
|
|
pm->submodel[n].nocollide_this_only = true;
|
|
else
|
|
pm->submodel[n].nocollide_this_only = false;
|
|
|
|
if (strstr(props, "$collide_invisible") != NULL )
|
|
pm->submodel[n].collide_invisible = true;
|
|
else
|
|
pm->submodel[n].collide_invisible = false;
|
|
|
|
if (strstr(props, "$gun_rotation") != NULL)
|
|
pm->submodel[n].gun_rotation = true;
|
|
else
|
|
pm->submodel[n].gun_rotation = false;
|
|
|
|
if ( (p = strstr(props, "$lod0_name")) != NULL)
|
|
get_user_prop_value(p+10, pm->submodel[n].lod_name);
|
|
|
|
if (strstr(props, "$attach_thrusters") != NULL )
|
|
pm->submodel[n].attach_thrusters = true;
|
|
else
|
|
pm->submodel[n].attach_thrusters = false;
|
|
|
|
if ( (p = strstr(props, "$detail_box:")) != NULL ) {
|
|
p += 12;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].use_render_box = atoi(p);
|
|
|
|
if ( (p = strstr(props, "$box_offset:")) != NULL ) {
|
|
p += 12;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].render_box_offset.xyz.x = (float)strtod(p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_box_offset.xyz.y = (float)strtod(++p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_box_offset.xyz.z = (float)strtod(++p, (char **)NULL);
|
|
|
|
pm->submodel[n].use_render_box_offset = true;
|
|
}
|
|
|
|
if ( (p = strstr(props, "$box_min:")) != NULL ) {
|
|
p += 9;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].render_box_min.xyz.x = (float)strtod(p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_box_min.xyz.y = (float)strtod(++p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_box_min.xyz.z = (float)strtod(++p, (char **)NULL);
|
|
} else {
|
|
pm->submodel[n].render_box_min = pm->submodel[n].min;
|
|
}
|
|
|
|
if ( (p = strstr(props, "$box_max:")) != NULL ) {
|
|
p += 9;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].render_box_max.xyz.x = (float)strtod(p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_box_max.xyz.y = (float)strtod(++p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_box_max.xyz.z = (float)strtod(++p, (char **)NULL);
|
|
} else {
|
|
pm->submodel[n].render_box_max = pm->submodel[n].max;
|
|
}
|
|
}
|
|
|
|
if ( (p = strstr(props, "$detail_sphere:")) != NULL ) {
|
|
p += 15;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].use_render_sphere = atoi(p);
|
|
|
|
if ( (p = strstr(props, "$radius:")) != NULL ) {
|
|
p += 8;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].render_sphere_radius = (float)strtod(p, (char **)NULL);
|
|
} else {
|
|
pm->submodel[n].render_sphere_radius = pm->submodel[n].rad;
|
|
}
|
|
|
|
if ( (p = strstr(props, "$offset:")) != NULL ) {
|
|
p += 8;
|
|
while (*p == ' ') p++;
|
|
pm->submodel[n].render_sphere_offset.xyz.x = (float)strtod(p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_sphere_offset.xyz.y = (float)strtod(++p, (char **)NULL);
|
|
while (*p != ',') p++;
|
|
pm->submodel[n].render_sphere_offset.xyz.z = (float)strtod(++p, (char **)NULL);
|
|
|
|
pm->submodel[n].use_render_sphere_offset = true;
|
|
} else {
|
|
pm->submodel[n].render_sphere_offset = vmd_zero_vector;
|
|
}
|
|
}
|
|
|
|
// Added for new handling of turret orientation - KeldorKatarn
|
|
matrix *orient = &pm->submodel[n].orientation;
|
|
|
|
if ( (p = strstr(props, "$uvec:")) != NULL ) {
|
|
p += 6;
|
|
|
|
char *parsed_string = p;
|
|
|
|
while (*parsed_string == ' ') {
|
|
parsed_string++; // Skip spaces
|
|
}
|
|
|
|
orient->vec.uvec.xyz.x = (float)(strtod(parsed_string, (char **)NULL));
|
|
|
|
// Find end of number
|
|
parsed_string = strchr(parsed_string, ',');
|
|
if (parsed_string == NULL) {
|
|
Error( LOCATION,
|
|
"Submodel '%s' of model '%s' has an improperly formatted $uvec: declaration in its properties."
|
|
"\n\n$uvec: should be followed by 3 numbers separated with commas."
|
|
"\n\nCouldn't find first comma (,)!",
|
|
pm->submodel[n].name, filename);
|
|
}
|
|
parsed_string++;
|
|
|
|
while (*parsed_string == ' ') {
|
|
parsed_string++; // Skip spaces
|
|
}
|
|
|
|
orient->vec.uvec.xyz.y = (float)(strtod(parsed_string, (char **)NULL));
|
|
|
|
// Find end of number
|
|
parsed_string = strchr(parsed_string, ',');
|
|
if (parsed_string == NULL) {
|
|
Error( LOCATION,
|
|
"Submodel '%s' of model '%s' has an improperly formatted $uvec: declaration in its properties."
|
|
"\n\n$uvec: should be followed by 3 numbers separated with commas."
|
|
"\n\nCouldn't find second comma (,)!",
|
|
pm->submodel[n].name, filename);
|
|
}
|
|
parsed_string++;
|
|
|
|
while (*parsed_string == ' ') {
|
|
parsed_string++; // Skip spaces
|
|
}
|
|
|
|
orient->vec.uvec.xyz.z = (float)(strtod(parsed_string, (char **)NULL));
|
|
|
|
if ( (p = strstr(props, "$fvec:")) != NULL ) {
|
|
parsed_string = p + 6;
|
|
|
|
while (*parsed_string == ' ') {
|
|
parsed_string++; // Skip spaces
|
|
}
|
|
|
|
orient->vec.fvec.xyz.x = (float)(strtod(parsed_string, (char **)NULL));
|
|
|
|
// Find end of number
|
|
parsed_string = strchr(parsed_string, ',');
|
|
if (parsed_string == NULL) {
|
|
Error( LOCATION,
|
|
"Submodel '%s' of model '%s' has an improperly formatted $fvec: declaration in its properties."
|
|
"\n\n$fvec: should be followed by 3 numbers separated with commas."
|
|
"\n\nCouldn't find first comma (,)!",
|
|
pm->submodel[n].name, filename);
|
|
}
|
|
parsed_string++;
|
|
|
|
while (*parsed_string == ' ') {
|
|
parsed_string++; // Skip spaces
|
|
}
|
|
|
|
orient->vec.fvec.xyz.y = (float)(strtod(parsed_string, (char **)NULL));
|
|
|
|
// Find end of number
|
|
parsed_string = strchr(parsed_string, ',');
|
|
if (parsed_string == NULL) {
|
|
Error( LOCATION,
|
|
"Submodel '%s' of model '%s' has an improperly formatted $fvec: declaration in its properties."
|
|
"\n\n$fvec: should be followed by 3 numbers separated with commas."
|
|
"\n\nCouldn't find second comma (,)!",
|
|
pm->submodel[n].name, filename);
|
|
}
|
|
parsed_string++;
|
|
|
|
while (*parsed_string == ' ') {
|
|
parsed_string++; // Skip spaces
|
|
}
|
|
|
|
orient->vec.fvec.xyz.z = (float)(strtod(parsed_string, (char **)NULL));
|
|
|
|
pm->submodel[n].force_turret_normal = true;
|
|
|
|
vm_vec_normalize(&orient->vec.uvec);
|
|
vm_vec_normalize(&orient->vec.fvec);
|
|
|
|
vm_vec_cross(&orient->vec.rvec, &orient->vec.uvec, &orient->vec.fvec);
|
|
vm_vec_cross(&orient->vec.fvec, &orient->vec.rvec, &orient->vec.uvec);
|
|
|
|
vm_vec_normalize(&orient->vec.fvec);
|
|
vm_vec_normalize(&orient->vec.rvec);
|
|
|
|
vm_orthogonalize_matrix(orient);
|
|
} else {
|
|
int parent_num = pm->submodel[n].parent;
|
|
|
|
if (parent_num > -1) {
|
|
*orient = pm->submodel[parent_num].orientation;
|
|
} else {
|
|
*orient = vmd_identity_matrix;
|
|
}
|
|
|
|
Warning( LOCATION, "Improper custom orientation matrix for subsystem %s, you must define a up vector, then a forward vector", pm->submodel[n].name);
|
|
}
|
|
} else {
|
|
int parent_num = pm->submodel[n].parent;
|
|
|
|
if (parent_num > -1) {
|
|
*orient = pm->submodel[parent_num].orientation;
|
|
} else {
|
|
*orient = vmd_identity_matrix;
|
|
}
|
|
|
|
if (strstr(props, "$fvec:") != NULL) {
|
|
Warning( LOCATION, "Improper custom orientation matrix for subsystem %s, you must define a up vector, then a forward vector", pm->submodel[n].name);
|
|
}
|
|
}
|
|
|
|
if ( !rotating_submodel_has_subsystem ) {
|
|
nprintf(("Model", "Model %s: Rotating Submodel without subsystem: %s\n", pm->filename, pm->submodel[n].name));
|
|
|
|
// mark those submodels which should not rotate - ie, those with no subsystem
|
|
pm->submodel[n].movement_type = MOVEMENT_TYPE_NONE;
|
|
pm->submodel[n].movement_axis = MOVEMENT_AXIS_NONE;
|
|
}
|
|
|
|
|
|
pm->submodel[n].angs.p = 0.0f;
|
|
pm->submodel[n].angs.b = 0.0f;
|
|
pm->submodel[n].angs.h = 0.0f;
|
|
|
|
{
|
|
int nchunks = cfread_int( fp ); // Throw away nchunks
|
|
if ( nchunks > 0 ) {
|
|
Error( LOCATION, "Model '%s' is chunked. See John or Adam!\n", pm->filename );
|
|
}
|
|
}
|
|
pm->submodel[n].bsp_data_size = cfread_int(fp);
|
|
if ( pm->submodel[n].bsp_data_size > 0 ) {
|
|
pm->submodel[n].bsp_data = (ubyte *)vm_malloc(pm->submodel[n].bsp_data_size);
|
|
cfread(pm->submodel[n].bsp_data,1,pm->submodel[n].bsp_data_size,fp);
|
|
swap_bsp_data( pm, pm->submodel[n].bsp_data );
|
|
} else {
|
|
pm->submodel[n].bsp_data = NULL;
|
|
}
|
|
|
|
if ( strstr( pm->submodel[n].name, "thruster") )
|
|
pm->submodel[n].is_thruster=1;
|
|
else
|
|
pm->submodel[n].is_thruster=0;
|
|
|
|
// Genghis: if we have a thruster and none of the collision
|
|
// properties were provided, then set "nocollide_this_only".
|
|
if (pm->submodel[n].is_thruster && !(pm->submodel[n].no_collisions) && !(pm->submodel[n].nocollide_this_only) && !(pm->submodel[n].collide_invisible) )
|
|
{
|
|
pm->submodel[n].nocollide_this_only = true;
|
|
}
|
|
|
|
if ( strstr( pm->submodel[n].name, "-destroyed") )
|
|
pm->submodel[n].is_damaged=1;
|
|
else
|
|
pm->submodel[n].is_damaged=0;
|
|
|
|
//mprintf(( "Submodel %d, name '%s', parent = %d\n", n, pm->submodel[n].name, pm->submodel[n].parent ));
|
|
//key_getch();
|
|
|
|
//mprintf(( "Submodel %d, tree offset %d\n", n, pm->submodel[n].tree_offset ));
|
|
//mprintf(( "Submodel %d, data offset %d\n", n, pm->submodel[n].data_offset ));
|
|
//key_getch();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ID_SLDC: // kazan - Shield Collision tree
|
|
{
|
|
pm->sldc_size = cfread_int(fp);
|
|
pm->shield_collision_tree = (ubyte *)vm_malloc(pm->sldc_size);
|
|
cfread(pm->shield_collision_tree,1,pm->sldc_size,fp);
|
|
swap_sldc_data(pm->shield_collision_tree);
|
|
//mprintf(( "Shield Collision Tree, %d bytes in size\n", pm->sldc_size));
|
|
}
|
|
break;
|
|
|
|
case ID_SHLD:
|
|
{
|
|
pm->shield.nverts = cfread_int( fp ); // get the number of vertices in the list
|
|
|
|
if (pm->shield.nverts > 0) {
|
|
pm->shield.verts = (shield_vertex *)vm_malloc(pm->shield.nverts * sizeof(shield_vertex) );
|
|
Assert( pm->shield.verts );
|
|
for ( i = 0; i < pm->shield.nverts; i++ ) { // read in the vertex list
|
|
cfread_vector( &(pm->shield.verts[i].pos), fp );
|
|
}
|
|
}
|
|
|
|
pm->shield.ntris = cfread_int( fp ); // get the number of triangles that compose the shield
|
|
|
|
if (pm->shield.ntris > 0) {
|
|
pm->shield.tris = (shield_tri *)vm_malloc(pm->shield.ntris * sizeof(shield_tri) );
|
|
Assert( pm->shield.tris );
|
|
for ( i = 0; i < pm->shield.ntris; i++ ) {
|
|
cfread_vector( &temp_vec, fp );
|
|
vm_vec_normalize_safe(&temp_vec);
|
|
pm->shield.tris[i].norm = temp_vec;
|
|
for ( j = 0; j < 3; j++ ) {
|
|
pm->shield.tris[i].verts[j] = cfread_int( fp ); // read in the indices into the shield_vertex list
|
|
#ifndef NDEBUG
|
|
if (pm->shield.tris[i].verts[j] >= pm->shield.nverts) {
|
|
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]);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for ( j = 0; j < 3; j++ ) {
|
|
pm->shield.tris[i].neighbors[j] = cfread_int( fp ); // read in the neighbor indices -- indexes into tri list
|
|
#ifndef NDEBUG
|
|
if (pm->shield.tris[i].neighbors[j] >= pm->shield.ntris) {
|
|
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]);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_GPNT:
|
|
pm->n_guns = cfread_int(fp);
|
|
|
|
if (pm->n_guns > 0) {
|
|
pm->gun_banks = (w_bank *)vm_malloc(sizeof(w_bank) * pm->n_guns);
|
|
Assert( pm->gun_banks != NULL );
|
|
|
|
for (i = 0; i < pm->n_guns; i++ ) {
|
|
w_bank *bank = &pm->gun_banks[i];
|
|
|
|
bank->num_slots = cfread_int(fp);
|
|
Assert ( bank->num_slots < MAX_SLOTS );
|
|
for (j = 0; j < bank->num_slots; j++) {
|
|
cfread_vector( &(bank->pnt[j]), fp );
|
|
cfread_vector( &temp_vec, fp );
|
|
vm_vec_normalize_safe(&temp_vec);
|
|
bank->norm[j] = temp_vec;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_MPNT:
|
|
pm->n_missiles = cfread_int(fp);
|
|
|
|
if (pm->n_missiles > 0) {
|
|
pm->missile_banks = (w_bank *)vm_malloc(sizeof(w_bank) * pm->n_missiles);
|
|
Assert( pm->missile_banks != NULL );
|
|
|
|
for (i = 0; i < pm->n_missiles; i++ ) {
|
|
w_bank *bank = &pm->missile_banks[i];
|
|
|
|
bank->num_slots = cfread_int(fp);
|
|
Assert ( bank->num_slots < MAX_SLOTS );
|
|
for (j = 0; j < bank->num_slots; j++) {
|
|
cfread_vector( &(bank->pnt[j]), fp );
|
|
cfread_vector( &temp_vec, fp );
|
|
vm_vec_normalize_safe(&temp_vec);
|
|
bank->norm[j] = temp_vec;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_DOCK: {
|
|
char props[MAX_PROP_LEN];
|
|
|
|
pm->n_docks = cfread_int(fp);
|
|
|
|
if (pm->n_docks > 0) {
|
|
pm->docking_bays = (dock_bay *)vm_malloc(sizeof(dock_bay) * pm->n_docks);
|
|
Assert( pm->docking_bays != NULL );
|
|
|
|
for (i = 0; i < pm->n_docks; i++ ) {
|
|
char *p;
|
|
dock_bay *bay = &pm->docking_bays[i];
|
|
|
|
cfread_string_len( props, MAX_PROP_LEN, fp );
|
|
if ( (p = strstr(props, "$name"))!= NULL ) {
|
|
get_user_prop_value(p+5, bay->name);
|
|
|
|
auto length = strlen(bay->name);
|
|
if ((length > 0) && is_white_space(bay->name[length-1])) {
|
|
nprintf(("Model", "model '%s' has trailing whitespace on bay name '%s'; this will be trimmed\n", pm->filename, bay->name));
|
|
drop_trailing_white_space(bay->name);
|
|
}
|
|
if (strlen(bay->name) == 0) {
|
|
nprintf(("Model", "model '%s' has an empty name specified for docking point %d\n", pm->filename, i));
|
|
}
|
|
} else {
|
|
nprintf(("Model", "model '%s' has no name specified for docking point %d\n", pm->filename, i));
|
|
sprintf(bay->name, "<unnamed bay %c>", 'A' + i);
|
|
}
|
|
|
|
bay->num_spline_paths = cfread_int( fp );
|
|
if ( bay->num_spline_paths > 0 ) {
|
|
bay->splines = (int *)vm_malloc(sizeof(int) * bay->num_spline_paths);
|
|
for ( j = 0; j < bay->num_spline_paths; j++ )
|
|
bay->splines[j] = cfread_int(fp);
|
|
} else {
|
|
bay->splines = NULL;
|
|
}
|
|
|
|
// determine what this docking bay can be used for
|
|
if ( !strnicmp(bay->name, "cargo", 5) )
|
|
bay->type_flags = DOCK_TYPE_CARGO;
|
|
else
|
|
bay->type_flags = (DOCK_TYPE_REARM | DOCK_TYPE_GENERIC);
|
|
|
|
bay->num_slots = cfread_int(fp);
|
|
|
|
if(bay->num_slots != 2) {
|
|
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);
|
|
}
|
|
|
|
for (j = 0; j < bay->num_slots; j++) {
|
|
cfread_vector( &(bay->pnt[j]), fp );
|
|
cfread_vector( &(bay->norm[j]), fp );
|
|
if(vm_vec_mag(&(bay->norm[j])) <= 0.0f) {
|
|
Warning(LOCATION, "Model '%s' dock point '%s' has a null normal. ", filename, bay->name);
|
|
}
|
|
}
|
|
|
|
if(vm_vec_same(&bay->pnt[0], &bay->pnt[1])) {
|
|
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);
|
|
|
|
// just move the second point over by some amount
|
|
bay->pnt[1].xyz.z += 10.0f;
|
|
}
|
|
|
|
vec3d diff;
|
|
vm_vec_normalized_dir(&diff, &bay->pnt[0], &bay->pnt[1]);
|
|
float dot = vm_vec_dot(&diff, &bay->norm[0]);
|
|
if(fl_abs(dot) > 0.99f) {
|
|
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);
|
|
|
|
// generate a simple rotation matrix in all three dimensions (though bank is probably not needed)
|
|
angles a = { PI_2, PI_2, PI_2 };
|
|
matrix m;
|
|
vm_angles_2_matrix(&m, &a);
|
|
|
|
// rotate the docking normal
|
|
vec3d temp = bay->norm[0];
|
|
vm_vec_rotate(&bay->norm[0], &temp, &m);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ID_GLOW: //start glow point reading -Bobboau
|
|
{
|
|
char props[MAX_PROP_LEN];
|
|
|
|
int gpb_num = cfread_int(fp);
|
|
|
|
pm->n_glow_point_banks = gpb_num;
|
|
pm->glow_point_banks = NULL;
|
|
|
|
if (gpb_num > 0)
|
|
{
|
|
pm->glow_point_banks = (glow_point_bank *) vm_malloc(sizeof(glow_point_bank) * gpb_num);
|
|
Assert(pm->glow_point_banks != NULL);
|
|
}
|
|
|
|
for (int gpb = 0; gpb < gpb_num; gpb++)
|
|
{
|
|
glow_point_bank *bank = &pm->glow_point_banks[gpb];
|
|
|
|
bank->is_on = true;
|
|
bank->glow_timestamp = 0;
|
|
bank->disp_time = cfread_int(fp);
|
|
bank->on_time = cfread_int(fp);
|
|
bank->off_time = cfread_int(fp);
|
|
bank->submodel_parent = cfread_int(fp);
|
|
bank->LOD = cfread_int(fp);
|
|
bank->type = cfread_int(fp);
|
|
bank->num_points = cfread_int(fp);
|
|
bank->points = NULL;
|
|
|
|
if (bank->num_points > 0)
|
|
bank->points = (glow_point *) vm_malloc(sizeof(glow_point) * bank->num_points);
|
|
|
|
//if((bank->off_time > 0) && (bank->disp_time > 0))
|
|
//bank->is_on = false;
|
|
|
|
cfread_string_len(props, MAX_PROP_LEN, fp);
|
|
// look for $glow_texture=xxx
|
|
auto length = strlen(props);
|
|
|
|
if (length > 0)
|
|
{
|
|
auto base_length = strlen("$glow_texture=");
|
|
Assert(strstr( (const char *)&props, "$glow_texture=") != NULL);
|
|
Assert(length > base_length);
|
|
char *glow_texture_name = props + base_length;
|
|
|
|
if (glow_texture_name[0] == '$')
|
|
glow_texture_name++;
|
|
|
|
bank->glow_bitmap = bm_load(glow_texture_name);
|
|
|
|
if (bank->glow_bitmap < 0)
|
|
{
|
|
Warning( LOCATION, "Couldn't open glowpoint texture '%s'\nreferenced by model '%s'\n", glow_texture_name, pm->filename);
|
|
}
|
|
else
|
|
{
|
|
nprintf(( "Model", "Glow point bank %i texture num is %d for '%s'\n", gpb, bank->glow_bitmap, pm->filename));
|
|
}
|
|
|
|
strcat(glow_texture_name, "-neb");
|
|
bank->glow_neb_bitmap = bm_load(glow_texture_name);
|
|
|
|
if (bank->glow_neb_bitmap < 0)
|
|
{
|
|
bank->glow_neb_bitmap = bank->glow_bitmap;
|
|
nprintf(( "Model", "Glow point bank nebula texture not found for '%s', using normal glowpoint texture instead\n", pm->filename));
|
|
// Error( LOCATION, "Couldn't open texture '%s'\nreferenced by model '%s'\n", glow_texture_name, pm->filename );
|
|
}
|
|
else
|
|
{
|
|
nprintf(( "Model", "Glow point bank %i nebula texture num is %d for '%s'\n", gpb, bank->glow_neb_bitmap, pm->filename));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// niffiwan: no "props" string found - ensure we don't have a random texture assigned!
|
|
bank->glow_bitmap = -1;
|
|
bank->glow_neb_bitmap = -1;
|
|
Warning( LOCATION, "No Glow point texture for bank '%d' referenced by model '%s'\n", gpb, pm->filename);
|
|
}
|
|
|
|
for (j = 0; j < bank->num_points; j++)
|
|
{
|
|
glow_point *p = &bank->points[j];
|
|
|
|
cfread_vector(&(p->pnt), fp);
|
|
cfread_vector( &temp_vec, fp );
|
|
if (!IS_VEC_NULL_SQ_SAFE(&temp_vec))
|
|
vm_vec_normalize(&temp_vec);
|
|
else
|
|
vm_vec_zero(&temp_vec);
|
|
p->norm = temp_vec;
|
|
p->radius = cfread_float( fp);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ID_FUEL:
|
|
char props[MAX_PROP_LEN];
|
|
pm->n_thrusters = cfread_int(fp);
|
|
|
|
if (pm->n_thrusters > 0) {
|
|
pm->thrusters = (thruster_bank *)vm_malloc(sizeof(thruster_bank) * pm->n_thrusters);
|
|
Assert( pm->thrusters != NULL );
|
|
|
|
for (i = 0; i < pm->n_thrusters; i++ ) {
|
|
thruster_bank *bank = &pm->thrusters[i];
|
|
|
|
bank->num_points = cfread_int(fp);
|
|
bank->points = NULL;
|
|
|
|
if (bank->num_points > 0)
|
|
bank->points = (glow_point *) vm_malloc(sizeof(glow_point) * bank->num_points);
|
|
|
|
bank->obj_num = -1;
|
|
bank->submodel_num = -1;
|
|
|
|
if (pm->version < 2117) {
|
|
bank->wash_info_pointer = NULL;
|
|
} else {
|
|
cfread_string_len( props, MAX_PROP_LEN, fp );
|
|
// look for $engine_subsystem=xxx
|
|
auto length = strlen(props);
|
|
if (length > 0) {
|
|
auto base_length = strlen("$engine_subsystem=");
|
|
Assert( strstr( (const char *)&props, "$engine_subsystem=") != NULL );
|
|
Assert( length > base_length );
|
|
char *engine_subsys_name = props + base_length;
|
|
if (engine_subsys_name[0] == '$') {
|
|
engine_subsys_name++;
|
|
}
|
|
|
|
nprintf(("wash", "Ship %s with engine wash associated with subsys %s\n", filename, engine_subsys_name));
|
|
|
|
// set wash_info_index to invalid
|
|
int table_error = 1;
|
|
bank->wash_info_pointer = NULL;
|
|
for (int k=0; k<n_subsystems; k++) {
|
|
if ( !subsystem_stricmp(subsystems[k].subobj_name, engine_subsys_name) ) {
|
|
bank->submodel_num = subsystems[k].subobj_num;
|
|
|
|
bank->wash_info_pointer = subsystems[k].engine_wash_pointer;
|
|
if (bank->wash_info_pointer != NULL) {
|
|
table_error = 0;
|
|
}
|
|
// also set what subsystem this is attached to but not if we only have one thruster bank
|
|
// do this so that original :V: models still work like they used to
|
|
if (pm->n_thrusters > 1) {
|
|
bank->obj_num = k;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( (bank->wash_info_pointer == NULL) && (n_subsystems > 0) ) {
|
|
if (table_error) {
|
|
// Warning(LOCATION, "No engine wash table entry in ships.tbl for ship model %s", filename);
|
|
} else {
|
|
Warning(LOCATION, "Inconsistent model: Engine wash engine subsystem does not match any ship subsytem names for ship model %s", filename);
|
|
}
|
|
}
|
|
} else {
|
|
bank->wash_info_pointer = NULL;
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < bank->num_points; j++) {
|
|
glow_point *p = &bank->points[j];
|
|
|
|
cfread_vector( &(p->pnt), fp );
|
|
cfread_vector( &temp_vec, fp );
|
|
vm_vec_normalize_safe(&temp_vec);
|
|
p->norm = temp_vec;
|
|
|
|
if ( pm->version > 2004 ) {
|
|
p->radius = cfread_float( fp );
|
|
//mprintf(( "Rad = %.2f\n", rad ));
|
|
} else {
|
|
p->radius = 1.0f;
|
|
}
|
|
}
|
|
//mprintf(( "Num slots = %d\n", bank->num_slots ));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_TGUN:
|
|
case ID_TMIS: {
|
|
int n_banks = cfread_int(fp); // Number of turrets
|
|
|
|
for ( i = 0; i < n_banks; i++ ) {
|
|
int parent; // The parent subobj of the turret (the gun base)
|
|
int physical_parent; // The subobj that the firepoints are physically attached to (the gun barrel)
|
|
int n_slots; // How many firepoints the turret has
|
|
model_subsystem *subsystemp; // The actual turret subsystem
|
|
|
|
parent = cfread_int( fp );
|
|
physical_parent = cfread_int(fp);
|
|
|
|
int snum=-1;
|
|
if ( subsystems ) {
|
|
for ( snum = 0; snum < n_subsystems; snum++ ) {
|
|
subsystemp = &subsystems[snum];
|
|
|
|
if ( parent == subsystemp->subobj_num ) {
|
|
cfread_vector( &temp_vec, fp );
|
|
vm_vec_normalize_safe(&temp_vec);
|
|
subsystemp->turret_norm = temp_vec;
|
|
vm_vector_2_matrix(&subsystemp->turret_matrix,&subsystemp->turret_norm,NULL,NULL);
|
|
|
|
n_slots = cfread_int( fp );
|
|
subsystemp->turret_gun_sobj = physical_parent;
|
|
if(n_slots > MAX_TFP) {
|
|
Warning(LOCATION, "Model %s has %i turret firing points on subsystem %s, maximum is %i", pm->filename, n_slots, subsystemp->name, MAX_TFP);
|
|
}
|
|
|
|
for (j = 0; j < n_slots; j++ ) {
|
|
if(j < MAX_TFP)
|
|
cfread_vector( &subsystemp->turret_firing_point[j], fp );
|
|
else
|
|
{
|
|
vec3d bogus;
|
|
cfread_vector(&bogus, fp);
|
|
}
|
|
}
|
|
Assertion( n_slots > 0, "Turret %s in model %s has no firing points.\n", subsystemp->name, pm->filename);
|
|
|
|
subsystemp->turret_num_firing_points = n_slots;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (n_subsystems == 0) || (snum == n_subsystems) ) {
|
|
vec3d bogus;
|
|
|
|
nprintf(("Warning", "Turret submodel %i not found for turret %i in model %s\n", parent, i, pm->filename));
|
|
cfread_vector( &bogus, fp );
|
|
n_slots = cfread_int( fp );
|
|
for (j = 0; j < n_slots; j++ )
|
|
cfread_vector( &bogus, fp );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ID_SPCL: {
|
|
char name[MAX_NAME_LEN], props_spcl[MAX_PROP_LEN], *p;
|
|
int n_specials;
|
|
float radius;
|
|
vec3d pnt;
|
|
|
|
n_specials = cfread_int(fp); // get the number of special subobjects we have
|
|
for (i = 0; i < n_specials; i++) {
|
|
|
|
// get the next free object of the subobject list. Flag error if no more room
|
|
|
|
cfread_string_len(name, MAX_NAME_LEN, fp); // get the name of this special polygon
|
|
|
|
cfread_string_len(props_spcl, MAX_PROP_LEN, fp); // will definately have properties as well!
|
|
cfread_vector( &pnt, fp );
|
|
radius = cfread_float( fp );
|
|
|
|
// check if $Split
|
|
p = strstr(name, "$split");
|
|
if (p != NULL) {
|
|
pm->split_plane[pm->num_split_plane] = pnt.xyz.z;
|
|
pm->num_split_plane++;
|
|
Assert(pm->num_split_plane <= MAX_SPLIT_PLANE);
|
|
} else if ( ( p = strstr(props_spcl, "$special"))!= NULL ) {
|
|
char type[64];
|
|
|
|
get_user_prop_value(p+9, type);
|
|
if ( !stricmp(type, "subsystem") ) { // if we have a subsystem, put it into the list!
|
|
do_new_subsystem( n_subsystems, subsystems, -1, radius, &pnt, props_spcl, &name[1], pm->id ); // skip the first '$' character of the name
|
|
} else if ( !stricmp(type, "shieldpoint") ) {
|
|
pm->shield_points.push_back(pnt);
|
|
}
|
|
} else if ( strstr(name, "$enginelarge") || strstr(name, "$enginehuge") ){
|
|
do_new_subsystem( n_subsystems, subsystems, -1, radius, &pnt, props_spcl, &name[1], pm->id ); // skip the first '$' character of the name
|
|
} else {
|
|
nprintf(("Warning", "Unknown special object type %s while reading model %s\n", name, pm->filename));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ID_TXTR: { //Texture filename list
|
|
int n;
|
|
// char name_buf[128];
|
|
|
|
//mprintf(0,"Got chunk TXTR, len=%d\n",len);
|
|
|
|
|
|
n = cfread_int(fp);
|
|
pm->n_textures = n;
|
|
// Don't overwrite memory!!
|
|
Verify(pm->n_textures <= MAX_MODEL_TEXTURES);
|
|
//mprintf(0," num textures = %d\n",n);
|
|
for (i=0; i<n; i++ )
|
|
{
|
|
char tmp_name[256];
|
|
cfread_string_len(tmp_name,127,fp);
|
|
model_load_texture(pm, i, tmp_name);
|
|
//mprintf(0,"<%s>\n",name_buf);
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
/* case ID_IDTA: //Interpreter data
|
|
//mprintf(0,"Got chunk IDTA, len=%d\n",len);
|
|
|
|
pm->model_data = (ubyte *)vm_malloc(len);
|
|
pm->model_data_size = len;
|
|
Assert(pm->model_data != NULL );
|
|
|
|
cfread(pm->model_data,1,len,fp);
|
|
|
|
break;
|
|
*/
|
|
|
|
case ID_INFO: // don't need to do anything with info stuff
|
|
|
|
#ifndef NDEBUG
|
|
pm->debug_info_size = len;
|
|
pm->debug_info = (char *)vm_malloc(pm->debug_info_size+1);
|
|
Assert(pm->debug_info!=NULL);
|
|
memset(pm->debug_info,0,len+1);
|
|
cfread( pm->debug_info, 1, len, fp );
|
|
#endif
|
|
break;
|
|
|
|
case ID_GRID:
|
|
break;
|
|
|
|
case ID_PATH:
|
|
pm->n_paths = cfread_int( fp );
|
|
|
|
if (pm->n_paths <= 0) {
|
|
break;
|
|
}
|
|
|
|
pm->paths = (model_path *)vm_malloc(sizeof(model_path)*pm->n_paths);
|
|
Assert( pm->paths != NULL );
|
|
|
|
memset( pm->paths, 0, sizeof(model_path) * pm->n_paths );
|
|
|
|
for (i=0; i<pm->n_paths; i++ ) {
|
|
cfread_string_len(pm->paths[i].name , MAX_NAME_LEN-1, fp);
|
|
if ( pm->version >= 2002 ) {
|
|
// store the sub_model name number of the parent
|
|
cfread_string_len(pm->paths[i].parent_name , MAX_NAME_LEN-1, fp);
|
|
// get rid of leading '$' char in name
|
|
if ( pm->paths[i].parent_name[0] == '$' ) {
|
|
char tmpbuf[MAX_NAME_LEN];
|
|
strcpy_s(tmpbuf, pm->paths[i].parent_name+1);
|
|
strcpy_s(pm->paths[i].parent_name, tmpbuf);
|
|
}
|
|
// store the sub_model index (ie index into pm->submodel) of the parent
|
|
pm->paths[i].parent_submodel = -1;
|
|
for ( j = 0; j < pm->n_models; j++ ) {
|
|
if ( !stricmp( pm->submodel[j].name, pm->paths[i].parent_name) ) {
|
|
pm->paths[i].parent_submodel = j;
|
|
}
|
|
}
|
|
} else {
|
|
pm->paths[i].parent_name[0] = 0;
|
|
pm->paths[i].parent_submodel = -1;
|
|
}
|
|
|
|
pm->paths[i].nverts = cfread_int( fp );
|
|
pm->paths[i].verts = (mp_vert *)vm_malloc( sizeof(mp_vert) * pm->paths[i].nverts );
|
|
pm->paths[i].goal = pm->paths[i].nverts - 1;
|
|
pm->paths[i].type = MP_TYPE_UNUSED;
|
|
pm->paths[i].value = 0;
|
|
Assert(pm->paths[i].verts!=NULL);
|
|
memset( pm->paths[i].verts, 0, sizeof(mp_vert) * pm->paths[i].nverts );
|
|
|
|
for (j=0; j<pm->paths[i].nverts; j++ ) {
|
|
cfread_vector(&pm->paths[i].verts[j].pos,fp );
|
|
pm->paths[i].verts[j].radius = cfread_float( fp );
|
|
|
|
{ // version 1802 added turret stuff
|
|
int nturrets, k;
|
|
|
|
nturrets = cfread_int( fp );
|
|
pm->paths[i].verts[j].nturrets = nturrets;
|
|
|
|
if (nturrets > 0) {
|
|
pm->paths[i].verts[j].turret_ids = (int *)vm_malloc( sizeof(int) * nturrets );
|
|
for ( k = 0; k < nturrets; k++ )
|
|
pm->paths[i].verts[j].turret_ids[k] = cfread_int( fp );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_EYE: // an eye position(s)
|
|
{
|
|
int num_eyes;
|
|
|
|
// all eyes points are stored simply as vectors and their normals.
|
|
// 0th element is used as usual player view position.
|
|
|
|
num_eyes = cfread_int( fp );
|
|
pm->n_view_positions = num_eyes;
|
|
Assert ( num_eyes < MAX_EYES );
|
|
for (i = 0; i < num_eyes; i++ ) {
|
|
pm->view_positions[i].parent = cfread_int( fp );
|
|
cfread_vector( &pm->view_positions[i].pnt, fp );
|
|
cfread_vector( &pm->view_positions[i].norm, fp );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_INSG:
|
|
int num_ins, num_verts, num_faces, idx, idx2, idx3;
|
|
|
|
// get the # of insignias
|
|
num_ins = cfread_int(fp);
|
|
pm->num_ins = num_ins;
|
|
|
|
// read in the insignias
|
|
for(idx=0; idx<num_ins; idx++){
|
|
// get the detail level
|
|
pm->ins[idx].detail_level = cfread_int(fp);
|
|
if (pm->ins[idx].detail_level < 0) {
|
|
Warning(LOCATION, "Model '%s': insignia uses an invalid LOD (%i)\n", pm->filename, pm->ins[idx].detail_level);
|
|
}
|
|
|
|
// # of faces
|
|
num_faces = cfread_int(fp);
|
|
pm->ins[idx].num_faces = num_faces;
|
|
Assert(num_faces <= MAX_INS_FACES);
|
|
|
|
// # of vertices
|
|
num_verts = cfread_int(fp);
|
|
Assert(num_verts <= MAX_INS_VECS);
|
|
|
|
// read in all the vertices
|
|
for(idx2=0; idx2<num_verts; idx2++){
|
|
cfread_vector(&pm->ins[idx].vecs[idx2], fp);
|
|
}
|
|
|
|
// read in world offset
|
|
cfread_vector(&pm->ins[idx].offset, fp);
|
|
|
|
// read in all the faces
|
|
for(idx2=0; idx2<pm->ins[idx].num_faces; idx2++){
|
|
// read in 3 vertices
|
|
for(idx3=0; idx3<3; idx3++){
|
|
pm->ins[idx].faces[idx2][idx3] = cfread_int(fp);
|
|
pm->ins[idx].u[idx2][idx3] = cfread_float(fp);
|
|
pm->ins[idx].v[idx2][idx3] = cfread_float(fp);
|
|
}
|
|
vec3d tempv;
|
|
|
|
//get three points (rotated) and compute normal
|
|
|
|
vm_vec_perp(&tempv,
|
|
&pm->ins[idx].vecs[pm->ins[idx].faces[idx2][0]],
|
|
&pm->ins[idx].vecs[pm->ins[idx].faces[idx2][1]],
|
|
&pm->ins[idx].vecs[pm->ins[idx].faces[idx2][2]]);
|
|
|
|
vm_vec_normalize_safe(&tempv);
|
|
|
|
pm->ins[idx].norm[idx2] = tempv;
|
|
// 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));
|
|
|
|
}
|
|
}
|
|
break;
|
|
|
|
// autocentering info
|
|
case ID_ACEN:
|
|
cfread_vector(&pm->autocenter, fp);
|
|
pm->flags |= PM_FLAG_AUTOCEN;
|
|
break;
|
|
|
|
default:
|
|
mprintf(("Unknown chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len));
|
|
cfseek(fp,len,SEEK_CUR);
|
|
break;
|
|
|
|
}
|
|
cfseek(fp,next_chunk,SEEK_SET);
|
|
|
|
id = cfread_int(fp);
|
|
len = cfread_int(fp);
|
|
next_chunk = cftell(fp) + len;
|
|
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
if ( ss_fp) {
|
|
int size;
|
|
|
|
cfclose(ss_fp);
|
|
ss_fp = cfopen(debug_name, "rb");
|
|
if ( ss_fp ) {
|
|
size = cfilelength(ss_fp);
|
|
cfclose(ss_fp);
|
|
if ( size <= 0 ) {
|
|
_unlink(debug_name);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
cfclose(fp);
|
|
|
|
// mprintf(("Done processing chunks\n"));
|
|
return 1;
|
|
}
|
|
|
|
//Goober
|
|
void model_load_texture(polymodel *pm, int i, char *file)
|
|
{
|
|
// NOTE: it doesn't help to use more than MAX_FILENAME_LEN here as bmpman will use that restriction
|
|
// we also have to make sure there is always a trailing NUL since overflow doesn't add it
|
|
char tmp_name[MAX_FILENAME_LEN];
|
|
strcpy_s(tmp_name, file);
|
|
strlwr(tmp_name);
|
|
|
|
texture_map *tmap = &pm->maps[i];
|
|
tmap->Clear();
|
|
|
|
//WMC - IMPORTANT!!
|
|
//The Fred_running checks are there so that FRED will see those textures and put them in the
|
|
//texture replacement box.
|
|
|
|
// base maps ---------------------------------------------------------------
|
|
texture_info *tbase = &tmap->textures[TM_BASE_TYPE];
|
|
|
|
if (strstr(tmp_name, "thruster") || strstr(tmp_name, "invisible") || strstr(tmp_name, "warpmap"))
|
|
{
|
|
// Don't load textures for thruster animations or invisible textures
|
|
// or warp models!-Bobboau
|
|
tbase->clear();
|
|
}
|
|
else
|
|
{
|
|
// check if we should be transparent, include "-trans" but make sure to skip anything that might be "-transport"
|
|
if ( (strstr(tmp_name, "-trans") && !strstr(tmp_name, "-transpo")) || strstr(tmp_name, "shockwave") || !strcmp(tmp_name, "nameplate") ) {
|
|
tmap->is_transparent = true;
|
|
}
|
|
|
|
if (strstr(tmp_name, "-amb")) {
|
|
tmap->is_ambient = true;
|
|
}
|
|
|
|
tbase->LoadTexture(tmp_name, pm->filename);
|
|
|
|
if ( tbase->GetTexture() < 0 ) {
|
|
Warning(LOCATION, "Couldn't open texture '%s'\nreferenced by model '%s'\n", tmp_name, pm->filename);
|
|
}
|
|
}
|
|
// -------------------------------------------------------------------------
|
|
|
|
// glow maps ---------------------------------------------------------------
|
|
texture_info *tglow = &tmap->textures[TM_GLOW_TYPE];
|
|
if ( (!Cmdline_glow && !Fred_running) || (tbase->GetTexture() < 0))
|
|
{
|
|
tglow->clear();
|
|
}
|
|
else
|
|
{
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-glow" );
|
|
strlwr(tmp_name);
|
|
|
|
tglow->LoadTexture(tmp_name, pm->filename);
|
|
}
|
|
// -------------------------------------------------------------------------
|
|
|
|
// specular maps -----------------------------------------------------------
|
|
texture_info *tspec = &tmap->textures[TM_SPECULAR_TYPE];
|
|
texture_info *tspecgloss = &tmap->textures[TM_SPEC_GLOSS_TYPE];
|
|
if ( (!Cmdline_spec && !Fred_running) || (tbase->GetTexture() < 0))
|
|
{
|
|
tspec->clear();
|
|
tspecgloss->clear();
|
|
}
|
|
else
|
|
{
|
|
// look for reflectance map
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-reflect");
|
|
strlwr(tmp_name);
|
|
|
|
tspecgloss->LoadTexture(tmp_name, pm->filename);
|
|
|
|
// look for a legacy shine map as well
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-shine");
|
|
strlwr(tmp_name);
|
|
|
|
tspec->LoadTexture(tmp_name, pm->filename);
|
|
}
|
|
//tmap->spec_map.original_texture = tmap->spec_map.texture;
|
|
// -------------------------------------------------------------------------
|
|
|
|
// bump maps ---------------------------------------------------------------
|
|
texture_info *tnorm = &tmap->textures[TM_NORMAL_TYPE];
|
|
if ( (!Cmdline_normal && !Fred_running) || (tbase->GetTexture() < 0) ) {
|
|
tnorm->clear();
|
|
} else {
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-normal");
|
|
strlwr(tmp_name);
|
|
|
|
tnorm->LoadTexture(tmp_name, pm->filename);
|
|
}
|
|
|
|
// try to get a height map too
|
|
texture_info *theight = &tmap->textures[TM_HEIGHT_TYPE];
|
|
if ((!Cmdline_height && !Fred_running) || (tbase->GetTexture() < 0)) {
|
|
theight->clear();
|
|
} else {
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-height");
|
|
strlwr(tmp_name);
|
|
|
|
theight->LoadTexture(tmp_name, pm->filename);
|
|
}
|
|
|
|
// ambient occlusion maps
|
|
texture_info *tambient = &tmap->textures[TM_AMBIENT_TYPE];
|
|
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-ao");
|
|
strlwr(tmp_name);
|
|
|
|
tambient->LoadTexture(tmp_name, pm->filename);
|
|
|
|
// Utility map -------------------------------------------------------------
|
|
texture_info *tmisc = &tmap->textures[TM_MISC_TYPE];
|
|
|
|
strcpy_s(tmp_name, file);
|
|
strcat_s(tmp_name, "-misc");
|
|
strlwr(tmp_name);
|
|
|
|
tmisc->LoadTexture(tmp_name, pm->filename);
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// See if we need to compile a new shader for this material
|
|
int shader_flags = 0;
|
|
|
|
if (tbase->GetTexture() > 0)
|
|
shader_flags |= SDR_FLAG_MODEL_DIFFUSE_MAP;
|
|
if (tglow->GetTexture() > 0 && Cmdline_glow)
|
|
shader_flags |= SDR_FLAG_MODEL_GLOW_MAP;
|
|
if ((tspec->GetTexture() > 0 || tspecgloss->GetTexture() > 0) && Cmdline_spec)
|
|
shader_flags |= SDR_FLAG_MODEL_SPEC_MAP;
|
|
if (tnorm->GetTexture() > 0 && Cmdline_normal)
|
|
shader_flags |= SDR_FLAG_MODEL_NORMAL_MAP;
|
|
if (theight->GetTexture() > 0 && Cmdline_height)
|
|
shader_flags |= SDR_FLAG_MODEL_HEIGHT_MAP;
|
|
if ((tspec->GetTexture() > 0 || tspecgloss->GetTexture() > 0) && Cmdline_env && Cmdline_spec) // No env maps without spec map
|
|
shader_flags |= SDR_FLAG_MODEL_ENV_MAP;
|
|
if (tmisc->GetTexture() > 0)
|
|
shader_flags |= SDR_FLAG_MODEL_MISC_MAP;
|
|
if (tambient->GetTexture() >0)
|
|
shader_flags |= SDR_FLAG_MODEL_AMBIENT_MAP;
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, SDR_FLAG_MODEL_SHADOW_MAP);
|
|
|
|
shader_flags |= SDR_FLAG_MODEL_CLIP;
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED);
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED | SDR_FLAG_MODEL_FOG);
|
|
|
|
shader_flags |= SDR_FLAG_MODEL_DEFERRED;
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT);
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_FOG);
|
|
|
|
if( !Cmdline_no_batching ) {
|
|
shader_flags &= ~SDR_FLAG_MODEL_DEFERRED;
|
|
shader_flags |= SDR_FLAG_MODEL_TRANSFORM;
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED);
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED | SDR_FLAG_MODEL_FOG);
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT);
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_FOG);
|
|
|
|
shader_flags |= SDR_FLAG_MODEL_DEFERRED;
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED);
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_ANIMATED | SDR_FLAG_MODEL_FOG);
|
|
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT);
|
|
gr_maybe_create_shader(SDR_TYPE_MODEL, shader_flags | SDR_FLAG_MODEL_LIGHT | SDR_FLAG_MODEL_FOG);
|
|
}
|
|
}
|
|
|
|
//returns the number of this model
|
|
int model_load(const char *filename, int n_subsystems, model_subsystem *subsystems, int ferror, int duplicate)
|
|
{
|
|
int i, num, arc_idx;
|
|
polymodel *pm = NULL;
|
|
|
|
if ( !model_initted )
|
|
model_init();
|
|
|
|
num = -1;
|
|
|
|
for (i=0; i< MAX_POLYGON_MODELS; i++) {
|
|
if ( Polygon_models[i] ) {
|
|
if (!stricmp(filename, Polygon_models[i]->filename) && !duplicate) {
|
|
// Model already loaded; just return.
|
|
Polygon_models[i]->used_this_mission++;
|
|
return Polygon_models[i]->id;
|
|
}
|
|
} else if ( num == -1 ) {
|
|
// This is the first empty slot
|
|
num = i;
|
|
}
|
|
}
|
|
|
|
// No empty slot
|
|
if ( num == -1 ) {
|
|
Error( LOCATION, "Too many models" );
|
|
return -1;
|
|
}
|
|
|
|
TRACE_SCOPE(tracing::LoadModelFile);
|
|
|
|
mprintf(( "Loading model '%s' into slot '%i'\n", filename, num ));
|
|
|
|
pm = new polymodel;
|
|
Polygon_models[num] = pm;
|
|
|
|
pm->n_paths = 0;
|
|
pm->paths = NULL;
|
|
|
|
uint org_sig = static_cast<uint>(Model_signature);
|
|
if ( org_sig + MAX_POLYGON_MODELS > INT_MAX || org_sig + MAX_POLYGON_MODELS < org_sig ) {
|
|
Model_signature = 0; // Overflow
|
|
} else {
|
|
Model_signature+=MAX_POLYGON_MODELS; // No overflow
|
|
}
|
|
Assert( (Model_signature % MAX_POLYGON_MODELS) == 0 );
|
|
pm->id = Model_signature + num;
|
|
Assert( (pm->id % MAX_POLYGON_MODELS) == num );
|
|
|
|
extern int Parse_normal_problem_count;
|
|
Parse_normal_problem_count = 0;
|
|
|
|
pm->used_this_mission = 0;
|
|
|
|
#ifndef NDEBUG
|
|
char busy_text[60] = { '\0' };
|
|
|
|
strcat_s( busy_text, "** ModelLoad: " );
|
|
strcat_s( busy_text, filename );
|
|
strcat_s( busy_text, " **" );
|
|
|
|
game_busy(busy_text);
|
|
#endif
|
|
|
|
if (read_model_file(pm, filename, n_subsystems, subsystems, ferror) < 0) {
|
|
if (pm != NULL) {
|
|
delete pm;
|
|
}
|
|
|
|
Polygon_models[num] = NULL;
|
|
return -1;
|
|
}
|
|
|
|
pm->used_this_mission++;
|
|
|
|
#ifdef _DEBUG
|
|
if(Fred_running && Parse_normal_problem_count > 0)
|
|
{
|
|
char buffer[100];
|
|
sprintf(buffer,"Serious problem loading model %s, %d normals capped to zero",
|
|
filename, Parse_normal_problem_count);
|
|
os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, buffer);
|
|
}
|
|
#endif
|
|
|
|
//=============================
|
|
// Find the destroyed replacement models
|
|
|
|
// Set up the default values
|
|
for (i=0; i<pm->n_models; i++ ) {
|
|
pm->submodel[i].my_replacement = -1; // assume nothing replaces this
|
|
pm->submodel[i].i_replace = -1; // assume this doesn't replaces anything
|
|
}
|
|
|
|
// Search for models that have destroyed versions
|
|
for (i=0; i<pm->n_models; i++ ) {
|
|
int j;
|
|
char destroyed_name[128];
|
|
|
|
strcpy_s( destroyed_name, pm->submodel[i].name );
|
|
strcat_s( destroyed_name, "-destroyed" );
|
|
for (j=0; j<pm->n_models; j++ ) {
|
|
if ( !stricmp( pm->submodel[j].name, destroyed_name )) {
|
|
pm->submodel[i].my_replacement = j;
|
|
pm->submodel[j].i_replace = i;
|
|
}
|
|
}
|
|
|
|
// Search for models with live debris
|
|
// This debris comes from a destroyed subsystem when ship is still alive
|
|
char live_debris_name[128];
|
|
|
|
strcpy_s( live_debris_name, "debris-" );
|
|
strcat_s( live_debris_name, pm->submodel[i].name );
|
|
|
|
pm->submodel[i].num_live_debris = 0;
|
|
for (j=0; j<pm->n_models; j++ ) {
|
|
// check if current model name is substring of destroyed
|
|
if ( strstr( pm->submodel[j].name, live_debris_name )) {
|
|
mprintf(( "Found live debris model for '%s'\n", pm->submodel[i].name ));
|
|
Assert(pm->submodel[i].num_live_debris < MAX_LIVE_DEBRIS);
|
|
pm->submodel[i].live_debris[pm->submodel[i].num_live_debris++] = j;
|
|
pm->submodel[j].is_live_debris = 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
create_family_tree(pm);
|
|
|
|
// maybe generate vertex buffers
|
|
create_vertex_buffer(pm);
|
|
|
|
//==============================
|
|
// Find all the lower detail versions of the hires model
|
|
for (i=0; i<pm->n_models; i++ ) {
|
|
int j;
|
|
size_t l1;
|
|
bsp_info * sm1 = &pm->submodel[i];
|
|
|
|
// set all arc types to be default
|
|
for(arc_idx=0; arc_idx < MAX_ARC_EFFECTS; arc_idx++){
|
|
sm1->arc_type[arc_idx] = MARC_TYPE_NORMAL;
|
|
}
|
|
|
|
sm1->num_details = 0;
|
|
// If a backward compatibility LOD name is declared use it
|
|
if (sm1->lod_name[0] != '\0') {
|
|
l1=strlen(sm1->lod_name);
|
|
}
|
|
// otherwise use the name for LOD comparision
|
|
else {
|
|
l1 = strlen(sm1->name);
|
|
}
|
|
|
|
for (j=0; j<pm->num_debris_objects;j++ ) {
|
|
if ( i == pm->debris_objects[j] ) {
|
|
sm1->is_damaged = 1;
|
|
}
|
|
}
|
|
|
|
|
|
for (j=0; j<MAX_MODEL_DETAIL_LEVELS; j++ ) {
|
|
sm1->details[j] = -1;
|
|
}
|
|
|
|
for (j=0; j<pm->n_models; j++ ) {
|
|
bsp_info * sm2 = &pm->submodel[j];
|
|
|
|
if ( i==j ) continue;
|
|
|
|
// set all arc types to be default
|
|
for(arc_idx=0; arc_idx < MAX_ARC_EFFECTS; arc_idx++){
|
|
sm2->arc_type[arc_idx] = MARC_TYPE_NORMAL;
|
|
}
|
|
|
|
// if sm2 is a detail of sm1 and sm1 is a high detail, then add it to sm1's list
|
|
if (strlen(sm2->name)!=l1) continue;
|
|
|
|
int ndiff = 0;
|
|
size_t first_diff = 0;
|
|
for ( size_t k=0; k<l1; k++) {
|
|
// If a backward compatibility LOD name is declared use it
|
|
if (sm1->lod_name[0] != '\0') {
|
|
if (sm1->lod_name[k] != sm2->name[k] ) {
|
|
if (ndiff==0) first_diff = k;
|
|
ndiff++;
|
|
}
|
|
}
|
|
// otherwise do the standard LOD comparision
|
|
else {
|
|
if (sm1->name[k] != sm2->name[k] ) {
|
|
if (ndiff==0) first_diff = k;
|
|
ndiff++;
|
|
}
|
|
}
|
|
}
|
|
if (ndiff==1) { // They only differ by one character!
|
|
int dl1, dl2;
|
|
// If a backward compatibility LOD name is declared use it
|
|
if (sm1->lod_name[0] != '\0') {
|
|
dl1 = tolower(sm1->lod_name[first_diff]) - 'a';
|
|
}
|
|
// otherwise do the standard LOD comparision
|
|
else {
|
|
dl1 = tolower(sm1->name[first_diff]) - 'a';
|
|
}
|
|
dl2 = tolower(sm2->name[first_diff]) - 'a';
|
|
|
|
// Handle LODs named "detail0/1/2/etc" too (as opposed to "detaila/b/c/etc")
|
|
if (sm1->parent == -1 && sm2->parent == -1 && !sm1->is_damaged && !sm2->is_damaged && !sm1->is_live_debris && !sm2->is_live_debris) {
|
|
dl2 = dl2 - dl1;
|
|
dl1 = 0;
|
|
}
|
|
|
|
if ( (dl1<0) || (dl2<0) || (dl1>=MAX_MODEL_DETAIL_LEVELS) || (dl2>=MAX_MODEL_DETAIL_LEVELS) ) continue; // invalid detail levels
|
|
|
|
if ( dl1 == 0 ) {
|
|
dl2--; // Start from 1 up...
|
|
if (dl2 >= sm1->num_details ) sm1->num_details = dl2+1;
|
|
sm1->details[dl2] = j;
|
|
mprintf(( "Submodel '%s' is detail level %d of '%s'\n", sm2->name, dl2 + 1, sm1->name ));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (j=0; j<sm1->num_details; j++ ) {
|
|
if ( sm1->details[j] == -1 ) {
|
|
sm1->num_details = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
model_octant_create( pm );
|
|
|
|
if ( !Cmdline_old_collision_sys ) {
|
|
TRACE_SCOPE(tracing::ModelParseAllBSPTrees);
|
|
|
|
for ( i = 0; i < pm->n_models; ++i ) {
|
|
pm->submodel[i].collision_tree_index = model_create_bsp_collision_tree();
|
|
bsp_collision_tree *tree = model_get_bsp_collision_tree(pm->submodel[i].collision_tree_index);
|
|
|
|
model_collide_parse_bsp(tree, pm->submodel[i].bsp_data, pm->version);
|
|
}
|
|
}
|
|
|
|
// Find the core_radius... the minimum of
|
|
float rx, ry, rz;
|
|
rx = fl_abs( pm->submodel[pm->detail[0]].max.xyz.x - pm->submodel[pm->detail[0]].min.xyz.x );
|
|
ry = fl_abs( pm->submodel[pm->detail[0]].max.xyz.y - pm->submodel[pm->detail[0]].min.xyz.y );
|
|
rz = fl_abs( pm->submodel[pm->detail[0]].max.xyz.z - pm->submodel[pm->detail[0]].min.xyz.z );
|
|
|
|
pm->core_radius = MIN( rx, MIN(ry, rz) ) / 2.0f;
|
|
|
|
for (i=0; i<pm->n_view_positions; i++ ) {
|
|
if ( pm->view_positions[i].parent == pm->detail[0] ) {
|
|
float d = vm_vec_mag( &pm->view_positions[i].pnt );
|
|
|
|
d += 0.1f; // Make the eye 1/10th of a meter inside the sphere.
|
|
|
|
if ( d > pm->core_radius ) {
|
|
pm->core_radius = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Goober5000 - originally done in ship_create for no apparent reason
|
|
model_set_subsys_path_nums(pm, n_subsystems, subsystems);
|
|
model_set_bay_path_nums(pm);
|
|
|
|
return pm->id;
|
|
}
|
|
|
|
int model_create_instance(bool is_ship, int model_num)
|
|
{
|
|
int i = 0;
|
|
int open_slot = -1;
|
|
|
|
// go through model instances and find an empty slot
|
|
for ( i = 0; i < (int)Polygon_model_instances.size(); i++) {
|
|
if ( !Polygon_model_instances[i] ) {
|
|
open_slot = i;
|
|
}
|
|
}
|
|
|
|
polymodel_instance *pmi = (polymodel_instance*)vm_malloc(sizeof(polymodel_instance));
|
|
memset(pmi, 0, sizeof(polymodel_instance));
|
|
pmi->model_num = model_num;
|
|
|
|
// if not found, create a slot
|
|
if ( open_slot < 0 ) {
|
|
Polygon_model_instances.push_back( pmi );
|
|
open_slot = (int)(Polygon_model_instances.size() - 1);
|
|
} else {
|
|
Polygon_model_instances[open_slot] = pmi;
|
|
}
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
pmi->submodel = (submodel_instance*)vm_malloc( sizeof(submodel_instance)*pm->n_models );
|
|
|
|
for ( i = 0; i < pm->n_models; i++ ) {
|
|
model_clear_submodel_instance( &pmi->submodel[i], &pm->submodel[i] );
|
|
}
|
|
|
|
// add intrinsic_rotation instances if this model is intrinsic-rotating
|
|
if (pm->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
|
|
intrinsic_rotation intrinsic_rotate(is_ship, open_slot);
|
|
|
|
for (i = 0; i < pm->n_models; i++) {
|
|
if (pm->submodel[i].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
|
|
intrinsic_rotate.list.push_back(submodel_intrinsic_rotation(i, pm->submodel[i].dumb_turn_rate));
|
|
}
|
|
}
|
|
|
|
if (intrinsic_rotate.list.empty()) {
|
|
Assertion(!intrinsic_rotate.list.empty(), "This model has the PM_FLAG_HAS_INTRINSIC_ROTATE flag; why doesn't it have an intrinsic-rotating submodel?");
|
|
} else {
|
|
Intrinsic_rotations.push_back(intrinsic_rotate);
|
|
}
|
|
}
|
|
|
|
return open_slot;
|
|
}
|
|
|
|
void model_delete_instance(int model_instance_num)
|
|
{
|
|
Assert(model_instance_num >= 0);
|
|
Assert(model_instance_num < (int)Polygon_model_instances.size());
|
|
Assert(Polygon_model_instances[model_instance_num] != NULL);
|
|
|
|
polymodel_instance *pmi = Polygon_model_instances[model_instance_num];
|
|
|
|
if ( pmi->submodel ) {
|
|
vm_free(pmi->submodel);
|
|
}
|
|
|
|
vm_free(pmi);
|
|
|
|
Polygon_model_instances[model_instance_num] = NULL;
|
|
|
|
// delete intrinsic rotations associated with this instance
|
|
for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it) {
|
|
if (intrinsic_it->model_instance_num == model_instance_num) {
|
|
Intrinsic_rotations.erase(intrinsic_it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ensure that the subsys path is at least SUBSYS_PATH_DIST from the
|
|
// second last to last point.
|
|
void model_maybe_fixup_subsys_path(polymodel *pm, int path_num)
|
|
{
|
|
vec3d *v1, *v2, dir;
|
|
float dist;
|
|
int index_1, index_2;
|
|
|
|
Assert( (path_num >= 0) && (path_num < pm->n_paths) );
|
|
|
|
model_path *mp;
|
|
mp = &pm->paths[path_num];
|
|
|
|
Assert(mp != NULL);
|
|
if (mp->nverts <= 1 ) {
|
|
Error(LOCATION, "Subsystem Path (%s) Parent (%s) in model (%s) has less than 2 vertices/points!", mp->name, mp->parent_name, pm->filename);
|
|
}
|
|
|
|
index_1 = 1;
|
|
index_2 = 0;
|
|
|
|
v1 = &mp->verts[index_1].pos;
|
|
v2 = &mp->verts[index_2].pos;
|
|
|
|
dist = vm_vec_dist(v1, v2);
|
|
if (dist < (SUBSYS_PATH_DIST - 10))
|
|
{
|
|
vm_vec_normalized_dir(&dir, v2, v1);
|
|
vm_vec_scale_add(v2, v1, &dir, SUBSYS_PATH_DIST);
|
|
}
|
|
}
|
|
|
|
// fill in the path_num field inside the model_subsystem struct. This is an index into
|
|
// the pm->paths[] array, which is a path that provides a frontal approach to a subsystem
|
|
// (used for attacking purposes)
|
|
//
|
|
// NOTE: path_num in model_subsystem has the follows the following convention:
|
|
// > 0 => index into pm->paths[] for model that subsystem sits on
|
|
// -1 => path is not yet determined (may or may not exist)
|
|
// -2 => path doesn't yet exist for this subsystem
|
|
void model_set_subsys_path_nums(polymodel *pm, int n_subsystems, model_subsystem *subsystems)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < n_subsystems; i++)
|
|
subsystems[i].path_num = -1;
|
|
|
|
for (i = 0; i < n_subsystems; i++)
|
|
{
|
|
for (j = 0; j < pm->n_paths; j++)
|
|
{
|
|
if ( ((subsystems[i].subobj_num != -1) && (subsystems[i].subobj_num == pm->paths[j].parent_submodel)) ||
|
|
(!subsystem_stricmp(subsystems[i].subobj_name, pm->paths[j].parent_name)) )
|
|
{
|
|
if (pm->n_paths > j)
|
|
{
|
|
subsystems[i].path_num = j;
|
|
model_maybe_fixup_subsys_path(pm, j);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a path num wasn't located, then set value to -2
|
|
if (subsystems[i].path_num == -1)
|
|
subsystems[i].path_num = -2;
|
|
}
|
|
}
|
|
|
|
// Determine the path indices (indicies into pm->paths[]) for the paths used for approaching/departing
|
|
// a fighter bay on a capital ship.
|
|
void model_set_bay_path_nums(polymodel *pm)
|
|
{
|
|
int i;
|
|
|
|
if (pm->ship_bay != NULL)
|
|
{
|
|
vm_free(pm->ship_bay);
|
|
pm->ship_bay = NULL;
|
|
}
|
|
|
|
/*
|
|
// currently only capital ships have fighter bays
|
|
if ( !(sip->is_big_or_huge()) ) {
|
|
return;
|
|
}
|
|
*/
|
|
|
|
// malloc out storage for the path information
|
|
pm->ship_bay = (ship_bay *) vm_malloc(sizeof(ship_bay));
|
|
Assert(pm->ship_bay != NULL);
|
|
|
|
pm->ship_bay->num_paths = 0;
|
|
// TODO: determine if zeroing out here is affecting any earlier initializations
|
|
pm->ship_bay->arrive_flags = 0; // bitfield, set to 1 when that path number is reserved for an arrival
|
|
pm->ship_bay->depart_flags = 0; // bitfield, set to 1 when that path number is reserved for a departure
|
|
|
|
|
|
// iterate through the paths that exist in the polymodel, searching for $bayN pathnames
|
|
bool too_many_paths = false;
|
|
for (i = 0; i < pm->n_paths; i++)
|
|
{
|
|
if (!strnicmp(pm->paths[i].name, NOX("$bay"), 4))
|
|
{
|
|
int bay_num;
|
|
char temp[3];
|
|
|
|
strncpy(temp, pm->paths[i].name + 4, 2);
|
|
temp[2] = 0;
|
|
bay_num = atoi(temp);
|
|
|
|
if (bay_num < 1 || bay_num > MAX_SHIP_BAY_PATHS)
|
|
{
|
|
if(bay_num > MAX_SHIP_BAY_PATHS)
|
|
{
|
|
too_many_paths = true;
|
|
}
|
|
if(bay_num < 1)
|
|
{
|
|
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);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
pm->ship_bay->path_indexes[bay_num - 1] = i;
|
|
pm->ship_bay->num_paths++;
|
|
}
|
|
}
|
|
if(too_many_paths)
|
|
{
|
|
Warning(LOCATION, "Model '%s' has too many bay paths - max is %d", pm->filename, MAX_SHIP_BAY_PATHS);
|
|
}
|
|
}
|
|
|
|
// Get "parent" submodel for live debris submodel
|
|
int model_get_parent_submodel_for_live_debris( int model_num, int live_debris_model_num )
|
|
{
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
Assert(pm->submodel[live_debris_model_num].is_live_debris == 1);
|
|
|
|
int mn;
|
|
bsp_info *child;
|
|
|
|
// Start with the high level of detail hull
|
|
// Check all its children until we find the submodel to which the live debris belongs
|
|
child = &pm->submodel[pm->detail[0]];
|
|
mn = child->first_child;
|
|
|
|
while (mn > 0) {
|
|
child = &pm->submodel[mn];
|
|
|
|
if (child->num_live_debris > 0) {
|
|
// check all live debris submodels for the current child
|
|
for (int idx=0; idx<child->num_live_debris; idx++) {
|
|
if (child->live_debris[idx] == live_debris_model_num) {
|
|
return mn;
|
|
}
|
|
}
|
|
// DKA 5/26/99: can multiple live debris subsystems with each ship
|
|
// NO LONGER TRUE Can only be 1 submodel with live debris
|
|
// Error( LOCATION, "Could not find parent submodel for live debris. Possible model error");
|
|
}
|
|
|
|
// get next child
|
|
mn = child->next_sibling;
|
|
}
|
|
Error( LOCATION, "Could not find parent submodel for live debris");
|
|
return -1;
|
|
}
|
|
|
|
|
|
float model_get_radius( int modelnum )
|
|
{
|
|
polymodel *pm;
|
|
|
|
pm = model_get(modelnum);
|
|
|
|
return pm->rad;
|
|
}
|
|
|
|
float model_get_core_radius( int modelnum )
|
|
{
|
|
polymodel *pm;
|
|
|
|
pm = model_get(modelnum);
|
|
|
|
return pm->core_radius;
|
|
}
|
|
|
|
float submodel_get_radius( int modelnum, int submodelnum )
|
|
{
|
|
polymodel *pm;
|
|
|
|
pm = model_get(modelnum);
|
|
|
|
return pm->submodel[submodelnum].rad;
|
|
}
|
|
|
|
|
|
|
|
polymodel * model_get(int model_num)
|
|
{
|
|
if ( model_num < 0 ) {
|
|
Warning(LOCATION, "Invalid model number %d requested. Please post the call stack where an SCP coder can see it.\n", model_num);
|
|
return NULL;
|
|
}
|
|
|
|
int num = model_num % MAX_POLYGON_MODELS;
|
|
|
|
Assertion( num >= 0, "Model id %d is invalid. Please backtrace and investigate.\n", num);
|
|
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 );
|
|
Assertion( Polygon_models[num], "No model with id %d found. Please backtrace and investigate.\n", num );
|
|
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 );
|
|
|
|
if (num < 0 || num > MAX_POLYGON_MODELS || !Polygon_models[num] || Polygon_models[num]->id != model_num)
|
|
return NULL;
|
|
|
|
return Polygon_models[num];
|
|
}
|
|
|
|
polymodel_instance* model_get_instance(int model_instance_num)
|
|
{
|
|
Assert( model_instance_num >= 0 );
|
|
Assert( model_instance_num < (int)Polygon_model_instances.size() );
|
|
if ( model_instance_num < 0 || model_instance_num >= (int)Polygon_model_instances.size() ) {
|
|
return NULL;
|
|
}
|
|
|
|
return Polygon_model_instances[model_instance_num];
|
|
}
|
|
|
|
// Returns zero is x1,y1,x2,y2 are valid
|
|
// returns 1 for invalid model, 2 for point offscreen.
|
|
// note that x1,y1,x2,y2 aren't clipped to 2d screen coordinates!
|
|
int model_find_2d_bound_min(int model_num,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
|
|
{
|
|
polymodel * po;
|
|
int n_valid_pts;
|
|
int i, x,y,min_x, min_y, max_x, max_y;
|
|
int rval = 0;
|
|
|
|
po = model_get(model_num);
|
|
|
|
g3_start_instance_matrix(pos,orient,false);
|
|
|
|
n_valid_pts = 0;
|
|
|
|
int hull = po->detail[0];
|
|
|
|
min_x = min_y = max_x = max_y = 0;
|
|
|
|
for (i=0; i<8; i++ ) {
|
|
vertex pt;
|
|
ubyte flags;
|
|
|
|
flags = g3_rotate_vertex(&pt,&po->submodel[hull].bounding_box[i]);
|
|
if ( !(flags&CC_BEHIND) ) {
|
|
g3_project_vertex(&pt);
|
|
|
|
if (!(pt.flags & PF_OVERFLOW)) {
|
|
x = fl2i(pt.screen.xyw.x);
|
|
y = fl2i(pt.screen.xyw.y);
|
|
if ( n_valid_pts == 0 ) {
|
|
min_x = x;
|
|
min_y = y;
|
|
max_x = x;
|
|
max_y = y;
|
|
} else {
|
|
if ( x < min_x ) min_x = x;
|
|
if ( y < min_y ) min_y = y;
|
|
|
|
if ( x > max_x ) max_x = x;
|
|
if ( y > max_y ) max_y = y;
|
|
}
|
|
n_valid_pts++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( n_valid_pts < 8 ) {
|
|
rval = 2;
|
|
}
|
|
|
|
if (x1) *x1 = min_x;
|
|
if (y1) *y1 = min_y;
|
|
|
|
if (x2) *x2 = max_x;
|
|
if (y2) *y2 = max_y;
|
|
|
|
g3_done_instance(false);
|
|
|
|
return rval;
|
|
}
|
|
|
|
|
|
// Returns zero is x1,y1,x2,y2 are valid
|
|
// returns 1 for invalid model, 2 for point offscreen.
|
|
// note that x1,y1,x2,y2 aren't clipped to 2d screen coordinates!
|
|
int submodel_find_2d_bound_min(int model_num,int submodel, matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
|
|
{
|
|
polymodel * po;
|
|
int n_valid_pts;
|
|
int i, x,y,min_x, min_y, max_x, max_y;
|
|
bsp_info * sm;
|
|
|
|
po = model_get(model_num);
|
|
if ( (submodel < 0) || (submodel >= po->n_models ) ) return 1;
|
|
sm = &po->submodel[submodel];
|
|
|
|
g3_start_instance_matrix(pos,orient,false);
|
|
|
|
n_valid_pts = 0;
|
|
|
|
min_x = min_y = max_x = max_y = 0;
|
|
|
|
for (i=0; i<8; i++ ) {
|
|
vertex pt;
|
|
ubyte flags;
|
|
|
|
flags = g3_rotate_vertex(&pt,&sm->bounding_box[i]);
|
|
if ( !(flags&CC_BEHIND) ) {
|
|
g3_project_vertex(&pt);
|
|
|
|
if (!(pt.flags & PF_OVERFLOW)) {
|
|
x = fl2i(pt.screen.xyw.x);
|
|
y = fl2i(pt.screen.xyw.y);
|
|
if ( n_valid_pts == 0 ) {
|
|
min_x = x;
|
|
min_y = y;
|
|
max_x = x;
|
|
max_y = y;
|
|
} else {
|
|
if ( x < min_x ) min_x = x;
|
|
if ( y < min_y ) min_y = y;
|
|
|
|
if ( x > max_x ) max_x = x;
|
|
if ( y > max_y ) max_y = y;
|
|
}
|
|
n_valid_pts++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( n_valid_pts == 0 ) {
|
|
return 2;
|
|
}
|
|
|
|
if (x1) *x1 = min_x;
|
|
if (y1) *y1 = min_y;
|
|
|
|
if (x2) *x2 = max_x;
|
|
if (y2) *y2 = max_y;
|
|
|
|
g3_done_instance(false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find 2D bound for model
|
|
*
|
|
* Note that x1,y1,x2,y2 aren't clipped to 2D screen coordinates.
|
|
*
|
|
* @return zero if x1,y1,x2,y2 are valid
|
|
* @return 2 for point offscreen
|
|
*/
|
|
int model_find_2d_bound(int model_num,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
|
|
{
|
|
float t,w,h;
|
|
vertex pnt;
|
|
polymodel * po;
|
|
|
|
po = model_get(model_num);
|
|
float width = po->rad;
|
|
float height = po->rad;
|
|
|
|
g3_rotate_vertex(&pnt,pos);
|
|
|
|
if ( pnt.flags & CC_BEHIND )
|
|
return 2;
|
|
|
|
if (!(pnt.flags&PF_PROJECTED))
|
|
g3_project_vertex(&pnt);
|
|
|
|
if (pnt.flags & PF_OVERFLOW)
|
|
return 2;
|
|
|
|
t = (width * Canv_w2)/pnt.world.xyz.z;
|
|
w = t*Matrix_scale.xyz.x;
|
|
|
|
t = (height*Canv_h2)/pnt.world.xyz.z;
|
|
h = t*Matrix_scale.xyz.y;
|
|
|
|
if (x1) *x1 = fl2i(pnt.screen.xyw.x - w);
|
|
if (y1) *y1 = fl2i(pnt.screen.xyw.y - h);
|
|
|
|
if (x2) *x2 = fl2i(pnt.screen.xyw.x + w);
|
|
if (y2) *y2 = fl2i(pnt.screen.xyw.y + h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Find 2D bound for sub object
|
|
*
|
|
* Note that x1,y1,x2,y2 aren't clipped to 2D screen coordinates.
|
|
*
|
|
* @return zero if x1,y1,x2,y2 are valid
|
|
* @return 2 for point offscreen
|
|
*/
|
|
int subobj_find_2d_bound(float radius ,matrix *orient, vec3d * pos,int *x1, int *y1, int *x2, int *y2 )
|
|
{
|
|
float t,w,h;
|
|
vertex pnt;
|
|
|
|
float width = radius;
|
|
float height = radius;
|
|
|
|
g3_rotate_vertex(&pnt,pos);
|
|
|
|
if ( pnt.flags & CC_BEHIND )
|
|
return 2;
|
|
|
|
if (!(pnt.flags&PF_PROJECTED))
|
|
g3_project_vertex(&pnt);
|
|
|
|
if (pnt.flags & PF_OVERFLOW)
|
|
return 2;
|
|
|
|
t = (width * Canv_w2)/pnt.world.xyz.z;
|
|
w = t*Matrix_scale.xyz.x;
|
|
|
|
t = (height*Canv_h2)/pnt.world.xyz.z;
|
|
h = t*Matrix_scale.xyz.y;
|
|
|
|
if (x1) *x1 = fl2i(pnt.screen.xyw.x - w);
|
|
if (y1) *y1 = fl2i(pnt.screen.xyw.y - h);
|
|
|
|
if (x2) *x2 = fl2i(pnt.screen.xyw.x + w);
|
|
if (y2) *y2 = fl2i(pnt.screen.xyw.y + h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Given a vector that is in sub_model_num's frame of
|
|
// reference, and given the object's orient and position,
|
|
// return the vector in the model's frame of reference.
|
|
void model_find_obj_dir(vec3d *w_vec, vec3d *m_vec, int model_num, int submodel_num, matrix *objorient)
|
|
{
|
|
vec3d tvec, vec;
|
|
matrix m;
|
|
int mn;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
vec = *m_vec;
|
|
mn = submodel_num;
|
|
|
|
// instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_unrotate(&tvec, &vec, &m);
|
|
vec = tvec;
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
// now instance for the entire object
|
|
vm_vec_unrotate(w_vec, &vec, objorient);
|
|
}
|
|
|
|
void model_instance_find_obj_dir(vec3d *w_vec, vec3d *m_vec, int model_instance_num, int submodel_num, matrix *objorient)
|
|
{
|
|
vec3d tvec, vec;
|
|
matrix m;
|
|
int mn;
|
|
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
vec = *m_vec;
|
|
mn = submodel_num;
|
|
|
|
// instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[mn].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_unrotate(&tvec, &vec, &m);
|
|
vec = tvec;
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
// now instance for the entire object
|
|
vm_vec_unrotate(w_vec, &vec, objorient);
|
|
}
|
|
|
|
|
|
// Given a rotating submodel, find the ship and world axes of rotation.
|
|
void model_get_rotating_submodel_axis(vec3d *model_axis, vec3d *world_axis, int model_instance_num, int submodel_num, matrix *objorient)
|
|
{
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
bsp_info *sm = &pm->submodel[submodel_num];
|
|
Assert(sm->movement_type == MOVEMENT_TYPE_ROT || sm->movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE);
|
|
|
|
if (sm->movement_axis == MOVEMENT_AXIS_X) {
|
|
vm_vec_make(model_axis, 1.0f, 0.0f, 0.0f);
|
|
} else if (sm->movement_axis == MOVEMENT_AXIS_Y) {
|
|
vm_vec_make(model_axis, 0.0f, 1.0f, 0.0f);
|
|
} else {
|
|
Assert(sm->movement_axis == MOVEMENT_AXIS_Z);
|
|
vm_vec_make(model_axis, 0.0f, 0.0f, 1.0f);
|
|
}
|
|
|
|
model_instance_find_obj_dir(world_axis, model_axis, model_instance_num, submodel_num, objorient);
|
|
}
|
|
|
|
|
|
// Does stepped rotation of a submodel
|
|
void submodel_stepped_rotate(model_subsystem *psub, submodel_instance_info *sii)
|
|
{
|
|
Assert(psub->flags[Model::Subsystem_Flags::Stepped_rotate]);
|
|
|
|
if ( psub->subobj_num < 0 ) return;
|
|
|
|
polymodel *pm = model_get(psub->model_num);
|
|
bsp_info *sm = &pm->submodel[psub->subobj_num];
|
|
|
|
if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
|
|
|
|
// get active rotation time this frame
|
|
int end_stamp = timestamp();
|
|
// just to make sure this issue wont pop up again... might cause odd jerking in some extremely odd situations
|
|
// but given that those issues would require the timer to be reseted in any case it probably wont hurt
|
|
float rotation_time;
|
|
if ((end_stamp - sii->step_zero_timestamp) < 0) {
|
|
sii->step_zero_timestamp = end_stamp;
|
|
rotation_time = 0.0f;
|
|
} else {
|
|
rotation_time = 0.001f * (end_stamp - sii->step_zero_timestamp);
|
|
}
|
|
//Assert(rotation_time >= 0);
|
|
|
|
// save last angles
|
|
sii->prev_angs = sii->angs;
|
|
|
|
// float pointer into struct to get angle (either p,b,h)
|
|
float *ang_prev = NULL, *ang_next = NULL;
|
|
switch( sm->movement_axis ) {
|
|
case MOVEMENT_AXIS_X:
|
|
ang_prev = &sii->prev_angs.p;
|
|
ang_next = &sii->angs.p;
|
|
break;
|
|
|
|
case MOVEMENT_AXIS_Y:
|
|
ang_prev = &sii->prev_angs.h;
|
|
ang_next = &sii->angs.h;
|
|
break;
|
|
|
|
case MOVEMENT_AXIS_Z:
|
|
ang_prev = &sii->prev_angs.b;
|
|
ang_next = &sii->angs.b;
|
|
break;
|
|
}
|
|
|
|
// just in case we got through that switch statement in error
|
|
if ( (ang_prev == NULL) && (ang_next == NULL) )
|
|
return;
|
|
|
|
// angular displacement of one step
|
|
float step_size = (PI2 / psub->stepped_rotation->num_steps);
|
|
|
|
// get time to complete one step, including pause
|
|
float step_time = psub->stepped_rotation->t_transit + psub->stepped_rotation->t_pause;
|
|
|
|
// cur_step is step number relative to zero (0 - num_steps)
|
|
// step_offset_time is TIME into current step
|
|
float step_offset_time = (float)fmod(rotation_time, step_time);
|
|
// subtract off fractional step part, round up (ie, 1.999999 -> 2)
|
|
int cur_step = int( ((rotation_time - step_offset_time) / step_time) + 0.5f);
|
|
// mprintf(("cur step %d\n", cur_step));
|
|
// Assert(step_offset_time >= 0);
|
|
|
|
if (cur_step >= psub->stepped_rotation->num_steps) {
|
|
// I don;t know why, but removing this line makes it all good.
|
|
// sii->step_zero_timestamp += int(1000.0f * (psub->stepped_rotation->num_steps * step_time) + 0.5f);
|
|
|
|
// reset cur_step (use mod to handle physics/ai pause)
|
|
cur_step = cur_step % psub->stepped_rotation->num_steps;
|
|
}
|
|
|
|
// get base angle
|
|
*ang_next = cur_step * step_size;
|
|
|
|
// determine which phase of rotation we're in
|
|
float coast_start_time = psub->stepped_rotation->fraction * psub->stepped_rotation->t_transit;
|
|
float decel_start_time = psub->stepped_rotation->t_transit * (1.0f - psub->stepped_rotation->fraction);
|
|
float pause_start_time = psub->stepped_rotation->t_transit;
|
|
|
|
float start_coast_angle = 0.5f * psub->stepped_rotation->max_turn_accel * coast_start_time * coast_start_time;
|
|
|
|
if (step_offset_time < coast_start_time) {
|
|
// do accel
|
|
float accel_time = step_offset_time;
|
|
*ang_next += 0.5f * psub->stepped_rotation->max_turn_accel * accel_time * accel_time;
|
|
sii->cur_turn_rate = psub->stepped_rotation->max_turn_accel * accel_time;
|
|
} else if (step_offset_time < decel_start_time) {
|
|
// do coast
|
|
float coast_time = step_offset_time - coast_start_time;
|
|
*ang_next += start_coast_angle + psub->stepped_rotation->max_turn_rate * coast_time;
|
|
sii->cur_turn_rate = psub->stepped_rotation->max_turn_rate;
|
|
} else if (step_offset_time < pause_start_time) {
|
|
// do decel
|
|
float time_to_pause = psub->stepped_rotation->t_transit - step_offset_time;
|
|
*ang_next += (step_size - 0.5f * psub->stepped_rotation->max_turn_accel * time_to_pause * time_to_pause);
|
|
sii->cur_turn_rate = psub->stepped_rotation->max_turn_rate * time_to_pause;
|
|
} else {
|
|
// do pause
|
|
*ang_next += step_size;
|
|
sii->cur_turn_rate = 0.0f;
|
|
}
|
|
}
|
|
|
|
void world_find_real_model_point(vec3d *out, vec3d *world_pt, polymodel *pm, int submodel_num, matrix *orient, vec3d *pos);
|
|
|
|
void submodel_look_at(polymodel *pm, int mn)
|
|
{
|
|
bsp_info * sm;
|
|
|
|
if ( mn < 0 ) {
|
|
return;
|
|
}
|
|
|
|
sm = &pm->submodel[mn];
|
|
angles *angs = &pm->submodel[mn].angs;
|
|
|
|
if ( sm->movement_type != MOVEMENT_TYPE_LOOK_AT ) {
|
|
return;
|
|
}
|
|
|
|
vec3d other, mp;
|
|
|
|
int pmn = pm->id;
|
|
|
|
// 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
|
|
if (sm->look_at_num == -2) {
|
|
// Search through submodels for the look_at target name
|
|
for (int i = 0; i < pm->n_models; i++) {
|
|
if (!strcmp(sm->look_at, pm->submodel[i].name)) {
|
|
sm->look_at_num = i; // Found it
|
|
nprintf(("Model", "NOTE: Matched $look_at: target <%s> with subobject id %d\n", sm->look_at, i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sm->look_at_num == -2) {
|
|
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 );
|
|
sm->look_at_num = -1; // Set to -1 to not break stuff
|
|
}
|
|
}
|
|
|
|
model_find_world_point(&mp, &vmd_zero_vector, pmn, sm->look_at_num, &vmd_identity_matrix, &vmd_zero_vector);
|
|
world_find_real_model_point(&other, &mp, pm, mn, &vmd_identity_matrix, &vmd_zero_vector);
|
|
|
|
if (!IS_MAT_NULL(&pm->submodel[mn].orientation)) {
|
|
vm_vec_rotate(&mp, &other, &pm->submodel[mn].orientation);
|
|
} else {
|
|
mp = other;
|
|
}
|
|
|
|
vec3d d, l;
|
|
model_find_submodel_offset(&d, pmn, mn);
|
|
model_find_submodel_offset(&l, pmn, sm->look_at_num);
|
|
vm_vec_sub(&other, &l, &d);
|
|
|
|
if (!IS_MAT_NULL(&pm->submodel[mn].orientation)) {
|
|
vm_vec_rotate(&l, &other, &pm->submodel[mn].orientation);
|
|
} else {
|
|
l = other;
|
|
}
|
|
|
|
float *a;
|
|
int axis;
|
|
|
|
switch( sm->movement_axis ) {
|
|
default:
|
|
case MOVEMENT_AXIS_X:
|
|
l.xyz.x = 0;
|
|
mp.xyz.x = 0;
|
|
a = &angs->p;
|
|
axis = 0;
|
|
break;
|
|
|
|
case MOVEMENT_AXIS_Y:
|
|
l.xyz.y = 0;
|
|
mp.xyz.y = 0;
|
|
a = &angs->h;
|
|
axis = 1;
|
|
break;
|
|
|
|
case MOVEMENT_AXIS_Z:
|
|
l.xyz.z = 0;
|
|
mp.xyz.z = 0;
|
|
a = &angs->b;
|
|
axis = 2;
|
|
break;
|
|
}
|
|
|
|
vm_vec_normalize(&mp);
|
|
vm_vec_normalize(&l);
|
|
|
|
vec3d c;
|
|
vm_vec_cross(&c, &l, &mp);
|
|
float dot=vm_vec_dot(&l,&mp);
|
|
if (dot>=0.0f) {
|
|
*a = asinf(c.a1d[axis]);
|
|
} else {
|
|
*a = PI-asinf(c.a1d[axis]);
|
|
}
|
|
|
|
if (*a > PI2 ) {
|
|
*a -= PI2;
|
|
} else { if (*a < 0.0f )
|
|
*a += PI2;
|
|
}
|
|
|
|
for (int k=0; k<sm->num_details; k++ ) {
|
|
pm->submodel[sm->details[k]].angs = *angs;
|
|
}
|
|
|
|
}
|
|
|
|
// Rotates the angle of a submodel, when the submodel has a subsystem (which is almost always the case)
|
|
void submodel_rotate(model_subsystem *psub, submodel_instance_info *sii)
|
|
{
|
|
bsp_info * sm;
|
|
|
|
if ( psub->subobj_num < 0 ) return;
|
|
|
|
polymodel *pm = model_get(psub->model_num);
|
|
sm = &pm->submodel[psub->subobj_num];
|
|
|
|
if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
|
|
|
|
submodel_rotate(sm, sii);
|
|
}
|
|
|
|
// Rotates the angle of a submodel. If the submodel has a subsystem, the execution flow should first go through the other
|
|
// submodel_rotate function before this one. (This function is called directly in the case of dumb_rotation.)
|
|
void submodel_rotate(bsp_info *sm, submodel_instance_info *sii)
|
|
{
|
|
// save last angles
|
|
sii->prev_angs = sii->angs;
|
|
|
|
// probably send in a calculated desired turn rate
|
|
float diff = sii->desired_turn_rate - sii->cur_turn_rate;
|
|
|
|
float final_turn_rate;
|
|
if (diff > 0) {
|
|
final_turn_rate = sii->cur_turn_rate + sii->turn_accel * flFrametime;
|
|
if (final_turn_rate > sii->desired_turn_rate) {
|
|
final_turn_rate = sii->desired_turn_rate;
|
|
}
|
|
} else if (diff < 0) {
|
|
final_turn_rate = sii->cur_turn_rate - sii->turn_accel * flFrametime;
|
|
if (final_turn_rate < sii->desired_turn_rate) {
|
|
final_turn_rate = sii->desired_turn_rate;
|
|
}
|
|
} else {
|
|
final_turn_rate = sii->desired_turn_rate;
|
|
}
|
|
|
|
float delta = (sii->cur_turn_rate + final_turn_rate) * 0.5f * flFrametime;
|
|
sii->cur_turn_rate = final_turn_rate;
|
|
|
|
// Apply rotation in the axis of movement
|
|
// then normalize the angle angle so that we are within a valid range:
|
|
// greater than or equal to 0
|
|
// less than PI2
|
|
switch( sm->movement_axis ) {
|
|
case MOVEMENT_AXIS_X:
|
|
sii->angs.p += delta;
|
|
|
|
while (sii->angs.p > PI2)
|
|
sii->angs.p -= PI2;
|
|
while (sii->angs.p < 0.0f)
|
|
sii->angs.p += PI2;
|
|
|
|
break;
|
|
case MOVEMENT_AXIS_Y:
|
|
sii->angs.h += delta;
|
|
|
|
while (sii->angs.h > PI2)
|
|
sii->angs.h -= PI2;
|
|
while (sii->angs.h < 0.0f)
|
|
sii->angs.h += PI2;
|
|
|
|
break;
|
|
case MOVEMENT_AXIS_Z:
|
|
sii->angs.b += delta;
|
|
|
|
while (sii->angs.b > PI2)
|
|
sii->angs.b -= PI2;
|
|
while (sii->angs.b < 0.0f)
|
|
sii->angs.b += PI2;
|
|
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
void submodel_ai_rotate(model_subsystem *psub, submodel_instance_info *sii)
|
|
{
|
|
bsp_info * sm;
|
|
|
|
if ( psub->subobj_num < 0 ) return;
|
|
if(psub->ai_rotation.type = 0) return;
|
|
|
|
polymodel *pm = model_get(psub->model_num);
|
|
sm = &pm->submodel[psub->subobj_num];
|
|
|
|
if ( sm->movement_type != MOVEMENT_TYPE_ROT ) return;
|
|
|
|
|
|
// save last angles
|
|
sii->prev_angs = sii->angs;
|
|
|
|
// probably send in a calculated desired turn rate
|
|
float diff = sii->desired_turn_rate - sii->cur_turn_rate;
|
|
|
|
float final_turn_rate;
|
|
if (diff > 0) {
|
|
final_turn_rate = sii->cur_turn_rate + sii->turn_accel * flFrametime;
|
|
if (final_turn_rate > sii->desired_turn_rate) {
|
|
final_turn_rate = sii->desired_turn_rate;
|
|
}
|
|
} else if (diff < 0) {
|
|
final_turn_rate = sii->cur_turn_rate - sii->turn_accel * flFrametime;
|
|
if (final_turn_rate < sii->desired_turn_rate) {
|
|
final_turn_rate = sii->desired_turn_rate;
|
|
}
|
|
} else {
|
|
final_turn_rate = sii->desired_turn_rate;
|
|
}
|
|
|
|
float delta = (sii->cur_turn_rate + final_turn_rate) * 0.5f * flFrametime;
|
|
sii->cur_turn_rate = final_turn_rate;
|
|
|
|
|
|
//float delta = psub->turn_rate * flFrametime;
|
|
|
|
switch( sm->movement_axis ) {
|
|
case MOVEMENT_AXIS_X:
|
|
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
|
|
sii->angs.p = psub->ai_rotation.max;
|
|
return;
|
|
} else if(sii->angs.p + delta < psub->ai_rotation.min){
|
|
sii->angs.p = psub->ai_rotation.min;
|
|
return;
|
|
}
|
|
sii->angs.p += delta;
|
|
if (sii->angs.p > PI2 )
|
|
sii->angs.p -= PI2;
|
|
else if (sii->angs.p < 0.0f )
|
|
sii->angs.p += PI2;
|
|
break;
|
|
case MOVEMENT_AXIS_Y:
|
|
sii->angs.h += delta;
|
|
if (sii->angs.h > PI2 )
|
|
sii->angs.h -= PI2;
|
|
else if (sii->angs.h < 0.0f )
|
|
sii->angs.h += PI2;
|
|
break;
|
|
case MOVEMENT_AXIS_Z:
|
|
sii->angs.b += delta;
|
|
if (sii->angs.b > PI2 )
|
|
sii->angs.b -= PI2;
|
|
else if (sii->angs.b < 0.0f )
|
|
sii->angs.b += PI2;
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
//=========================================================================
|
|
// Make a turret's correct orientation matrix. This should be done when
|
|
// the model is read, but I wasn't sure at what point all the data that I
|
|
// needed was read, so I just check a flag and call this routine when
|
|
// I determine I need the correct matrix. In this code, you can't use
|
|
// vm_vec_2_matrix or anything, since these turrets could be either
|
|
// right handed or left handed.
|
|
void model_make_turret_matrix(int model_num, model_subsystem * turret )
|
|
{
|
|
polymodel * pm;
|
|
vec3d fvec, uvec, rvec;
|
|
|
|
pm = model_get(model_num);
|
|
bsp_info * gun = &pm->submodel[turret->turret_gun_sobj];
|
|
bsp_info * base = &pm->submodel[turret->subobj_num];
|
|
float offset_base_h = 0.0f;
|
|
float offset_barrel_h = 0.0f;
|
|
#ifdef WMC_SIDE_TURRETS
|
|
offset_base_h = -PI_2;
|
|
offset_barrel_h = -PI_2;
|
|
#endif
|
|
|
|
if (base->force_turret_normal == true)
|
|
turret->turret_norm = base->orientation.vec.uvec;
|
|
|
|
model_clear_instance(model_num);
|
|
base->angs.h = offset_base_h;
|
|
gun->angs.h = offset_barrel_h;
|
|
model_find_world_dir(&fvec, &turret->turret_norm, model_num, turret->turret_gun_sobj, &vmd_identity_matrix);
|
|
|
|
base->angs.h = -PI_2 + offset_base_h;
|
|
gun->angs.p = -PI_2;
|
|
gun->angs.h = offset_barrel_h;
|
|
model_find_world_dir(&rvec, &turret->turret_norm, model_num, turret->turret_gun_sobj, &vmd_identity_matrix);
|
|
|
|
base->angs.h = 0.0f + offset_base_h;
|
|
gun->angs.p = -PI_2;
|
|
gun->angs.h = offset_barrel_h;
|
|
model_find_world_dir(&uvec, &turret->turret_norm, model_num, turret->turret_gun_sobj, &vmd_identity_matrix);
|
|
|
|
vm_vec_normalize(&fvec);
|
|
vm_vec_normalize(&rvec);
|
|
vm_vec_normalize(&uvec);
|
|
|
|
turret->turret_matrix.vec.fvec = fvec;
|
|
turret->turret_matrix.vec.rvec = rvec;
|
|
turret->turret_matrix.vec.uvec = uvec;
|
|
|
|
// vm_vector_2_matrix(&turret->turret_matrix,&turret->turret_norm,NULL,NULL);
|
|
|
|
// HACK!! WARNING!!!
|
|
// I'm doing nothing to verify that this matrix is orthogonal!!
|
|
// In other words, there's no guarantee that the vectors are 90 degrees
|
|
// from each other.
|
|
// I'm not doing this because I don't know how to do it without ruining
|
|
// the handedness of the matrix... however, I'm not too worried about
|
|
// this because I am creating these 3 vectors by making them 90 degrees
|
|
// apart, so this should be close enough. I think this will start
|
|
// causing weird errors when we view from turrets. -John
|
|
turret->flags.set(Model::Subsystem_Flags::Turret_matrix);
|
|
}
|
|
|
|
// Tries to move joints so that the turret points to the point dst.
|
|
// turret1 is the angles of the turret, turret2 is the angles of the gun from turret
|
|
// Returns 1 if rotated gun, 0 if no gun to rotate (rotation handled by AI)
|
|
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)
|
|
{
|
|
polymodel * pm;
|
|
object *objp = &Objects[obj_idx];
|
|
ship *shipp = &Ships[objp->instance];
|
|
ship_subsys *ss = ship_get_subsys(shipp, turret->subobj_name);
|
|
|
|
pm = model_get(model_num);
|
|
bsp_info * gun = &pm->submodel[turret->turret_gun_sobj];
|
|
bsp_info * base = &pm->submodel[turret->subobj_num];
|
|
bool limited_base_rotation = false;
|
|
|
|
// Check for a valid turret
|
|
Assert( turret->turret_num_firing_points > 0 );
|
|
// Check for a valid subsystem
|
|
Assert( ss != NULL );
|
|
|
|
//This should not happen
|
|
if ( base == gun ) {
|
|
return 0;
|
|
}
|
|
|
|
// Build the correct turret matrix if there isn't already one
|
|
if ( !(turret->flags[Model::Subsystem_Flags::Turret_matrix]) )
|
|
model_make_turret_matrix(model_num, turret );
|
|
|
|
Assert( turret->flags[Model::Subsystem_Flags::Turret_matrix]);
|
|
// Assert( gun->movement_axis == MOVEMENT_AXIS_X ); // Gun must be able to change pitch
|
|
// Assert( base->movement_axis == MOVEMENT_AXIS_Z ); // Parent must be able to change heading
|
|
|
|
//------------
|
|
// rotate the dest point into the turret gun normal's frame of
|
|
// reference, but not using the turret's angles.
|
|
// Call this vector of_dst
|
|
vec3d of_dst;
|
|
matrix world_to_turret_matrix; // converts world coordinates to turret's FOR
|
|
vec3d world_to_turret_translate; // converts world coordinates to turret's FOR
|
|
vec3d tempv;
|
|
|
|
vm_vec_unrotate( &tempv, &base->offset, orient);
|
|
vm_vec_add( &world_to_turret_translate, pos, &tempv );
|
|
|
|
if (turret->flags[Model::Subsystem_Flags::Turret_alt_math])
|
|
world_to_turret_matrix = ss->world_to_turret_matrix;
|
|
else
|
|
vm_matrix_x_matrix( &world_to_turret_matrix, orient, &turret->turret_matrix );
|
|
|
|
vm_vec_sub( &tempv, dst, &world_to_turret_translate );
|
|
vm_vec_rotate( &of_dst, &tempv, &world_to_turret_matrix );
|
|
|
|
vm_vec_normalize(&of_dst);
|
|
|
|
//------------
|
|
// Find the heading and pitch that the gun needs to turn to
|
|
// by extracting them from the of_dst vector.
|
|
// Call this the desired_angles
|
|
angles desired_angles;
|
|
// vm_extract_angles_vector(&desired_angles, &of_dst);
|
|
|
|
if (reset == false) {
|
|
desired_angles.p = acosf(of_dst.xyz.z);
|
|
desired_angles.h = PI - atan2_safe(of_dst.xyz.x, of_dst.xyz.y);
|
|
desired_angles.b = 0.0f;
|
|
} else {
|
|
desired_angles.p = 0.0f;
|
|
desired_angles.h = 0.0f;
|
|
desired_angles.b = 0.0f;
|
|
if (turret->n_triggers > 0) {
|
|
int i;
|
|
for (i = 0; i<turret->n_triggers; i++) {
|
|
desired_angles.p = turret->triggers[i].angle.xyz.x;
|
|
desired_angles.h = turret->triggers[i].angle.xyz.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (turret->flags[Model::Subsystem_Flags::Turret_alt_math])
|
|
limited_base_rotation = true;
|
|
|
|
// mprintf(( "Z = %.1f, atan= %.1f\n", of_dst.xyz.z, desired_angles.p ));
|
|
|
|
//------------
|
|
// Gradually turn the turret towards the desired angles
|
|
float step_size = turret->turret_turning_rate * flFrametime;
|
|
float base_delta, gun_delta;
|
|
|
|
if (reset == true)
|
|
step_size /= 3.0f;
|
|
else
|
|
ss->rotation_timestamp = timestamp(turret->turret_reset_delay);
|
|
|
|
// reset these two
|
|
ss->base_rotation_rate_pct = 0.0f;
|
|
ss->gun_rotation_rate_pct = 0.0f;
|
|
|
|
base_delta = vm_interp_angle(&base_angles->h, desired_angles.h, step_size, limited_base_rotation);
|
|
gun_delta = vm_interp_angle(&gun_angles->p, desired_angles.p, step_size);
|
|
|
|
if (turret->turret_base_rotation_snd != -1)
|
|
{
|
|
if (step_size > 0)
|
|
{
|
|
base_delta = (float) (fabs(base_delta)) / step_size;
|
|
if (base_delta > 1.0f)
|
|
base_delta = 1.0f;
|
|
ss->base_rotation_rate_pct = base_delta;
|
|
}
|
|
}
|
|
|
|
if (turret->turret_gun_rotation_snd != -1)
|
|
{
|
|
if (step_size > 0)
|
|
{
|
|
gun_delta = (float) (fabs(gun_delta)) / step_size;
|
|
if (gun_delta > 1.0f)
|
|
gun_delta = 1.0f;
|
|
ss->gun_rotation_rate_pct = gun_delta;
|
|
}
|
|
}
|
|
|
|
// base_angles->h -= step_size*(key_down_timef(KEY_1)-key_down_timef(KEY_2) );
|
|
// gun_angles->p += step_size*(key_down_timef(KEY_3)-key_down_timef(KEY_4) );
|
|
|
|
if (turret->flags[Model::Subsystem_Flags::Fire_on_target])
|
|
{
|
|
base_delta = vm_delta_from_interp_angle( base_angles->h, desired_angles.h );
|
|
gun_delta = vm_delta_from_interp_angle( gun_angles->p, desired_angles.p );
|
|
ss->points_to_target = sqrt( pow(base_delta,2) + pow(gun_delta,2));
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
// Goober5000
|
|
// For a submodel, return its overall offset from the main model.
|
|
void model_find_submodel_offset(vec3d *outpnt, int model_num, int sub_model_num)
|
|
{
|
|
int mn;
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
vm_vec_zero(outpnt);
|
|
mn = sub_model_num;
|
|
|
|
//instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
vm_vec_add2(outpnt, &pm->submodel[mn].offset);
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
}
|
|
|
|
void make_submodel_world_matrix(polymodel *pm, int sn, vec3d*v){
|
|
if (pm->submodel[sn].parent != -1) {
|
|
make_submodel_world_matrix(pm,pm->submodel[sn].parent,v);
|
|
}
|
|
|
|
vm_vec_sub2(v, &pm->submodel[sn].offset);
|
|
vec3d t = *v;
|
|
|
|
matrix a;
|
|
vm_angles_2_matrix(&a, &pm->submodel[sn].angs);
|
|
if (!IS_MAT_NULL(&pm->submodel[sn].orientation)) {
|
|
matrix inv, f;
|
|
vm_copy_transpose(&inv, &pm->submodel[sn].orientation);
|
|
vm_matrix_x_matrix(&f, &a, &inv);
|
|
vm_matrix_x_matrix(&a, &pm->submodel[sn].orientation, &f);
|
|
}
|
|
vm_vec_rotate(v, &t, &a);
|
|
}
|
|
|
|
// just like below, exept it actualy does what it says it does
|
|
void world_find_real_model_point(vec3d *out, vec3d *world_pt, polymodel *pm, int submodel_num, matrix *orient, vec3d *pos)
|
|
{
|
|
vec3d tempv1, tempv2;
|
|
|
|
// get into ship RF
|
|
vm_vec_sub(&tempv1, world_pt, pos);
|
|
vm_vec_rotate(&tempv2, &tempv1, orient);
|
|
|
|
if (pm->submodel[submodel_num].parent == -1) {
|
|
*out = tempv2;
|
|
return;
|
|
}
|
|
|
|
//vec3d os = ZERO_VECTOR;
|
|
// put into submodel RF
|
|
make_submodel_world_matrix(pm,submodel_num, &tempv2);
|
|
*out = tempv2;
|
|
}
|
|
|
|
// Given a point (pnt) that is in submodel_num's frame of
|
|
// reference, and given the object's orient and position,
|
|
// return the point in 3-space in outpnt.
|
|
void model_find_world_point(vec3d *outpnt, vec3d *mpnt, int model_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
|
|
{
|
|
vec3d pnt;
|
|
vec3d tpnt;
|
|
matrix m;
|
|
int mn;
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
pnt = *mpnt;
|
|
mn = submodel_num;
|
|
|
|
//instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_unrotate(&tpnt, &pnt, &m);
|
|
|
|
vm_vec_add(&pnt, &tpnt, &pm->submodel[mn].offset);
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
//now instance for the entire object
|
|
vm_vec_unrotate(outpnt,&pnt,objorient);
|
|
vm_vec_add2(outpnt,objpos);
|
|
}
|
|
|
|
void model_instance_find_world_point(vec3d *outpnt, vec3d *mpnt, int model_instance_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
|
|
{
|
|
vec3d pnt;
|
|
vec3d tpnt;
|
|
matrix m;
|
|
int mn;
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
pnt = *mpnt;
|
|
mn = submodel_num;
|
|
|
|
//instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[mn].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_unrotate(&tpnt, &pnt, &m);
|
|
|
|
vm_vec_add(&pnt, &tpnt, &pm->submodel[mn].offset);
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
//now instance for the entire object
|
|
vm_vec_unrotate(outpnt,&pnt,objorient);
|
|
vm_vec_add2(outpnt,objpos);
|
|
}
|
|
|
|
// Given a point in the world RF, find the corresponding point in the model RF.
|
|
// This is special purpose code, specific for model collision.
|
|
// NOTE - this code ASSUMES submodel is 1 level down from hull (detail[0])
|
|
//
|
|
// out - point in model RF
|
|
// world_pt - point in world RF
|
|
// pm - polygon model
|
|
// submodel_num - submodel in whose RF we're trying to find the corresponding world point
|
|
// orient - orient matrix of ship
|
|
// pos - pos vector of ship
|
|
void world_find_model_point(vec3d *out, vec3d *world_pt, const polymodel *pm, int submodel_num, const matrix *orient, const vec3d *pos)
|
|
{
|
|
Assert( (pm->submodel[submodel_num].parent == pm->detail[0]) || (pm->submodel[submodel_num].parent == -1) );
|
|
|
|
vec3d tempv1, tempv2;
|
|
matrix m;
|
|
|
|
// get into ship RF
|
|
vm_vec_sub(&tempv1, world_pt, pos);
|
|
vm_vec_rotate(&tempv2, &tempv1, orient);
|
|
|
|
if (pm->submodel[submodel_num].parent == -1) {
|
|
*out = tempv2;
|
|
return;
|
|
}
|
|
|
|
// put into submodel RF
|
|
vm_vec_sub2(&tempv2, &pm->submodel[submodel_num].offset);
|
|
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[submodel_num].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[submodel_num].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_rotate(out, &tempv2, &m);
|
|
}
|
|
|
|
void world_find_model_instance_point(vec3d *out, vec3d *world_pt, const polymodel_instance *pmi, int submodel_num, const matrix *orient, const vec3d *pos)
|
|
{
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
Assert( (pm->submodel[submodel_num].parent == pm->detail[0]) || (pm->submodel[submodel_num].parent == -1) );
|
|
|
|
vec3d tempv1, tempv2;
|
|
matrix m;
|
|
|
|
// get into ship RF
|
|
vm_vec_sub(&tempv1, world_pt, pos);
|
|
vm_vec_rotate(&tempv2, &tempv1, orient);
|
|
|
|
if (pm->submodel[submodel_num].parent == -1) {
|
|
*out = tempv2;
|
|
return;
|
|
}
|
|
|
|
// put into submodel RF
|
|
vm_vec_sub2(&tempv2, &pm->submodel[submodel_num].offset);
|
|
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[submodel_num].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[submodel_num].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_rotate(out, &tempv2, &m);
|
|
}
|
|
|
|
/**
|
|
* Finds the current location of a submodel (in the ship's frame of reference),
|
|
* taking into account the rotations of any parent submodels it might have.
|
|
*
|
|
* @param *outpnt Output point
|
|
* @param model_instance_num Index into Polygon_model_instances
|
|
* @param submodel_num The number of the submodel we're interested in
|
|
*/
|
|
void find_submodel_instance_point(vec3d *outpnt, int model_instance_num, int submodel_num)
|
|
{
|
|
vm_vec_zero(outpnt);
|
|
matrix submodel_instance_matrix, rotation_matrix, inv_orientation;
|
|
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
int mn = submodel_num;
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
vec3d offset = pm->submodel[mn].offset;
|
|
|
|
int parent_mn = pm->submodel[mn].parent;
|
|
|
|
if (pm->submodel[parent_mn].can_move) {
|
|
rotation_matrix = pm->submodel[parent_mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[parent_mn].angs);
|
|
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[parent_mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
|
|
|
|
vec3d tvec = offset;
|
|
vm_vec_unrotate(&offset, &tvec, &submodel_instance_matrix);
|
|
}
|
|
|
|
vm_vec_add2(outpnt, &offset);
|
|
|
|
mn = parent_mn;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the current location and rotation (in the ship's frame of reference) of
|
|
* a submodel point, taking into account the rotations of the submodel and any
|
|
* parent submodels it might have.
|
|
*
|
|
* @param *outpnt Output point
|
|
* @param *outnorm Output normal
|
|
* @param model_instance_num Index into Polygon_model_instances
|
|
* @param submodel_num The number of the submodel we're interested in
|
|
* @param *submodel_pnt The point which's current position we want, in the submodel's frame of reference
|
|
* @param *submodel_norm The normal which's current direction we want, in the ship's frame of reference
|
|
*/
|
|
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)
|
|
{
|
|
*outnorm = *submodel_norm;
|
|
vm_vec_zero(outpnt);
|
|
matrix submodel_instance_matrix, rotation_matrix, inv_orientation;
|
|
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
int mn = submodel_num;
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
vec3d offset = pm->submodel[mn].offset;
|
|
|
|
if ( mn == submodel_num) {
|
|
vec3d submodel_pnt_offset = *submodel_pnt;
|
|
|
|
rotation_matrix = pm->submodel[submodel_num].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[submodel_num].angs);
|
|
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
|
|
|
|
vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
|
|
|
|
vec3d tvec = submodel_pnt_offset;
|
|
vm_vec_unrotate(&submodel_pnt_offset, &tvec, &submodel_instance_matrix);
|
|
|
|
vec3d tnorm = *outnorm;
|
|
vm_vec_unrotate(outnorm, &tnorm, &submodel_instance_matrix);
|
|
|
|
vm_vec_add2(&offset, &submodel_pnt_offset);
|
|
}
|
|
|
|
int parent_model_num = pm->submodel[mn].parent;
|
|
|
|
rotation_matrix = pm->submodel[parent_model_num].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[parent_model_num].angs);
|
|
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[parent_model_num].orientation);
|
|
|
|
vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
|
|
|
|
vec3d tvec = offset;
|
|
vm_vec_unrotate(&offset, &tvec, &submodel_instance_matrix);
|
|
|
|
vec3d tnorm = *outnorm;
|
|
vm_vec_unrotate(outnorm, &tnorm, &submodel_instance_matrix);
|
|
|
|
vm_vec_add2(outpnt, &offset);
|
|
|
|
mn = parent_model_num;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same as find_submodel_instance_point_normal, except that this takes and
|
|
* returns matrices instead of normals.
|
|
*
|
|
* Finds the current location and rotation (in the ship's frame of reference) of
|
|
* a submodel point, taking into account the rotations of the submodel and any
|
|
* parent submodels it might have.
|
|
*
|
|
* @param *outpnt Output point
|
|
* @param *outorient Output matrix
|
|
* @param model_instance_num Index into Polygon_model_instances
|
|
* @param submodel_num The number of the submodel we're interested in
|
|
* @param *submodel_pnt The point which's current position we want, in the submodel's frame of reference
|
|
* @param *submodel_orient The local matrix which's current orientation in the ship's frame of reference we want
|
|
*/
|
|
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)
|
|
{
|
|
*outorient = *submodel_orient;
|
|
vm_vec_zero(outpnt);
|
|
matrix submodel_instance_matrix, rotation_matrix, inv_orientation;
|
|
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
int mn = submodel_num;
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
vec3d offset = pm->submodel[mn].offset;
|
|
|
|
if ( mn == submodel_num) {
|
|
vec3d submodel_pnt_offset = *submodel_pnt;
|
|
|
|
rotation_matrix = pm->submodel[submodel_num].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[submodel_num].angs);
|
|
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[submodel_num].orientation);
|
|
|
|
vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
|
|
|
|
vec3d tvec = submodel_pnt_offset;
|
|
vm_vec_unrotate(&submodel_pnt_offset, &tvec, &submodel_instance_matrix);
|
|
|
|
matrix tnorm = *outorient;
|
|
vm_matrix_x_matrix(outorient, &tnorm, &submodel_instance_matrix);
|
|
|
|
vm_vec_add2(&offset, &submodel_pnt_offset);
|
|
}
|
|
|
|
int parent_model_num = pm->submodel[mn].parent;
|
|
|
|
rotation_matrix = pm->submodel[parent_model_num].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[parent_model_num].angs);
|
|
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[parent_model_num].orientation);
|
|
|
|
vm_matrix_x_matrix(&submodel_instance_matrix, &rotation_matrix, &inv_orientation);
|
|
|
|
vec3d tvec = offset;
|
|
vm_vec_unrotate(&offset, &tvec, &submodel_instance_matrix);
|
|
|
|
matrix tnorm = *outorient;
|
|
vm_matrix_x_matrix(outorient, &tnorm, &submodel_instance_matrix);
|
|
|
|
vm_vec_add2(outpnt, &offset);
|
|
|
|
mn = parent_model_num;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the current world location of a submodel, taking into account the
|
|
* rotations of any parent submodels it might have.
|
|
*
|
|
* @param *outpnt Output point
|
|
* @param model_instance_num Index into Polygon_model_instances
|
|
* @param submodel_num The number of the submodel we're interested in
|
|
*/
|
|
void find_submodel_instance_world_point(vec3d *outpnt, int model_instance_num, int submodel_num, const matrix *objorient, const vec3d *objpos)
|
|
{
|
|
vec3d loc_pnt;
|
|
|
|
find_submodel_instance_point(&loc_pnt, model_instance_num, submodel_num);
|
|
|
|
vm_vec_unrotate(outpnt, &loc_pnt, objorient);
|
|
vm_vec_add2(outpnt, objpos);
|
|
}
|
|
|
|
// Verify rotating submodel has corresponding ship subsystem -- info in which to store rotation angle
|
|
int rotating_submodel_has_ship_subsys(int submodel, ship *shipp)
|
|
{
|
|
model_subsystem *psub;
|
|
ship_subsys *pss;
|
|
|
|
int found = 0;
|
|
|
|
// Go through all subsystems and look for submodel
|
|
// the subsystems that need it.
|
|
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
|
|
psub = pss->system_info;
|
|
if (psub->subobj_num == submodel) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* Get all submodel indexes that satisfy the following:
|
|
* 1) Have the rotating or intrinsic-rotating movement type
|
|
* 2) Are currently rotating (i.e. actually moving and not part of the superstructure due to being destroyed or replaced)
|
|
* 3) Are not rotating too far for collision detection (c.f. MAX_SUBMODEL_COLLISION_ROT_ANGLE)
|
|
*/
|
|
void model_get_rotating_submodel_list(SCP_vector<int> *submodel_vector, object *objp)
|
|
{
|
|
Assert(objp->type == OBJ_SHIP || objp->type == OBJ_WEAPON || objp->type == OBJ_ASTEROID);
|
|
|
|
int model_instance_num;
|
|
int model_num;
|
|
if (objp->type == OBJ_SHIP) {
|
|
model_instance_num = Ships[objp->instance].model_instance_num;
|
|
model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
|
|
}
|
|
else if (objp->type == OBJ_WEAPON) {
|
|
model_instance_num = Weapons[objp->instance].model_instance_num;
|
|
if (model_instance_num < 0) {
|
|
return;
|
|
}
|
|
model_num = Weapon_info[Weapons[objp->instance].weapon_info_index].model_num;
|
|
}
|
|
else if (objp->type == OBJ_ASTEROID) {
|
|
model_instance_num = Asteroids[objp->instance].model_instance_num;
|
|
if (model_instance_num < 0) {
|
|
return;
|
|
}
|
|
model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
bsp_info *child_submodel = &pm->submodel[pm->detail[0]];
|
|
|
|
if(child_submodel->no_collisions) { // if detail0 has $no_collision set dont check childs
|
|
return;
|
|
}
|
|
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
|
|
int i = child_submodel->first_child;
|
|
while ( i >= 0 ) {
|
|
child_submodel = &pm->submodel[i];
|
|
|
|
// Don't check it or its children if it is destroyed or it is a replacement (non-moving)
|
|
if ( !child_submodel->blown_off && (child_submodel->i_replace == -1) && !child_submodel->no_collisions && !child_submodel->nocollide_this_only) {
|
|
|
|
// Only look for submodels that rotate or intrinsic-rotate
|
|
if (child_submodel->movement_type == MOVEMENT_TYPE_ROT || child_submodel->movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE) {
|
|
|
|
// check submodel rotation is less than max allowed.
|
|
submodel_instance_info *sii = pmi->submodel[i].sii;
|
|
|
|
// If there is no instance info yet then ignore this submodel
|
|
if (sii != nullptr) {
|
|
|
|
// found the correct submodel instance - now check delta rotation angle not too large
|
|
float delta_angle = get_submodel_delta_angle(sii);
|
|
if (delta_angle < MAX_SUBMODEL_COLLISION_ROT_ANGLE) {
|
|
submodel_vector->push_back(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
i = child_submodel->next_sibling;
|
|
}
|
|
}
|
|
|
|
void model_get_submodel_tree_list(SCP_vector<int> &submodel_vector, polymodel* pm, int mn)
|
|
{
|
|
if ( pm->submodel[mn].buffer.model_list != NULL ) {
|
|
submodel_vector.push_back(mn);
|
|
}
|
|
|
|
int i = pm->submodel[mn].first_child;
|
|
|
|
while ( i >= 0 ) {
|
|
model_get_submodel_tree_list(submodel_vector, pm, i);
|
|
|
|
i = pm->submodel[i].next_sibling;
|
|
}
|
|
}
|
|
|
|
// Given a direction (pnt) that is in submodel_num's frame of
|
|
// reference, and given the object's orient and position,
|
|
// return the point in 3-space in outpnt.
|
|
void model_find_world_dir(vec3d *out_dir, vec3d *in_dir, int model_num, int submodel_num, const matrix *objorient)
|
|
{
|
|
vec3d pnt;
|
|
vec3d tpnt;
|
|
matrix m;
|
|
int mn;
|
|
polymodel *pm = model_get(model_num);
|
|
|
|
pnt = *in_dir;
|
|
mn = submodel_num;
|
|
|
|
//instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_unrotate(&tpnt, &pnt, &m);
|
|
pnt = tpnt;
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
//now instance for the entire object
|
|
vm_vec_unrotate(out_dir,&pnt,objorient);
|
|
}
|
|
|
|
// the same as above - just taking model instance data into account
|
|
// model_find_world_dir
|
|
void model_instance_find_world_dir(vec3d *out_dir, vec3d *in_dir, int model_instance_num, int submodel_num, const matrix *objorient)
|
|
{
|
|
vec3d pnt;
|
|
vec3d tpnt;
|
|
matrix m;
|
|
int mn;
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
pnt = *in_dir;
|
|
mn = submodel_num;
|
|
|
|
//instance up the tree for this point
|
|
while ( (mn >= 0) && (pm->submodel[mn].parent >= 0) ) {
|
|
// By using this kind of computation, the rotational angles can always
|
|
// be computed relative to the submodel itself, instead of relative
|
|
// to the parent - KeldorKatarn
|
|
matrix rotation_matrix = pm->submodel[mn].orientation;
|
|
vm_rotate_matrix_by_angles(&rotation_matrix, &pmi->submodel[mn].angs);
|
|
|
|
matrix inv_orientation;
|
|
vm_copy_transpose(&inv_orientation, &pm->submodel[mn].orientation);
|
|
|
|
vm_matrix_x_matrix(&m, &rotation_matrix, &inv_orientation);
|
|
|
|
vm_vec_unrotate(&tpnt, &pnt, &m);
|
|
pnt = tpnt;
|
|
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
//now instance for the entire object
|
|
vm_vec_unrotate(out_dir,&pnt,objorient);
|
|
}
|
|
|
|
|
|
// Clears all the submodel instances stored in a model to their defaults.
|
|
void model_clear_instance(int model_num)
|
|
{
|
|
polymodel * pm;
|
|
int i;
|
|
|
|
pm = model_get(model_num);
|
|
|
|
pm->gun_submodel_rotation = 0.0f;
|
|
// reset textures to original ones
|
|
for (i=0; i<pm->n_textures; i++ ) {
|
|
pm->maps[i].ResetToOriginal();
|
|
}
|
|
|
|
for (i=0; i<pm->n_models; i++ ) {
|
|
bsp_info *sm = &pm->submodel[i];
|
|
|
|
if ( pm->submodel[i].is_damaged ) {
|
|
sm->blown_off = 1;
|
|
} else {
|
|
sm->blown_off = 0;
|
|
}
|
|
sm->angs.p = 0.0f;
|
|
sm->angs.b = 0.0f;
|
|
sm->angs.h = 0.0f;
|
|
|
|
sm->num_arcs = 0; // Turn off any electric arcing effects
|
|
}
|
|
|
|
for (i=0; i<pm->num_lights; i++ ) {
|
|
pm->lights[i].value = 0.0f;
|
|
}
|
|
|
|
interp_clear_instance();
|
|
|
|
// if ( keyd_pressed[KEY_1] ) pm->lights[0].value = 1.0f/255.0f;
|
|
// if ( keyd_pressed[KEY_2] ) pm->lights[1].value = 1.0f/255.0f;
|
|
// if ( keyd_pressed[KEY_3] ) pm->lights[2].value = 1.0f/255.0f;
|
|
// if ( keyd_pressed[KEY_4] ) pm->lights[3].value = 1.0f/255.0f;
|
|
// if ( keyd_pressed[KEY_5] ) pm->lights[4].value = 1.0f/255.0f;
|
|
// if ( keyd_pressed[KEY_6] ) pm->lights[5].value = 1.0f/255.0f;
|
|
|
|
|
|
}
|
|
|
|
// initialization during ship set
|
|
void model_clear_instance_info( submodel_instance_info * sii )
|
|
{
|
|
sii->blown_off = 0;
|
|
sii->angs.p = 0.0f;
|
|
sii->angs.b = 0.0f;
|
|
sii->angs.h = 0.0f;
|
|
sii->prev_angs.p = 0.0f;
|
|
sii->prev_angs.b = 0.0f;
|
|
sii->prev_angs.h = 0.0f;
|
|
|
|
sii->cur_turn_rate = 0.0f;
|
|
sii->desired_turn_rate = 0.0f;
|
|
sii->turn_accel = 0.0f;
|
|
}
|
|
|
|
void model_clear_submodel_instance( submodel_instance *sm_instance, bsp_info *sm )
|
|
{
|
|
sm_instance->angs.p = 0.0f;
|
|
sm_instance->angs.b = 0.0f;
|
|
sm_instance->angs.h = 0.0f;
|
|
|
|
sm_instance->blown_off = sm->is_damaged ? true : false;
|
|
|
|
sm_instance->collision_checked = false;
|
|
sm_instance->sii = NULL;
|
|
}
|
|
|
|
void model_clear_submodel_instances( int model_instance_num )
|
|
{
|
|
int i;
|
|
polymodel_instance *pmi = model_get_instance(model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
for ( i = 0; i < pm->n_models; i++ ) {
|
|
model_clear_submodel_instance(&pmi->submodel[i], &pm->submodel[i]);
|
|
}
|
|
}
|
|
|
|
// initialization during ship set
|
|
void model_set_instance_info(submodel_instance_info *sii, float turn_rate, float turn_accel)
|
|
{
|
|
sii->blown_off = 0;
|
|
sii->angs.p = 0.0f;
|
|
sii->angs.b = 0.0f;
|
|
sii->angs.h = 0.0f;
|
|
sii->prev_angs.p = 0.0f;
|
|
sii->prev_angs.b = 0.0f;
|
|
sii->prev_angs.h = 0.0f;
|
|
|
|
sii->cur_turn_rate = turn_rate * 0.0f;
|
|
sii->desired_turn_rate = turn_rate;
|
|
sii->turn_accel = turn_accel;
|
|
sii->axis_set = 0;
|
|
sii->step_zero_timestamp = timestamp();
|
|
}
|
|
|
|
// Sets the submodel instance data in a submodel (for all detail levels)
|
|
void model_set_instance(int model_num, int sub_model_num, submodel_instance_info *sii, flagset<Ship::Subsystem_Flags>* flags)
|
|
{
|
|
int i;
|
|
polymodel * pm;
|
|
|
|
flagset<Ship::Subsystem_Flags> instance_flags;
|
|
|
|
if (flags != NULL) {
|
|
instance_flags = *flags;
|
|
}
|
|
|
|
pm = model_get(model_num);
|
|
|
|
Assert( sub_model_num >= 0 );
|
|
Assert( sub_model_num < pm->n_models );
|
|
|
|
if ( sub_model_num < 0 ) return;
|
|
if ( sub_model_num >= pm->n_models ) return;
|
|
bsp_info *sm = &pm->submodel[sub_model_num];
|
|
|
|
if (instance_flags[Ship::Subsystem_Flags::No_disappear]) {
|
|
sm->blown_off = 0;
|
|
} else {
|
|
// Set the "blown out" flags
|
|
sm->blown_off = sii->blown_off;
|
|
}
|
|
|
|
if ( (sm->blown_off) && (!(instance_flags[Ship::Subsystem_Flags::No_replace])) ) {
|
|
if ( sm->my_replacement > -1 ) {
|
|
pm->submodel[sm->my_replacement].blown_off = 0;
|
|
pm->submodel[sm->my_replacement].angs = sii->angs;
|
|
}
|
|
} else {
|
|
// If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
|
|
// the replacement model from being drawn by marking it as having been blown off
|
|
if ( sm->my_replacement > -1 && sm->my_replacement != sub_model_num) {
|
|
pm->submodel[sm->my_replacement].blown_off = 1;
|
|
}
|
|
}
|
|
|
|
// Set the angles
|
|
sm->angs = sii->angs;
|
|
|
|
// For all the detail levels of this submodel, set them also.
|
|
for (i=0; i<sm->num_details; i++ ) {
|
|
model_set_instance(model_num, sm->details[i], sii, &instance_flags);
|
|
}
|
|
}
|
|
|
|
// Sets the submodel instance data in a submodel (for all detail levels)
|
|
// Techroom version uses two floats of setting rotation angles (for turrets)
|
|
// instead of using larger but largely unused structures for storing the same data
|
|
void model_set_instance_techroom(int model_num, int sub_model_num, float angle_1, float angle_2)
|
|
{
|
|
polymodel * pm;
|
|
|
|
pm = model_get(model_num);
|
|
|
|
Assert( sub_model_num >= 0 );
|
|
Assert( sub_model_num < pm->n_models );
|
|
|
|
if ( sub_model_num < 0 ) return;
|
|
if ( sub_model_num >= pm->n_models ) return;
|
|
bsp_info *sm = &pm->submodel[sub_model_num];
|
|
|
|
// If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
|
|
// the replacement model from being drawn by marking it as having been blown off
|
|
if ( sm->my_replacement > -1 && sm->my_replacement != sub_model_num) {
|
|
pm->submodel[sm->my_replacement].blown_off = 1;
|
|
}
|
|
|
|
// Set the angles
|
|
sm->angs.p = angle_1;
|
|
sm->angs.h = angle_2;
|
|
}
|
|
|
|
void model_update_instance(int model_instance_num, int sub_model_num, submodel_instance_info *sii, flagset<Ship::Subsystem_Flags>& flags)
|
|
{
|
|
int i;
|
|
polymodel *pm;
|
|
polymodel_instance *pmi;
|
|
|
|
pmi = model_get_instance(model_instance_num);
|
|
pm = model_get(pmi->model_num);
|
|
|
|
Assertion(sub_model_num >= 0 && sub_model_num < pm->n_models,
|
|
"Sub model number (%d) which should be updated is out of range! Must be between 0 and %d. This happend on model %s.",
|
|
sub_model_num, pm->n_models - 1, pm->filename);
|
|
|
|
if ( sub_model_num < 0 ) return;
|
|
if ( sub_model_num >= pm->n_models ) return;
|
|
|
|
submodel_instance *smi = &pmi->submodel[sub_model_num];
|
|
bsp_info *sm = &pm->submodel[sub_model_num];
|
|
|
|
// Set the "blown out" flags
|
|
if ( flags[Ship::Subsystem_Flags::No_disappear] ) {
|
|
smi->blown_off = false;
|
|
} else {
|
|
smi->blown_off = sii->blown_off ? true : false;
|
|
}
|
|
|
|
if ( smi->blown_off && !(flags[Ship::Subsystem_Flags::No_replace]) ) {
|
|
if ( sm->my_replacement > -1 ) {
|
|
pmi->submodel[sm->my_replacement].blown_off = false;
|
|
pmi->submodel[sm->my_replacement].angs = sii->angs;
|
|
pmi->submodel[sm->my_replacement].prev_angs = sii->prev_angs;
|
|
}
|
|
} else {
|
|
// If submodel isn't yet blown off and has a -destroyed replacement model, we prevent
|
|
// the replacement model from being drawn by marking it as having been blown off
|
|
if ( sm->my_replacement > -1 && sm->my_replacement != sub_model_num) {
|
|
pmi->submodel[sm->my_replacement].blown_off = true;
|
|
}
|
|
}
|
|
|
|
// Set the angles
|
|
smi->angs = sii->angs;
|
|
smi->prev_angs = sii->prev_angs;
|
|
smi->sii = sii;
|
|
|
|
// For all the detail levels of this submodel, set them also.
|
|
for (i=0; i<sm->num_details; i++ ) {
|
|
model_update_instance(model_instance_num, sm->details[i], sii, flags );
|
|
}
|
|
}
|
|
|
|
void model_do_intrinsic_rotations_sub(intrinsic_rotation *ir)
|
|
{
|
|
polymodel_instance *pmi = model_get_instance(ir->model_instance_num);
|
|
Assert(pmi != nullptr);
|
|
|
|
// Handle all submodels which have $dumb_rotate
|
|
for (auto submodel_it = ir->list.begin(); submodel_it != ir->list.end(); ++submodel_it)
|
|
{
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
Assert(pm != nullptr);
|
|
bsp_info *sm = &pm->submodel[submodel_it->submodel_num];
|
|
|
|
// First, calculate the angles for the rotation
|
|
submodel_rotate(sm, &submodel_it->submodel_info_1);
|
|
|
|
// Now actually rotate the submodel instance
|
|
// (Since this is an intrinsic rotation, we have no associated subsystem, so pass 0 for subsystem flags.)
|
|
flagset<Ship::Subsystem_Flags> empty;
|
|
model_update_instance(ir->model_instance_num, submodel_it->submodel_num, &submodel_it->submodel_info_1, empty);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// 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,
|
|
// they are both handled here.
|
|
//
|
|
// 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
|
|
// 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
|
|
// 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
|
|
// or damage sparks that aren't attached to models.)
|
|
//
|
|
// -- Goober5000
|
|
void model_do_intrinsic_rotations(int model_instance_num)
|
|
{
|
|
// we are handling a specific ship
|
|
if (model_instance_num >= 0)
|
|
{
|
|
for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it)
|
|
{
|
|
if (intrinsic_it->model_instance_num == model_instance_num)
|
|
{
|
|
Assertion(intrinsic_it->is_ship, "This code path is only for ship rotations! See the comments associated with the model_do_intrinsic_rotations function!");
|
|
|
|
// we're just doing one ship, and in ship_model_update_instance, that ship's angles were already set to zero
|
|
|
|
// Now update the angles in the submodels
|
|
model_do_intrinsic_rotations_sub(&(*intrinsic_it));
|
|
|
|
// once we've handled this one ship, we're done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// we are handling all non-ships
|
|
else
|
|
{
|
|
for (auto intrinsic_it = Intrinsic_rotations.begin(); intrinsic_it != Intrinsic_rotations.end(); ++intrinsic_it)
|
|
{
|
|
if (!intrinsic_it->is_ship)
|
|
{
|
|
// Just as in ship_model_update_instance: first clear all the angles in the model to zero
|
|
model_clear_submodel_instances(intrinsic_it->model_instance_num);
|
|
|
|
// Now update the angles in the submodels
|
|
model_do_intrinsic_rotations_sub(&(*intrinsic_it));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void model_do_children_look_at(polymodel * pm, int mn)
|
|
{
|
|
while ( mn >= 0 ) {
|
|
submodel_look_at(pm, mn);
|
|
if (pm->submodel[mn].first_child >-1) {
|
|
model_do_children_look_at(pm, pm->submodel[mn].first_child);
|
|
}
|
|
mn = pm->submodel[mn].next_sibling;
|
|
}
|
|
}
|
|
|
|
void model_do_look_at(int pn)
|
|
{
|
|
polymodel * pm;
|
|
pm = model_get(pn);
|
|
int mn = pm->detail[0];
|
|
model_do_children_look_at(pm,mn);
|
|
}
|
|
|
|
// Finds a point on the rotation axis of a submodel, used in collision, generally find rotational velocity
|
|
void model_init_submodel_axis_pt(submodel_instance_info *sii, int model_num, int submodel_num)
|
|
{
|
|
vec3d axis;
|
|
vec3d *mpoint1, *mpoint2;
|
|
vec3d p1, v1, p2, v2, int1;
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
Assert(pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_ROT || pm->submodel[submodel_num].movement_type == MOVEMENT_TYPE_INTRINSIC_ROTATE);
|
|
Assert(sii);
|
|
|
|
mpoint1 = NULL;
|
|
mpoint2 = NULL;
|
|
|
|
// find 2 fixed points in submodel RF
|
|
// these will be rotated to about the axis an angle of 0 and PI and we'll find the intersection of the
|
|
// two lines to find a point on the axis
|
|
if (pm->submodel[submodel_num].movement_axis == MOVEMENT_AXIS_X) {
|
|
axis = vmd_x_vector;
|
|
mpoint1 = &vmd_y_vector;
|
|
mpoint2 = &vmd_z_vector;
|
|
} else if (pm->submodel[submodel_num].movement_axis == MOVEMENT_AXIS_Y) {
|
|
mpoint1 = &vmd_x_vector;
|
|
axis = vmd_z_vector; // rotation about y is a change in heading (p,b,h), so we need z
|
|
mpoint2 = &vmd_z_vector;
|
|
} else if (pm->submodel[submodel_num].movement_axis == MOVEMENT_AXIS_Z) {
|
|
mpoint1 = &vmd_x_vector;
|
|
mpoint2 = &vmd_y_vector;
|
|
axis = vmd_y_vector; // rotation about z is a change in bank (p,b,h), so we need y
|
|
} else {
|
|
// must be one of these axes or submodel_rot_hit is incorrectly set
|
|
Int3();
|
|
}
|
|
|
|
// copy submodel angs
|
|
angles copy_angs = pm->submodel[submodel_num].angs;
|
|
|
|
// find two points rotated into model RF when angs set to 0
|
|
vm_vec_copy_scale((vec3d*)&pm->submodel[submodel_num].angs, &axis, 0.0f);
|
|
model_find_world_point(&p1, mpoint1, model_num, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
|
|
model_find_world_point(&p2, mpoint2, model_num, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
|
|
|
|
// find two points rotated into model RF when angs set to PI
|
|
vm_vec_copy_scale((vec3d*)&pm->submodel[submodel_num].angs, &axis, PI);
|
|
model_find_world_point(&v1, mpoint1, model_num, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
|
|
model_find_world_point(&v2, mpoint2, model_num, submodel_num, &vmd_identity_matrix, &vmd_zero_vector);
|
|
|
|
// reset submodel angs
|
|
pm->submodel[submodel_num].angs = copy_angs;
|
|
|
|
// find direction vectors of the two lines
|
|
vm_vec_sub2(&v1, &p1);
|
|
vm_vec_sub2(&v2, &p2);
|
|
|
|
// find the intersection of the two lines
|
|
float s, t;
|
|
fvi_two_lines_in_3space(&p1, &v1, &p2, &v2, &s, &t);
|
|
|
|
// find the actual intersection points
|
|
vm_vec_scale_add(&int1, &p1, &v1, s);
|
|
|
|
// set flag to init
|
|
sii->pt_on_axis = int1;
|
|
sii->axis_set = 1;
|
|
}
|
|
|
|
|
|
// Adds an electrical arcing effect to a submodel
|
|
void model_add_arc(int model_num, int sub_model_num, vec3d *v1, vec3d *v2, int arc_type )
|
|
{
|
|
polymodel * pm;
|
|
|
|
pm = model_get(model_num);
|
|
|
|
if ( sub_model_num == -1 ) {
|
|
sub_model_num = pm->detail[0];
|
|
}
|
|
|
|
Assert( sub_model_num >= 0 );
|
|
Assert( sub_model_num < pm->n_models );
|
|
|
|
if ( sub_model_num < 0 ) return;
|
|
if ( sub_model_num >= pm->n_models ) return;
|
|
bsp_info *sm = &pm->submodel[sub_model_num];
|
|
|
|
if ( sm->num_arcs < MAX_ARC_EFFECTS ) {
|
|
sm->arc_type[sm->num_arcs] = (ubyte)arc_type;
|
|
sm->arc_pts[sm->num_arcs][0] = *v1;
|
|
sm->arc_pts[sm->num_arcs][1] = *v2;
|
|
sm->num_arcs++;
|
|
}
|
|
}
|
|
|
|
// function to return an index into the docking_bays array which matches the criteria passed
|
|
// to this function. dock_type is one of the DOCK_TYPE_XXX defines in model.h
|
|
// Goober5000 - now finds more than one dockpoint of this type
|
|
int model_find_dock_index(int modelnum, int dock_type, int index_to_start_at)
|
|
{
|
|
int i;
|
|
polymodel *pm;
|
|
|
|
// get model and make sure it has dockpoints
|
|
pm = model_get(modelnum);
|
|
if ( pm->n_docks <= 0 )
|
|
return -1;
|
|
|
|
// look for a dockpoint of this type
|
|
for (i = index_to_start_at; i < pm->n_docks; i++ )
|
|
{
|
|
if ( dock_type & pm->docking_bays[i].type_flags )
|
|
return i;
|
|
}
|
|
|
|
// if we get here, type wasn't found -- return -1 and hope for the best
|
|
return -1;
|
|
}
|
|
|
|
// function to return an index into the docking_bays array which matches the string passed
|
|
// Fred uses strings to identify docking positions. This function also accepts generic strings
|
|
// so that a desginer doesn't have to know exact names if building a mission from hand.
|
|
int model_find_dock_name_index( int modelnum, char *name )
|
|
{
|
|
int i;
|
|
polymodel *pm;
|
|
|
|
// get model and make sure it has dockpoints
|
|
pm = model_get(modelnum);
|
|
if ( pm->n_docks <= 0 )
|
|
return -1;
|
|
|
|
// check the generic names and call previous function to find first dock point of
|
|
// the specified type
|
|
for(i = 0; i < Num_dock_type_names; i++)
|
|
{
|
|
if(!stricmp(name, Dock_type_names[i].name)) {
|
|
return model_find_dock_index(modelnum, Dock_type_names[i].def);
|
|
}
|
|
}
|
|
/*
|
|
if ( !stricmp(name, "cargo") )
|
|
return model_find_dock_index( modelnum, DOCK_TYPE_CARGO );
|
|
else if (!stricmp( name, "rearm") )
|
|
return model_find_dock_index( modelnum, DOCK_TYPE_REARM );
|
|
else if (!stricmp( name, "generic") )
|
|
return model_find_dock_index( modelnum, DOCK_TYPE_GENERIC );
|
|
*/
|
|
|
|
// look for a dockpoint with this name
|
|
for (i = 0; i < pm->n_docks; i++ )
|
|
{
|
|
if ( !stricmp(pm->docking_bays[i].name, name) )
|
|
return i;
|
|
}
|
|
|
|
// if the bay does not have a name in the model, the model loading code
|
|
// will assign it a default name... check for that here
|
|
if (!strnicmp(name, "<unnamed bay ", 13))
|
|
{
|
|
int index = (name[13] - 'A');
|
|
if (index >= 0 && index < pm->n_docks)
|
|
return index;
|
|
}
|
|
|
|
// if we get here, name wasn't found -- return -1 and hope for the best
|
|
return -1;
|
|
}
|
|
|
|
// returns the actual name of a docking point on a model, needed by Fred.
|
|
char *model_get_dock_name(int modelnum, int index)
|
|
{
|
|
polymodel *pm;
|
|
|
|
pm = model_get(modelnum);
|
|
Assert((index >= 0) && (index < pm->n_docks));
|
|
return pm->docking_bays[index].name;
|
|
}
|
|
|
|
int model_get_num_dock_points(int modelnum)
|
|
{
|
|
polymodel *pm;
|
|
|
|
pm = model_get(modelnum);
|
|
return pm->n_docks;
|
|
}
|
|
|
|
int model_get_dock_index_type(int modelnum, int index)
|
|
{
|
|
polymodel *pm = model_get(modelnum);
|
|
|
|
return pm->docking_bays[index].type_flags;
|
|
}
|
|
|
|
// get all the different docking point types on a model
|
|
int model_get_dock_types(int modelnum)
|
|
{
|
|
int i, type = 0;
|
|
polymodel *pm;
|
|
|
|
pm = model_get(modelnum);
|
|
for (i=0; i<pm->n_docks; i++)
|
|
type |= pm->docking_bays[i].type_flags;
|
|
|
|
return type;
|
|
}
|
|
|
|
// Goober5000
|
|
// returns index in [0, MAX_SHIP_BAY_PATHS)
|
|
int model_find_bay_path(int modelnum, char *bay_path_name)
|
|
{
|
|
int i;
|
|
polymodel *pm = model_get(modelnum);
|
|
|
|
if (pm->ship_bay == NULL)
|
|
return -1;
|
|
|
|
if (pm->ship_bay->num_paths <= 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < pm->ship_bay->num_paths; i++)
|
|
{
|
|
if (!stricmp(pm->paths[pm->ship_bay->path_indexes[i]].name, bay_path_name))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int model_create_bsp_collision_tree()
|
|
{
|
|
// first find an open slot
|
|
size_t i;
|
|
bool slot_found = false;
|
|
|
|
for ( i = 0; i < Bsp_collision_tree_list.size(); ++i ) {
|
|
if ( !Bsp_collision_tree_list[i].used ) {
|
|
slot_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( slot_found ) {
|
|
Bsp_collision_tree_list[i].used = true;
|
|
|
|
return (int)i;
|
|
}
|
|
|
|
bsp_collision_tree tree;
|
|
|
|
tree.used = true;
|
|
Bsp_collision_tree_list.push_back(tree);
|
|
|
|
return (int)(Bsp_collision_tree_list.size() - 1);
|
|
}
|
|
|
|
bsp_collision_tree *model_get_bsp_collision_tree(int tree_index)
|
|
{
|
|
Assert(tree_index >= 0);
|
|
Assert((uint) tree_index < Bsp_collision_tree_list.size());
|
|
|
|
return &Bsp_collision_tree_list[tree_index];
|
|
}
|
|
|
|
void model_remove_bsp_collision_tree(int tree_index)
|
|
{
|
|
Bsp_collision_tree_list[tree_index].used = false;
|
|
|
|
if ( Bsp_collision_tree_list[tree_index].node_list ) {
|
|
vm_free(Bsp_collision_tree_list[tree_index].node_list);
|
|
}
|
|
|
|
if ( Bsp_collision_tree_list[tree_index].leaf_list ) {
|
|
vm_free(Bsp_collision_tree_list[tree_index].leaf_list);
|
|
}
|
|
|
|
if ( Bsp_collision_tree_list[tree_index].point_list ) {
|
|
vm_free( Bsp_collision_tree_list[tree_index].point_list );
|
|
}
|
|
|
|
if ( Bsp_collision_tree_list[tree_index].vert_list ) {
|
|
vm_free( Bsp_collision_tree_list[tree_index].vert_list);
|
|
}
|
|
}
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
|
|
// tigital -
|
|
void swap_bsp_defpoints(ubyte * p)
|
|
{
|
|
int n, i;
|
|
int nverts = INTEL_INT( w(p+8) ); //tigital
|
|
int offset = INTEL_INT( w(p+16) );
|
|
int n_norms = INTEL_INT( w(p+12) );
|
|
|
|
w(p+8) = nverts;
|
|
w(p+16) = offset;
|
|
w(p+12) = n_norms;
|
|
|
|
ubyte * normcount = p+20;
|
|
vec3d *src = vp(p+offset);
|
|
|
|
model_allocate_interp_data(nverts, n_norms);
|
|
|
|
for (n=0; n<nverts; n++ ) {
|
|
src->xyz.x = INTEL_FLOAT( &src->xyz.x ); //tigital
|
|
src->xyz.y = INTEL_FLOAT( &src->xyz.y );
|
|
src->xyz.z = INTEL_FLOAT( &src->xyz.z );
|
|
|
|
Interp_verts[n] = src;
|
|
src++; //tigital
|
|
|
|
for (i=0; i<normcount[n]; i++){
|
|
src->xyz.x = INTEL_FLOAT( &src->xyz.x ); //tigital
|
|
src->xyz.y = INTEL_FLOAT( &src->xyz.y );
|
|
src->xyz.z = INTEL_FLOAT( &src->xyz.z );
|
|
src++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void swap_bsp_tmappoly( polymodel * pm, ubyte * p )
|
|
{
|
|
int i, nv;
|
|
model_tmap_vert *verts;
|
|
vec3d * normal = vp(p+8); //tigital
|
|
vec3d * center = vp(p+20);
|
|
float radius = INTEL_FLOAT( &fl(p+32) );
|
|
|
|
fl(p+32) = radius;
|
|
|
|
normal->xyz.x = INTEL_FLOAT( &normal->xyz.x );
|
|
normal->xyz.y = INTEL_FLOAT( &normal->xyz.y );
|
|
normal->xyz.z = INTEL_FLOAT( &normal->xyz.z );
|
|
center->xyz.x = INTEL_FLOAT( ¢er->xyz.x );
|
|
center->xyz.y = INTEL_FLOAT( ¢er->xyz.y );
|
|
center->xyz.z = INTEL_FLOAT( ¢er->xyz.z );
|
|
|
|
nv = INTEL_INT( w(p+36)); //tigital
|
|
w(p+36) = nv;
|
|
|
|
int tmap_num = INTEL_INT( w(p+40) ); //tigital
|
|
w(p+40) = tmap_num;
|
|
|
|
if ( nv < 0 ) return;
|
|
|
|
verts = (model_tmap_vert *)(p+44);
|
|
for (i=0;i<nv;i++){
|
|
verts[i].vertnum = INTEL_SHORT( verts[i].vertnum );
|
|
verts[i].normnum = INTEL_SHORT( verts[i].normnum );
|
|
verts[i].u = INTEL_FLOAT( &verts[i].u );
|
|
verts[i].v = INTEL_FLOAT( &verts[i].v );
|
|
}
|
|
|
|
if ( pm->version < 2003 ) {
|
|
// Set the "normal_point" part of field to be the center of the polygon
|
|
vec3d center_point;
|
|
vm_vec_zero( ¢er_point );
|
|
|
|
for (i=0;i<nv;i++) {
|
|
vm_vec_add2( ¢er_point, Interp_verts[verts[i].vertnum] );
|
|
}
|
|
|
|
center_point.xyz.x /= nv;
|
|
center_point.xyz.y /= nv;
|
|
center_point.xyz.z /= nv;
|
|
|
|
*vp(p+20) = center_point;
|
|
|
|
float rad = 0.0f;
|
|
|
|
for (i=0;i<nv;i++) {
|
|
float dist = vm_vec_dist( ¢er_point, Interp_verts[verts[i].vertnum] );
|
|
if ( dist > rad ) {
|
|
rad = dist;
|
|
}
|
|
}
|
|
fl(p+32) = rad;
|
|
}
|
|
}
|
|
|
|
void swap_bsp_flatpoly( polymodel * pm, ubyte * p )
|
|
{
|
|
int i, nv;
|
|
short *verts;
|
|
vec3d * normal = vp(p+8); //tigital
|
|
vec3d * center = vp(p+20);
|
|
|
|
float radius = INTEL_FLOAT( &fl(p+32) );
|
|
|
|
fl(p+32) = radius;
|
|
|
|
normal->xyz.x = INTEL_FLOAT( &normal->xyz.x );
|
|
normal->xyz.y = INTEL_FLOAT( &normal->xyz.y );
|
|
normal->xyz.z = INTEL_FLOAT( &normal->xyz.z );
|
|
center->xyz.x = INTEL_FLOAT( ¢er->xyz.x );
|
|
center->xyz.y = INTEL_FLOAT( ¢er->xyz.y );
|
|
center->xyz.z = INTEL_FLOAT( ¢er->xyz.z );
|
|
|
|
nv = INTEL_INT( w(p+36)); //tigital
|
|
w(p+36) = nv;
|
|
|
|
if ( nv < 0 ) return;
|
|
|
|
verts = (short *)(p+44);
|
|
for (i=0; i<nv*2; i++){
|
|
verts[i] = INTEL_SHORT( verts[i] );
|
|
}
|
|
|
|
if ( pm->version < 2003 ) {
|
|
// Set the "normal_point" part of field to be the center of the polygon
|
|
vec3d center_point;
|
|
vm_vec_zero( ¢er_point );
|
|
|
|
for (i=0;i<nv;i++) {
|
|
vm_vec_add2( ¢er_point, Interp_verts[verts[i*2]] );
|
|
}
|
|
|
|
center_point.xyz.x /= nv;
|
|
center_point.xyz.y /= nv;
|
|
center_point.xyz.z /= nv;
|
|
|
|
*vp(p+20) = center_point;
|
|
|
|
float rad = 0.0f;
|
|
|
|
for (i=0;i<nv;i++) {
|
|
float dist = vm_vec_dist( ¢er_point, Interp_verts[verts[i*2]] );
|
|
if ( dist > rad ) {
|
|
rad = dist;
|
|
}
|
|
}
|
|
fl(p+32) = rad;
|
|
}
|
|
}
|
|
|
|
void swap_bsp_sortnorms( polymodel * pm, ubyte * p )
|
|
{
|
|
int frontlist = INTEL_INT( w(p+36) ); //tigital
|
|
int backlist = INTEL_INT( w(p+40) );
|
|
int prelist = INTEL_INT( w(p+44) );
|
|
int postlist = INTEL_INT( w(p+48) );
|
|
int onlist = INTEL_INT( w(p+52) );
|
|
|
|
w(p+36) = frontlist;
|
|
w(p+40) = backlist;
|
|
w(p+44) = prelist;
|
|
w(p+48) = postlist;
|
|
w(p+52) = onlist;
|
|
|
|
vec3d * normal = vp(p+8); //tigital
|
|
vec3d * center = vp(p+20);
|
|
int tmp = INTEL_INT( w(p+32) );
|
|
|
|
w(p+32) = tmp;
|
|
|
|
normal->xyz.x = INTEL_FLOAT( &normal->xyz.x );
|
|
normal->xyz.y = INTEL_FLOAT( &normal->xyz.y );
|
|
normal->xyz.z = INTEL_FLOAT( &normal->xyz.z );
|
|
center->xyz.x = INTEL_FLOAT( ¢er->xyz.x );
|
|
center->xyz.y = INTEL_FLOAT( ¢er->xyz.y );
|
|
center->xyz.z = INTEL_FLOAT( ¢er->xyz.z );
|
|
|
|
vec3d * bmin = vp(p+56); //tigital
|
|
vec3d * bmax = vp(p+68);
|
|
|
|
bmin->xyz.x = INTEL_FLOAT( &bmin->xyz.x );
|
|
bmin->xyz.y = INTEL_FLOAT( &bmin->xyz.y );
|
|
bmin->xyz.z = INTEL_FLOAT( &bmin->xyz.z );
|
|
bmax->xyz.x = INTEL_FLOAT( &bmax->xyz.x );
|
|
bmax->xyz.y = INTEL_FLOAT( &bmax->xyz.y );
|
|
bmax->xyz.z = INTEL_FLOAT( &bmax->xyz.z );
|
|
|
|
if (prelist) swap_bsp_data(pm,p+prelist);
|
|
if (backlist) swap_bsp_data(pm,p+backlist);
|
|
if (onlist) swap_bsp_data(pm,p+onlist);
|
|
if (frontlist) swap_bsp_data(pm,p+frontlist);
|
|
if (postlist) swap_bsp_data(pm,p+postlist);
|
|
}
|
|
#endif // BIG_ENDIAN
|
|
|
|
void swap_bsp_data( polymodel * pm, void *model_ptr )
|
|
{
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
ubyte *p = (ubyte *)model_ptr;
|
|
int chunk_type, chunk_size;
|
|
vec3d * min;
|
|
vec3d * max;
|
|
|
|
chunk_type = INTEL_INT( w(p) ); //tigital
|
|
chunk_size = INTEL_INT( w(p+4) );
|
|
w(p) = chunk_type;
|
|
w(p+4) = chunk_size;
|
|
|
|
while (chunk_type != OP_EOF) {
|
|
switch (chunk_type) {
|
|
case OP_EOF:
|
|
return;
|
|
case OP_DEFPOINTS:
|
|
swap_bsp_defpoints(p);
|
|
break;
|
|
case OP_FLATPOLY:
|
|
swap_bsp_flatpoly(pm, p);
|
|
break;
|
|
case OP_TMAPPOLY:
|
|
swap_bsp_tmappoly(pm, p);
|
|
break;
|
|
case OP_SORTNORM:
|
|
swap_bsp_sortnorms(pm, p);
|
|
break;
|
|
case OP_BOUNDBOX:
|
|
min = vp(p+8);
|
|
max = vp(p+20);
|
|
min->xyz.x = INTEL_FLOAT( &min->xyz.x );
|
|
min->xyz.y = INTEL_FLOAT( &min->xyz.y );
|
|
min->xyz.z = INTEL_FLOAT( &min->xyz.z );
|
|
max->xyz.x = INTEL_FLOAT( &max->xyz.x );
|
|
max->xyz.y = INTEL_FLOAT( &max->xyz.y );
|
|
max->xyz.z = INTEL_FLOAT( &max->xyz.z );
|
|
break;
|
|
default:
|
|
mprintf(( "Bad chunk type %d, len=%d in modelread:swap_bsp_data\n", chunk_type, chunk_size ));
|
|
Int3(); // Bad chunk type!
|
|
return;
|
|
}
|
|
|
|
p += chunk_size;
|
|
chunk_type = INTEL_INT( w(p)); //tigital
|
|
chunk_size = INTEL_INT( w(p+4) );
|
|
w(p) = chunk_type;
|
|
w(p+4) = chunk_size;
|
|
}
|
|
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
void swap_sldc_data(ubyte *buffer)
|
|
{
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
char *type_p = (char *)(buffer);
|
|
int *size_p = (int *)(buffer+1);
|
|
*size_p = INTEL_INT(*size_p);
|
|
|
|
// split and polygons
|
|
vec3d *minbox_p = (vec3d*)(buffer+5);
|
|
vec3d *maxbox_p = (vec3d*)(buffer+17);
|
|
|
|
minbox_p->xyz.x = INTEL_FLOAT(&minbox_p->xyz.x);
|
|
minbox_p->xyz.y = INTEL_FLOAT(&minbox_p->xyz.y);
|
|
minbox_p->xyz.z = INTEL_FLOAT(&minbox_p->xyz.z);
|
|
|
|
maxbox_p->xyz.x = INTEL_FLOAT(&maxbox_p->xyz.x);
|
|
maxbox_p->xyz.y = INTEL_FLOAT(&maxbox_p->xyz.y);
|
|
maxbox_p->xyz.z = INTEL_FLOAT(&maxbox_p->xyz.z);
|
|
|
|
|
|
// split
|
|
unsigned int *front_offset_p = (unsigned int*)(buffer+29);
|
|
unsigned int *back_offset_p = (unsigned int*)(buffer+33);
|
|
|
|
// polygons
|
|
unsigned int *num_polygons_p = (unsigned int*)(buffer+29);
|
|
|
|
unsigned int *shld_polys = (unsigned int*)(buffer+33);
|
|
|
|
if (*type_p == 0) // SPLIT
|
|
{
|
|
*front_offset_p = INTEL_INT(*front_offset_p);
|
|
*back_offset_p = INTEL_INT(*back_offset_p);
|
|
}
|
|
else
|
|
{
|
|
*num_polygons_p = INTEL_INT(*num_polygons_p);
|
|
for (unsigned int i = 0; i < *num_polygons_p; i++)
|
|
{
|
|
shld_polys[i] = INTEL_INT(shld_polys[i]);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void glowpoint_override_defaults(glow_point_bank_override *gpo)
|
|
{
|
|
gpo->name[0] = 0;
|
|
gpo->type = 0;
|
|
gpo->on_time = 0;
|
|
gpo->off_time = 0;
|
|
gpo->disp_time = 0;
|
|
gpo->glow_bitmap = -1;
|
|
gpo->glow_neb_bitmap = -1;
|
|
gpo->is_on = true;
|
|
gpo->type_override = false;
|
|
gpo->on_time_override = false;
|
|
gpo->off_time_override = false;
|
|
gpo->disp_time_override = false;
|
|
gpo->glow_bitmap_override = false;
|
|
gpo->pulse_period_override = false;
|
|
gpo->pulse_type = 0;
|
|
gpo->pulse_period = 0;
|
|
gpo->pulse_amplitude = 1.0f;
|
|
gpo->pulse_bias = 0.0f;
|
|
gpo->pulse_exponent = 1.0f;
|
|
gpo->is_lightsource = false;
|
|
gpo->radius_multi = 15.0f;
|
|
gpo->light_color = vmd_zero_vector;
|
|
gpo->light_mix_color = vmd_zero_vector;
|
|
gpo->lightcone = false;
|
|
gpo->cone_angle = 90.0f;
|
|
gpo->cone_direction = vmd_zero_vector;
|
|
gpo->dualcone = false;
|
|
gpo->rotating = false;
|
|
gpo->rotation_axis = vmd_zero_vector;
|
|
gpo->rotation_speed = 0.0f;
|
|
}
|
|
|
|
SCP_vector<glow_point_bank_override>::iterator get_glowpoint_bank_override_by_name(const char* name)
|
|
{
|
|
SCP_vector<glow_point_bank_override>::iterator gpo = glowpoint_bank_overrides.begin();
|
|
for(;gpo != glowpoint_bank_overrides.end(); ++gpo) {
|
|
if(!strcmp(gpo->name, name)) {
|
|
return gpo;
|
|
}
|
|
}
|
|
return glowpoint_bank_overrides.end();
|
|
}
|
|
|
|
void parse_glowpoint_table(const char *filename)
|
|
{
|
|
try {
|
|
if (cf_exists_full(filename, CF_TYPE_TABLES))
|
|
read_file_text(filename, CF_TYPE_TABLES);
|
|
else
|
|
return;
|
|
|
|
reset_parse();
|
|
|
|
if (!optional_string("#Glowpoint overrides")) {
|
|
return;
|
|
}
|
|
|
|
while (!required_string_either("$Name:", "#End")) {
|
|
glow_point_bank_override gpo;
|
|
glowpoint_override_defaults(&gpo);
|
|
|
|
bool replace = false;
|
|
bool skip = false;
|
|
|
|
required_string("$Name:");
|
|
stuff_string(gpo.name, F_NAME, NAME_LENGTH);
|
|
|
|
if (optional_string("+nocreate")) {
|
|
if (Parsing_modular_table) {
|
|
replace = true;
|
|
}
|
|
else {
|
|
mprintf(("+nocreate specified in non-modular glowpoint table.\n"));
|
|
}
|
|
}
|
|
|
|
if (optional_string("$On:")) {
|
|
stuff_boolean(&gpo.is_on);
|
|
}
|
|
|
|
if (optional_string("$Displacement time:")) {
|
|
stuff_int(&gpo.disp_time);
|
|
gpo.disp_time_override = true;
|
|
}
|
|
|
|
if (optional_string("$On time:")) {
|
|
stuff_int(&gpo.on_time);
|
|
gpo.on_time_override = true;
|
|
}
|
|
|
|
if (optional_string("$Off time:")) {
|
|
stuff_int(&gpo.off_time);
|
|
gpo.off_time_override = true;
|
|
}
|
|
|
|
if (optional_string("$Texture:")) {
|
|
char glow_texture_name[32];
|
|
stuff_string(glow_texture_name, F_NAME, NAME_LENGTH);
|
|
|
|
gpo.glow_bitmap_override = true;
|
|
|
|
if (stricmp(glow_texture_name, "none")) {
|
|
gpo.glow_bitmap = bm_load(glow_texture_name);
|
|
|
|
if (gpo.glow_bitmap < 0)
|
|
{
|
|
Warning(LOCATION, "Couldn't open texture '%s'\nreferenced by glowpoint present '%s'\n", glow_texture_name, gpo.name);
|
|
}
|
|
else
|
|
{
|
|
nprintf(("Model", "Glowpoint preset %s texture num is %d\n", gpo.name, gpo.glow_bitmap));
|
|
}
|
|
|
|
char glow_texture_neb_name[256];
|
|
strncpy(glow_texture_neb_name, glow_texture_name, 256);
|
|
strcat(glow_texture_neb_name, "-neb");
|
|
gpo.glow_neb_bitmap = bm_load(glow_texture_neb_name);
|
|
|
|
if (gpo.glow_neb_bitmap < 0)
|
|
{
|
|
gpo.glow_neb_bitmap = gpo.glow_bitmap;
|
|
nprintf(("Model", "Glowpoint preset nebula texture not found for '%s', using normal glowpoint texture instead\n", gpo.name));
|
|
}
|
|
else
|
|
{
|
|
nprintf(("Model", "Glowpoint preset %s nebula texture num is %d\n", gpo.name, gpo.glow_neb_bitmap));
|
|
}
|
|
}
|
|
else {
|
|
gpo.glow_bitmap_override = true;
|
|
}
|
|
}
|
|
|
|
if (optional_string("$Type:")) {
|
|
stuff_int(&gpo.type);
|
|
gpo.type_override = true;
|
|
}
|
|
|
|
if (optional_string("$Pulse type:")) {
|
|
char pulsetype[33];
|
|
stuff_string(pulsetype, F_NAME, NAME_LENGTH);
|
|
if (!stricmp(pulsetype, "sine")) {
|
|
gpo.pulse_type = PULSE_SIN;
|
|
}
|
|
else if (!stricmp(pulsetype, "cosine")) {
|
|
gpo.pulse_type = PULSE_COS;
|
|
}
|
|
else if (!stricmp(pulsetype, "triangle")) {
|
|
gpo.pulse_type = PULSE_TRI;
|
|
}
|
|
else if (!stricmp(pulsetype, "shiftedtriangle")) {
|
|
gpo.pulse_type = PULSE_SHIFTTRI;
|
|
}
|
|
}
|
|
|
|
if (optional_string("$Pulse period:")) {
|
|
stuff_int(&gpo.pulse_period);
|
|
gpo.pulse_period_override = true;
|
|
}
|
|
|
|
if (optional_string("$Pulse amplitude:")) {
|
|
stuff_float(&gpo.pulse_amplitude);
|
|
}
|
|
|
|
if (optional_string("$Pulse bias:")) {
|
|
stuff_float(&gpo.pulse_bias);
|
|
}
|
|
|
|
if (optional_string("$Pulse exponent:")) {
|
|
stuff_float(&gpo.pulse_exponent);
|
|
}
|
|
|
|
if (optional_string("+light")) {
|
|
gpo.is_lightsource = true;
|
|
|
|
if (optional_string("$Light radius multiplier:")) {
|
|
stuff_float(&gpo.radius_multi);
|
|
}
|
|
|
|
required_string("$Light color:");
|
|
int temp;
|
|
stuff_int(&temp);
|
|
gpo.light_color.xyz.x = temp / 255.0f;
|
|
stuff_int(&temp);
|
|
gpo.light_color.xyz.y = temp / 255.0f;
|
|
stuff_int(&temp);
|
|
gpo.light_color.xyz.z = temp / 255.0f;
|
|
|
|
if (optional_string("$Light mix color:")) {
|
|
stuff_int(&temp);
|
|
gpo.light_mix_color.xyz.x = temp / 255.0f;
|
|
stuff_int(&temp);
|
|
gpo.light_mix_color.xyz.y = temp / 255.0f;
|
|
stuff_int(&temp);
|
|
gpo.light_mix_color.xyz.z = temp / 255.0f;
|
|
}
|
|
|
|
if (optional_string("+lightcone")) {
|
|
gpo.lightcone = true;
|
|
|
|
if (optional_string("$Cone angle:")) {
|
|
stuff_float(&gpo.cone_angle);
|
|
gpo.cone_inner_angle = cosf((gpo.cone_angle - ((gpo.cone_angle < 20.0f) ? gpo.cone_angle*0.5f : 20.0f)) / 180.0f * PI);
|
|
gpo.cone_angle = cosf(gpo.cone_angle / 180.0f * PI);
|
|
}
|
|
|
|
required_string("$Cone direction:");
|
|
stuff_float_list(gpo.cone_direction.a1d, 3);
|
|
if (vm_vec_mag_quick(&gpo.cone_direction) != 0.0f) {
|
|
vm_vec_normalize(&gpo.cone_direction);
|
|
}
|
|
else {
|
|
Warning(LOCATION, "Null vector specified in cone direction for glowpoint override %s. Discarding preset.", gpo.name);
|
|
skip = true;
|
|
}
|
|
if (optional_string("+dualcone")) {
|
|
gpo.dualcone = true;
|
|
}
|
|
|
|
if (optional_string("+rotating")) {
|
|
gpo.rotating = true;
|
|
required_string("$Rotation axis:");
|
|
stuff_float_list(gpo.rotation_axis.a1d, 3);
|
|
if (vm_vec_mag_quick(&gpo.rotation_axis) != 0.0f) {
|
|
vm_vec_normalize(&gpo.rotation_axis);
|
|
}
|
|
else {
|
|
Warning(LOCATION, "Null vector specified in rotation axis for glowpoint override %s. Discarding preset.", gpo.name);
|
|
skip = true;
|
|
}
|
|
required_string("$Rotation speed:");
|
|
stuff_float(&gpo.rotation_speed);
|
|
}
|
|
}
|
|
}
|
|
if (!skip) {
|
|
SCP_vector<glow_point_bank_override>::iterator gpoi = get_glowpoint_bank_override_by_name(gpo.name);
|
|
if (gpoi == glowpoint_bank_overrides.end()) {
|
|
if (!replace) {
|
|
glowpoint_bank_overrides.push_back(gpo);
|
|
}
|
|
}
|
|
else {
|
|
if (!replace) {
|
|
Warning(LOCATION, "+nocreate not specified for glowpoint override that already exists. Discarding duplicate entry: %s", gpo.name);
|
|
}
|
|
else {
|
|
glowpoint_bank_overrides.erase(gpoi);
|
|
glowpoint_bank_overrides.push_back(gpo);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
required_string("#End");
|
|
} catch (const parse::ParseException& e) {
|
|
mprintf(("Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void glowpoint_init()
|
|
{
|
|
glowpoint_bank_overrides.clear();
|
|
parse_glowpoint_table("glowpoints.tbl");
|
|
parse_modular_table(NOX("*-gpo.tbm"), parse_glowpoint_table);
|
|
}
|
|
|
|
void model_subsystem::reset()
|
|
{
|
|
flags.reset();
|
|
memset(name, 0, sizeof(name));
|
|
memset(subobj_name, 0, sizeof(alt_dmg_sub_name));
|
|
memset(alt_sub_name, 0, sizeof(alt_sub_name));
|
|
memset(alt_dmg_sub_name, 0, sizeof(alt_dmg_sub_name));
|
|
subobj_num = 0;
|
|
model_num = 0;
|
|
type = 0;
|
|
pnt.xyz.x = pnt.xyz.y = pnt.xyz.z = 0.0f;
|
|
radius = 0;
|
|
|
|
max_subsys_strength = 0;
|
|
armor_type_idx = 0;
|
|
|
|
memset(crewspot, 0, sizeof(crewspot));
|
|
turret_norm.xyz.x = turret_norm.xyz.y = turret_norm.xyz.z = 0.0f;
|
|
|
|
for (auto it = std::begin(turret_matrix.a1d); it != std::end(turret_matrix.a1d); ++it)
|
|
*it = 0.0f;
|
|
|
|
turret_fov = 0;
|
|
turret_max_fov = 0;
|
|
turret_y_fov = 0;
|
|
turret_num_firing_points = 0;
|
|
for (auto it = std::begin(turret_firing_point); it != std::end(turret_firing_point); ++it)
|
|
it->xyz.x = it->xyz.y = it->xyz.z = 0.0f;
|
|
turret_gun_sobj = 0;
|
|
turret_turning_rate = 0;
|
|
turret_base_rotation_snd = 0;
|
|
turret_base_rotation_snd_mult = 0;
|
|
turret_gun_rotation_snd = 0;
|
|
turret_gun_rotation_snd_mult = 0;
|
|
|
|
alive_snd = 0;
|
|
dead_snd = 0;
|
|
rotation_snd = 0;
|
|
|
|
engine_wash_pointer = NULL;
|
|
turn_rate = 0;
|
|
weapon_rotation_pbank = 0;
|
|
stepped_rotation = NULL;
|
|
|
|
awacs_intensity = 0.0f;
|
|
awacs_radius = 0.0f;
|
|
|
|
for (auto it = std::begin(primary_banks); it != std::end(primary_banks); ++it)
|
|
*it = 0;
|
|
for (auto it = std::begin(primary_bank_capacity); it != std::end(primary_bank_capacity); ++it)
|
|
*it = 0;
|
|
for (auto it = std::begin(secondary_banks); it != std::end(secondary_banks); ++it)
|
|
*it = 0;
|
|
for (auto it = std::begin(secondary_bank_capacity); it != std::end(secondary_bank_capacity); ++it)
|
|
*it = 0;
|
|
|
|
path_num = 0;
|
|
|
|
n_triggers = 0;
|
|
triggers = NULL;
|
|
|
|
turret_reset_delay = 0;
|
|
|
|
for (auto it = std::begin(target_priority); it != std::end(target_priority); ++it)
|
|
*it = 0;
|
|
|
|
num_target_priorities = 0;
|
|
|
|
optimum_range = 0;
|
|
favor_current_facing = 0;
|
|
|
|
turret_rof_scaler = 0;
|
|
|
|
turret_max_bomb_ownage = 0;
|
|
turret_max_target_ownage = 0;
|
|
}
|