/// <summary> /// Calculate tangents and bitangents for each vertex in order using the method described at /// http://www.terathon.com/code/tangent.html /// </summary> public void CalculateTangentsAndBitangents() { GrimModelVertexArray vertexArray = VertexArrays[VERTEX_ARRAY_POSITION]; GrimModelVertexArray texArray = VertexArrays[VERTEX_ARRAY_TEXCOORD0]; GrimModelVertexArray normalArray = VertexArrays[VERTEX_ARRAY_NORMAL]; float[][] vertexData = vertexArray.getVertexDataAsFloatArray(); float[][] texData = texArray.getVertexDataAsFloatArray(); GrimVec3[] normalData = normalArray.getVertexDataAsVec3Array(); if (vertexData == null || texData == null || normalData == null) { Logger.Instance.Error("Need position, normal and texture data in existing mesh to calculate tangents."); return; } List <GrimVec3>[] vertexTangents = new List <GrimVec3> [vertexData.Length]; List <GrimVec3>[] vertexBitangents = new List <GrimVec3> [vertexData.Length]; GrimVec3[] tangentData = new GrimVec3[vertexData.Length]; GrimVec3[] bitangentData = new GrimVec3[vertexData.Length]; foreach (GrimModelMeshSegment segment in Segments) { // Reset all of the values for this segment for (int i = 0; i < vertexData.Length; i++) { vertexTangents[i] = new List <GrimVec3>(); vertexBitangents[i] = new List <GrimVec3>(); } // Calculate the range of indices we are working with int firstIndex = segment.FirstIndex; int lastIndex = firstIndex + segment.TriCount * 3; // Go over each triangle in this segment and calculate tangent / bitangent data for the related vertices for (int i = firstIndex; i < lastIndex; i += 3) { uint[] triIndices = new uint[] { Indices[i], Indices[i + 1], Indices[i + 2] }; GrimVec3[] triVerts = new GrimVec3[] { new GrimVec3(vertexData[triIndices[0]]), new GrimVec3(vertexData[triIndices[1]]), new GrimVec3(vertexData[triIndices[2]]) }; float[][] triUVs = new float[][] { texData[triIndices[0]], texData[triIndices[1]], texData[triIndices[2]] }; GrimVec3 side0 = triVerts[1] - triVerts[0]; GrimVec3 side1 = triVerts[2] - triVerts[0]; float dU0 = triUVs[1][0] - triUVs[0][0]; float dU1 = triUVs[2][0] - triUVs[0][0]; float dV0 = triUVs[1][1] - triUVs[0][1]; float dV1 = triUVs[2][1] - triUVs[0][1]; GrimVec3 tangent = dV1 * side0 - dV0 * side1; GrimVec3 bitangent = dU1 * side0 - dU0 * side1; GrimVec3 tangentCross = GrimVec3.Cross(tangent, bitangent); // Normalize at the end now // tangent.Normalize(); // bitangent.Normalize(); //wind the tangent into the other direction if necassary; means: if the UVs are mirrored //http://www.opentk.com/node/2520 /* * if (GrimVec3.Dot(surfaceNormal, tangentCross) < 0.0f) * { * biTangent *= -1.0f; * tangent *= -1.0f; * } */ for (int j = 0; j < 3; j++) { vertexTangents[triIndices[j]].Add(tangent); vertexBitangents[triIndices[j]].Add(bitangent); } } // Go over each vertex, see if we have created 1+ tangents for that vertex and do some maths if yes. for (int i = 0; i < vertexData.Length; i++) { List <GrimVec3> t = vertexTangents[i]; List <GrimVec3> b = vertexBitangents[i]; if (t.Count != b.Count || t.Count == 0) { continue; } tangentData[i] = new GrimVec3(); bitangentData[i] = new GrimVec3(); for (int j = 0; j < t.Count; j++) { tangentData[i] = tangentData[i] + t[j]; bitangentData[i] = bitangentData[i] + b[j]; } tangentData[i] = (tangentData[i] - (normalData[i] * GrimVec3.Dot(normalData[i], tangentData[i]))); bitangentData[i] = (bitangentData[i] - (normalData[i] * GrimVec3.Dot(normalData[i], bitangentData[i]))); tangentData[i].Normalize(); bitangentData[i].Normalize(); } } VertexArrays[VERTEX_ARRAY_TANGENT].putVertexDataAsVec3Array(tangentData); VertexArrays[VERTEX_ARRAY_BITANGENT].putVertexDataAsVec3Array(bitangentData); }
/// <summary> /// Destroy the normal data for this mesh and recalculate them using a simple vertex averaging system. /// </summary> public void CalculateAverageVertexNormals(bool simpleWeighting = true) { GrimModelVertexArray vertexArray = VertexArrays[VERTEX_ARRAY_POSITION]; float[][] vertexData = vertexArray.getVertexDataAsFloatArray(); if (vertexData == null) { Logger.Instance.Error("No position vertex data in existing mesh."); return; } List <GrimVec3>[] vertexValues = new List <GrimVec3> [vertexData.Length]; float[][] normalData = new float[vertexData.Length][]; // Initialise all of the normals incase we don't compute any (the data needs to be the right size) for (int i = 0; i < vertexData.Length; i++) { normalData[i] = new float[3]; } // Calculate the normal for each triangle and store that value for each vertex of that face (some vertices will have multiple values in a list). // We set each Normal Pt index to the same index as the vertex, as we are making these normals from scratch - we might as well make them parallel. foreach (GrimModelMeshSegment segment in Segments) { // Reset all of the values for this segment for (int i = 0; i < vertexData.Length; i++) { vertexValues[i] = new List <GrimVec3>(); } // Calculate the range of indices we are working with int firstIndex = segment.FirstIndex; int lastIndex = firstIndex + segment.TriCount * 3; // Iterate over each vertex and calculate a normal at that point for each face it is used in for (int i = firstIndex; i < lastIndex; i += 3) { uint[] triIndices = new uint[] { Indices[i], Indices[i + 1], Indices[i + 2] }; GrimVec3[] verts = new GrimVec3[] { new GrimVec3(vertexData[triIndices[0]]), new GrimVec3(vertexData[triIndices[1]]), new GrimVec3(vertexData[triIndices[2]]) }; GrimVec3 normal = GrimVec3.Cross((verts[1] - verts[0]), (verts[2] - verts[0])); // Apply Weighting if (simpleWeighting) { for (int j = 0; j < 3; j++) { GrimVec3 a = verts[(j + 1) % 3] - verts[j]; GrimVec3 b = verts[(j + 2) % 3] - verts[j]; float weight = (float)Math.Acos(GrimVec3.Dot(a, b) / (a.Length() * b.Length())); normal *= weight; } } vertexValues[triIndices[0]].Add(normal); vertexValues[triIndices[1]].Add(normal); vertexValues[triIndices[2]].Add(normal); } for (int i = 0; i < vertexValues.Length; i++) { List <GrimVec3> vertexNormals = vertexValues[i]; if (vertexNormals.Count == 0) { continue; } GrimVec3 normal = new GrimVec3(); for (int j = 0; j < vertexNormals.Count; j++) { normal = normal + vertexNormals[j]; } normal.Normalize(); normalData[i] = normal.ToFloatArray(); } } VertexArrays[VERTEX_ARRAY_NORMAL].putVertexDataAsFloatArray(normalData); }