mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 11:28:56 +00:00
3235 lines
92 KiB
C++
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 "log.h"
|
|
#include "mem.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) {
|
|
LOG_WARNING.printf("Old POF Version of %d fixed up to %d", version, version * 100);
|
|
version *= 100;
|
|
}
|
|
|
|
if (version < PM_COMPATIBLE_VERSION || version > PM_OBJFILE_VERSION) {
|
|
LOG_ERROR.printf("Bad version (%d) in model file!", 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 {
|
|
LOG_WARNING << "Ingoring ATTACH normals - total number doesn't match number of attach points";
|
|
// 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)) {
|
|
LOG_WARNING.printf("You have a rotator that has no keyframe on model %s.", pm->name);
|
|
pm->submodel[i].flags &= ~SOF_ROTATE;
|
|
} else if (pm->submodel[i].num_key_angles == 0 && (pm->submodel[i].flags & SOF_TURRET)) {
|
|
LOG_WARNING.printf("You have a turret that has no keyframe on model %s.", 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) {
|
|
LOG_ERROR << "Couldn't get a good keyframe!";
|
|
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) {
|
|
LOG_ERROR << "Couldn't get a good keyframe!";
|
|
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) {
|
|
LOG_ERROR.printf("This model has more than the max number of subobjects! (%d)", 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;
|
|
|
|
LOG_DEBUG.printf("Model '%s' usage count is now %d.", 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;
|
|
}
|
|
|
|
LOG_DEBUG.printf("Paging in polymodel %s.", 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
|
|
LOG_DEBUG.printf("Remapping model textures for model %s.", 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
|
|
LOG_DEBUG.printf("Remapping model textures for model %s.", 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;
|
|
}
|