Ejemplo n.º 1
0
/*
=============
Tess_SurfaceMDV
=============
*/
static void Tess_SurfaceMDV( mdvSurface_t *srf )
{
	int           i, j;
	int           numIndexes = 0;
	int           numVertexes;
	mdvXyz_t      *oldVert, *newVert;
	mdvNormal_t   *oldNormal, *newNormal;
	mdvSt_t       *st;
	srfTriangle_t *tri;
	float         backlerp;
	float         oldXyzScale, newXyzScale;

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

	if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame )
	{
		backlerp = 0;
	}
	else
	{
		backlerp = backEnd.currentEntity->e.backlerp;
	}

	newXyzScale = ( 1.0f - backlerp );
	oldXyzScale = backlerp;

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

	numIndexes = srf->numTriangles * 3;

	for ( i = 0, tri = srf->triangles; 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 ];
	}

	newVert = srf->verts + ( backEnd.currentEntity->e.frame * srf->numVerts );
	oldVert = srf->verts + ( backEnd.currentEntity->e.oldframe * srf->numVerts );
	newNormal = srf->normals + ( backEnd.currentEntity->e.frame * srf->numVerts );
	oldNormal = srf->normals + ( backEnd.currentEntity->e.oldframe * srf->numVerts );
	st = srf->st;

	numVertexes = srf->numVerts;

	for ( j = 0; j < numVertexes; j++, newVert++, oldVert++, st++ )
	{
		vec3_t tmpVert;

		if ( backlerp == 0 )
		{
			// just copy
			VectorCopy( newVert->xyz, tmpVert );
		}
		else
		{
			// interpolate the xyz
			VectorScale( oldVert->xyz, oldXyzScale, tmpVert );
			VectorMA( tmpVert, newXyzScale, newVert->xyz, tmpVert );
		}

		tess.verts[ tess.numVertexes + j ].xyz[ 0 ] = tmpVert[ 0 ];
		tess.verts[ tess.numVertexes + j ].xyz[ 1 ] = tmpVert[ 1 ];
		tess.verts[ tess.numVertexes + j ].xyz[ 2 ] = tmpVert[ 2 ];

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

	tess.attribsSet |= ATTR_POSITION | ATTR_TEXCOORD;

	// calc tangent spaces
	if ( !tess.skipTangentSpaces )
	{
		int         i;
		float       *v;
		const float *v0, *v1, *v2;
		const int16_t *t0, *t1, *t2;
		vec3_t      tangent, *tangents;
		vec3_t      binormal, *binormals;
		vec3_t      *normals;
		glIndex_t   *indices;

		tess.attribsSet |= ATTR_QTANGENT;

		tangents = (vec3_t *)ri.Hunk_AllocateTempMemory( numVertexes * sizeof( vec3_t ) );
		binormals = (vec3_t *)ri.Hunk_AllocateTempMemory( numVertexes * sizeof( vec3_t ) );
		normals = (vec3_t *)ri.Hunk_AllocateTempMemory( numVertexes * sizeof( vec3_t ) );

		for ( i = 0; i < numVertexes; i++ )
		{
			VectorClear( tangents[ i ] );
			VectorClear( binormals[ i ] );

			if ( backlerp == 0 )
			{
				// just copy
				VectorCopy( newNormal->normal, normals[ i ] );
			}
			else
			{
				// interpolate the xyz
				VectorScale( oldNormal->normal, oldXyzScale, normals[ i ] );
				VectorMA( normals[ i ], newXyzScale, newNormal->normal, normals[ i ] );
				VectorNormalizeFast( normals[ i ] );
			}
		}

		for ( i = 0, indices = tess.indexes + tess.numIndexes; i < numIndexes; i += 3, indices += 3 )
		{
			v0 = tess.verts[ indices[ 0 ] ].xyz;
			v1 = tess.verts[ indices[ 1 ] ].xyz;
			v2 = tess.verts[ indices[ 2 ] ].xyz;

			t0 = tess.verts[ indices[ 0 ] ].texCoords;
			t1 = tess.verts[ indices[ 1 ] ].texCoords;
			t2 = tess.verts[ indices[ 2 ] ].texCoords;

			R_CalcTangents( tangent, binormal, v0, v1, v2, t0, t1, t2 );

			for ( j = 0; j < 3; j++ )
			{
				v = tangents[ indices[ j ] - tess.numVertexes ];
				VectorAdd( v, tangent, v );

				v = binormals[ indices[ j ] - tess.numVertexes ];
				VectorAdd( v, binormal, v );
			}
		}

		for ( i = 0; i < numVertexes; i++ )
		{
			R_TBNtoQtangents( tangents[ i ], binormals[ i ],
					  normals[ i ], tess.verts[ numVertexes + i ].qtangents );
		}

		ri.Hunk_FreeTempMemory( normals );
		ri.Hunk_FreeTempMemory( binormals );
		ri.Hunk_FreeTempMemory( tangents );
	}

	tess.numIndexes += numIndexes;
	tess.numVertexes += numVertexes;
}
Ejemplo n.º 2
0
/*
==============
Tess_AddQuadStampExt2
==============
*/
void Tess_AddQuadStampExt2( vec4_t quadVerts[ 4 ], const vec4_t color, float s1, float t1, float s2, float t2 )
{
	int    i;
	vec3_t normal, tangent, binormal;
	int    ndx;

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

	Tess_CheckOverflow( 4, 6 );

	ndx = tess.numVertexes;

	// triangle indexes for a simple quad
	tess.indexes[ tess.numIndexes ] = ndx;
	tess.indexes[ tess.numIndexes + 1 ] = ndx + 1;
	tess.indexes[ tess.numIndexes + 2 ] = ndx + 3;

	tess.indexes[ tess.numIndexes + 3 ] = ndx + 3;
	tess.indexes[ tess.numIndexes + 4 ] = ndx + 1;
	tess.indexes[ tess.numIndexes + 5 ] = ndx + 2;

	VectorCopy( quadVerts[ 0 ], tess.verts[ ndx + 0 ].xyz );
	VectorCopy( quadVerts[ 1 ], tess.verts[ ndx + 1 ].xyz );
	VectorCopy( quadVerts[ 2 ], tess.verts[ ndx + 2 ].xyz );
	VectorCopy( quadVerts[ 3 ], tess.verts[ ndx + 3 ].xyz );

	tess.attribsSet |= ATTR_POSITION | ATTR_COLOR | ATTR_TEXCOORD | ATTR_QTANGENT;

	// constant normal all the way around
	vec2_t st[ 3 ] = { { s1, t1 }, { s2, t1 }, { s2, t2 } };
	R_CalcFaceNormal( normal, quadVerts[ 0 ], quadVerts[ 1 ], quadVerts[ 2 ] );
	R_CalcTangents( tangent, binormal,
			quadVerts[ 0 ], quadVerts[ 1 ], quadVerts[ 2 ],
			st[ 0 ], st[ 1 ], st[ 2 ] );
	//if ( !calcNormals )
	//{
	//	VectorNegate( backEnd.viewParms.orientation.axis[ 0 ], normal );
	//}

	R_TBNtoQtangents( tangent, binormal, normal, tess.verts[ ndx ].qtangents );
	Vector4Copy( tess.verts[ ndx ].qtangents, tess.verts[ ndx + 1 ].qtangents );
	Vector4Copy( tess.verts[ ndx ].qtangents, tess.verts[ ndx + 2 ].qtangents );
	Vector4Copy( tess.verts[ ndx ].qtangents, tess.verts[ ndx + 3 ].qtangents );

	// standard square texture coordinates
	tess.verts[ ndx ].texCoords[ 0 ] = floatToHalf( s1 );
	tess.verts[ ndx ].texCoords[ 1 ] = floatToHalf( t1 );

	tess.verts[ ndx + 1 ].texCoords[ 0 ] = floatToHalf( s2 );
	tess.verts[ ndx + 1 ].texCoords[ 1 ] = floatToHalf( t1 );

	tess.verts[ ndx + 2 ].texCoords[ 0 ] = floatToHalf( s2 );
	tess.verts[ ndx + 2 ].texCoords[ 1 ] = floatToHalf( t2 );

	tess.verts[ ndx + 3 ].texCoords[ 0 ] = floatToHalf( s1 );
	tess.verts[ ndx + 3 ].texCoords[ 1 ] = floatToHalf( t2 );

	// constant color all the way around
	// should this be identity and let the shader specify from entity?

	u8vec4_t iColor;
	floatToUnorm8( color, iColor );
	for ( i = 0; i < 4; i++ )
	{
		Vector4Copy( iColor, tess.verts[ ndx + i ].color );
	}

	tess.numVertexes += 4;
	tess.numIndexes += 6;
}
Ejemplo n.º 3
0
/*
=============
Tess_SurfacePolychain
=============
*/
static void Tess_SurfacePolychain( srfPoly_t *p )
{
	int i, j;
	int numVertexes;
	int numIndexes;

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

	Tess_CheckOverflow( p->numVerts, 3 * ( p->numVerts - 2 ) );

	// fan triangles into the tess array
	numVertexes = 0;

	for ( i = 0; i < p->numVerts; i++ )
	{
		VectorCopy( p->verts[ i ].xyz, tess.verts[ tess.numVertexes + i ].xyz );

		tess.verts[ tess.numVertexes + i ].texCoords[ 0 ] = floatToHalf( p->verts[ i ].st[ 0 ] );
		tess.verts[ tess.numVertexes + i ].texCoords[ 1 ] = floatToHalf( p->verts[ i ].st[ 1 ] );

		Vector4Copy( p->verts[ i ].modulate, tess.verts[ tess.numVertexes + i ].color );

		numVertexes++;
	}

	// generate fan indexes into the tess array
	numIndexes = 0;

	for ( i = 0; i < p->numVerts - 2; i++ )
	{
		tess.indexes[ tess.numIndexes + i * 3 + 0 ] = tess.numVertexes;
		tess.indexes[ tess.numIndexes + i * 3 + 1 ] = tess.numVertexes + i + 1;
		tess.indexes[ tess.numIndexes + i * 3 + 2 ] = tess.numVertexes + i + 2;
		numIndexes += 3;
	}

	tess.attribsSet |= ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR;

	// calc tangent spaces
	if ( tess.surfaceShader->interactLight && !tess.skipTangentSpaces )
	{
		int         i;
		float       *v;
		const float *v0, *v1, *v2;
		const int16_t *t0, *t1, *t2;
		vec3_t      tangent, *tangents;
		vec3_t      binormal, *binormals;
		vec3_t      normal, *normals;
		glIndex_t   *indices;

		tangents = (vec3_t *)ri.Hunk_AllocateTempMemory( numVertexes * sizeof( vec3_t ) );
		binormals = (vec3_t *)ri.Hunk_AllocateTempMemory( numVertexes * sizeof( vec3_t ) );
		normals = (vec3_t *)ri.Hunk_AllocateTempMemory( numVertexes * sizeof( vec3_t ) );

		for ( i = 0; i < numVertexes; i++ )
		{
			VectorClear( tangents[ i ] );
			VectorClear( binormals[ i ] );
			VectorClear( normals[ i ] );
		}

		for ( i = 0, indices = tess.indexes + tess.numIndexes; i < numIndexes; i += 3, indices += 3 )
		{
			v0 = tess.verts[ indices[ 0 ] ].xyz;
			v1 = tess.verts[ indices[ 1 ] ].xyz;
			v2 = tess.verts[ indices[ 2 ] ].xyz;

			t0 = tess.verts[ indices[ 0 ] ].texCoords;
			t1 = tess.verts[ indices[ 1 ] ].texCoords;
			t2 = tess.verts[ indices[ 2 ] ].texCoords;

			R_CalcFaceNormal( normal, v0, v1, v2 );
			R_CalcTangents( tangent, binormal, v0, v1, v2, t0, t1, t2 );

			for ( j = 0; j < 3; j++ )
			{
				v = tangents[ indices[ j ] - tess.numVertexes ];
				VectorAdd( v, tangent, v );
				v = binormals[ indices[ j ] - tess.numVertexes ];
				VectorAdd( v, binormal, v );
				v = normals[ indices[ j ] - tess.numVertexes ];
				VectorAdd( v, normal, v );
			}
		}

		for ( i = 0; i < numVertexes; i++ )
		{
			VectorNormalizeFast( normals[ i ] );
			R_TBNtoQtangents( tangents[ i ], binormals[ i ],
					  normals[ i ], tess.verts[ tess.numVertexes + i ].qtangents );
		}

		ri.Hunk_FreeTempMemory( normals );
		ri.Hunk_FreeTempMemory( binormals );
		ri.Hunk_FreeTempMemory( tangents );

		tess.attribsSet |= ATTR_QTANGENT;
	}

	tess.numIndexes += numIndexes;
	tess.numVertexes += numVertexes;
}
Ejemplo n.º 4
0
/*
==============
Tess_AddQuadStampExt
==============
*/
void Tess_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, const vec4_t color, float s1, float t1, float s2, float t2 )
{
	int    i;
	vec3_t normal;
	int    ndx;

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

	Tess_CheckOverflow( 4, 6 );

	ndx = tess.numVertexes;

	// triangle indexes for a simple quad
	tess.indexes[ tess.numIndexes ] = ndx;
	tess.indexes[ tess.numIndexes + 1 ] = ndx + 1;
	tess.indexes[ tess.numIndexes + 2 ] = ndx + 3;

	tess.indexes[ tess.numIndexes + 3 ] = ndx + 3;
	tess.indexes[ tess.numIndexes + 4 ] = ndx + 1;
	tess.indexes[ tess.numIndexes + 5 ] = ndx + 2;

	tess.verts[ ndx ].xyz[ 0 ] = origin[ 0 ] + left[ 0 ] + up[ 0 ];
	tess.verts[ ndx ].xyz[ 1 ] = origin[ 1 ] + left[ 1 ] + up[ 1 ];
	tess.verts[ ndx ].xyz[ 2 ] = origin[ 2 ] + left[ 2 ] + up[ 2 ];

	tess.verts[ ndx + 1 ].xyz[ 0 ] = origin[ 0 ] - left[ 0 ] + up[ 0 ];
	tess.verts[ ndx + 1 ].xyz[ 1 ] = origin[ 1 ] - left[ 1 ] + up[ 1 ];
	tess.verts[ ndx + 1 ].xyz[ 2 ] = origin[ 2 ] - left[ 2 ] + up[ 2 ];

	tess.verts[ ndx + 2 ].xyz[ 0 ] = origin[ 0 ] - left[ 0 ] - up[ 0 ];
	tess.verts[ ndx + 2 ].xyz[ 1 ] = origin[ 1 ] - left[ 1 ] - up[ 1 ];
	tess.verts[ ndx + 2 ].xyz[ 2 ] = origin[ 2 ] - left[ 2 ] - up[ 2 ];

	tess.verts[ ndx + 3 ].xyz[ 0 ] = origin[ 0 ] + left[ 0 ] - up[ 0 ];
	tess.verts[ ndx + 3 ].xyz[ 1 ] = origin[ 1 ] + left[ 1 ] - up[ 1 ];
	tess.verts[ ndx + 3 ].xyz[ 2 ] = origin[ 2 ] + left[ 2 ] - up[ 2 ];

	// constant normal all the way around
	VectorSubtract( vec3_origin, backEnd.viewParms.orientation.axis[ 0 ], normal );
	R_TBNtoQtangents( left, up, normal, tess.verts[ ndx ].qtangents );
	Vector4Copy( tess.verts[ ndx ].qtangents, tess.verts[ ndx + 1 ].qtangents );
	Vector4Copy( tess.verts[ ndx ].qtangents, tess.verts[ ndx + 2 ].qtangents );
	Vector4Copy( tess.verts[ ndx ].qtangents, tess.verts[ ndx + 3 ].qtangents );

	// standard square texture coordinates
	tess.verts[ ndx ].texCoords[ 0 ] = floatToHalf( s1 );
	tess.verts[ ndx ].texCoords[ 1 ] = floatToHalf( t1 );

	tess.verts[ ndx + 1 ].texCoords[ 0 ] = floatToHalf( s2 );
	tess.verts[ ndx + 1 ].texCoords[ 1 ] = floatToHalf( t1 );

	tess.verts[ ndx + 2 ].texCoords[ 0 ] = floatToHalf( s2 );
	tess.verts[ ndx + 2 ].texCoords[ 1 ] = floatToHalf( t2 );

	tess.verts[ ndx + 3 ].texCoords[ 0 ] = floatToHalf( s1 );
	tess.verts[ ndx + 3 ].texCoords[ 1 ] = floatToHalf( t2 );

	// constant color all the way around
	// should this be identity and let the shader specify from entity?

	u8vec4_t iColor;
	floatToUnorm8( color, iColor );
	for ( i = 0; i < 4; i++ )
	{
		Vector4Copy( iColor, tess.verts[ ndx + i ].color );
	}

	tess.numVertexes += 4;
	tess.numIndexes += 6;

	tess.attribsSet |= ATTR_POSITION | ATTR_QTANGENT | ATTR_COLOR | ATTR_TEXCOORD;
}
Ejemplo n.º 5
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;
}
Ejemplo n.º 6
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;
}
Ejemplo n.º 7
0
/*
=================
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;
}
Ejemplo n.º 8
0
/*
=================
MakeMeshNormals

Handles all the complicated wrapping and degenerate cases
=================
*/
static void MakeMeshNormals( int width, int height, srfVert_t ctrl[ MAX_GRID_SIZE ][ MAX_GRID_SIZE ] )
{
	int        i, j, k, dist;
	vec3_t     tangent, binormal, normal;
	vec3_t     sum, sumTangents, sumBinormals;
	int        count;
	vec3_t     base;
	vec3_t     delta;
	int        x, y;
	srfVert_t  *dv;
	vec3_t     around[ 8 ], temp;
	bool   good[ 8 ];
	vec2_t     st[8];
	bool   wrapWidth, wrapHeight;
	float      len;
	static int neighbors[ 8 ][ 2 ] =
	{
		{ 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 }, { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }
	};

	wrapWidth = false;

	for ( i = 0; i < height; i++ )
	{
		VectorSubtract( ctrl[ i ][ 0 ].xyz, ctrl[ i ][ width - 1 ].xyz, delta );
		len = VectorLengthSquared( delta );

		if ( len > 1.0 )
		{
			break;
		}
	}

	if ( i == height )
	{
		wrapWidth = true;
	}

	wrapHeight = false;

	for ( i = 0; i < width; i++ )
	{
		VectorSubtract( ctrl[ 0 ][ i ].xyz, ctrl[ height - 1 ][ i ].xyz, delta );
		len = VectorLengthSquared( delta );

		if ( len > 1.0 )
		{
			break;
		}
	}

	if ( i == width )
	{
		wrapHeight = true;
	}

	for ( i = 0; i < width; i++ )
	{
		for ( j = 0; j < height; j++ )
		{
			count = 0;
			dv = &ctrl[ j ][ i ];
			VectorCopy( dv->xyz, base );

			for ( k = 0; k < 8; k++ )
			{
				VectorClear( around[ k ] );
				good[ k ] = false;

				for ( dist = 1; dist <= 3; dist++ )
				{
					x = i + neighbors[ k ][ 0 ] * dist;
					y = j + neighbors[ k ][ 1 ] * dist;

					if ( wrapWidth )
					{
						if ( x < 0 )
						{
							x = width - 1 + x;
						}
						else if ( x >= width )
						{
							x = 1 + x - width;
						}
					}

					if ( wrapHeight )
					{
						if ( y < 0 )
						{
							y = height - 1 + y;
						}
						else if ( y >= height )
						{
							y = 1 + y - height;
						}
					}

					if ( x < 0 || x >= width || y < 0 || y >= height )
					{
						break; // edge of patch
					}

					VectorSubtract( ctrl[ y ][ x ].xyz, base, temp );

					if ( VectorNormalize2( temp, temp ) == 0 )
					{
						continue; // degenerate edge, get more dist
					}
					else
					{
						good[ k ] = true;
						VectorCopy( temp, around[ k ] );
						Vector2Copy( ctrl[ y ][ x ].st, st[ k ] );
						break; // good edge
					}
				}
			}

			VectorClear( sum );
			VectorClear( sumTangents );
			VectorClear( sumBinormals );

			for ( k = 0; k < 8; k++ )
			{
				if ( !good[ k ] || !good[( k + 1 ) & 7 ] )
				{
					continue; // didn't get two points
				}

				CrossProduct( around[( k + 1 ) & 7 ], around[ k ], normal );

				if ( VectorNormalize2( normal, normal ) == 0 )
				{
					continue;
				}

				R_CalcTangents( tangent, binormal,
						vec3_origin, around[ k ], around[ ( k + 1 ) & 7 ],
						dv->st, st[ k ], st[ ( k + 1 ) & 7 ] );

				VectorAdd( normal, sum, sum );
				VectorAdd( tangent, sumTangents, sumTangents );
				VectorAdd( binormal, sumBinormals, sumBinormals );
				count++;
			}

			if ( count == 0 )
			{
				VectorSet( dv->normal, 0.0f, 0.0f, 1.0f );
			} else {
				VectorNormalize2( sum, dv->normal );
			}

			R_TBNtoQtangents( sumTangents, sumBinormals, dv->normal,
					  dv->qtangent );
		}
	}
}