Improve IQM loading

- Only allocate memory for vertex arrays that are present in the IQM
file and are actually used (may not have colors or blend index/weights,
don't load tangents in opengl1). (Colors is fixed to next commit.)
- Explicitly handle loading IQM files without meshes (bones only).
- Better IQM validation. Header data offset 0 mean data is not present
in file. Check if required vertex arrays are present.

This involved a lot of white space changes and moving code around.
This commit is contained in:
Zack Middleton 2018-07-27 17:40:16 -05:00
parent 6c3d92133d
commit fdc08e860e
4 changed files with 924 additions and 751 deletions

View File

@ -599,6 +599,7 @@ typedef struct {
int num_poses;
struct srfIQModel_s *surfaces;
int *triangles;
float *positions;
float *texcoords;
float *normals;
@ -609,18 +610,17 @@ typedef struct {
byte *b;
} blendWeights;
byte *colors;
int *triangles;
// depending upon the exporter, blend indices and weights might be int/float
// as opposed to the recommended byte/byte, for example Noesis exports
// int/float whereas the official IQM tool exports byte/byte
byte blendWeightsType; // IQM_UBYTE or IQM_FLOAT
char *jointNames;
int *jointParents;
float *jointMats;
float *poseMats;
float *bounds;
char *names;
} iqmData_t;
// inter-quake-model surface

View File

@ -37,7 +37,7 @@ static qboolean IQM_CheckRange( iqmHeader_t *header, int offset,
// return true if the range specified by offset, count and size
// doesn't fit into the file
return ( count <= 0 ||
offset < 0 ||
offset <= 0 ||
offset > header->filesize ||
offset + count * size < 0 ||
offset + count * size > header->filesize );
@ -147,10 +147,11 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
float jointInvMats[IQM_MAX_JOINTS * 12] = {0.0f};
float *mat, *matInv;
size_t size, joint_names;
byte *dataPtr;
iqmData_t *iqmData;
srfIQModel_t *surface;
char meshName[MAX_QPATH];
byte blendIndexesType, blendWeightsType;
int vertexArrayFormat[IQM_COLOR+1];
if( filesize < sizeof(iqmHeader_t) ) {
return qfalse;
@ -206,8 +207,12 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
return qfalse;
}
blendIndexesType = blendWeightsType = IQM_UBYTE;
for ( i = 0; i < ARRAY_LEN( vertexArrayFormat ); i++ ) {
vertexArrayFormat[i] = -1;
}
if ( header->num_meshes )
{
// check and swap vertex arrays
if( IQM_CheckRange( header, header->ofs_vertexarrays,
header->num_vertexarrays,
@ -253,6 +258,10 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
break;
}
if( vertexarray->type < ARRAY_LEN( vertexArrayFormat ) ) {
vertexArrayFormat[vertexarray->type] = vertexarray->format;
}
switch( vertexarray->type ) {
case IQM_POSITION:
case IQM_NORMAL:
@ -279,7 +288,6 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
vertexarray->size != 4 ) {
return qfalse;
}
blendIndexesType = vertexarray->format;
break;
case IQM_BLENDWEIGHTS:
if( (vertexarray->format != IQM_FLOAT &&
@ -287,7 +295,6 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
vertexarray->size != 4 ) {
return qfalse;
}
blendWeightsType = vertexarray->format;
break;
case IQM_COLOR:
if( vertexarray->format != IQM_UBYTE ||
@ -298,6 +305,29 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
}
}
// 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;
}
// opengl1 renderer doesn't use tangents
vertexArrayFormat[IQM_TANGENT] = -1;
// FIXME: don't require colors array when drawing
vertexArrayFormat[IQM_COLOR] = IQM_UBYTE;
// check and swap triangles
if( IQM_CheckRange( header, header->ofs_triangles,
header->num_triangles, sizeof(iqmTriangle_t) ) ) {
@ -337,15 +367,13 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
}
// check ioq3 limits
if ( mesh->num_vertexes >= SHADER_MAX_VERTEXES )
{
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 )
{
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 );
@ -361,6 +389,7 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
return qfalse;
}
}
}
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",
@ -460,26 +489,41 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
// allocate the model and copy the data
size = sizeof(iqmData_t);
size += header->num_meshes * sizeof( srfIQModel_t );
size += header->num_joints * 12 * sizeof( float ); // joint mats
size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
if(header->ofs_bounds)
size += header->num_frames * 6 * sizeof(float); // model bounds
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
size += header->num_vertexes * 4 * sizeof(float); // tangents
size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes
size += header->num_vertexes * 4 * sizeof(byte); // colors
size += header->num_joints * sizeof(int); // parents
size += header->num_triangles * 3 * sizeof(int); // triangles
size += joint_names; // joint names
// blendWeights
if (blendWeightsType == IQM_FLOAT) {
size += header->num_vertexes * 4 * sizeof(float);
} else {
size += header->num_vertexes * 4 * sizeof(byte);
if ( vertexArrayFormat[IQM_TANGENT] != -1 ) {
size += header->num_vertexes * 4 * sizeof(float); // tangents
}
if ( vertexArrayFormat[IQM_BLENDINDEXES] != -1 ) {
size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes
}
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_UBYTE ) {
size += header->num_vertexes * 4 * sizeof(byte); // blendWeights
} else if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
size += header->num_vertexes * 4 * sizeof(float); // blendWeights
}
if ( vertexArrayFormat[IQM_COLOR] != -1 ) {
size += header->num_vertexes * 4 * sizeof(byte); // colors
}
}
if( header->num_joints ) {
size += joint_names; // joint names
size += header->num_joints * sizeof(int); // joint parents
size += header->num_joints * 12 * sizeof( float ); // joint mats
}
if( header->num_poses ) {
size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
}
if( header->ofs_bounds ) {
size += header->num_frames * 6 * sizeof(float); // model bounds
}
mod->type = MOD_IQM;
@ -487,45 +531,186 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
mod->modelData = iqmData;
// fill header
iqmData->num_vertexes = header->num_vertexes;
iqmData->num_triangles = header->num_triangles;
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 = blendWeightsType;
iqmData->surfaces = (srfIQModel_t *)(iqmData + 1);
iqmData->jointMats = (float *) (iqmData->surfaces + iqmData->num_surfaces);
iqmData->poseMats = iqmData->jointMats + 12 * header->num_joints;
if(header->ofs_bounds)
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_BLENDINDEXES] != -1 ) {
iqmData->blendIndexes = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // blendIndexes
}
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_UBYTE ) {
iqmData->blendWeights.b = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // blendWeights
} else if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
iqmData->blendWeights.f = (float*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(float); // blendWeights
}
if ( vertexArrayFormat[IQM_COLOR] != -1 ) {
iqmData->colors = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // colors
}
}
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->jointMats = (float*)dataPtr;
dataPtr += header->num_joints * 12 * sizeof( float ); // joint mats
}
if( header->num_poses ) {
iqmData->poseMats = (float*)dataPtr;
dataPtr += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
}
if( header->ofs_bounds ) {
iqmData->bounds = (float*)dataPtr;
dataPtr += header->num_frames * 6 * sizeof(float); // model bounds
}
if( header->num_meshes )
{
iqmData->bounds = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
iqmData->positions = iqmData->bounds + 6 * header->num_frames;
// 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;
}
else
iqmData->positions = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
iqmData->texcoords = iqmData->positions + 3 * header->num_vertexes;
iqmData->normals = iqmData->texcoords + 2 * header->num_vertexes;
iqmData->tangents = iqmData->normals + 3 * header->num_vertexes;
iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes);
if(blendWeightsType == IQM_FLOAT) {
iqmData->blendWeights.f = (float *)(iqmData->blendIndexes + 4 * header->num_vertexes);
iqmData->colors = (byte *)(iqmData->blendWeights.f + 4 * header->num_vertexes);
// 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:
if( vertexarray->format == IQM_INT ) {
int *data = (int*)((byte*)header + vertexarray->offset);
for ( j = 0; j < n; j++ ) {
iqmData->blendIndexes[j] = (byte)data[j];
}
} else {
iqmData->blendWeights.b = iqmData->blendIndexes + 4 * header->num_vertexes;
iqmData->colors = iqmData->blendWeights.b + 4 * header->num_vertexes;
Com_Memcpy( iqmData->blendIndexes,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_BLENDWEIGHTS:
if( vertexarray->format == IQM_FLOAT ) {
Com_Memcpy( iqmData->blendWeights.f,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
} else {
Com_Memcpy( iqmData->blendWeights.b,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_COLOR:
Com_Memcpy( iqmData->colors,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
break;
}
}
}
iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes);
iqmData->triangles = iqmData->jointParents + header->num_joints;
iqmData->names = (char *)(iqmData->triangles + 3 * header->num_triangles);
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;
}
if ( header->num_joints == 0 )
iqmData->jointMats = NULL;
if ( header->num_poses == 0 )
iqmData->poseMats = NULL;
// 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 joint matrices and their inverses
// joint inverses are needed only until the pose matrices are calculated
@ -553,7 +738,10 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
matInv += 12;
}
}
}
if( header->num_poses )
{
// calculate pose matrices
framedata = (unsigned short *)((byte *)header + header->ofs_frames);
mat = iqmData->poseMats;
@ -612,109 +800,6 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
mat += 12;
}
}
// 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 vertexarrays and indexes
vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
int n;
// 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:
if( blendIndexesType == IQM_INT ) {
int *data = (int*)((byte*)header + vertexarray->offset);
for ( j = 0; j < n; j++ ) {
iqmData->blendIndexes[j] = (byte)data[j];
}
} else {
Com_Memcpy( iqmData->blendIndexes,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_BLENDWEIGHTS:
if( blendWeightsType == IQM_FLOAT ) {
Com_Memcpy( iqmData->blendWeights.f,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
} else {
Com_Memcpy( iqmData->blendWeights.b,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_COLOR:
Com_Memcpy( iqmData->colors,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
break;
}
}
// 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;
}
// 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 joint names
str = iqmData->names;
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 model bounds
@ -1152,7 +1237,7 @@ int R_IQMLerpTag( orientation_t *tag, iqmData_t *data,
float frac, const char *tagName ) {
float jointMats[IQM_MAX_JOINTS * 12];
int joint;
char *names = data->names;
char *names = data->jointNames;
// get joint number by reading the joint names
for( joint = 0; joint < data->num_joints; joint++ ) {

View File

@ -949,6 +949,7 @@ typedef struct {
int num_poses;
struct srfIQModel_s *surfaces;
int *triangles;
float *positions;
float *texcoords;
float *normals;
@ -959,18 +960,17 @@ typedef struct {
byte *b;
} blendWeights;
byte *colors;
int *triangles;
// depending upon the exporter, blend indices and weights might be int/float
// as opposed to the recommended byte/byte, for example Noesis exports
// int/float whereas the official IQM tool exports byte/byte
byte blendWeightsType; // IQM_UBYTE or IQM_FLOAT
char *jointNames;
int *jointParents;
float *jointMats;
float *poseMats;
float *bounds;
char *names;
} iqmData_t;
// inter-quake-model surface

View File

@ -37,7 +37,7 @@ static qboolean IQM_CheckRange( iqmHeader_t *header, int offset,
// return true if the range specified by offset, count and size
// doesn't fit into the file
return ( count <= 0 ||
offset < 0 ||
offset <= 0 ||
offset > header->filesize ||
offset + count * size < 0 ||
offset + count * size > header->filesize );
@ -147,10 +147,11 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
float jointInvMats[IQM_MAX_JOINTS * 12] = {0.0f};
float *mat, *matInv;
size_t size, joint_names;
byte *dataPtr;
iqmData_t *iqmData;
srfIQModel_t *surface;
char meshName[MAX_QPATH];
byte blendIndexesType, blendWeightsType;
int vertexArrayFormat[IQM_COLOR+1];
if( filesize < sizeof(iqmHeader_t) ) {
return qfalse;
@ -206,8 +207,12 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
return qfalse;
}
blendIndexesType = blendWeightsType = IQM_UBYTE;
for ( i = 0; i < ARRAY_LEN( vertexArrayFormat ); i++ ) {
vertexArrayFormat[i] = -1;
}
if ( header->num_meshes )
{
// check and swap vertex arrays
if( IQM_CheckRange( header, header->ofs_vertexarrays,
header->num_vertexarrays,
@ -253,6 +258,10 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
break;
}
if( vertexarray->type < ARRAY_LEN( vertexArrayFormat ) ) {
vertexArrayFormat[vertexarray->type] = vertexarray->format;
}
switch( vertexarray->type ) {
case IQM_POSITION:
case IQM_NORMAL:
@ -279,7 +288,6 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
vertexarray->size != 4 ) {
return qfalse;
}
blendIndexesType = vertexarray->format;
break;
case IQM_BLENDWEIGHTS:
if( (vertexarray->format != IQM_FLOAT &&
@ -287,7 +295,6 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
vertexarray->size != 4 ) {
return qfalse;
}
blendWeightsType = vertexarray->format;
break;
case IQM_COLOR:
if( vertexarray->format != IQM_UBYTE ||
@ -298,6 +305,32 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
}
}
// 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;
}
// FIXME: don't require colors array when drawing
vertexArrayFormat[IQM_COLOR] = IQM_UBYTE;
// check and swap triangles
if( IQM_CheckRange( header, header->ofs_triangles,
header->num_triangles, sizeof(iqmTriangle_t) ) ) {
@ -337,15 +370,13 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
}
// check ioq3 limits
if ( mesh->num_vertexes >= SHADER_MAX_VERTEXES )
{
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 )
{
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 );
@ -361,6 +392,7 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
return qfalse;
}
}
}
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",
@ -460,26 +492,41 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
// allocate the model and copy the data
size = sizeof(iqmData_t);
size += header->num_meshes * sizeof( srfIQModel_t );
size += header->num_joints * 12 * sizeof( float ); // joint mats
size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
if(header->ofs_bounds)
size += header->num_frames * 6 * sizeof(float); // model bounds
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
size += header->num_vertexes * 4 * sizeof(float); // tangents
size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes
size += header->num_vertexes * 4 * sizeof(byte); // colors
size += header->num_joints * sizeof(int); // parents
size += header->num_triangles * 3 * sizeof(int); // triangles
size += joint_names; // joint names
// blendWeights
if (blendWeightsType == IQM_FLOAT) {
size += header->num_vertexes * 4 * sizeof(float);
} else {
size += header->num_vertexes * 4 * sizeof(byte);
if ( vertexArrayFormat[IQM_TANGENT] != -1 ) {
size += header->num_vertexes * 4 * sizeof(float); // tangents
}
if ( vertexArrayFormat[IQM_BLENDINDEXES] != -1 ) {
size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes
}
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_UBYTE ) {
size += header->num_vertexes * 4 * sizeof(byte); // blendWeights
} else if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
size += header->num_vertexes * 4 * sizeof(float); // blendWeights
}
if ( vertexArrayFormat[IQM_COLOR] != -1 ) {
size += header->num_vertexes * 4 * sizeof(byte); // colors
}
}
if( header->num_joints ) {
size += joint_names; // joint names
size += header->num_joints * sizeof(int); // joint parents
size += header->num_joints * 12 * sizeof( float ); // joint mats
}
if( header->num_poses ) {
size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
}
if( header->ofs_bounds ) {
size += header->num_frames * 6 * sizeof(float); // model bounds
}
mod->type = MOD_IQM;
@ -487,45 +534,186 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
mod->modelData = iqmData;
// fill header
iqmData->num_vertexes = header->num_vertexes;
iqmData->num_triangles = header->num_triangles;
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 = blendWeightsType;
iqmData->surfaces = (srfIQModel_t *)(iqmData + 1);
iqmData->jointMats = (float *) (iqmData->surfaces + iqmData->num_surfaces);
iqmData->poseMats = iqmData->jointMats + 12 * header->num_joints;
if(header->ofs_bounds)
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_BLENDINDEXES] != -1 ) {
iqmData->blendIndexes = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // blendIndexes
}
if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_UBYTE ) {
iqmData->blendWeights.b = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // blendWeights
} else if( vertexArrayFormat[IQM_BLENDWEIGHTS] == IQM_FLOAT ) {
iqmData->blendWeights.f = (float*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(float); // blendWeights
}
if ( vertexArrayFormat[IQM_COLOR] != -1 ) {
iqmData->colors = (byte*)dataPtr;
dataPtr += header->num_vertexes * 4 * sizeof(byte); // colors
}
}
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->jointMats = (float*)dataPtr;
dataPtr += header->num_joints * 12 * sizeof( float ); // joint mats
}
if( header->num_poses ) {
iqmData->poseMats = (float*)dataPtr;
dataPtr += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
}
if( header->ofs_bounds ) {
iqmData->bounds = (float*)dataPtr;
dataPtr += header->num_frames * 6 * sizeof(float); // model bounds
}
if( header->num_meshes )
{
iqmData->bounds = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
iqmData->positions = iqmData->bounds + 6 * header->num_frames;
// 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;
}
else
iqmData->positions = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
iqmData->texcoords = iqmData->positions + 3 * header->num_vertexes;
iqmData->normals = iqmData->texcoords + 2 * header->num_vertexes;
iqmData->tangents = iqmData->normals + 3 * header->num_vertexes;
iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes);
if(blendWeightsType == IQM_FLOAT) {
iqmData->blendWeights.f = (float *)(iqmData->blendIndexes + 4 * header->num_vertexes);
iqmData->colors = (byte *)(iqmData->blendWeights.f + 4 * header->num_vertexes);
// 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:
if( vertexarray->format == IQM_INT ) {
int *data = (int*)((byte*)header + vertexarray->offset);
for ( j = 0; j < n; j++ ) {
iqmData->blendIndexes[j] = (byte)data[j];
}
} else {
iqmData->blendWeights.b = iqmData->blendIndexes + 4 * header->num_vertexes;
iqmData->colors = iqmData->blendWeights.b + 4 * header->num_vertexes;
Com_Memcpy( iqmData->blendIndexes,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_BLENDWEIGHTS:
if( vertexarray->format == IQM_FLOAT ) {
Com_Memcpy( iqmData->blendWeights.f,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
} else {
Com_Memcpy( iqmData->blendWeights.b,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_COLOR:
Com_Memcpy( iqmData->colors,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
break;
}
}
}
iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes);
iqmData->triangles = iqmData->jointParents + header->num_joints;
iqmData->names = (char *)(iqmData->triangles + 3 * header->num_triangles);
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;
}
if ( header->num_joints == 0 )
iqmData->jointMats = NULL;
if ( header->num_poses == 0 )
iqmData->poseMats = NULL;
// 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 joint matrices and their inverses
// joint inverses are needed only until the pose matrices are calculated
@ -553,7 +741,10 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
matInv += 12;
}
}
}
if( header->num_poses )
{
// calculate pose matrices
framedata = (unsigned short *)((byte *)header + header->ofs_frames);
mat = iqmData->poseMats;
@ -612,109 +803,6 @@ qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_na
mat += 12;
}
}
// 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 vertexarrays and indexes
vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
int n;
// 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:
if( blendIndexesType == IQM_INT ) {
int *data = (int*)((byte*)header + vertexarray->offset);
for ( j = 0; j < n; j++ ) {
iqmData->blendIndexes[j] = (byte)data[j];
}
} else {
Com_Memcpy( iqmData->blendIndexes,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_BLENDWEIGHTS:
if( blendWeightsType == IQM_FLOAT ) {
Com_Memcpy( iqmData->blendWeights.f,
(byte *)header + vertexarray->offset,
n * sizeof(float) );
} else {
Com_Memcpy( iqmData->blendWeights.b,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
}
break;
case IQM_COLOR:
Com_Memcpy( iqmData->colors,
(byte *)header + vertexarray->offset,
n * sizeof(byte) );
break;
}
}
// 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;
}
// 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 joint names
str = iqmData->names;
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 model bounds
@ -1161,7 +1249,7 @@ int R_IQMLerpTag( orientation_t *tag, iqmData_t *data,
float frac, const char *tagName ) {
float jointMats[IQM_MAX_JOINTS * 12];
int joint;
char *names = data->names;
char *names = data->jointNames;
// get joint number by reading the joint names
for( joint = 0; joint < data->num_joints; joint++ ) {