Beispiel #1
0
/*
==============
Tess_SurfaceVBOMD5Mesh
==============
*/
static void Tess_SurfaceVBOMD5Mesh( srfVBOMD5Mesh_t *srf )
{
	int        i;
	md5Model_t *model;

	GLimp_LogComment( "--- Tess_SurfaceVBOMD5Mesh ---\n" );

	if ( !srf->vbo || !srf->ibo )
	{
		return;
	}

	Tess_EndBegin();

	R_BindVBO( srf->vbo );
	R_BindIBO( srf->ibo );

	tess.numIndexes = srf->numIndexes;
	tess.numVertexes = srf->numVerts;

	model = srf->md5Model;

	tess.vboVertexSkinning = true;
	tess.numBones = srf->numBoneRemap;

	for ( i = 0; i < srf->numBoneRemap; i++ )
	{
		refBone_t *bone = &backEnd.currentEntity->e.skeleton.bones[ srf->boneRemapInverse[ i ] ];

		if ( backEnd.currentEntity->e.skeleton.type == SK_ABSOLUTE )
		{
			TransInitRotationQuat( model->bones[ srf->boneRemapInverse[ i ] ].rotation, &tess.bones[ i ] );
			TransAddTranslation( model->bones[ srf->boneRemapInverse[ i ] ].origin, &tess.bones[ i ] );
			TransInverse( &tess.bones[ i ], &tess.bones[ i ] );
			TransCombine( &tess.bones[ i ], &bone->t, &tess.bones[ i ] );
		} else {
			TransInit( &tess.bones[ i ] );
		}
		TransAddScale( backEnd.currentEntity->e.skeleton.scale, &tess.bones[ i ] );
		TransInsScale( model->internalScale, &tess.bones[ i ] );
	}

	Tess_End();
}
Beispiel #2
0
/*
==============
Tess_SurfaceMD5
==============
*/
static void Tess_SurfaceMD5( md5Surface_t *srf )
{
	int             j;
	int             numIndexes = 0;
	int             numVertexes;
	md5Model_t      *model;
	md5Vertex_t     *v;
	srfTriangle_t   *tri;

	GLimp_LogComment( "--- Tess_SurfaceMD5 ---\n" );

	Tess_CheckOverflow( srf->numVerts, srf->numTriangles * 3 );

	model = srf->model;

	numIndexes = srf->numTriangles * 3;

    tri = srf->triangles;
	for (unsigned i = 0; i < srf->numTriangles; i++, tri++ )
	{
		tess.indexes[ tess.numIndexes + i * 3 + 0 ] = tess.numVertexes + tri->indexes[ 0 ];
		tess.indexes[ tess.numIndexes + i * 3 + 1 ] = tess.numVertexes + tri->indexes[ 1 ];
		tess.indexes[ tess.numIndexes + i * 3 + 2 ] = tess.numVertexes + tri->indexes[ 2 ];
	}

	tess.attribsSet |= ATTR_POSITION | ATTR_TEXCOORD;

	if ( tess.skipTangentSpaces )
	{
		// convert bones back to matrices
		for (unsigned i = 0; i < model->numBones; i++ )
		{
			if ( backEnd.currentEntity->e.skeleton.type == SK_ABSOLUTE )
			{
				refBone_t *bone = &backEnd.currentEntity->e.skeleton.bones[ i ];

				TransInitRotationQuat( model->bones[ i ].rotation, &bones[ i ] );
				TransAddTranslation( model->bones[ i ].origin, &bones[ i ] );
				TransInverse( &bones[ i ], &bones[ i ] );
				TransCombine( &bones[ i ], &bone->t, &bones[ i ] );
				TransAddScale( backEnd.currentEntity->e.skeleton.scale, &bones[ i ] );
			}
			else
			{
				TransInitRotationQuat( model->bones[ i ].rotation, &bones[i] );
				TransAddTranslation( model->bones[ i ].origin, &bones[ i ] );
			}
			TransInsScale( model->internalScale, &bones[ i ] );
		}

		// deform the vertices by the lerped bones
		numVertexes = srf->numVerts;

		for ( j = 0, v = srf->verts; j < numVertexes; j++, v++ )
		{
			vec3_t tmp;

			VectorClear( tess.verts[ tess.numVertexes + j ].xyz );
			for (unsigned k = 0; k < v->numWeights; k++ ) {
				TransformPoint( &bones[ v->boneIndexes[ k ] ],
						v->position, tmp );
				VectorMA( tess.verts[ tess.numVertexes + j ].xyz,
					  v->boneWeights[ k ], tmp,
					  tess.verts[ tess.numVertexes + j ].xyz );

			}

			tess.verts[ tess.numVertexes + j ].texCoords[ 0 ] = floatToHalf( v->texCoords[ 0 ] );
			tess.verts[ tess.numVertexes + j ].texCoords[ 1 ] = floatToHalf( v->texCoords[ 1 ] );
		}
	}
	else
	{
		tess.attribsSet |= ATTR_QTANGENT;

		// convert bones back to matrices
		for (unsigned i = 0; i < model->numBones; i++ )
		{
			if ( backEnd.currentEntity->e.skeleton.type == SK_ABSOLUTE )
			{
				refBone_t *bone = &backEnd.currentEntity->e.skeleton.bones[ i ];
				TransInitRotationQuat( model->bones[ i ].rotation, &bones[ i ] );
				TransAddTranslation( model->bones[ i ].origin, &bones[ i ] );
				TransInverse( &bones[ i ], &bones[ i ] );

				TransCombine( &bones[ i ], &bone->t, &bones[ i ] );
				TransAddScale( backEnd.currentEntity->e.skeleton.scale, &bones[ i ] );
			}
			else
			{
				TransInitScale( backEnd.currentEntity->e.skeleton.scale, &bones[ i ] );
			}
			TransInsScale( model->internalScale, &bones[ i ] );
		}

		// deform the vertices by the lerped bones
		numVertexes = srf->numVerts;

		for ( j = 0, v = srf->verts; j < numVertexes; j++, v++ )
		{
			vec3_t tangent, binormal, normal, tmp;

			VectorClear( tess.verts[ tess.numVertexes + j ].xyz );
			VectorClear( normal );
			VectorClear( binormal );
			VectorClear( tangent );

			for(unsigned k = 0; k < v->numWeights; k++ ) {
				TransformPoint( &bones[ v->boneIndexes[ k ] ],
						v->position, tmp );
				VectorMA( tess.verts[ tess.numVertexes + j ].xyz,
					  v->boneWeights[ k ], tmp,
					  tess.verts[ tess.numVertexes + j ].xyz );

				TransformNormalVector( &bones[ v->boneIndexes[ k ] ],
						       v->normal, tmp );
				VectorMA( normal, v->boneWeights[ k ], tmp, normal );

				TransformNormalVector( &bones[ v->boneIndexes[ k ] ],
						       v->tangent, tmp );
				VectorMA( tangent, v->boneWeights[ k ], tmp, tangent );

				TransformNormalVector( &bones[ v->boneIndexes[ k ] ],
						       v->binormal, tmp );
				VectorMA( binormal, v->boneWeights[ k ], tmp, binormal );
			}
			VectorNormalize( normal );
			VectorNormalize( tangent );
			VectorNormalize( binormal );

			R_TBNtoQtangents( tangent, binormal, normal, tess.verts[ tess.numVertexes + j ].qtangents );

			tess.verts[ tess.numVertexes + j ].texCoords[ 0 ] = floatToHalf( v->texCoords[ 0 ] );
			tess.verts[ tess.numVertexes + j ].texCoords[ 1 ] = floatToHalf( v->texCoords[ 1 ] );
		}
	}

	tess.numIndexes += numIndexes;
	tess.numVertexes += numVertexes;
}
Beispiel #3
0
/*
=================
Tess_SurfaceIQM

Compute vertices for this model surface
=================
*/
void Tess_SurfaceIQM( srfIQModel_t *surf ) {
	IQModel_t       *model = surf->data;
	int             i, j;
	int             offset = tess.numVertexes - surf->first_vertex;

	GLimp_LogComment( "--- RB_SurfaceIQM ---\n" );

	Tess_CheckOverflow( surf->num_vertexes, surf->num_triangles * 3 );

	// compute bones
	for ( i = 0; i < model->num_joints; i++ )
	{

		if ( backEnd.currentEntity->e.skeleton.type == SK_ABSOLUTE )
		{
			refBone_t *bone = &backEnd.currentEntity->e.skeleton.bones[ i ];

			TransInverse( &model->joints[ i ], &bones[ i ] );
			TransCombine( &bones[ i ], &bone->t, &bones[ i ] );
		}
		else
		{
			TransInit( &bones[ i ] );
		}
		TransAddScale( backEnd.currentEntity->e.skeleton.scale, &bones[ i ] );
		TransInsScale( model->internalScale, &bones[ i ] );
	}

	if( surf->vbo && surf->ibo ) {
		if( model->num_joints > 0 ) {
			Com_Memcpy( tess.bones, bones, model->num_joints * sizeof(transform_t) );
			tess.numBones = model->num_joints;
		} else {
			TransInitScale( model->internalScale * backEnd.currentEntity->e.skeleton.scale, &tess.bones[ 0 ] );
			tess.numBones = 1;
		}
		R_BindVBO( surf->vbo );
		R_BindIBO( surf->ibo );
		tess.vboVertexSkinning = true;

		tess.multiDrawIndexes[ tess.multiDrawPrimitives ] = ((glIndex_t *)nullptr) + surf->first_triangle * 3;
		tess.multiDrawCounts[ tess.multiDrawPrimitives ] = surf->num_triangles * 3;
		tess.multiDrawPrimitives++;

		Tess_End();
		return;
	}

	for ( i = 0; i < surf->num_triangles; i++ )
	{
		tess.indexes[ tess.numIndexes + i * 3 + 0 ] = offset + model->triangles[ 3 * ( surf->first_triangle + i ) + 0 ];
		tess.indexes[ tess.numIndexes + i * 3 + 1 ] = offset + model->triangles[ 3 * ( surf->first_triangle + i ) + 1 ];
		tess.indexes[ tess.numIndexes + i * 3 + 2 ] = offset + model->triangles[ 3 * ( surf->first_triangle + i ) + 2 ];
	}

	tess.attribsSet |= ATTR_POSITION | ATTR_TEXCOORD | ATTR_QTANGENT;

	if( model->num_joints > 0 && model->blendWeights && model->blendIndexes ) {
		// deform the vertices by the lerped bones
		for ( i = 0; i < surf->num_vertexes; i++ )
		{
			int    idxIn = surf->first_vertex + i;
			int    idxOut = tess.numVertexes + i;
			const float weightFactor = 1.0f / 255.0f;
			vec3_t tangent, binormal, normal, tmp;

			if( model->blendWeights[ 4 * idxIn + 0 ] == 0 &&
			    model->blendWeights[ 4 * idxIn + 1 ] == 0 &&
			    model->blendWeights[ 4 * idxIn + 2 ] == 0 &&
			    model->blendWeights[ 4 * idxIn + 3 ] == 0 )
				model->blendWeights[ 4 * idxIn + 0 ] = 255;

			VectorClear( tess.verts[ idxOut ].xyz );
			VectorClear( normal );
			VectorClear( tangent );
			VectorClear( binormal );
			for ( j = 0; j < 4; j++ ) {
				int bone = model->blendIndexes[ 4 * idxIn + j ];
				float weight = weightFactor * model->blendWeights[ 4 * idxIn + j ];

				TransformPoint( &bones[ bone ],
						&model->positions[ 3 * idxIn ], tmp );
				VectorMA( tess.verts[ idxOut ].xyz, weight, tmp,
					  tess.verts[ idxOut ].xyz );

				TransformNormalVector( &bones[ bone ],
						       &model->normals[ 3 * idxIn ], tmp );
				VectorMA( normal, weight, tmp, normal );
				TransformNormalVector( &bones[ bone ],
						       &model->tangents[ 3 * idxIn ], tmp );
				VectorMA( tangent, weight, tmp, tangent );
				TransformNormalVector( &bones[ bone ],
						       &model->bitangents[ 3 * idxIn ], tmp );
				VectorMA( binormal, weight, tmp, binormal );
			}
			VectorNormalize( normal );
			VectorNormalize( tangent );
			VectorNormalize( binormal );

			R_TBNtoQtangents( tangent, binormal, normal, tess.verts[ idxOut ].qtangents );

			tess.verts[ idxOut ].texCoords[ 0 ] = model->texcoords[ 2 * idxIn + 0 ];
			tess.verts[ idxOut ].texCoords[ 1 ] = model->texcoords[ 2 * idxIn + 1 ];
		}
	} else {
		for ( i = 0; i < surf->num_vertexes; i++ )
		{
			int    idxIn = surf->first_vertex + i;
			int    idxOut = tess.numVertexes + i;
			float  scale = model->internalScale * backEnd.currentEntity->e.skeleton.scale;

			VectorScale( &model->positions[ 3 * idxIn ], scale, tess.verts[ idxOut ].xyz );
			R_TBNtoQtangents( &model->tangents[ 3 * idxIn ],
					  &model->bitangents[ 3 * idxIn ],
					  &model->normals[ 3 * idxIn ],
					  tess.verts[ idxOut ].qtangents );

			tess.verts[ idxOut ].texCoords[ 0 ] = model->texcoords[ 2 * idxIn + 0 ];
			tess.verts[ idxOut ].texCoords[ 1 ] = model->texcoords[ 2 * idxIn + 1 ];
		}
	}

	tess.numIndexes  += 3 * surf->num_triangles;
	tess.numVertexes += surf->num_vertexes;
}
/*
=================
R_LoadIQModel

Load an IQM model and compute the joint matrices for every frame.
=================
*/
bool R_LoadIQModel( 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;
	iqmAnim_t		*anim;
	unsigned short		*framedata;
	char			*str, *name;
	int		len;
	transform_t		*trans, *poses;
	float			*bounds;
	size_t			size, len_names;
	IQModel_t		*IQModel;
	IQAnim_t		*IQAnim;
	srfIQModel_t		*surface;
	vboData_t               vboData;
	float                   *weightbuf;
	int                     *indexbuf;
	i16vec4_t               *qtangentbuf;
	VBO_t                   *vbo;
	IBO_t                   *ibo;
	void                    *ptr;
	u8vec4_t                *weights;

	if( !LoadIQMFile( buffer, filesize, mod_name, &len_names ) ) {
		return false;
	}

	header = (iqmHeader_t *)buffer;

	// compute required space
	size = sizeof(IQModel_t);
	size += header->num_meshes * sizeof( srfIQModel_t );
	size += header->num_anims * sizeof( IQAnim_t );
	size += header->num_joints * sizeof( transform_t );
	size = PAD( size, 16 );
	size += header->num_joints * header->num_frames * sizeof( transform_t );
	if(header->ofs_bounds)
		size += header->num_frames * 6 * sizeof(float);	// model bounds
	size += header->num_vertexes * 3 * sizeof(float);	// positions
	size += header->num_vertexes * 3 * sizeof(float);	// normals
	size += header->num_vertexes * 3 * sizeof(float);	// tangents
	size += header->num_vertexes * 3 * sizeof(float);	// bitangents
	size += header->num_vertexes * 2 * sizeof(int16_t);	// texcoords
	size += header->num_vertexes * 4 * sizeof(byte);	// blendIndexes
	size += header->num_vertexes * 4 * sizeof(byte);	// blendWeights
	size += header->num_vertexes * 4 * sizeof(byte);	// colors
	size += header->num_triangles * 3 * sizeof(int);	// triangles
	size += header->num_joints * sizeof(int);		// parents
	size += len_names;					// joint and anim names

	IQModel = (IQModel_t *)ri.Hunk_Alloc( size, ha_pref::h_low );
	mod->type = modtype_t::MOD_IQM;
	mod->iqm = IQModel;
	ptr = IQModel + 1;

	// fill header and setup pointers
	IQModel->num_vertexes = header->num_vertexes;
	IQModel->num_triangles = header->num_triangles;
	IQModel->num_frames   = header->num_frames;
	IQModel->num_surfaces = header->num_meshes;
	IQModel->num_joints   = header->num_joints;
	IQModel->num_anims    = header->num_anims;

	IQModel->surfaces = (srfIQModel_t *)ptr;
	ptr = IQModel->surfaces + header->num_meshes;

	if( header->ofs_anims ) {
		IQModel->anims = (IQAnim_t *)ptr;
		ptr = IQModel->anims + header->num_anims;
	} else {
		IQModel->anims = nullptr;
	}

	IQModel->joints = (transform_t *)PADP(ptr, 16);
	ptr = IQModel->joints + header->num_joints;

	if( header->ofs_poses ) {
		poses = (transform_t *)ptr;
		ptr = poses + header->num_poses * header->num_frames;
	} else {
		poses = nullptr;
	}

	if( header->ofs_bounds ) {
		bounds = (float *)ptr;
		ptr = bounds + 6 * header->num_frames;
	} else {
		bounds = nullptr;
	}

	IQModel->positions = (float *)ptr;
	ptr = IQModel->positions + 3 * header->num_vertexes;

	IQModel->normals = (float *)ptr;
	ptr = IQModel->normals + 3 * header->num_vertexes;

	IQModel->tangents = (float *)ptr;
	ptr = IQModel->tangents + 3 * header->num_vertexes;

	IQModel->bitangents = (float *)ptr;
	ptr = IQModel->bitangents + 3 * header->num_vertexes;

	IQModel->texcoords = (int16_t *)ptr;
	ptr = IQModel->texcoords + 2 * header->num_vertexes;

	IQModel->blendIndexes = (byte *)ptr;
	ptr = IQModel->blendIndexes + 4 * header->num_vertexes;

	IQModel->blendWeights = (byte *)ptr;
	ptr = IQModel->blendWeights + 4 * header->num_vertexes;

	IQModel->colors = (byte *)ptr;
	ptr = IQModel->colors + 4 * header->num_vertexes;

	IQModel->jointParents = (int *)ptr;
	ptr = IQModel->jointParents + header->num_joints;

	IQModel->triangles = (int *)ptr;
	ptr = IQModel->triangles + 3 * header->num_triangles;

	str                   = (char *)ptr;
	IQModel->jointNames   = str;

	// copy joint names
	joint = ( iqmJoint_t* )IQMPtr( header, header->ofs_joints );
	for(unsigned i = 0; i < header->num_joints; i++, joint++ ) {
		name = ( char* )IQMPtr( header, header->ofs_text + joint->name );
		len = strlen( name ) + 1;
		Com_Memcpy( str, name, len );
		str += len;
	}

	// setup animations
	IQAnim = IQModel->anims;
	anim = ( iqmAnim_t* )IQMPtr( header, header->ofs_anims );
	for(int i = 0; i < IQModel->num_anims; i++, IQAnim++, anim++ ) {
		IQAnim->num_frames   = anim->num_frames;
		IQAnim->framerate    = anim->framerate;
		IQAnim->num_joints   = header->num_joints;
		IQAnim->flags        = anim->flags;
		IQAnim->jointParents = IQModel->jointParents;
		if( poses ) {
			IQAnim->poses    = poses + anim->first_frame * header->num_poses;
		} else {
			IQAnim->poses    = nullptr;
		}
		if( bounds ) {
			IQAnim->bounds   = bounds + anim->first_frame * 6;
		} else {
			IQAnim->bounds    = nullptr;
		}
		IQAnim->name         = str;
		IQAnim->jointNames   = IQModel->jointNames;

		name = ( char* )IQMPtr( header, header->ofs_text + anim->name );
		len = strlen( name ) + 1;
		Com_Memcpy( str, name, len );
		str += len;
	}

	// calculate joint transforms
	trans = IQModel->joints;
	joint = ( iqmJoint_t* )IQMPtr( header, header->ofs_joints );
	for(unsigned i = 0; i < header->num_joints; i++, joint++, trans++ ) {
		if( joint->parent >= (int) i ) {
			Log::Warn("R_LoadIQModel: file %s contains an invalid parent joint number.",
				  mod_name );
			return false;
		}

		TransInitRotationQuat( joint->rotate, trans );
		TransAddScale( joint->scale[0], trans );
		TransAddTranslation( joint->translate, trans );

		if( joint->parent >= 0 ) {
			TransCombine( trans, &IQModel->joints[ joint->parent ],
				      trans );
		}

		IQModel->jointParents[i] = joint->parent;
	}

	// calculate pose transforms
	framedata = ( short unsigned int* )IQMPtr( header, header->ofs_frames );
	trans = poses;
	for(unsigned i = 0; i < header->num_frames; i++ ) {
		pose = ( iqmPose_t* )IQMPtr( header, header->ofs_poses );
		for(unsigned j = 0; j < header->num_poses; j++, pose++, trans++ ) {
			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];

			if( scale[0] < 0.0f ||
			    (int)( scale[0] - scale[1] ) ||
			    (int)( scale[1] - scale[2] ) ) {
				Log::Warn("R_LoadIQM: file %s contains an invalid scale.", mod_name );
				return false;
			    }

			// construct transformation
			TransInitRotationQuat( rotate, trans );
			TransAddScale( scale[0], trans );
			TransAddTranslation( translate, trans );
		}
	}

	// copy vertexarrays and indexes
	vertexarray = ( iqmVertexArray_t* )IQMPtr( header, header->ofs_vertexarrays );
	for(unsigned 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:
			ClearBounds( IQModel->bounds[ 0 ], IQModel->bounds[ 1 ] );
			Com_Memcpy( IQModel->positions,
				    IQMPtr( header, vertexarray->offset ),
				    n * sizeof(float) );
			for( int j = 0; j < n; j += vertexarray->size ) {
				AddPointToBounds( &IQModel->positions[ j ],
						  IQModel->bounds[ 0 ],
						  IQModel->bounds[ 1 ] );
			}
			IQModel->internalScale = BoundsMaxExtent( IQModel->bounds[ 0 ], IQModel->bounds[ 1 ] );
			if( IQModel->internalScale > 0.0f ) {
				float inverseScale = 1.0f / IQModel->internalScale;
				for( int j = 0; j < n; j += vertexarray->size ) {
					VectorScale( &IQModel->positions[ j ],
						     inverseScale,
						     &IQModel->positions[ j ] );
				}
			}

			break;
		case IQM_NORMAL:
			Com_Memcpy( IQModel->normals,
				    IQMPtr( header, vertexarray->offset ),
				    n * sizeof(float) );
			break;
		case IQM_TANGENT:
			BuildTangents( header->num_vertexes,
				       ( float* )IQMPtr( header, vertexarray->offset ),
				       IQModel->normals, IQModel->tangents,
				       IQModel->bitangents );
			break;
		case IQM_TEXCOORD:
			for( int j = 0; j < n; j++ ) {
				IQModel->texcoords[ j ] = floatToHalf( ((float *)IQMPtr( header, vertexarray->offset ))[ j ] );
			}
			break;
		case IQM_BLENDINDEXES:
			Com_Memcpy( IQModel->blendIndexes,
				    IQMPtr( header, vertexarray->offset ),
				    n * sizeof(byte) );
			break;
		case IQM_BLENDWEIGHTS:
			weights = (u8vec4_t *)IQMPtr( header, vertexarray->offset );
			for(unsigned j = 0; j < header->num_vertexes; j++ ) {
				IQModel->blendWeights[ 4 * j + 0 ] = 255 - weights[ j ][ 1 ] - weights[ j ][ 2 ] - weights[ j ][ 3 ];
				IQModel->blendWeights[ 4 * j + 1 ] = weights[ j ][ 1 ];
				IQModel->blendWeights[ 4 * j + 2 ] = weights[ j ][ 2 ];
				IQModel->blendWeights[ 4 * j + 3 ] = weights[ j ][ 3 ];
			}
			break;
		case IQM_COLOR:
			Com_Memcpy( IQModel->colors,
				    IQMPtr( header, vertexarray->offset ),
				    n * sizeof(byte) );
			break;
		}
	}

	// copy triangles
	triangle = ( iqmTriangle_t* )IQMPtr( header, header->ofs_triangles );
	for(unsigned i = 0; i < header->num_triangles; i++, triangle++ ) {
		IQModel->triangles[3*i+0] = triangle->vertex[0];
		IQModel->triangles[3*i+1] = triangle->vertex[1];
		IQModel->triangles[3*i+2] = triangle->vertex[2];
	}

	// convert data where necessary and create VBO
	if( r_vboModels->integer && glConfig2.vboVertexSkinningAvailable
	    && IQModel->num_joints <= glConfig2.maxVertexSkinningBones ) {

		if( IQModel->blendIndexes ) {
			indexbuf = (int *)ri.Hunk_AllocateTempMemory( sizeof(int[4]) * IQModel->num_vertexes );
			for(int i = 0; i < IQModel->num_vertexes; i++ ) {
				indexbuf[ 4 * i + 0 ] = IQModel->blendIndexes[ 4 * i + 0 ];
				indexbuf[ 4 * i + 1 ] = IQModel->blendIndexes[ 4 * i + 1 ];
				indexbuf[ 4 * i + 2 ] = IQModel->blendIndexes[ 4 * i + 2 ];
				indexbuf[ 4 * i + 3 ] = IQModel->blendIndexes[ 4 * i + 3 ];
			}
		} else {
			indexbuf = nullptr;
		}
		if( IQModel->blendWeights ) {
			const float weightscale = 1.0f / 255.0f;

			weightbuf = (float *)ri.Hunk_AllocateTempMemory( sizeof(vec4_t) * IQModel->num_vertexes );
			for(int i = 0; i < IQModel->num_vertexes; i++ ) {
				if( IQModel->blendWeights[ 4 * i + 0 ] == 0 &&
				    IQModel->blendWeights[ 4 * i + 1 ] == 0 &&
				    IQModel->blendWeights[ 4 * i + 2 ] == 0 &&
				    IQModel->blendWeights[ 4 * i + 3 ] == 0 )
					IQModel->blendWeights[ 4 * i + 0 ] = 255;

				weightbuf[ 4 * i + 0 ] = weightscale * IQModel->blendWeights[ 4 * i + 0 ];
				weightbuf[ 4 * i + 1 ] = weightscale * IQModel->blendWeights[ 4 * i + 1 ];
				weightbuf[ 4 * i + 2 ] = weightscale * IQModel->blendWeights[ 4 * i + 2 ];
				weightbuf[ 4 * i + 3 ] = weightscale * IQModel->blendWeights[ 4 * i + 3 ];
			}
		} else {
			weightbuf = nullptr;
		}

		qtangentbuf = (i16vec4_t *)ri.Hunk_AllocateTempMemory( sizeof( i16vec4_t ) * IQModel->num_vertexes );

		for(int i = 0; i < IQModel->num_vertexes; i++ ) {
			R_TBNtoQtangents( &IQModel->tangents[ 3 * i ],
					  &IQModel->bitangents[ 3 * i ],
					  &IQModel->normals[ 3 * i ],
					  qtangentbuf[ i ] );
		}

		vboData.xyz = (vec3_t *)IQModel->positions;
		vboData.qtangent = qtangentbuf;
		vboData.numFrames = 0;
		vboData.color = (u8vec4_t *)IQModel->colors;
		vboData.st = (i16vec2_t *)IQModel->texcoords;
		vboData.noLightCoords = true;
		vboData.boneIndexes = (int (*)[4])indexbuf;
		vboData.boneWeights = (vec4_t *)weightbuf;
		vboData.numVerts = IQModel->num_vertexes;


		vbo = R_CreateStaticVBO( "IQM surface VBO", vboData,
					 vboLayout_t::VBO_LAYOUT_SKELETAL );

		if( qtangentbuf ) {
			ri.Hunk_FreeTempMemory( qtangentbuf );
		}
		if( weightbuf ) {
			ri.Hunk_FreeTempMemory( weightbuf );
		}
		if( indexbuf ) {
			ri.Hunk_FreeTempMemory( indexbuf );
		}

		// create IBO
		ibo = R_CreateStaticIBO( "IQM surface IBO", ( glIndex_t* )IQModel->triangles, IQModel->num_triangles * 3 );
	} else {
		vbo = nullptr;
		ibo = nullptr;
	}

	// register shaders
	// overwrite the material offset with the shader index
	mesh = ( iqmMesh_t* )IQMPtr( header, header->ofs_meshes );
	surface = IQModel->surfaces;
	for(unsigned i = 0; i < header->num_meshes; i++, mesh++, surface++ ) {
		surface->surfaceType = surfaceType_t::SF_IQM;

		if( mesh->name ) {
			surface->name = str;
			name = ( char* )IQMPtr( header, header->ofs_text + mesh->name );
			len = strlen( name ) + 1;
			Com_Memcpy( str, name, len );
			str += len;
		} else {
			surface->name = nullptr;
		}

		surface->shader = R_FindShader( ( char* )IQMPtr(header, header->ofs_text + mesh->material),
						shaderType_t::SHADER_3D_DYNAMIC, RSF_DEFAULT );
		if( surface->shader->defaultShader )
			surface->shader = tr.defaultShader;
		surface->data = IQModel;
		surface->first_vertex = mesh->first_vertex;
		surface->num_vertexes = mesh->num_vertexes;
		surface->first_triangle = mesh->first_triangle;
		surface->num_triangles = mesh->num_triangles;
		surface->vbo = vbo;
		surface->ibo = ibo;
	}

	// copy model bounds
	if(header->ofs_bounds)
	{
		iqmBounds_t *ptr = ( iqmBounds_t* )IQMPtr( header, header->ofs_bounds );
		for(unsigned i = 0; i < header->num_frames; i++)
		{
			VectorCopy( ptr->bbmin, bounds );
			bounds += 3;
			VectorCopy( ptr->bbmax, bounds );
			bounds += 3;

			ptr++;
		}
	}

	// register animations
	IQAnim = IQModel->anims;
	if( header->num_anims == 1 ) {
		RE_RegisterAnimationIQM( mod_name, IQAnim );
	}
	for(unsigned i = 0; i < header->num_anims; i++, IQAnim++ ) {
		char name[ MAX_QPATH ];

		Com_sprintf( name, MAX_QPATH, "%s:%s", mod_name, IQAnim->name );
		RE_RegisterAnimationIQM( name, IQAnim );
	}

	// build VBO

	return true;
}