mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 13:11:47 +00:00
1585 lines
42 KiB
C++
1585 lines
42 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code 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.
|
|
|
|
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
|
|
#include "tr_local.h"
|
|
#include "Model_local.h"
|
|
|
|
|
|
static const __m128 vector_float_posInfinity = { idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY };
|
|
static const __m128 vector_float_negInfinity = { -idMath::INFINITY, -idMath::INFINITY, -idMath::INFINITY, -idMath::INFINITY };
|
|
|
|
|
|
static const char* MD5_SnapshotName = "_MD5_Snapshot_";
|
|
|
|
static const byte MD5B_VERSION = 106;
|
|
static const unsigned int MD5B_MAGIC = ( '5' << 24 ) | ( 'D' << 16 ) | ( 'M' << 8 ) | MD5B_VERSION;
|
|
|
|
idCVar r_useGPUSkinning( "r_useGPUSkinning", "1", CVAR_INTEGER, "animate normals and tangents instead of deriving" );
|
|
|
|
/***********************************************************************
|
|
|
|
idMD5Mesh
|
|
|
|
***********************************************************************/
|
|
|
|
static int c_numVerts = 0;
|
|
static int c_numWeights = 0;
|
|
static int c_numWeightJoints = 0;
|
|
|
|
struct vertexWeight_t
|
|
{
|
|
int joint;
|
|
idVec3 offset;
|
|
float jointWeight;
|
|
};
|
|
|
|
/*
|
|
====================
|
|
idMD5Mesh::idMD5Mesh
|
|
====================
|
|
*/
|
|
idMD5Mesh::idMD5Mesh()
|
|
{
|
|
shader = NULL;
|
|
numVerts = 0;
|
|
numTris = 0;
|
|
meshJoints = NULL;
|
|
numMeshJoints = 0;
|
|
maxJointVertDist = 0.0f;
|
|
deformInfo = NULL;
|
|
surfaceNum = 0;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idMD5Mesh::~idMD5Mesh
|
|
====================
|
|
*/
|
|
idMD5Mesh::~idMD5Mesh()
|
|
{
|
|
if( meshJoints != NULL )
|
|
{
|
|
Mem_Free( meshJoints );
|
|
meshJoints = NULL;
|
|
}
|
|
if( deformInfo != NULL )
|
|
{
|
|
R_FreeDeformInfo( deformInfo );
|
|
deformInfo = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idMD5Mesh::ParseMesh
|
|
====================
|
|
*/
|
|
void idMD5Mesh::ParseMesh( idLexer& parser, int numJoints, const idJointMat* joints )
|
|
{
|
|
idToken token;
|
|
idToken name;
|
|
|
|
parser.ExpectTokenString( "{" );
|
|
|
|
//
|
|
// parse name
|
|
//
|
|
if( parser.CheckTokenString( "name" ) )
|
|
{
|
|
parser.ReadToken( &name );
|
|
}
|
|
|
|
//
|
|
// parse shader
|
|
//
|
|
parser.ExpectTokenString( "shader" );
|
|
|
|
parser.ReadToken( &token );
|
|
idStr shaderName = token;
|
|
|
|
shader = declManager->FindMaterial( shaderName );
|
|
|
|
//
|
|
// parse texture coordinates
|
|
//
|
|
parser.ExpectTokenString( "numverts" );
|
|
int count = parser.ParseInt();
|
|
if( count < 0 )
|
|
{
|
|
parser.Error( "Invalid size: %s", token.c_str() );
|
|
}
|
|
|
|
this->numVerts = count;
|
|
|
|
idList<idVec2> texCoords;
|
|
idList<int> firstWeightForVertex;
|
|
idList<int> numWeightsForVertex;
|
|
|
|
texCoords.SetNum( count );
|
|
firstWeightForVertex.SetNum( count );
|
|
numWeightsForVertex.SetNum( count );
|
|
|
|
int numWeights = 0;
|
|
int maxweight = 0;
|
|
for( int i = 0; i < texCoords.Num(); i++ )
|
|
{
|
|
parser.ExpectTokenString( "vert" );
|
|
parser.ParseInt();
|
|
|
|
parser.Parse1DMatrix( 2, texCoords[ i ].ToFloatPtr() );
|
|
|
|
firstWeightForVertex[ i ] = parser.ParseInt();
|
|
numWeightsForVertex[ i ] = parser.ParseInt();
|
|
|
|
if( !numWeightsForVertex[ i ] )
|
|
{
|
|
parser.Error( "Vertex without any joint weights." );
|
|
}
|
|
|
|
numWeights += numWeightsForVertex[ i ];
|
|
if( numWeightsForVertex[ i ] + firstWeightForVertex[ i ] > maxweight )
|
|
{
|
|
maxweight = numWeightsForVertex[ i ] + firstWeightForVertex[ i ];
|
|
}
|
|
}
|
|
|
|
//
|
|
// parse tris
|
|
//
|
|
parser.ExpectTokenString( "numtris" );
|
|
count = parser.ParseInt();
|
|
if( count < 0 )
|
|
{
|
|
parser.Error( "Invalid size: %d", count );
|
|
}
|
|
|
|
idList<int> tris;
|
|
tris.SetNum( count * 3 );
|
|
numTris = count;
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
parser.ExpectTokenString( "tri" );
|
|
parser.ParseInt();
|
|
|
|
tris[ i * 3 + 0 ] = parser.ParseInt();
|
|
tris[ i * 3 + 1 ] = parser.ParseInt();
|
|
tris[ i * 3 + 2 ] = parser.ParseInt();
|
|
}
|
|
|
|
//
|
|
// parse weights
|
|
//
|
|
parser.ExpectTokenString( "numweights" );
|
|
count = parser.ParseInt();
|
|
if( count < 0 )
|
|
{
|
|
parser.Error( "Invalid size: %d", count );
|
|
}
|
|
|
|
if( maxweight > count )
|
|
{
|
|
parser.Warning( "Vertices reference out of range weights in model (%d of %d weights).", maxweight, count );
|
|
}
|
|
|
|
idList<vertexWeight_t> tempWeights;
|
|
tempWeights.SetNum( count );
|
|
assert( numJoints < 256 ); // so we can pack into bytes
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
parser.ExpectTokenString( "weight" );
|
|
parser.ParseInt();
|
|
|
|
int jointnum = parser.ParseInt();
|
|
if( ( jointnum < 0 ) || ( jointnum >= numJoints ) )
|
|
{
|
|
parser.Error( "Joint Index out of range(%d): %d", numJoints, jointnum );
|
|
}
|
|
|
|
tempWeights[ i ].joint = jointnum;
|
|
tempWeights[ i ].jointWeight = parser.ParseFloat();
|
|
|
|
parser.Parse1DMatrix( 3, tempWeights[ i ].offset.ToFloatPtr() );
|
|
}
|
|
|
|
// create pre-scaled weights and an index for the vertex/joint lookup
|
|
idVec4* scaledWeights = ( idVec4* ) Mem_Alloc16( numWeights * sizeof( scaledWeights[0] ), TAG_MD5_WEIGHT );
|
|
int* weightIndex = ( int* ) Mem_Alloc16( numWeights * 2 * sizeof( weightIndex[0] ), TAG_MD5_INDEX );
|
|
memset( weightIndex, 0, numWeights * 2 * sizeof( weightIndex[0] ) );
|
|
|
|
count = 0;
|
|
for( int i = 0; i < texCoords.Num(); i++ )
|
|
{
|
|
int num = firstWeightForVertex[i];
|
|
for( int j = 0; j < numWeightsForVertex[i]; j++, num++, count++ )
|
|
{
|
|
scaledWeights[count].ToVec3() = tempWeights[num].offset * tempWeights[num].jointWeight;
|
|
scaledWeights[count].w = tempWeights[num].jointWeight;
|
|
weightIndex[count * 2 + 0] = tempWeights[num].joint * sizeof( idJointMat );
|
|
}
|
|
weightIndex[count * 2 - 1] = 1;
|
|
}
|
|
|
|
parser.ExpectTokenString( "}" );
|
|
|
|
// update counters
|
|
c_numVerts += texCoords.Num();
|
|
c_numWeights += numWeights;
|
|
c_numWeightJoints++;
|
|
for( int i = 0; i < numWeights; i++ )
|
|
{
|
|
c_numWeightJoints += weightIndex[i * 2 + 1];
|
|
}
|
|
|
|
//
|
|
// build a base pose that can be used for skinning
|
|
//
|
|
idDrawVert* basePose = ( idDrawVert* )Mem_ClearedAlloc( texCoords.Num() * sizeof( *basePose ), TAG_MD5_BASE );
|
|
for( int j = 0, i = 0; i < texCoords.Num(); i++ )
|
|
{
|
|
idVec3 v = ( *( idJointMat* )( ( byte* )joints + weightIndex[j * 2 + 0] ) ) * scaledWeights[j];
|
|
while( weightIndex[j * 2 + 1] == 0 )
|
|
{
|
|
j++;
|
|
v += ( *( idJointMat* )( ( byte* )joints + weightIndex[j * 2 + 0] ) ) * scaledWeights[j];
|
|
}
|
|
j++;
|
|
|
|
basePose[i].Clear();
|
|
basePose[i].xyz = v;
|
|
basePose[i].SetTexCoord( texCoords[i] );
|
|
}
|
|
|
|
// build the weights and bone indexes into the verts, so they will be duplicated
|
|
// as necessary at mirror seems
|
|
|
|
static int maxWeightsPerVert;
|
|
static float maxResidualWeight;
|
|
|
|
const int MAX_VERTEX_WEIGHTS = 4;
|
|
|
|
idList< bool > jointIsUsed;
|
|
jointIsUsed.SetNum( numJoints );
|
|
for( int i = 0; i < jointIsUsed.Num(); i++ )
|
|
{
|
|
jointIsUsed[i] = false;
|
|
}
|
|
|
|
numMeshJoints = 0;
|
|
maxJointVertDist = 0.0f;
|
|
|
|
//-----------------------------------------
|
|
// new-style setup for fixed four weights and normal / tangent deformation
|
|
//
|
|
// Several important models have >25% residual weight in joints after the
|
|
// first four, which is worrisome for using a fixed four joint deformation.
|
|
//-----------------------------------------
|
|
for( int i = 0; i < texCoords.Num(); i++ )
|
|
{
|
|
idDrawVert& dv = basePose[i];
|
|
|
|
// some models do have >4 joint weights, so it is necessary to sort and renormalize
|
|
|
|
// sort the weights and take the four largest
|
|
int weights[256];
|
|
const int numWeights = numWeightsForVertex[ i ];
|
|
for( int j = 0; j < numWeights; j++ )
|
|
{
|
|
weights[j] = firstWeightForVertex[i] + j;
|
|
}
|
|
// bubble sort
|
|
for( int j = 0; j < numWeights; j++ )
|
|
{
|
|
for( int k = 0; k < numWeights - 1 - j; k++ )
|
|
{
|
|
if( tempWeights[weights[k]].jointWeight < tempWeights[weights[k + 1]].jointWeight )
|
|
{
|
|
SwapValues( weights[k], weights[k + 1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( numWeights > maxWeightsPerVert )
|
|
{
|
|
maxWeightsPerVert = numWeights;
|
|
}
|
|
|
|
const int usedWeights = Min( MAX_VERTEX_WEIGHTS, numWeights );
|
|
|
|
float totalWeight = 0;
|
|
for( int j = 0; j < numWeights; j++ )
|
|
{
|
|
totalWeight += tempWeights[weights[j]].jointWeight;
|
|
}
|
|
assert( totalWeight > 0.999f && totalWeight < 1.001f );
|
|
|
|
float usedWeight = 0;
|
|
for( int j = 0; j < usedWeights; j++ )
|
|
{
|
|
usedWeight += tempWeights[weights[j]].jointWeight;
|
|
}
|
|
|
|
const float residualWeight = totalWeight - usedWeight;
|
|
if( residualWeight > maxResidualWeight )
|
|
{
|
|
maxResidualWeight = residualWeight;
|
|
}
|
|
|
|
byte finalWeights[MAX_VERTEX_WEIGHTS] = { 0 };
|
|
byte finalJointIndecies[MAX_VERTEX_WEIGHTS] = { 0 };
|
|
for( int j = 0; j < usedWeights; j++ )
|
|
{
|
|
const vertexWeight_t& weight = tempWeights[weights[j]];
|
|
const int jointIndex = weight.joint;
|
|
const float fw = weight.jointWeight;
|
|
assert( fw >= 0.0f && fw <= 1.0f );
|
|
const float normalizedWeight = fw / usedWeight;
|
|
finalWeights[j] = idMath::Ftob( normalizedWeight * 255.0f );
|
|
finalJointIndecies[j] = jointIndex;
|
|
}
|
|
|
|
// Sort the weights and indices for hardware skinning
|
|
for( int k = 0; k < 3; ++k )
|
|
{
|
|
for( int l = k + 1; l < 4; ++l )
|
|
{
|
|
if( finalWeights[l] > finalWeights[k] )
|
|
{
|
|
SwapValues( finalWeights[k], finalWeights[l] );
|
|
SwapValues( finalJointIndecies[k], finalJointIndecies[l] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Give any left over to the biggest weight
|
|
finalWeights[0] += Max( 255 - finalWeights[0] - finalWeights[1] - finalWeights[2] - finalWeights[3], 0 );
|
|
|
|
dv.color[0] = finalJointIndecies[0];
|
|
dv.color[1] = finalJointIndecies[1];
|
|
dv.color[2] = finalJointIndecies[2];
|
|
dv.color[3] = finalJointIndecies[3];
|
|
|
|
dv.color2[0] = finalWeights[0];
|
|
dv.color2[1] = finalWeights[1];
|
|
dv.color2[2] = finalWeights[2];
|
|
dv.color2[3] = finalWeights[3];
|
|
|
|
for( int j = usedWeights; j < 4; j++ )
|
|
{
|
|
assert( dv.color2[j] == 0 );
|
|
}
|
|
|
|
for( int j = 0; j < usedWeights; j++ )
|
|
{
|
|
if( !jointIsUsed[finalJointIndecies[j]] )
|
|
{
|
|
jointIsUsed[finalJointIndecies[j]] = true;
|
|
numMeshJoints++;
|
|
}
|
|
const idJointMat& joint = joints[finalJointIndecies[j]];
|
|
float dist = ( dv.xyz - joint.GetTranslation() ).Length();
|
|
if( dist > maxJointVertDist )
|
|
{
|
|
maxJointVertDist = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
meshJoints = ( byte* ) Mem_Alloc( numMeshJoints * sizeof( meshJoints[0] ), TAG_MODEL );
|
|
numMeshJoints = 0;
|
|
for( int i = 0; i < numJoints; i++ )
|
|
{
|
|
if( jointIsUsed[i] )
|
|
{
|
|
meshJoints[numMeshJoints++] = i;
|
|
}
|
|
}
|
|
|
|
// build the deformInfo and collect a final base pose with the mirror
|
|
// seam verts properly including the bone weights
|
|
deformInfo = R_BuildDeformInfo( texCoords.Num(), basePose, tris.Num(), tris.Ptr(),
|
|
shader->UseUnsmoothedTangents() );
|
|
|
|
for( int i = 0; i < deformInfo->numOutputVerts; i++ )
|
|
{
|
|
for( int j = 0; j < 4; j++ )
|
|
{
|
|
if( deformInfo->verts[i].color[j] >= numJoints )
|
|
{
|
|
idLib::FatalError( "Bad joint index" );
|
|
}
|
|
}
|
|
}
|
|
|
|
Mem_Free( basePose );
|
|
}
|
|
|
|
/*
|
|
============
|
|
TransformVertsAndTangents
|
|
============
|
|
*/
|
|
void TransformVertsAndTangents( idDrawVert* targetVerts, const int numVerts, const idDrawVert* baseVerts, const idJointMat* joints )
|
|
{
|
|
for( int i = 0; i < numVerts; i++ )
|
|
{
|
|
const idDrawVert& base = baseVerts[i];
|
|
|
|
const idJointMat& j0 = joints[base.color[0]];
|
|
const idJointMat& j1 = joints[base.color[1]];
|
|
const idJointMat& j2 = joints[base.color[2]];
|
|
const idJointMat& j3 = joints[base.color[3]];
|
|
|
|
const float w0 = base.color2[0] * ( 1.0f / 255.0f );
|
|
const float w1 = base.color2[1] * ( 1.0f / 255.0f );
|
|
const float w2 = base.color2[2] * ( 1.0f / 255.0f );
|
|
const float w3 = base.color2[3] * ( 1.0f / 255.0f );
|
|
|
|
idJointMat accum;
|
|
idJointMat::Mul( accum, j0, w0 );
|
|
idJointMat::Mad( accum, j1, w1 );
|
|
idJointMat::Mad( accum, j2, w2 );
|
|
idJointMat::Mad( accum, j3, w3 );
|
|
|
|
targetVerts[i].xyz = accum * idVec4( base.xyz.x, base.xyz.y, base.xyz.z, 1.0f );
|
|
targetVerts[i].SetNormal( accum * base.GetNormal() );
|
|
targetVerts[i].SetTangent( accum * base.GetTangent() );
|
|
targetVerts[i].tangent[3] = base.tangent[3];
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idMD5Mesh::UpdateSurface
|
|
====================
|
|
*/
|
|
void idMD5Mesh::UpdateSurface( const struct renderEntity_s* ent, const idJointMat* entJoints,
|
|
const idJointMat* entJointsInverted, modelSurface_t* surf )
|
|
{
|
|
|
|
tr.pc.c_deformedSurfaces++;
|
|
tr.pc.c_deformedVerts += deformInfo->numOutputVerts;
|
|
tr.pc.c_deformedIndexes += deformInfo->numIndexes;
|
|
|
|
surf->shader = shader;
|
|
|
|
if( surf->geometry != NULL )
|
|
{
|
|
// if the number of verts and indexes are the same we can re-use the triangle surface
|
|
if( surf->geometry->numVerts == deformInfo->numOutputVerts && surf->geometry->numIndexes == deformInfo->numIndexes )
|
|
{
|
|
R_FreeStaticTriSurfVertexCaches( surf->geometry );
|
|
}
|
|
else
|
|
{
|
|
R_FreeStaticTriSurf( surf->geometry );
|
|
surf->geometry = R_AllocStaticTriSurf();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
surf->geometry = R_AllocStaticTriSurf();
|
|
}
|
|
|
|
srfTriangles_t* tri = surf->geometry;
|
|
|
|
// note that some of the data is referenced, and should not be freed
|
|
tri->referencedIndexes = true;
|
|
tri->numIndexes = deformInfo->numIndexes;
|
|
tri->indexes = deformInfo->indexes;
|
|
tri->silIndexes = deformInfo->silIndexes;
|
|
tri->numMirroredVerts = deformInfo->numMirroredVerts;
|
|
tri->mirroredVerts = deformInfo->mirroredVerts;
|
|
tri->numDupVerts = deformInfo->numDupVerts;
|
|
tri->dupVerts = deformInfo->dupVerts;
|
|
tri->numSilEdges = deformInfo->numSilEdges;
|
|
tri->silEdges = deformInfo->silEdges;
|
|
|
|
tri->indexCache = deformInfo->staticIndexCache;
|
|
|
|
tri->numVerts = deformInfo->numOutputVerts;
|
|
if( r_useGPUSkinning.GetBool() )
|
|
{
|
|
if( tri->verts != NULL && tri->verts != deformInfo->verts )
|
|
{
|
|
R_FreeStaticTriSurfVerts( tri );
|
|
}
|
|
tri->verts = deformInfo->verts;
|
|
tri->ambientCache = deformInfo->staticAmbientCache;
|
|
tri->shadowCache = deformInfo->staticShadowCache;
|
|
tri->referencedVerts = true;
|
|
}
|
|
else
|
|
{
|
|
if( tri->verts == NULL || tri->verts == deformInfo->verts )
|
|
{
|
|
tri->verts = NULL;
|
|
R_AllocStaticTriSurfVerts( tri, deformInfo->numOutputVerts );
|
|
assert( tri->verts != NULL ); // quiet analyze warning
|
|
memcpy( tri->verts, deformInfo->verts, deformInfo->numOutputVerts * sizeof( deformInfo->verts[0] ) ); // copy over the texture coordinates
|
|
}
|
|
TransformVertsAndTangents( tri->verts, deformInfo->numOutputVerts, deformInfo->verts, entJointsInverted );
|
|
tri->referencedVerts = false;
|
|
}
|
|
tri->tangentsCalculated = true;
|
|
|
|
CalculateBounds( entJoints, tri->bounds );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idMD5Mesh::CalculateBounds
|
|
====================
|
|
*/
|
|
void idMD5Mesh::CalculateBounds( const idJointMat* entJoints, idBounds& bounds ) const
|
|
{
|
|
|
|
__m128 minX = vector_float_posInfinity;
|
|
__m128 minY = vector_float_posInfinity;
|
|
__m128 minZ = vector_float_posInfinity;
|
|
__m128 maxX = vector_float_negInfinity;
|
|
__m128 maxY = vector_float_negInfinity;
|
|
__m128 maxZ = vector_float_negInfinity;
|
|
for( int i = 0; i < numMeshJoints; i++ )
|
|
{
|
|
const idJointMat& joint = entJoints[meshJoints[i]];
|
|
__m128 x = _mm_load_ps( joint.ToFloatPtr() + 0 * 4 );
|
|
__m128 y = _mm_load_ps( joint.ToFloatPtr() + 1 * 4 );
|
|
__m128 z = _mm_load_ps( joint.ToFloatPtr() + 2 * 4 );
|
|
minX = _mm_min_ps( minX, x );
|
|
minY = _mm_min_ps( minY, y );
|
|
minZ = _mm_min_ps( minZ, z );
|
|
maxX = _mm_max_ps( maxX, x );
|
|
maxY = _mm_max_ps( maxY, y );
|
|
maxZ = _mm_max_ps( maxZ, z );
|
|
}
|
|
__m128 expand = _mm_splat_ps( _mm_load_ss( & maxJointVertDist ), 0 );
|
|
minX = _mm_sub_ps( minX, expand );
|
|
minY = _mm_sub_ps( minY, expand );
|
|
minZ = _mm_sub_ps( minZ, expand );
|
|
maxX = _mm_add_ps( maxX, expand );
|
|
maxY = _mm_add_ps( maxY, expand );
|
|
maxZ = _mm_add_ps( maxZ, expand );
|
|
_mm_store_ss( bounds.ToFloatPtr() + 0, _mm_splat_ps( minX, 3 ) );
|
|
_mm_store_ss( bounds.ToFloatPtr() + 1, _mm_splat_ps( minY, 3 ) );
|
|
_mm_store_ss( bounds.ToFloatPtr() + 2, _mm_splat_ps( minZ, 3 ) );
|
|
_mm_store_ss( bounds.ToFloatPtr() + 3, _mm_splat_ps( maxX, 3 ) );
|
|
_mm_store_ss( bounds.ToFloatPtr() + 4, _mm_splat_ps( maxY, 3 ) );
|
|
_mm_store_ss( bounds.ToFloatPtr() + 5, _mm_splat_ps( maxZ, 3 ) );
|
|
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idMD5Mesh::NearestJoint
|
|
====================
|
|
*/
|
|
int idMD5Mesh::NearestJoint( int a, int b, int c ) const
|
|
{
|
|
// duplicated vertices might not have weights
|
|
int vertNum;
|
|
if( a >= 0 && a < numVerts )
|
|
{
|
|
vertNum = a;
|
|
}
|
|
else if( b >= 0 && b < numVerts )
|
|
{
|
|
vertNum = b;
|
|
}
|
|
else if( c >= 0 && c < numVerts )
|
|
{
|
|
vertNum = c;
|
|
}
|
|
else
|
|
{
|
|
// all vertices are duplicates which shouldn't happen
|
|
return 0;
|
|
}
|
|
|
|
const idDrawVert& v = deformInfo->verts[vertNum];
|
|
|
|
int bestWeight = 0;
|
|
int bestJoint = 0;
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
if( v.color2[i] > bestWeight )
|
|
{
|
|
bestWeight = v.color2[i];
|
|
bestJoint = v.color[i];
|
|
}
|
|
}
|
|
|
|
return bestJoint;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
idRenderModelMD5
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::ParseJoint
|
|
====================
|
|
*/
|
|
void idRenderModelMD5::ParseJoint( idLexer& parser, idMD5Joint* joint, idJointQuat* defaultPose )
|
|
{
|
|
//
|
|
// parse name
|
|
//
|
|
idToken token;
|
|
parser.ReadToken( &token );
|
|
joint->name = token;
|
|
|
|
//
|
|
// parse parent
|
|
//
|
|
int num = parser.ParseInt();
|
|
if( num < 0 )
|
|
{
|
|
joint->parent = NULL;
|
|
}
|
|
else
|
|
{
|
|
if( num >= joints.Num() - 1 )
|
|
{
|
|
parser.Error( "Invalid parent for joint '%s'", joint->name.c_str() );
|
|
}
|
|
joint->parent = &joints[ num ];
|
|
}
|
|
|
|
//
|
|
// parse default pose
|
|
//
|
|
parser.Parse1DMatrix( 3, defaultPose->t.ToFloatPtr() );
|
|
parser.Parse1DMatrix( 3, defaultPose->q.ToFloatPtr() );
|
|
defaultPose->q.w = defaultPose->q.CalcW();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::InitFromFile
|
|
====================
|
|
*/
|
|
void idRenderModelMD5::InitFromFile( const char* fileName )
|
|
{
|
|
name = fileName;
|
|
LoadModel();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idRenderModelMD5::LoadBinaryModel
|
|
========================
|
|
*/
|
|
bool idRenderModelMD5::LoadBinaryModel( idFile* file, const ID_TIME_T sourceTimeStamp )
|
|
{
|
|
|
|
if( !idRenderModelStatic::LoadBinaryModel( file, sourceTimeStamp ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
unsigned int magic = 0;
|
|
file->ReadBig( magic );
|
|
if( magic != MD5B_MAGIC )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int tempNum;
|
|
file->ReadBig( tempNum );
|
|
joints.SetNum( tempNum );
|
|
for( int i = 0; i < joints.Num(); i++ )
|
|
{
|
|
file->ReadString( joints[i].name );
|
|
int offset;
|
|
file->ReadBig( offset );
|
|
if( offset >= 0 )
|
|
{
|
|
joints[i].parent = joints.Ptr() + offset;
|
|
}
|
|
else
|
|
{
|
|
joints[i].parent = NULL;
|
|
}
|
|
}
|
|
|
|
file->ReadBig( tempNum );
|
|
defaultPose.SetNum( tempNum );
|
|
for( int i = 0; i < defaultPose.Num(); i++ )
|
|
{
|
|
file->ReadBig( defaultPose[i].q.x );
|
|
file->ReadBig( defaultPose[i].q.y );
|
|
file->ReadBig( defaultPose[i].q.z );
|
|
file->ReadBig( defaultPose[i].q.w );
|
|
file->ReadVec3( defaultPose[i].t );
|
|
}
|
|
|
|
file->ReadBig( tempNum );
|
|
invertedDefaultPose.SetNum( tempNum );
|
|
for( int i = 0; i < invertedDefaultPose.Num(); i++ )
|
|
{
|
|
file->ReadBigArray( invertedDefaultPose[ i ].ToFloatPtr(), JOINTMAT_TYPESIZE );
|
|
}
|
|
SIMD_INIT_LAST_JOINT( invertedDefaultPose.Ptr(), joints.Num() );
|
|
|
|
file->ReadBig( tempNum );
|
|
meshes.SetNum( tempNum );
|
|
for( int i = 0; i < meshes.Num(); i++ )
|
|
{
|
|
|
|
idStr materialName;
|
|
file->ReadString( materialName );
|
|
if( materialName.IsEmpty() )
|
|
{
|
|
meshes[i].shader = NULL;
|
|
}
|
|
else
|
|
{
|
|
meshes[i].shader = declManager->FindMaterial( materialName );
|
|
}
|
|
|
|
file->ReadBig( meshes[i].numVerts );
|
|
file->ReadBig( meshes[i].numTris );
|
|
|
|
file->ReadBig( meshes[i].numMeshJoints );
|
|
meshes[i].meshJoints = ( byte* ) Mem_Alloc( meshes[i].numMeshJoints * sizeof( meshes[i].meshJoints[0] ), TAG_MODEL );
|
|
file->ReadBigArray( meshes[i].meshJoints, meshes[i].numMeshJoints );
|
|
file->ReadBig( meshes[i].maxJointVertDist );
|
|
|
|
meshes[i].deformInfo = ( deformInfo_t* )R_ClearedStaticAlloc( sizeof( deformInfo_t ) );
|
|
deformInfo_t& deform = *meshes[i].deformInfo;
|
|
|
|
file->ReadBig( deform.numSourceVerts );
|
|
file->ReadBig( deform.numOutputVerts );
|
|
file->ReadBig( deform.numIndexes );
|
|
file->ReadBig( deform.numMirroredVerts );
|
|
file->ReadBig( deform.numDupVerts );
|
|
file->ReadBig( deform.numSilEdges );
|
|
|
|
srfTriangles_t tri;
|
|
memset( &tri, 0, sizeof( srfTriangles_t ) );
|
|
|
|
if( deform.numOutputVerts > 0 )
|
|
{
|
|
R_AllocStaticTriSurfVerts( &tri, deform.numOutputVerts );
|
|
deform.verts = tri.verts;
|
|
file->ReadBigArray( deform.verts, deform.numOutputVerts );
|
|
}
|
|
|
|
if( deform.numIndexes > 0 )
|
|
{
|
|
R_AllocStaticTriSurfIndexes( &tri, deform.numIndexes );
|
|
R_AllocStaticTriSurfSilIndexes( &tri, deform.numIndexes );
|
|
deform.indexes = tri.indexes;
|
|
deform.silIndexes = tri.silIndexes;
|
|
file->ReadBigArray( deform.indexes, deform.numIndexes );
|
|
file->ReadBigArray( deform.silIndexes, deform.numIndexes );
|
|
}
|
|
|
|
if( deform.numMirroredVerts > 0 )
|
|
{
|
|
R_AllocStaticTriSurfMirroredVerts( &tri, deform.numMirroredVerts );
|
|
deform.mirroredVerts = tri.mirroredVerts;
|
|
file->ReadBigArray( deform.mirroredVerts, deform.numMirroredVerts );
|
|
}
|
|
|
|
if( deform.numDupVerts > 0 )
|
|
{
|
|
R_AllocStaticTriSurfDupVerts( &tri, deform.numDupVerts );
|
|
deform.dupVerts = tri.dupVerts;
|
|
file->ReadBigArray( deform.dupVerts, deform.numDupVerts * 2 );
|
|
}
|
|
|
|
if( deform.numSilEdges > 0 )
|
|
{
|
|
R_AllocStaticTriSurfSilEdges( &tri, deform.numSilEdges );
|
|
deform.silEdges = tri.silEdges;
|
|
assert( deform.silEdges != NULL );
|
|
for( int j = 0; j < deform.numSilEdges; j++ )
|
|
{
|
|
file->ReadBig( deform.silEdges[j].p1 );
|
|
file->ReadBig( deform.silEdges[j].p2 );
|
|
file->ReadBig( deform.silEdges[j].v1 );
|
|
file->ReadBig( deform.silEdges[j].v2 );
|
|
}
|
|
}
|
|
|
|
idShadowVertSkinned* shadowVerts = ( idShadowVertSkinned* ) Mem_Alloc( ALIGN( deform.numOutputVerts * 2 * sizeof( idShadowVertSkinned ), 16 ), TAG_MODEL );
|
|
idShadowVertSkinned::CreateShadowCache( shadowVerts, deform.verts, deform.numOutputVerts );
|
|
|
|
deform.staticAmbientCache = vertexCache.AllocStaticVertex( deform.verts, ALIGN( deform.numOutputVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) );
|
|
deform.staticIndexCache = vertexCache.AllocStaticIndex( deform.indexes, ALIGN( deform.numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) );
|
|
deform.staticShadowCache = vertexCache.AllocStaticVertex( shadowVerts, ALIGN( deform.numOutputVerts * 2 * sizeof( idShadowVertSkinned ), VERTEX_CACHE_ALIGN ) );
|
|
|
|
Mem_Free( shadowVerts );
|
|
|
|
file->ReadBig( meshes[i].surfaceNum );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idRenderModelMD5::WriteBinaryModel
|
|
========================
|
|
*/
|
|
void idRenderModelMD5::WriteBinaryModel( idFile* file, ID_TIME_T* _timeStamp ) const
|
|
{
|
|
|
|
idRenderModelStatic::WriteBinaryModel( file );
|
|
|
|
if( file == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
file->WriteBig( MD5B_MAGIC );
|
|
|
|
file->WriteBig( joints.Num() );
|
|
for( int i = 0; i < joints.Num(); i++ )
|
|
{
|
|
file->WriteString( joints[i].name );
|
|
int offset = -1;
|
|
if( joints[i].parent != NULL )
|
|
{
|
|
offset = joints[i].parent - joints.Ptr();
|
|
}
|
|
file->WriteBig( offset );
|
|
}
|
|
|
|
file->WriteBig( defaultPose.Num() );
|
|
for( int i = 0; i < defaultPose.Num(); i++ )
|
|
{
|
|
file->WriteBig( defaultPose[i].q.x );
|
|
file->WriteBig( defaultPose[i].q.y );
|
|
file->WriteBig( defaultPose[i].q.z );
|
|
file->WriteBig( defaultPose[i].q.w );
|
|
file->WriteVec3( defaultPose[i].t );
|
|
}
|
|
|
|
file->WriteBig( invertedDefaultPose.Num() );
|
|
for( int i = 0; i < invertedDefaultPose.Num(); i++ )
|
|
{
|
|
file->WriteBigArray( invertedDefaultPose[ i ].ToFloatPtr(), JOINTMAT_TYPESIZE );
|
|
}
|
|
|
|
file->WriteBig( meshes.Num() );
|
|
for( int i = 0; i < meshes.Num(); i++ )
|
|
{
|
|
|
|
if( meshes[i].shader != NULL && meshes[i].shader->GetName() != NULL )
|
|
{
|
|
file->WriteString( meshes[i].shader->GetName() );
|
|
}
|
|
else
|
|
{
|
|
file->WriteString( "" );
|
|
}
|
|
|
|
file->WriteBig( meshes[i].numVerts );
|
|
file->WriteBig( meshes[i].numTris );
|
|
|
|
file->WriteBig( meshes[i].numMeshJoints );
|
|
file->WriteBigArray( meshes[i].meshJoints, meshes[i].numMeshJoints );
|
|
file->WriteBig( meshes[i].maxJointVertDist );
|
|
|
|
deformInfo_t& deform = *meshes[i].deformInfo;
|
|
|
|
file->WriteBig( deform.numSourceVerts );
|
|
file->WriteBig( deform.numOutputVerts );
|
|
file->WriteBig( deform.numIndexes );
|
|
file->WriteBig( deform.numMirroredVerts );
|
|
file->WriteBig( deform.numDupVerts );
|
|
file->WriteBig( deform.numSilEdges );
|
|
|
|
if( deform.numOutputVerts > 0 )
|
|
{
|
|
file->WriteBigArray( deform.verts, deform.numOutputVerts );
|
|
}
|
|
|
|
if( deform.numIndexes > 0 )
|
|
{
|
|
file->WriteBigArray( deform.indexes, deform.numIndexes );
|
|
file->WriteBigArray( deform.silIndexes, deform.numIndexes );
|
|
}
|
|
|
|
if( deform.numMirroredVerts > 0 )
|
|
{
|
|
file->WriteBigArray( deform.mirroredVerts, deform.numMirroredVerts );
|
|
}
|
|
|
|
if( deform.numDupVerts > 0 )
|
|
{
|
|
file->WriteBigArray( deform.dupVerts, deform.numDupVerts * 2 );
|
|
}
|
|
|
|
if( deform.numSilEdges > 0 )
|
|
{
|
|
for( int j = 0; j < deform.numSilEdges; j++ )
|
|
{
|
|
file->WriteBig( deform.silEdges[j].p1 );
|
|
file->WriteBig( deform.silEdges[j].p2 );
|
|
file->WriteBig( deform.silEdges[j].v1 );
|
|
file->WriteBig( deform.silEdges[j].v2 );
|
|
}
|
|
}
|
|
|
|
file->WriteBig( meshes[i].surfaceNum );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::LoadModel
|
|
|
|
used for initial loads, reloadModel, and reloading the data of purged models
|
|
Upon exit, the model will absolutely be valid, but possibly as a default model
|
|
====================
|
|
*/
|
|
void idRenderModelMD5::LoadModel()
|
|
{
|
|
|
|
int version;
|
|
int num;
|
|
int parentNum;
|
|
idToken token;
|
|
idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS );
|
|
|
|
if( !purged )
|
|
{
|
|
PurgeModel();
|
|
}
|
|
purged = false;
|
|
|
|
if( !parser.LoadFile( name ) )
|
|
{
|
|
MakeDefaultModel();
|
|
return;
|
|
}
|
|
|
|
parser.ExpectTokenString( MD5_VERSION_STRING );
|
|
version = parser.ParseInt();
|
|
|
|
if( version != MD5_VERSION )
|
|
{
|
|
parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION );
|
|
}
|
|
|
|
//
|
|
// skip commandline
|
|
//
|
|
parser.ExpectTokenString( "commandline" );
|
|
parser.ReadToken( &token );
|
|
|
|
// parse num joints
|
|
parser.ExpectTokenString( "numJoints" );
|
|
num = parser.ParseInt();
|
|
joints.SetGranularity( 1 );
|
|
joints.SetNum( num );
|
|
defaultPose.SetGranularity( 1 );
|
|
defaultPose.SetNum( num );
|
|
|
|
// parse num meshes
|
|
parser.ExpectTokenString( "numMeshes" );
|
|
num = parser.ParseInt();
|
|
if( num < 0 )
|
|
{
|
|
parser.Error( "Invalid size: %d", num );
|
|
}
|
|
meshes.SetGranularity( 1 );
|
|
meshes.SetNum( num );
|
|
|
|
//
|
|
// parse joints
|
|
//
|
|
parser.ExpectTokenString( "joints" );
|
|
parser.ExpectTokenString( "{" );
|
|
idJointMat* poseMat = ( idJointMat* )_alloca16( joints.Num() * sizeof( poseMat[0] ) );
|
|
for( int i = 0; i < joints.Num(); i++ )
|
|
{
|
|
idMD5Joint* joint = &joints[i];
|
|
idJointQuat* pose = &defaultPose[i];
|
|
|
|
ParseJoint( parser, joint, pose );
|
|
poseMat[ i ].SetRotation( pose->q.ToMat3() );
|
|
poseMat[ i ].SetTranslation( pose->t );
|
|
if( joint->parent )
|
|
{
|
|
parentNum = joint->parent - joints.Ptr();
|
|
pose->q = ( poseMat[ i ].ToMat3() * poseMat[ parentNum ].ToMat3().Transpose() ).ToQuat();
|
|
pose->t = ( poseMat[ i ].ToVec3() - poseMat[ parentNum ].ToVec3() ) * poseMat[ parentNum ].ToMat3().Transpose();
|
|
}
|
|
}
|
|
parser.ExpectTokenString( "}" );
|
|
|
|
//-----------------------------------------
|
|
// create the inverse of the base pose joints to support tech6 style deformation
|
|
// of base pose vertexes, normals, and tangents.
|
|
//
|
|
// vertex * joints * inverseJoints == vertex when joints is the base pose
|
|
// When the joints are in another pose, it gives the animated vertex position
|
|
//-----------------------------------------
|
|
invertedDefaultPose.SetNum( SIMD_ROUND_JOINTS( joints.Num() ) );
|
|
for( int i = 0; i < joints.Num(); i++ )
|
|
{
|
|
invertedDefaultPose[i] = poseMat[i];
|
|
invertedDefaultPose[i].Invert();
|
|
}
|
|
SIMD_INIT_LAST_JOINT( invertedDefaultPose.Ptr(), joints.Num() );
|
|
|
|
for( int i = 0; i < meshes.Num(); i++ )
|
|
{
|
|
parser.ExpectTokenString( "mesh" );
|
|
meshes[i].ParseMesh( parser, defaultPose.Num(), poseMat );
|
|
}
|
|
|
|
// calculate the bounds of the model
|
|
bounds.Clear();
|
|
for( int i = 0; i < meshes.Num(); i++ )
|
|
{
|
|
idBounds meshBounds;
|
|
meshes[i].CalculateBounds( poseMat, meshBounds );
|
|
bounds.AddBounds( meshBounds );
|
|
}
|
|
|
|
// set the timestamp for reloadmodels
|
|
fileSystem->ReadFile( name, NULL, &timeStamp );
|
|
|
|
common->UpdateLevelLoadPacifier();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderModelMD5::Print
|
|
==============
|
|
*/
|
|
void idRenderModelMD5::Print() const
|
|
{
|
|
common->Printf( "%s\n", name.c_str() );
|
|
common->Printf( "Dynamic model.\n" );
|
|
common->Printf( "Generated smooth normals.\n" );
|
|
common->Printf( " verts tris weights material\n" );
|
|
int totalVerts = 0;
|
|
int totalTris = 0;
|
|
const idMD5Mesh* mesh = meshes.Ptr();
|
|
for( int i = 0; i < meshes.Num(); i++, mesh++ )
|
|
{
|
|
totalVerts += mesh->NumVerts();
|
|
totalTris += mesh->NumTris();
|
|
common->Printf( "%2i: %5i %5i %s\n", i, mesh->NumVerts(), mesh->NumTris(), mesh->shader->GetName() );
|
|
}
|
|
common->Printf( "-----\n" );
|
|
common->Printf( "%4i verts.\n", totalVerts );
|
|
common->Printf( "%4i tris.\n", totalTris );
|
|
common->Printf( "%4i joints.\n", joints.Num() );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderModelMD5::List
|
|
==============
|
|
*/
|
|
void idRenderModelMD5::List() const
|
|
{
|
|
int i;
|
|
const idMD5Mesh* mesh;
|
|
int totalTris = 0;
|
|
int totalVerts = 0;
|
|
|
|
for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ )
|
|
{
|
|
totalTris += mesh->numTris;
|
|
totalVerts += mesh->NumVerts();
|
|
}
|
|
common->Printf( " %4ik %3i %4i %4i %s(MD5)", Memory() / 1024, meshes.Num(), totalVerts, totalTris, Name() );
|
|
|
|
if( defaulted )
|
|
{
|
|
common->Printf( " (DEFAULTED)" );
|
|
}
|
|
|
|
common->Printf( "\n" );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::Bounds
|
|
|
|
This calculates a rough bounds by using the joint radii without
|
|
transforming all the points
|
|
====================
|
|
*/
|
|
idBounds idRenderModelMD5::Bounds( const renderEntity_t* ent ) const
|
|
{
|
|
if( ent == NULL )
|
|
{
|
|
// this is the bounds for the reference pose
|
|
return bounds;
|
|
}
|
|
|
|
return ent->bounds;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::DrawJoints
|
|
====================
|
|
*/
|
|
void idRenderModelMD5::DrawJoints( const renderEntity_t* ent, const viewDef_t* view ) const
|
|
{
|
|
int i;
|
|
int num;
|
|
idVec3 pos;
|
|
const idJointMat* joint;
|
|
const idMD5Joint* md5Joint;
|
|
int parentNum;
|
|
|
|
num = ent->numJoints;
|
|
joint = ent->joints;
|
|
md5Joint = joints.Ptr();
|
|
for( i = 0; i < num; i++, joint++, md5Joint++ )
|
|
{
|
|
pos = ent->origin + joint->ToVec3() * ent->axis;
|
|
if( md5Joint->parent )
|
|
{
|
|
parentNum = md5Joint->parent - joints.Ptr();
|
|
common->RW()->DebugLine( colorWhite, ent->origin + ent->joints[ parentNum ].ToVec3() * ent->axis, pos );
|
|
}
|
|
|
|
common->RW()->DebugLine( colorRed, pos, pos + joint->ToMat3()[ 0 ] * 2.0f * ent->axis );
|
|
common->RW()->DebugLine( colorGreen, pos, pos + joint->ToMat3()[ 1 ] * 2.0f * ent->axis );
|
|
common->RW()->DebugLine( colorBlue, pos, pos + joint->ToMat3()[ 2 ] * 2.0f * ent->axis );
|
|
}
|
|
|
|
idBounds bounds;
|
|
|
|
bounds.FromTransformedBounds( ent->bounds, vec3_zero, ent->axis );
|
|
common->RW()->DebugBounds( colorMagenta, bounds, ent->origin );
|
|
|
|
if( ( r_jointNameScale.GetFloat() != 0.0f ) && ( bounds.Expand( 128.0f ).ContainsPoint( view->renderView.vieworg - ent->origin ) ) )
|
|
{
|
|
idVec3 offset( 0, 0, r_jointNameOffset.GetFloat() );
|
|
float scale;
|
|
|
|
scale = r_jointNameScale.GetFloat();
|
|
joint = ent->joints;
|
|
num = ent->numJoints;
|
|
for( i = 0; i < num; i++, joint++ )
|
|
{
|
|
pos = ent->origin + joint->ToVec3() * ent->axis;
|
|
common->RW()->DrawText( joints[ i ].name, pos + offset, scale, colorWhite, view->renderView.viewaxis, 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
TransformJoints
|
|
====================
|
|
*/
|
|
static void TransformJoints( idJointMat* __restrict outJoints, const int numJoints, const idJointMat* __restrict inJoints1, const idJointMat* __restrict inJoints2 )
|
|
{
|
|
|
|
float* outFloats = outJoints->ToFloatPtr();
|
|
const float* inFloats1 = inJoints1->ToFloatPtr();
|
|
const float* inFloats2 = inJoints2->ToFloatPtr();
|
|
|
|
assert_16_byte_aligned( outFloats );
|
|
assert_16_byte_aligned( inFloats1 );
|
|
assert_16_byte_aligned( inFloats2 );
|
|
|
|
|
|
const __m128 mask_keep_last = __m128c( _mm_set_epi32( 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 ) );
|
|
|
|
for( int i = 0; i < numJoints; i += 2, inFloats1 += 2 * 12, inFloats2 += 2 * 12, outFloats += 2 * 12 )
|
|
{
|
|
__m128 m1a0 = _mm_load_ps( inFloats1 + 0 * 12 + 0 );
|
|
__m128 m1b0 = _mm_load_ps( inFloats1 + 0 * 12 + 4 );
|
|
__m128 m1c0 = _mm_load_ps( inFloats1 + 0 * 12 + 8 );
|
|
__m128 m1a1 = _mm_load_ps( inFloats1 + 1 * 12 + 0 );
|
|
__m128 m1b1 = _mm_load_ps( inFloats1 + 1 * 12 + 4 );
|
|
__m128 m1c1 = _mm_load_ps( inFloats1 + 1 * 12 + 8 );
|
|
|
|
__m128 m2a0 = _mm_load_ps( inFloats2 + 0 * 12 + 0 );
|
|
__m128 m2b0 = _mm_load_ps( inFloats2 + 0 * 12 + 4 );
|
|
__m128 m2c0 = _mm_load_ps( inFloats2 + 0 * 12 + 8 );
|
|
__m128 m2a1 = _mm_load_ps( inFloats2 + 1 * 12 + 0 );
|
|
__m128 m2b1 = _mm_load_ps( inFloats2 + 1 * 12 + 4 );
|
|
__m128 m2c1 = _mm_load_ps( inFloats2 + 1 * 12 + 8 );
|
|
|
|
__m128 tj0 = _mm_and_ps( m1a0, mask_keep_last );
|
|
__m128 tk0 = _mm_and_ps( m1b0, mask_keep_last );
|
|
__m128 tl0 = _mm_and_ps( m1c0, mask_keep_last );
|
|
__m128 tj1 = _mm_and_ps( m1a1, mask_keep_last );
|
|
__m128 tk1 = _mm_and_ps( m1b1, mask_keep_last );
|
|
__m128 tl1 = _mm_and_ps( m1c1, mask_keep_last );
|
|
|
|
__m128 ta0 = _mm_splat_ps( m1a0, 0 );
|
|
__m128 td0 = _mm_splat_ps( m1b0, 0 );
|
|
__m128 tg0 = _mm_splat_ps( m1c0, 0 );
|
|
__m128 ta1 = _mm_splat_ps( m1a1, 0 );
|
|
__m128 td1 = _mm_splat_ps( m1b1, 0 );
|
|
__m128 tg1 = _mm_splat_ps( m1c1, 0 );
|
|
|
|
__m128 ra0 = _mm_add_ps( tj0, _mm_mul_ps( ta0, m2a0 ) );
|
|
__m128 rd0 = _mm_add_ps( tk0, _mm_mul_ps( td0, m2a0 ) );
|
|
__m128 rg0 = _mm_add_ps( tl0, _mm_mul_ps( tg0, m2a0 ) );
|
|
__m128 ra1 = _mm_add_ps( tj1, _mm_mul_ps( ta1, m2a1 ) );
|
|
__m128 rd1 = _mm_add_ps( tk1, _mm_mul_ps( td1, m2a1 ) );
|
|
__m128 rg1 = _mm_add_ps( tl1, _mm_mul_ps( tg1, m2a1 ) );
|
|
|
|
__m128 tb0 = _mm_splat_ps( m1a0, 1 );
|
|
__m128 te0 = _mm_splat_ps( m1b0, 1 );
|
|
__m128 th0 = _mm_splat_ps( m1c0, 1 );
|
|
__m128 tb1 = _mm_splat_ps( m1a1, 1 );
|
|
__m128 te1 = _mm_splat_ps( m1b1, 1 );
|
|
__m128 th1 = _mm_splat_ps( m1c1, 1 );
|
|
|
|
__m128 rb0 = _mm_add_ps( ra0, _mm_mul_ps( tb0, m2b0 ) );
|
|
__m128 re0 = _mm_add_ps( rd0, _mm_mul_ps( te0, m2b0 ) );
|
|
__m128 rh0 = _mm_add_ps( rg0, _mm_mul_ps( th0, m2b0 ) );
|
|
__m128 rb1 = _mm_add_ps( ra1, _mm_mul_ps( tb1, m2b1 ) );
|
|
__m128 re1 = _mm_add_ps( rd1, _mm_mul_ps( te1, m2b1 ) );
|
|
__m128 rh1 = _mm_add_ps( rg1, _mm_mul_ps( th1, m2b1 ) );
|
|
|
|
__m128 tc0 = _mm_splat_ps( m1a0, 2 );
|
|
__m128 tf0 = _mm_splat_ps( m1b0, 2 );
|
|
__m128 ti0 = _mm_splat_ps( m1c0, 2 );
|
|
__m128 tf1 = _mm_splat_ps( m1b1, 2 );
|
|
__m128 ti1 = _mm_splat_ps( m1c1, 2 );
|
|
__m128 tc1 = _mm_splat_ps( m1a1, 2 );
|
|
|
|
__m128 rc0 = _mm_add_ps( rb0, _mm_mul_ps( tc0, m2c0 ) );
|
|
__m128 rf0 = _mm_add_ps( re0, _mm_mul_ps( tf0, m2c0 ) );
|
|
__m128 ri0 = _mm_add_ps( rh0, _mm_mul_ps( ti0, m2c0 ) );
|
|
__m128 rc1 = _mm_add_ps( rb1, _mm_mul_ps( tc1, m2c1 ) );
|
|
__m128 rf1 = _mm_add_ps( re1, _mm_mul_ps( tf1, m2c1 ) );
|
|
__m128 ri1 = _mm_add_ps( rh1, _mm_mul_ps( ti1, m2c1 ) );
|
|
|
|
_mm_store_ps( outFloats + 0 * 12 + 0, rc0 );
|
|
_mm_store_ps( outFloats + 0 * 12 + 4, rf0 );
|
|
_mm_store_ps( outFloats + 0 * 12 + 8, ri0 );
|
|
_mm_store_ps( outFloats + 1 * 12 + 0, rc1 );
|
|
_mm_store_ps( outFloats + 1 * 12 + 4, rf1 );
|
|
_mm_store_ps( outFloats + 1 * 12 + 8, ri1 );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::InstantiateDynamicModel
|
|
====================
|
|
*/
|
|
idRenderModel* idRenderModelMD5::InstantiateDynamicModel( const struct renderEntity_s* ent, const viewDef_t* view, idRenderModel* cachedModel )
|
|
{
|
|
if( cachedModel != NULL && !r_useCachedDynamicModels.GetBool() )
|
|
{
|
|
delete cachedModel;
|
|
cachedModel = NULL;
|
|
}
|
|
|
|
if( purged )
|
|
{
|
|
common->DWarning( "model %s instantiated while purged", Name() );
|
|
LoadModel();
|
|
}
|
|
|
|
if( !ent->joints )
|
|
{
|
|
common->Printf( "idRenderModelMD5::InstantiateDynamicModel: NULL joints on renderEntity for '%s'\n", Name() );
|
|
delete cachedModel;
|
|
return NULL;
|
|
}
|
|
else if( ent->numJoints != joints.Num() )
|
|
{
|
|
common->Printf( "idRenderModelMD5::InstantiateDynamicModel: renderEntity has different number of joints than model for '%s'\n", Name() );
|
|
delete cachedModel;
|
|
return NULL;
|
|
}
|
|
|
|
tr.pc.c_generateMd5++;
|
|
|
|
idRenderModelStatic* staticModel;
|
|
if( cachedModel != NULL )
|
|
{
|
|
assert( dynamic_cast<idRenderModelStatic*>( cachedModel ) != NULL );
|
|
assert( idStr::Icmp( cachedModel->Name(), MD5_SnapshotName ) == 0 );
|
|
staticModel = static_cast<idRenderModelStatic*>( cachedModel );
|
|
}
|
|
else
|
|
{
|
|
staticModel = new( TAG_MODEL ) idRenderModelStatic;
|
|
staticModel->InitEmpty( MD5_SnapshotName );
|
|
}
|
|
|
|
staticModel->bounds.Clear();
|
|
|
|
if( r_showSkel.GetInteger() )
|
|
{
|
|
if( ( view != NULL ) && ( !r_skipSuppress.GetBool() || !ent->suppressSurfaceInViewID || ( ent->suppressSurfaceInViewID != view->renderView.viewID ) ) )
|
|
{
|
|
// only draw the skeleton
|
|
DrawJoints( ent, view );
|
|
}
|
|
|
|
if( r_showSkel.GetInteger() > 1 )
|
|
{
|
|
// turn off the model when showing the skeleton
|
|
staticModel->InitEmpty( MD5_SnapshotName );
|
|
return staticModel;
|
|
}
|
|
}
|
|
|
|
// update the GPU joints array
|
|
const int numInvertedJoints = SIMD_ROUND_JOINTS( joints.Num() );
|
|
if( staticModel->jointsInverted == NULL )
|
|
{
|
|
staticModel->numInvertedJoints = numInvertedJoints;
|
|
const int alignment = glConfig.uniformBufferOffsetAlignment;
|
|
staticModel->jointsInverted = ( idJointMat* )Mem_ClearedAlloc( ALIGN( numInvertedJoints * sizeof( idJointMat ), alignment ), TAG_JOINTMAT );
|
|
staticModel->jointsInvertedBuffer = 0;
|
|
}
|
|
else
|
|
{
|
|
assert( staticModel->numInvertedJoints == numInvertedJoints );
|
|
}
|
|
|
|
TransformJoints( staticModel->jointsInverted, joints.Num(), ent->joints, invertedDefaultPose.Ptr() );
|
|
|
|
// create all the surfaces
|
|
idMD5Mesh* mesh = meshes.Ptr();
|
|
for( int i = 0; i < meshes.Num(); i++, mesh++ )
|
|
{
|
|
// avoid deforming the surface if it will be a nodraw due to a skin remapping
|
|
const idMaterial* shader = mesh->shader;
|
|
|
|
shader = R_RemapShaderBySkin( shader, ent->customSkin, ent->customShader );
|
|
|
|
if( !shader || ( !shader->IsDrawn() && !shader->SurfaceCastsShadow() ) )
|
|
{
|
|
staticModel->DeleteSurfaceWithId( i );
|
|
mesh->surfaceNum = -1;
|
|
continue;
|
|
}
|
|
|
|
modelSurface_t* surf;
|
|
|
|
int surfaceNum = 0;
|
|
if( staticModel->FindSurfaceWithId( i, surfaceNum ) )
|
|
{
|
|
mesh->surfaceNum = surfaceNum;
|
|
surf = &staticModel->surfaces[surfaceNum];
|
|
}
|
|
else
|
|
{
|
|
mesh->surfaceNum = staticModel->NumSurfaces();
|
|
surf = &staticModel->surfaces.Alloc();
|
|
surf->geometry = NULL;
|
|
surf->shader = NULL;
|
|
surf->id = i;
|
|
}
|
|
|
|
mesh->UpdateSurface( ent, ent->joints, staticModel->jointsInverted, surf );
|
|
assert( surf->geometry != NULL ); // to get around compiler warning
|
|
|
|
// the deformation of the tangents can be deferred until each surface is added to the view
|
|
surf->geometry->staticModelWithJoints = staticModel;
|
|
|
|
staticModel->bounds.AddBounds( surf->geometry->bounds );
|
|
}
|
|
|
|
return staticModel;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::IsDynamicModel
|
|
====================
|
|
*/
|
|
dynamicModel_t idRenderModelMD5::IsDynamicModel() const
|
|
{
|
|
return DM_CACHED;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::NumJoints
|
|
====================
|
|
*/
|
|
int idRenderModelMD5::NumJoints() const
|
|
{
|
|
return joints.Num();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::GetJoints
|
|
====================
|
|
*/
|
|
const idMD5Joint* idRenderModelMD5::GetJoints() const
|
|
{
|
|
return joints.Ptr();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::GetDefaultPose
|
|
====================
|
|
*/
|
|
const idJointQuat* idRenderModelMD5::GetDefaultPose() const
|
|
{
|
|
return defaultPose.Ptr();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::GetJointHandle
|
|
====================
|
|
*/
|
|
jointHandle_t idRenderModelMD5::GetJointHandle( const char* name ) const
|
|
{
|
|
const idMD5Joint* joint = joints.Ptr();
|
|
for( int i = 0; i < joints.Num(); i++, joint++ )
|
|
{
|
|
if( idStr::Icmp( joint->name.c_str(), name ) == 0 )
|
|
{
|
|
return ( jointHandle_t )i;
|
|
}
|
|
}
|
|
|
|
return INVALID_JOINT;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idRenderModelMD5::GetJointName
|
|
=====================
|
|
*/
|
|
const char* idRenderModelMD5::GetJointName( jointHandle_t handle ) const
|
|
{
|
|
if( ( handle < 0 ) || ( handle >= joints.Num() ) )
|
|
{
|
|
return "<invalid joint>";
|
|
}
|
|
|
|
return joints[ handle ].name;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::NearestJoint
|
|
====================
|
|
*/
|
|
int idRenderModelMD5::NearestJoint( int surfaceNum, int a, int b, int c ) const
|
|
{
|
|
if( surfaceNum > meshes.Num() )
|
|
{
|
|
common->Error( "idRenderModelMD5::NearestJoint: surfaceNum > meshes.Num()" );
|
|
}
|
|
|
|
const idMD5Mesh* mesh = meshes.Ptr();
|
|
for( int i = 0; i < meshes.Num(); i++, mesh++ )
|
|
{
|
|
if( mesh->surfaceNum == surfaceNum )
|
|
{
|
|
return mesh->NearestJoint( a, b, c );
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderModelMD5::TouchData
|
|
|
|
models that are already loaded at level start time
|
|
will still touch their materials to make sure they
|
|
are kept loaded
|
|
====================
|
|
*/
|
|
void idRenderModelMD5::TouchData()
|
|
{
|
|
for( int i = 0; i < meshes.Num(); i++ )
|
|
{
|
|
declManager->FindMaterial( meshes[i].shader->GetName() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderModelMD5::PurgeModel
|
|
|
|
frees all the data, but leaves the class around for dangling references,
|
|
which can regenerate the data with LoadModel()
|
|
===================
|
|
*/
|
|
void idRenderModelMD5::PurgeModel()
|
|
{
|
|
purged = true;
|
|
joints.Clear();
|
|
defaultPose.Clear();
|
|
meshes.Clear();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderModelMD5::Memory
|
|
===================
|
|
*/
|
|
int idRenderModelMD5::Memory() const
|
|
{
|
|
int total = sizeof( *this );
|
|
total += joints.MemoryUsed() + defaultPose.MemoryUsed() + meshes.MemoryUsed();
|
|
|
|
// count up strings
|
|
for( int i = 0; i < joints.Num(); i++ )
|
|
{
|
|
total += joints[i].name.DynamicMemoryUsed();
|
|
}
|
|
|
|
// count up meshes
|
|
for( int i = 0; i < meshes.Num(); i++ )
|
|
{
|
|
const idMD5Mesh* mesh = &meshes[i];
|
|
|
|
total += mesh->numMeshJoints * sizeof( mesh->meshJoints[0] );
|
|
|
|
// sum up deform info
|
|
total += sizeof( mesh->deformInfo );
|
|
total += R_DeformInfoMemoryUsed( mesh->deformInfo );
|
|
}
|
|
return total;
|
|
}
|