mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-15 08:52:15 +00:00
1981 lines
50 KiB
C++
1981 lines
50 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
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/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../server/exe_headers.h"
|
|
|
|
#ifndef __Q_SHARED_H
|
|
#include "../qcommon/q_shared.h"
|
|
#endif
|
|
|
|
#include "tr_common.h"
|
|
|
|
#if !defined(TR_LOCAL_H)
|
|
#include "tr_local.h"
|
|
#endif
|
|
|
|
#include "qcommon/matcomp.h"
|
|
|
|
#if !defined(G2_H_INC)
|
|
#include "../ghoul2/G2.h"
|
|
#endif
|
|
|
|
#if !defined (MINIHEAP_H_INC)
|
|
#include "../qcommon/MiniHeap.h"
|
|
#endif
|
|
|
|
#define G2_MODEL_OK(g) ((g)&&(g)->mValid&&(g)->aHeader&&(g)->currentModel&&(g)->animModel)
|
|
|
|
#include "../server/server.h"
|
|
|
|
#include <float.h>
|
|
|
|
#include "qcommon/ojk_saved_game_helper.h"
|
|
|
|
#ifdef _G2_GORE
|
|
#include "../ghoul2/ghoul2_gore.h"
|
|
|
|
#define GORE_TAG_UPPER (256)
|
|
#define GORE_TAG_MASK (~255)
|
|
|
|
static int CurrentTag=GORE_TAG_UPPER+1;
|
|
static int CurrentTagUpper=GORE_TAG_UPPER;
|
|
|
|
static std::map<int,GoreTextureCoordinates> GoreRecords;
|
|
static std::map<std::pair<int,int>,int> GoreTagsTemp; // this is a surface index to gore tag map used only
|
|
// temporarily during the generation phase so we reuse gore tags per LOD
|
|
int goreModelIndex;
|
|
|
|
static cvar_t *cg_g2MarksAllModels=NULL;
|
|
|
|
GoreTextureCoordinates *FindGoreRecord(int tag);
|
|
static inline void DestroyGoreTexCoordinates(int tag)
|
|
{
|
|
GoreTextureCoordinates *gTC = FindGoreRecord(tag);
|
|
if (!gTC)
|
|
{
|
|
return;
|
|
}
|
|
(*gTC).~GoreTextureCoordinates();
|
|
//I don't know what's going on here, it should call the destructor for
|
|
//this when it erases the record but sometimes it doesn't. -rww
|
|
}
|
|
|
|
//TODO: This needs to be set via a scalability cvar with some reasonable minimum value if pgore is used at all
|
|
#define MAX_GORE_RECORDS (500)
|
|
|
|
int AllocGoreRecord()
|
|
{
|
|
while (GoreRecords.size()>MAX_GORE_RECORDS)
|
|
{
|
|
int tagHigh=(*GoreRecords.begin()).first&GORE_TAG_MASK;
|
|
std::map<int,GoreTextureCoordinates>::iterator it;
|
|
GoreTextureCoordinates *gTC;
|
|
|
|
it = GoreRecords.begin();
|
|
gTC = &(*it).second;
|
|
|
|
if (gTC)
|
|
{
|
|
gTC->~GoreTextureCoordinates();
|
|
}
|
|
GoreRecords.erase(GoreRecords.begin());
|
|
while (GoreRecords.size())
|
|
{
|
|
if (((*GoreRecords.begin()).first&GORE_TAG_MASK)!=tagHigh)
|
|
{
|
|
break;
|
|
}
|
|
it = GoreRecords.begin();
|
|
gTC = &(*it).second;
|
|
|
|
if (gTC)
|
|
{
|
|
gTC->~GoreTextureCoordinates();
|
|
}
|
|
GoreRecords.erase(GoreRecords.begin());
|
|
}
|
|
}
|
|
int ret=CurrentTag;
|
|
GoreRecords[CurrentTag]=GoreTextureCoordinates();
|
|
CurrentTag++;
|
|
return ret;
|
|
}
|
|
|
|
void ResetGoreTag()
|
|
{
|
|
GoreTagsTemp.clear();
|
|
CurrentTag=CurrentTagUpper;
|
|
CurrentTagUpper+=GORE_TAG_UPPER;
|
|
}
|
|
|
|
GoreTextureCoordinates *FindGoreRecord(int tag)
|
|
{
|
|
std::map<int,GoreTextureCoordinates>::iterator i=GoreRecords.find(tag);
|
|
if (i!=GoreRecords.end())
|
|
{
|
|
return &(*i).second;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void *G2_GetGoreRecord(int tag)
|
|
{
|
|
return FindGoreRecord(tag);
|
|
}
|
|
|
|
void DeleteGoreRecord(int tag)
|
|
{
|
|
DestroyGoreTexCoordinates(tag);
|
|
GoreRecords.erase(tag);
|
|
}
|
|
|
|
static int CurrentGoreSet=1; // this is a UUID for gore sets
|
|
static std::map<int,CGoreSet *> GoreSets; // map from uuid to goreset
|
|
|
|
CGoreSet *FindGoreSet(int goreSetTag)
|
|
{
|
|
std::map<int,CGoreSet *>::iterator f=GoreSets.find(goreSetTag);
|
|
if (f!=GoreSets.end())
|
|
{
|
|
return (*f).second;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
CGoreSet *NewGoreSet()
|
|
{
|
|
CGoreSet *ret=new CGoreSet(CurrentGoreSet++);
|
|
GoreSets[ret->mMyGoreSetTag]=ret;
|
|
ret->mRefCount = 1;
|
|
return ret;
|
|
}
|
|
|
|
void DeleteGoreSet(int goreSetTag)
|
|
{
|
|
std::map<int,CGoreSet *>::iterator f=GoreSets.find(goreSetTag);
|
|
if (f!=GoreSets.end())
|
|
{
|
|
if ( (*f).second->mRefCount == 0 || (*f).second->mRefCount - 1 == 0 )
|
|
{
|
|
delete (*f).second;
|
|
GoreSets.erase(f);
|
|
}
|
|
else
|
|
{
|
|
(*f).second->mRefCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
CGoreSet::~CGoreSet()
|
|
{
|
|
std::multimap<int,SGoreSurface>::iterator i;
|
|
for (i=mGoreRecords.begin();i!=mGoreRecords.end();++i)
|
|
{
|
|
DeleteGoreRecord((*i).second.mGoreTag);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
extern mdxaBone_t worldMatrix;
|
|
extern mdxaBone_t worldMatrixInv;
|
|
|
|
const mdxaBone_t &EvalBoneCache(int index,CBoneCache *boneCache);
|
|
class CTraceSurface
|
|
{
|
|
public:
|
|
int surfaceNum;
|
|
surfaceInfo_v &rootSList;
|
|
const model_t *currentModel;
|
|
const int lod;
|
|
vec3_t rayStart;
|
|
vec3_t rayEnd;
|
|
CCollisionRecord *collRecMap;
|
|
const int entNum;
|
|
const int modelIndex;
|
|
const skin_t *skin;
|
|
const jk_shader_t *cust_shader;
|
|
intptr_t *TransformedVertsArray;
|
|
const EG2_Collision eG2TraceType;
|
|
bool hitOne;
|
|
float m_fRadius;
|
|
|
|
#ifdef _G2_GORE
|
|
//gore application thing
|
|
float ssize;
|
|
float tsize;
|
|
float theta;
|
|
int goreShader;
|
|
CGhoul2Info *ghoul2info;
|
|
|
|
// Procedural-gore application things
|
|
SSkinGoreData *gore;
|
|
#endif
|
|
|
|
CTraceSurface(
|
|
int initsurfaceNum,
|
|
surfaceInfo_v &initrootSList,
|
|
const model_t *initcurrentModel,
|
|
int initlod,
|
|
vec3_t initrayStart,
|
|
vec3_t initrayEnd,
|
|
CCollisionRecord *initcollRecMap,
|
|
int initentNum,
|
|
int initmodelIndex,
|
|
const skin_t *initskin,
|
|
const jk_shader_t *initcust_shader,
|
|
intptr_t *initTransformedVertsArray,
|
|
const EG2_Collision einitG2TraceType,
|
|
#ifdef _G2_GORE
|
|
float fRadius,
|
|
float initssize,
|
|
float inittsize,
|
|
float inittheta,
|
|
int initgoreShader,
|
|
CGhoul2Info *initghoul2info,
|
|
SSkinGoreData *initgore
|
|
#else
|
|
float fRadius
|
|
#endif
|
|
):
|
|
|
|
surfaceNum(initsurfaceNum),
|
|
rootSList(initrootSList),
|
|
currentModel(initcurrentModel),
|
|
lod(initlod),
|
|
collRecMap(initcollRecMap),
|
|
entNum(initentNum),
|
|
modelIndex(initmodelIndex),
|
|
skin(initskin),
|
|
cust_shader(initcust_shader),
|
|
TransformedVertsArray(initTransformedVertsArray),
|
|
eG2TraceType(einitG2TraceType),
|
|
hitOne(false),
|
|
#ifdef _G2_GORE
|
|
m_fRadius(fRadius),
|
|
ssize(initssize),
|
|
tsize(inittsize),
|
|
theta(inittheta),
|
|
goreShader(initgoreShader),
|
|
ghoul2info(initghoul2info),
|
|
gore(initgore)
|
|
#else
|
|
m_fRadius(fRadius)
|
|
#endif
|
|
{
|
|
VectorCopy(initrayStart, rayStart);
|
|
VectorCopy(initrayEnd, rayEnd);
|
|
}
|
|
};
|
|
|
|
// assorted Ghoul 2 functions.
|
|
// list all surfaces associated with a model
|
|
void G2_List_Model_Surfaces(const char *fileName)
|
|
{
|
|
int i, x;
|
|
model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName));
|
|
mdxmSurfHierarchy_t *surf;
|
|
|
|
surf = (mdxmSurfHierarchy_t *) ( (byte *)mod_m->mdxm + mod_m->mdxm->ofsSurfHierarchy );
|
|
mdxmSurface_t *surface = (mdxmSurface_t *)((byte *)mod_m->mdxm + mod_m->mdxm->ofsLODs + sizeof(mdxmLOD_t));
|
|
|
|
for ( x = 0 ; x < mod_m->mdxm->numSurfaces ; x++)
|
|
{
|
|
Com_Printf("Surface %i Name %s\n", x, surf->name);
|
|
if (r_verbose->value)
|
|
{
|
|
Com_Printf("Num Descendants %i\n", surf->numChildren);
|
|
for (i=0; i<surf->numChildren; i++)
|
|
{
|
|
Com_Printf("Descendant %i\n", surf->childIndexes[i]);
|
|
}
|
|
}
|
|
// find the next surface
|
|
surf = (mdxmSurfHierarchy_t *)( (byte *)surf + (intptr_t)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surf->numChildren ] ));
|
|
surface =(mdxmSurface_t *)( (byte *)surface + surface->ofsEnd );
|
|
}
|
|
|
|
}
|
|
|
|
// list all bones associated with a model
|
|
void G2_List_Model_Bones(const char *fileName, int frame)
|
|
{
|
|
int x, i;
|
|
mdxaSkel_t *skel;
|
|
mdxaSkelOffsets_t *offsets;
|
|
model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName));
|
|
model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex);
|
|
// mdxaFrame_t *aframe=0;
|
|
// int frameSize;
|
|
mdxaHeader_t *header = mod_a->mdxa;
|
|
|
|
// figure out where the offset list is
|
|
offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t));
|
|
|
|
// frameSize = (int)( &((mdxaFrame_t *)0)->boneIndexes[ header->numBones ] );
|
|
|
|
// aframe = (mdxaFrame_t *)((byte *)header + header->ofsFrames + (frame * frameSize));
|
|
// walk each bone and list it's name
|
|
for (x=0; x< mod_a->mdxa->numBones; x++)
|
|
{
|
|
skel = (mdxaSkel_t *)((byte *)header + sizeof(mdxaHeader_t) + offsets->offsets[x]);
|
|
Com_Printf("Bone %i Name %s\n", x, skel->name);
|
|
|
|
Com_Printf("X pos %f, Y pos %f, Z pos %f\n", skel->BasePoseMat.matrix[0][3], skel->BasePoseMat.matrix[1][3], skel->BasePoseMat.matrix[2][3]);
|
|
|
|
// if we are in verbose mode give us more details
|
|
if (r_verbose->value)
|
|
{
|
|
Com_Printf("Num Descendants %i\n", skel->numChildren);
|
|
for (i=0; i<skel->numChildren; i++)
|
|
{
|
|
Com_Printf("Num Descendants %i\n", skel->numChildren);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************************************
|
|
* G2_GetAnimFileName
|
|
* obtain the .gla filename for a model
|
|
*
|
|
* Input
|
|
* filename of model
|
|
*
|
|
* Output
|
|
* true if we successfully obtained a filename, false otherwise
|
|
*
|
|
************************************************************************************************/
|
|
qboolean G2_GetAnimFileName(const char *fileName, char **filename)
|
|
{
|
|
// find the model we want
|
|
model_t *mod = R_GetModelByHandle(RE_RegisterModel(fileName));
|
|
|
|
if (mod && mod->mdxm && (mod->mdxm->animName[0] != 0))
|
|
{
|
|
*filename = mod->mdxm->animName;
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Code for collision detection for models gameside
|
|
//
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
int G2_DecideTraceLod(CGhoul2Info &ghoul2, int useLod)
|
|
{
|
|
int returnLod = useLod;
|
|
|
|
// if we are overriding the LOD at top level, then we can afford to only check this level of model
|
|
if (ghoul2.mLodBias > returnLod)
|
|
{
|
|
returnLod = ghoul2.mLodBias;
|
|
}
|
|
assert(G2_MODEL_OK(&ghoul2));
|
|
|
|
assert(ghoul2.currentModel);
|
|
assert(ghoul2.currentModel->mdxm);
|
|
//what about r_lodBias?
|
|
|
|
// now ensure that we haven't selected a lod that doesn't exist for this model
|
|
if ( returnLod >= ghoul2.currentModel->mdxm->numLODs )
|
|
{
|
|
returnLod = ghoul2.currentModel->mdxm->numLODs - 1;
|
|
}
|
|
|
|
return returnLod;
|
|
}
|
|
|
|
void R_TransformEachSurface( const mdxmSurface_t *surface, vec3_t scale, CMiniHeap *G2VertSpace, intptr_t *TransformedVertsArray,CBoneCache *boneCache)
|
|
{
|
|
int j, k;
|
|
mdxmVertex_t *v;
|
|
float *TransformedVerts;
|
|
|
|
//
|
|
// deform the vertexes by the lerped bones
|
|
//
|
|
int *piBoneReferences = (int*) ((byte*)surface + surface->ofsBoneReferences);
|
|
|
|
// alloc some space for the transformed verts to get put in
|
|
TransformedVerts = (float *)G2VertSpace->MiniHeapAlloc(surface->numVerts * 5 * 4);
|
|
TransformedVertsArray[surface->thisSurfaceIndex] = (intptr_t)TransformedVerts;
|
|
if (!TransformedVerts)
|
|
{
|
|
assert(TransformedVerts);
|
|
Com_Error(ERR_DROP, "Ran out of transform space for Ghoul2 Models. Adjust G2_MINIHEAP_SIZE in sv_init.cpp.\n");
|
|
}
|
|
|
|
// whip through and actually transform each vertex
|
|
const int numVerts = surface->numVerts;
|
|
v = (mdxmVertex_t *) ((byte *)surface + surface->ofsVerts);
|
|
mdxmVertexTexCoord_t *pTexCoords = (mdxmVertexTexCoord_t *) &v[numVerts];
|
|
|
|
// optimisation issue
|
|
if ((scale[0] != 1.0) || (scale[1] != 1.0) || (scale[2] != 1.0))
|
|
{
|
|
for ( j = 0; j < numVerts; j++ )
|
|
{
|
|
vec3_t tempVert, tempNormal;
|
|
// mdxmWeight_t *w;
|
|
|
|
VectorClear( tempVert );
|
|
VectorClear( tempNormal );
|
|
// w = v->weights;
|
|
|
|
const int iNumWeights = G2_GetVertWeights( v );
|
|
|
|
float fTotalWeight = 0.0f;
|
|
for ( k = 0 ; k < iNumWeights ; k++ )
|
|
{
|
|
int iBoneIndex = G2_GetVertBoneIndex( v, k );
|
|
float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights );
|
|
|
|
const mdxaBone_t &bone=EvalBoneCache(piBoneReferences[iBoneIndex],boneCache);
|
|
|
|
tempVert[0] += fBoneWeight * ( DotProduct( bone.matrix[0], v->vertCoords ) + bone.matrix[0][3] );
|
|
tempVert[1] += fBoneWeight * ( DotProduct( bone.matrix[1], v->vertCoords ) + bone.matrix[1][3] );
|
|
tempVert[2] += fBoneWeight * ( DotProduct( bone.matrix[2], v->vertCoords ) + bone.matrix[2][3] );
|
|
|
|
tempNormal[0] += fBoneWeight * DotProduct( bone.matrix[0], v->normal );
|
|
tempNormal[1] += fBoneWeight * DotProduct( bone.matrix[1], v->normal );
|
|
tempNormal[2] += fBoneWeight * DotProduct( bone.matrix[2], v->normal );
|
|
}
|
|
int pos = j * 5;
|
|
|
|
// copy tranformed verts into temp space
|
|
TransformedVerts[pos++] = tempVert[0] * scale[0];
|
|
TransformedVerts[pos++] = tempVert[1] * scale[1];
|
|
TransformedVerts[pos++] = tempVert[2] * scale[2];
|
|
// we will need the S & T coors too for hitlocation and hitmaterial stuff
|
|
TransformedVerts[pos++] = pTexCoords[j].texCoords[0];
|
|
TransformedVerts[pos] = pTexCoords[j].texCoords[1];
|
|
|
|
v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int pos = 0;
|
|
for ( j = 0; j < numVerts; j++ )
|
|
{
|
|
vec3_t tempVert, tempNormal;
|
|
// const mdxmWeight_t *w;
|
|
|
|
VectorClear( tempVert );
|
|
VectorClear( tempNormal );
|
|
// w = v->weights;
|
|
|
|
const int iNumWeights = G2_GetVertWeights( v );
|
|
|
|
float fTotalWeight = 0.0f;
|
|
for ( k = 0 ; k < iNumWeights ; k++ )
|
|
{
|
|
int iBoneIndex = G2_GetVertBoneIndex( v, k );
|
|
float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights );
|
|
|
|
const mdxaBone_t &bone=EvalBoneCache(piBoneReferences[iBoneIndex],boneCache);
|
|
|
|
tempVert[0] += fBoneWeight * ( DotProduct( bone.matrix[0], v->vertCoords ) + bone.matrix[0][3] );
|
|
tempVert[1] += fBoneWeight * ( DotProduct( bone.matrix[1], v->vertCoords ) + bone.matrix[1][3] );
|
|
tempVert[2] += fBoneWeight * ( DotProduct( bone.matrix[2], v->vertCoords ) + bone.matrix[2][3] );
|
|
|
|
tempNormal[0] += fBoneWeight * DotProduct( bone.matrix[0], v->normal );
|
|
tempNormal[1] += fBoneWeight * DotProduct( bone.matrix[1], v->normal );
|
|
tempNormal[2] += fBoneWeight * DotProduct( bone.matrix[2], v->normal );
|
|
}
|
|
|
|
// copy tranformed verts into temp space
|
|
TransformedVerts[pos++] = tempVert[0];
|
|
TransformedVerts[pos++] = tempVert[1];
|
|
TransformedVerts[pos++] = tempVert[2];
|
|
// we will need the S & T coors too for hitlocation and hitmaterial stuff
|
|
TransformedVerts[pos++] = pTexCoords[j].texCoords[0];
|
|
TransformedVerts[pos++] = pTexCoords[j].texCoords[1];
|
|
|
|
v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights];
|
|
}
|
|
}
|
|
}
|
|
|
|
void G2_TransformSurfaces(int surfaceNum, surfaceInfo_v &rootSList,
|
|
CBoneCache *boneCache, const model_t *currentModel, int lod, vec3_t scale, CMiniHeap *G2VertSpace, intptr_t *TransformedVertArray, bool secondTimeAround)
|
|
{
|
|
int i;
|
|
assert(currentModel);
|
|
assert(currentModel->mdxm);
|
|
// back track and get the surfinfo struct for this surface
|
|
const mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(currentModel, surfaceNum, lod);
|
|
const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)currentModel->mdxm + sizeof(mdxmHeader_t));
|
|
const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]);
|
|
|
|
// see if we have an override surface in the surface list
|
|
const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(surfaceNum, rootSList);
|
|
|
|
// really, we should use the default flags for this surface unless it's been overriden
|
|
int offFlags = surfInfo->flags;
|
|
|
|
if (surfOverride)
|
|
{
|
|
offFlags = surfOverride->offFlags;
|
|
}
|
|
// if this surface is not off, add it to the shader render list
|
|
if (!offFlags)
|
|
{
|
|
|
|
R_TransformEachSurface(surface, scale, G2VertSpace, TransformedVertArray, boneCache);
|
|
}
|
|
|
|
// if we are turning off all descendants, then stop this recursion now
|
|
if (offFlags & G2SURFACEFLAG_NODESCENDANTS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// now recursively call for the children
|
|
for (i=0; i< surfInfo->numChildren; i++)
|
|
{
|
|
G2_TransformSurfaces(surfInfo->childIndexes[i], rootSList, boneCache, currentModel, lod, scale, G2VertSpace, TransformedVertArray, secondTimeAround);
|
|
}
|
|
}
|
|
|
|
// main calling point for the model transform for collision detection. At this point all of the skeleton has been transformed.
|
|
#ifdef _G2_GORE
|
|
void G2_TransformModel(CGhoul2Info_v &ghoul2, const int frameNum, vec3_t scale, CMiniHeap *G2VertSpace, int useLod, bool ApplyGore, SSkinGoreData *gore)
|
|
#else
|
|
void G2_TransformModel(CGhoul2Info_v &ghoul2, const int frameNum, vec3_t scale, CMiniHeap *G2VertSpace, int useLod)
|
|
#endif
|
|
{
|
|
int i, lod;
|
|
vec3_t correctScale;
|
|
|
|
#if !defined(JK2_MODE) || defined(_G2_GORE)
|
|
qboolean firstModelOnly = qfalse;
|
|
#endif // !JK2_MODE || _G2_GORE
|
|
|
|
#ifndef JK2_MODE
|
|
if ( cg_g2MarksAllModels == NULL )
|
|
{
|
|
cg_g2MarksAllModels = ri.Cvar_Get( "cg_g2MarksAllModels", "0", 0 );
|
|
}
|
|
|
|
if (cg_g2MarksAllModels == NULL
|
|
|| !cg_g2MarksAllModels->integer )
|
|
{
|
|
firstModelOnly = qtrue;
|
|
}
|
|
#endif // !JK2_MODE
|
|
|
|
#ifdef _G2_GORE
|
|
if ( gore
|
|
&& gore->firstModel > 0 )
|
|
{
|
|
firstModelOnly = qfalse;
|
|
}
|
|
#endif
|
|
|
|
VectorCopy(scale, correctScale);
|
|
// check for scales of 0 - that's the default I believe
|
|
if (!scale[0])
|
|
{
|
|
correctScale[0] = 1.0;
|
|
}
|
|
if (!scale[1])
|
|
{
|
|
correctScale[1] = 1.0;
|
|
}
|
|
if (!scale[2])
|
|
{
|
|
correctScale[2] = 1.0;
|
|
}
|
|
|
|
// walk each possible model for this entity and try rendering it out
|
|
for (i=0; i<ghoul2.size(); i++)
|
|
{
|
|
CGhoul2Info &g=ghoul2[i];
|
|
// don't bother with models that we don't care about.
|
|
if (!g.mValid)
|
|
{
|
|
continue;
|
|
}
|
|
assert(g.mBoneCache);
|
|
assert(G2_MODEL_OK(&g));
|
|
// stop us building this model more than once per frame
|
|
g.mMeshFrameNum = frameNum;
|
|
|
|
// decide the LOD
|
|
#ifdef _G2_GORE
|
|
if (ApplyGore)
|
|
{
|
|
lod=useLod;
|
|
assert(g.currentModel);
|
|
if (lod>=g.currentModel->numLods)
|
|
{
|
|
g.mTransformedVertsArray = 0;
|
|
if ( firstModelOnly )
|
|
{
|
|
// we don't really need to do multiple models for gore.
|
|
return;
|
|
}
|
|
//do the rest
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
lod = G2_DecideTraceLod(g, useLod);
|
|
}
|
|
|
|
// give us space for the transformed vertex array to be put in
|
|
g.mTransformedVertsArray = (intptr_t *)G2VertSpace->MiniHeapAlloc(g.currentModel->mdxm->numSurfaces * sizeof (intptr_t));
|
|
if (!g.mTransformedVertsArray)
|
|
{
|
|
Com_Error(ERR_DROP, "Ran out of transform space for Ghoul2 Models. Adjust G2_MINIHEAP_SIZE in sv_init.cpp.\n");
|
|
}
|
|
|
|
memset(g.mTransformedVertsArray, 0,(g.currentModel->mdxm->numSurfaces * sizeof (intptr_t)));
|
|
|
|
G2_FindOverrideSurface(-1,g.mSlist); //reset the quick surface override lookup;
|
|
// recursively call the model surface transform
|
|
G2_TransformSurfaces(g.mSurfaceRoot, g.mSlist, g.mBoneCache, g.currentModel, lod, correctScale, G2VertSpace, g.mTransformedVertsArray, false);
|
|
|
|
#ifdef _G2_GORE
|
|
|
|
if (ApplyGore && firstModelOnly )
|
|
{
|
|
// we don't really need to do multiple models for gore.
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
// work out how much space a triangle takes
|
|
static float G2_AreaOfTri(const vec3_t A, const vec3_t B, const vec3_t C)
|
|
{
|
|
vec3_t cross, ab, cb;
|
|
VectorSubtract(A, B, ab);
|
|
VectorSubtract(C, B, cb);
|
|
|
|
CrossProduct(ab, cb, cross);
|
|
|
|
return VectorLength(cross);
|
|
}
|
|
|
|
// actually determine the S and T of the coordinate we hit in a given poly
|
|
static void G2_BuildHitPointST( const vec3_t A, const float SA, const float TA,
|
|
const vec3_t B, const float SB, const float TB,
|
|
const vec3_t C, const float SC, const float TC,
|
|
const vec3_t P, float *s, float *t,float &bary_i,float &bary_j)
|
|
{
|
|
float areaABC = G2_AreaOfTri(A, B, C);
|
|
|
|
float i = G2_AreaOfTri(P, B, C) / areaABC;
|
|
bary_i=i;
|
|
float j = G2_AreaOfTri(A, P, C) / areaABC;
|
|
bary_j=j;
|
|
float k = G2_AreaOfTri(A, B, P) / areaABC;
|
|
|
|
*s = SA * i + SB * j + SC * k;
|
|
*t = TA * i + TB * j + TC * k;
|
|
|
|
*s=fmod(*s, 1);
|
|
if (*s< 0)
|
|
{
|
|
*s+= 1.0;
|
|
}
|
|
|
|
*t=fmod(*t, 1);
|
|
if (*t< 0)
|
|
{
|
|
*t+= 1.0;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// routine that works out given a ray whether or not it hits a poly
|
|
static inline qboolean G2_SegmentTriangleTest( const vec3_t start, const vec3_t end,
|
|
const vec3_t A, const vec3_t B, const vec3_t C,
|
|
qboolean backFaces,qboolean frontFaces,vec3_t returnedPoint,vec3_t returnedNormal, float *denom)
|
|
{
|
|
static const float tiny=1E-10f;
|
|
vec3_t returnedNormalT;
|
|
vec3_t edgeAC;
|
|
|
|
VectorSubtract(C, A, edgeAC);
|
|
VectorSubtract(B, A, returnedNormalT);
|
|
|
|
CrossProduct(returnedNormalT, edgeAC, returnedNormal);
|
|
|
|
vec3_t ray;
|
|
VectorSubtract(end, start, ray);
|
|
|
|
*denom=DotProduct(ray, returnedNormal);
|
|
|
|
if (Q_fabs(*denom)<tiny|| // triangle parallel to ray
|
|
(!backFaces && *denom>0)|| // not accepting back faces
|
|
(!frontFaces && *denom<0)) //not accepting front faces
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
vec3_t toPlane;
|
|
VectorSubtract(A, start, toPlane);
|
|
|
|
float t=DotProduct(toPlane, returnedNormal)/ *denom;
|
|
|
|
if (t<0.0f||t>1.0f)
|
|
{
|
|
return qfalse; // off segment
|
|
}
|
|
|
|
VectorScale(ray, t, ray);
|
|
|
|
VectorAdd(ray, start, returnedPoint);
|
|
|
|
vec3_t edgePA;
|
|
VectorSubtract(A, returnedPoint, edgePA);
|
|
|
|
vec3_t edgePB;
|
|
VectorSubtract(B, returnedPoint, edgePB);
|
|
|
|
vec3_t edgePC;
|
|
VectorSubtract(C, returnedPoint, edgePC);
|
|
|
|
vec3_t temp;
|
|
|
|
CrossProduct(edgePA, edgePB, temp);
|
|
if (DotProduct(temp, returnedNormal)<0.0f)
|
|
{
|
|
return qfalse; // off triangle
|
|
}
|
|
|
|
CrossProduct(edgePC, edgePA, temp);
|
|
if (DotProduct(temp,returnedNormal)<0.0f)
|
|
{
|
|
return qfalse; // off triangle
|
|
}
|
|
|
|
CrossProduct(edgePB, edgePC, temp);
|
|
if (DotProduct(temp, returnedNormal)<0.0f)
|
|
{
|
|
return qfalse; // off triangle
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
#ifdef _G2_GORE
|
|
struct SVertexTemp
|
|
{
|
|
int flags;
|
|
int touch;
|
|
int newindex;
|
|
float tex[2];
|
|
SVertexTemp()
|
|
{
|
|
touch=0;
|
|
}
|
|
};
|
|
|
|
#define MAX_GORE_VERTS (3000)
|
|
static SVertexTemp GoreVerts[MAX_GORE_VERTS];
|
|
static int GoreIndexCopy[MAX_GORE_VERTS];
|
|
static int GoreTouch=1;
|
|
|
|
#define MAX_GORE_INDECIES (6000)
|
|
static int GoreIndecies[MAX_GORE_INDECIES];
|
|
|
|
#define GORE_MARGIN (0.0f)
|
|
int G2API_GetTime(int argTime);
|
|
|
|
// now we at poly level, check each model space transformed poly against the model world transfomed ray
|
|
static void G2_GorePolys( const mdxmSurface_t *surface, CTraceSurface &TS, const mdxmSurfHierarchy_t *surfInfo)
|
|
{
|
|
int j;
|
|
vec3_t basis1;
|
|
vec3_t basis2;
|
|
vec3_t taxis;
|
|
vec3_t saxis;
|
|
|
|
if (!TS.gore)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!TS.gore->useTheta)
|
|
{
|
|
VectorCopy(TS.gore->uaxis,basis2);
|
|
CrossProduct(TS.rayEnd,basis2,basis1);
|
|
if (DotProduct(basis1,basis1)<0.005f)
|
|
{ //shot dir and slash dir are too close
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (TS.gore->useTheta)
|
|
{
|
|
basis2[0]=0.0f;
|
|
basis2[1]=0.0f;
|
|
basis2[2]=1.0f;
|
|
|
|
CrossProduct(TS.rayEnd,basis2,basis1);
|
|
|
|
if (DotProduct(basis1,basis1)<.1f)
|
|
{
|
|
basis2[0]=0.0f;
|
|
basis2[1]=1.0f;
|
|
basis2[2]=0.0f;
|
|
CrossProduct(TS.rayEnd,basis2,basis1);
|
|
}
|
|
CrossProduct(TS.rayEnd,basis1,basis2);
|
|
}
|
|
|
|
// Give me a shot direction not a bunch of zeros :) -Gil
|
|
assert(DotProduct(basis1,basis1)>.0001f);
|
|
assert(DotProduct(basis2,basis2)>.0001f);
|
|
|
|
VectorNormalize(basis1);
|
|
VectorNormalize(basis2);
|
|
|
|
float c=cos(TS.theta);
|
|
float s=sin(TS.theta);
|
|
|
|
VectorScale(basis1,.5f*c/TS.tsize,taxis);
|
|
VectorMA(taxis,.5f*s/TS.tsize,basis2,taxis);
|
|
|
|
VectorScale(basis1,-.5f*s/TS.ssize,saxis);
|
|
VectorMA(saxis,.5f*c/TS.ssize,basis2,saxis);
|
|
|
|
//fixme, everything above here should be pre-calculated in G2API_AddSkinGore
|
|
float *verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex];
|
|
int numVerts = surface->numVerts;
|
|
int flags=63;
|
|
assert(numVerts<MAX_GORE_VERTS);
|
|
for ( j = 0; j < numVerts; j++ )
|
|
{
|
|
int pos=j*5;
|
|
vec3_t delta;
|
|
delta[0]=verts[pos+0]-TS.rayStart[0];
|
|
delta[1]=verts[pos+1]-TS.rayStart[1];
|
|
delta[2]=verts[pos+2]-TS.rayStart[2];
|
|
float s=DotProduct(delta,saxis)+0.5f;
|
|
float t=DotProduct(delta,taxis)+0.5f;
|
|
float depth = DotProduct(delta,TS.rayEnd);
|
|
int vflags=0;
|
|
if (s>GORE_MARGIN)
|
|
{
|
|
vflags|=1;
|
|
}
|
|
if (s<1.0f-GORE_MARGIN)
|
|
{
|
|
vflags|=2;
|
|
}
|
|
if (t>GORE_MARGIN)
|
|
{
|
|
vflags|=4;
|
|
}
|
|
if (t<1.0f-GORE_MARGIN)
|
|
{
|
|
vflags|=8;
|
|
}
|
|
if (depth > TS.gore->depthStart)
|
|
{
|
|
vflags|=16;
|
|
}
|
|
if (depth < TS.gore->depthEnd)
|
|
{
|
|
vflags|=32;
|
|
}
|
|
vflags=(~vflags);
|
|
flags&=vflags;
|
|
GoreVerts[j].flags=vflags;
|
|
GoreVerts[j].tex[0]=s;
|
|
GoreVerts[j].tex[1]=t;
|
|
}
|
|
if (flags)
|
|
{
|
|
return; // completely off the gore splotch.
|
|
}
|
|
int numTris,newNumTris,newNumVerts;
|
|
numTris = surface->numTriangles;
|
|
mdxmTriangle_t *tris;
|
|
tris = (mdxmTriangle_t *) ((byte *)surface + surface->ofsTriangles);
|
|
verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex];
|
|
newNumTris=0;
|
|
newNumVerts=0;
|
|
GoreTouch++;
|
|
for ( j = 0; j < numTris; j++ )
|
|
{
|
|
assert(tris[j].indexes[0]>=0&&tris[j].indexes[0]<numVerts);
|
|
assert(tris[j].indexes[1]>=0&&tris[j].indexes[1]<numVerts);
|
|
assert(tris[j].indexes[2]>=0&&tris[j].indexes[2]<numVerts);
|
|
flags=63&
|
|
GoreVerts[tris[j].indexes[0]].flags&
|
|
GoreVerts[tris[j].indexes[1]].flags&
|
|
GoreVerts[tris[j].indexes[2]].flags;
|
|
if (flags)
|
|
{
|
|
continue;
|
|
}
|
|
if (!TS.gore->frontFaces || !TS.gore->backFaces)
|
|
{
|
|
// we need to back/front face cull
|
|
vec3_t e1,e2,n;
|
|
|
|
VectorSubtract(&verts[tris[j].indexes[1]*5],&verts[tris[j].indexes[0]*5],e1);
|
|
VectorSubtract(&verts[tris[j].indexes[2]*5],&verts[tris[j].indexes[0]*5],e2);
|
|
CrossProduct(e1,e2,n);
|
|
if (DotProduct(TS.rayEnd,n)>0.0f)
|
|
{
|
|
if (!TS.gore->frontFaces)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!TS.gore->backFaces)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int k;
|
|
|
|
assert(newNumTris*3+3<MAX_GORE_INDECIES);
|
|
for (k=0;k<3;k++)
|
|
{
|
|
if (GoreVerts[tris[j].indexes[k]].touch==GoreTouch)
|
|
{
|
|
GoreIndecies[newNumTris*3+k]=GoreVerts[tris[j].indexes[k]].newindex;
|
|
}
|
|
else
|
|
{
|
|
GoreVerts[tris[j].indexes[k]].touch=GoreTouch;
|
|
GoreVerts[tris[j].indexes[k]].newindex=newNumVerts;
|
|
GoreIndecies[newNumTris*3+k]=newNumVerts;
|
|
GoreIndexCopy[newNumVerts]=tris[j].indexes[k];
|
|
newNumVerts++;
|
|
}
|
|
}
|
|
newNumTris++;
|
|
}
|
|
if (!newNumVerts)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int newTag;
|
|
std::map<std::pair<int,int>,int>::iterator f=GoreTagsTemp.find(std::make_pair(goreModelIndex,TS.surfaceNum));
|
|
if (f==GoreTagsTemp.end()) // need to generate a record
|
|
{
|
|
newTag=AllocGoreRecord();
|
|
CGoreSet *goreSet=0;
|
|
if (TS.ghoul2info->mGoreSetTag)
|
|
{
|
|
goreSet=FindGoreSet(TS.ghoul2info->mGoreSetTag);
|
|
}
|
|
if (!goreSet)
|
|
{
|
|
goreSet=NewGoreSet();
|
|
TS.ghoul2info->mGoreSetTag=goreSet->mMyGoreSetTag;
|
|
}
|
|
assert(goreSet);
|
|
SGoreSurface add;
|
|
add.shader=TS.goreShader;
|
|
add.mDeleteTime=0;
|
|
if (TS.gore->lifeTime)
|
|
{
|
|
add.mDeleteTime=G2API_GetTime(0) + TS.gore->lifeTime;
|
|
}
|
|
add.mFadeTime = TS.gore->fadeOutTime;
|
|
add.mFadeRGB = TS.gore->fadeRGB;
|
|
add.mGoreTag = newTag;
|
|
|
|
add.mGoreGrowStartTime=G2API_GetTime(0);
|
|
if( TS.gore->growDuration == -1)
|
|
{
|
|
add.mGoreGrowEndTime = -1; // set this to -1 to disable growing
|
|
}
|
|
else
|
|
{
|
|
add.mGoreGrowEndTime = G2API_GetTime(0) + TS.gore->growDuration;
|
|
}
|
|
|
|
assert(TS.gore->growDuration != 0);
|
|
add.mGoreGrowFactor = ( 1.0f - TS.gore->goreScaleStartFraction) / (float)(TS.gore->growDuration); //curscale = (curtime-mGoreGrowStartTime)*mGoreGrowFactor;
|
|
add.mGoreGrowOffset = TS.gore->goreScaleStartFraction;
|
|
|
|
goreSet->mGoreRecords.insert(std::make_pair(TS.surfaceNum,add));
|
|
GoreTagsTemp[std::make_pair(goreModelIndex,TS.surfaceNum)]=newTag;
|
|
}
|
|
else
|
|
{
|
|
newTag=(*f).second;
|
|
}
|
|
GoreTextureCoordinates *gore=FindGoreRecord(newTag);
|
|
if (gore)
|
|
{
|
|
assert(sizeof(float)==sizeof(int));
|
|
// data block format:
|
|
unsigned int size=
|
|
sizeof(int)+ // num verts
|
|
sizeof(int)+ // num tris
|
|
sizeof(int)*newNumVerts+ // which verts to copy from original surface
|
|
sizeof(float)*4*newNumVerts+ // storgage for deformed verts
|
|
sizeof(float)*4*newNumVerts+ // storgage for deformed normal
|
|
sizeof(float)*2*newNumVerts+ // texture coordinates
|
|
sizeof(int)*newNumTris*3; // new indecies
|
|
|
|
int *data=(int *)R_Malloc ( sizeof(int)*size, TAG_GHOUL2, qtrue );
|
|
|
|
if ( gore->tex[TS.lod] )
|
|
R_Free(gore->tex[TS.lod]);
|
|
|
|
gore->tex[TS.lod]=(float *)data;
|
|
*data++=newNumVerts;
|
|
*data++=newNumTris;
|
|
|
|
memcpy(data,GoreIndexCopy,sizeof(int)*newNumVerts);
|
|
data+=newNumVerts*9; // skip verts and normals
|
|
float *fdata=(float *)data;
|
|
|
|
for (j=0;j<newNumVerts;j++)
|
|
{
|
|
*fdata++=GoreVerts[GoreIndexCopy[j]].tex[0];
|
|
*fdata++=GoreVerts[GoreIndexCopy[j]].tex[1];
|
|
}
|
|
data=(int *)fdata;
|
|
memcpy(data,GoreIndecies,sizeof(int)*newNumTris*3);
|
|
data+=newNumTris*3;
|
|
assert((data-(int *)gore->tex[TS.lod])*sizeof(int)==size);
|
|
fdata = (float *)data;
|
|
// build the entity to gore matrix
|
|
VectorCopy(saxis,fdata+0);
|
|
VectorCopy(taxis,fdata+4);
|
|
VectorCopy(TS.rayEnd,fdata+8);
|
|
VectorNormalize(fdata+0);
|
|
VectorNormalize(fdata+4);
|
|
VectorNormalize(fdata+8);
|
|
fdata[3]=-0.5f; // subtract texture center
|
|
fdata[7]=-0.5f;
|
|
fdata[11]=0.0f;
|
|
vec3_t shotOriginInCurrentSpace; // unknown space
|
|
TransformPoint(TS.rayStart,shotOriginInCurrentSpace,(mdxaBone_t *)fdata); // dest middle arg
|
|
// this will insure the shot origin in our unknown space is now the shot origin, making it a known space
|
|
fdata[3]-=shotOriginInCurrentSpace[0];
|
|
fdata[7]-=shotOriginInCurrentSpace[1];
|
|
fdata[11]-=shotOriginInCurrentSpace[2];
|
|
Inverse_Matrix((mdxaBone_t *)fdata,(mdxaBone_t *)(fdata+12)); // dest 2nd arg
|
|
data+=24;
|
|
|
|
// assert((data - (int *)gore->tex[TS.lod]) * sizeof(int) == size);
|
|
}
|
|
}
|
|
#else
|
|
struct SVertexTemp
|
|
{
|
|
int flags;
|
|
// int touch;
|
|
// int newindex;
|
|
// float tex[2];
|
|
SVertexTemp()
|
|
{
|
|
// touch=0;
|
|
}
|
|
};
|
|
|
|
#define MAX_GORE_VERTS (3000)
|
|
static SVertexTemp GoreVerts[MAX_GORE_VERTS];
|
|
#endif
|
|
|
|
// now we're at poly level, check each model space transformed poly against the model world transfomed ray
|
|
static bool G2_TracePolys(const mdxmSurface_t *surface, const mdxmSurfHierarchy_t *surfInfo, CTraceSurface &TS)
|
|
{
|
|
int j, numTris;
|
|
|
|
// whip through and actually transform each vertex
|
|
const mdxmTriangle_t *tris = (mdxmTriangle_t *) ((byte *)surface + surface->ofsTriangles);
|
|
const float *verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex];
|
|
numTris = surface->numTriangles;
|
|
for ( j = 0; j < numTris; j++ )
|
|
{
|
|
float face;
|
|
vec3_t hitPoint, normal;
|
|
// determine actual coords for this triangle
|
|
const float *point1 = &verts[(tris[j].indexes[0] * 5)];
|
|
const float *point2 = &verts[(tris[j].indexes[1] * 5)];
|
|
const float *point3 = &verts[(tris[j].indexes[2] * 5)];
|
|
// did we hit it?
|
|
if (G2_SegmentTriangleTest(TS.rayStart, TS.rayEnd, point1, point2, point3, qtrue, qtrue, hitPoint, normal, &face))
|
|
{ // find space in the collision records for this record
|
|
int i=0;
|
|
for (; i<MAX_G2_COLLISIONS;i++)
|
|
{
|
|
if (TS.collRecMap[i].mEntityNum == -1)
|
|
{
|
|
CCollisionRecord &newCol = TS.collRecMap[i];
|
|
vec3_t distVect;
|
|
float x_pos = 0, y_pos = 0;
|
|
|
|
newCol.mPolyIndex = j;
|
|
newCol.mEntityNum = TS.entNum;
|
|
newCol.mSurfaceIndex = surface->thisSurfaceIndex;
|
|
newCol.mModelIndex = TS.modelIndex;
|
|
if (face>0)
|
|
{
|
|
newCol.mFlags = G2_FRONTFACE;
|
|
}
|
|
else
|
|
{
|
|
newCol.mFlags = G2_BACKFACE;
|
|
}
|
|
|
|
VectorSubtract(hitPoint, TS.rayStart, distVect);
|
|
newCol.mDistance = VectorLength(distVect);
|
|
assert( !Q_isnan(newCol.mDistance) );
|
|
|
|
// put the hit point back into world space
|
|
TransformAndTranslatePoint(hitPoint, newCol.mCollisionPosition, &worldMatrix);
|
|
|
|
// transform normal (but don't translate) into world angles
|
|
TransformPoint(normal, newCol.mCollisionNormal, &worldMatrix);
|
|
VectorNormalize(newCol.mCollisionNormal);
|
|
|
|
newCol.mMaterial = newCol.mLocation = 0;
|
|
|
|
// Determine our location within the texture, and barycentric coordinates
|
|
G2_BuildHitPointST(point1, point1[3], point1[4],
|
|
point2, point2[3], point2[4],
|
|
point3, point3[3], point3[4],
|
|
hitPoint, &x_pos, &y_pos,newCol.mBarycentricI,newCol.mBarycentricJ);
|
|
|
|
/*
|
|
const jk_shader_t *shader = 0;
|
|
// now, we know what surface this hit belongs to, we need to go get the shader handle so we can get the correct hit location and hit material info
|
|
if ( cust_shader )
|
|
{
|
|
shader = cust_shader;
|
|
}
|
|
else if ( skin )
|
|
{
|
|
int j;
|
|
|
|
// match the surface name to something in the skin file
|
|
shader = tr.defaultShader;
|
|
for ( j = 0 ; j < skin->numSurfaces ; j++ )
|
|
{
|
|
// the names have both been lowercased
|
|
if ( !strcmp( skin->surfaces[j]->name, surfInfo->name ) )
|
|
{
|
|
shader = skin->surfaces[j]->shader;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
shader = R_GetShaderByHandle( surfInfo->shaderIndex );
|
|
}
|
|
|
|
// do we even care to decide what the hit or location area's are? If we don't have them in the shader there is little point
|
|
if ((shader->hitLocation) || (shader->hitMaterial))
|
|
{
|
|
// ok, we have a floating point position. - determine location in data we need to look at
|
|
if (shader->hitLocation)
|
|
{
|
|
newCol.mLocation = *(hitMatReg[shader->hitLocation].loc +
|
|
((int)(y_pos * hitMatReg[shader->hitLocation].height) * hitMatReg[shader->hitLocation].width) +
|
|
((int)(x_pos * hitMatReg[shader->hitLocation].width)));
|
|
Com_Printf("G2_TracePolys hit location: %d\n", newCol.mLocation);
|
|
}
|
|
|
|
if (shader->hitMaterial)
|
|
{
|
|
newCol.mMaterial = *(hitMatReg[shader->hitMaterial].loc +
|
|
((int)(y_pos * hitMatReg[shader->hitMaterial].height) * hitMatReg[shader->hitMaterial].width) +
|
|
((int)(x_pos * hitMatReg[shader->hitMaterial].width)));
|
|
}
|
|
}
|
|
*/
|
|
// exit now if we should
|
|
if (TS.eG2TraceType == G2_RETURNONHIT)
|
|
{
|
|
TS.hitOne = true;
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
if (i == MAX_G2_COLLISIONS)
|
|
{
|
|
assert(i!=MAX_G2_COLLISIONS); // run out of collision record space - will probalbly never happen
|
|
TS.hitOne = true; //force stop recursion
|
|
return true; // return true to avoid wasting further time, but no hit will result without a record
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// now we're at poly level, check each model space transformed poly against the model world transfomed ray
|
|
static bool G2_RadiusTracePolys(
|
|
const mdxmSurface_t *surface,
|
|
CTraceSurface &TS
|
|
)
|
|
{
|
|
int j;
|
|
vec3_t basis1;
|
|
vec3_t basis2;
|
|
vec3_t taxis;
|
|
vec3_t saxis;
|
|
|
|
basis2[0]=0.0f;
|
|
basis2[1]=0.0f;
|
|
basis2[2]=1.0f;
|
|
|
|
vec3_t v3RayDir;
|
|
VectorSubtract(TS.rayEnd, TS.rayStart, v3RayDir);
|
|
|
|
CrossProduct(v3RayDir,basis2,basis1);
|
|
|
|
if (DotProduct(basis1,basis1)<.1f)
|
|
{
|
|
basis2[0]=0.0f;
|
|
basis2[1]=1.0f;
|
|
basis2[2]=0.0f;
|
|
CrossProduct(v3RayDir,basis2,basis1);
|
|
}
|
|
|
|
CrossProduct(v3RayDir,basis1,basis2);
|
|
// Give me a shot direction not a bunch of zeros :) -Gil
|
|
// assert(DotProduct(basis1,basis1)>.0001f);
|
|
// assert(DotProduct(basis2,basis2)>.0001f);
|
|
|
|
VectorNormalize(basis1);
|
|
VectorNormalize(basis2);
|
|
|
|
const float c=cos(0.0f);//theta
|
|
const float s=sin(0.0f);//theta
|
|
|
|
VectorScale(basis1, 0.5f * c / TS.m_fRadius,taxis);
|
|
VectorMA(taxis, 0.5f * s / TS.m_fRadius,basis2,taxis);
|
|
|
|
VectorScale(basis1,-0.5f * s /TS.m_fRadius,saxis);
|
|
VectorMA( saxis, 0.5f * c /TS.m_fRadius,basis2,saxis);
|
|
|
|
const float * const verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex];
|
|
const int numVerts = surface->numVerts;
|
|
|
|
int flags=63;
|
|
//rayDir/=lengthSquared(raydir);
|
|
const float f = VectorLengthSquared(v3RayDir);
|
|
v3RayDir[0]/=f;
|
|
v3RayDir[1]/=f;
|
|
v3RayDir[2]/=f;
|
|
|
|
for ( j = 0; j < numVerts; j++ )
|
|
{
|
|
const int pos=j*5;
|
|
vec3_t delta;
|
|
delta[0]=verts[pos+0]-TS.rayStart[0];
|
|
delta[1]=verts[pos+1]-TS.rayStart[1];
|
|
delta[2]=verts[pos+2]-TS.rayStart[2];
|
|
const float s=DotProduct(delta,saxis)+0.5f;
|
|
const float t=DotProduct(delta,taxis)+0.5f;
|
|
const float u=DotProduct(delta,v3RayDir);
|
|
int vflags=0;
|
|
|
|
if (s>0)
|
|
{
|
|
vflags|=1;
|
|
}
|
|
if (s<1)
|
|
{
|
|
vflags|=2;
|
|
}
|
|
if (t>0)
|
|
{
|
|
vflags|=4;
|
|
}
|
|
if (t<1)
|
|
{
|
|
vflags|=8;
|
|
}
|
|
if (u>0)
|
|
{
|
|
vflags|=16;
|
|
}
|
|
if (u<1)
|
|
{
|
|
vflags|=32;
|
|
}
|
|
|
|
vflags=(~vflags);
|
|
flags&=vflags;
|
|
GoreVerts[j].flags=vflags;
|
|
}
|
|
|
|
if (flags)
|
|
{
|
|
return false; // completely off the gore splotch (so presumably hit nothing? -Ste)
|
|
}
|
|
const int numTris = surface->numTriangles;
|
|
const mdxmTriangle_t * const tris = (mdxmTriangle_t *) ((byte *)surface + surface->ofsTriangles);
|
|
|
|
for ( j = 0; j < numTris; j++ )
|
|
{
|
|
assert(tris[j].indexes[0]>=0&&tris[j].indexes[0]<numVerts);
|
|
assert(tris[j].indexes[1]>=0&&tris[j].indexes[1]<numVerts);
|
|
assert(tris[j].indexes[2]>=0&&tris[j].indexes[2]<numVerts);
|
|
flags=63&
|
|
GoreVerts[tris[j].indexes[0]].flags&
|
|
GoreVerts[tris[j].indexes[1]].flags&
|
|
GoreVerts[tris[j].indexes[2]].flags;
|
|
if (flags)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// we hit a triangle, so init a collision record...
|
|
//
|
|
int i = 0;
|
|
for (; i<MAX_G2_COLLISIONS;i++)
|
|
{
|
|
if (TS.collRecMap[i].mEntityNum == -1)
|
|
{
|
|
CCollisionRecord &newCol = TS.collRecMap[i];
|
|
|
|
newCol.mPolyIndex = j;
|
|
newCol.mEntityNum = TS.entNum;
|
|
newCol.mSurfaceIndex = surface->thisSurfaceIndex;
|
|
newCol.mModelIndex = TS.modelIndex;
|
|
// if (face>0)
|
|
// {
|
|
newCol.mFlags = G2_FRONTFACE;
|
|
// }
|
|
// else
|
|
// {
|
|
// newCol.mFlags = G2_BACKFACE;
|
|
// }
|
|
|
|
//get normal from triangle
|
|
const float *A = &verts[(tris[j].indexes[0] * 5)];
|
|
const float *B = &verts[(tris[j].indexes[1] * 5)];
|
|
const float *C = &verts[(tris[j].indexes[2] * 5)];
|
|
vec3_t normal;
|
|
vec3_t edgeAC, edgeBA;
|
|
|
|
VectorSubtract(C, A, edgeAC);
|
|
VectorSubtract(B, A, edgeBA);
|
|
CrossProduct(edgeBA, edgeAC, normal);
|
|
|
|
// transform normal (but don't translate) into world angles
|
|
TransformPoint(normal, newCol.mCollisionNormal, &worldMatrix);
|
|
VectorNormalize(newCol.mCollisionNormal);
|
|
|
|
newCol.mMaterial = newCol.mLocation = 0;
|
|
// exit now if we should
|
|
if (TS.eG2TraceType == G2_RETURNONHIT)
|
|
{
|
|
TS.hitOne = true;
|
|
return true;
|
|
}
|
|
|
|
vec3_t distVect;
|
|
#if 0
|
|
//i don't know the hitPoint, but let's just assume it's the first vert for now...
|
|
float *hitPoint = (float *)A;
|
|
#else
|
|
//yeah, I want the collision point. Let's work out the impact point on the triangle. -rww
|
|
vec3_t hitPoint;
|
|
float side, side2;
|
|
float dist;
|
|
float third = -(A[0]*(B[1]*C[2] - C[1]*B[2]) + B[0]*(C[1]*A[2] - A[1]*C[2]) + C[0]*(A[1]*B[2] - B[1]*A[2]) );
|
|
|
|
VectorSubtract(TS.rayEnd, TS.rayStart, distVect);
|
|
side = normal[0]*TS.rayStart[0] + normal[1]*TS.rayStart[1] + normal[2]*TS.rayStart[2] + third;
|
|
side2 = normal[0]*distVect[0] + normal[1]*distVect[1] + normal[2]*distVect[2];
|
|
if (fabsf(side2)<1E-8f)
|
|
{
|
|
//i don't know the hitPoint, but let's just assume it's the first vert for now...
|
|
VectorSubtract(A, TS.rayStart, distVect);
|
|
dist = VectorLength(distVect);
|
|
VectorSubtract(TS.rayEnd, TS.rayStart, distVect);
|
|
VectorMA(TS.rayStart, dist/VectorLength(distVect), distVect, hitPoint);
|
|
}
|
|
else
|
|
{
|
|
dist = side/side2;
|
|
VectorMA(TS.rayStart, -dist, distVect, hitPoint);
|
|
}
|
|
#endif
|
|
|
|
VectorSubtract(hitPoint, TS.rayStart, distVect);
|
|
newCol.mDistance = VectorLength(distVect);
|
|
assert( !Q_isnan(newCol.mDistance) );
|
|
|
|
// put the hit point back into world space
|
|
TransformAndTranslatePoint(hitPoint, newCol.mCollisionPosition, &worldMatrix);
|
|
newCol.mBarycentricI = newCol.mBarycentricJ = 0.0f;
|
|
|
|
break;
|
|
}
|
|
}
|
|
if (i==MAX_G2_COLLISIONS)
|
|
{
|
|
//assert(i!=MAX_G2_COLLISIONS); // run out of collision record space - happens OFTEN
|
|
TS.hitOne = true; //force stop recursion
|
|
return true; // return true to avoid wasting further time, but no hit will result without a record
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// look at a surface and then do the trace on each poly
|
|
static void G2_TraceSurfaces(CTraceSurface &TS)
|
|
{
|
|
int i;
|
|
// back track and get the surfinfo struct for this surface
|
|
assert(TS.currentModel);
|
|
assert(TS.currentModel->mdxm);
|
|
const mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(TS.currentModel, TS.surfaceNum, TS.lod);
|
|
const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)TS.currentModel->mdxm + sizeof(mdxmHeader_t));
|
|
const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]);
|
|
|
|
// see if we have an override surface in the surface list
|
|
const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(TS.surfaceNum, TS.rootSList);
|
|
|
|
// don't allow recursion if we've already hit a polygon
|
|
if (TS.hitOne)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// really, we should use the default flags for this surface unless it's been overriden
|
|
int offFlags = surfInfo->flags;
|
|
|
|
// set the off flags if we have some
|
|
if (surfOverride)
|
|
{
|
|
offFlags = surfOverride->offFlags;
|
|
}
|
|
|
|
// if this surface is not off, try to hit it
|
|
if (!offFlags)
|
|
{
|
|
#ifdef _G2_GORE
|
|
if (TS.collRecMap)
|
|
{
|
|
#endif
|
|
if (!(Q_fabs(TS.m_fRadius) < 0.1)) // if not a point-trace
|
|
{
|
|
// .. then use radius check
|
|
//
|
|
if (G2_RadiusTracePolys(surface, // const mdxmSurface_t *surface,
|
|
TS
|
|
)
|
|
&& (TS.eG2TraceType == G2_RETURNONHIT)
|
|
)
|
|
{
|
|
TS.hitOne = true;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// go away and trace the polys in this surface
|
|
if (G2_TracePolys(surface, surfInfo, TS)
|
|
&& (TS.eG2TraceType == G2_RETURNONHIT)
|
|
)
|
|
{
|
|
// ok, we hit one, *and* we want to return instantly because the returnOnHit is set
|
|
// so indicate we've hit one, so other surfaces don't get hit and return
|
|
TS.hitOne = true;
|
|
return;
|
|
}
|
|
}
|
|
#ifdef _G2_GORE
|
|
}
|
|
else
|
|
{
|
|
G2_GorePolys(surface, TS, surfInfo);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// if we are turning off all descendants, then stop this recursion now
|
|
if (offFlags & G2SURFACEFLAG_NODESCENDANTS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// now recursively call for the children
|
|
for (i=0; i< surfInfo->numChildren && !TS.hitOne; i++)
|
|
{
|
|
TS.surfaceNum = surfInfo->childIndexes[i];
|
|
G2_TraceSurfaces(TS);
|
|
}
|
|
}
|
|
|
|
#ifdef _G2_GORE
|
|
void G2_TraceModels(CGhoul2Info_v &ghoul2, vec3_t rayStart, vec3_t rayEnd, CCollisionRecord *collRecMap, int entNum, EG2_Collision eG2TraceType, int useLod, float fRadius, float ssize,float tsize,float theta,int shader, SSkinGoreData *gore, qboolean skipIfLODNotMatch)
|
|
#else
|
|
void G2_TraceModels(CGhoul2Info_v &ghoul2, vec3_t rayStart, vec3_t rayEnd, CCollisionRecord *collRecMap, int entNum, EG2_Collision eG2TraceType, int useLod, float fRadius)
|
|
#endif
|
|
{
|
|
int i, lod;
|
|
skin_t *skin;
|
|
jk_shader_t *cust_shader;
|
|
#if !defined(JK2_MODE) || defined(_G2_GORE)
|
|
qboolean firstModelOnly = qfalse;
|
|
#endif // !JK2_MODE || _G2_GORE
|
|
int firstModel = 0;
|
|
|
|
#ifndef JK2_MODE
|
|
if ( cg_g2MarksAllModels == NULL )
|
|
{
|
|
cg_g2MarksAllModels = ri.Cvar_Get( "cg_g2MarksAllModels", "0", 0 );
|
|
}
|
|
|
|
if (cg_g2MarksAllModels == NULL
|
|
|| !cg_g2MarksAllModels->integer )
|
|
{
|
|
firstModelOnly = qtrue;
|
|
}
|
|
#endif // !JK2_MODE
|
|
|
|
#ifdef _G2_GORE
|
|
if ( gore
|
|
&& gore->firstModel > 0 )
|
|
{
|
|
firstModel = gore->firstModel;
|
|
firstModelOnly = qfalse;
|
|
}
|
|
#endif
|
|
|
|
// walk each possible model for this entity and try tracing against it
|
|
for (i=firstModel; i<ghoul2.size(); i++)
|
|
{
|
|
CGhoul2Info &g=ghoul2[i];
|
|
#ifdef _G2_GORE
|
|
goreModelIndex=i;
|
|
// don't bother with models that we don't care about.
|
|
if (g.mModelindex == -1)
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
// don't bother with models that we don't care about.
|
|
if (!g.mValid)
|
|
{
|
|
continue;
|
|
}
|
|
assert(G2_MODEL_OK(&ghoul2[i]));
|
|
// do we really want to collide with this object?
|
|
if (g.mFlags & GHOUL2_NOCOLLIDE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (g.mCustomShader)
|
|
{
|
|
cust_shader = R_GetShaderByHandle(g.mCustomShader );
|
|
}
|
|
else
|
|
{
|
|
cust_shader = NULL;
|
|
}
|
|
|
|
// figure out the custom skin thing
|
|
if ( g.mSkin > 0 && g.mSkin < tr.numSkins )
|
|
{
|
|
skin = R_GetSkinByHandle( g.mSkin );
|
|
}
|
|
else
|
|
{
|
|
skin = NULL;
|
|
}
|
|
|
|
lod = G2_DecideTraceLod(g,useLod);
|
|
|
|
#ifndef JK2_MODE
|
|
if ( skipIfLODNotMatch )
|
|
{//we only want to hit this SPECIFIC LOD...
|
|
if ( lod != useLod )
|
|
{//doesn't match, skip this model
|
|
continue;
|
|
}
|
|
}
|
|
#endif // !JK2_MODE
|
|
|
|
//reset the quick surface override lookup
|
|
G2_FindOverrideSurface(-1, g.mSlist);
|
|
|
|
#ifdef _G2_GORE
|
|
CTraceSurface TS(g.mSurfaceRoot, g.mSlist, g.currentModel, lod, rayStart, rayEnd, collRecMap, entNum, i, skin, cust_shader, g.mTransformedVertsArray, eG2TraceType, fRadius, ssize, tsize, theta, shader, &g, gore);
|
|
#else
|
|
CTraceSurface TS(g.mSurfaceRoot, g.mSlist, g.currentModel, lod, rayStart, rayEnd, collRecMap, entNum, i, skin, cust_shader, g.mTransformedVertsArray, eG2TraceType, fRadius);
|
|
#endif
|
|
// start the surface recursion loop
|
|
G2_TraceSurfaces(TS);
|
|
|
|
// if we've hit one surface on one model, don't bother doing the rest
|
|
if (TS.hitOne)
|
|
{
|
|
break;
|
|
}
|
|
#ifdef _G2_GORE
|
|
if ( !collRecMap && firstModelOnly )
|
|
{
|
|
// we don't really need to do multiple models for gore.
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void TransformPoint (const vec3_t in, vec3_t out, mdxaBone_t *mat) {
|
|
for (int i=0;i<3;i++)
|
|
{
|
|
out[i]= in[0]*mat->matrix[i][0] + in[1]*mat->matrix[i][1] + in[2]*mat->matrix[i][2];
|
|
}
|
|
}
|
|
|
|
void TransformAndTranslatePoint (const vec3_t in, vec3_t out, mdxaBone_t *mat) {
|
|
|
|
for (int i=0;i<3;i++)
|
|
{
|
|
out[i]= in[0]*mat->matrix[i][0] + in[1]*mat->matrix[i][1] + in[2]*mat->matrix[i][2] + mat->matrix[i][3];
|
|
}
|
|
}
|
|
|
|
|
|
// create a matrix using a set of angles
|
|
void Create_Matrix(const float *angle, mdxaBone_t *matrix)
|
|
{
|
|
vec3_t axis[3];
|
|
|
|
// convert angles to axis
|
|
AnglesToAxis( angle, axis );
|
|
matrix->matrix[0][0] = axis[0][0];
|
|
matrix->matrix[1][0] = axis[0][1];
|
|
matrix->matrix[2][0] = axis[0][2];
|
|
|
|
matrix->matrix[0][1] = axis[1][0];
|
|
matrix->matrix[1][1] = axis[1][1];
|
|
matrix->matrix[2][1] = axis[1][2];
|
|
|
|
matrix->matrix[0][2] = axis[2][0];
|
|
matrix->matrix[1][2] = axis[2][1];
|
|
matrix->matrix[2][2] = axis[2][2];
|
|
|
|
matrix->matrix[0][3] = 0;
|
|
matrix->matrix[1][3] = 0;
|
|
matrix->matrix[2][3] = 0;
|
|
|
|
|
|
}
|
|
|
|
// given a matrix, generate the inverse of that matrix
|
|
void Inverse_Matrix(mdxaBone_t *src, mdxaBone_t *dest)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
dest->matrix[i][j]=src->matrix[j][i];
|
|
}
|
|
}
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
dest->matrix[i][3]=0;
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
dest->matrix[i][3]-=dest->matrix[i][j]*src->matrix[j][3];
|
|
}
|
|
}
|
|
}
|
|
|
|
// generate the world matrix for a given set of angles and origin - called from lots of places
|
|
void G2_GenerateWorldMatrix(const vec3_t angles, const vec3_t origin)
|
|
{
|
|
Create_Matrix(angles, &worldMatrix);
|
|
worldMatrix.matrix[0][3] = origin[0];
|
|
worldMatrix.matrix[1][3] = origin[1];
|
|
worldMatrix.matrix[2][3] = origin[2];
|
|
|
|
Inverse_Matrix(&worldMatrix, &worldMatrixInv);
|
|
}
|
|
|
|
// go away and determine what the pointer for a specific surface definition within the model definition is
|
|
void *G2_FindSurface(const model_s *mod, int index, int lod)
|
|
{
|
|
assert(mod);
|
|
assert(mod->mdxm);
|
|
|
|
// point at first lod list
|
|
byte *current = (byte*)((intptr_t)mod->mdxm + (intptr_t)mod->mdxm->ofsLODs);
|
|
int i;
|
|
|
|
//walk the lods
|
|
assert(lod>=0&&lod<mod->mdxm->numLODs);
|
|
for (i=0; i<lod; i++)
|
|
{
|
|
mdxmLOD_t *lodData = (mdxmLOD_t *)current;
|
|
current += lodData->ofsEnd;
|
|
}
|
|
|
|
// avoid the lod pointer data structure
|
|
current += sizeof(mdxmLOD_t);
|
|
|
|
mdxmLODSurfOffset_t *indexes = (mdxmLODSurfOffset_t *)current;
|
|
// we are now looking at the offset array
|
|
assert(index>=0&&index<mod->mdxm->numSurfaces);
|
|
current += indexes->offsets[index];
|
|
|
|
return (void *)current;
|
|
}
|
|
|
|
|
|
#define SURFACE_SAVE_BLOCK_SIZE sizeof(surfaceInfo_t)
|
|
#define BOLT_SAVE_BLOCK_SIZE sizeof(boltInfo_t)
|
|
#define BONE_SAVE_BLOCK_SIZE sizeof(boneInfo_t)
|
|
|
|
|
|
void G2_SaveGhoul2Models(
|
|
CGhoul2Info_v& ghoul2)
|
|
{
|
|
ojk::SavedGameHelper saved_game(
|
|
::ri.saved_game);
|
|
|
|
saved_game.reset_buffer();
|
|
|
|
// is there anything to save?
|
|
if (!ghoul2.IsValid() || ghoul2.size() == 0)
|
|
{
|
|
const int zero_size = 0;
|
|
|
|
#ifdef JK2_MODE
|
|
saved_game.write<int32_t>(
|
|
zero_size);
|
|
|
|
saved_game.write_chunk_and_size<int32_t>(
|
|
INT_ID('G', 'L', '2', 'S'),
|
|
INT_ID('G', 'H', 'L', '2'));
|
|
#else
|
|
saved_game.write_chunk<int32_t>(
|
|
INT_ID('G', 'H', 'L', '2'),
|
|
zero_size); //write out a zero buffer
|
|
#endif // JK2_MODE
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// save out how many ghoul2 models we have
|
|
const int model_count = static_cast<int>(ghoul2.size());
|
|
|
|
saved_game.write<int32_t>(
|
|
model_count);
|
|
|
|
for (int i = 0; i < model_count; ++i)
|
|
{
|
|
// first save out the ghoul2 details themselves
|
|
ghoul2[i].sg_export(
|
|
saved_game);
|
|
|
|
// save out how many surfaces we have
|
|
const int surface_count = static_cast<int>(ghoul2[i].mSlist.size());
|
|
|
|
saved_game.write<int32_t>(
|
|
surface_count);
|
|
|
|
// now save the all the surface list info
|
|
for (int x = 0; x < surface_count; ++x)
|
|
{
|
|
ghoul2[i].mSlist[x].sg_export(
|
|
saved_game);
|
|
}
|
|
|
|
// save out how many bones we have
|
|
const int bone_count = static_cast<int>(ghoul2[i].mBlist.size());
|
|
|
|
saved_game.write<int32_t>(
|
|
bone_count);
|
|
|
|
// now save the all the bone list info
|
|
for (int x = 0; x < bone_count; ++x)
|
|
{
|
|
ghoul2[i].mBlist[x].sg_export(
|
|
saved_game);
|
|
}
|
|
|
|
// save out how many bolts we have
|
|
const int bolt_count = static_cast<int>(ghoul2[i].mBltlist.size());
|
|
|
|
saved_game.write<int32_t>(
|
|
bolt_count);
|
|
|
|
// lastly save the all the bolt list info
|
|
for (int x = 0; x < bolt_count; ++x)
|
|
{
|
|
ghoul2[i].mBltlist[x].sg_export(
|
|
saved_game);
|
|
}
|
|
}
|
|
|
|
#ifdef JK2_MODE
|
|
saved_game.write_chunk_and_size<int32_t>(
|
|
INT_ID('G', 'L', '2', 'S'),
|
|
INT_ID('G', 'H', 'L', '2'));
|
|
#else
|
|
saved_game.write_chunk(
|
|
INT_ID('G', 'H', 'L', '2'));
|
|
#endif // JK2_MODE
|
|
}
|
|
|
|
// FIXME Remove 'buffer' parameter
|
|
void G2_LoadGhoul2Model(
|
|
CGhoul2Info_v& ghoul2,
|
|
char* buffer)
|
|
{
|
|
static_cast<void>(buffer);
|
|
|
|
ojk::SavedGameHelper saved_game(
|
|
::ri.saved_game);
|
|
|
|
// first thing, lets see how many ghoul2 models we have, and resize our buffers accordingly
|
|
int model_count = 0;
|
|
|
|
#ifdef JK2_MODE
|
|
if (saved_game.get_buffer_size() > 0)
|
|
{
|
|
#endif // JK2_MODE
|
|
|
|
saved_game.read<int32_t>(
|
|
model_count);
|
|
|
|
#ifdef JK2_MODE
|
|
}
|
|
#endif // JK2_MODE
|
|
|
|
|
|
ghoul2.resize(
|
|
model_count);
|
|
|
|
// did we actually resize to a value?
|
|
if (model_count == 0)
|
|
{
|
|
// no, ok, well, done then.
|
|
return;
|
|
}
|
|
|
|
// now we have enough instances, lets go through each one and load up the relevant details
|
|
for (decltype(model_count) i = 0; i < model_count; ++i)
|
|
{
|
|
ghoul2[i].mSkelFrameNum = 0;
|
|
ghoul2[i].mModelindex = -1;
|
|
ghoul2[i].mFileName[0] = 0;
|
|
ghoul2[i].mValid = false;
|
|
|
|
// load the ghoul2 info from the buffer
|
|
ghoul2[i].sg_import(
|
|
saved_game);
|
|
|
|
if (ghoul2[i].mModelindex != -1 && ghoul2[i].mFileName[0])
|
|
{
|
|
ghoul2[i].mModelindex = i;
|
|
|
|
::G2_SetupModelPointers(
|
|
&ghoul2[i]);
|
|
}
|
|
|
|
// give us enough surfaces to load up the data
|
|
int surface_count = 0;
|
|
|
|
saved_game.read<int32_t>(
|
|
surface_count);
|
|
|
|
ghoul2[i].mSlist.resize(surface_count);
|
|
|
|
// now load all the surfaces
|
|
for (decltype(surface_count) x = 0; x < surface_count; ++x)
|
|
{
|
|
ghoul2[i].mSlist[x].sg_import(
|
|
saved_game);
|
|
}
|
|
|
|
// give us enough bones to load up the data
|
|
int bone_count = 0;
|
|
|
|
saved_game.read<int32_t>(
|
|
bone_count);
|
|
|
|
ghoul2[i].mBlist.resize(
|
|
bone_count);
|
|
|
|
// now load all the bones
|
|
for (decltype(bone_count) x = 0; x < bone_count; ++x)
|
|
{
|
|
ghoul2[i].mBlist[x].sg_import(
|
|
saved_game);
|
|
}
|
|
|
|
// give us enough bolts to load up the data
|
|
int bolt_count = 0;
|
|
|
|
saved_game.read<int32_t>(
|
|
bolt_count);
|
|
|
|
ghoul2[i].mBltlist.resize(
|
|
bolt_count);
|
|
|
|
// now load all the bolts
|
|
for (decltype(bolt_count) x = 0; x < bolt_count; ++x)
|
|
{
|
|
ghoul2[i].mBltlist[x].sg_import(
|
|
saved_game);
|
|
}
|
|
}
|
|
|
|
saved_game.ensure_all_data_read();
|
|
}
|