Esempio n. 1
0
/*
* Mod_LoadSkeletalModel
*/
void Mod_LoadSkeletalModel( model_t *mod, const model_t *parent, void *buffer, bspFormatDesc_t *unused )
{
	unsigned int i, j, k;
	size_t filesize;
	qbyte *pbase;
	size_t memsize;
	qbyte *pmem;
	iqmheader_t *header;
	char *texts;
	iqmvertexarray_t *va;
	iqmjoint_t *joints;
	bonepose_t *baseposes;
	iqmpose_t *poses;
	unsigned short *framedata;
	const int *inelems;
	elem_t *outelems;
	iqmmesh_t *inmesh;
	iqmbounds_t *inbounds;
	float *vposition, *vtexcoord, *vnormal, *vtangent;
	qbyte *vblendindices_byte, *vblendweights_byte;
	int *vblendindexes_int;
	float *vblendweights_float;
	mskmodel_t *poutmodel;

	baseposes = NULL;
	header = ( iqmheader_t * )buffer;

	// check IQM magic
	if( memcmp( header->magic, "INTERQUAKEMODEL", 16 ) ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s is not an Inter-Quake Model\n", mod->name );
		goto error;
	}

	// check header version
	header->version = LittleLong( header->version );
	if( header->version != IQM_VERSION ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s has wrong type number (%i should be %i)\n", mod->name, header->version, IQM_VERSION );
		goto error;
	}

	// byteswap header
#define H_SWAP(s) (header->s = LittleLong( header->s ))
	H_SWAP( filesize );
	H_SWAP( flags );
	H_SWAP( num_text );
	H_SWAP( ofs_text );
	H_SWAP( num_meshes );
	H_SWAP( ofs_meshes );
	H_SWAP( num_vertexarrays );
	H_SWAP( num_vertexes );
	H_SWAP( ofs_vertexarrays );
	H_SWAP( num_triangles );
	H_SWAP( ofs_triangles );
	H_SWAP( ofs_adjacency );
	H_SWAP( num_joints );
	H_SWAP( ofs_joints );
	H_SWAP( num_poses );
	H_SWAP( ofs_poses );
	H_SWAP( num_anims );
	H_SWAP( ofs_anims );
	H_SWAP( num_frames );
	H_SWAP( num_framechannels );
	H_SWAP( ofs_frames );
	H_SWAP( ofs_bounds );
	H_SWAP( num_comment );
	H_SWAP( ofs_comment );
	H_SWAP( num_extensions );
	H_SWAP( ofs_extensions );
#undef H_SWAP

	if( header->num_triangles < 1 || header->num_vertexes < 3 || header->num_vertexarrays < 1 || header->num_meshes < 1 ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s has no geometry\n", mod->name );
		goto error;
	}
	if( header->num_frames < 1 || header->num_anims < 1 ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s has no animations\n", mod->name );
		goto error;
	}
	if( header->num_joints != header->num_poses ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s has an invalid number of poses: %i vs %i\n", mod->name, header->num_joints, header->num_poses );
		goto error;
	}
	if( !header->ofs_bounds ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s has no frame bounds\n", mod->name );
		goto error;
	}

	pbase = ( qbyte * )buffer;
	filesize = header->filesize;

	// check data offsets against the filesize
	if( header->ofs_text + header->num_text > filesize
		|| header->ofs_vertexarrays + header->num_vertexarrays * sizeof( iqmvertexarray_t ) > filesize
		|| header->ofs_joints + header->num_joints * sizeof( iqmjoint_t ) > filesize
		|| header->ofs_frames + header->num_frames * header->num_framechannels * sizeof( unsigned short ) > filesize
		|| header->ofs_triangles + header->num_triangles * sizeof( int[3] ) > filesize
		|| header->ofs_meshes + header->num_meshes * sizeof( iqmmesh_t ) > filesize
		|| header->ofs_bounds + header->num_frames * sizeof( iqmbounds_t ) > filesize
		) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s has invalid size or offset information\n", mod->name );
		goto error;
	}

	poutmodel = mod->extradata = Mod_Malloc( mod, sizeof( *poutmodel ) );


	// load text
	texts = Mod_Malloc( mod, header->num_text + 1 );
	if( header->ofs_text ) {
		memcpy( texts, (const char *)(pbase + header->ofs_text), header->num_text );
	}
	texts[header->ofs_text] = '\0';


	// load vertex arrays
	vposition = NULL;
	vtexcoord = NULL;
	vnormal = NULL;
	vtangent = NULL;
	vblendindices_byte = NULL;
	vblendindexes_int = NULL;
	vblendweights_byte = NULL;
	vblendweights_float = NULL;

	va = ( iqmvertexarray_t * )( pbase + header->ofs_vertexarrays );
	for( i = 0; i < header->num_vertexarrays; i++ ) {
		size_t vsize;

		va[i].type = LittleLong( va[i].type );
		va[i].flags = LittleLong( va[i].flags );
		va[i].format = LittleLong( va[i].format );
		va[i].size = LittleLong( va[i].size );
		va[i].offset = LittleLong( va[i].offset );

		vsize = header->num_vertexes*va[i].size;
		switch( va[i].format ) { 
			case IQM_FLOAT:
				vsize *= sizeof( float );
				break;
			case IQM_INT:
			case IQM_UINT:
				vsize *= sizeof( int );
				break;
			case IQM_BYTE:
			case IQM_UBYTE:
				vsize *= sizeof( unsigned char );
				break;
			default:
				continue;
		}

		if( va[i].offset + vsize > filesize ) {
			continue;
		}

		switch( va[i].type ) {
			case IQM_POSITION:
				if( va[i].format == IQM_FLOAT && va[i].size == 3 ) {
					vposition = ( float * )( pbase + va[i].offset );
				}
				break;
			case IQM_TEXCOORD:
				if( va[i].format == IQM_FLOAT && va[i].size == 2 ) {
					vtexcoord = ( float * )( pbase + va[i].offset );
				}
				break;
			case IQM_NORMAL:
				if( va[i].format == IQM_FLOAT && va[i].size == 3 ) {
					vnormal = ( float * )( pbase + va[i].offset );
				}
				break;
			case IQM_TANGENT:
				if( va[i].format == IQM_FLOAT && va[i].size == 4 ) {
					vtangent = ( float * )( pbase + va[i].offset );
				}
				break;
			case IQM_BLENDINDEXES:
				if( va[i].size != SKM_MAX_WEIGHTS )
					break;
				if( va[i].format == IQM_BYTE || va[i].format == IQM_UBYTE ) {
					vblendindices_byte = ( qbyte * )( pbase + va[i].offset );
				}
				else if( va[i].format == IQM_INT || va[i].format == IQM_UINT ) {
					vblendindexes_int = ( int * )( pbase + va[i].offset );
				}
				break;
			case IQM_BLENDWEIGHTS:
				if( va[i].size != SKM_MAX_WEIGHTS )
					break;
				if( va[i].format == IQM_UBYTE ) {
					vblendweights_byte = ( qbyte * )( pbase + va[i].offset );
				}
				else if( va[i].format == IQM_FLOAT ) {
					vblendweights_float = ( float * )( pbase + va[i].offset );
				}
				break;
			default:
				break;
		}
	}

	if( !vposition || !vtexcoord 
		|| !(vblendindices_byte || vblendindexes_int) 
		|| !(vblendweights_byte || vblendweights_float) ) {
		ri.Com_Printf( S_COLOR_RED "ERROR: %s is missing vertex array data\n", mod->name );
		goto error;
	}

	// load joints
	memsize = 0;
	memsize += sizeof( bonepose_t ) * header->num_joints;
	pmem = Mod_Malloc( mod, memsize );

	baseposes = ( void * )pmem; pmem += sizeof( *baseposes );

	memsize = 0;
	memsize += sizeof( mskbone_t ) * header->num_joints;
	memsize += sizeof( bonepose_t ) * header->num_joints;
	pmem = Mod_Malloc( mod, memsize );

	poutmodel->numbones = header->num_joints;
	poutmodel->bones = ( void * )pmem; pmem += sizeof( *poutmodel->bones ) * poutmodel->numbones;
	poutmodel->invbaseposes = ( void * )pmem; pmem += sizeof( *poutmodel->invbaseposes ) * poutmodel->numbones;

	joints = ( iqmjoint_t * )( pbase + header->ofs_joints );
	for( i = 0; i < poutmodel->numbones; i++ ) {
		joints[i].name = LittleLong( joints[i].name );
		joints[i].parent = LittleLong( joints[i].parent );

		for( j = 0; j < 3; j++ ) {
			joints[i].translate[j] = LittleFloat( joints[i].translate[j] );
			joints[i].rotate[j] = LittleFloat( joints[i].rotate[j] );
			joints[i].scale[j] = LittleFloat( joints[i].scale[j] );
		}

		if( joints[i].parent >= (int)i ) {
			ri.Com_Printf( S_COLOR_RED "ERROR: %s bone[%i].parent(%i) >= %i\n", mod->name, i, joints[i].parent, i );
			goto error;
		}

		poutmodel->bones[i].name = texts + joints[i].name;
		poutmodel->bones[i].parent = joints[i].parent;

		DualQuat_FromQuat3AndVector( joints[i].rotate, joints[i].translate, baseposes[i].dualquat );

		// scale is unused

		// reconstruct invserse bone pose

		if( joints[i].parent >= 0 )
		{
			bonepose_t bp, *pbp;
			bp = baseposes[i];
			pbp = &baseposes[joints[i].parent];

			DualQuat_Multiply( pbp->dualquat, bp.dualquat, baseposes[i].dualquat );
		}

		DualQuat_Copy( baseposes[i].dualquat, poutmodel->invbaseposes[i].dualquat );
		DualQuat_Invert( poutmodel->invbaseposes[i].dualquat );
	}


	// load frames
	poses = ( iqmpose_t * )( pbase + header->ofs_poses );
	for( i = 0; i < header->num_poses; i++ ) {
		poses[i].parent = LittleLong( poses[i].parent );
		poses[i].mask = LittleLong( poses[i].mask );

		for( j = 0; j < 10; j++ ) {
			poses[i].channeloffset[j] = LittleFloat( poses[i].channeloffset[j] );
			poses[i].channelscale[j] = LittleFloat( poses[i].channelscale[j] );
		}
	}

	memsize = 0;
	memsize += sizeof( mskframe_t ) * header->num_frames;
	memsize += sizeof( bonepose_t ) * header->num_joints * header->num_frames;
	pmem = Mod_Malloc( mod, memsize );

	poutmodel->numframes = header->num_frames;
	poutmodel->frames = ( mskframe_t * )pmem; pmem += sizeof( mskframe_t ) * poutmodel->numframes;

	framedata = ( unsigned short * )( pbase + header->ofs_frames );
	for( i = 0; i < header->num_frames; i++ ) {
		bonepose_t *pbp;
		vec3_t translate;
		quat_t rotate;

		poutmodel->frames[i].boneposes = ( bonepose_t * )pmem; pmem += sizeof( bonepose_t ) * poutmodel->numbones;

		for( j = 0, pbp = poutmodel->frames[i].boneposes; j < header->num_poses; j++, pbp++ ) {
			translate[0] = poses[j].channeloffset[0]; if( poses[j].mask & 0x01 ) translate[0] += *framedata++ * poses[j].channelscale[0];
			translate[1] = poses[j].channeloffset[1]; if( poses[j].mask & 0x02 ) translate[1] += *framedata++ * poses[j].channelscale[1];
			translate[2] = poses[j].channeloffset[2]; if( poses[j].mask & 0x04 ) translate[2] += *framedata++ * poses[j].channelscale[2];

			rotate[0] = poses[j].channeloffset[3]; if( poses[j].mask & 0x08 ) rotate[0] += *framedata++ * poses[j].channelscale[3];
			rotate[1] = poses[j].channeloffset[4]; if( poses[j].mask & 0x10 ) rotate[1] += *framedata++ * poses[j].channelscale[4];
			rotate[2] = poses[j].channeloffset[5]; if( poses[j].mask & 0x20 ) rotate[2] += *framedata++ * poses[j].channelscale[5];
			rotate[3] = poses[j].channeloffset[6]; if( poses[j].mask & 0x40 ) rotate[3] += *framedata++ * poses[j].channelscale[6];
			if( rotate[3] > 0 ) {
				Vector4Inverse( rotate );
			}
			Vector4Normalize( rotate );

			// scale is unused
			if( poses[j].mask & 0x80  ) framedata++;
			if( poses[j].mask & 0x100 ) framedata++;
			if( poses[j].mask & 0x200 ) framedata++;

			DualQuat_FromQuatAndVector( rotate, translate, pbp->dualquat );
		}
	}


	// load triangles
	memsize = 0;
	memsize += sizeof( *outelems ) * header->num_triangles * 3;
	pmem = Mod_Malloc( mod, memsize );

	poutmodel->numtris = header->num_triangles;
	poutmodel->elems = ( elem_t * )pmem; pmem += sizeof( *outelems ) * header->num_triangles * 3;

	inelems = ( const int * )(pbase + header->ofs_triangles);
	outelems = poutmodel->elems;

	for( i = 0; i < header->num_triangles; i++ ) {
		for( j = 0; j < 3; j++ ) {
			outelems[j] = LittleLong( inelems[j] );
		}
		inelems += 3;
		outelems += 3;
	}


	// load vertices
	memsize = 0;
	memsize += sizeof( *poutmodel->sVectorsArray ) * header->num_vertexes;	// 16-bytes aligned
	memsize += sizeof( *poutmodel->xyzArray ) * header->num_vertexes;
	memsize += sizeof( *poutmodel->normalsArray ) * header->num_vertexes;
	memsize += sizeof( *poutmodel->stArray ) * header->num_vertexes;
	memsize += sizeof( *poutmodel->blendWeights ) * header->num_vertexes * SKM_MAX_WEIGHTS;
	memsize += sizeof( *poutmodel->blendIndices ) * header->num_vertexes * SKM_MAX_WEIGHTS;
	pmem = Mod_Malloc( mod, memsize );

	poutmodel->numverts = header->num_vertexes;

	// S-vectors
	poutmodel->sVectorsArray = ( vec4_t * )pmem; pmem += sizeof( *poutmodel->sVectorsArray ) * header->num_vertexes;

	if( vtangent ) {
		for( i = 0; i < header->num_vertexes; i++ ) {
			for( j = 0; j < 4; j++ ) {
				poutmodel->sVectorsArray[i][j] = LittleFloat( vtangent[j] );
			}
			vtangent += 4;
		}
	}

	// XYZ positions
	poutmodel->xyzArray = ( vec4_t * )pmem; pmem += sizeof( *poutmodel->xyzArray ) * header->num_vertexes;
	for( i = 0; i < header->num_vertexes; i++ ) {
		for( j = 0; j < 3; j++ ) {
			poutmodel->xyzArray[i][j] = LittleFloat( vposition[j] );
		}
		poutmodel->xyzArray[i][3] = 1;
		vposition += 3;
	}

	// normals
	poutmodel->normalsArray = ( vec4_t * )pmem; pmem += sizeof( *poutmodel->normalsArray ) * header->num_vertexes;
	for( i = 0; i < header->num_vertexes; i++ ) {
		for( j = 0; j < 3; j++ ) {
			poutmodel->normalsArray[i][j] = LittleFloat( vnormal[j] );
		}
		poutmodel->normalsArray[i][3] = 0;
		vnormal += 3;
	}

	// texture coordinates
	poutmodel->stArray = ( vec2_t * )pmem; pmem += sizeof( *poutmodel->stArray ) * header->num_vertexes;
	for( i = 0; i < header->num_vertexes; i++ ) {
		for( j = 0; j < 2; j++ ) {
			poutmodel->stArray[i][j] = LittleFloat( vtexcoord[j] );
		}
		vtexcoord += 2;
	}

	if( !vtangent ) {
		// if the loaded file is missing precomputed S-vectors, compute them now
		R_BuildTangentVectors( poutmodel->numverts, poutmodel->xyzArray, poutmodel->normalsArray, poutmodel->stArray, 
			poutmodel->numtris, poutmodel->elems, poutmodel->sVectorsArray );
	}

	// blend indices
	poutmodel->blendIndices = ( qbyte * )pmem; pmem += sizeof( *poutmodel->blendIndices ) * header->num_vertexes * SKM_MAX_WEIGHTS;
	if( vblendindices_byte ) {
		memcpy( poutmodel->blendIndices, vblendindices_byte, sizeof( qbyte ) * header->num_vertexes * SKM_MAX_WEIGHTS );
	} else if( vblendindexes_int ) {
		for( j = 0; j < header->num_vertexes * SKM_MAX_WEIGHTS; j++ ) {
			poutmodel->blendIndices[j] = LittleLong( vblendindexes_int[j] );
		}
	}

	// blend weights
	poutmodel->blendWeights = ( qbyte * )pmem; pmem += sizeof( *poutmodel->blendWeights ) * header->num_vertexes * SKM_MAX_WEIGHTS;
	if( vblendweights_byte ) {
		memcpy( poutmodel->blendWeights, vblendweights_byte, sizeof( qbyte ) * header->num_vertexes * SKM_MAX_WEIGHTS );
	}
	else if( vblendweights_float ) {
		for( j = 0; j < header->num_vertexes * SKM_MAX_WEIGHTS; j++ ) {
			poutmodel->blendWeights[j] = LittleFloat( vblendweights_float[j] ) * 255.0f;
		}
	}


	// blends
	memsize = 0;
	memsize += poutmodel->numverts * ( sizeof( mskblend_t ) + sizeof( unsigned int ) );
	pmem = Mod_Malloc( mod, memsize );

	poutmodel->numblends = 0;
	poutmodel->blends = ( mskblend_t * )pmem; pmem += sizeof( *poutmodel->blends ) * poutmodel->numverts;
	poutmodel->vertexBlends = ( unsigned int * )pmem;

	vblendindices_byte = poutmodel->blendIndices;
	vblendweights_byte = poutmodel->blendWeights;

	for( i = 0; i < poutmodel->numverts; i++ ) {
		mskblend_t blend;

		for( j = 0; j < SKM_MAX_WEIGHTS; j++ ) {
			blend.indices[j] = vblendindices_byte[j];
			blend.weights[j] = vblendweights_byte[j];
		}

		poutmodel->vertexBlends[i] = Mod_SkeletalModel_AddBlend( poutmodel, &blend );

		vblendindices_byte += SKM_MAX_WEIGHTS;
		vblendweights_byte += SKM_MAX_WEIGHTS;
	}

	// meshes
	memsize = 0;
	memsize += sizeof( mskmesh_t ) * header->num_meshes;
	memsize += sizeof( drawSurfaceSkeletal_t ) * header->num_meshes;
	pmem = Mod_Malloc( mod, memsize );

	poutmodel->nummeshes = header->num_meshes;
	poutmodel->meshes = ( mskmesh_t * )pmem; pmem += sizeof( *poutmodel->meshes ) * header->num_meshes;

	inmesh = ( iqmmesh_t * )(pbase + header->ofs_meshes);
	for( i = 0; i < header->num_meshes; i++ ) {
		inmesh[i].name = LittleLong( inmesh[i].name );
		inmesh[i].material = LittleLong( inmesh[i].material );
		inmesh[i].first_vertex = LittleLong( inmesh[i].first_vertex );
		inmesh[i].num_vertexes = LittleLong( inmesh[i].num_vertexes );
		inmesh[i].first_triangle = LittleLong( inmesh[i].first_triangle );
		inmesh[i].num_triangles = LittleLong( inmesh[i].num_triangles );

		poutmodel->meshes[i].name = texts + inmesh[i].name;
		Mod_StripLODSuffix( poutmodel->meshes[i].name );

		poutmodel->meshes[i].skin.name = texts + inmesh[i].material;
		poutmodel->meshes[i].skin.shader = R_RegisterSkin( poutmodel->meshes[i].skin.name );

		poutmodel->meshes[i].elems = poutmodel->elems + inmesh[i].first_triangle * 3;
		poutmodel->meshes[i].numtris = inmesh[i].num_triangles;

		poutmodel->meshes[i].numverts = inmesh[i].num_vertexes;
		poutmodel->meshes[i].xyzArray = poutmodel->xyzArray + inmesh[i].first_vertex;
		poutmodel->meshes[i].normalsArray = poutmodel->normalsArray + inmesh[i].first_vertex;
		poutmodel->meshes[i].stArray = poutmodel->stArray + inmesh[i].first_vertex;
		poutmodel->meshes[i].sVectorsArray = poutmodel->sVectorsArray + inmesh[i].first_vertex;

		poutmodel->meshes[i].blendIndices = poutmodel->blendIndices + inmesh[i].first_vertex * SKM_MAX_WEIGHTS;
		poutmodel->meshes[i].blendWeights = poutmodel->blendWeights + inmesh[i].first_vertex * SKM_MAX_WEIGHTS;

		poutmodel->meshes[i].vertexBlends = poutmodel->vertexBlends + inmesh[i].first_vertex;

		// elements are always offset to start vertex 0 for each mesh
		outelems = poutmodel->meshes[i].elems;
		for( j = 0; j < poutmodel->meshes[i].numtris; j++ ) {
			outelems[0] -= inmesh[i].first_vertex;
			outelems[1] -= inmesh[i].first_vertex;
			outelems[2] -= inmesh[i].first_vertex;
			outelems += 3;
		}

		poutmodel->meshes[i].maxWeights = 1;

		vblendweights_byte = poutmodel->meshes[i].blendWeights;
		for( j = 0; j < poutmodel->meshes[i].numverts; j++ ) {
			for( k = 1; k < SKM_MAX_WEIGHTS && vblendweights_byte[k]; k++ );

			if( k > poutmodel->meshes[i].maxWeights ) {
				poutmodel->meshes[i].maxWeights = k;
				if( k == SKM_MAX_WEIGHTS ) {
					break;
				}
			}
			vblendweights_byte += SKM_MAX_WEIGHTS;
		}

		// creating a VBO only makes sense if GLSL is present and the number of bones 
		// we can handle on the GPU is sufficient
		if( glConfig.ext.vertex_buffer_object && poutmodel->numbones <= glConfig.maxGLSLBones ) {
			// build a static vertex buffer object for this mesh
			Mod_SkeletalBuildStaticVBOForMesh( &poutmodel->meshes[i] );
		}
	}

	poutmodel->drawSurfs = ( drawSurfaceSkeletal_t * )pmem; pmem += sizeof( *poutmodel->drawSurfs ) * header->num_meshes;
	for( i = 0; i < header->num_meshes; i++ ) {
		poutmodel->drawSurfs[i].type = ST_SKELETAL;
		poutmodel->drawSurfs[i].model = mod;
		poutmodel->drawSurfs[i].mesh = poutmodel->meshes + i;
	}

	// bounds
	ClearBounds( mod->mins, mod->maxs );

	inbounds = ( iqmbounds_t * )(pbase + header->ofs_bounds);
	for( i = 0; i < header->num_frames; i++ ) {
		for( j = 0; j < 3; j++ ) {
			inbounds[i].bbmin[j] = LittleFloat( inbounds[i].bbmin[j] );
			inbounds[i].bbmax[j] = LittleFloat( inbounds[i].bbmax[j] );
		}
		inbounds[i].radius = LittleFloat( inbounds[i].radius );
		inbounds[i].xyradius = LittleFloat( inbounds[i].xyradius );

		VectorCopy( inbounds[i].bbmin, poutmodel->frames[i].mins );
		VectorCopy( inbounds[i].bbmax, poutmodel->frames[i].maxs );
		poutmodel->frames[i].radius = inbounds[i].radius;

		AddPointToBounds( poutmodel->frames[i].mins, mod->mins, mod->maxs );
		AddPointToBounds( poutmodel->frames[i].maxs, mod->mins, mod->maxs );
	}

	mod->radius = RadiusFromBounds( mod->mins, mod->maxs );
	mod->type = mod_skeletal;
	mod->registrationSequence = rsh.registrationSequence;
	mod->touch = &Mod_TouchSkeletalModel;

	R_Free( baseposes );
	return;

