static Vector2 GetTexCoord(ref SMikkTSpaceContext context, int index) { int iF, iI; Vector2 res = new Vector2(); IndexToData(out iF, out iI, index); res = context.m_pInterface.m_getTexCoord(ref context, iF, iI); return(res); }
static Vector3 GetNormal(ref SMikkTSpaceContext context, int index) { int iF, iI; Vector3 res = new Vector3(); IndexToData(out iF, out iI, index); res = context.m_pInterface.m_getNormal(ref context, iF, iI); return(res); }
public static void GenTangents(Mesh m) { SMikkTSpaceContext ctx = new SMikkTSpaceContext() { m_pInterface = new SMikkTSpaceInterface(), m_pUserdata = m }; genTangSpaceDefault(ref ctx); }
public static void GenTangents(Mesh m, float angularThreshold) { SMikkTSpaceContext ctx = new SMikkTSpaceContext() { m_pInterface = new SMikkTSpaceInterface(), m_pUserdata = m }; genTangSpace(ref ctx, angularThreshold); }
static float CalcTexArea(ref SMikkTSpaceContext context, int[] indices, int offset) { Vector2 t1 = GetTexCoord(ref context, indices[offset + 0]); Vector2 t2 = GetTexCoord(ref context, indices[offset + 1]); Vector2 t3 = GetTexCoord(ref context, indices[offset + 2]); Vector2 t2x = t2 - t1; Vector2 t3x = t3 - t1; float fSignedAreaSTx2 = t2x.X * t3x.Y - t2x.Y * t3x.X; return(fSignedAreaSTx2 < 0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2); }
static void InitTriInfo(ref STriInfo[] pTriInfos, int[] pTriListIn, ref SMikkTSpaceContext context, int iNrTrianglesIn) { int f = 0, i = 0; for (f = 0; f < iNrTrianglesIn; ++f) { pTriInfos[f].faceNeighbors = new int[3]; pTriInfos[f].AssignedGroup = new SGroup[3]; for (i = 0; i < 3; ++i) { pTriInfos[f].faceNeighbors[i] = -1; pTriInfos[f].iFlag |= GROUP_WITH_ANY; } } for (f = 0; f < iNrTrianglesIn; ++f) { Vector3 v1 = GetPosition(ref context, pTriListIn[f * 3]); Vector3 v2 = GetPosition(ref context, pTriListIn[f * 3 + 1]); Vector3 v3 = GetPosition(ref context, pTriListIn[f * 3 + 2]); Vector2 t1 = GetTexCoord(ref context, pTriListIn[f * 3]); Vector2 t2 = GetTexCoord(ref context, pTriListIn[f * 3 + 1]); Vector2 t3 = GetTexCoord(ref context, pTriListIn[f * 3 + 2]); Vector2 t2x = t2 - t1; Vector2 t3x = t3 - t1; Vector3 d1 = v2 - v1; Vector3 d2 = v3 - v1; float signedAreasSTx2 = t2x.X * t3x.Y - t2x.Y * t3x.X; Vector3 vOs = d1 * t3x.Y - d2 * t2x.Y; Vector3 vOt = d1 * -t3x.X - d2 * t2x.X; pTriInfos[f].iFlag |= (signedAreasSTx2 > 0 ? ORIENT_PRESERVING : 0); if (NotZero(signedAreasSTx2)) { float fAbsArea = Math.Abs(signedAreasSTx2); float fLenOs = vOs.Length; float fLenOt = vOt.Length; float fS = ((pTriInfos[f].iFlag & ORIENT_PRESERVING) == 0) ? -1.0f : 1.0f; if (NotZero(fLenOs)) { pTriInfos[f].vOs = fS / fLenOs * vOs; } if (NotZero(fLenOt)) { pTriInfos[f].vOt = fS / fLenOt * vOt; } pTriInfos[f].fMagS = fLenOs / fAbsArea; pTriInfos[f].fMagT = fLenOt / fAbsArea; if (NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) { pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); } } } { SEdge[] pEdges = new SEdge[iNrTrianglesIn * 3]; //build neighbors fast BuildNeighborsFast(ref pTriInfos, ref pEdges, pTriListIn, iNrTrianglesIn); } }
public Vector3 m_getNormal(ref SMikkTSpaceContext context, int iFace, int iVert) { Mesh m = context.m_pUserdata as Mesh; return(m.GetNormal(iFace, iVert)); }
static bool GenerateTSpaces(ref STSpace[] psTspace, ref STriInfo[] pTriInfos, SGroup[] pGroups, int iNrActiveGroups, int[] piTriListIn, int[] pGroupTriangleBuffer, float fThresCos, ref SMikkTSpaceContext context) { STSpace[] pSubGroupTSpace = null; SSubGroup[] pUniSubGroups = null; int[] pTmpMembers = null; int iMaxNrFaces = 0, iUniqueTSpaces = 0, g = 0, i = 0; for (g = 0; g < iNrActiveGroups; ++g) { if (iMaxNrFaces < pGroups[g].iNrFaces) { iMaxNrFaces = pGroups[g].iNrFaces; } } if (iMaxNrFaces == 0) { return(true); } pSubGroupTSpace = new STSpace[iMaxNrFaces]; pUniSubGroups = new SSubGroup[iMaxNrFaces]; pTmpMembers = new int[iMaxNrFaces]; iUniqueTSpaces = 0; for (g = 0; g < iNrActiveGroups; ++g) { int iUniqueSubGroups = 0; for (i = 0; i < pGroups[g].iNrFaces; ++i) { int f = pGroupTriangleBuffer[pGroups[g].faceOffset]; int index = -1, iVertIndex = -1, iOF_1 = -1, iMembers = 0, j = 0, l = 0; SSubGroup tmp_group; bool bFound = false; Vector3 n = Vector3.Zero, vOs = Vector3.Zero, vOt = Vector3.Zero; if (pTriInfos[f].AssignedGroup[0] == pGroups[g]) { index = 0; } else if (pTriInfos[f].AssignedGroup[1] == pGroups[g]) { index = 1; } else if (pTriInfos[f].AssignedGroup[2] == pGroups[g]) { index = 2; } iVertIndex = piTriListIn[f * 3 + index]; n = GetNormal(ref context, iVertIndex); vOs = pTriInfos[f].vOs - (Vector3.Dot(n, pTriInfos[f].vOs) * n); vOt = pTriInfos[f].vOt - (Vector3.Dot(n, pTriInfos[f].vOt) * n); if (VNotZero(ref vOs)) { vOs.Normalize(); } if (VNotZero(ref vOt)) { vOt.Normalize(); } iOF_1 = pTriInfos[f].iOrgFaceNumber; iMembers = 0; for (j = 0; j < pGroups[g].iNrFaces; ++j) { int t = pGroupTriangleBuffer[pGroups[g].faceOffset + j]; int iOf_2 = pTriInfos[t].iOrgFaceNumber; Vector3 vOs2 = (pTriInfos[t].vOs - (Vector3.Dot(n, pTriInfos[t].vOs) * n)); Vector3 vOt2 = (pTriInfos[t].vOt - (Vector3.Dot(n, pTriInfos[t].vOt) * n)); if (VNotZero(ref vOs2)) { vOs2.Normalize(); } if (VNotZero(ref vOt2)) { vOt2.Normalize(); } { bool bAny = ((pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY) != 0; bool bSameOrgFace = iOF_1 == iOf_2; float fCosS = Vector3.Dot(vOs, vOs2); float fCosT = Vector3.Dot(vOt, vOt2); if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos)) { pTmpMembers[iMembers++] = t; } } } tmp_group.iNrFaces = iMembers; tmp_group.triMembers = new List <int>(pTmpMembers); if (iMembers > 1) { tmp_group.triMembers.Sort(); } bFound = false; l = 0; while (l < iUniqueSubGroups && !bFound) { bFound = CompareSubGroups(ref tmp_group, ref pUniSubGroups[l]); if (!bFound) { ++l; } } if (!bFound) { pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers; pUniSubGroups[iUniqueSubGroups].triMembers = new List <int>(tmp_group.triMembers); pSubGroupTSpace[iUniqueSubGroups] = EvalTspace(tmp_group.triMembers, iMembers, piTriListIn, ref pTriInfos, ref context, pGroups[g].iVertex); iUniqueSubGroups++; } { int iOffs = pTriInfos[f].iTSpacesOffs; int iVert = pTriInfos[f].vert_num[index]; if (psTspace[iOffs + iVert].iCounter == 1) { psTspace[iOffs + iVert] = AvgTSpace(ref psTspace[iOffs + iVert], ref pSubGroupTSpace[l]); psTspace[iOffs + iVert].iCounter = 2; psTspace[iOffs + iVert].bOrient = pGroups[g].bOrientPreservering; } else { psTspace[iOffs + iVert] = pSubGroupTSpace[l]; psTspace[iOffs + iVert].iCounter = 1; psTspace[iOffs + iVert].bOrient = pGroups[g].bOrientPreservering; } } } iUniqueTSpaces += iUniqueSubGroups; } return(true); }
public int m_getNumFaces(ref SMikkTSpaceContext context) { Mesh m = context.m_pUserdata as Mesh; return(m.triangles.Count); }
static void MergeVertsFast(int[] piTriList_in_and_out, ref STmpVert[] pTmpVert, ref SMikkTSpaceContext context, int iL_in, int iR_in) { int c = 0, l = 0, channel = 0; float[] fvMin = new float[3], fvMax = new float[3]; float dx = 0, dy = 0, dz = 0, fSep = 0; for (c = 0; c < 3; c++) { fvMin[c] = pTmpVert[iL_in].vert[c]; fvMax[c] = fvMin[c]; } for (l = (iL_in + 1); l <= iR_in; l++) { for (c = 0; c < 3; c++) { if (fvMin[c] > pTmpVert[l].vert[c]) { fvMin[c] = pTmpVert[l].vert[c]; } else if (fvMax[c] < pTmpVert[l].vert[c]) { fvMax[c] = pTmpVert[l].vert[c]; } } } dx = fvMax[0] - fvMin[0]; dy = fvMax[1] - fvMin[1]; dz = fvMax[2] - fvMin[2]; channel = 0; if (dy > dx && dy > dz) { channel = 1; } else if (dz > dx) { channel = 2; } fSep = 0.5f * (fvMax[channel] + fvMin[channel]); if (fSep >= fvMax[channel] || fSep <= fvMin[channel]) { for (l = iL_in; l <= iR_in; l++) { int i = pTmpVert[l].index; int index = piTriList_in_and_out[i]; Vector3 vP = GetPosition(ref context, index); Vector3 vN = GetNormal(ref context, index); Vector2 vT = GetTexCoord(ref context, index); bool bNotFound = true; int l2 = iL_in, i2rec = -1; while (l2 < l && bNotFound) { int i2 = pTmpVert[l2].index; int index2 = piTriList_in_and_out[i2]; Vector3 vP2 = GetPosition(ref context, index2); Vector3 vN2 = GetNormal(ref context, index2); Vector2 vT2 = GetTexCoord(ref context, index2); i2rec = i2; if (vP.Equals(vP2) && vN.Equals(vN2) && vT.Equals(vT2)) { bNotFound = false; } else { l2++; } } if (!bNotFound) { piTriList_in_and_out[i] = piTriList_in_and_out[i2rec]; } } } else { int iL = iL_in, iR = iR = iR_in; while (iL < iR) { bool bReadyLeftSwap = false, bReadyRightSwap = false; while ((!bReadyLeftSwap) && iL < iR) { bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep); if (!bReadyLeftSwap) { ++iL; } } while ((!bReadyRightSwap) && iL < iR) { bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep; if (!bReadyRightSwap) { --iR; } } if (bReadyLeftSwap && bReadyRightSwap) { STmpVert sTmp = pTmpVert[iL]; pTmpVert[iL] = pTmpVert[iR]; pTmpVert[iR] = sTmp; ++iL; --iR; } } if (iL == iR) { bool bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep; if (bReadyRightSwap) { ++iL; } else { --iR; } } if (iL_in < iR) { MergeVertsFast(piTriList_in_and_out, ref pTmpVert, ref context, iL_in, iR); } if (iL < iR_in) { MergeVertsFast(piTriList_in_and_out, ref pTmpVert, ref context, iL, iR_in); } } }
static void GenerateSharedVerticesIndexList(int[] piTriList_in_and_out, ref SMikkTSpaceContext pContext, int iNrTrianglesIn) { int[] piHashTable = null, piHashCount = null, piHashOffsets = null, piHashCount2 = null; STmpVert[] pTmpVer = null; int i = 0, iChannel = 0, k = 0, e = 0; int iMaxCount = 0; Vector3 vMin = GetPosition(ref pContext, 0), vMax = vMin, vDim = new Vector3(); float fMin, fMax; for (i = 1; i < (iNrTrianglesIn * 3); ++i) { int index = piTriList_in_and_out[i]; Vector3 vP = GetPosition(ref pContext, index); if (vMin.X > vP.X) { vMin.X = vP.X; } else if (vMax.X < vP.X) { vMax.X = vP.X; } if (vMin.Y > vP.Y) { vMin.Y = vP.Y; } else if (vMax.Y < vP.Y) { vMax.Y = vP.Y; } if (vMin.Z > vP.Z) { vMin.Z = vP.Z; } else if (vMax.Z < vP.Z) { vMax.Z = vP.Z; } } vDim = vMax - vMin; iChannel = 0; fMin = vMin.X; fMax = vMax.X; if (vDim.Y > vDim.X && vDim.Y > vDim.Z) { iChannel = 1; fMin = vMin.Y; fMax = vMax.Y; } else if (vDim.Z > vDim.X) { iChannel = 2; fMin = vMin.Z; fMax = vMax.Z; } piHashTable = new int[iNrTrianglesIn * 3]; piHashCount = new int[g_iCells]; piHashOffsets = new int[g_iCells]; piHashCount2 = new int[g_iCells]; try { for (i = 0; i < iNrTrianglesIn * 3; ++i) { int index = piTriList_in_and_out[i]; Vector3 vP = GetPosition(ref pContext, index); float fVal = iChannel == 0 ? vP.X : (iChannel == 1) ? vP.Y : vP.Z; int iCell = FindGridCell(fMin, fMax, fVal); piHashCount[iCell]++; } } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } piHashOffsets[0] = 0; for (k = 1; k < g_iCells; ++k) { piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1]; } try { for (i = 0; i < iNrTrianglesIn * 3; ++i) { int index = piTriList_in_and_out[i]; Vector3 vP = GetPosition(ref pContext, index); float fVal = iChannel == 0 ? vP.X : (iChannel == 1) ? vP.Y : vP.Z; int iCell = FindGridCell(fMin, fMax, fVal); piHashTable[piHashOffsets[iCell] + piHashCount2[iCell]] = i; piHashCount2[iCell]++; } } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } piHashCount2 = null; iMaxCount = piHashCount[0]; for (k = 1; k < g_iCells; ++k) { if (iMaxCount < piHashCount[k]) { iMaxCount = piHashCount[k]; } } pTmpVer = new STmpVert[iMaxCount]; try { for (k = 0; k < g_iCells; ++k) { int entries = piHashCount[k]; if (entries < 2) { continue; } for (e = 0; e < entries; e++) { pTmpVer[e].vert = new float[3]; int iv = piHashTable[piHashOffsets[k] + e]; Vector3 vP = GetPosition(ref pContext, piTriList_in_and_out[iv]); pTmpVer[e].vert[0] = vP.X; pTmpVer[e].vert[1] = vP.Y; pTmpVer[e].vert[2] = vP.Z; pTmpVer[e].index = iv; } //merge verts fast here MergeVertsFast(piTriList_in_and_out, ref pTmpVer, ref pContext, 0, entries - 1); } } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } pTmpVer = null; }
/// <summary> /// this version of Mikktspace only deals with triangles /// in order to save sometime in moving the code over to c# /// </summary> /// <param name="context"></param> /// <returns></returns> public static bool genTangSpace(ref SMikkTSpaceContext context, float fAngularThreshold) { int[] piTriListIn = null; int[] piGroupTrianglesBuffer = null; STriInfo[] pTriInfos = null; SGroup[] pGroups = null; STSpace[] psTspace = null; int iNrTrianglesIn = 0, f = 0, t = 0, i = 0; int iNrTSPaces = 0, iNrMaxGroups = 0; int iNrActiveGroups = 0, index = 0; int iNrFaces = context.m_pInterface.m_getNumFaces(ref context); float fThresCos = (float)Math.Cos((fAngularThreshold * ((float)Math.PI / 180.0f))); for (f = 0; f < iNrFaces; f++) { int verts = context.m_pInterface.m_getNumVerticesOfFace(ref context, f); if (verts == 3) { iNrTrianglesIn++; } } if (iNrTrianglesIn <= 0) { return(false); } piTriListIn = new int[3 * iNrTrianglesIn]; pTriInfos = new STriInfo[iNrTrianglesIn]; iNrTSPaces = GenerateInitialVerticesIndexList(ref pTriInfos, piTriListIn, ref context, iNrTrianglesIn); GenerateSharedVerticesIndexList(piTriListIn, ref context, iNrTrianglesIn); InitTriInfo(ref pTriInfos, piTriListIn, ref context, iNrTrianglesIn); iNrMaxGroups = iNrTrianglesIn * 3; pGroups = new SGroup[iNrMaxGroups]; for (f = 0; f < iNrMaxGroups; f++) { pGroups[f] = new SGroup(); } piGroupTrianglesBuffer = new int[iNrTrianglesIn * 3]; try { iNrActiveGroups = Build4RuleGroups(ref pTriInfos, ref pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn); psTspace = new STSpace[iNrTSPaces]; for (t = 0; t < iNrTSPaces; t++) { psTspace[t].vOs.X = 1.0f; psTspace[t].fMagS = 1.0f; psTspace[t].vOt.Y = 1.0f; psTspace[t].fMagT = 1.0f; } GenerateTSpaces(ref psTspace, ref pTriInfos, pGroups, iNrActiveGroups, piTriListIn, piGroupTrianglesBuffer, fThresCos, ref context); index = 0; for (f = 0; f < iNrFaces; f++) { int verts = context.m_pInterface.m_getNumVerticesOfFace(ref context, f); if (verts != 3) { continue; } for (i = 0; i < verts; ++i) { var pSpace = psTspace[index]; float[] tang = new float[] { pSpace.vOs.X, pSpace.vOs.Y, pSpace.vOs.Z }; float[] bitang = new float[] { pSpace.vOt.X, pSpace.vOt.Y, pSpace.vOt.Z }; context.m_pInterface.m_setTSpace(ref context, tang, bitang, pSpace.fMagS, pSpace.fMagT, pSpace.bOrient, f, i); context.m_pInterface.m_setTSpaceBasic(ref context, tang, pSpace.bOrient ? 1 : -1, f, i); ++index; } } } catch (Exception e) { Console.WriteLine(e.StackTrace); } return(true); }
/// <summary> /// this version of Mikktspace only deals with triangles /// in order to save sometime in moving the code over to c# /// </summary> /// <param name="context"></param> /// <returns></returns> public static bool genTangSpaceDefault(ref SMikkTSpaceContext context) { return(genTangSpace(ref context, 180.0f)); }
public Vector2 m_getTexCoord(ref SMikkTSpaceContext context, int iFace, int iVert) { Mesh m = context.m_pUserdata as Mesh; return(m.GetUV(iFace, iVert)); }
//we return 3 because we triangulate the mesh before we put it through here public int m_getNumVerticesOfFace(ref SMikkTSpaceContext context, int iFace) { return(3); }
public void m_setTSpaceBasic(ref SMikkTSpaceContext context, float[] fvTangent, float fSign, int iFace, int iVert) { Mesh m = context.m_pUserdata as Mesh; m.SetTangent(fvTangent, fSign, iFace, iVert); }
static int GenerateInitialVerticesIndexList(ref STriInfo[] pTriInfos, int[] piTriList_out, ref SMikkTSpaceContext context, int iNrTrianglesIn) { int iTSpacesOffs = 0, f = 0, t = 0; int iDstTriIndex = 0; for (f = 0; f < context.m_pInterface.m_getNumFaces(ref context); f++) { int verts = context.m_pInterface.m_getNumVerticesOfFace(ref context, f); if (verts != 3 && verts != 4) { continue; } pTriInfos[iDstTriIndex].iOrgFaceNumber = f; pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; if (verts == 3) { if (pTriInfos[iDstTriIndex].vert_num == null) { pTriInfos[iDstTriIndex].vert_num = new int[3]; } int[] pVerts = pTriInfos[iDstTriIndex].vert_num; pVerts[0] = 0; pVerts[1] = 1; pVerts[2] = 2; piTriList_out[iDstTriIndex * 3] = MakeIndex(f, 0); piTriList_out[iDstTriIndex * 3 + 1] = MakeIndex(f, 1); piTriList_out[iDstTriIndex * 3 + 2] = MakeIndex(f, 2); ++iDstTriIndex; } else { continue; } iTSpacesOffs += verts; } for (t = 0; t < iNrTrianglesIn; t++) { pTriInfos[t].iFlag = 0; } return(iTSpacesOffs); }
public void m_setTSpace(ref SMikkTSpaceContext context, float[] fvTangent, float[] fvBiTangent, float fMagS, float fMagT, bool bIsOrientationPreserving, int iFace, int iVert) { }
static STSpace EvalTspace(List <int> face_indices, int iFaces, int[] piTriListIn, ref STriInfo[] pTriInfos, ref SMikkTSpaceContext context, int iVertexRepresentitive) { STSpace res = new STSpace(); float fAngleSum = 0; int face = 0; res.vOs = Vector3.Zero; res.vOt = Vector3.Zero; res.fMagS = 0; res.fMagT = 0; for (face = 0; face < iFaces; face++) { int f = face_indices[face]; if ((pTriInfos[f].iFlag & GROUP_WITH_ANY) == 0) { Vector3 n, vOs, vOt, p0, p1, p2, v1, v2; float fCos, fAngle, fMagS, fMagT; int i = -1, index = -1, i0 = -1, i1 = -1, i2 = -1; if (piTriListIn[3 * f] == iVertexRepresentitive) { i = 0; } else if (piTriListIn[3 * f + 1] == iVertexRepresentitive) { i = 1; } else if (piTriListIn[3 * f + 2] == iVertexRepresentitive) { i = 2; } index = piTriListIn[3 * f + i]; n = GetNormal(ref context, index); vOs = pTriInfos[f].vOs - (Vector3.Dot(n, pTriInfos[f].vOs) * n); vOt = pTriInfos[f].vOt - (Vector3.Dot(n, pTriInfos[f].vOt) * n); if (VNotZero(ref vOs)) { vOs.Normalize(); } if (VNotZero(ref vOt)) { vOt.Normalize(); } i2 = piTriListIn[3 * f + (i < 2 ? (i + 1) : 0)]; i1 = piTriListIn[3 * f + i]; i0 = piTriListIn[3 * f + (i > 0 ? (i - 1) : 2)]; p0 = GetPosition(ref context, i0); p1 = GetPosition(ref context, i1); p2 = GetPosition(ref context, i2); v1 = p0 - p1; v2 = p2 - p1; v1 = v1 - Vector3.Dot(n, v1) * n; v2 = v2 - Vector3.Dot(n, v2) * n; if (VNotZero(ref v1)) { v1.Normalize(); } if (VNotZero(ref v2)) { v2.Normalize(); } fCos = Vector3.Dot(v1, v2); fCos = fCos > 1 ? 1 : (fCos < -1 ? -1 : fCos); fAngle = (float)Math.Acos(fCos); fMagS = pTriInfos[f].fMagS; fMagT = pTriInfos[f].fMagT; res.vOs = res.vOs + vOs * fAngle; res.vOt = res.vOt + vOt * fAngle; res.fMagS += (fAngle * fMagS); res.fMagT += (fAngle * fMagT); fAngleSum += fAngle; } } if (VNotZero(ref res.vOs)) { res.vOs.Normalize(); } if (VNotZero(ref res.vOt)) { res.vOt.Normalize(); } if (fAngleSum > 0) { res.fMagS /= fAngleSum; res.fMagT /= fAngleSum; } return(res); }