q3rally/engine/code/renderergl2/tr_model_iqm.c
zturtleman 1568196e27 ioquake3 resync to revision 6920 from 6893.
Updated to latest recommended build settings, increased deployment target to 10.7
Further tweaks to Xcode project
Now works as well as possible in Xcode 11
Figured out method of referencing GLSL generated C files outside of code directory
Update README.md
Add C syntax highlighting to readme
Simplify glsl -> C stringification
Make LCC path resolution more robust
GitHub Actions setup
Remove old CI system configurations
Add status badge to README
Fix shader stringify
Run apt-get update before installing deps
Avoid platform sed differences
Run actions on pull request too
Use `r_texturemode GL_LINEAR_MIPMAP_LINEAR` by default
[sdl] Turn tentative definition into actual definition.
Add TOOLS_CFLAGS to build preamble
Fix use of TOOLS_CC being reported as CC
Use the correct compiler for tools when cross building under cygwin
Allow using pulseaudio for SDL audio capture
Restore bots crushing unseen player on q3tourney6 in non-CTF
Fix the number of weights in the IQM model calculation
Fixes a crash when compiling the project on windows in 64 bit mode.
2021-06-07 09:11:19 +00:00

1758 lines
55 KiB
C

/*
===========================================================================
Copyright (C) 2011 Thilo Schulz <thilo@tjps.eu>
Copyright (C) 2011 Matthias Bentrup <matthias.bentrup@googlemail.com>
Copyright (C) 2011-2019 Zack Middleton <zturtleman@gmail.com>
This file is part of Quake III Arena source code.
Quake III Arena 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 2 of the License,
or (at your option) any later version.
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "tr_local.h"
#define LL(x) x=LittleLong(x)
// 3x4 identity matrix
static float identityMatrix[12] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0
};
static qboolean IQM_CheckRange( iqmHeader_t *header, int offset,
int count, int size ) {
// return true if the range specified by offset, count and size
// doesn't fit into the file
return ( count <= 0 ||
offset <= 0 ||
offset > header->filesize ||
offset + count * size < 0 ||
offset + count * size > header->filesize );
}
// "multiply" 3x4 matrices, these are assumed to be the top 3 rows
// of a 4x4 matrix with the last row = (0 0 0 1)
static void Matrix34Multiply( const float *a, const float *b, float *out ) {
out[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8];
out[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9];
out[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10];
out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3];
out[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8];
out[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9];
out[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10];
out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7];
out[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8];
out[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9];
out[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10];
out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11];
}
static void JointToMatrix( const quat_t rot, const vec3_t scale, const vec3_t trans,
float *mat ) {
float xx = 2.0f * rot[0] * rot[0];
float yy = 2.0f * rot[1] * rot[1];
float zz = 2.0f * rot[2] * rot[2];
float xy = 2.0f * rot[0] * rot[1];
float xz = 2.0f * rot[0] * rot[2];
float yz = 2.0f * rot[1] * rot[2];
float wx = 2.0f * rot[3] * rot[0];
float wy = 2.0f * rot[3] * rot[1];
float wz = 2.0f * rot[3] * rot[2];
mat[ 0] = scale[0] * (1.0f - (yy + zz));
mat[ 1] = scale[0] * (xy - wz);
mat[ 2] = scale[0] * (xz + wy);
mat[ 3] = trans[0];
mat[ 4] = scale[1] * (xy + wz);
mat[ 5] = scale[1] * (1.0f - (xx + zz));
mat[ 6] = scale[1] * (yz - wx);
mat[ 7] = trans[1];
mat[ 8] = scale[2] * (xz - wy);
mat[ 9] = scale[2] * (yz + wx);
mat[10] = scale[2] * (1.0f - (xx + yy));
mat[11] = trans[2];
}
static void Matrix34Invert( const float *inMat, float *outMat ) {
vec3_t trans;
float invSqrLen, *v;
outMat[ 0] = inMat[ 0]; outMat[ 1] = inMat[ 4]; outMat[ 2] = inMat[ 8];
outMat[ 4] = inMat[ 1]; outMat[ 5] = inMat[ 5]; outMat[ 6] = inMat[ 9];
outMat[ 8] = inMat[ 2]; outMat[ 9] = inMat[ 6]; outMat[10] = inMat[10];
v = outMat + 0; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
v = outMat + 4; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
v = outMat + 8; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
trans[0] = inMat[ 3];
trans[1] = inMat[ 7];
trans[2] = inMat[11];
outMat[ 3] = -DotProduct(outMat + 0, trans);
outMat[ 7] = -DotProduct(outMat + 4, trans);
outMat[11] = -DotProduct(outMat + 8, trans);
}
static void QuatSlerp(const quat_t from, const quat_t _to, float fraction, quat_t out) {
float angle, cosAngle, sinAngle, backlerp, lerp;
quat_t to;
// cos() of angle
cosAngle = from[0] * _to[0] + from[1] * _to[1] + from[2] * _to[2] + from[3] * _to[3];
// negative handling is needed for taking shortest path (required for model joints)
if ( cosAngle < 0.0f ) {
cosAngle = -cosAngle;
to[0] = - _to[0];
to[1] = - _to[1];
to[2] = - _to[2];
to[3] = - _to[3];
} else {
QuatCopy( _to, to );
}
if ( cosAngle < 0.999999f ) {
// spherical lerp (slerp)
angle = acosf( cosAngle );
sinAngle = sinf( angle );
backlerp = sinf( ( 1.0f - fraction ) * angle ) / sinAngle;
lerp = sinf( fraction * angle ) / sinAngle;
} else {
// linear lerp
backlerp = 1.0f - fraction;
lerp = fraction;
}
out[0] = from[0] * backlerp + to[0] * lerp;
out[1] = from[1] * backlerp + to[1] * lerp;
out[2] = from[2] * backlerp + to[2] * lerp;
out[3] = from[3] * backlerp + to[3] * lerp;
}
static vec_t QuatNormalize2( const quat_t v, quat_t out) {
float length, ilength;
length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2] + v[3]*v[3];
if (length) {
/* writing it this way allows gcc to recognize that rsqrt can be used */
ilength = 1/(float)sqrt (length);
/* sqrt(length) = length * (1 / sqrt(length)) */
length *= ilength;
out[0] = v[0]*ilength;
out[1] = v[1]*ilength;
out[2] = v[2]*ilength;
out[3] = v[3]*ilength;
} else {
out[0] = out[1] = out[2] = 0;
out[3] = -1;
}
return length;
}
/*
=================
R_LoadIQM
Load an IQM model and compute the joint matrices for every frame.
=================
*/
qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_name ) {
iqmHeader_t *header;
iqmVertexArray_t *vertexarray;
iqmTriangle_t *triangle;
iqmMesh_t *mesh;
iqmJoint_t *joint;
iqmPose_t *pose;
iqmBounds_t *bounds;
unsigned short *framedata;
char *str;
int i, j, k;
iqmTransform_t *transform;
float *mat, *matInv;
size_t size, joint_names;
byte *dataPtr;
iqmData_t *iqmData;
srfIQModel_t *surface;
char meshName[MAX_QPATH];
int vertexArrayFormat[IQM_COLOR+1];
int allocateInfluences;
byte *blendIndexes;
union {
byte *b;
float *f;
} blendWeights;
if( filesize < sizeof(iqmHeader_t) ) {
return qfalse;
}
header = (iqmHeader_t *)buffer;
if( Q_strncmp( header->magic, IQM_MAGIC, sizeof(header->magic) ) ) {
return qfalse;
}
LL( header->version );
if( header->version != IQM_VERSION ) {
ri.Printf(PRINT_WARNING, "R_LoadIQM: %s is a unsupported IQM version (%d), only version %d is supported.\n",
mod_name, header->version, IQM_VERSION);
return qfalse;
}
LL( header->filesize );
if( header->filesize > filesize || header->filesize > 16<<20 ) {
return qfalse;
}
LL( header->flags );
LL( header->num_text );
LL( header->ofs_text );
LL( header->num_meshes );
LL( header->ofs_meshes );
LL( header->num_vertexarrays );
LL( header->num_vertexes );
LL( header->ofs_vertexarrays );
LL( header->num_triangles );
LL( header->ofs_triangles );
LL( header->ofs_adjacency );
LL( header->num_joints );
LL( header->ofs_joints );
LL( header->num_poses );
LL( header->ofs_poses );
LL( header->num_anims );
LL( header->ofs_anims );
LL( header->num_frames );
LL( header->num_framechannels );
LL( header->ofs_frames );
LL( header->ofs_bounds );
LL( header->num_comment );
LL( header->ofs_comment );
LL( header->num_extensions );
LL( header->ofs_extensions );
// check ioq3 joint limit
if ( header->num_joints > IQM_MAX_JOINTS ) {
ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %d joints (%d).\n",
mod_name, IQM_MAX_JOINTS, header->num_joints);
return qfalse;
}
for ( i = 0; i < ARRAY_LEN( vertexArrayFormat ); i++ ) {
vertexArrayFormat[i] = -1;
}
blendIndexes = NULL;
blendWeights.b = NULL;
allocateInfluences = 0;
if ( header->num_meshes )
{
// check and swap vertex arrays
if( IQM_CheckRange( header, header->ofs_vertexarrays,
header->num_vertexarrays,
sizeof(iqmVertexArray_t) ) ) {
return qfalse;
}
vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
int n, *intPtr;
if( vertexarray->size <= 0 || vertexarray->size > 4 ) {
return qfalse;
}
// total number of values
n = header->num_vertexes * vertexarray->size;
switch( vertexarray->format ) {
case IQM_BYTE:
case IQM_UBYTE:
// 1 byte, no swapping necessary
if( IQM_CheckRange( header, vertexarray->offset,
n, sizeof(byte) ) ) {
return qfalse;
}
break;
case IQM_INT:
case IQM_UINT:
case IQM_FLOAT:
// 4-byte swap
if( IQM_CheckRange( header, vertexarray->offset,
n, sizeof(float) ) ) {
return qfalse;
}
intPtr = (int *)((byte *)header + vertexarray->offset);
for( j = 0; j < n; j++, intPtr++ ) {
LL( *intPtr );
}
break;
default:
// not supported
return qfalse;
break;
}
if( vertexarray->type < ARRAY_LEN( vertexArrayFormat ) ) {
vertexArrayFormat[vertexarray->type] = vertexarray->format;
}
switch( vertexarray->type ) {
case IQM_POSITION:
case IQM_NORMAL:
if( vertexarray->format != IQM_FLOAT ||
vertexarray->size != 3 ) {
return qfalse;
}
break;
case IQM_TANGENT:
if( vertexarray->format != IQM_FLOAT ||
vertexarray->size != 4 ) {
return qfalse;
}
break;
case IQM_TEXCOORD:
if( vertexarray->format != IQM_FLOAT ||
vertexarray->size != 2 ) {
return qfalse;
}
break;
case IQM_BLENDINDEXES:
if( (vertexarray->format != IQM_INT &&
vertexarray->format != IQM_UBYTE) ||
vertexarray->size != 4 ) {
return qfalse;
}
blendIndexes = (byte*)header + vertexarray->offset;
break;
case IQM_BLENDWEIGHTS:
if( (vertexarray->format != IQM_FLOAT &&
vertexarray->format != IQM_UBYTE) ||
vertexarray->size != 4 ) {
return qfalse;
}
if( vertexarray->format == IQM_FLOAT ) {
blendWeights.f = (float*)( (byte*)header + vertexarray->offset );
} else {
blendWeights.b = (byte*)header + vertexarray->offset;
}
break;
case IQM_COLOR:
if( vertexarray->format != IQM_UBYTE ||
vertexarray->size != 4 ) {
return qfalse;
}
break;
}
}
// check for required vertex arrays
if( vertexArrayFormat[IQM_POSITION] == -1 || vertexArrayFormat[IQM_NORMAL] == -1 || vertexArrayFormat[IQM_TEXCOORD] == -1 ) {
ri.Printf( PRINT_WARNING, "R_LoadIQM: %s is missing IQM_POSITION, IQM_NORMAL, and/or IQM_TEXCOORD array.\n", mod_name );
return qfalse;
}
if( header->num_joints ) {
if( vertexArrayFormat[IQM_BLENDINDEXES] == -1 || vertexArrayFormat[IQM_BLENDWEIGHTS] == -1 ) {
ri.Printf( PRINT_WARNING, "R_LoadIQM: %s is missing IQM_BLENDINDEXES and/or IQM_BLENDWEIGHTS array.\n", mod_name );
return qfalse;
}
} else {
// ignore blend arrays if present
vertexArrayFormat[IQM_BLENDINDEXES] = -1;
vertexArrayFormat[IQM_BLENDWEIGHTS] = -1;
}
// opengl2 renderer requires tangents
if( vertexArrayFormat[IQM_TANGENT] == -1 ) {
ri.Printf( PRINT_WARNING, "R_LoadIQM: %s is missing IQM_TANGENT array.\n", mod_name );
return qfalse;
}
// check and swap triangles
if( IQM_CheckRange( header, header->ofs_triangles,
header->num_triangles, sizeof(iqmTriangle_t) ) ) {
return qfalse;
}
triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles);
for( i = 0; i < header->num_triangles; i++, triangle++ ) {
LL( triangle->vertex[0] );
LL( triangle->vertex[1] );
LL( triangle->vertex[2] );
if( triangle->vertex[0] > header->num_vertexes ||
triangle->vertex[1] > header->num_vertexes ||
triangle->vertex[2] > header->num_vertexes ) {
return qfalse;
}
}
// check and swap meshes
if( IQM_CheckRange( header, header->ofs_meshes,
header->num_meshes, sizeof(iqmMesh_t) ) ) {
return qfalse;
}
mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes);
for( i = 0; i < header->num_meshes; i++, mesh++) {
LL( mesh->name );
LL( mesh->material );
LL( mesh->first_vertex );
LL( mesh->num_vertexes );
LL( mesh->first_triangle );
LL( mesh->num_triangles );
if ( mesh->name < header->num_text ) {
Q_strncpyz( meshName, (char*)header + header->ofs_text + mesh->name, sizeof (meshName) );
} else {
meshName[0] = '\0';
}
// check ioq3 limits
if ( mesh->num_vertexes >= SHADER_MAX_VERTEXES ) {
ri.Printf( PRINT_WARNING, "R_LoadIQM: %s has more than %i verts on %s (%i).\n",
mod_name, SHADER_MAX_VERTEXES - 1, meshName[0] ? meshName : "a surface",
mesh->num_vertexes );
return qfalse;
}
if ( mesh->num_triangles*3 >= SHADER_MAX_INDEXES ) {
ri.Printf( PRINT_WARNING, "R_LoadIQM: %s has more than %i triangles on %s (%i).\n",
mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, meshName[0] ? meshName : "a surface",
mesh->num_triangles );
return qfalse;
}
if( mesh->first_vertex >= header->num_vertexes ||
mesh->first_vertex + mesh->num_vertexes > header->num_vertexes ||
mesh->first_triangle >= header->num_triangles ||
mesh->first_triangle + mesh->num_triangles > header->num_triangles ||
mesh->name >= header->num_text ||
mesh->material >= header->num_text ) {
return qfalse;
}
// find number of unique blend influences per mesh
if( header->num_joints ) {
for( j = 0; j < mesh->num_vertexes; j++ ) {
int vtx = mesh->first_vertex + j;
for( k = 0; k < j; k++ ) {
int influence = mesh->first_vertex + k;
if( *(int*)&blendIndexes[4*influence] != *(int*)&blendIndexes[4*vtx] ) {
continue;
}
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
if ( blendWeights.f[4*influence+0] == blendWeights.f[4*vtx+0] &&
blendWeights.f[4*influence+1] == blendWeights.f[4*vtx+1] &&
blendWeights.f[4*influence+2] == blendWeights.f[4*vtx+2] &&
blendWeights.f[4*influence+3] == blendWeights.f[4*vtx+3] ) {
break;
}
} else {
if ( *(int*)&blendWeights.b[4*influence] == *(int*)&blendWeights.b[4*vtx] ) {
break;
}
}
}
if ( k == j ) {
allocateInfluences++;
}
}
}
}
}
if( header->num_poses != header->num_joints && header->num_poses != 0 ) {
ri.Printf( PRINT_WARNING, "R_LoadIQM: %s has %d poses and %d joints, must have the same number or 0 poses\n",
mod_name, header->num_poses, header->num_joints );
return qfalse;
}
joint_names = 0;
if ( header->num_joints )
{
// check and swap joints
if( IQM_CheckRange( header, header->ofs_joints,
header->num_joints, sizeof(iqmJoint_t) ) ) {
return qfalse;
}
joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
for( i = 0; i < header->num_joints; i++, joint++ ) {
LL( joint->name );
LL( joint->parent );
LL( joint->translate[0] );
LL( joint->translate[1] );
LL( joint->translate[2] );
LL( joint->rotate[0] );
LL( joint->rotate[1] );
LL( joint->rotate[2] );
LL( joint->rotate[3] );
LL( joint->scale[0] );
LL( joint->scale[1] );
LL( joint->scale[2] );
if( joint->parent < -1 ||
joint->parent >= (int)header->num_joints ||
joint->name >= (int)header->num_text ) {
return qfalse;
}
joint_names += strlen( (char *)header + header->ofs_text +
joint->name ) + 1;
}
}
if ( header->num_poses )
{
// check and swap poses
if( IQM_CheckRange( header, header->ofs_poses,
header->num_poses, sizeof(iqmPose_t) ) ) {
return qfalse;
}
pose = (iqmPose_t *)((byte *)header + header->ofs_poses);
for( i = 0; i < header->num_poses; i++, pose++ ) {
LL( pose->parent );
LL( pose->mask );
LL( pose->channeloffset[0] );
LL( pose->channeloffset[1] );
LL( pose->channeloffset[2] );
LL( pose->channeloffset[3] );
LL( pose->channeloffset[4] );
LL( pose->channeloffset[5] );
LL( pose->channeloffset[6] );
LL( pose->channeloffset[7] );
LL( pose->channeloffset[8] );
LL( pose->channeloffset[9] );
LL( pose->channelscale[0] );
LL( pose->channelscale[1] );
LL( pose->channelscale[2] );
LL( pose->channelscale[3] );
LL( pose->channelscale[4] );
LL( pose->channelscale[5] );
LL( pose->channelscale[6] );
LL( pose->channelscale[7] );
LL( pose->channelscale[8] );
LL( pose->channelscale[9] );
}
}
if (header->ofs_bounds)
{
// check and swap model bounds
if(IQM_CheckRange(header, header->ofs_bounds,
header->num_frames, sizeof(*bounds)))
{
return qfalse;
}
bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds);
for(i = 0; i < header->num_frames; i++)
{
LL(bounds->bbmin[0]);
LL(bounds->bbmin[1]);
LL(bounds->bbmin[2]);
LL(bounds->bbmax[0]);
LL(bounds->bbmax[1]);
LL(bounds->bbmax[2]);
bounds++;
}
}
// allocate the model and copy the data
size = sizeof(iqmData_t);
if( header->num_meshes ) {
size += header->num_meshes * sizeof( srfIQModel_t ); // surfaces
size += header->num_triangles * 3 * sizeof(int); // triangles
size += header->num_vertexes * 3 * sizeof(float); // positions
size += header->num_vertexes * 2 * sizeof(float); // texcoords
size += header->num_vertexes * 3 * sizeof(float); // normals
if ( vertexArrayFormat[IQM_TANGENT] != -1 ) {
size += header->num_vertexes * 4 * sizeof(float); // tangents
}
if ( vertexArrayFormat[IQM_COLOR] != -1 ) {
size += header->num_vertexes * 4 * sizeof(byte); // colors
}
if ( allocateInfluences ) {
size += header->num_vertexes * sizeof(int); // influences
size += allocateInfluences * 4 * sizeof(byte); // influenceBlendIndexes
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_UBYTE ) {
size += allocateInfluences * 4 * sizeof(byte); // influenceBlendWeights
} else if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
size += allocateInfluences * 4 * sizeof(float); // influenceBlendWeights
}
}
}
if( header->num_joints ) {
size += joint_names; // joint names
size += header->num_joints * sizeof(int); // joint parents
size += header->num_joints * 12 * sizeof(float); // bind joint matricies
size += header->num_joints * 12 * sizeof(float); // inverse bind joint matricies
}
if( header->num_poses ) {
size += header->num_poses * header->num_frames * sizeof(iqmTransform_t); // pose transforms
}
if( header->ofs_bounds ) {
size += header->num_frames * 6 * sizeof(float); // model bounds
} else if( header->num_meshes && header->num_frames == 0 ) {
size += 6 * sizeof(float); // model bounds
}
mod->type = MOD_IQM;
iqmData = (iqmData_t *)ri.Hunk_Alloc( size, h_low );
mod->modelData = iqmData;
// fill header
iqmData->num_vertexes = ( header->num_meshes > 0 ) ? header->num_vertexes : 0;
iqmData->num_triangles = ( header->num_meshes > 0 ) ? header->num_triangles : 0;
iqmData->num_frames = header->num_frames;
iqmData->num_surfaces = header->num_meshes;
iqmData->num_joints = header->num_joints;
iqmData->num_poses = header->num_poses;
iqmData->blendWeightsType = vertexArrayFormat[IQM_BLENDWEIGHTS];
dataPtr = (byte*)iqmData + sizeof(iqmData_t);
if( header->num_meshes ) {
iqmData->surfaces = (struct srfIQModel_s*)dataPtr;
dataPtr += header->num_meshes * sizeof( srfIQModel_t );
iqmData->triangles = (int*)dataPtr;
dataPtr += header->num_triangles * 3 * sizeof(int); // triangles
iqmData->positions = (float*)dataPtr;
dataPtr += header->num_vertexes * 3 * sizeof(float); // positions
iqmData->texcoords = (float*)dataPtr;
dataPtr += header->num_vertexes * 2 * sizeof(float); // texcoords
iqmData->normals = (float*)dataPtr;
dataPtr += header->num_vertexes * 3 * sizeof(float); // normals
if ( vertexArrayFormat[IQM_TANGENT] != -1 ) {
iqmData->tangents = (float*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(float); // tangents
}
if ( vertexArrayFormat[IQM_COLOR] != -1 ) {
iqmData->colors = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // colors
}
if ( allocateInfluences ) {
iqmData->influences = (int*)dataPtr;
dataPtr += header->num_vertexes * sizeof(int); // influences
iqmData->influenceBlendIndexes = (byte*)dataPtr;
dataPtr += allocateInfluences * 4 * sizeof(byte); // influenceBlendIndexes
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_UBYTE ) {
iqmData->influenceBlendWeights.b = (byte*)dataPtr;
dataPtr += allocateInfluences * 4 * sizeof(byte); // influenceBlendWeights
} else if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
iqmData->influenceBlendWeights.f = (float*)dataPtr;
dataPtr += allocateInfluences * 4 * sizeof(float); // influenceBlendWeights
}
}
}
if( header->num_joints ) {
iqmData->jointNames = (char*)dataPtr;
dataPtr += joint_names; // joint names
iqmData->jointParents = (int*)dataPtr;
dataPtr += header->num_joints * sizeof(int); // joint parents
iqmData->bindJoints = (float*)dataPtr;
dataPtr += header->num_joints * 12 * sizeof(float); // bind joint matricies
iqmData->invBindJoints = (float*)dataPtr;
dataPtr += header->num_joints * 12 * sizeof(float); // inverse bind joint matricies
}
if( header->num_poses ) {
iqmData->poses = (iqmTransform_t*)dataPtr;
dataPtr += header->num_poses * header->num_frames * sizeof(iqmTransform_t); // pose transforms
}
if( header->ofs_bounds ) {
iqmData->bounds = (float*)dataPtr;
dataPtr += header->num_frames * 6 * sizeof(float); // model bounds
} else if( header->num_meshes && header->num_frames == 0 ) {
iqmData->bounds = (float*)dataPtr;
dataPtr += 6 * sizeof(float); // model bounds
}
if( header->num_meshes )
{
// register shaders
// overwrite the material offset with the shader index
mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes);
surface = iqmData->surfaces;
str = (char *)header + header->ofs_text;
for( i = 0; i < header->num_meshes; i++, mesh++, surface++ ) {
surface->surfaceType = SF_IQM;
Q_strncpyz(surface->name, str + mesh->name, sizeof (surface->name));
Q_strlwr(surface->name); // lowercase the surface name so skin compares are faster
surface->shader = R_FindShader( str + mesh->material, LIGHTMAP_NONE, qtrue );
if( surface->shader->defaultShader )
surface->shader = tr.defaultShader;
surface->data = iqmData;
surface->first_vertex = mesh->first_vertex;
surface->num_vertexes = mesh->num_vertexes;
surface->first_triangle = mesh->first_triangle;
surface->num_triangles = mesh->num_triangles;
}
// copy triangles
triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles);
for( i = 0; i < header->num_triangles; i++, triangle++ ) {
iqmData->triangles[3*i+0] = triangle->vertex[0];
iqmData->triangles[3*i+1] = triangle->vertex[1];
iqmData->triangles[3*i+2] = triangle->vertex[2];
}
// copy vertexarrays and indexes
vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
int n;
// skip disabled arrays
if( vertexarray->type < ARRAY_LEN( vertexArrayFormat )
&& vertexArrayFormat[vertexarray->type] == -1 )
continue;
// total number of values
n = header->num_vertexes * vertexarray->size;
switch( vertexarray->type ) {
case IQM_POSITION:
Com_Memcpy( iqmData->positions,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
break;
case IQM_NORMAL:
Com_Memcpy( iqmData->normals,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
break;
case IQM_TANGENT:
Com_Memcpy( iqmData->tangents,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
break;
case IQM_TEXCOORD:
Com_Memcpy( iqmData->texcoords,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
break;
case IQM_BLENDINDEXES:
case IQM_BLENDWEIGHTS:
break;
case IQM_COLOR:
Com_Memcpy( iqmData->colors,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
break;
}
}
// find unique blend influences per mesh
if( allocateInfluences ) {
int vtx, influence, totalInfluences = 0;
surface = iqmData->surfaces;
for( i = 0; i < header->num_meshes; i++, surface++ ) {
surface->first_influence = totalInfluences;
surface->num_influences = 0;
for( j = 0; j < surface->num_vertexes; j++ ) {
vtx = surface->first_vertex + j;
for( k = 0; k < surface->num_influences; k++ ) {
influence = surface->first_influence + k;
if( *(int*)&iqmData->influenceBlendIndexes[4*influence] != *(int*)&blendIndexes[4*vtx] ) {
continue;
}
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
if ( iqmData->influenceBlendWeights.f[4*influence+0] == blendWeights.f[4*vtx+0] &&
iqmData->influenceBlendWeights.f[4*influence+1] == blendWeights.f[4*vtx+1] &&
iqmData->influenceBlendWeights.f[4*influence+2] == blendWeights.f[4*vtx+2] &&
iqmData->influenceBlendWeights.f[4*influence+3] == blendWeights.f[4*vtx+3] ) {
break;
}
} else {
if ( *(int*)&iqmData->influenceBlendWeights.b[4*influence] == *(int*)&blendWeights.b[4*vtx] ) {
break;
}
}
}
iqmData->influences[vtx] = surface->first_influence + k;
if( k == surface->num_influences ) {
influence = surface->first_influence + k;
iqmData->influenceBlendIndexes[4*influence+0] = blendIndexes[4*vtx+0];
iqmData->influenceBlendIndexes[4*influence+1] = blendIndexes[4*vtx+1];
iqmData->influenceBlendIndexes[4*influence+2] = blendIndexes[4*vtx+2];
iqmData->influenceBlendIndexes[4*influence+3] = blendIndexes[4*vtx+3];
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
iqmData->influenceBlendWeights.f[4*influence+0] = blendWeights.f[4*vtx+0];
iqmData->influenceBlendWeights.f[4*influence+1] = blendWeights.f[4*vtx+1];
iqmData->influenceBlendWeights.f[4*influence+2] = blendWeights.f[4*vtx+2];
iqmData->influenceBlendWeights.f[4*influence+3] = blendWeights.f[4*vtx+3];
} else {
iqmData->influenceBlendWeights.b[4*influence+0] = blendWeights.b[4*vtx+0];
iqmData->influenceBlendWeights.b[4*influence+1] = blendWeights.b[4*vtx+1];
iqmData->influenceBlendWeights.b[4*influence+2] = blendWeights.b[4*vtx+2];
iqmData->influenceBlendWeights.b[4*influence+3] = blendWeights.b[4*vtx+3];
}
totalInfluences++;
surface->num_influences++;
}
}
}
}
}
if( header->num_joints )
{
// copy joint names
str = iqmData->jointNames;
joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
for( i = 0; i < header->num_joints; i++, joint++ ) {
char *name = (char *)header + header->ofs_text +
joint->name;
int len = strlen( name ) + 1;
Com_Memcpy( str, name, len );
str += len;
}
// copy joint parents
joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
for( i = 0; i < header->num_joints; i++, joint++ ) {
iqmData->jointParents[i] = joint->parent;
}
// calculate bind joint matrices and their inverses
mat = iqmData->bindJoints;
matInv = iqmData->invBindJoints;
joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
for( i = 0; i < header->num_joints; i++, joint++ ) {
float baseFrame[12], invBaseFrame[12];
QuatNormalize2( joint->rotate, joint->rotate );
JointToMatrix( joint->rotate, joint->scale, joint->translate, baseFrame );
Matrix34Invert( baseFrame, invBaseFrame );
if ( joint->parent >= 0 )
{
Matrix34Multiply( iqmData->bindJoints + 12 * joint->parent, baseFrame, mat );
mat += 12;
Matrix34Multiply( invBaseFrame, iqmData->invBindJoints + 12 * joint->parent, matInv );
matInv += 12;
}
else
{
Com_Memcpy( mat, baseFrame, sizeof(baseFrame) );
mat += 12;
Com_Memcpy( matInv, invBaseFrame, sizeof(invBaseFrame) );
matInv += 12;
}
}
}
if( header->num_poses )
{
// calculate pose transforms
transform = iqmData->poses;
framedata = (unsigned short *)((byte *)header + header->ofs_frames);
for( i = 0; i < header->num_frames; i++ ) {
pose = (iqmPose_t *)((byte *)header + header->ofs_poses);
for( j = 0; j < header->num_poses; j++, pose++, transform++ ) {
vec3_t translate;
quat_t rotate;
vec3_t scale;
translate[0] = pose->channeloffset[0];
if( pose->mask & 0x001)
translate[0] += *framedata++ * pose->channelscale[0];
translate[1] = pose->channeloffset[1];
if( pose->mask & 0x002)
translate[1] += *framedata++ * pose->channelscale[1];
translate[2] = pose->channeloffset[2];
if( pose->mask & 0x004)
translate[2] += *framedata++ * pose->channelscale[2];
rotate[0] = pose->channeloffset[3];
if( pose->mask & 0x008)
rotate[0] += *framedata++ * pose->channelscale[3];
rotate[1] = pose->channeloffset[4];
if( pose->mask & 0x010)
rotate[1] += *framedata++ * pose->channelscale[4];
rotate[2] = pose->channeloffset[5];
if( pose->mask & 0x020)
rotate[2] += *framedata++ * pose->channelscale[5];
rotate[3] = pose->channeloffset[6];
if( pose->mask & 0x040)
rotate[3] += *framedata++ * pose->channelscale[6];
scale[0] = pose->channeloffset[7];
if( pose->mask & 0x080)
scale[0] += *framedata++ * pose->channelscale[7];
scale[1] = pose->channeloffset[8];
if( pose->mask & 0x100)
scale[1] += *framedata++ * pose->channelscale[8];
scale[2] = pose->channeloffset[9];
if( pose->mask & 0x200)
scale[2] += *framedata++ * pose->channelscale[9];
VectorCopy( translate, transform->translate );
QuatNormalize2( rotate, transform->rotate );
VectorCopy( scale, transform->scale );
}
}
}
// copy model bounds
if(header->ofs_bounds)
{
mat = iqmData->bounds;
bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds);
for(i = 0; i < header->num_frames; i++)
{
mat[0] = bounds->bbmin[0];
mat[1] = bounds->bbmin[1];
mat[2] = bounds->bbmin[2];
mat[3] = bounds->bbmax[0];
mat[4] = bounds->bbmax[1];
mat[5] = bounds->bbmax[2];
mat += 6;
bounds++;
}
}
else if( header->num_meshes && header->num_frames == 0 )
{
mat = iqmData->bounds;
ClearBounds( &iqmData->bounds[0], &iqmData->bounds[3] );
for ( i = 0 ; i < header->num_vertexes ; i++ ) {
AddPointToBounds( &iqmData->positions[i*3], &iqmData->bounds[0], &iqmData->bounds[3] );
}
}
// Create VAO surfaces
if ( iqmData->num_surfaces && iqmData->num_joints <= glRefConfig.glslMaxAnimatedBones )
{
srfVaoIQModel_t *vaoSurf;
srfIQModel_t *surf;
iqmData->numVaoSurfaces = iqmData->num_surfaces;
iqmData->vaoSurfaces = ri.Hunk_Alloc(sizeof(*iqmData->vaoSurfaces) * iqmData->numVaoSurfaces, h_low);
vaoSurf = iqmData->vaoSurfaces;
surf = iqmData->surfaces;
for (i = 0; i < iqmData->num_surfaces; i++, vaoSurf++, surf++)
{
uint32_t offset_xyz, offset_st, offset_normal, offset_tangent;
uint32_t offset_blendindexes, offset_blendweights, stride;
uint32_t dataSize, dataOfs;
uint8_t *data;
glIndex_t indexes[SHADER_MAX_INDEXES];
glIndex_t *ptr;
int *tri;
offset_xyz = 0;
offset_st = offset_xyz + sizeof(float) * 3;
offset_normal = offset_st + sizeof(float) * 2;
offset_tangent = offset_normal + sizeof(int16_t) * 4;
if ( iqmData->num_joints )
{
offset_blendindexes = offset_tangent + sizeof(int16_t) * 4;
offset_blendweights = offset_blendindexes + sizeof(byte) * 4;
if ( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
stride = offset_blendweights + sizeof(float) * 4;
} else {
stride = offset_blendweights + sizeof(byte) * 4;
}
}
else
{
stride = offset_tangent + sizeof(int16_t) * 4;
}
dataSize = surf->num_vertexes * stride;
data = ri.Malloc(dataSize);
dataOfs = 0;
for ( j = 0; j < surf->num_vertexes; j++ )
{
int vtx = surf->first_vertex + j;
// xyz
memcpy(data + dataOfs, &iqmData->positions[vtx*3], sizeof(float) * 3);
dataOfs += sizeof(float) * 3;
// st
memcpy(data + dataOfs, &iqmData->texcoords[vtx*2], sizeof(float) * 2);
dataOfs += sizeof(float) * 2;
// normal
R_VaoPackNormal((int16_t*)(data + dataOfs), &iqmData->normals[vtx*3]);
dataOfs += sizeof(int16_t) * 4;
// tangent
R_VaoPackTangent((int16_t*)(data + dataOfs), &iqmData->tangents[vtx*4]);
dataOfs += sizeof(int16_t) * 4;
if ( iqmData->num_joints )
{
// blendindexes
memcpy(data + dataOfs, &blendIndexes[vtx*4], sizeof(byte) * 4);
dataOfs += sizeof(byte) * 4;
// blendweights
if ( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
memcpy(data + dataOfs, &blendWeights.f[vtx*4], sizeof(float) * 4);
dataOfs += sizeof(float) * 4;
} else {
memcpy(data + dataOfs, &blendWeights.b[vtx*4], sizeof(byte) * 4);
dataOfs += sizeof(byte) * 4;
}
}
}
tri = iqmData->triangles + 3 * surf->first_triangle;
ptr = indexes;
for( j = 0; j < surf->num_triangles; j++ ) {
*ptr++ = (*tri++ - surf->first_vertex);
*ptr++ = (*tri++ - surf->first_vertex);
*ptr++ = (*tri++ - surf->first_vertex);
}
vaoSurf->surfaceType = SF_VAO_IQM;
vaoSurf->iqmData = iqmData;
vaoSurf->iqmSurface = surf;
vaoSurf->numIndexes = surf->num_triangles * 3;
vaoSurf->numVerts = surf->num_vertexes;
vaoSurf->vao = R_CreateVao(va("staticIQMMesh_VAO '%s'", surf->name), data, dataSize, (byte *)indexes, surf->num_triangles * 3 * sizeof(indexes[0]), VAO_USAGE_STATIC);
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].count = 3;
vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].count = 2;
vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].count = 4;
vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].count = 4;
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].type = GL_FLOAT;
vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].type = GL_FLOAT;
vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT;
vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT;
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].normalized = GL_FALSE;
vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].normalized = GL_FALSE;
vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE;
vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE;
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].offset = offset_xyz;
vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].offset = offset_st;
vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].offset = offset_normal;
vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].offset = offset_tangent;
vaoSurf->vao->attribs[ATTR_INDEX_POSITION].stride = stride;
vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].stride = stride;
vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].stride = stride;
vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].stride = stride;
if ( iqmData->num_joints )
{
vaoSurf->vao->attribs[ATTR_INDEX_BONE_INDEXES].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].enabled = 1;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_INDEXES].count = 4;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].count = 4;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_INDEXES].type = GL_UNSIGNED_BYTE;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_INDEXES].normalized = GL_FALSE;
if ( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].type = GL_FLOAT;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].normalized = GL_FALSE;
} else {
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].type = GL_UNSIGNED_BYTE;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].normalized = GL_TRUE;
}
vaoSurf->vao->attribs[ATTR_INDEX_BONE_INDEXES].offset = offset_blendindexes;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].offset = offset_blendweights;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_INDEXES].stride = stride;
vaoSurf->vao->attribs[ATTR_INDEX_BONE_WEIGHTS].stride = stride;
}
Vao_SetVertexPointers(vaoSurf->vao);
ri.Free(data);
}
}
return qtrue;
}
/*
=============
R_CullIQM
=============
*/
static int R_CullIQM( iqmData_t *data, trRefEntity_t *ent ) {
vec3_t bounds[2];
vec_t *oldBounds, *newBounds;
int i;
if (!data->bounds) {
tr.pc.c_box_cull_md3_clip++;
return CULL_CLIP;
}
// compute bounds pointers
oldBounds = data->bounds + 6*ent->e.oldframe;
newBounds = data->bounds + 6*ent->e.frame;
// calculate a bounding box in the current coordinate system
for (i = 0 ; i < 3 ; i++) {
bounds[0][i] = oldBounds[i] < newBounds[i] ? oldBounds[i] : newBounds[i];
bounds[1][i] = oldBounds[i+3] > newBounds[i+3] ? oldBounds[i+3] : newBounds[i+3];
}
switch ( R_CullLocalBox( bounds ) )
{
case CULL_IN:
tr.pc.c_box_cull_md3_in++;
return CULL_IN;
case CULL_CLIP:
tr.pc.c_box_cull_md3_clip++;
return CULL_CLIP;
case CULL_OUT:
default:
tr.pc.c_box_cull_md3_out++;
return CULL_OUT;
}
}
/*
=================
R_ComputeIQMFogNum
=================
*/
int R_ComputeIQMFogNum( iqmData_t *data, trRefEntity_t *ent ) {
int i, j;
fog_t *fog;
const vec_t *bounds;
const vec_t defaultBounds[6] = { -8, -8, -8, 8, 8, 8 };
vec3_t diag, center;
vec3_t localOrigin;
vec_t radius;
if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
return 0;
}
// FIXME: non-normalized axis issues
if (data->bounds) {
bounds = data->bounds + 6*ent->e.frame;
} else {
bounds = defaultBounds;
}
VectorSubtract( bounds+3, bounds, diag );
VectorMA( bounds, 0.5f, diag, center );
VectorAdd( ent->e.origin, center, localOrigin );
radius = 0.5f * VectorLength( diag );
for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
fog = &tr.world->fogs[i];
for ( j = 0 ; j < 3 ; j++ ) {
if ( localOrigin[j] - radius >= fog->bounds[1][j] ) {
break;
}
if ( localOrigin[j] + radius <= fog->bounds[0][j] ) {
break;
}
}
if ( j == 3 ) {
return i;
}
}
return 0;
}
/*
=================
R_AddIQMSurfaces
Add all surfaces of this model
=================
*/
void R_AddIQMSurfaces( trRefEntity_t *ent ) {
iqmData_t *data;
srfIQModel_t *surface;
void *drawSurf;
int i, j;
qboolean personalModel;
int cull;
int fogNum;
int cubemapIndex;
shader_t *shader;
skin_t *skin;
data = tr.currentModel->modelData;
surface = data->surfaces;
// don't add third_person objects if not in a portal
personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal
|| (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW)));
if ( ent->e.renderfx & RF_WRAP_FRAMES ) {
ent->e.frame %= data->num_frames;
ent->e.oldframe %= data->num_frames;
}
//
// Validate the frames so there is no chance of a crash.
// This will write directly into the entity structure, so
// when the surfaces are rendered, they don't need to be
// range checked again.
//
if ( (ent->e.frame >= data->num_frames)
|| (ent->e.frame < 0)
|| (ent->e.oldframe >= data->num_frames)
|| (ent->e.oldframe < 0) ) {
ri.Printf( PRINT_DEVELOPER, "R_AddIQMSurfaces: no such frame %d to %d for '%s'\n",
ent->e.oldframe, ent->e.frame,
tr.currentModel->name );
ent->e.frame = 0;
ent->e.oldframe = 0;
}
//
// cull the entire model if merged bounding box of both frames
// is outside the view frustum.
//
cull = R_CullIQM ( data, ent );
if ( cull == CULL_OUT ) {
return;
}
//
// set up lighting now that we know we aren't culled
//
if ( !personalModel || r_shadows->integer > 1 ) {
R_SetupEntityLighting( &tr.refdef, ent );
}
//
// see if we are in a fog volume
//
fogNum = R_ComputeIQMFogNum( data, ent );
cubemapIndex = R_CubemapForPoint(ent->e.origin);
for ( i = 0 ; i < data->num_surfaces ; i++ ) {
if(ent->e.customShader)
shader = R_GetShaderByHandle( ent->e.customShader );
else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins)
{
skin = R_GetSkinByHandle(ent->e.customSkin);
shader = tr.defaultShader;
for(j = 0; j < skin->numSurfaces; j++)
{
if (!strcmp(skin->surfaces[j].name, surface->name))
{
shader = skin->surfaces[j].shader;
break;
}
}
} else {
shader = surface->shader;
}
if ( data->numVaoSurfaces ) {
drawSurf = &data->vaoSurfaces[i];
} else {
drawSurf = surface;
}
// we will add shadows even if the main object isn't visible in the view
// stencil shadows can't do personal models unless I polyhedron clip
if ( !personalModel
&& r_shadows->integer == 2
&& fogNum == 0
&& !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) )
&& shader->sort == SS_OPAQUE ) {
R_AddDrawSurf( drawSurf, tr.shadowShader, 0, 0, 0, 0 );
}
// projection shadows work fine with personal models
if ( r_shadows->integer == 3
&& fogNum == 0
&& (ent->e.renderfx & RF_SHADOW_PLANE )
&& shader->sort == SS_OPAQUE ) {
R_AddDrawSurf( drawSurf, tr.projectionShadowShader, 0, 0, 0, 0 );
}
if( !personalModel ) {
R_AddDrawSurf( drawSurf, shader, fogNum, 0, 0, cubemapIndex );
}
surface++;
}
}
static void ComputePoseMats( iqmData_t *data, int frame, int oldframe,
float backlerp, float *poseMats ) {
iqmTransform_t relativeJoints[IQM_MAX_JOINTS];
iqmTransform_t *relativeJoint;
const iqmTransform_t *pose;
const iqmTransform_t *oldpose;
const int *jointParent;
const float *invBindMat;
float *poseMat, lerp;
int i;
relativeJoint = relativeJoints;
// copy or lerp animation frame pose
if ( oldframe == frame ) {
pose = &data->poses[frame * data->num_poses];
for ( i = 0; i < data->num_poses; i++, pose++, relativeJoint++ ) {
VectorCopy( pose->translate, relativeJoint->translate );
QuatCopy( pose->rotate, relativeJoint->rotate );
VectorCopy( pose->scale, relativeJoint->scale );
}
} else {
lerp = 1.0f - backlerp;
pose = &data->poses[frame * data->num_poses];
oldpose = &data->poses[oldframe * data->num_poses];
for ( i = 0; i < data->num_poses; i++, oldpose++, pose++, relativeJoint++ ) {
relativeJoint->translate[0] = oldpose->translate[0] * backlerp + pose->translate[0] * lerp;
relativeJoint->translate[1] = oldpose->translate[1] * backlerp + pose->translate[1] * lerp;
relativeJoint->translate[2] = oldpose->translate[2] * backlerp + pose->translate[2] * lerp;
relativeJoint->scale[0] = oldpose->scale[0] * backlerp + pose->scale[0] * lerp;
relativeJoint->scale[1] = oldpose->scale[1] * backlerp + pose->scale[1] * lerp;
relativeJoint->scale[2] = oldpose->scale[2] * backlerp + pose->scale[2] * lerp;
QuatSlerp( oldpose->rotate, pose->rotate, lerp, relativeJoint->rotate );
}
}
// multiply by inverse of bind pose and parent 'pose mat' (bind pose transform matrix)
relativeJoint = relativeJoints;
jointParent = data->jointParents;
invBindMat = data->invBindJoints;
poseMat = poseMats;
for ( i = 0; i < data->num_poses; i++, relativeJoint++, jointParent++, invBindMat += 12, poseMat += 12 ) {
float mat1[12], mat2[12];
JointToMatrix( relativeJoint->rotate, relativeJoint->scale, relativeJoint->translate, mat1 );
if ( *jointParent >= 0 ) {
Matrix34Multiply( &data->bindJoints[(*jointParent)*12], mat1, mat2 );
Matrix34Multiply( mat2, invBindMat, mat1 );
Matrix34Multiply( &poseMats[(*jointParent)*12], mat1, poseMat );
} else {
Matrix34Multiply( mat1, invBindMat, poseMat );
}
}
}
static void ComputeJointMats( iqmData_t *data, int frame, int oldframe,
float backlerp, float *mat ) {
float *mat1;
int i;
if ( data->num_poses == 0 ) {
Com_Memcpy( mat, data->bindJoints, data->num_joints * 12 * sizeof(float) );
return;
}
ComputePoseMats( data, frame, oldframe, backlerp, mat );
for( i = 0; i < data->num_joints; i++ ) {
float outmat[12];
mat1 = mat + 12 * i;
Com_Memcpy(outmat, mat1, sizeof(outmat));
Matrix34Multiply( outmat, data->bindJoints + 12*i, mat1 );
}
}
/*
=================
RB_AddIQMSurfaces
Compute vertices for this model surface
=================
*/
void RB_IQMSurfaceAnim( surfaceType_t *surface ) {
srfIQModel_t *surf = (srfIQModel_t *)surface;
iqmData_t *data = surf->data;
float poseMats[IQM_MAX_JOINTS * 12];
float influenceVtxMat[SHADER_MAX_VERTEXES * 12];
float influenceNrmMat[SHADER_MAX_VERTEXES * 9];
int i;
float *xyz;
float *normal;
float *tangent;
float *texCoords;
byte *color;
vec4_t *outXYZ;
int16_t *outNormal;
int16_t *outTangent;
vec2_t *outTexCoord;
uint16_t *outColor;
int frame = data->num_frames ? backEnd.currentEntity->e.frame % data->num_frames : 0;
int oldframe = data->num_frames ? backEnd.currentEntity->e.oldframe % data->num_frames : 0;
float backlerp = backEnd.currentEntity->e.backlerp;
int *tri;
glIndex_t *ptr;
glIndex_t base;
RB_CHECKOVERFLOW( surf->num_vertexes, surf->num_triangles * 3 );
xyz = &data->positions[surf->first_vertex * 3];
normal = &data->normals[surf->first_vertex * 3];
tangent = &data->tangents[surf->first_vertex * 4];
texCoords = &data->texcoords[surf->first_vertex * 2];
if ( data->colors ) {
color = &data->colors[surf->first_vertex * 4];
} else {
color = NULL;
}
outXYZ = &tess.xyz[tess.numVertexes];
outNormal = tess.normal[tess.numVertexes];
outTangent = tess.tangent[tess.numVertexes];
outTexCoord = &tess.texCoords[tess.numVertexes];
outColor = tess.color[tess.numVertexes];
if ( data->num_poses > 0 ) {
// compute interpolated joint matrices
ComputePoseMats( data, frame, oldframe, backlerp, poseMats );
// compute vertex blend influence matricies
for( i = 0; i < surf->num_influences; i++ ) {
int influence = surf->first_influence + i;
float *vtxMat = &influenceVtxMat[12*i];
float *nrmMat = &influenceNrmMat[9*i];
int j;
float blendWeights[4];
if ( data->blendWeightsType == IQM_FLOAT ) {
blendWeights[0] = data->influenceBlendWeights.f[4*influence + 0];
blendWeights[1] = data->influenceBlendWeights.f[4*influence + 1];
blendWeights[2] = data->influenceBlendWeights.f[4*influence + 2];
blendWeights[3] = data->influenceBlendWeights.f[4*influence + 3];
} else {
blendWeights[0] = (float)data->influenceBlendWeights.b[4*influence + 0] / 255.0f;
blendWeights[1] = (float)data->influenceBlendWeights.b[4*influence + 1] / 255.0f;
blendWeights[2] = (float)data->influenceBlendWeights.b[4*influence + 2] / 255.0f;
blendWeights[3] = (float)data->influenceBlendWeights.b[4*influence + 3] / 255.0f;
}
if ( blendWeights[0] <= 0.0f ) {
// no blend joint, use identity matrix.
vtxMat[0] = identityMatrix[0];
vtxMat[1] = identityMatrix[1];
vtxMat[2] = identityMatrix[2];
vtxMat[3] = identityMatrix[3];
vtxMat[4] = identityMatrix[4];
vtxMat[5] = identityMatrix[5];
vtxMat[6] = identityMatrix[6];
vtxMat[7] = identityMatrix[7];
vtxMat[8] = identityMatrix[8];
vtxMat[9] = identityMatrix[9];
vtxMat[10] = identityMatrix[10];
vtxMat[11] = identityMatrix[11];
} else {
// compute the vertex matrix by blending the up to
// four blend weights
vtxMat[0] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 0];
vtxMat[1] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 1];
vtxMat[2] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 2];
vtxMat[3] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 3];
vtxMat[4] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 4];
vtxMat[5] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 5];
vtxMat[6] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 6];
vtxMat[7] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 7];
vtxMat[8] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 8];
vtxMat[9] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 9];
vtxMat[10] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 10];
vtxMat[11] = blendWeights[0] * poseMats[12 * data->influenceBlendIndexes[4*influence + 0] + 11];
for( j = 1; j < 4; j++ ) {
if ( blendWeights[j] <= 0.0f ) {
break;
}
vtxMat[0] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 0];
vtxMat[1] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 1];
vtxMat[2] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 2];
vtxMat[3] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 3];
vtxMat[4] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 4];
vtxMat[5] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 5];
vtxMat[6] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 6];
vtxMat[7] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 7];
vtxMat[8] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 8];
vtxMat[9] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 9];
vtxMat[10] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 10];
vtxMat[11] += blendWeights[j] * poseMats[12 * data->influenceBlendIndexes[4*influence + j] + 11];
}
}
// compute the normal matrix as transpose of the adjoint
// of the vertex matrix
nrmMat[ 0] = vtxMat[ 5]*vtxMat[10] - vtxMat[ 6]*vtxMat[ 9];
nrmMat[ 1] = vtxMat[ 6]*vtxMat[ 8] - vtxMat[ 4]*vtxMat[10];
nrmMat[ 2] = vtxMat[ 4]*vtxMat[ 9] - vtxMat[ 5]*vtxMat[ 8];
nrmMat[ 3] = vtxMat[ 2]*vtxMat[ 9] - vtxMat[ 1]*vtxMat[10];
nrmMat[ 4] = vtxMat[ 0]*vtxMat[10] - vtxMat[ 2]*vtxMat[ 8];
nrmMat[ 5] = vtxMat[ 1]*vtxMat[ 8] - vtxMat[ 0]*vtxMat[ 9];
nrmMat[ 6] = vtxMat[ 1]*vtxMat[ 6] - vtxMat[ 2]*vtxMat[ 5];
nrmMat[ 7] = vtxMat[ 2]*vtxMat[ 4] - vtxMat[ 0]*vtxMat[ 6];
nrmMat[ 8] = vtxMat[ 0]*vtxMat[ 5] - vtxMat[ 1]*vtxMat[ 4];
}
// transform vertexes and fill other data
for( i = 0; i < surf->num_vertexes;
i++, xyz+=3, normal+=3, tangent+=4, texCoords+=2,
outXYZ++, outNormal+=4, outTangent+=4, outTexCoord++ ) {
int influence = data->influences[surf->first_vertex + i] - surf->first_influence;
float *vtxMat = &influenceVtxMat[12*influence];
float *nrmMat = &influenceNrmMat[9*influence];
(*outTexCoord)[0] = texCoords[0];
(*outTexCoord)[1] = texCoords[1];
(*outXYZ)[0] =
vtxMat[ 0] * xyz[0] +
vtxMat[ 1] * xyz[1] +
vtxMat[ 2] * xyz[2] +
vtxMat[ 3];
(*outXYZ)[1] =
vtxMat[ 4] * xyz[0] +
vtxMat[ 5] * xyz[1] +
vtxMat[ 6] * xyz[2] +
vtxMat[ 7];
(*outXYZ)[2] =
vtxMat[ 8] * xyz[0] +
vtxMat[ 9] * xyz[1] +
vtxMat[10] * xyz[2] +
vtxMat[11];
{
vec3_t unpackedNormal;
vec4_t unpackedTangent;
unpackedNormal[0] = DotProduct(&nrmMat[0], normal);
unpackedNormal[1] = DotProduct(&nrmMat[3], normal);
unpackedNormal[2] = DotProduct(&nrmMat[6], normal);
R_VaoPackNormal(outNormal, unpackedNormal);
unpackedTangent[0] = DotProduct(&nrmMat[0], tangent);
unpackedTangent[1] = DotProduct(&nrmMat[3], tangent);
unpackedTangent[2] = DotProduct(&nrmMat[6], tangent);
unpackedTangent[3] = tangent[3];
R_VaoPackTangent(outTangent, unpackedTangent);
}
}
} else {
// copy vertexes and fill other data
for( i = 0; i < surf->num_vertexes;
i++, xyz+=3, normal+=3, tangent+=4, texCoords+=2,
outXYZ++, outNormal+=4, outTangent+=4, outTexCoord++ ) {
(*outTexCoord)[0] = texCoords[0];
(*outTexCoord)[1] = texCoords[1];
(*outXYZ)[0] = xyz[0];
(*outXYZ)[1] = xyz[1];
(*outXYZ)[2] = xyz[2];
R_VaoPackNormal(outNormal, normal);
R_VaoPackTangent(outTangent, tangent);
}
}
if ( color ) {
for( i = 0; i < surf->num_vertexes; i++, color+=4, outColor+=4 ) {
outColor[0] = color[0] * 257;
outColor[1] = color[1] * 257;
outColor[2] = color[2] * 257;
outColor[3] = color[3] * 257;
}
} else {
for( i = 0; i < surf->num_vertexes; i++, outColor+=4 ) {
outColor[0] = 0;
outColor[1] = 0;
outColor[2] = 0;
outColor[3] = 0;
}
}
tri = data->triangles + 3 * surf->first_triangle;
ptr = &tess.indexes[tess.numIndexes];
base = tess.numVertexes;
for( i = 0; i < surf->num_triangles; i++ ) {
*ptr++ = base + (*tri++ - surf->first_vertex);
*ptr++ = base + (*tri++ - surf->first_vertex);
*ptr++ = base + (*tri++ - surf->first_vertex);
}
tess.numIndexes += 3 * surf->num_triangles;
tess.numVertexes += surf->num_vertexes;
}
/*
=================
RB_IQMSurfaceAnimVao
=================
*/
void RB_IQMSurfaceAnimVao(srfVaoIQModel_t * surface)
{
iqmData_t *data = surface->iqmData;
if (ShaderRequiresCPUDeforms(tess.shader))
{
RB_IQMSurfaceAnim((surfaceType_t*)surface->iqmSurface);
return;
}
if(!surface->vao)
return;
//RB_CheckVao(surface->vao);
RB_EndSurface();
RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex);
R_BindVao(surface->vao);
tess.useInternalVao = qfalse;
tess.numIndexes = surface->numIndexes;
tess.numVertexes = surface->numVerts;
glState.boneAnimation = data->num_poses;
if ( glState.boneAnimation ) {
float jointMats[IQM_MAX_JOINTS * 12];
int frame = data->num_frames ? backEnd.currentEntity->e.frame % data->num_frames : 0;
int oldframe = data->num_frames ? backEnd.currentEntity->e.oldframe % data->num_frames : 0;
float backlerp = backEnd.currentEntity->e.backlerp;
int i;
// compute interpolated joint matrices
ComputePoseMats( surface->iqmData, frame, oldframe, backlerp, jointMats );
// convert row-major order 3x4 matrix to column-major order 4x4 matrix
for ( i = 0; i < data->num_poses; i++ ) {
glState.boneMatrix[i][0] = jointMats[i*12+0];
glState.boneMatrix[i][1] = jointMats[i*12+4];
glState.boneMatrix[i][2] = jointMats[i*12+8];
glState.boneMatrix[i][3] = 0.0f;
glState.boneMatrix[i][4] = jointMats[i*12+1];
glState.boneMatrix[i][5] = jointMats[i*12+5];
glState.boneMatrix[i][6] = jointMats[i*12+9];
glState.boneMatrix[i][7] = 0.0f;
glState.boneMatrix[i][8] = jointMats[i*12+2];
glState.boneMatrix[i][9] = jointMats[i*12+6];
glState.boneMatrix[i][10] = jointMats[i*12+10];
glState.boneMatrix[i][11] = 0.0f;
glState.boneMatrix[i][12] = jointMats[i*12+3];
glState.boneMatrix[i][13] = jointMats[i*12+7];
glState.boneMatrix[i][14] = jointMats[i*12+11];
glState.boneMatrix[i][15] = 1.0f;
}
}
RB_EndSurface();
glState.boneAnimation = 0;
}
int R_IQMLerpTag( orientation_t *tag, iqmData_t *data,
int startFrame, int endFrame,
float frac, const char *tagName ) {
float jointMats[IQM_MAX_JOINTS * 12];
int joint;
char *names = data->jointNames;
// get joint number by reading the joint names
for( joint = 0; joint < data->num_joints; joint++ ) {
if( !strcmp( tagName, names ) )
break;
names += strlen( names ) + 1;
}
if( joint >= data->num_joints ) {
AxisClear( tag->axis );
VectorClear( tag->origin );
return qfalse;
}
ComputeJointMats( data, startFrame, endFrame, frac, jointMats );
tag->axis[0][0] = jointMats[12 * joint + 0];
tag->axis[1][0] = jointMats[12 * joint + 1];
tag->axis[2][0] = jointMats[12 * joint + 2];
tag->origin[0] = jointMats[12 * joint + 3];
tag->axis[0][1] = jointMats[12 * joint + 4];
tag->axis[1][1] = jointMats[12 * joint + 5];
tag->axis[2][1] = jointMats[12 * joint + 6];
tag->origin[1] = jointMats[12 * joint + 7];
tag->axis[0][2] = jointMats[12 * joint + 8];
tag->axis[1][2] = jointMats[12 * joint + 9];
tag->axis[2][2] = jointMats[12 * joint + 10];
tag->origin[2] = jointMats[12 * joint + 11];
return qtrue;
}