error:
	if( baseposes ) {
		R_Free( baseposes );
	}
	mod->type = mod_bad;
}
Esempio n. 2
0
/*
* Mod_LoadAliasMD3Model
*/
void Mod_LoadAliasMD3Model( model_t *mod, model_t *parent, void *buffer, bspFormatDesc_t *unused )
{
	int version, i, j, l;
	int bufsize, numverts;
	qbyte *buf;
	dmd3header_t *pinmodel;
	dmd3frame_t *pinframe;
	dmd3tag_t *pintag;
	dmd3mesh_t *pinmesh;
	dmd3skin_t *pinskin;
	dmd3coord_t *pincoord;
	dmd3vertex_t *pinvert;
	elem_t *pinelem, *poutelem;
	maliasvertex_t *poutvert;
	vec2_t *poutcoord;
	maliasskin_t *poutskin;
	maliasmesh_t *poutmesh;
	maliastag_t *pouttag;
	maliasframe_t *poutframe;
	maliasmodel_t *poutmodel;
	drawSurfaceAlias_t *drawSurf;

	pinmodel = ( dmd3header_t * )buffer;
	version = LittleLong( pinmodel->version );

	if( version != MD3_ALIAS_VERSION )
		ri.Com_Error( ERR_DROP, "%s has wrong version number (%i should be %i)",
		mod->name, version, MD3_ALIAS_VERSION );

	mod->type = mod_alias;
	mod->extradata = poutmodel = Mod_Malloc( mod, sizeof( maliasmodel_t ) );
	mod->radius = 0;
	mod->registrationSequence = rf.registrationSequence;
	mod->touch = &Mod_TouchAliasModel;

	ClearBounds( mod->mins, mod->maxs );

	// byte swap the header fields and sanity check
	poutmodel->numframes = LittleLong( pinmodel->num_frames );
	poutmodel->numtags = LittleLong( pinmodel->num_tags );
	poutmodel->nummeshes = LittleLong( pinmodel->num_meshes );
	poutmodel->numskins = 0;

	if( poutmodel->numframes <= 0 )
		ri.Com_Error( ERR_DROP, "model %s has no frames", mod->name );
	//	else if( poutmodel->numframes > MD3_MAX_FRAMES )
	//		ri.Com_Error( ERR_DROP, "model %s has too many frames", mod->name );

	if( poutmodel->numtags > MD3_MAX_TAGS )
		ri.Com_Error( ERR_DROP, "model %s has too many tags", mod->name );
	else if( poutmodel->numtags < 0 )
		ri.Com_Error( ERR_DROP, "model %s has invalid number of tags", mod->name );

	if( poutmodel->nummeshes < 0 )
		ri.Com_Error( ERR_DROP, "model %s has invalid number of meshes", mod->name );
	else if( !poutmodel->nummeshes && !poutmodel->numtags )
		ri.Com_Error( ERR_DROP, "model %s has no meshes and no tags", mod->name );
	//	else if( poutmodel->nummeshes > MD3_MAX_MESHES )
	//		ri.Com_Error( ERR_DROP, "model %s has too many meshes", mod->name );

	bufsize = poutmodel->numframes * ( sizeof( maliasframe_t ) + sizeof( maliastag_t ) * poutmodel->numtags ) +
		poutmodel->nummeshes * sizeof( maliasmesh_t ) + 
		poutmodel->nummeshes * sizeof( drawSurfaceAlias_t );
	buf = Mod_Malloc( mod, bufsize );

	//
	// load the frames
	//
	pinframe = ( dmd3frame_t * )( ( qbyte * )pinmodel + LittleLong( pinmodel->ofs_frames ) );
	poutframe = poutmodel->frames = ( maliasframe_t * )buf; buf += sizeof( maliasframe_t ) * poutmodel->numframes;
	for( i = 0; i < poutmodel->numframes; i++, pinframe++, poutframe++ )
	{
		for( j = 0; j < 3; j++ )
		{
			poutframe->scale[j] = MD3_XYZ_SCALE;
			poutframe->translate[j] = LittleFloat( pinframe->translate[j] );
		}

		// never trust the modeler utility and recalculate bbox and radius
		ClearBounds( poutframe->mins, poutframe->maxs );
	}

	//
	// load the tags
	//
	pintag = ( dmd3tag_t * )( ( qbyte * )pinmodel + LittleLong( pinmodel->ofs_tags ) );
	pouttag = poutmodel->tags = ( maliastag_t * )buf; buf += sizeof( maliastag_t ) * poutmodel->numframes * poutmodel->numtags;
	for( i = 0; i < poutmodel->numframes; i++ )
	{
		for( l = 0; l < poutmodel->numtags; l++, pintag++, pouttag++ )
		{
			mat3_t axis;

			for( j = 0; j < 3; j++ )
			{
				axis[AXIS_FORWARD+j] = LittleFloat( pintag->axis[0][j] );
				axis[AXIS_RIGHT+j] = LittleFloat( pintag->axis[1][j] );
				axis[AXIS_UP+j] = LittleFloat( pintag->axis[2][j] );
				pouttag->origin[j] = LittleFloat( pintag->origin[j] );
			}

			Quat_FromMatrix3( axis, pouttag->quat );
			Quat_Normalize( pouttag->quat );

			Q_strncpyz( pouttag->name, pintag->name, MD3_MAX_PATH );
		}
	}

	//
	// allocate drawSurfs
	//
	drawSurf = poutmodel->drawSurfs = ( drawSurfaceAlias_t * )buf; buf += sizeof( drawSurfaceAlias_t ) * poutmodel->nummeshes;
	for( i = 0; i < poutmodel->nummeshes; i++, drawSurf++ )
	{
		drawSurf->type = ST_ALIAS;
		drawSurf->model = mod;
		drawSurf->mesh = poutmodel->meshes + i;
	}

	//
	// load meshes
	//
	pinmesh = ( dmd3mesh_t * )( ( qbyte * )pinmodel + LittleLong( pinmodel->ofs_meshes ) );
	poutmesh = poutmodel->meshes = ( maliasmesh_t * )buf; buf += sizeof( maliasmesh_t ) * poutmodel->nummeshes;
	for( i = 0; i < poutmodel->nummeshes; i++, poutmesh++ )
	{
		if( strncmp( (const char *)pinmesh->id, IDMD3HEADER, 4 ) )
			ri.Com_Error( ERR_DROP, "mesh %s in model %s has wrong id (%s should be %s)",
			pinmesh->name, mod->name, pinmesh->id, IDMD3HEADER );

		Q_strncpyz( poutmesh->name, pinmesh->name, MD3_MAX_PATH );

		Mod_StripLODSuffix( poutmesh->name );

		poutmesh->numtris = LittleLong( pinmesh->num_tris );
		poutmesh->numskins = LittleLong( pinmesh->num_skins );
		poutmesh->numverts = numverts = LittleLong( pinmesh->num_verts );

		/*		if( poutmesh->numskins <= 0 )
		ri.Com_Error( ERR_DROP, "mesh %i in model %s has no skins", i, mod->name );
		else*/ if( poutmesh->numskins > MD3_MAX_SHADERS )
			ri.Com_Error( ERR_DROP, "mesh %i in model %s has too many skins", i, mod->name );
		if( poutmesh->numtris <= 0 )
			ri.Com_Error( ERR_DROP, "mesh %i in model %s has no elements", i, mod->name );
		else if( poutmesh->numtris > MD3_MAX_TRIANGLES )
			ri.Com_Error( ERR_DROP, "mesh %i in model %s has too many triangles", i, mod->name );
		if( poutmesh->numverts <= 0 )
			ri.Com_Error( ERR_DROP, "mesh %i in model %s has no vertices", i, mod->name );
		else if( poutmesh->numverts > MD3_MAX_VERTS )
			ri.Com_Error( ERR_DROP, "mesh %i in model %s has too many vertices", i, mod->name );

		bufsize = sizeof( maliasskin_t ) * poutmesh->numskins + poutmesh->numtris * sizeof( elem_t ) * 3 +
			numverts * ( sizeof( vec2_t ) + sizeof( maliasvertex_t ) * poutmodel->numframes );
		buf = Mod_Malloc( mod, bufsize );

		//
		// load the skins
		//
		pinskin = ( dmd3skin_t * )( ( qbyte * )pinmesh + LittleLong( pinmesh->ofs_skins ) );
		poutskin = poutmesh->skins = ( maliasskin_t * )buf; buf += sizeof( maliasskin_t ) * poutmesh->numskins;
		for( j = 0; j < poutmesh->numskins; j++, pinskin++, poutskin++ ) {
			Q_strncpyz( poutskin->name, pinskin->name, sizeof( poutskin->name ) );
			poutskin->shader = R_RegisterSkin( poutskin->name );
		}
		
		//
		// load the elems
		//
		pinelem = ( elem_t * )( ( qbyte * )pinmesh + LittleLong( pinmesh->ofs_elems ) );
		poutelem = poutmesh->elems = ( elem_t * )buf; buf += poutmesh->numtris * sizeof( elem_t ) * 3;
		for( j = 0; j < poutmesh->numtris; j++, pinelem += 3, poutelem += 3 )
		{
			poutelem[0] = (elem_t)LittleLong( pinelem[0] );
			poutelem[1] = (elem_t)LittleLong( pinelem[1] );
			poutelem[2] = (elem_t)LittleLong( pinelem[2] );
		}

		//
		// load the texture coordinates
		//
		pincoord = ( dmd3coord_t * )( ( qbyte * )pinmesh + LittleLong( pinmesh->ofs_tcs ) );
		poutcoord = poutmesh->stArray = ( vec2_t * )buf; buf += poutmesh->numverts * sizeof( vec2_t );
		for( j = 0; j < poutmesh->numverts; j++, pincoord++ )
		{
			poutcoord[j][0] = LittleFloat( pincoord->st[0] );
			poutcoord[j][1] = LittleFloat( pincoord->st[1] );
		}

		//
		// load the vertexes and normals
		//
		pinvert = ( dmd3vertex_t * )( ( qbyte * )pinmesh + LittleLong( pinmesh->ofs_verts ) );
		poutvert = poutmesh->vertexes = ( maliasvertex_t * )buf;
		for( l = 0, poutframe = poutmodel->frames; l < poutmodel->numframes; l++, poutframe++, pinvert += poutmesh->numverts, poutvert += poutmesh->numverts )
		{
			vec3_t v;

			for( j = 0; j < poutmesh->numverts; j++ )
			{
				poutvert[j].point[0] = LittleShort( pinvert[j].point[0] );
				poutvert[j].point[1] = LittleShort( pinvert[j].point[1] );
				poutvert[j].point[2] = LittleShort( pinvert[j].point[2] );

				poutvert[j].latlong[0] = pinvert[j].norm[0];
				poutvert[j].latlong[1] = pinvert[j].norm[1];

				VectorCopy( poutvert[j].point, v );
				AddPointToBounds( v, poutframe->mins, poutframe->maxs );
			}
		}

		pinmesh = ( dmd3mesh_t * )( ( qbyte * )pinmesh + LittleLong( pinmesh->meshsize ) );
	}

	//
	// setup drawSurfs
	//
	for( i = 0; i < poutmodel->nummeshes; i++ )
	{
		drawSurf = poutmodel->drawSurfs + i;
		drawSurf->type = ST_ALIAS;
		drawSurf->model = mod;
		drawSurf->mesh = poutmodel->meshes + i;
	}

	//
	// build S and T vectors for frame 0
	//
	Mod_AliasBuildMeshesForFrame0( mod );

	//
	// calculate model bounds
	//
	poutframe = poutmodel->frames;
	for( i = 0; i < poutmodel->numframes; i++, poutframe++ )
	{
		VectorMA( poutframe->translate, MD3_XYZ_SCALE, poutframe->mins, poutframe->mins );
		VectorMA( poutframe->translate, MD3_XYZ_SCALE, poutframe->maxs, poutframe->maxs );
		poutframe->radius = RadiusFromBounds( poutframe->mins, poutframe->maxs );

		AddPointToBounds( poutframe->mins, mod->mins, mod->maxs );
		AddPointToBounds( poutframe->maxs, mod->mins, mod->maxs );
		mod->radius = max( mod->radius, poutframe->radius );
	}
}