void URuntimeMeshLibrary::CalculateTangentsForMesh(TFunction<int32(int32 Index)> IndexAccessor, TFunction<FVector(int32 Index)> VertexAccessor, TFunction<FVector2D(int32 Index)> UVAccessor,
	TFunction<void(int32 Index, FVector TangentX, FVector TangentY, FVector TangentZ)> TangentSetter, int32 NumVertices, int32 NumUVs, int32 NumIndices, bool bCreateSmoothNormals)
{
	SCOPE_CYCLE_COUNTER(STAT_RuntimeMeshLibrary_CalculateTangentsForMesh);

	if (NumVertices == 0 || NumIndices == 0)
	{
		return;
	}

	// Calculate the duplicate vertices map if we're wanting smooth normals.  Don't find duplicates if we don't want smooth normals
	// that will cause it to only smooth across faces sharing a common vertex, not across faces with vertices of common position
	const TMultiMap<uint32, uint32> DuplicateVertexMap = bCreateSmoothNormals ? FRuntimeMeshInternalUtilities::FindDuplicateVerticesMap(VertexAccessor, NumVertices) : TMultiMap<uint32, uint32>();


	// Number of triangles
	const int32 NumTris = NumIndices / 3;

	// Map of vertex to triangles in Triangles array
	TMultiMap<uint32, uint32> VertToTriMap;
	// Map of vertex to triangles to consider for normal calculation
	TMultiMap<uint32, uint32> VertToTriSmoothMap;

	// Normal/tangents for each face
	TArray<FVector> FaceTangentX, FaceTangentY, FaceTangentZ;
	FaceTangentX.AddUninitialized(NumTris);
	FaceTangentY.AddUninitialized(NumTris);
	FaceTangentZ.AddUninitialized(NumTris);

	// Iterate over triangles
	for (int TriIdx = 0; TriIdx < NumTris; TriIdx++)
	{
		uint32 CornerIndex[3];
		FVector P[3];

		for (int32 CornerIdx = 0; CornerIdx < 3; CornerIdx++)
		{
			// Find vert index (clamped within range)
			uint32 VertIndex = FMath::Min(IndexAccessor((TriIdx * 3) + CornerIdx), NumVertices - 1);

			CornerIndex[CornerIdx] = VertIndex;
			P[CornerIdx] = VertexAccessor(VertIndex);

			// Find/add this vert to index buffer
			TArray<uint32> VertOverlaps;
			DuplicateVertexMap.MultiFind(VertIndex, VertOverlaps);

			// Remember which triangles map to this vert
			VertToTriMap.AddUnique(VertIndex, TriIdx);
			VertToTriSmoothMap.AddUnique(VertIndex, TriIdx);

			// Also update map of triangles that 'overlap' this vert (ie don't match UV, but do match smoothing) and should be considered when calculating normal
			for (int32 OverlapIdx = 0; OverlapIdx < VertOverlaps.Num(); OverlapIdx++)
			{
				// For each vert we overlap..
				int32 OverlapVertIdx = VertOverlaps[OverlapIdx];

				// Add this triangle to that vert
				VertToTriSmoothMap.AddUnique(OverlapVertIdx, TriIdx);

				// And add all of its triangles to us
				TArray<uint32> OverlapTris;
				VertToTriMap.MultiFind(OverlapVertIdx, OverlapTris);
				for (int32 OverlapTriIdx = 0; OverlapTriIdx < OverlapTris.Num(); OverlapTriIdx++)
				{
					VertToTriSmoothMap.AddUnique(VertIndex, OverlapTris[OverlapTriIdx]);
				}
			}
		}

		// Calculate triangle edge vectors and normal
		const FVector Edge21 = P[1] - P[2];
		const FVector Edge20 = P[0] - P[2];
		const FVector TriNormal = (Edge21 ^ Edge20).GetSafeNormal();

		// If we have UVs, use those to calculate
		if (NumUVs == NumVertices)
		{
			const FVector2D T1 = UVAccessor(CornerIndex[0]);
			const FVector2D T2 = UVAccessor(CornerIndex[1]);
			const FVector2D T3 = UVAccessor(CornerIndex[2]);

// 			float X1 = P[1].X - P[0].X;
// 			float X2 = P[2].X - P[0].X;
// 			float Y1 = P[1].Y - P[0].Y;
// 			float Y2 = P[2].Y - P[0].Y;
// 			float Z1 = P[1].Z - P[0].Z;
// 			float Z2 = P[2].Z - P[0].Z;
// 
// 			float S1 = U1.X - U0.X;
// 			float S2 = U2.X - U0.X;
// 			float T1 = U1.Y - U0.Y;
// 			float T2 = U2.Y - U0.Y;
// 
// 			float R = 1.0f / (S1 * T2 - S2 * T1);
// 			FaceTangentX[TriIdx] = FVector((T2 * X1 - T1 * X2) * R, (T2 * Y1 - T1 * Y2) * R,
// 				(T2 * Z1 - T1 * Z2) * R);
// 			FaceTangentY[TriIdx] = FVector((S1 * X2 - S2 * X1) * R, (S1 * Y2 - S2 * Y1) * R,
// 				(S1 * Z2 - S2 * Z1) * R);




			FMatrix	ParameterToLocal(
				FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
				FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
				FPlane(P[0].X, P[0].Y, P[0].Z, 0),
				FPlane(0, 0, 0, 1)
			);

			FMatrix ParameterToTexture(
				FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
				FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
				FPlane(T1.X, T1.Y, 1, 0),
				FPlane(0, 0, 0, 1)
			);

			// Use InverseSlow to catch singular matrices.  Inverse can miss this sometimes.
			const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;

			FaceTangentX[TriIdx] = TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal();
			FaceTangentY[TriIdx] = TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal();
		}
		else
		{
			FaceTangentX[TriIdx] = Edge20.GetSafeNormal();
			FaceTangentY[TriIdx] = (FaceTangentX[TriIdx] ^ TriNormal).GetSafeNormal();
		}

		FaceTangentZ[TriIdx] = TriNormal;
	}


	// Arrays to accumulate tangents into
	TArray<FVector> VertexTangentXSum, VertexTangentYSum, VertexTangentZSum;
	VertexTangentXSum.AddZeroed(NumVertices);
	VertexTangentYSum.AddZeroed(NumVertices);
	VertexTangentZSum.AddZeroed(NumVertices);

	// For each vertex..
	for (int VertxIdx = 0; VertxIdx < NumVertices; VertxIdx++)
	{
		// Find relevant triangles for normal
		TArray<uint32> SmoothTris;
		VertToTriSmoothMap.MultiFind(VertxIdx, SmoothTris);

		for (int i = 0; i < SmoothTris.Num(); i++)
		{
			uint32 TriIdx = SmoothTris[i];
			VertexTangentZSum[VertxIdx] += FaceTangentZ[TriIdx];
		}

		// Find relevant triangles for tangents
		TArray<uint32> TangentTris;
		VertToTriMap.MultiFind(VertxIdx, TangentTris);

		for (int i = 0; i < TangentTris.Num(); i++)
		{
			uint32 TriIdx = TangentTris[i];
			VertexTangentXSum[VertxIdx] += FaceTangentX[TriIdx];
			VertexTangentYSum[VertxIdx] += FaceTangentY[TriIdx];
		}
	}

	// Finally, normalize tangents and build output arrays	
	for (int VertxIdx = 0; VertxIdx < NumVertices; VertxIdx++)
	{
		FVector& TangentX = VertexTangentXSum[VertxIdx];
		FVector& TangentY = VertexTangentYSum[VertxIdx];
		FVector& TangentZ = VertexTangentZSum[VertxIdx];

		TangentX.Normalize();
		//TangentY.Normalize();
		TangentZ.Normalize();

		// Use Gram-Schmidt orthogonalization to make sure X is orthonormal with Z
		TangentX -= TangentZ * (TangentZ | TangentX);
		TangentX.Normalize();
		TangentY.Normalize();


		TangentSetter(VertxIdx, TangentX, TangentY, TangentZ);
	}
}
Ejemplo n.º 2
0
int	SceneIFC::DoUnSmoothVerts(VActor *Thing, INT DoTangentVectorSeams  )
{
	//
	// Special Spherical-harmonics Tangent-space criterium -> unsmooth boundaries where there is
	//

	FaceDeterminants.Empty();

	// Compute face tangents. Face tangent determinants whose signs don't match should not have smoothing between them (i.e. require
	// duplicated vertices.

	if( DoTangentVectorSeams )
	{
		FaceDeterminants.AddZeroed( Thing->SkinData.Faces.Num());

		for( INT FaceIndex=0; FaceIndex< Thing->SkinData.Faces.Num(); FaceIndex++)
		{

			FVector	P1 = Thing->SkinData.Points[ Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[0] ].PointIndex ].Point;
			FVector	P2 = Thing->SkinData.Points[ Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[1] ].PointIndex ].Point;
			FVector	P3 = Thing->SkinData.Points[ Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[2] ].PointIndex ].Point;

			FVector	TriangleNormal = FPlane(P3,P2,P1);
			FMatrix	ParameterToLocal(
				FPlane(	P2.X - P1.X,	P2.Y - P1.Y,	P2.Z - P1.Z,	0	),
				FPlane(	P3.X - P1.X,	P3.Y - P1.Y,	P3.Z - P1.Z,	0	),
				FPlane(	P1.X,			P1.Y,			P1.Z,			0	),
				FPlane(	0,				0,				0,				1	)
				);


			FLOAT T1U= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[0] ].UV.U;
			FLOAT T1V= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[0] ].UV.V;

			FLOAT T2U= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[1] ].UV.U;
			FLOAT T2V= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[1] ].UV.V;

			FLOAT T3U= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[2] ].UV.U;
			FLOAT T3V= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[2] ].UV.V;


			FMatrix	ParameterToTexture(
				FPlane(	T2U - T1U,	T2V - T1V,	0,	0	),
				FPlane(	T3U - T1U,	T3V - T1V,	0,	0	),
				FPlane(	T1U,		T1V,		1,	0	),
				FPlane(	0,			0,			0,	1	)
				);

			FMatrix	TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
			FVector	TangentX = TextureToLocal.TransformNormal(FVector(1,0,0)).SafeNormal(),
					TangentY = TextureToLocal.TransformNormal(FVector(0,1,0)).SafeNormal(),
					TangentZ;

			TangentX = TangentX - TriangleNormal * (TangentX | TriangleNormal);
			TangentY = TangentY - TriangleNormal * (TangentY | TriangleNormal);
			TangentZ = TriangleNormal;

			TangentX.SafeNormal();
			TangentY.SafeNormal();
			TangentZ.SafeNormal();

			FMatrix TangentBasis( TangentX, TangentY, TangentZ );

			TangentBasis.Transpose(); //

			FaceDeterminants[FaceIndex] = TangentBasis.Determinant3x3();
		}
	}


	//
	// Connectivity: triangles with non-matching smoothing groups will be physically split.
	//
	// -> Splitting involves: the UV+material-contaning vertex AND the 3d point.
	//
	// -> Tally smoothing groups for each and every (textured) vertex.
	//
	// -> Collapse:
	// -> start from a vertex and all its adjacent triangles - go over
	// each triangle - if any connecting one (sharing more than one vertex) gives a smoothing match,
	// accumulate it. Then IF more than one resulting section,
	// ensure each boundary 'vert' is split _if not already_ to give each smoothing group
	// independence from all others.
	//

	int DuplicatedVertCount = 0;
	int RemappedHoeks = 0;

	int TotalSmoothMatches = 0;
	int TotalConnexChex = 0;

	// Link _all_ faces to vertices.
	TArray<VertsFans>  Fans;
	TArray<tInfluences> PointInfluences;
	TArray<tWedgeList>  PointWedges;

	Fans.AddExactZeroed(            Thing->SkinData.Points.Num() );
	PointInfluences.AddExactZeroed( Thing->SkinData.Points.Num() );
	PointWedges.AddExactZeroed(     Thing->SkinData.Points.Num() );

	{for(INT i=0; i< Thing->SkinData.RawWeights.Num(); i++)
	{
		PointInfluences[ Thing->SkinData.RawWeights[i].PointIndex ].RawInfIndices.AddItem( i );
	}}

	{for(INT i=0; i< Thing->SkinData.Wedges.Num(); i++)
	{
		PointWedges[ Thing->SkinData.Wedges[i].PointIndex ].WedgeList.AddItem( i );
	}}

	for(INT f=0; f< Thing->SkinData.Faces.Num(); f++ )
	{
		// For each face, add a pointer to that face into the Fans[vertex].
		for( INT i=0; i<3; i++)
		{
			INT WedgeIndex = Thing->SkinData.Faces[f].WedgeIndex[i];
			INT PointIndex = Thing->SkinData.Wedges[ WedgeIndex ].PointIndex;
		    tFaceRecord NewFR;

			NewFR.FaceIndex = f;
			NewFR.HoekIndex = i;
			NewFR.WedgeIndex = WedgeIndex; // This face touches the point courtesy of Wedges[Wedgeindex].
			NewFR.SmoothFlags = Thing->SkinData.Faces[f].SmoothingGroups;
			NewFR.FanFlags = 0;
			Fans[ PointIndex ].FaceRecord.AddItem( NewFR );
			Fans[ PointIndex ].FanGroupCount = 0;
		}
	}

	// Investigate connectivity and assign common group numbers (1..+) to the fans' individual FanFlags.
	for( INT p=0; p< Fans.Num(); p++) // The fan of faces for each 3d point 'p'.
	{
		// All faces connecting.
		if( Fans[p].FaceRecord.Num() > 0 )
		{
			INT FacesProcessed = 0;
			TArray<tFaceSet> FaceSets; // Sets with indices INTO FANS, not into face array.

			// Digest all faces connected to this vertex (p) into one or more smooth sets. only need to check
			// all faces MINUS one..
			while( FacesProcessed < Fans[p].FaceRecord.Num()  )
			{
				// One loop per group. For the current ThisFaceIndex, tally all truly connected ones
				// and put them in a new Tarray. Once no more can be connected, stop.

				INT NewSetIndex = FaceSets.Num(); // 0 to start
				FaceSets.AddZeroed(1);			            // first one will be just ThisFaceIndex.

				// Find the first non-processed face. There will be at least one.
				INT ThisFaceFanIndex = 0;
				{
					INT SearchIndex = 0;
					while( Fans[p].FaceRecord[SearchIndex].FanFlags == -1 ) // -1 indicates already  processed.
					{
						SearchIndex++;
					}
					ThisFaceFanIndex = SearchIndex; //Fans[p].FaceRecord[SearchIndex].FaceIndex;
				}

				// Inital face.
				FaceSets[ NewSetIndex ].Faces.AddItem( ThisFaceFanIndex );   // Add the unprocessed Face index to the "local smoothing group" [NewSetIndex].
				Fans[p].FaceRecord[ThisFaceFanIndex].FanFlags = -1;              // Mark as processed.
				FacesProcessed++;

				// Find all faces connected to this face, and if there's any
				// smoothing group matches, put it in current face set and mark it as processed;
				// until no more match.
				INT NewMatches = 0;
				do
				{
					NewMatches = 0;
					// Go over all current faces in this faceset and set if the FaceRecord (local smoothing groups) has any matches.
					// there will be at least one face already in this faceset - the first face in the fan.
					for( INT n=0; n< FaceSets[NewSetIndex].Faces.Num(); n++)
					{
						INT HookFaceIdx = Fans[p].FaceRecord[ FaceSets[NewSetIndex].Faces[n] ].FaceIndex;

						//Go over the fan looking for matches.
						for( INT s=0; s< Fans[p].FaceRecord.Num(); s++)
						{
							// Skip if same face, skip if face already processed.
							if( ( HookFaceIdx != Fans[p].FaceRecord[s].FaceIndex )  && ( Fans[p].FaceRecord[s].FanFlags != -1  ))
							{
								TotalConnexChex++;
								// Process if connected with more than one vertex, AND smooth..
								if( FacesAreSmoothlyConnected( Thing, HookFaceIdx, Fans[p].FaceRecord[s].FaceIndex ) )
								{
									TotalSmoothMatches++;
									Fans[p].FaceRecord[s].FanFlags = -1; // Mark as processed.
									FacesProcessed++;
									// Add
									FaceSets[NewSetIndex].Faces.AddItem( s ); // Store FAN index of this face index into smoothing group's faces.
									// Tally
									NewMatches++;
								}
							} // not the same...
						}// all faces in fan
					} // all faces in FaceSet
				}while( NewMatches );

			}// Repeat until all faces processed.

			// For the new non-initialized  face sets,
			// Create a new point, influences, and uv-vertex(-ices) for all individual FanFlag groups with an index of 2+ and also remap
			// the face's vertex into those new ones.
			if( FaceSets.Num() > 1 )
			{
				for( INT f=1; f<FaceSets.Num(); f++ )
				{
					// We duplicate the current vertex. (3d point)
					INT NewPointIndex = Thing->SkinData.Points.Num();
					Thing->SkinData.Points.AddItem( Thing->SkinData.Points[p] );

					DuplicatedVertCount++;

					// Duplicate all related weights.
					for( INT t=0; t< PointInfluences[p].RawInfIndices.Num(); t++ )
					{
						// Add new weight
						INT NewWeightIndex = Thing->SkinData.RawWeights.Num();
						Thing->SkinData.RawWeights.AddItem( Thing->SkinData.RawWeights[ PointInfluences[p].RawInfIndices[t] ] );
						Thing->SkinData.RawWeights[NewWeightIndex].PointIndex = NewPointIndex;
					}

					// Duplicate any and all Wedges associated with it; and all Faces' wedges involved.
					for( INT w=0; w< PointWedges[p].WedgeList.Num(); w++)
					{
						INT OldWedgeIndex = PointWedges[p].WedgeList[w];
						INT NewWedgeIndex = Thing->SkinData.Wedges.Num();
						Thing->SkinData.Wedges.AddItem( Thing->SkinData.Wedges[ OldWedgeIndex ] );
						Thing->SkinData.Wedges[ NewWedgeIndex ].PointIndex = NewPointIndex;

						//  Update relevant face's Wedges. Inelegant: just check all associated faces for every new wedge.
						for( INT s=0; s< FaceSets[f].Faces.Num(); s++)
						{
							INT FanIndex = FaceSets[f].Faces[s];
							if( Fans[p].FaceRecord[ FanIndex ].WedgeIndex == OldWedgeIndex )
							{
								// Update just the right one for this face (HoekIndex!)
								Thing->SkinData.Faces[ Fans[p].FaceRecord[ FanIndex].FaceIndex ].WedgeIndex[ Fans[p].FaceRecord[ FanIndex ].HoekIndex ] = NewWedgeIndex;
								RemappedHoeks++;
							}
						}
					}
				}
			} //  if FaceSets.Num(). -> duplicate stuff
		}//	while( FacesProcessed < Fans[p].FaceRecord.Num() )
	} // Fans for each 3d point

	return DuplicatedVertCount;
}