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); } }
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; }