/// <summary> /// Fills in a set of spherically mapped texture coordinates to the passed in mesh /// </summary> private static void SetSphericalTextureCoordinates(Mesh mesh) { BoundingSphere boundingSphere = BufferHelper.ComputeBoundingSphere(mesh); var vertexes = BufferHelper.ReadVertexBuffer <PositionNormalTextured>(mesh); for (int i = 0; i < vertexes.Length; i++) { Vector3 vertexRay = Vector3.Normalize(vertexes[i].Position - boundingSphere.Center); double phi = Math.Acos(vertexRay.Z); vertexes[i].Tu = CalculateTu(vertexRay, phi); vertexes[i].Tv = (float)(phi / Math.PI); } BufferHelper.WriteVertexBuffer(mesh, vertexes); }
/// <summary> /// Sets box texture coordinates where a 3x2 grid of face textures is split one per face /// </summary> private static void SetBoxTextureCoordinates(Mesh mesh) { #region Sanity checks // Check the mesh looks like a box and has texture coordinates if (mesh.VertexCount != 24 || mesh.FaceCount != 12) { throw new ArgumentException("The mesh does not look like a box.", nameof(mesh)); } if (mesh.VertexFormat != PositionNormalTextured.Format) { throw new ArgumentException("The mesh doesn't have the correct format.", nameof(mesh)); } #endregion // Copy the vertex buffer content to an array var vertexes = BufferHelper.ReadVertexBuffer <PositionNormalTextured>(mesh); #region Set texture coordinates // Bottom vertexes[13].Tu = 0f; vertexes[13].Tv = 0f; vertexes[14].Tu = 1f / 3f; vertexes[14].Tv = 0f; vertexes[15].Tu = 1f / 3f; vertexes[15].Tv = 0.5f; vertexes[12].Tu = 0f; vertexes[12].Tv = 0.5f; // Top vertexes[5].Tu = 1f / 3f; vertexes[5].Tv = 0f; vertexes[6].Tu = 2f / 3f; vertexes[6].Tv = 0f; vertexes[7].Tu = 2f / 3f; vertexes[7].Tv = 0.5f; vertexes[4].Tu = 1f / 3f; vertexes[4].Tv = 0.5f; // Back vertexes[18].Tu = 2f / 3f; vertexes[18].Tv = 0f; vertexes[19].Tu = 1f; vertexes[19].Tv = 0f; vertexes[16].Tu = 1f; vertexes[16].Tv = 0.5f; vertexes[17].Tu = 2f / 3f; vertexes[17].Tv = 0.5f; // Left vertexes[2].Tu = 0f; vertexes[2].Tv = 0.5f; vertexes[3].Tu = 1f / 3f; vertexes[3].Tv = 0.5f; vertexes[0].Tu = 1f / 3f; vertexes[0].Tv = 1f; vertexes[1].Tu = 0f; vertexes[1].Tv = 1f; // Front vertexes[21].Tu = 1f / 3f; vertexes[21].Tv = 0.5f; vertexes[22].Tu = 2f / 3f; vertexes[22].Tv = 0.5f; vertexes[23].Tu = 2f / 3f; vertexes[23].Tv = 1f; vertexes[20].Tu = 1f / 3f; vertexes[20].Tv = 1f; // Right vertexes[8].Tu = 2f / 3f; vertexes[8].Tv = 0.5f; vertexes[9].Tu = 1f; vertexes[9].Tv = 0.5f; vertexes[10].Tu = 1f; vertexes[10].Tv = 1f; vertexes[11].Tu = 2f / 3f; vertexes[11].Tv = 1f; #endregion // Copy the array back into the vertex buffer BufferHelper.WriteVertexBuffer(mesh, vertexes); }
/// <summary> /// Generate normals and tangents if not present and convert into TangentVertex format for shaders. /// </summary> /// <param name="device">The <see cref="Device"/> containing the mesh</param> /// <param name="mesh">The mesh to be manipulated</param> /// <param name="weldVertexes">Weld vertexes before generating tangents. /// Useful for organic objects, stones, trees, etc. (anything with a lot of round surfaces). /// If a lot of single faces are not connected on the texture (e.g. rockets, buildings, etc.) do not use.</param> public static void GenerateNormalsAndTangents(Device device, ref Mesh mesh, bool weldVertexes) { #region Sanity checks if (device == null) { throw new ArgumentNullException(nameof(device)); } if (mesh == null) { throw new ArgumentNullException(nameof(mesh)); } #endregion bool hadNormals, hadTangents; if (!ExpandDeclaration(device, ref mesh, out hadNormals, out hadTangents)) { return; } var decl = mesh.GetDeclaration(); #region Check existing info bool gotMilkErmTexCoords = false; bool gotValidNormals = true; bool gotValidTangents = true; var vertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(mesh); // Check all vertexes for (int num = 0; num < vertexes.Length; num++) { // We need at least 1 texture coordinate different from (0, 0) if (vertexes[num].Tu != 0.0f || vertexes[num].Tv != 0.0f) { gotMilkErmTexCoords = true; } // All normals and tangents must be valid, otherwise generate them below if (vertexes[num].Normal == default(Vector3)) { gotValidNormals = false; } if (vertexes[num].Tangent == default(Vector3)) { gotValidTangents = false; } // If we found valid texture coordinates and no normals or tangents, // there isn't anything left to check here if (gotMilkErmTexCoords && !gotValidNormals && !gotValidTangents) { break; } } // If declaration had normals, but we found no valid normals, // set hadNormals to false and generate valid normals (see below) if (!gotValidNormals) { hadNormals = false; } // Same check for tangents if (!gotValidTangents) { hadTangents = false; } // Generate dummy texture coordinates if (!gotMilkErmTexCoords) { for (int num = 0; num < vertexes.Length; num++) { vertexes[num].Tu = -0.75f + vertexes[num].Position.X / 2.0f; vertexes[num].Tv = +0.75f - vertexes[num].Position.Y / 2.0f; } } BufferHelper.WriteVertexBuffer(mesh, vertexes); #endregion if (!hadNormals) { using (new TimedLogEvent("Computed normals")) mesh.ComputeNormals(); } if (weldVertexes) { // Reduce amount of vertexes var weldEpsilons = new WeldEpsilons { Position = 0.0001f, Normal = 0.0001f }; mesh.WeldVertices(WeldFlags.WeldPartialMatches, weldEpsilons); if (!hadTangents) { #region Compute tangents using (new TimedLogEvent("Computed tangents")) { // If the vertexes for a smoothend point exist several times the // DirectX ComputeTangent method is not able to treat them all the // same way. // To circumvent this, we collapse all vertexes in a cloned mesh // even if the texture coordinates don't fit. Then we copy the // generated tangents back to the original mesh vertexes (duplicating // the tangents for vertexes at the same point with the same normals // if required). This happens usually with models exported from 3DSMax. // Clone mesh just for tangent generation Mesh dummyTangentGenerationMesh = mesh.Clone(device, mesh.CreationOptions, decl); // Reuse weldEpsilons, just change the TextureCoordinates, which we don't care about anymore weldEpsilons.TextureCoordinate1 = 1; weldEpsilons.TextureCoordinate2 = 1; weldEpsilons.TextureCoordinate3 = 1; weldEpsilons.TextureCoordinate4 = 1; weldEpsilons.TextureCoordinate5 = 1; weldEpsilons.TextureCoordinate6 = 1; weldEpsilons.TextureCoordinate7 = 1; weldEpsilons.TextureCoordinate8 = 1; // Rest of the weldEpsilons values can stay 0, we don't use them dummyTangentGenerationMesh.WeldVertices(WeldFlags.WeldPartialMatches, weldEpsilons); // Compute tangents if (!CompareDecl(PositionNormalMultiTextured.GetVertexElements(), decl)) { dummyTangentGenerationMesh.ComputeTangent(0, 0, 0, false); } var tangentVertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(dummyTangentGenerationMesh); dummyTangentGenerationMesh.Dispose(); // Copy generated tangents back vertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(mesh); for (int num = 0; num < vertexes.Length; num++) { // Search for tangent vertex with the exact same position and normal. for (int tangentVertexNum = 0; tangentVertexNum < tangentVertexes.Length; tangentVertexNum++) { if (vertexes[num].Position == tangentVertexes[tangentVertexNum].Position && vertexes[num].Normal == tangentVertexes[tangentVertexNum].Normal) { // Copy the tangent over vertexes[num].Tangent = tangentVertexes[tangentVertexNum].Tangent; // No more checks required, proceed with next vertex break; } } } BufferHelper.WriteVertexBuffer(mesh, vertexes); } #endregion } } else { if (!hadTangents && CompareDecl(PositionNormalMultiTextured.GetVertexElements(), decl)) { using (new TimedLogEvent("Computed tangents")) mesh.ComputeTangent(0, 0, D3DX.Default, false); } } Optimize(mesh); }
/// <summary> /// Generate normals if not present and convert into TangentVertex format for shaders. /// Tangent data is left empty /// </summary> /// <param name="device">The <see cref="Device"/> containing the mesh</param> /// <param name="mesh">The mesh to be manipulated</param> public static void GenerateNormals(Device device, ref Mesh mesh) { #region Sanity checks if (device == null) { throw new ArgumentNullException(nameof(device)); } if (mesh == null) { throw new ArgumentNullException(nameof(mesh)); } #endregion bool hadNormals, hadTangents; if (!ExpandDeclaration(device, ref mesh, out hadNormals, out hadTangents)) { return; } #region Check existing info bool gotMilkErmTexCoords = false; bool gotValidNormals = true; var vertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(mesh); // Check all vertexes for (int num = 0; num < vertexes.Length; num++) { // We need at least 1 texture coordinate different from (0, 0) if (vertexes[num].Tu != 0.0f || vertexes[num].Tv != 0.0f) { gotMilkErmTexCoords = true; } // All normals and tangents must be valid, otherwise generate them below if (vertexes[num].Normal == default(Vector3)) { gotValidNormals = false; } } // If declaration had normals, but we found no valid normals, // set hadNormals to false and generate valid normals (see below) if (!gotValidNormals) { hadNormals = false; } // Generate dummy texture coordinates if (!gotMilkErmTexCoords) { for (int num = 0; num < vertexes.Length; num++) { vertexes[num].Tu = -0.75f + vertexes[num].Position.X / 2.0f; vertexes[num].Tv = +0.75f - vertexes[num].Position.Y / 2.0f; } } BufferHelper.WriteVertexBuffer(mesh, vertexes); #endregion // Assume meshes with propper normal data also have been optimized for rendering if (!hadNormals) { using (new TimedLogEvent("Computed normals")) mesh.ComputeNormals(); Optimize(mesh); } }