/// <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> /// Creates a model of a textured round disc with a hole in the middle. /// </summary> /// <param name="device">The <see cref="Device"/> to create the mesh in.</param> /// <param name="radiusInner">The radius of the inner circle of the ring.</param> /// <param name="radiusOuter">The radius of the outer circle of the ring.</param> /// <param name="height">The height of the ring.</param> /// <param name="segments">The number of segments the ring shall consist of.</param> public static Mesh Disc(Device device, float radiusInner, float radiusOuter, float height, int segments) { #region Sanity checks if (device == null) { throw new ArgumentNullException(nameof(device)); } #endregion const float tuInner = 0, tuOuter = 1; Log.Info("Generate predefined model: Disc"); var posInner = new Vector3(radiusInner, 0, 0); var posOuter = new Vector3(radiusOuter, 0, 0); #region Generate vertexes int vertCount = 0; var step = (float)(Math.PI * 2 / segments); var vertexes = new PositionTextured[segments * 4]; for (int i = 0; i < segments; i++) { vertexes[vertCount++] = new PositionTextured(posInner.X, posInner.Y - height / 2, posInner.Z, tuInner, 0); vertexes[vertCount++] = new PositionTextured(posInner.X, posInner.Y + height / 2, posInner.Z, tuInner, 0); vertexes[vertCount++] = new PositionTextured(posOuter.X, posOuter.Y - height / 2, posOuter.Z, tuOuter, 0); vertexes[vertCount++] = new PositionTextured(posOuter.X, posOuter.Y + height / 2, posOuter.Z, tuOuter, 0); // Increment rotation posInner = Vector3.TransformCoordinate(posInner, Matrix.RotationY(step)); posOuter = Vector3.TransformCoordinate(posOuter, Matrix.RotationY(step)); } #endregion #region Generate indexes int indexCount = 0; var indexes = new short[segments * 24]; for (int i = 0; i < segments; i++) { short innerBottom1 = (short)(i * 4), innerTop1 = (short)(i * 4 + 1); short outerBottom1 = (short)(i * 4 + 2), outerTop1 = (short)(i * 4 + 3); short innerBottom2 = (short)(i * 4 + 4), innerTop2 = (short)(i * 4 + 5); short outerBottom2 = (short)(i * 4 + 6), outerTop2 = (short)(i * 4 + 7); if (innerBottom2 >= vertexes.Length) { innerBottom2 -= (short)vertexes.Length; } if (innerTop2 >= vertexes.Length) { innerTop2 -= (short)vertexes.Length; } if (outerBottom2 >= vertexes.Length) { outerBottom2 -= (short)vertexes.Length; } if (outerTop2 >= vertexes.Length) { outerTop2 -= (short)vertexes.Length; } // Bottom 2 triangles indexes[indexCount++] = innerBottom1; indexes[indexCount++] = innerBottom2; indexes[indexCount++] = outerBottom2; indexes[indexCount++] = innerBottom1; indexes[indexCount++] = outerBottom2; indexes[indexCount++] = outerBottom1; // Top 2 triangles indexes[indexCount++] = innerTop1; indexes[indexCount++] = outerTop2; indexes[indexCount++] = innerTop2; indexes[indexCount++] = innerTop1; indexes[indexCount++] = outerTop1; indexes[indexCount++] = outerTop2; // Inner 2 triangles indexes[indexCount++] = innerTop1; indexes[indexCount++] = innerBottom2; indexes[indexCount++] = innerBottom1; indexes[indexCount++] = innerTop1; indexes[indexCount++] = innerTop2; indexes[indexCount++] = innerBottom2; // Outer 2 triangles indexes[indexCount++] = outerBottom1; indexes[indexCount++] = outerBottom2; indexes[indexCount++] = outerTop1; indexes[indexCount++] = outerBottom2; indexes[indexCount++] = outerTop2; indexes[indexCount++] = outerTop1; } #endregion var mesh = new Mesh(device, indexes.Length / 3, vertexes.Length, MeshFlags.Managed, PositionTextured.Format); BufferHelper.WriteVertexBuffer(mesh, vertexes); BufferHelper.WriteIndexBuffer(mesh, indexes); MeshHelper.GenerateNormalsAndTangents(device, ref mesh, false); return(mesh); }
/// <summary> /// Creates a new <see cref="Mesh"/> representing a textured sphere with spherical mapping. /// </summary> /// <param name="device">The <see cref="Device"/> to create the mesh in</param> /// <param name="radius">The radius of the sphere.</param> /// <param name="slices">The number of vertical slices to divide the sphere into.</param> /// <param name="stacks">The number of horizontal stacks to divide the sphere into.</param> /// <remarks>The sphere is formed like the one created by <see cref="Mesh.CreateSphere"/>.</remarks> public static Mesh Sphere(Device device, float radius, int slices, int stacks) { #region Sanity checks if (device == null) { throw new ArgumentNullException(nameof(device)); } if (slices <= 0) { throw new ArgumentOutOfRangeException(nameof(slices)); } if (stacks <= 0) { throw new ArgumentOutOfRangeException(nameof(stacks)); } #endregion int numVertexes = (slices + 1) * (stacks + 1); int numFaces = slices * stacks * 2; int indexCount = numFaces * 3; var mesh = new Mesh(device, numFaces, numVertexes, MeshFlags.Managed, PositionNormalTextured.Format); #region Build sphere vertexes var vertexes = new PositionNormalTextured[mesh.VertexCount]; int vertIndex = 0; for (int slice = 0; slice <= slices; slice++) { float alphaY = (float)slice / slices * (float)Math.PI * 2.0f; // Angle around Y-axis for (int stack = 0; stack <= stacks; stack++) { if (slice == slices) { vertexes[vertIndex] = vertexes[stack]; } else { var pnt = new PositionNormalTextured(); float alphaZ = ((stack - stacks * 0.5f) / stacks) * (float)Math.PI * 1.0f; // Angle around Z-axis pnt.X = (float)(Math.Cos(alphaY) * radius) * (float)Math.Cos(alphaZ); pnt.Z = (float)(Math.Sin(alphaY) * radius) * (float)Math.Cos(alphaZ); pnt.Y = (float)(Math.Sin(alphaZ) * radius); pnt.Nx = pnt.X / radius; pnt.Ny = pnt.Y / radius; pnt.Nz = pnt.Z / radius; pnt.Tv = 0.5f - (float)(Math.Asin(pnt.Y / radius) / Math.PI); vertexes.SetValue(pnt, vertIndex); } vertexes[vertIndex++].Tu = (float)slice / slices; } } #endregion BufferHelper.WriteVertexBuffer(mesh, vertexes); #region Build index buffer var indexes = new short[indexCount]; int i = 0; for (short x = 0; x < slices; x++) { var leftVertex = (short)((stacks + 1) * x); var rightVertex = (short)(leftVertex + stacks + 1); for (int y = 0; y < stacks; y++) { indexes[i++] = rightVertex; indexes[i++] = leftVertex; indexes[i++] = (short)(leftVertex + 1); indexes[i++] = rightVertex; indexes[i++] = (short)(leftVertex + 1); indexes[i++] = (short)(rightVertex + 1); leftVertex++; rightVertex++; } } #endregion BufferHelper.WriteIndexBuffer(mesh, indexes); return(mesh); }
/// <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); } }