Descent3/model/polymodel.cpp
2024-09-03 13:26:51 +02:00

3235 lines
92 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/model/polymodel.cpp $
* $Revision: 152 $
* $Date: 10/08/01 4:20p $
* $Author: Matt $
*
* Jason should put something here
*
* $Log: /DescentIII/Main/model/polymodel.cpp $
*
* 152 10/08/01 4:20p Matt
* Added system to check for errors when reading in add-on data.
*
* 151 10/01/01 1:58p Matt
* In release build, Increment the used count when loading a model that's
* already in memory. This was already done in non-release builds.
*
* 150 5/10/00 2:43p Jeff
* fixed bug when paging in data if the tablefile specifies a full path to
* a primative instead of just a filename (bug in 3rd party tablefile
* editors)
*
* 149 4/19/00 5:34p Matt
* From Duane for 1.4
* Check for glow_info memory already allocated before mallocing
*
* 148 10/21/99 2:40p Kevin
* Mac merge
*
* 147 10/16/99 6:40p Jeff
* swaped way of determining textures for objects on load
*
* 146 10/14/99 3:02p Jeff
* correctly look up texture names
*
* 145 7/08/99 5:48p Jason
* changes for new bumpmapping system in 1.1 update patch
*
* 144 6/08/99 1:00p Jason
* changes for bumpmapping
*
* 143 5/18/99 10:31a Jason
* polymodel stuff wasn't getting paged in before drawing
*
* 142 5/14/99 2:04p Jason
* added polymodel errors to catch bugs
*
* 141 4/28/99 1:55a Samir
* error instead of assert when we can't open a model for paging in a
* polymodel.
*
* 140 4/19/99 3:50p Jeff
* fixed mem_free/copy-paste bug, wasn't setting the correct pointer to
* NULL after free
*
* 139 4/19/99 4:00a Jeff
* fixed min/max for Linux
*
* 138 4/09/99 12:51p Jason
* fixed bug with new lookup code
*
* 137 4/09/99 12:06p Jason
* made model setup code faster
*
* 136 4/08/99 11:45a Jason
* greatly sped up the time it takes to get model anges/positions by
* precalculation
*
* 135 3/05/99 11:24a Jason
* fixed pesky no-lightmap drawing problem that has plagued me for the
* last 10 months
*
* 134 2/16/99 12:42p Kevin
* Improvements to the paging data progress indicator
*
* 133 2/10/99 4:58p Jeff
* put assert if pageinpolymodel file open fails
*
* 132 2/09/99 7:01p Kevin
* First work for new and improved progress screen while loading a level.
* Note that this is a hack at this point, while I get the details worked
* out, then I'll make it cleaner.
*
* 131 1/15/99 3:04p Jason
* fixed polymodel data freeing problem
*
* 130 1/13/99 12:43p Jason
* added some more detail settings
*
* 129 10/16/98 1:54p Kevin
* Changes for Demo Beta 4
*
* 128 10/13/98 5:25p Jason
* fixed zero faced submodel problem
*
* 127 10/12/98 10:59a Jason
* fixed zero length malloc
*
* 126 10/12/98 9:35a Jason
* New polymodel memory scheme
*
* 125 10/08/98 4:24p Kevin
* Changed code to comply with memory library usage. Always use mem_malloc
* , mem_free and mem_strdup
*
* 124 10/08/98 2:27p Jason
* sped up table file loading
*
* 123 10/07/98 5:29p Chris
* Added SOF_THRUSTER support
*
* 122 10/07/98 3:40p Chris
* Added $thruster
*
* 121 9/11/98 4:04p Jason
* added better multitexture support
*
* 120 8/27/98 11:16a Jason
* fixed math bug in GetPolymodelPointInWorld
*
* 119 8/25/98 12:22p Jason
* more changes for cockpits
*
* 118 8/24/98 6:43p Keneta
* Long proofed the turrets and weapon batteries
*
* 117 8/24/98 2:44p Jason
* checked in so Samir can test
*
* 116 8/21/98 5:14p Jason
* made better memory use of primitives
*
* 115 8/18/98 11:38a Jason
* fixed polymodel fog lighting
*
* 114 8/18/98 11:26a Matt
* Changed outline code to only outline polymodel objects when they're
* being rendered in the mine, and not when they're being drawn in the
* editor, main menu, cockpit, etc.
*
* 113 8/17/98 12:10p Chris
* Fixed MAJOR bug in getting gunpoint positions
*
* 112 8/16/98 9:52p Chris
* Added some error checking for the attach system
*
* 111 8/14/98 4:00p Jason
* added specular objects outside
*
* 110 8/13/98 6:56p Jason
* made objects foggable correctly
*
* 109 8/12/98 12:04p Chris
* Attach system version .5 (still needs more work to be multiplayer
* friendly)
*
* 108 8/07/98 2:49p Keneta
* fixed number of subobjects assertion
*
* 107 8/04/98 2:32p Chris
* Improved attach code added more fixes to the AABB partial computation
* patch
*
* 106 7/31/98 11:52a Chris
* Weapons can be persistent. Added ability for objects to be manually
* set for no object collisions.
*
* 105 7/30/98 1:13p Chris
* Improved attach point code
*
* 104 7/29/98 5:05p Chris
* Made more of the wb info dynamically allocated. Added Attach points to
* the polymodel structure
*
* 103 7/27/98 10:39a Jason
* added customizable skins
*
* 102 7/02/98 2:49p Chris
*
* 101 7/02/98 2:47p Chris
* Dynamic weapon info is now dynamically allocated
* Dynamic weapon info is now dynamically allocated
*
* 100 6/15/98 4:00p Jason
* replaced monochromatic polymodel lighting with rgb lighting
*
* 99 5/27/98 5:17p Jason
* fixed some bugs for the E3 Demo
*
* 98 5/15/98 3:24p Keneta
*
* 97 4/30/98 11:32a Chris
* ClearWB to WBClear
*
* 96 4/24/98 1:35p Jason
* Don't sort non-alphaed polymodels
*
* 95 4/22/98 12:10p Chris
* Fixed path length problems
*
* 94 4/17/98 12:45p Jason
* various changes for multiplayer
*
* 93 4/15/98 3:28p Jason
* changed glow stuff to work with new system
*
* 92 4/07/98 3:35p Jason
* possible fix for polymodel paging bug
*
* 91 4/06/98 3:03p Jason
* added polygon rendering overlays
*
* 90 4/03/98 12:23p Jason
* dealt with overlay types being loaded from disk more than once
*
* 89 4/03/98 11:55a Jason
* fixed polymodel paging problem
*
* 88 4/03/98 10:07a Chris
* Added support for objects getting their size computed when the
* polymodel is paged in the first time as an object
*
* 87 4/02/98 3:54p Jason
* first pass in getting polymodel paging to work
*
* 86 4/01/98 6:23p Jason
* added a slew of stuff for multiplayer
*
* 85 3/31/98 3:49p Jason
* added memory lib
*
* 84 3/31/98 11:12a Brent
* added assert for max textures
*
* 83 3/30/98 12:27a Jason
* fixed memory leaks as reported by BoundsChecker
*
* 82 3/25/98 5:51p Chris
* Added full model/body animations for weapon firing
*
* 81 3/24/98 2:12p Brent
* fixed another glowcount bug
*
* 80 3/24/98 12:47p Brent
* fixed glow bug
*
* 79 3/24/98 12:38p Brent
* added assert
*
* 78 3/23/98 10:03a Chris
* Added independent wb animations
*
* 77 3/19/98 4:30p Samir
* added ability to mark subobjects as layered.
*
* 76 3/18/98 6:24p Samir
* Fixed some bugs with normalizing MONITORs and added VIEWER flag.
*
* 75 3/16/98 11:34a Chris
* Polymodels have 3 levels of AABB (object, subobject, and face level)
*
* 74 3/10/98 2:03p Chris
* Added support for 0.0 angle fov
*
* 73 2/16/98 4:05p Jason
* render polymodels sorted or unsorted depending on whether or not they
* have alpha faces
*
* 72 2/16/98 2:49p Chris
* Made the MAX_SUBOBJECTS determine the number of normalized_time values
* to be processed. No longer a 'literal' problem.
*
* 71 2/16/98 2:25p Jason
* fixed bad indexing into subobjects problem
*
* 70 2/16/98 2:12p Jason
* fixed bug with lightmaps and door shells
*
* 69 2/13/98 6:59p Chris
* FIxed the normalized_time array from being smaller than the number of
* subobjects. Also update newstyle_fi.cpp
*
* 68 2/13/98 12:44p Jason
* upped max number of subobjects
*
* 67 2/10/98 3:50p Jason
* added pulsing walls
*
* 66 2/06/98 8:08p Jason
* fixed size problem with facing subobjects
*
* 65 2/06/98 6:44p Jason
* made polymodels replace themselves if they find the same name
*
* 64 2/06/98 4:33p Jason
* fixed facing model sizes
*
* 63 2/06/98 1:45p Jason
* No really, I fixed the min max problem.
*
* 62 2/06/98 12:53p Jason
* fixed object min max stuff
*
* 61 2/04/98 9:28p Jason
* added the ability to have models that always face you
*
* 60 1/26/98 4:32p Jason
* took out some goofy mprintfs
*
* 59 1/23/98 5:39p Matt
* Removed some door frontface error checking now more thoroughly handled
* in WorldObjectsDoorDialog.cpp
*
* 58 1/23/98 11:16a Luke
* Remove a really old Assert that limitted which types could animate
*
* 57 1/13/98 3:46p Jason
* fixed memory corruption bug introduced by my last rev
*
* 56 1/13/98 3:09p Jason
* added glow effect for engines
*
* 55 1/02/98 1:03p Jason
* fixed memory deallocation leak in FreePolymodel
*
* 54 12/31/97 3:34p Jason
* added alpha per vertex for polymodels
*
* 53 12/08/97 6:18p Jason
* more tweaks for destroyable buildings
*
* 52 11/17/97 6:28p Mark
* FROM Jason:fixed memory leak
*
* 51 11/17/97 12:02p Jason
* added extra newstyle variable set
*
* 50 11/10/97 3:33p Jason
* ignore whitespace
*
* 49 11/04/97 4:58p Samir
* Added 4 more monitors.
*
* 48 10/30/97 10:53a Jason
* set color model to be MONO when drawing gouraud shaded polymodels
*
* 47 10/28/97 6:37p Samir
* Submodels now can be monitors.
*
* 46 10/28/97 11:10a Jason
* tweaked subojbect rotation
*
* 45 10/24/97 11:57a Jason
* fixed memory leak
*
* 44 10/23/97 2:07p Jason
* fixed some problems with debris not spinning about their center point
*
* 43 10/20/97 4:46p Jason
* changes for explosions
*
* 42 10/06/97 6:39p Jason
* fixed frame min/max problem
*
* 41 10/05/97 5:30a Chris
* Added more support for TIMED animations
*
* 40 10/01/97 7:00p Jason
* did more work on object lightmaps
*
* 39 9/30/97 6:40p Jason
* got lightmap stuff sort of working with objects
*
* 38 9/29/97 3:48p Jason
* added more code to support new timed models
*
* 37 9/26/97 12:09p Jason
* made positional/rotational animations have differing
* track_min/track_max
*
* 36 9/25/97 4:54p Jason
* added timer info to polymodels
*
* 35 9/18/97 1:27p Matt
* Cleaned up object struct
*
* 34 9/17/97 10:59a Chris
* Added a new way to compute radi
*
* 33 9/15/97 11:11a Jason
* fixed mins/maxs to be based on the center of the model
*
* 32 9/12/97 5:38p Jason
* got doors working
*
* 31 9/11/97 5:38p Jason
* initial door coding for room engine
*
* 30 9/10/97 6:01p Jason
* fixed double used variable bug
*
* 29 9/10/97 5:17p Jason
* more lighting improvements
*
* 28 9/09/97 6:55p Jason
* better error checking for model usage counts
*
* 27 9/09/97 6:15p Jason
* made dynamic lighting on objects more memory efficient
*
* 26 9/09/97 11:05a Jason
* fixed wacky bizarre problem with instancing and positional
* interpolation
*
* 25 9/05/97 1:29p Jason
* revamped generic object lighting
*
* 24 9/04/97 5:38p Jason
* fixed dumb bug that was fixed a long time ago...model positional
* interpolation
*
* 23 9/04/97 3:49p Chris
* Added additional turret information from pofgen, added ground plane
* stuff
*
* 22 9/03/97 4:41p Chris
* Moved some code so that pofview will compile
*
* 21 9/03/97 2:12p Chris
* Added new weapon battery system and made the animation system usable.
*
* 20 8/25/97 6:19p Chris
* Added support for knowing which subobject a gun point is linked to
*
* 19 8/24/97 1:40p Jason
* don't compute normals on model load...let pofgen do it
*
* 18 8/22/97 12:13p Jason
* made rotators work on keyframe 1 instead of keyframe 0 since max
* insists on some weird "start axis" for keyframe 0
*
* 17 8/20/97 4:24p Matt
* Removed unused code
*
* 16 8/20/97 3:16p Chris
* Added some hooks for turrets
*
* 15 8/08/97 4:38p Jason
* added sliding textures for models
*
* 14 8/08/97 2:30p Jason
* added error checking for rotators without keyframes
*
* 13 8/08/97 10:58a Jason
* fixed out of bounds error
*
* 12 8/07/97 3:19p Jason
* only remap models when explicitely told to
*
* 11 8/06/97 3:07p Jason
* fixed problem where polymodels were being freed twice
*
* 10 8/06/97 12:40p Jason
* fixed some potentially serious memory problems
*
* 9 8/05/97 5:18p Jason
* fixed circular dependencies with child/parents
*
* 8 8/03/97 2:25p Jason
* made polymodels use less memory
*
* 7 7/28/97 1:14p Chris
* Added support for sub-object visibility. Plus, debris.
*
* 6 7/23/97 11:48a Jason
* added support for newstyle pof format
*
* 5 7/22/97 1:34a Jason
* added code to support newstyle polymodels
*
* 4 7/15/97 4:27p Mark
*
* 36 6/24/97 6:13p Matt
* Got rid of DrawPolygonObject(), since it required the inclusion of
* object.h, which is a main directory header file (& thus shouldn't be
* accessible by a library).
*
* 34 5/20/97 7:21p Jason
* fixed stupid off-by-one bug
*
* 33 5/20/97 5:52p Jason
* tweaked a couple of things with magnitude division
*
* 32 5/19/97 5:10p Jason
* changes for our new abstracted renderer code
*
* 31 5/19/97 11:46a Jason
* normalize rotation vectors
*
* 30 5/16/97 3:13p Jason
* fixed more problems with model rotation
*
* 29 5/16/97 1:24p Jason
* changed the way models work their angle magic
* Now it is much more intuitive
*
* 28 5/16/97 12:04p Jason
* better memory use for rotational keyframes
*
* 27 5/15/97 4:23p Jason
* swap interpreted model data for mac
*
* 26 5/14/97 11:57p Jason
* made polymodels use far less memory
*
* 25 5/14/97 7:45p Jason
* fixed polymodel bug where it would not get deallocated correctly
*
*
* 24 5/13/97 5:59p Jason
* fixed yet another local transform bug
*
* 23 5/13/97 12:43p Jason
* fixed a delta rotation problem, plus add interpolated rotational axis
*
* 22 5/08/97 5:11p Jason
* fixed bug with animation rotation keyframes
*
* 21 5/08/97 1:16p Jason
* made ChangeEndName work with device independent calls
*
* 20 4/30/97 5:43p Jason
* remap polymodels when pagefile is done loading
*
* 19 4/28/97 6:46p Jason
* made ships have multiple gun points
*
* 18 4/21/97 5:29p Jason
* got animating textures to work on polygonal objects
*
* 17 4/21/97 4:09p Jason
* added GetNormalizedKeyframe function
*
* 16 4/04/97 11:48a Jason
* render polygon objects as linear maps
*
* 15 4/02/97 5:21p Jason
* added the ability for pages to free the models that they point to
*
* 14 3/26/97 3:29p Jason
* made robots work/render
*
* 13 3/18/97 5:22p Jason
* took out dumb mprintfs
*
* 12 3/14/97 5:54p Jason
* made positional interpolation work with 3d doors
*
* 11 3/14/97 10:55a Jason
* made polymodels use delta angles instead of absolute angles
*
* 10 3/13/97 6:13p Jason
* got poly doors working
*
* 9 3/07/97 3:59p Samir
* Took out include to memory.h for portability.
*
* 8 3/05/97 3:10p Jason
* added FreeAllModels call
*
* 7 3/05/97 12:17p Jason
* took out autoloading of the ship.pof upon startup
*
* 6 3/04/97 11:52a Jason
* made polymodels remap correctly
*
* 5 3/03/97 6:21p Matt
* Changed cfile functions to use D3 naming convention
*
* $NoKeywords: $
*/
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include "3d.h"
#include "cfile.h"
#include "game.h"
#include "gamesequence.h"
#include "mem.h"
#include "mono.h"
#include "objinfo.h"
#include "polymodel.h"
#include "pserror.h"
int Num_poly_models = 0;
poly_model Poly_models[MAX_POLY_MODELS];
g3Point Robot_points[MAX_POLYGON_VECS];
vector Interp_pos_instance_vec = {0, 0, 0};
vector Instance_vec_stack[MAX_SUBOBJECTS];
int Instance_vec_cnt = 0;
#ifdef _DEBUG
// Flag to draw an outline around the faces
bool Polymodel_outline_mode = false;
#endif
#define ID_OHDR 'RDHO' // POF file header
#define ID_SOBJ 'JBOS' // Subobject header
#define ID_IDTA 'ATDI' // Interpreter data
#define ID_TXTR 'RTXT' // Texture filename list
#define ID_INFO 'FNIP' // POF file information, like command line, etc
#define ID_GRID 'DIRG' // Grid information
#define ID_GPNT 'TNPG' // gun points
#define ID_ROT_ANIM 'INAR' // angular animation data
#define ID_POS_ANIM 'INAP' // positional animation data
#define ID_ANIM 'MINA' // angular information
#define ID_WBS 'TABW' // Weapon Battery Info
#define ID_GROUND 'DNRG' // Ground Plane info
#define ID_ATTACH 'HCTA' // Attach points
#define ID_ATTACH_NORMALS 'HTAN' // Attach uvecs
int Polymodel_use_effect = 0;
polymodel_effect Polymodel_effect;
polymodel_light_type Polymodel_light_type = POLYMODEL_LIGHTING_STATIC;
float Polylighting_static_red;
float Polylighting_static_green;
float Polylighting_static_blue;
lightmap_object *Polylighting_lightmap_object;
vector *Polymodel_light_direction, Polymodel_fog_plane, Polymodel_specular_pos, Polymodel_fog_portal_vert,
Polymodel_bump_pos;
static inline void RecursiveAssignWB(poly_model *pm, int sm_index, int wb_index);
static void FindWBSubobjects(poly_model *pm);
/// Sets aside a polymodel for use.
/// Errors and returns -1 if none free.
static int AllocPolyModel();
static void ReadModelVector(vector *vec, CFILE *infile);
static void ReadModelStringLen(char *ptr, int len, CFILE *infile);
/// Given a modelnumber, opens the original pof file and attempts to rematch that
/// models textures with the bitmaps with have in memory.
static int ReloadModelTextures(int modelnum);
static void SetPolymodelProperties(bsp_info *subobj, char *props);
static void MinMaxSubmodel(poly_model *pm, bsp_info *sm, vector offset);
static void FindMinMaxForModel(poly_model *pm);
static int ReadNewModelFile(int polynum, CFILE *infile);
static void SetNormalizedTimeObjTimed(object *obj, float *normalized_time);
static void SetNormalizedTimeAnimTimed(float frame, float *normalized_time, poly_model *pm);
static void FreeAllModels();
/// Given a model pointer and an array of floats that go from 0..1, calculate the interpolated
/// position/angle of each corresponding subobject.
static void SetModelAnglesAndPosTimed(poly_model *po, float *normalized_time, uint32_t subobj_flags);
static void BuildModelAngleMatrix(matrix *mat, angle ang, vector *axis);
void WBClearInfo(poly_model *pm) { pm->num_wbs = 0; }
inline void RecursiveAssignWB(poly_model *pm, int sm_index, int wb_index) {
int flags;
int i;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
flags = wb_index << WB_INDEX_SHIFT;
pm->submodel[sm_index].flags |= flags | SOF_WB;
for (i = 0; i < pm->submodel[sm_index].num_children; i++) {
RecursiveAssignWB(pm, pm->submodel[sm_index].children[i], wb_index);
}
}
void FindWBSubobjects(poly_model *pm) {
int i;
bool found;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
for (i = 0; i < pm->n_models; i++) {
if (pm->submodel[i].flags & SOF_TURRET) {
int j;
found = false;
for (j = 0; j < pm->num_wbs; j++) {
int k;
for (k = 0; k < pm->poly_wb[j].num_turrets; k++) {
if (pm->poly_wb[j].turret_index[k] == i) {
found = true;
RecursiveAssignWB(pm, i, j);
}
if (found)
break;
}
if (found)
break;
}
}
}
}
// Sets aside a polymodel for use
// Errors and returns -1 if none free
int AllocPolyModel() {
for (int i = 0; i < MAX_POLY_MODELS; i++)
if (Poly_models[i].used == 0) {
WBClearInfo(&Poly_models[i]);
memset(&Poly_models[i], 0, sizeof(poly_model));
Poly_models[i].used = 1;
Poly_models[i].flags |= PMF_NOT_RESIDENT; // not in memory yet!
return i;
}
Error("Couldn't find a free polymodel!");
return -1;
}
// Frees all the polymodel data, but doesn't free the actual polymodel itself
void FreePolymodelData(int i) {
int t;
for (t = 0; t < Poly_models[i].n_models; t++) {
if (Poly_models[i].submodel) {
if (Poly_models[i].submodel[t].keyframe_axis) {
mem_free(Poly_models[i].submodel[t].keyframe_axis);
Poly_models[i].submodel[t].keyframe_axis = nullptr;
}
if (Poly_models[i].submodel[t].tick_pos_remap) {
mem_free(Poly_models[i].submodel[t].tick_pos_remap);
Poly_models[i].submodel[t].tick_pos_remap = nullptr;
}
if (Poly_models[i].submodel[t].tick_ang_remap) {
mem_free(Poly_models[i].submodel[t].tick_ang_remap);
Poly_models[i].submodel[t].tick_ang_remap = nullptr;
}
if (Poly_models[i].submodel[t].keyframe_angles) {
mem_free(Poly_models[i].submodel[t].keyframe_angles);
Poly_models[i].submodel[t].keyframe_angles = nullptr;
}
if (Poly_models[i].submodel[t].keyframe_matrix) {
mem_free(Poly_models[i].submodel[t].keyframe_matrix);
Poly_models[i].submodel[t].keyframe_matrix = nullptr;
}
if (Poly_models[i].submodel[t].keyframe_pos) {
mem_free(Poly_models[i].submodel[t].keyframe_pos);
Poly_models[i].submodel[t].keyframe_pos = nullptr;
}
if (Poly_models[i].submodel[t].verts) {
mem_free(Poly_models[i].submodel[t].verts);
Poly_models[i].submodel[t].verts = nullptr;
}
if (Poly_models[i].submodel[t].vertnorms) {
mem_free(Poly_models[i].submodel[t].vertnorms);
Poly_models[i].submodel[t].vertnorms = nullptr;
}
if (Poly_models[i].submodel[t].alpha) {
mem_free(Poly_models[i].submodel[t].alpha);
Poly_models[i].submodel[t].alpha = nullptr;
}
if (Poly_models[i].submodel[t].vertnum_memory) {
mem_free(Poly_models[i].submodel[t].vertnum_memory);
Poly_models[i].submodel[t].vertnum_memory = nullptr;
}
if (Poly_models[i].submodel[t].u_memory) {
mem_free(Poly_models[i].submodel[t].u_memory);
Poly_models[i].submodel[t].u_memory = nullptr;
}
if (Poly_models[i].submodel[t].v_memory) {
mem_free(Poly_models[i].submodel[t].v_memory);
Poly_models[i].submodel[t].v_memory = nullptr;
}
if (Poly_models[i].flags & PMF_TIMED) {
if (Poly_models[i].submodel[t].rot_start_time) {
mem_free(Poly_models[i].submodel[t].rot_start_time);
Poly_models[i].submodel[t].rot_start_time = nullptr;
}
if (Poly_models[i].submodel[t].pos_start_time) {
mem_free(Poly_models[i].submodel[t].pos_start_time);
Poly_models[i].submodel[t].pos_start_time = nullptr;
}
}
if (Poly_models[i].submodel[t].flags & (SOF_GLOW | SOF_THRUSTER)) {
mem_free(Poly_models[i].submodel[t].glow_info);
Poly_models[i].submodel[t].glow_info = nullptr;
}
if (Poly_models[i].submodel[t].faces) {
mem_free(Poly_models[i].submodel[t].faces);
Poly_models[i].submodel[t].faces = nullptr;
if (Poly_models[i].submodel[t].face_min) {
mem_free(Poly_models[i].submodel[t].face_min);
Poly_models[i].submodel[t].face_min = nullptr;
}
if (Poly_models[i].submodel[t].face_max) {
mem_free(Poly_models[i].submodel[t].face_max);
Poly_models[i].submodel[t].face_max = nullptr;
}
}
}
}
if (Poly_models[i].model_data) {
mem_free(Poly_models[i].model_data);
Poly_models[i].model_data = nullptr;
}
if (Poly_models[i].gun_slots) {
mem_free(Poly_models[i].gun_slots);
Poly_models[i].gun_slots = nullptr;
}
if (Poly_models[i].poly_wb) {
mem_free(Poly_models[i].poly_wb);
Poly_models[i].poly_wb = nullptr;
}
if (Poly_models[i].attach_slots) {
mem_free(Poly_models[i].attach_slots);
Poly_models[i].attach_slots = nullptr;
}
if (Poly_models[i].ground_slots) {
mem_free(Poly_models[i].ground_slots);
Poly_models[i].ground_slots = nullptr;
}
if (Poly_models[i].submodel) {
mem_free(Poly_models[i].submodel);
Poly_models[i].submodel = nullptr;
}
Poly_models[i].flags |= PMF_NOT_RESIDENT;
Poly_models[i].n_models = 0;
}
// Frees polymodel located in index of Poly_models array
void FreePolyModel(int i) {
ASSERT(Poly_models[i].used > 0);
Poly_models[i].used--;
if (Poly_models[i].used)
return;
FreePolymodelData(i);
Poly_models[i].used = 0;
Poly_models[i].flags |= PMF_NOT_RESIDENT;
}
void ReadModelVector(vector *vec, CFILE *infile) {
vec->x = cf_ReadFloat(infile);
vec->y = cf_ReadFloat(infile);
vec->z = cf_ReadFloat(infile);
}
void ReadModelStringLen(char *ptr, int len, CFILE *infile) {
int i;
int mlen = cf_ReadInt(infile);
for (i = 0; i < mlen; i++)
ptr[i] = cf_ReadByte(infile);
}
// Given a modelnumber, opens the original pof file and attempts to rematch that
// models textures with the bitmaps with have in memory
int ReloadModelTextures(int modelnum) {
CFILE *infile;
int done = 0, id, len;
poly_model *pm = &Poly_models[modelnum];
ASSERT(!(Poly_models[modelnum].flags & PMF_NOT_RESIDENT));
infile = cfopen(Poly_models[modelnum].name, "rb");
if (!infile)
return 0;
id = cf_ReadInt(infile);
if (id != 'OPSP')
Error("Bad ID in model file!");
cf_ReadInt(infile); // skip version
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
id = cf_ReadInt(infile); // read chunk type
len = cf_ReadInt(infile); // read chunk length
switch (id) {
case ID_TXTR: {
// Texture filename list
int i, n;
char name_buf[128];
n = cf_ReadInt(infile);
if (n != pm->n_textures) {
Int3(); // Get Jason, new model doesn't match old model!!!
cfclose(infile);
return 0;
}
for (i = 0; i < n; i++) {
int ret;
char temp[256];
// Read the name of this texture
ReadModelStringLen(name_buf, 127, infile);
strcpy(temp, name_buf);
strcat(temp, ".OGF");
ret = FindTextureBitmapName(temp);
if (ret == -1) {
// See if it is already in memory
ret = FindTextureName(name_buf);
if (ret == -1) {
ret = 0;
// mprintf(0,"Object texture %s is not in memory!\n",name_buf);
}
}
pm->textures[i] = ret;
if (GameTextures[ret].alpha < .99)
pm->flags |= PMF_ALPHA;
}
done = 1;
break;
}
default:
cfseek(infile, len, SEEK_CUR);
break;
}
}
cfclose(infile);
return 1;
}
void BuildModelAngleMatrix(matrix *mat, angle ang, vector *axis) {
float x, y, z;
float s, c, t;
x = axis->x;
y = axis->y;
z = axis->z;
s = (float)FixSin(ang);
c = (float)FixCos(ang);
t = 1.0f - c;
mat->rvec.x = t * x * x + c;
mat->rvec.y = t * x * y + s * z;
mat->rvec.z = t * x * z - s * y;
mat->uvec.x = t * x * y - s * z;
mat->uvec.y = t * y * y + c;
mat->uvec.z = t * y * z + s * x;
mat->fvec.x = t * x * z + s * y;
mat->fvec.y = t * y * z - s * x;
mat->fvec.z = t * z * z + c;
}
void SetPolymodelProperties(bsp_info *subobj, char *props) {
// first, extract the command
int len, i;
char command[200], data[200];
len = strlen(props);
if (len < 3)
return; // nothing to set!
for (i = 0; i < len; i++) {
command[i] = props[i];
if (command[i] == '=') {
i++;
break; // we've found some data!
}
}
command[i] = 0;
if (len != i) {
int datastart = i;
for (; i < len; i++) {
data[i - datastart] = props[i];
}
data[i - datastart] = 0;
}
// now act on the command/data pair
if (!stricmp(command, "$rotate=")) {
// constant rotation for a subobject
float spinrate = atof(data);
if (spinrate <= 0 || spinrate > 20)
return; // bad data
subobj->flags |= SOF_ROTATE;
subobj->rps = 1.0f / spinrate;
return;
}
if (!strnicmp(command, "$jitter", 7)) {
// this subobject is a jittery object
subobj->flags |= SOF_JITTER;
return;
}
if (!strnicmp(command, "$shell", 6)) {
// this subobject is a door shell
subobj->flags |= SOF_SHELL;
return;
}
if (!strnicmp(command, "$facing", 7)) {
// this subobject always faces you
subobj->flags |= SOF_FACING;
return;
}
if (!strnicmp(command, "$frontface", 10)) {
// this subobject is a door front
subobj->flags |= SOF_FRONTFACE;
return;
}
if (!stricmp(command, "$glow=")) {
float r, g, b;
float size;
int num_found;
ASSERT(!(subobj->flags & (SOF_GLOW | SOF_THRUSTER)));
num_found = sscanf(data, " %f, %f, %f, %f", &r, &g, &b, &size);
ASSERT(num_found == 4);
subobj->flags |= SOF_GLOW;
if (subobj->glow_info == nullptr) // DAJ may already exist
subobj->glow_info = (glowinfo *)mem_malloc(sizeof(glowinfo));
subobj->glow_info->glow_r = r;
subobj->glow_info->glow_g = g;
subobj->glow_info->glow_b = b;
subobj->glow_info->glow_size = size;
return;
}
if (!stricmp(command, "$thruster=")) {
float r, g, b;
float size;
int num_found;
ASSERT(!(subobj->flags & (SOF_GLOW | SOF_THRUSTER)));
num_found = sscanf(data, " %f, %f, %f, %f", &r, &g, &b, &size);
ASSERT(num_found == 4);
subobj->flags |= SOF_THRUSTER;
if (subobj->glow_info == nullptr) // DAJ may already exist
subobj->glow_info = (glowinfo *)mem_malloc(sizeof(glowinfo));
subobj->glow_info->glow_r = r;
subobj->glow_info->glow_g = g;
subobj->glow_info->glow_b = b;
subobj->glow_info->glow_size = size;
return;
}
if (!stricmp(command, "$fov=")) {
float fov_angle;
float turret_spr;
float reaction_time;
int num_found;
num_found = sscanf(data, " %f, %f, %f", &fov_angle, &turret_spr, &reaction_time);
ASSERT(num_found == 3);
if (fov_angle < 0.0f || fov_angle > 360.0f) {
// Bad data
ASSERT(0);
fov_angle = 1.0;
}
// .4 is really fast and really arbitrary
if (turret_spr < 0.0f || turret_spr > 60.0f) {
// Bad data
ASSERT(0);
turret_spr = 1.0f;
}
// 10 seconds is really slow and really arbitrary
if (reaction_time < 0.0f || reaction_time > 10.0f) {
// Bad data
ASSERT(0);
reaction_time = 10.0f;
}
subobj->flags |= SOF_TURRET;
subobj->fov = fov_angle / 720.0f; // 720 = 360 * 2 and we want to make fov the amount we can move in either
// direction it has a minimum value of (0.0) to [0.5]
subobj->rps = 1.0f / turret_spr; // convert spr to rps (rotations per second)
subobj->think_interval = reaction_time;
return;
}
if (!stricmp(command, "$monitor01")) {
// this subobject is a monitor
subobj->flags |= SOF_MONITOR1;
return;
}
if (!stricmp(command, "$monitor02")) {
// this subobject is a 2nd monitor
subobj->flags |= SOF_MONITOR2;
return;
}
if (!stricmp(command, "$monitor03")) {
// this subobject is a 3rd monitor
subobj->flags |= SOF_MONITOR3;
return;
}
if (!stricmp(command, "$monitor04")) {
// this subobject is a 4th monitor
subobj->flags |= SOF_MONITOR4;
return;
}
if (!stricmp(command, "$monitor05")) {
// this subobject is a 4th monitor
subobj->flags |= SOF_MONITOR5;
return;
}
if (!stricmp(command, "$monitor06")) {
// this subobject is a 4th monitor
subobj->flags |= SOF_MONITOR6;
return;
}
if (!stricmp(command, "$monitor07")) {
// this subobject is a 4th monitor
subobj->flags |= SOF_MONITOR7;
return;
}
if (!stricmp(command, "$monitor08")) {
// this subobject is a 4th monitor
subobj->flags |= SOF_MONITOR8;
return;
}
if (!stricmp(command, "$viewer")) {
// this subobject is a viewer
subobj->flags |= SOF_VIEWER;
return;
}
if (!stricmp(command, "$layer")) {
// this subobject is a layer to be drawn after original object.
subobj->flags |= SOF_LAYER;
return;
}
if (!stricmp(command, "$custom")) {
// this subobject has custom textures/colors
subobj->flags |= SOF_CUSTOM;
return;
}
}
void MinMaxSubmodel(poly_model *pm, bsp_info *sm, vector offset) {
offset += sm->offset;
// Get max
if ((sm->max.x + offset.x) > pm->maxs.x)
pm->maxs.x = sm->max.x + offset.x;
if ((sm->max.y + offset.y) > pm->maxs.y)
pm->maxs.y = sm->max.y + offset.y;
if ((sm->max.z + offset.z) > pm->maxs.z)
pm->maxs.z = sm->max.z + offset.z;
// Get min
if ((sm->min.x + offset.x) < pm->mins.x)
pm->mins.x = sm->min.x + offset.x;
if ((sm->min.y + offset.y) < pm->mins.y)
pm->mins.y = sm->min.y + offset.y;
if ((sm->min.z + offset.z) < pm->mins.z)
pm->mins.z = sm->min.z + offset.z;
for (int i = 0; i < sm->num_children; i++)
MinMaxSubmodel(pm, &pm->submodel[sm->children[i]], offset);
}
void FindMinMaxForModel(poly_model *pm) {
vector zero_vec;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
vm_MakeZero(&zero_vec);
pm->mins.x = pm->mins.y = pm->mins.z = 90000;
pm->maxs.x = pm->maxs.y = pm->maxs.z = -90000;
for (int i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
if (sm->parent == -1)
MinMaxSubmodel(pm, sm, zero_vec);
}
}
int ReadNewModelFile(int polynum, CFILE *infile) {
int version, done = 0, i, t, version_major;
int id, len;
poly_model *pm = &Poly_models[polynum];
int timed = 0;
ASSERT(pm->new_style);
id = cf_ReadInt(infile);
if (id != 'OPSP')
Error("Bad ID in model file!");
// Version is major*100+minor
// So, major = version / 100;
// minor = version % 100;
version = cf_ReadInt(infile);
if (version < 18) {
mprintf(0, "Old POF Version of %d fixed up to %d\n", version, version * 100);
version *= 100;
}
if (version < PM_COMPATIBLE_VERSION || version > PM_OBJFILE_VERSION) {
mprintf(0, "Bad version (%d) in model file!\n", version);
Int3();
return 0;
}
pm->version = version;
version_major = version / 100;
if (version_major >= 21)
pm->flags |= PMF_LIGHTMAP_RES;
if (version_major >= 22) {
timed = 1;
pm->flags |= PMF_TIMED;
pm->frame_min = 0;
pm->frame_max = 0;
}
pm->n_attach = 0;
memset(&pm->view_pos, 0, sizeof(pm->view_pos));
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
id = cf_ReadInt(infile);
len = cf_ReadInt(infile);
switch (id) {
case ID_OHDR: {
// Object header
// mprintf(0,"Object header...\n");
pm->n_models = cf_ReadInt(infile);
pm->rad = cf_ReadFloat(infile);
pm->submodel = (bsp_info *)mem_malloc(sizeof(bsp_info) * pm->n_models);
ASSERT(pm->submodel != nullptr);
memset(pm->submodel, 0, sizeof(bsp_info) * pm->n_models);
ReadModelVector(&pm->mins, infile);
ReadModelVector(&pm->maxs, infile);
// pm->n_detail_levels = cf_ReadInt(infile);
int det = cf_ReadInt(infile); // skip detail
for (i = 0; i < det; i++) {
// pm->detail[i] = cf_ReadInt(infile);
// pm->detail_depth[i] = 0.0f;
cf_ReadInt(infile);
}
// Adjust min/maxs to be based on zero
/*vector temp_vec=pm->maxs-pm->mins;
temp_vec/=2;
pm->mins=-temp_vec;
pm->maxs=temp_vec;*/
break;
}
case ID_SOBJ: {
// Subobject header
int n;
char props[MAX_PROP_LEN];
float d;
// mprintf(0,"Got chunk SOBJ, len=%d\n",len);
n = cf_ReadInt(infile);
ASSERT(n < pm->n_models);
pm->submodel[n].min.x = pm->submodel[n].min.y = pm->submodel[n].min.z = 90000;
pm->submodel[n].max.x = pm->submodel[n].max.y = pm->submodel[n].max.z = -90000;
pm->submodel[n].parent = cf_ReadInt(infile);
ReadModelVector(&pm->submodel[n].norm, infile);
d = cf_ReadFloat(infile);
ReadModelVector(&pm->submodel[n].pnt, infile);
ReadModelVector(&pm->submodel[n].offset, infile);
pm->submodel[n].rad = cf_ReadFloat(infile); // radius
pm->submodel[n].tree_offset = cf_ReadInt(infile); // offset
pm->submodel[n].data_offset = cf_ReadInt(infile); // offset
if (pm->version > 1805)
ReadModelVector(&pm->submodel[n].geometric_center, infile);
else {
// for old models, hack in 0,0,0, which says the geometric center is
// equal to pivot point, which is not always true.
vm_MakeZero(&pm->submodel[n].geometric_center);
}
pm->submodel[n].name[0] = '\0';
ReadModelStringLen(pm->submodel[n].name, PAGENAME_LEN, infile);
ReadModelStringLen(props, MAX_PROP_LEN, infile); // and the user properites
SetPolymodelProperties(&pm->submodel[n], props);
pm->submodel[n].movement_type = cf_ReadInt(infile);
pm->submodel[n].movement_axis = cf_ReadInt(infile);
if (pm->submodel[n].name[0] == '\0')
strcpy(pm->submodel[n].name, "unknown object name");
memset(&pm->submodel[n].angs, 0, sizeof(angvec));
// Skip freespace chunks
int n_chunks = cf_ReadInt(infile);
if (n_chunks > 0) {
for (i = 0; i < n_chunks; i++)
cf_ReadInt(infile);
}
int nverts = cf_ReadInt(infile);
ASSERT(nverts < MAX_POLYGON_VECS);
if (nverts) {
pm->submodel[n].verts = (vector *)mem_malloc(nverts * sizeof(vector));
pm->submodel[n].vertnorms = (vector *)mem_malloc(nverts * sizeof(vector));
pm->submodel[n].alpha = (float *)mem_malloc(nverts * sizeof(float));
ASSERT(pm->submodel[n].verts);
ASSERT(pm->submodel[n].vertnorms);
ASSERT(pm->submodel[n].alpha);
} else {
// Let me take a moment right here to say how annoying it is that I can't
// set all these pointers to nullptr with one assignment on the same line due
// to the stupid strong typying they've added to C.
pm->submodel[n].verts = nullptr;
pm->submodel[n].vertnorms = nullptr;
pm->submodel[n].alpha = nullptr;
}
pm->submodel[n].nverts = nverts;
for (i = 0; i < nverts; i++) {
ReadModelVector(&pm->submodel[n].verts[i], infile);
// Get max
if (pm->submodel[n].verts[i].x > pm->submodel[n].max.x)
pm->submodel[n].max.x = pm->submodel[n].verts[i].x;
if (pm->submodel[n].verts[i].y > pm->submodel[n].max.y)
pm->submodel[n].max.y = pm->submodel[n].verts[i].y;
if (pm->submodel[n].verts[i].z > pm->submodel[n].max.z)
pm->submodel[n].max.z = pm->submodel[n].verts[i].z;
// Get min
if (pm->submodel[n].verts[i].x < pm->submodel[n].min.x)
pm->submodel[n].min.x = pm->submodel[n].verts[i].x;
if (pm->submodel[n].verts[i].y < pm->submodel[n].min.y)
pm->submodel[n].min.y = pm->submodel[n].verts[i].y;
if (pm->submodel[n].verts[i].z < pm->submodel[n].min.z)
pm->submodel[n].min.z = pm->submodel[n].verts[i].z;
}
for (i = 0; i < nverts; i++)
ReadModelVector(&pm->submodel[n].vertnorms[i], infile);
// Read alpha per vertex
if (version_major >= 23) {
for (i = 0; i < nverts; i++) {
pm->submodel[n].alpha[i] = cf_ReadFloat(infile);
if (pm->submodel[n].alpha[i] < .99)
pm->flags |= PMF_ALPHA;
}
} else {
for (i = 0; i < nverts; i++)
pm->submodel[n].alpha[i] = 1.0;
}
int nfaces = cf_ReadInt(infile);
pm->submodel[n].num_faces = nfaces;
if (nfaces) {
pm->submodel[n].faces = (polyface *)mem_malloc(nfaces * sizeof(polyface));
pm->submodel[n].face_min = (vector *)mem_malloc(nfaces * sizeof(vector));
pm->submodel[n].face_max = (vector *)mem_malloc(nfaces * sizeof(vector));
ASSERT(pm->submodel[n].faces);
} else {
pm->submodel[n].faces = nullptr;
pm->submodel[n].face_max = nullptr;
pm->submodel[n].face_min = nullptr;
}
// Find out how much space we'll need
bsp_info *sm = &pm->submodel[n];
vector tvec;
int current_count = 0;
int save_position = cftell(infile);
int *start_index;
if (nfaces) {
start_index = (int *)mem_malloc(sizeof(int) * nfaces);
ASSERT(start_index);
} else
start_index = nullptr;
uint8_t tempbuf[2000];
for (i = 0; i < nfaces; i++) {
ReadModelVector(&tvec, infile);
int num_verts = cf_ReadInt(infile);
ASSERT(num_verts < 100);
ASSERT(num_verts > 0);
start_index[i] = current_count;
current_count += num_verts;
// Skip the rest
if (cf_ReadInt(infile))
cf_ReadInt(infile);
else
cf_ReadBytes(tempbuf, 3, infile);
cf_ReadBytes(tempbuf, 12 * num_verts, infile);
if (version_major >= 21)
cf_ReadBytes(tempbuf, 8, infile);
}
// Allocate our space
if (current_count) {
sm->vertnum_memory = (int16_t *)mem_malloc(current_count * sizeof(int16_t));
ASSERT(sm->vertnum_memory);
sm->u_memory = (float *)mem_malloc(current_count * sizeof(float));
ASSERT(sm->u_memory);
sm->v_memory = (float *)mem_malloc(current_count * sizeof(float));
ASSERT(sm->v_memory);
} else {
sm->vertnum_memory = nullptr;
sm->u_memory = nullptr;
sm->v_memory = nullptr;
}
// Now go through and set up our fake pointers
for (i = 0; i < nfaces; i++) {
sm->faces[i].vertnums = &sm->vertnum_memory[start_index[i]];
sm->faces[i].u = &sm->u_memory[start_index[i]];
sm->faces[i].v = &sm->v_memory[start_index[i]];
}
// Reset our file pointer and free the temp memory
cfseek(infile, save_position, SEEK_SET);
if (start_index)
mem_free(start_index);
for (i = 0; i < nfaces; i++) {
ReadModelVector(&pm->submodel[n].faces[i].normal, infile);
int num_verts = cf_ReadInt(infile);
pm->submodel[n].faces[i].nverts = num_verts;
int textured = cf_ReadInt(infile);
if (textured) {
pm->submodel[n].faces[i].texnum = cf_ReadInt(infile);
} else {
pm->submodel[n].faces[i].texnum = -1;
uint8_t r, g, b;
r = cf_ReadByte(infile);
g = cf_ReadByte(infile);
b = cf_ReadByte(infile);
pm->submodel[n].faces[i].color = GR_RGB(r, g, b);
}
for (t = 0; t < pm->submodel[n].faces[i].nverts; t++) {
int val;
vector *min_ptr = &pm->submodel[n].face_min[i];
vector *max_ptr = &pm->submodel[n].face_max[i];
vector *v_ptr;
val = cf_ReadInt(infile);
pm->submodel[n].faces[i].vertnums[t] = val;
pm->submodel[n].faces[i].u[t] = cf_ReadFloat(infile);
pm->submodel[n].faces[i].v[t] = cf_ReadFloat(infile);
v_ptr = &pm->submodel[n].verts[pm->submodel[n].faces[i].vertnums[t]];
if (t == 0) {
*min_ptr = *max_ptr = *v_ptr;
} else {
if (v_ptr->x < min_ptr->x)
min_ptr->x = v_ptr->x;
else if (v_ptr->x > max_ptr->x)
max_ptr->x = v_ptr->x;
if (v_ptr->y < min_ptr->y)
min_ptr->y = v_ptr->y;
else if (v_ptr->y > max_ptr->y)
max_ptr->y = v_ptr->y;
if (v_ptr->z < min_ptr->z)
min_ptr->z = v_ptr->z;
else if (v_ptr->z > max_ptr->z)
max_ptr->z = v_ptr->z;
}
}
// Do lightmap res computation
if (version_major >= 21) {
float xdiff, ydiff;
xdiff = cf_ReadFloat(infile);
ydiff = cf_ReadFloat(infile);
}
}
break;
}
case ID_GPNT:
pm->n_guns = cf_ReadInt(infile);
pm->gun_slots = (w_bank *)mem_malloc(sizeof(w_bank) * pm->n_guns);
ASSERT(pm->gun_slots != nullptr);
for (i = 0; i < pm->n_guns; i++) {
w_bank *bank = &pm->gun_slots[i];
// In Version 19.08 and beyond, gunpoints are associated to their parent object.
if (version >= 19 * 100 + 8) {
bank->parent = cf_ReadInt(infile);
} else {
bank->parent = 0;
}
ReadModelVector(&bank->pnt, infile);
ReadModelVector(&bank->norm, infile);
}
break;
case ID_ATTACH:
pm->n_attach = cf_ReadInt(infile);
if (pm->n_attach) {
pm->attach_slots = (a_bank *)mem_malloc(sizeof(a_bank) * pm->n_attach);
ASSERT(pm->attach_slots != nullptr);
for (i = 0; i < pm->n_attach; i++) {
a_bank *bank = &pm->attach_slots[i];
bank->parent = cf_ReadInt(infile);
ReadModelVector(&bank->pnt, infile);
ReadModelVector(&bank->norm, infile);
bank->f_uvec = false;
}
} else {
pm->attach_slots = nullptr;
}
break;
case ID_ATTACH_NORMALS: {
bool f_uvec = false;
int num_normals;
if (pm->n_attach == (num_normals = cf_ReadInt(infile)))
f_uvec = true;
else {
mprintf(0, "WARNING: Ingoring ATTACH normals - total number doesn't match number of attach points\n");
// Int3();
DataError("Model <%s> specifies %d attach points but only contains %d attach normals\n", pm->name, pm->n_attach,
num_normals);
}
if (num_normals) {
for (i = 0; i < num_normals; i++) {
vector temp;
a_bank *bank = &pm->attach_slots[i];
cf_ReadInt(infile);
ReadModelVector(&temp, infile);
ReadModelVector(&bank->uvec, infile);
bank->f_uvec = f_uvec;
}
} else {
pm->attach_slots = nullptr;
}
break;
}
case ID_WBS: {
int i;
int j;
// Get the number of weapon batteries on this object
pm->num_wbs = cf_ReadInt(infile);
if (pm->num_wbs) {
pm->poly_wb = (poly_wb_info *)mem_malloc(sizeof(poly_wb_info) * pm->num_wbs);
// Get each individual wb info struct
for (i = 0; i < pm->num_wbs; i++) {
pm->poly_wb[i].num_gps = cf_ReadInt(infile);
for (j = 0; j < pm->poly_wb[i].num_gps; j++) {
if (j <= 7)
pm->poly_wb[i].gp_index[j] = cf_ReadInt(infile);
else
cf_ReadInt(infile);
}
if (pm->poly_wb[i].num_gps > 8)
pm->poly_wb[i].num_gps = 8;
pm->poly_wb[i].num_turrets = cf_ReadInt(infile);
for (j = 0; j < pm->poly_wb[i].num_turrets; j++) {
if (j <= 7)
pm->poly_wb[i].turret_index[j] = cf_ReadInt(infile);
else
cf_ReadInt(infile);
}
if (pm->poly_wb[i].num_turrets > 8)
pm->poly_wb[i].num_turrets = 8;
}
} else {
pm->poly_wb = nullptr;
}
break;
}
case ID_GROUND:
pm->n_ground = cf_ReadInt(infile);
pm->ground_slots = (w_bank *)mem_malloc(sizeof(w_bank) * pm->n_ground);
ASSERT(pm->ground_slots != nullptr);
for (i = 0; i < pm->n_ground; i++) {
w_bank *bank = &pm->ground_slots[i];
bank->parent = cf_ReadInt(infile);
ReadModelVector(&bank->pnt, infile);
ReadModelVector(&bank->norm, infile);
}
break;
case ID_TXTR: {
// Texture filename list
int i, n;
char name_buf[128];
// mprintf(0,"Got chunk TXTR, len=%d\n",len);
n = cf_ReadInt(infile);
pm->n_textures = n;
ASSERT(n < MAX_MODEL_TEXTURES);
for (i = 0; i < n; i++) {
int ret;
char temp[256];
// Read the name of this texture
ReadModelStringLen(name_buf, 127, infile);
strcpy(temp, name_buf);
strcat(temp, ".OGF");
ret = FindTextureBitmapName(temp);
if (ret == -1) {
// See if it is already in memory
ret = FindTextureName(name_buf);
if (ret == -1) {
ret = 0;
// mprintf(0,"Object texture %s is not in memory!\n",name_buf);
}
}
pm->textures[i] = ret;
if (GameTextures[ret].alpha < .99)
pm->flags |= PMF_ALPHA;
}
break;
}
case ID_ROT_ANIM:
case ID_ANIM: {
int nframes = 0;
// mprintf(0,"ROT ANIM chunk!!!\n");
if (!timed) {
nframes = cf_ReadInt(infile);
pm->num_key_angles = nframes;
}
for (i = 0; i < pm->n_models; i++) {
if (timed) {
pm->submodel[i].num_key_angles = cf_ReadInt(infile);
pm->submodel[i].rot_track_min = cf_ReadInt(infile);
pm->submodel[i].rot_track_max = cf_ReadInt(infile);
if (pm->submodel[i].rot_track_min < pm->frame_min)
pm->frame_min = pm->submodel[i].rot_track_min;
if (pm->submodel[i].rot_track_max > pm->frame_max)
pm->frame_max = pm->submodel[i].rot_track_max;
} else {
pm->submodel[i].num_key_angles = nframes;
}
pm->submodel[i].keyframe_axis = (vector *)mem_malloc((pm->submodel[i].num_key_angles + 1) * sizeof(vector));
pm->submodel[i].keyframe_angles = (int *)mem_malloc((pm->submodel[i].num_key_angles + 1) * sizeof(int));
pm->submodel[i].keyframe_matrix = (matrix *)mem_malloc((pm->submodel[i].num_key_angles + 1) * sizeof(matrix));
if (timed) {
pm->submodel[i].rot_start_time = (int *)mem_malloc((pm->submodel[i].num_key_angles + 1) * sizeof(int));
ASSERT(pm->submodel[i].rot_start_time != nullptr);
int num_ticks = (pm->submodel[i].rot_track_max - pm->submodel[i].rot_track_min);
if (num_ticks > 0) {
pm->submodel[i].tick_ang_remap = (uint16_t *)mem_malloc(num_ticks * 2);
ASSERT(pm->submodel[i].tick_ang_remap);
} else {
pm->submodel[i].tick_ang_remap = nullptr;
}
}
ASSERT(pm->submodel[i].keyframe_axis != nullptr);
ASSERT(pm->submodel[i].keyframe_angles != nullptr);
ASSERT(pm->submodel[i].keyframe_matrix != nullptr);
for (t = 0; t < pm->submodel[i].num_key_angles; t++) {
vector *axis;
float mag;
if (timed)
pm->submodel[i].rot_start_time[t] = cf_ReadInt(infile);
ReadModelVector(&pm->submodel[i].keyframe_axis[t + 0], infile);
axis = &pm->submodel[i].keyframe_axis[t + 0];
mag = vm_GetMagnitude(axis);
if (mag > 0)
pm->submodel[i].keyframe_axis[t] /= mag;
pm->submodel[i].keyframe_angles[t + 0] = cf_ReadInt(infile);
// if this axis is backwards, rotate the angle the other direction
if (t == 0) {
/*if (pm->submodel[i].keyframe_angles[0]>=32768)
{
mprintf(0,"\nFlipping suboject %s!\n",pm->submodel[i].name);
pm->submodel[i].keyframe_angles[0]*=-1;
}*/
}
/*else
dir=vm_DotProduct (axis,&pm->submodel[i].keyframe_axis[1]);
if (dir<0)
{
pm->submodel[i].keyframe_angles[t+0]*=-1;
pm->submodel[i].keyframe_axis[t+0]*=-1.0;
}*/
}
}
break;
}
case ID_POS_ANIM: {
int nframes = 0;
// mprintf(0,"POS ANIM chunk!!!\n");
if (!timed) {
nframes = cf_ReadInt(infile);
pm->num_key_pos = nframes;
}
for (i = 0; i < pm->n_models; i++) {
if (timed) {
pm->submodel[i].num_key_pos = cf_ReadInt(infile);
pm->submodel[i].pos_track_min = cf_ReadInt(infile);
pm->submodel[i].pos_track_max = cf_ReadInt(infile);
if (pm->submodel[i].pos_track_min < pm->frame_min)
pm->frame_min = pm->submodel[i].pos_track_min;
if (pm->submodel[i].pos_track_max > pm->frame_max)
pm->frame_max = pm->submodel[i].pos_track_max;
int num_ticks = (pm->submodel[i].pos_track_max - pm->submodel[i].pos_track_min);
if (num_ticks > 0) {
pm->submodel[i].tick_pos_remap = (uint16_t *)mem_malloc(num_ticks * 2);
ASSERT(pm->submodel[i].tick_pos_remap);
} else {
pm->submodel[i].tick_pos_remap = nullptr;
}
} else
pm->submodel[i].num_key_pos = nframes;
pm->submodel[i].keyframe_pos = (vector *)mem_malloc((pm->submodel[i].num_key_pos + 1) * sizeof(vector));
ASSERT(pm->submodel[i].keyframe_pos != nullptr);
if (timed) {
pm->submodel[i].pos_start_time = (int *)mem_malloc((pm->submodel[i].num_key_pos + 1) * sizeof(int));
ASSERT(pm->submodel[i].pos_start_time != nullptr);
}
for (t = 0; t < pm->submodel[i].num_key_pos; t++) {
if (timed)
pm->submodel[i].pos_start_time[t] = cf_ReadInt(infile);
ReadModelVector(&pm->submodel[i].keyframe_pos[t + 0], infile);
}
}
break;
}
default:
// mprintf(0,"Unknown chunk <%c%c%c%c>, len = %d\n",id,id>>8,id>>16,id>>24,len);
cfseek(infile, len, SEEK_CUR);
break;
}
}
// Set the greater of keyframe of positions or keyframe angles to to be max keyframes
pm->max_keys = std::max(pm->num_key_pos, pm->num_key_angles);
// Build animation keyframe matrices
matrix base_matrix, dest_matrix, temp_matrix;
int cur_angle;
int parent;
for (i = 0; i < pm->n_models; i++) {
vm_MakeIdentity(&base_matrix);
bsp_info *sm = &pm->submodel[i];
for (int newt = 0; newt < sm->num_key_angles; newt++) {
cur_angle = sm->keyframe_angles[newt];
BuildModelAngleMatrix(&temp_matrix, cur_angle, &sm->keyframe_axis[newt]);
vm_MakeIdentity(&dest_matrix);
dest_matrix = temp_matrix * base_matrix;
base_matrix = dest_matrix;
sm->keyframe_matrix[newt] = base_matrix;
}
// Build children
parent = pm->submodel[i].parent;
if (parent == i) {
pm->submodel[i].parent = -1;
} else if (parent != -1) {
pm->submodel[parent].children[pm->submodel[parent].num_children] = i;
pm->submodel[parent].num_children++;
}
if (pm->submodel[i].num_key_angles == 0 && (pm->submodel[i].flags & SOF_ROTATE)) {
mprintf(0, "You have a rotator that has no keyframe on model %s.\n", pm->name);
pm->submodel[i].flags &= ~SOF_ROTATE;
} else if (pm->submodel[i].num_key_angles == 0 && (pm->submodel[i].flags & SOF_TURRET)) {
mprintf(0, "You have a turret that has no keyframe on model %s.\n", pm->name);
pm->submodel[i].flags &= ~SOF_TURRET;
}
// Figure out the size of this facing subobject
if (pm->submodel[i].flags & SOF_FACING) {
ASSERT(pm->submodel[i].num_faces == 1); // This facing has more than one face
vector vecs[30];
vector avg;
for (int newt = 0; newt < pm->submodel[i].faces[0].nverts; newt++) {
vecs[newt] = pm->submodel[i].verts[pm->submodel[i].faces[0].vertnums[newt]];
}
pm->submodel[i].rad = (sqrt(vm_GetCentroid(&avg, vecs, pm->submodel[i].faces[0].nverts)) / 2);
pm->flags |= PMF_FACING;
}
if (pm->submodel[i].flags & (SOF_GLOW | SOF_THRUSTER)) {
ASSERT(pm->submodel[i].num_faces == 1); // This glow has more than one face
vector vecs[30];
for (t = 0; t < pm->submodel[i].faces[0].nverts; t++)
vecs[t] = pm->submodel[i].verts[pm->submodel[i].faces[0].vertnums[t]];
vm_GetNormal(&pm->submodel[i].glow_info->normal, &vecs[0], &vecs[1], &vecs[2]);
pm->flags |= PMF_FACING; // Set this so we know when to draw
}
// Now build the tick/keyframe remap list
int num_ticks = (pm->submodel[i].pos_track_max - pm->submodel[i].pos_track_min);
for (t = 0; timed && t < num_ticks; t++) {
int done = 0;
int current_tick = sm->pos_track_min + t;
if (sm->num_key_pos > 1) {
for (int k = sm->num_key_pos - 1; k >= 0 && !done; k--) {
if (current_tick >= sm->pos_start_time[k]) {
sm->tick_pos_remap[t] = k;
done = 1;
}
}
if (done == 0) {
mprintf(0, "Couldn't get a good keyframe!\n");
Int3();
}
}
}
// Now build the tick/keyframe remap list
num_ticks = (pm->submodel[i].rot_track_max - pm->submodel[i].rot_track_min);
for (t = 0; timed && t < num_ticks; t++) {
if (sm->num_key_angles > 1) {
int done = 0;
int current_tick = sm->rot_track_min + t;
for (int k = sm->num_key_angles - 1; k >= 0 && !done; k--) {
if (current_tick >= sm->rot_start_time[k]) {
sm->tick_ang_remap[t] = k;
done = 1;
}
}
if (done == 0) {
mprintf(0, "Couldn't get a good keyframe!\n");
Int3();
}
}
}
}
pm->flags &= ~PMF_NOT_RESIDENT; // mark it as in memory
// Find Min/Max of whole model
FindMinMaxForModel(pm);
// adjust positional interpolation frames
FindWBSubobjects(pm);
// Set as newstyle
pm->new_style = 1;
if (pm->n_models > MAX_SUBOBJECTS) {
mprintf(0, "This model has more than the max number of subobjects! (%d)\n", MAX_SUBOBJECTS);
Int3();
FreePolyModel(pm - Poly_models);
return 0;
}
return 1;
}
// given a filename, reads in a POF and returns an index into the Poly_models array
// returns -1 if something is wrong
int LoadPolyModel(const std::filesystem::path &filename, int pageable) {
int i, polynum = -1;
CFILE *infile = nullptr;
int overlay = 0;
ASSERT(Num_poly_models >= 0);
ASSERT(Num_poly_models < MAX_POLY_MODELS);
std::filesystem::path name = ChangePolyModelName(filename);
// If this polymodel is already in memory, just use that index
i = FindPolyModelName(name);
if (i != -1) {
#ifdef RELEASE
Poly_models[i].used++;
return i;
#endif
int old_used = Poly_models[i].used;
int not_res = 0;
if (Poly_models[i].flags & PMF_NOT_RESIDENT)
not_res = 1;
mprintf(1, "Model '%s' usage count is now %d.\n", Poly_models[i].name, Poly_models[i].used + 1);
Poly_models[i].used = 1;
FreePolyModel(i);
memset(&Poly_models[i], 0, sizeof(poly_model));
WBClearInfo(&Poly_models[i]);
Poly_models[i].used = old_used + 1;
if (not_res)
Poly_models[i].flags = PMF_NOT_RESIDENT;
overlay = 1;
polynum = i;
}
// Not in memory, so we must load it
if (!pageable) {
infile = cfopen(filename, "rb");
if (!infile)
return -1;
}
if (!overlay)
polynum = AllocPolyModel();
else {
if (!(Poly_models[polynum].flags & PMF_NOT_RESIDENT)) {
if (pageable) {
infile = cfopen(filename, "rb");
if (!infile)
return -1;
}
pageable = 0;
}
}
ASSERT(polynum >= 0);
// Used for progress bar when loading the level
if (infile) {
paged_in_count += cfilelength(infile);
paged_in_num++;
}
// if this is an oof instead of a pof, flag it as such
if (!stricmp(".oof", filename.extension().u8string().c_str())) {
Poly_models[polynum].new_style = 1;
} else
Poly_models[polynum].new_style = 0;
strcpy(Poly_models[polynum].name, name.u8string().c_str());
int ret = 0;
if (!pageable)
ret = ReadNewModelFile(polynum, infile);
else
ret = 1;
if (infile)
cfclose(infile);
Poly_models[polynum].id = polynum;
if (ret)
return polynum; // loaded successfully
return -1; // damn, didn't load
}
// Pages in a polymodel if it is not already in memory
void PageInPolymodel(int polynum, int type, float *size_ptr) {
if (!(Poly_models[polynum].flags & PMF_NOT_RESIDENT)) {
if (!(Poly_models[polynum].flags & PMF_SIZE_COMPUTED))
if (type != -1) {
ComputeDefaultSize(type, polynum, size_ptr);
}
return;
}
mprintf(0, "Paging in polymodel %s.\n", Poly_models[polynum].name);
CFILE *infile;
infile = (CFILE *)cfopen(Poly_models[polynum].name, "rb");
if (!infile) {
// due to a bug in some 3rd party tablefile editors, full paths might
// have been used when they shouldn't have been
char *end_ptr, *start_ptr;
start_ptr = Poly_models[polynum].name;
end_ptr = start_ptr + strlen(start_ptr) - 1;
while ((end_ptr >= start_ptr) && (*end_ptr != '\\'))
end_ptr--;
if (end_ptr < start_ptr) {
Error("Failed to page in %s.", Poly_models[polynum].name);
return;
}
ASSERT(*end_ptr == '\\');
end_ptr++;
infile = (CFILE *)cfopen(end_ptr, "rb");
if (!infile) {
Error("Failed to page in %s.", Poly_models[polynum].name);
return;
}
}
// ASSERT(infile);
int ret = ReadNewModelFile(polynum, infile);
cfclose(infile);
ASSERT(ret > 0);
// See if textures need to be remapped
int remap = 0;
for (int t = 0; t < Poly_models[polynum].n_textures; t++)
if (Poly_models[polynum].textures[t] == 0) // is there a bad texture?
remap = 1;
if (remap == 1) {
// remap the damn textures
mprintf(0, "Remapping model textures for model %s.\n", Poly_models[polynum].name);
ReloadModelTextures(polynum);
}
if (type != -1) {
ComputeDefaultSize(type, polynum, size_ptr);
}
}
// Gets a pointer to a polymodel. Pages it in if neccessary
poly_model *GetPolymodelPointer(int polynum) {
if (Poly_models[polynum].flags & PMF_NOT_RESIDENT)
PageInPolymodel(polynum);
return (&Poly_models[polynum]);
}
// gets the filename from a path
std::filesystem::path ChangePolyModelName(const std::filesystem::path &src) {
// Make sure we don't go over our name length limit
std::string dest = src.stem().string().substr(0, PAGENAME_LEN - 5);
std::filesystem::path filename = std::filesystem::path(dest).replace_extension(src.extension());
return filename;
}
// Searches thru all polymodels for a specific name, returns -1 if not found
// or index of polymodel with name
int FindPolyModelName(const std::filesystem::path &name) {
for (int i = 0; i < MAX_POLY_MODELS; i++) {
if (Poly_models[i].used && !stricmp(Poly_models[i].name, name.u8string().c_str())) {
return i;
}
}
return -1;
}
// Sets a positional instance
void StartPolyModelPosInstance(vector *posvec) {
Interp_pos_instance_vec = *posvec;
Instance_vec_stack[Instance_vec_cnt] = Interp_pos_instance_vec;
Instance_vec_cnt++;
}
// Pops a positional instance
void DonePolyModelPosInstance() {
Instance_vec_cnt--;
if (Instance_vec_cnt == 0)
memset(&Interp_pos_instance_vec, 0, sizeof(vector));
else
Interp_pos_instance_vec = Instance_vec_stack[Instance_vec_cnt];
}
void SetNormalizedTimeObjTimed(object *obj, float *normalized_time) {
int i, j;
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
object_info *obj_info = &Object_info[obj->id];
if (obj->type == OBJ_PLAYER || obj->type == OBJ_WEAPON)
return;
// Setup all the subobjects for the keyframe
float frame = obj->rtype.pobj_info.anim_frame;
for (i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
int x = (sm->flags & SOF_WB_MASKS) >> WB_INDEX_SHIFT;
if (!(sm->flags & SOF_TURRET) &&
!((sm->flags & SOF_WB) && ((obj_info->static_wb[x].flags & WBF_ANIM_MASKS) == WBF_ANIM_LOCAL))) {
if (frame <= sm->rot_track_min)
normalized_time[i] = 0.0;
else if (frame >= sm->rot_track_max)
normalized_time[i] = 1.0;
else {
float total_time = sm->rot_track_max - sm->rot_track_min;
normalized_time[i] = (frame - sm->rot_track_min) / total_time;
}
} else if ((sm->flags & SOF_WB) && !(sm->flags & SOF_TURRET)) {
static float w_frame;
ASSERT(x >= 0 && x < MAX_WBS_PER_OBJ);
if (obj->dynamic_wb)
w_frame = obj->dynamic_wb[x].wb_anim_frame;
else
w_frame = 0;
if (w_frame <= sm->rot_track_min)
normalized_time[i] = 0.0;
else if (w_frame >= sm->rot_track_max)
normalized_time[i] = 1.0;
else {
float total_time = sm->rot_track_max - sm->rot_track_min;
normalized_time[i] = (w_frame - sm->rot_track_min) / total_time;
}
}
}
// Now, override angles of weapon bank turrets
if (obj->dynamic_wb) {
for (i = 0; i < pm->num_wbs; i++) {
for (j = 0; j < pm->poly_wb[i].num_turrets; j++) {
int sobj_index;
sobj_index = pm->poly_wb[i].turret_index[j];
normalized_time[sobj_index] = obj->dynamic_wb[i].norm_turret_angle[j];
}
}
} else {
for (i = 0; i < pm->num_wbs; i++) {
for (j = 0; j < pm->poly_wb[i].num_turrets; j++) {
int sobj_index;
sobj_index = pm->poly_wb[i].turret_index[j];
normalized_time[sobj_index] = 0;
}
}
}
}
void SetNormalizedTimeObj(object *obj, float *normalized_time) {
int i, j;
if (Poly_models[obj->rtype.pobj_info.model_num].flags & PMF_TIMED) {
SetNormalizedTimeObjTimed(obj, normalized_time);
return;
}
float norm_anim_frame = GetNormalizedKeyframe(obj->rtype.pobj_info.model_num, obj->rtype.pobj_info.anim_frame);
// Setup all the subobjects for the keyframe
for (i = 0; i < Poly_models[obj->rtype.pobj_info.model_num].n_models; i++)
normalized_time[i] = norm_anim_frame;
// Currently, we are not handling player weapons in this manner
// chrishack -- weapons with turrets -- COOL.
if (obj->type == OBJ_PLAYER || obj->type == OBJ_WEAPON)
return;
ASSERT(obj->type == OBJ_POWERUP || obj->type == OBJ_ROBOT || obj->type == OBJ_BUILDING || obj->type == OBJ_DEBRIS ||
obj->type == OBJ_DOOR || obj->type == OBJ_CLUTTER);
// Now, override angles of weapon bank turrets
for (i = 0; i < Poly_models[obj->rtype.pobj_info.model_num].num_wbs; i++) {
for (j = 0; j < Poly_models[obj->rtype.pobj_info.model_num].poly_wb[i].num_turrets; j++) {
int sobj_index;
sobj_index = Poly_models[obj->rtype.pobj_info.model_num].poly_wb[i].turret_index[j];
normalized_time[sobj_index] = obj->dynamic_wb[i].norm_turret_angle[j];
}
}
}
void SetNormalizedTimeAnimTimed(float frame, float *normalized_time, poly_model *pm) {
int i, j;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
for (i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
if (frame <= sm->rot_track_min)
normalized_time[i] = 0.0;
else if (frame >= sm->rot_track_max)
normalized_time[i] = 1.0;
else {
float total_time = sm->rot_track_max - sm->rot_track_min;
normalized_time[i] = (frame - sm->rot_track_min) / total_time;
}
}
// Now, override angles of weapon bank turrets
for (i = 0; i < pm->num_wbs; i++) {
for (j = 0; j < pm->poly_wb[i].num_turrets; j++) {
int sobj_index;
sobj_index = pm->poly_wb[i].turret_index[j];
normalized_time[sobj_index] = 0.0;
}
}
}
// This is for non-rotated turrets or rotators
void SetNormalizedTimeAnim(float anim_frame, float *normalized_time, poly_model *pm) {
int i, j;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
float norm_anim_frame = anim_frame / pm->max_keys;
if (pm->flags & PMF_TIMED) {
SetNormalizedTimeAnimTimed(anim_frame, normalized_time, pm);
return;
}
// Setup all the subobjects for the keyframe
for (i = 0; i < pm->n_models; i++)
normalized_time[i] = norm_anim_frame;
// Now, override angles of weapon bank turrets
for (i = 0; i < pm->num_wbs; i++) {
for (j = 0; j < pm->poly_wb[i].num_turrets; j++) {
int sobj_index;
sobj_index = pm->poly_wb[i].turret_index[j];
normalized_time[sobj_index] = 0.0;
}
}
}
// Given a model pointer and an array of floats that go from 0..1, calculate the angles of each
// corresponding subobject
void SetModelAngles(poly_model *po, const float *normalized_angles) {
int i;
ASSERT(!(po->flags & PMF_NOT_RESIDENT));
if (po->num_key_angles > 0 && normalized_angles) {
// get time per keyframe state
float state_time = 1.0 / (po->num_key_angles - 1);
float cur_state_time;
int cur_state;
float normal_state_time;
for (i = 0; i < po->n_models; i++) {
// Don't rotate turrets or auto-rotators or weapon battery submodels
if (!(po->submodel[i].flags & (SOF_ROTATE | SOF_TURRET))) {
// Find out which keyframe we're at
cur_state = normalized_angles[i] / state_time;
matrix dest_matrix;
// Find out how far into that keyframe we are
cur_state_time = normalized_angles[i] - ((float)cur_state * state_time);
normal_state_time = cur_state_time / state_time;
// Now do a parametric adjustment on the angles
int cur_angle = 0;
// If we're already at the high point of the interpolation then just
// stuff some values
if (cur_state == po->num_key_angles - 1) {
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &po->submodel[i].keyframe_matrix[po->num_key_angles - 1]);
continue;
}
dest_matrix = po->submodel[i].keyframe_matrix[cur_state] +
((po->submodel[i].keyframe_matrix[cur_state + 1] - po->submodel[i].keyframe_matrix[cur_state]) *
normal_state_time);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &dest_matrix);
// po->submodel[i].mod_matrix=dest_matrix;
} else if (po->submodel[i].flags & SOF_ROTATE) {
float flrot = Gametime * po->submodel[i].rps;
int introt = flrot;
float fdiff = flrot - introt;
ASSERT(fdiff >= 0 && fdiff <= 1);
// fdiff now equals 0 to 1
matrix temp_matrix;
vector temp_vec;
if (po->new_style)
temp_vec = po->submodel[i].keyframe_axis[1];
else
temp_vec = po->submodel[i].norm;
BuildModelAngleMatrix(&temp_matrix, fdiff * 65535, &temp_vec);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &temp_matrix);
} else if (po->submodel[i].flags & SOF_TURRET) {
matrix temp_matrix;
if (po->new_style)
BuildModelAngleMatrix(&temp_matrix, normalized_angles[i] * 65535, &po->submodel[i].keyframe_axis[1]);
else
BuildModelAngleMatrix(&temp_matrix, normalized_angles[i] * 65535, &po->submodel[i].norm);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &temp_matrix);
}
}
} else {
for (i = 0; i < po->n_models; i++) {
if (po->submodel[i].flags & SOF_ROTATE) {
float flrot = Gametime * po->submodel[i].rps;
int introt = flrot;
float fdiff = flrot - introt;
ASSERT(fdiff >= 0 && fdiff <= 1);
// fdiff now equals 0 to 1
matrix temp_matrix;
vector temp_vec;
if (po->new_style)
temp_vec = po->submodel[i].keyframe_axis[1];
else
temp_vec = po->submodel[i].norm;
BuildModelAngleMatrix(&temp_matrix, fdiff * 65535, &temp_vec);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &temp_matrix);
} else if (po->submodel[i].flags & SOF_TURRET) {
// We need to find where in this rotation is relative to gametime
float flrot = Gametime;
int introt = flrot;
float fdiff = flrot - introt;
// fdiff now equals 0 to 1
matrix temp_matrix;
if (po->new_style)
BuildModelAngleMatrix(&temp_matrix, fdiff * 65535, &po->submodel[i].keyframe_axis[0]);
else
BuildModelAngleMatrix(&temp_matrix, fdiff * 65535, &po->submodel[i].norm);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &temp_matrix);
} else {
po->submodel[i].angs.h = 0;
po->submodel[i].angs.p = 0;
po->submodel[i].angs.h = 0;
}
}
}
}
// Given a model pointer and an array of floats that go from 0..1, calculate the interpolated
// position of each corresponding subobject
void SetModelInterpPos(poly_model *po, const float *normalized_pos) {
int i;
ASSERT(!(po->flags & PMF_NOT_RESIDENT));
if (normalized_pos && po->num_key_pos > 0) {
for (i = 0; i < po->n_models; i++) {
bsp_info *sm = &po->submodel[i];
// get time per keyframe state
float state_time = 1.0 / (po->num_key_pos - 1);
float cur_state_time;
int cur_state;
float normal_state_time;
// Find out which keyframe we're at
cur_state = normalized_pos[i] / state_time;
// Find out how far into that keyframe we are
cur_state_time = normalized_pos[i] - ((float)cur_state * state_time);
normal_state_time = cur_state_time / state_time;
// Now do a parametric adjustment on the positions
vector total_delta_pos = {0, 0, 0};
vector subpos;
vector final_pos;
// If we're already at the high point of the interpolation then just
// stuff some values
if (cur_state == sm->num_key_pos - 1) {
po->submodel[i].mod_pos = po->submodel[i].keyframe_pos[po->num_key_pos - 1];
continue;
}
if (cur_state == 0)
po->submodel[i].mod_pos = normal_state_time * po->submodel[i].keyframe_pos[1];
else {
vm_SubVectors(&subpos, &po->submodel[i].keyframe_pos[cur_state + 1], &po->submodel[i].keyframe_pos[cur_state]);
final_pos = po->submodel[i].keyframe_pos[cur_state] + (subpos * normal_state_time);
po->submodel[i].mod_pos = final_pos;
}
}
} else {
for (i = 0; i < po->n_models; i++) {
po->submodel[i].mod_pos.x = 0;
po->submodel[i].mod_pos.y = 0;
po->submodel[i].mod_pos.z = 0;
}
}
}
// Sets the effect used by a polymodel
void SetPolymodelEffect(polymodel_effect *poly_effect) { Polymodel_effect = *poly_effect; }
// Given a model pointer and an array of floats that go from 0..1, calculate the interpolated
// position/angle of each corresponding subobject
void SetModelAnglesAndPosTimed(poly_model *po, float *normalized_time, uint32_t subobj_flags) {
int i;
ASSERT(!(po->flags & PMF_NOT_RESIDENT));
if (normalized_time) {
// get time per keyframe state
for (i = 0; i < po->n_models; i++) {
bsp_info *sm = &po->submodel[i];
int int_tick, current_frame, total_ticks;
float normal_state_time, current_tick;
if (!(subobj_flags & (1 << i)))
continue;
if (sm->num_key_pos <= 1) {
vm_MakeZero(&sm->mod_pos);
goto do_angles;
}
if (normalized_time[i] == 1.0) {
sm->mod_pos = sm->keyframe_pos[sm->num_key_pos - 1];
goto do_angles;
}
// Find out which keyframe we're at
total_ticks = sm->pos_track_max - sm->pos_track_min;
current_tick = (normalized_time[i] * total_ticks) + sm->pos_track_min;
int_tick = current_tick;
current_frame = sm->tick_pos_remap[int_tick - sm->pos_track_min];
ASSERT(current_frame >= 0 && current_frame <= sm->num_key_pos - 1);
if (current_frame == sm->num_key_pos - 1) {
sm->mod_pos = sm->keyframe_pos[sm->num_key_pos - 1];
goto do_angles;
} else {
int ticks_between_frames = sm->pos_start_time[current_frame + 1] - sm->pos_start_time[current_frame];
float this_tick = current_tick - sm->pos_start_time[current_frame];
normal_state_time = (float)this_tick / (float)ticks_between_frames;
vector subpos;
vm_SubVectors(&subpos, &sm->keyframe_pos[current_frame + 1], &sm->keyframe_pos[current_frame]);
sm->mod_pos = sm->keyframe_pos[current_frame] + (subpos * normal_state_time);
}
// Now do angle stuff here
do_angles:
// Don't rotate turrets or auto-rotators
if (!(sm->flags & (SOF_TURRET | SOF_ROTATE))) {
if (sm->num_key_angles <= 1) {
sm->angs.p = 0;
sm->angs.h = 0;
sm->angs.b = 0;
continue;
}
if (normalized_time[i] == 1.0) {
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &po->submodel[i].keyframe_matrix[sm->num_key_angles - 1]);
continue;
}
// Find out which keyframe we're at
total_ticks = sm->rot_track_max - sm->rot_track_min;
current_tick = (normalized_time[i] * total_ticks) + sm->rot_track_min;
int_tick = current_tick;
current_frame = sm->tick_ang_remap[int_tick - sm->rot_track_min];
ASSERT(current_frame >= 0 && current_frame <= sm->num_key_angles - 1);
if (current_frame == sm->num_key_angles - 1) {
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &po->submodel[i].keyframe_matrix[sm->num_key_angles - 1]);
continue;
} else {
int ticks_between_frames = sm->rot_start_time[current_frame + 1] - sm->rot_start_time[current_frame];
float this_tick = current_tick - sm->rot_start_time[current_frame];
normal_state_time = (float)this_tick / (float)ticks_between_frames;
// Now do a parametric adjustment on the angles
matrix dest_matrix =
sm->keyframe_matrix[current_frame] +
((sm->keyframe_matrix[current_frame + 1] - sm->keyframe_matrix[current_frame]) * normal_state_time);
vm_ExtractAnglesFromMatrix(&sm->angs, &dest_matrix);
}
} else {
// Adjust special subobjects
if (po->submodel[i].flags & SOF_ROTATE) {
float flrot = Gametime * po->submodel[i].rps;
int introt = flrot;
float fdiff = flrot - introt;
ASSERT(fdiff >= 0 && fdiff <= 1);
// fdiff now equals 0 to 1
matrix temp_matrix;
vector temp_vec;
if (po->new_style)
temp_vec = po->submodel[i].keyframe_axis[1];
else
temp_vec = po->submodel[i].norm;
BuildModelAngleMatrix(&temp_matrix, fdiff * 65535, &temp_vec);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &temp_matrix);
} else if (po->submodel[i].flags & SOF_TURRET) {
matrix temp_matrix;
if (po->new_style)
BuildModelAngleMatrix(&temp_matrix, normalized_time[i] * 65535, &po->submodel[i].keyframe_axis[1]);
else
BuildModelAngleMatrix(&temp_matrix, normalized_time[i] * 65535, &po->submodel[i].norm);
vm_ExtractAnglesFromMatrix(&po->submodel[i].angs, &temp_matrix);
}
}
}
} else {
for (i = 0; i < po->n_models; i++) {
po->submodel[i].mod_pos.x = 0;
po->submodel[i].mod_pos.y = 0;
po->submodel[i].mod_pos.z = 0;
po->submodel[i].angs.p = 0;
po->submodel[i].angs.h = 0;
po->submodel[i].angs.b = 0;
}
}
}
// Sets the position and rotation of a polymodel. Used for rendering and collision detection
void SetModelAnglesAndPos(poly_model *po, float *normalized_time, uint32_t subobj_flags) {
ASSERT(!(po->flags & PMF_NOT_RESIDENT));
if (po->flags & PMF_TIMED) {
SetModelAnglesAndPosTimed(po, normalized_time, subobj_flags);
return;
} else {
SetModelAngles(po, normalized_time);
SetModelInterpPos(po, normalized_time);
}
}
static vector Instance_fog_plane_stack[MAX_SUBOBJECTS];
static vector Instance_fog_portal_vert_stack[MAX_SUBOBJECTS];
static vector Instance_light_stack[MAX_SUBOBJECTS];
static vector Instance_specular_pos[MAX_SUBOBJECTS];
static vector Instance_bump_pos[MAX_SUBOBJECTS];
static int Instance_light_cnt = 0;
void StartLightInstance(vector *pos, matrix *orient) {
int gouraud = 0, specular = 0, fogged = 0, bumped = 0;
if (Polymodel_light_type == POLYMODEL_LIGHTING_GOURAUD)
gouraud = 1;
if (Polymodel_use_effect && Polymodel_effect.type & PEF_FOGGED_MODEL)
fogged = 1;
if (Polymodel_use_effect && Polymodel_effect.type & (PEF_SPECULAR_MODEL | PEF_SPECULAR_FACES))
specular = 1;
if (Polymodel_use_effect && (Polymodel_effect.type & PEF_BUMPMAPPED))
bumped = 1;
if (gouraud)
Instance_light_stack[Instance_light_cnt] = *Polymodel_light_direction;
if (fogged) {
Instance_fog_plane_stack[Instance_light_cnt] = Polymodel_fog_plane;
Instance_fog_portal_vert_stack[Instance_light_cnt] = Polymodel_fog_portal_vert;
}
if (specular)
Instance_specular_pos[Instance_light_cnt] = Polymodel_specular_pos;
if (bumped)
Instance_bump_pos[Instance_light_cnt] = Polymodel_bump_pos;
Instance_light_cnt++;
vector temp_vec;
if (gouraud) {
vm_MatrixMulVector(&temp_vec, Polymodel_light_direction, orient);
*Polymodel_light_direction = temp_vec;
}
if (fogged) {
vector tempv = Polymodel_fog_portal_vert - *pos;
vm_MatrixMulVector(&temp_vec, &tempv, orient);
Polymodel_fog_portal_vert = temp_vec;
vm_MatrixMulVector(&temp_vec, &Polymodel_fog_plane, orient);
Polymodel_fog_plane = temp_vec;
}
if (specular) {
vector tempv = Polymodel_specular_pos - *pos;
vm_MatrixMulVector(&temp_vec, &tempv, orient);
Polymodel_specular_pos = temp_vec;
}
if (bumped) {
vector tempv = Polymodel_bump_pos - *pos;
vm_MatrixMulVector(&temp_vec, &tempv, orient);
Polymodel_bump_pos = temp_vec;
}
}
void DoneLightInstance() {
ASSERT(Instance_light_cnt != 0);
Instance_light_cnt--;
if (Polymodel_light_type == POLYMODEL_LIGHTING_GOURAUD)
*Polymodel_light_direction = Instance_light_stack[Instance_light_cnt];
if (Polymodel_use_effect && Polymodel_effect.type & PEF_FOGGED_MODEL) {
Polymodel_fog_plane = Instance_fog_plane_stack[Instance_light_cnt];
Polymodel_fog_portal_vert = Instance_fog_portal_vert_stack[Instance_light_cnt];
}
if (Polymodel_use_effect && Polymodel_effect.type & (PEF_SPECULAR_MODEL | PEF_SPECULAR_FACES))
Polymodel_specular_pos = Instance_specular_pos[Instance_light_cnt];
if (Polymodel_use_effect && (Polymodel_effect.type & PEF_BUMPMAPPED))
Polymodel_bump_pos = Instance_bump_pos[Instance_light_cnt];
}
// Draws a polygon model to the viewport
// Normalized_time is an array of floats from 0 to 1 that represent how far into
// an animation state we are
// This is the static light version
void DrawPolygonModel(vector *pos, matrix *orient, int model_num, float *normalized_time, int flags, float r, float g,
float b, uint32_t f_render_sub, uint8_t use_effect, uint8_t overlay) {
poly_model *po;
ASSERT(Poly_models[model_num].used);
ASSERT(!(Poly_models[model_num].flags & PMF_NOT_RESIDENT));
GetPolymodelPointer(model_num);
Polymodel_use_effect = use_effect;
Polymodel_light_type = POLYMODEL_LIGHTING_STATIC;
Polylighting_static_red = r;
Polylighting_static_green = g;
Polylighting_static_blue = b;
rend_SetOverlayType(OT_NONE);
rend_SetLighting(LS_NONE);
po = &Poly_models[model_num];
g3_StartInstanceMatrix(pos, orient);
if (Polymodel_use_effect && Polymodel_effect.type & PEF_FOGGED_MODEL) {
Polymodel_fog_plane = Polymodel_effect.fog_plane;
Polymodel_fog_portal_vert = Polymodel_effect.fog_portal_vert;
}
if (Polymodel_use_effect && Polymodel_effect.type & (PEF_SPECULAR_MODEL | PEF_SPECULAR_FACES))
Polymodel_specular_pos = Polymodel_effect.spec_light_pos;
if (Polymodel_use_effect && (Polymodel_effect.type & PEF_BUMPMAPPED))
Polymodel_bump_pos = Polymodel_effect.bump_light_pos;
StartLightInstance(pos, orient);
SetModelAnglesAndPos(po, normalized_time, f_render_sub);
if (f_render_sub == 0xFFFFFFFF || overlay) // draw entire object
{
if (po->new_style) {
RenderPolygonModel(po, f_render_sub);
}
} else {
ASSERT(po->new_style == 1);
rend_SetAlphaType(ATF_CONSTANT + ATF_VERTEX);
int i;
for (i = 0; i < po->n_models; i++) {
if ((f_render_sub & (1 << i))) {
if (i != 0) {
// if submodel, rotate around its center point, not pivot point
vector ofs = (po->submodel[i].min + po->submodel[i].max) / 2;
vector save_offset = po->submodel[i].offset;
vm_MakeZero(&po->submodel[i].offset);
vm_MakeZero(&po->submodel[i].mod_pos);
memset(&po->submodel[i].angs, 0, sizeof(angvec));
ofs *= -1;
po->submodel[i].offset = ofs;
RenderSubmodel(po, &po->submodel[i], f_render_sub);
po->submodel[i].offset = save_offset;
} else
RenderSubmodel(po, &po->submodel[i], f_render_sub);
}
}
}
DoneLightInstance();
g3_DoneInstance();
}
// This draws a gouraud shaded version
void DrawPolygonModel(vector *pos, matrix *orient, int model_num, float *normalized_time, int flags, vector *lightdir,
float r, float g, float b, uint32_t f_render_sub, uint8_t use_effect, uint8_t overlay) {
poly_model *po;
vector light_vec = *lightdir;
Polymodel_use_effect = use_effect;
ASSERT(Poly_models[model_num].used);
ASSERT(!(Poly_models[model_num].flags & PMF_NOT_RESIDENT));
GetPolymodelPointer(model_num);
rend_SetOverlayType(OT_NONE);
rend_SetLighting(LS_GOURAUD);
rend_SetColorModel(CM_RGB);
Polymodel_light_type = POLYMODEL_LIGHTING_GOURAUD;
Polylighting_static_red = r;
Polylighting_static_green = g;
Polylighting_static_blue = b;
Polymodel_light_direction = &light_vec;
if (Polymodel_use_effect && Polymodel_effect.type & PEF_FOGGED_MODEL) {
Polymodel_fog_plane = Polymodel_effect.fog_plane;
Polymodel_fog_portal_vert = Polymodel_effect.fog_portal_vert;
}
if (Polymodel_use_effect && Polymodel_effect.type & (PEF_SPECULAR_MODEL | PEF_SPECULAR_FACES))
Polymodel_specular_pos = Polymodel_effect.spec_light_pos;
if (Polymodel_use_effect && (Polymodel_effect.type & PEF_BUMPMAPPED))
Polymodel_bump_pos = Polymodel_effect.bump_light_pos;
g3_StartInstanceMatrix(pos, orient);
StartLightInstance(pos, orient);
po = &Poly_models[model_num];
SetModelAnglesAndPos(po, normalized_time, f_render_sub);
if (f_render_sub == 0xFFFFFFFF || overlay) // draw entire object
{
if (po->new_style)
RenderPolygonModel(po, f_render_sub);
} else {
ASSERT(po->new_style == 1);
rend_SetAlphaType(ATF_CONSTANT + ATF_VERTEX);
int i;
for (i = 0; i < po->n_models; i++) {
if ((f_render_sub & (1 << i))) {
if (i != 0) {
// if submodel, rotate around its center point, not pivot point
vector ofs = (po->submodel[i].min + po->submodel[i].max) / 2;
vector save_offset = po->submodel[i].offset;
vm_MakeZero(&po->submodel[i].offset);
vm_MakeZero(&po->submodel[i].mod_pos);
memset(&po->submodel[i].angs, 0, sizeof(angvec));
ofs *= -1;
po->submodel[i].offset = ofs;
RenderSubmodel(po, &po->submodel[i], f_render_sub);
po->submodel[i].offset = save_offset;
} else
RenderSubmodel(po, &po->submodel[i], f_render_sub);
}
}
}
g3_DoneInstance();
DoneLightInstance();
}
// This draws a lightmap shaded version
void DrawPolygonModel(vector *pos, matrix *orient, int model_num, float *normalized_time, int flags,
lightmap_object *lm_object, uint32_t f_render_sub, uint8_t use_effect, uint8_t overlay) {
poly_model *po;
ASSERT(Poly_models[model_num].used);
ASSERT(!(Poly_models[model_num].flags & PMF_NOT_RESIDENT));
GetPolymodelPointer(model_num);
Polymodel_use_effect = use_effect;
rend_SetLighting(LS_NONE);
Polymodel_light_type = POLYMODEL_LIGHTING_LIGHTMAP;
Polylighting_lightmap_object = lm_object;
if (Polymodel_use_effect && Polymodel_effect.type & PEF_FOGGED_MODEL) {
Polymodel_fog_plane = Polymodel_effect.fog_plane;
Polymodel_fog_portal_vert = Polymodel_effect.fog_portal_vert;
}
if (Polymodel_use_effect && Polymodel_effect.type & (PEF_SPECULAR_MODEL | PEF_SPECULAR_FACES))
Polymodel_specular_pos = Polymodel_effect.spec_light_pos;
if (Polymodel_use_effect && (Polymodel_effect.type & PEF_BUMPMAPPED))
Polymodel_bump_pos = Polymodel_effect.bump_light_pos;
g3_StartInstanceMatrix(pos, orient);
StartLightInstance(pos, orient);
po = &Poly_models[model_num];
SetModelAnglesAndPos(po, normalized_time, f_render_sub);
if (f_render_sub == 0xFFFFFFFF || overlay) // draw entire object
{
if (po->new_style)
RenderPolygonModel(po, f_render_sub);
} else {
ASSERT(po->new_style == 1);
rend_SetAlphaType(ATF_CONSTANT + ATF_VERTEX);
int i;
for (i = 0; i < po->n_models; i++) {
if ((f_render_sub & (1 << i))) {
if (i != 0) {
// if submodel, rotate around its center point, not pivot point
vector ofs = (po->submodel[i].min + po->submodel[i].max) / 2;
vector save_offset = po->submodel[i].offset;
vm_MakeZero(&po->submodel[i].offset);
vm_MakeZero(&po->submodel[i].mod_pos);
memset(&po->submodel[i].angs, 0, sizeof(angvec));
ofs *= -1;
po->submodel[i].offset = ofs;
RenderSubmodel(po, &po->submodel[i], f_render_sub);
po->submodel[i].offset = save_offset;
} else
RenderSubmodel(po, &po->submodel[i], f_render_sub);
}
}
}
g3_DoneInstance();
DoneLightInstance();
rend_SetOverlayType(OT_NONE);
}
void FreeAllModels() {
for (int i = 0; i < MAX_POLY_MODELS; i++) {
if (Poly_models[i].used > 0) {
Poly_models[i].used = 1;
FreePolyModel(i);
}
}
}
// Inits our models array and loads our ship pof
int InitModels() {
for (auto &Poly_model : Poly_models) {
memset(&Poly_model, 0, sizeof(poly_model));
Poly_model.used = 0;
}
atexit(FreeAllModels);
return 1;
}
// MTS: used only in this file.
// Given an actual keyframe number, returns the normalized (0 to 1) position of that
// keyframe
// Handle is an index into the Poly_models array
float GetNormalizedKeyframe(int handle, float num) {
poly_model *pm = &Poly_models[handle];
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
ASSERT(pm->used);
ASSERT(num >= 0 && num <= pm->max_keys);
return ((num / (float)pm->max_keys));
}
// Goes through all poly models and gets all missing textures
void RemapPolyModels() {
int i;
for (i = 0; i < MAX_POLY_MODELS; i++) {
int remap = 0;
if (Poly_models[i].used == 0 || (Poly_models[i].flags & PMF_NOT_RESIDENT))
continue;
for (int t = 0; t < Poly_models[i].n_textures; t++)
if (Poly_models[i].textures[t] == 0) // is there a bad texture?
remap = 1;
if (remap == 1) {
// remap the damn textures
mprintf(0, "Remapping model textures for model %s.\n", Poly_models[i].name);
ReloadModelTextures(i);
}
}
}
// MTS: not used?
// Returns the total number of faces in a model
int CountFacesInPolymodel(poly_model *pm) {
int i;
ASSERT(pm->used > 0);
int count = 0;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
for (i = 0; i < pm->n_models; i++) {
if (IsNonRenderableSubmodel(pm, i))
continue;
count += pm->submodel[i].num_faces;
}
return count;
}
// Given an object, a submodel, and a vertex number, calculates the world position
// of that point
void GetPolyModelPointInWorld(vector *dest, poly_model *pm, vector *wpos, matrix *orient, int subnum, vector *pos,
vector *norm) {
bsp_info *sm = &pm->submodel[subnum];
float normalized_time[MAX_SUBOBJECTS];
int i;
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
if (!pm->new_style)
return;
for (i = 0; i < MAX_SUBOBJECTS; i++)
normalized_time[i] = 0.0;
SetModelAnglesAndPos(pm, normalized_time);
vector pnt = *pos;
int mn = subnum;
vector cur_norm;
if (norm != nullptr)
cur_norm = *norm;
matrix m;
// Instance up the tree for this gun
while (mn != -1) {
vector tpnt;
vm_AnglesToMatrix(&m, pm->submodel[mn].angs.p, pm->submodel[mn].angs.h, pm->submodel[mn].angs.b);
vm_TransposeMatrix(&m);
tpnt = pnt * m;
if (norm != nullptr)
cur_norm = cur_norm * m;
pnt = tpnt + pm->submodel[mn].offset + pm->submodel[mn].mod_pos;
mn = pm->submodel[mn].parent;
}
// now instance for the entire object
m = *orient;
vm_TransposeMatrix(&m);
if (norm != nullptr)
*norm = (cur_norm * m);
*dest = pnt * m;
*dest += (*wpos);
}
void GetPolyModelPointInWorld(vector *dest, poly_model *pm, vector *wpos, matrix *orient, int subnum,
float *normalized_time, vector *pos, vector *norm) {
bsp_info *sm = &pm->submodel[subnum];
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
if (!pm->new_style)
return;
SetModelAnglesAndPos(pm, normalized_time);
vector pnt = *pos;
int mn = subnum;
vector cur_norm;
if (norm != nullptr)
cur_norm = *norm;
matrix m;
// Instance up the tree for this gun
while (mn != -1) {
vector tpnt;
vm_AnglesToMatrix(&m, pm->submodel[mn].angs.p, pm->submodel[mn].angs.h, pm->submodel[mn].angs.b);
vm_TransposeMatrix(&m);
tpnt = pnt * m;
if (norm != nullptr)
cur_norm = cur_norm * m;
pnt = tpnt + pm->submodel[mn].offset + pm->submodel[mn].mod_pos;
mn = pm->submodel[mn].parent;
}
// now instance for the entire object
m = *orient;
vm_TransposeMatrix(&m);
if (norm != nullptr)
*norm = (cur_norm * m);
*dest = pnt * m;
*dest += (*wpos);
}
// Returns 1 if this submodel shouldn't be rendered
int IsNonRenderableSubmodel(poly_model *pm, int submodelnum) {
ASSERT(pm->used);
ASSERT(!(pm->flags & PMF_NOT_RESIDENT));
if ((pm->submodel[submodelnum].flags & SOF_FRONTFACE) || (pm->submodel[submodelnum].flags & SOF_SHELL))
return 1;
if (pm->submodel[submodelnum].num_faces == 0)
return 1;
return 0;
}