/// <summary> /// Generates a mesh from supplied polygons, particularly useful for visualising a brush's polygons on a MeshFilter /// </summary> /// <param name="polygons">Polygons.</param> /// <param name="mesh">Mesh to be written to. Prior to settings the mesh buffers - if the existing mesh is null it will be set to a new one, otherwise the existing mesh is cleared</param> /// <param name="polygonIndices">Maps triangle index (input) to polygon index (output). i.e. int polyIndex = polygonIndices[triIndex];</param> public static void GenerateMeshFromPolygons(Polygon[] polygons, ref Mesh mesh, out List <int> polygonIndices) { if (mesh == null) { mesh = new Mesh(); } mesh.Clear(); // mesh = new Mesh(); List <Vector3> vertices = new List <Vector3>(); List <Vector3> normals = new List <Vector3>(); List <Vector2> uvs = new List <Vector2>(); List <Color> colors = new List <Color>(); List <int> triangles = new List <int>(); // Maps triangle index (input) to polygon index (output). i.e. int polyIndex = polygonIndices[triIndex]; polygonIndices = new List <int>(); // Set up an indexer that tracks unique vertices, so that we reuse vertex data appropiately VertexList vertexList = new VertexList(); // Iterate through every polygon and triangulate for (int i = 0; i < polygons.Length; i++) { Polygon polygon = polygons[i]; List <int> indices = new List <int>(); for (int j = 0; j < polygon.Vertices.Length; j++) { // Each vertex must know about its shared data for geometry tinting //polygon.Vertices[j].Shared = polygon.SharedBrushData; // If the vertex is already in the indexer, fetch the index otherwise add it and get the added index int index = vertexList.AddOrGet(polygon.Vertices[j]); // Put each vertex index in an array for use in the triangle generation indices.Add(index); } // Triangulate the n-sided polygon and allow vertex reuse by using indexed geometry for (int j = 2; j < indices.Count; j++) { triangles.Add(indices[0]); triangles.Add(indices[j - 1]); triangles.Add(indices[j]); // Map that this triangle is from the specified polygon (so we can map back from triangles to polygon) polygonIndices.Add(i); } } // Create the relevant buffers from the vertex array for (int i = 0; i < vertexList.Vertices.Count; i++) { vertices.Add(vertexList.Vertices[i].Position); normals.Add(vertexList.Vertices[i].Normal); uvs.Add(vertexList.Vertices[i].UV); // colors.Add(((SharedBrushData)indexer.Vertices[i].Shared).BrushTintColor); } // Set the mesh buffers mesh.vertices = vertices.ToArray(); mesh.normals = normals.ToArray(); mesh.colors = colors.ToArray(); mesh.uv = uvs.ToArray(); mesh.triangles = triangles.ToArray(); }
public static string ExportToString(Transform transform, List <Polygon> polygons, Material defaultMaterial) { // If a transform is provided, convert the world positions and normals to local to the transform if (transform != null) { for (int polygonIndex = 0; polygonIndex < polygons.Count; polygonIndex++) { Polygon polygon = polygons[polygonIndex]; for (int vertexIndex = 0; vertexIndex < polygon.Vertices.Length; vertexIndex++) { Vertex vertex = polygon.Vertices[vertexIndex]; vertex.Position = transform.InverseTransformPoint(vertex.Position); vertex.Normal = transform.InverseTransformDirection(vertex.Normal); } } } // Create polygon subsets for each material Dictionary <Material, List <Polygon> > polygonMaterialTable = new Dictionary <Material, List <Polygon> >(); // Iterate through every polygon adding it to the appropiate material list foreach (Polygon polygon in polygons) { if (polygon.UserExcludeFromFinal) { continue; } Material material = polygon.Material; if (material == null) { material = defaultMaterial; } if (!polygonMaterialTable.ContainsKey(material)) { polygonMaterialTable.Add(material, new List <Polygon>()); } polygonMaterialTable[material].Add(polygon); } // Use a string builder as this should allow faster concatenation StringBuilder stringBuilder = new StringBuilder(); OBJVertexList vertexList = new OBJVertexList(); int positionIndexOffset = 0; int uvIndexOffset = 0; int normalIndexOffset = 0; int meshIndex = 1; // Create a separate mesh for polygons of each material so that we batch by material foreach (KeyValuePair <Material, List <Polygon> > polygonMaterialGroup in polygonMaterialTable) { List <List <OBJFaceVertex> > faces = new List <List <OBJFaceVertex> >(polygonMaterialGroup.Value.Count); // Iterate through every polygon and triangulate foreach (Polygon polygon in polygonMaterialGroup.Value) { List <OBJFaceVertex> faceVertices = new List <OBJFaceVertex>(polygon.Vertices.Length); for (int i = 0; i < polygon.Vertices.Length; i++) { OBJFaceVertex faceVertex = vertexList.AddOrGet(polygon.Vertices[i]); faceVertices.Add(faceVertex); } faces.Add(faceVertices); } List <Vector3> positions = vertexList.Positions; List <Vector2> uvs = vertexList.UVs; List <Vector3> normals = vertexList.Normals; // Start a new group for the mesh stringBuilder.AppendLine("g Mesh" + meshIndex); // Write all the positions stringBuilder.AppendLine("# Vertex Positions: " + (positions.Count - positionIndexOffset)); for (int i = positionIndexOffset; i < positions.Count; i++) { stringBuilder.AppendLine("v " + WriteVector3(positions[i])); } // Write all the texture coordinates (UVs) stringBuilder.AppendLine("# Vertex UVs: " + (uvs.Count - uvIndexOffset)); for (int i = uvIndexOffset; i < uvs.Count; i++) { stringBuilder.AppendLine("vt " + WriteVector2(uvs[i])); } // Write all the normals stringBuilder.AppendLine("# Vertex Normals: " + (normals.Count - normalIndexOffset)); for (int i = normalIndexOffset; i < normals.Count; i++) { stringBuilder.AppendLine("vn " + WriteVector3(normals[i])); } // Write all the faces stringBuilder.AppendLine("# Faces: " + faces.Count); for (int i = 0; i < faces.Count; i++) { stringBuilder.Append("f "); for (int j = faces[i].Count - 1; j >= 0; j--) { stringBuilder.Append((faces[i][j].PositionIndex) + "/" + (faces[i][j].UVIndex) + "/" + (faces[i][j].NormalIndex) + " "); } stringBuilder.AppendLine(); } // Add some padding between this and the next mesh stringBuilder.AppendLine(); stringBuilder.AppendLine(); meshIndex++; // Update the offsets so that the next pass only writes new vertex information positionIndexOffset = positions.Count; uvIndexOffset = uvs.Count; normalIndexOffset = normals.Count; } return(stringBuilder.ToString()); }
/// <summary> /// Generates an ico-sphere of radius 2. Unlike a polar-sphere this has a more even distribution of vertices. /// </summary> /// <returns>Polygons to be supplied to a brush.</returns> /// <param name="iterationCount">Number of times the surface is subdivided, values of 1 or 2 are recommended.</param> public static Polygon[] GenerateIcoSphere(int iterationCount) { // Derived from http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html float longestDimension = (1 + Mathf.Sqrt(5f)) / 2f; Vector3 sourceVector = new Vector3(0, 1, longestDimension); // Make the longest dimension 1, so the icosphere fits in a 2,2,2 cube sourceVector.Normalize(); Vertex[] vertices = new Vertex[] { new Vertex(new Vector3(-sourceVector.y, +sourceVector.z, sourceVector.x), Vector3.zero, Vector2.zero), new Vertex(new Vector3(sourceVector.y, +sourceVector.z, sourceVector.x), Vector3.zero, Vector2.zero), new Vertex(new Vector3(-sourceVector.y, -sourceVector.z, sourceVector.x), Vector3.zero, Vector2.zero), new Vertex(new Vector3(sourceVector.y, -sourceVector.z, sourceVector.x), Vector3.zero, Vector2.zero), new Vertex(new Vector3(sourceVector.x, -sourceVector.y, +sourceVector.z), Vector3.zero, Vector2.zero), new Vertex(new Vector3(sourceVector.x, +sourceVector.y, +sourceVector.z), Vector3.zero, Vector2.zero), new Vertex(new Vector3(sourceVector.x, -sourceVector.y, -sourceVector.z), Vector3.zero, Vector2.zero), new Vertex(new Vector3(sourceVector.x, +sourceVector.y, -sourceVector.z), Vector3.zero, Vector2.zero), new Vertex(new Vector3(+sourceVector.z, sourceVector.x, -sourceVector.y), Vector3.zero, Vector2.zero), new Vertex(new Vector3(+sourceVector.z, sourceVector.x, +sourceVector.y), Vector3.zero, Vector2.zero), new Vertex(new Vector3(-sourceVector.z, sourceVector.x, -sourceVector.y), Vector3.zero, Vector2.zero), new Vertex(new Vector3(-sourceVector.z, sourceVector.x, +sourceVector.y), Vector3.zero, Vector2.zero), }; Polygon[] polygons = new Polygon[] { new Polygon(new Vertex[] { vertices[0].DeepCopy(), vertices[1].DeepCopy(), vertices[7].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[0].DeepCopy(), vertices[5].DeepCopy(), vertices[1].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[0].DeepCopy(), vertices[7].DeepCopy(), vertices[10].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[0].DeepCopy(), vertices[10].DeepCopy(), vertices[11].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[0].DeepCopy(), vertices[11].DeepCopy(), vertices[5].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[7].DeepCopy(), vertices[1].DeepCopy(), vertices[8].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[1].DeepCopy(), vertices[5].DeepCopy(), vertices[9].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[10].DeepCopy(), vertices[7].DeepCopy(), vertices[6].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[11].DeepCopy(), vertices[10].DeepCopy(), vertices[2].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[5].DeepCopy(), vertices[11].DeepCopy(), vertices[4].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[3].DeepCopy(), vertices[2].DeepCopy(), vertices[6].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[3].DeepCopy(), vertices[4].DeepCopy(), vertices[2].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[3].DeepCopy(), vertices[6].DeepCopy(), vertices[8].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[3].DeepCopy(), vertices[8].DeepCopy(), vertices[9].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[3].DeepCopy(), vertices[9].DeepCopy(), vertices[4].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[6].DeepCopy(), vertices[2].DeepCopy(), vertices[10].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[2].DeepCopy(), vertices[4].DeepCopy(), vertices[11].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[8].DeepCopy(), vertices[6].DeepCopy(), vertices[7].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[9].DeepCopy(), vertices[8].DeepCopy(), vertices[1].DeepCopy() }, null, false, false), new Polygon(new Vertex[] { vertices[4].DeepCopy(), vertices[9].DeepCopy(), vertices[5].DeepCopy() }, null, false, false), }; // Refine for (int i = 0; i < iterationCount; i++) { Polygon[] newPolygons = new Polygon[polygons.Length * 4]; for (int j = 0; j < polygons.Length; j++) { Vertex a = Vertex.Lerp(polygons[j].Vertices[0], polygons[j].Vertices[1], 0.5f); Vertex b = Vertex.Lerp(polygons[j].Vertices[1], polygons[j].Vertices[2], 0.5f); Vertex c = Vertex.Lerp(polygons[j].Vertices[2], polygons[j].Vertices[0], 0.5f); a.Position = a.Position.normalized; b.Position = b.Position.normalized; c.Position = c.Position.normalized; newPolygons[j * 4 + 0] = new Polygon(new Vertex[] { polygons[j].Vertices[0].DeepCopy(), a.DeepCopy(), c.DeepCopy() }, null, false, false); newPolygons[j * 4 + 1] = new Polygon(new Vertex[] { polygons[j].Vertices[1].DeepCopy(), b.DeepCopy(), a.DeepCopy() }, null, false, false); newPolygons[j * 4 + 2] = new Polygon(new Vertex[] { polygons[j].Vertices[2].DeepCopy(), c.DeepCopy(), b.DeepCopy() }, null, false, false); newPolygons[j * 4 + 3] = new Polygon(new Vertex[] { a.DeepCopy(), b.DeepCopy(), c.DeepCopy() }, null, false, false); } polygons = newPolygons; } for (int i = 0; i < polygons.Length; i++) { bool anyAboveHalf = false; for (int j = 0; j < polygons[i].Vertices.Length; j++) { Vector3 normal = polygons[i].Vertices[j].Position.normalized; polygons[i].Vertices[j].Normal = normal; float piReciprocal = 1f / Mathf.PI; float u = 0.5f - 0.5f * Mathf.Atan2(normal.x, -normal.z) * piReciprocal; float v = 1f - Mathf.Acos(normal.y) * piReciprocal; if (Mathf.Abs(u) < 0.01f || Mathf.Abs(1 - Mathf.Abs(u)) < 0.01f) { if (polygons[i].Plane.normal.x > 0) { u = 0; } else { u = 1; } } if (u > 0.75f) { anyAboveHalf = true; } //Debug.Log(u); polygons[i].Vertices[j].UV = new Vector2(u, v); //const float kOneOverPi = 1.0 / 3.14159265; //float u = 0.5 - 0.5 * atan(N.x, -N.z) * kOneOverPi; //float v = 1.0 - acos(N.y) * kOneOverPi; } if (anyAboveHalf) { for (int j = 0; j < polygons[i].Vertices.Length; j++) { Vector2 uv = polygons[i].Vertices[j].UV; if (uv.x < 0.5f) { uv.x += 1; } polygons[i].Vertices[j].UV = uv; } } } return(polygons); }
/// <summary> /// Generates a sphere of radius 2 /// </summary> /// <returns>Polygons to be supplied to a brush.</returns> /// <param name="lateralCount">Vertex count up from the south pole to the north pole.</param> /// <param name="longitudinalCount">Vertex count around the sphere equator.</param> public static Polygon[] GeneratePolarSphere(int lateralCount = 6, int longitudinalCount = 12) { Polygon[] polygons = new Polygon[lateralCount * longitudinalCount]; float angleDelta = 1f / lateralCount; float longitudinalDelta = 1f / longitudinalCount; // Generate tris for the top and bottom, then quads for the rest for (int i = 0; i < lateralCount; i++) { for (int j = 0; j < longitudinalCount; j++) { Vertex[] vertices; if (i == lateralCount - 1) { vertices = new Vertex[] { new Vertex(new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector2(i * (1f / lateralCount), (j + 1) * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector2((i + 1) * (1f / lateralCount), (j + 1) * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector2(i * (1f / lateralCount), j * (1f / longitudinalCount))), }; } else if (i > 0) { vertices = new Vertex[] { new Vertex(new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector2(i * (1f / lateralCount), (j + 1) * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector2((i + 1) * (1f / lateralCount), (j + 1) * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector2((i + 1) * (1f / lateralCount), j * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector2(i * (1f / lateralCount), j * (1f / longitudinalCount))), }; } else // i == 0 { vertices = new Vertex[] { new Vertex(new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * i * angleDelta), Mathf.Sin(Mathf.PI * i * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector2(i * (1f / lateralCount), (j + 1) * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * (j + 1) * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * (j + 1) * longitudinalDelta) ), new Vector2((i + 1) * (1f / lateralCount), (j + 1) * (1f / longitudinalCount))), new Vertex(new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector3(Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Cos(2 * Mathf.PI * j * longitudinalDelta), Mathf.Cos(Mathf.PI * (i + 1) * angleDelta), Mathf.Sin(Mathf.PI * (i + 1) * angleDelta) * Mathf.Sin(2 * Mathf.PI * j * longitudinalDelta) ), new Vector2((i + 1) * (1f / lateralCount), j * (1f / longitudinalCount))), }; } for (int d = 0; d < vertices.Length; d++) { vertices[d].UV = new Vector2(vertices[d].UV.y, 1 - vertices[d].UV.x); } polygons[i + j * lateralCount] = new Polygon(vertices, null, false, false); } } return(polygons); }
/// <summary> /// Finds the UV for a supplied position on a polygon, note this internally handles situations where vertices overlap or are colinear which the other version of this method does not /// </summary> /// <returns>The UV for the supplied position.</returns> /// <param name="polygon">Polygon.</param> /// <param name="newPosition">Position to find the UV for.</param> public static Vector2 GetUVForPosition(Polygon polygon, Vector3 newPosition) { int vertexIndex1 = 0; int vertexIndex2 = 0; int vertexIndex3 = 0; // Account for overlapping vertices for (int i = vertexIndex1 + 1; i < polygon.Vertices.Length; i++) { if (!polygon.Vertices[i].Position.EqualsWithEpsilon(polygon.Vertices[vertexIndex1].Position)) { vertexIndex2 = i; break; } } for (int i = vertexIndex2 + 1; i < polygon.Vertices.Length; i++) { if (!polygon.Vertices[i].Position.EqualsWithEpsilon(polygon.Vertices[vertexIndex2].Position)) { vertexIndex3 = i; break; } } // Now account for the fact that the picked three vertices might be collinear Vector3 pos1 = polygon.Vertices[vertexIndex1].Position; Vector3 pos2 = polygon.Vertices[vertexIndex2].Position; Vector3 pos3 = polygon.Vertices[vertexIndex3].Position; Plane plane = new Plane(pos1, pos2, pos3); if (plane.normal == Vector3.zero) { for (int i = 2; i < polygon.Vertices.Length; i++) { vertexIndex3 = i; pos3 = polygon.Vertices[vertexIndex3].Position; Plane tempPlane = new Plane(pos1, pos2, pos3); if (tempPlane.normal != Vector3.zero) { break; } } plane = new Plane(pos1, pos2, pos3); } // Should now have a good set of positions, so continue Vector3 planePoint = MathHelper.ClosestPointOnPlane(newPosition, plane); Vector2 uv1 = polygon.Vertices[vertexIndex1].UV; Vector2 uv2 = polygon.Vertices[vertexIndex2].UV; Vector2 uv3 = polygon.Vertices[vertexIndex3].UV; // calculate vectors from point f to vertices p1, p2 and p3: Vector3 f1 = pos1 - planePoint; Vector3 f2 = pos2 - planePoint; Vector3 f3 = pos3 - planePoint; // calculate the areas (parameters order is essential in this case): Vector3 va = Vector3.Cross(pos1 - pos2, pos1 - pos3); // main triangle cross product Vector3 va1 = Vector3.Cross(f2, f3); // p1's triangle cross product Vector3 va2 = Vector3.Cross(f3, f1); // p2's triangle cross product Vector3 va3 = Vector3.Cross(f1, f2); // p3's triangle cross product float a = va.magnitude; // main triangle area // calculate barycentric coordinates with sign: float a1 = va1.magnitude / a * Mathf.Sign(Vector3.Dot(va, va1)); float a2 = va2.magnitude / a * Mathf.Sign(Vector3.Dot(va, va2)); float a3 = va3.magnitude / a * Mathf.Sign(Vector3.Dot(va, va3)); // find the uv corresponding to point f (uv1/uv2/uv3 are associated to p1/p2/p3): Vector2 uv = uv1 * a1 + uv2 * a2 + uv3 * a3; return(uv); }
/// <summary> /// Generates a cube (size 2,2,2) /// </summary> /// <returns>Polygons to be supplied to a brush.</returns> public static Polygon[] GenerateCube() { Polygon[] polygons = new Polygon[6]; // Polygons and vertices are created in a clockwise order // Polygons are created in this order: back, left, front, right, bottom, top // Vertices with those polygons are created in this order: BR, BL, TL, TR (when viewed aligned with the UV) // Therefore to move the two bottom vertices of the left face, you would pick the second face (index 1) and // then manipulate the first two vertices (indexes 0 and 1) // Back polygons[0] = new Polygon(new Vertex[] { new Vertex(new Vector3(-1, -1, 1), new Vector3(0, 0, 1), new Vector2(1, 0)), new Vertex(new Vector3(1, -1, 1), new Vector3(0, 0, 1), new Vector2(0, 0)), new Vertex(new Vector3(1, 1, 1), new Vector3(0, 0, 1), new Vector2(0, 1)), new Vertex(new Vector3(-1, 1, 1), new Vector3(0, 0, 1), new Vector2(1, 1)), }, null, false, false); // Left polygons[1] = new Polygon(new Vertex[] { new Vertex(new Vector3(-1, -1, -1), new Vector3(-1, 0, 0), new Vector2(1, 0)), new Vertex(new Vector3(-1, -1, 1), new Vector3(-1, 0, 0), new Vector2(0, 0)), new Vertex(new Vector3(-1, 1, 1), new Vector3(-1, 0, 0), new Vector2(0, 1)), new Vertex(new Vector3(-1, 1, -1), new Vector3(-1, 0, 0), new Vector2(1, 1)), }, null, false, false); // Front polygons[2] = new Polygon(new Vertex[] { new Vertex(new Vector3(1, -1, 1), new Vector3(1, 0, 0), new Vector2(1, 0)), new Vertex(new Vector3(1, -1, -1), new Vector3(1, 0, 0), new Vector2(0, 0)), new Vertex(new Vector3(1, 1, -1), new Vector3(1, 0, 0), new Vector2(0, 1)), new Vertex(new Vector3(1, 1, 1), new Vector3(1, 0, 0), new Vector2(1, 1)), }, null, false, false); // Right polygons[3] = new Polygon(new Vertex[] { new Vertex(new Vector3(1, -1, -1), new Vector3(0, 0, -1), new Vector2(1, 0)), new Vertex(new Vector3(-1, -1, -1), new Vector3(0, 0, -1), new Vector2(0, 0)), new Vertex(new Vector3(-1, 1, -1), new Vector3(0, 0, -1), new Vector2(0, 1)), new Vertex(new Vector3(1, 1, -1), new Vector3(0, 0, -1), new Vector2(1, 1)), }, null, false, false); // Bottom polygons[4] = new Polygon(new Vertex[] { new Vertex(new Vector3(-1, -1, -1), new Vector3(0, -1, 0), new Vector2(1, 0)), new Vertex(new Vector3(1, -1, -1), new Vector3(0, -1, 0), new Vector2(0, 0)), new Vertex(new Vector3(1, -1, 1), new Vector3(0, -1, 0), new Vector2(0, 1)), new Vertex(new Vector3(-1, -1, 1), new Vector3(0, -1, 0), new Vector2(1, 1)), }, null, false, false); // Top polygons[5] = new Polygon(new Vertex[] { new Vertex(new Vector3(-1, 1, -1), new Vector3(0, 1, 0), new Vector2(1, 0)), new Vertex(new Vector3(-1, 1, 1), new Vector3(0, 1, 0), new Vector2(0, 0)), new Vertex(new Vector3(1, 1, 1), new Vector3(0, 1, 0), new Vector2(0, 1)), new Vertex(new Vector3(1, 1, -1), new Vector3(0, 1, 0), new Vector2(1, 1)), }, null, false, false); return(polygons); }
static bool IsPolygonCeiling(Polygon polygon) { return(Vector3.Dot(polygon.Plane.normal, Vector3.up) < -0.99f); }
public static void ExtrudePolygon(Polygon sourcePolygon, out Polygon[] outputPolygons, out Quaternion rotation) { float extrusionDistance = 1; Polygon basePolygon = sourcePolygon.DeepCopy(); basePolygon.UniqueIndex = -1; rotation = Quaternion.LookRotation(basePolygon.Plane.normal); Quaternion cancellingRotation = Quaternion.Inverse(rotation); Vertex[] vertices = basePolygon.Vertices; // Vector3 offsetPosition = vertices[0].Position; for (int i = 0; i < vertices.Length; i++) { // vertices[i].Position -= offsetPosition; vertices[i].Position = cancellingRotation * vertices[i].Position; vertices[i].Normal = cancellingRotation * vertices[i].Normal; } // Vector3 newOffsetPosition = vertices[0].Position; // Vector3 delta = newOffsetPosition - offsetPosition; // for (int i = 0; i < vertices.Length; i++) // { // vertices[i].Position += delta; // } basePolygon.SetVertices(vertices); Vector3 normal = basePolygon.Plane.normal; Polygon oppositePolygon = basePolygon.DeepCopy(); oppositePolygon.UniqueIndex = -1; basePolygon.Flip(); vertices = oppositePolygon.Vertices; for (int i = 0; i < vertices.Length; i++) { vertices[i].Position += normal; } oppositePolygon.SetVertices(vertices); Polygon[] brushSides = new Polygon[sourcePolygon.Vertices.Length]; for (int i = 0; i < basePolygon.Vertices.Length; i++) { Vertex vertex1 = basePolygon.Vertices[i].DeepCopy(); Vertex vertex2 = basePolygon.Vertices[(i + 1) % basePolygon.Vertices.Length].DeepCopy(); Vector2 uvDelta = vertex2.UV - vertex1.UV; float sourceDistance = Vector3.Distance(vertex1.Position, vertex2.Position); Vector2 rotatedUVDelta = uvDelta.Rotate(90) * (extrusionDistance / sourceDistance); Vertex vertex3 = vertex1.DeepCopy(); vertex3.Position += normal * extrusionDistance; vertex3.UV += rotatedUVDelta; Vertex vertex4 = vertex2.DeepCopy(); vertex4.Position += normal * extrusionDistance; vertex4.UV += rotatedUVDelta; Vertex[] newVertices = new Vertex[] { vertex1, vertex2, vertex4, vertex3 }; brushSides[i] = new Polygon(newVertices, sourcePolygon.Material, false, false); brushSides[i].Flip(); brushSides[i].ResetVertexNormals(); } List <Polygon> polygons = new List <Polygon>(); polygons.Add(basePolygon); polygons.Add(oppositePolygon); polygons.AddRange(brushSides); outputPolygons = polygons.ToArray(); }
static bool IsPolygonFloor(Polygon polygon) { return(Vector3.Dot(polygon.Plane.normal, Vector3.up) > 0.99f); }
/// <summary> /// Constructs a polygon from an unordered coplanar set of positions /// </summary> /// <param name="sourcePositions"></param> /// <param name="removeExtraPositions"></param> /// <returns></returns> public static Polygon ConstructPolygon(List <Vector3> sourcePositions, bool removeExtraPositions) { List <Vector3> positions; if (removeExtraPositions) { positions = new List <Vector3>(); for (int i = 0; i < sourcePositions.Count; i++) { Vector3 sourcePosition = sourcePositions[i]; bool contained = false; for (int j = 0; j < positions.Count; j++) { if (positions[j].EqualsWithEpsilonLower(sourcePosition)) { contained = true; break; } } if (!contained) { positions.Add(sourcePosition); } } } else { positions = sourcePositions; } // If positions is smaller than 3 then we can't construct a polygon. This could happen if you try to cut the // tip off a very, very thin brush. While the plane and the brushes would intersect, the actual // cross-sectional area is near zero and too small to create a valid polygon. In this case simply return // null to indicate polygon creation was impossible if (positions.Count < 3) { return(null); } // Find center point, so we can sort the positions around it Vector3 center = positions[0]; for (int i = 1; i < positions.Count; i++) { center += positions[i]; } center *= 1f / positions.Count; if (positions.Count < 3) { Debug.LogError("Position count is below 3, this is probably unhandled"); } // Find the plane UnityEngine.Plane plane = new UnityEngine.Plane(positions[0], positions[1], positions[2]); // Rotation to go from the polygon's plane to XY plane (for sorting) Quaternion cancellingRotation; if (plane.normal != Vector3.zero) { cancellingRotation = Quaternion.Inverse(Quaternion.LookRotation(plane.normal)); } else { cancellingRotation = Quaternion.identity; } // Rotate the center point onto the plane too Vector3 rotatedCenter = cancellingRotation * center; // Sort the positions, passing the rotation to put the positions on XY plane and the rotated center point IComparer <Vector3> comparer = new SortVectorsClockwise(cancellingRotation, rotatedCenter); positions.Sort(comparer); // Create the vertices from the positions Vertex[] newPolygonVertices = new Vertex[positions.Count]; for (int i = 0; i < positions.Count; i++) { newPolygonVertices[i] = new Vertex(positions[i], -plane.normal, (cancellingRotation * positions[i]) * 0.5f); } Polygon newPolygon = new Polygon(newPolygonVertices, null, false, false); if (newPolygon.Plane.normal == Vector3.zero) { Debug.LogError("Zero normal found, this leads to invalid polyhedron-point tests"); return(null); // hacky // if(removeExtraPositions) // { // Polygon.Vector3ComparerEpsilon equalityComparer = new Polygon.Vector3ComparerEpsilon(); // List<Vector3> testFoo = newPolygonVertices.Select(item => item.Position).Distinct(equalityComparer).ToList(); // } } return(newPolygon); }
internal static bool SplitCoplanarPolygonsByPlane(List <Polygon> polygons, // Source polygons that will be split Plane splitPlane, out List <Polygon> polygonsFront, out List <Polygon> polygonsBack) { polygonsFront = new List <Polygon>(); polygonsBack = new List <Polygon>(); for (int polygonIndex = 0; polygonIndex < polygons.Count; polygonIndex++) { Polygon.PolygonPlaneRelation planeRelation = Polygon.TestPolygonAgainstPlane(polygons[polygonIndex], splitPlane); // Polygon has been found to span both sides of the plane, attempt to split into two pieces if (planeRelation == Polygon.PolygonPlaneRelation.Spanning) { Polygon frontPolygon; Polygon backPolygon; Vertex newVertex1; Vertex newVertex2; // Attempt to split the polygon if (Polygon.SplitPolygon(polygons[polygonIndex], out frontPolygon, out backPolygon, out newVertex1, out newVertex2, splitPlane)) { // If the split algorithm was successful (produced two valid polygons) then add each polygon to // their respective points and track the intersection points polygonsFront.Add(frontPolygon); polygonsBack.Add(backPolygon); } else { // Two valid polygons weren't generated, so use the valid one if (frontPolygon != null) { planeRelation = Polygon.PolygonPlaneRelation.InFront; } else if (backPolygon != null) { planeRelation = Polygon.PolygonPlaneRelation.Behind; } else { planeRelation = Polygon.PolygonPlaneRelation.InFront; Debug.LogError("Polygon splitting has resulted in two zero area polygons. This is unhandled."); // Polygon.PolygonPlaneRelation secondplaneRelation = Polygon.TestPolygonAgainstPlane(polygons[polygonIndex], splitPlane); } } } // If the polygon is on one side of the plane or the other if (planeRelation != Polygon.PolygonPlaneRelation.Spanning) { if (planeRelation == Polygon.PolygonPlaneRelation.Behind) { polygonsBack.Add(polygons[polygonIndex]); } else { polygonsFront.Add(polygons[polygonIndex]); } } } if (polygonsBack.Count >= 1 && polygonsFront.Count >= 1) { return(true); } else { return(false); } }
internal static bool SplitPolygonsByPlane(List <Polygon> polygons, // Source polygons that will be split Plane splitPlane, bool excludeNewPolygons, // Whether new polygons should be marked as excludeFromBuild out List <Polygon> polygonsFront, out List <Polygon> polygonsBack) { polygonsFront = new List <Polygon>(); polygonsBack = new List <Polygon>(); // First of all make sure splitting actually needs to occur (we'll get bad issues if // we try splitting geometry when we don't need to) if (!GeometryHelper.PolygonsIntersectPlane(polygons, splitPlane)) { return(false); } // These are the vertices that will be used in the new caps List <Vertex> newVertices = new List <Vertex>(); for (int polygonIndex = 0; polygonIndex < polygons.Count; polygonIndex++) { Polygon.PolygonPlaneRelation planeRelation = Polygon.TestPolygonAgainstPlane(polygons[polygonIndex], splitPlane); // Polygon has been found to span both sides of the plane, attempt to split into two pieces if (planeRelation == Polygon.PolygonPlaneRelation.Spanning) { Polygon frontPolygon; Polygon backPolygon; Vertex newVertex1; Vertex newVertex2; // Attempt to split the polygon if (Polygon.SplitPolygon(polygons[polygonIndex], out frontPolygon, out backPolygon, out newVertex1, out newVertex2, splitPlane)) { // If the split algorithm was successful (produced two valid polygons) then add each polygon to // their respective points and track the intersection points polygonsFront.Add(frontPolygon); polygonsBack.Add(backPolygon); newVertices.Add(newVertex1); newVertices.Add(newVertex2); } else { // Two valid polygons weren't generated, so use the valid one if (frontPolygon != null) { planeRelation = Polygon.PolygonPlaneRelation.InFront; } else if (backPolygon != null) { planeRelation = Polygon.PolygonPlaneRelation.Behind; } else { planeRelation = Polygon.PolygonPlaneRelation.InFront; Debug.LogError("Polygon splitting has resulted in two zero area polygons. This is unhandled."); // Polygon.PolygonPlaneRelation secondplaneRelation = Polygon.TestPolygonAgainstPlane(polygons[polygonIndex], splitPlane); } } } // If the polygon is on one side of the plane or the other if (planeRelation != Polygon.PolygonPlaneRelation.Spanning) { // Make sure any points that are coplanar on non-straddling polygons are still used in polygon // construction for (int vertexIndex = 0; vertexIndex < polygons[polygonIndex].Vertices.Length; vertexIndex++) { if (Polygon.ComparePointToPlane(polygons[polygonIndex].Vertices[vertexIndex].Position, splitPlane) == Polygon.PointPlaneRelation.On) // if(Polygon.ComparePointToPlane2(polygons[polygonIndex].Vertices[vertexIndex].Position, splitPlane) == Polygon.PointPlaneRelation.On) { newVertices.Add(polygons[polygonIndex].Vertices[vertexIndex]); } } if (planeRelation == Polygon.PolygonPlaneRelation.Behind) { polygonsBack.Add(polygons[polygonIndex]); } else { polygonsFront.Add(polygons[polygonIndex]); } } } // If any splits occured or coplanar vertices are found. (For example if you're splitting a sphere at the // equator then no polygons will be split but there will be a bunch of coplanar vertices!) if (newVertices.Count > 0 && polygonsBack.Count >= 3 && polygonsFront.Count >= 3) { // HACK: This code is awful, because we end up with lots of duplicate vertices List <Vector3> positions = new List <Vector3>(newVertices.Count); for (int i = 0; i < newVertices.Count; i++) { positions.Add(newVertices[i].Position); } Polygon newPolygon = PolygonFactory.ConstructPolygon(positions, true); // Assuming it was possible to create a polygon if (newPolygon != null) { if (!MathHelper.PlaneEqualsLooser(newPolygon.Plane, splitPlane)) { // Polygons are sometimes constructed facing the wrong way, possibly due to a winding order // mismatch. If the two normals are opposite, flip the new polygon if (Vector3.Dot(newPolygon.Plane.normal, splitPlane.normal) < -0.9f) { newPolygon.Flip(); } } newPolygon.ExcludeFromFinal = excludeNewPolygons; polygonsFront.Add(newPolygon); newPolygon = newPolygon.DeepCopy(); newPolygon.Flip(); newPolygon.ExcludeFromFinal = excludeNewPolygons; if (newPolygon.Plane.normal == Vector3.zero) { Debug.LogError("Invalid Normal! Shouldn't be zero. This is unexpected since extraneous positions should have been removed!"); // Polygon fooNewPolygon = PolygonFactory.ConstructPolygon(positions, true); } polygonsBack.Add(newPolygon); } return(true); } else { // It wasn't possible to create the polygon, for example the constructed polygon was too small // This could happen if you attempt to clip the tip off a long but thin brush, the plane-polyhedron test // would say they intersect but in reality the resulting polygon would be near zero area return(false); } }
/// <summary> /// Generates a cone of height and radius 2. If the <see cref="sideCount"/> is 3, generates a triangular-based pyramid. /// </summary> /// <param name="sideCount">Side count for the cone (if 3, generates a triangular-based pyramid.).</param> /// <returns>Polygons to be supplied to a brush.</returns> public static Polygon[] GenerateCone(int sideCount = 20) { Polygon[] polygons = new Polygon[sideCount * 2]; // the cone generator will create a slightly distorted off-center output for 3 sides. // if the user sets the side count to 3 we will generate a triangular-based pyramid. if (sideCount == 3) { polygons = new Polygon[sideCount + 1]; polygons[0] = new Polygon(new Vertex[] { new Vertex(new Vector3(0, -1, -1), new Vector3(0.8402f, 0.2425f, -0.4851f), new Vector2(0.0000f, 0.0000f)), new Vertex(new Vector3(0, 1, 0), new Vector3(0.8402f, 0.2425f, -0.4851f), new Vector2(0.5000f, 1.0000f)), new Vertex(new Vector3(1, -1, 1), new Vector3(0.8402f, 0.2425f, -0.4851f), new Vector2(1.0000f, 0.0000f)), }, null, false, false); polygons[1] = new Polygon(new Vertex[] { new Vertex(new Vector3(1, -1, 1), new Vector3(-0.0000f, 0.2425f, 0.9701f), new Vector2(0.0000f, 0.0000f)), new Vertex(new Vector3(0, 1, 0), new Vector3(-0.0000f, 0.2425f, 0.9701f), new Vector2(0.5000f, 1.0000f)), new Vertex(new Vector3(-1, -1, 1), new Vector3(-0.0000f, 0.2425f, 0.9701f), new Vector2(1.0000f, 0.0000f)), }, null, false, false); polygons[2] = new Polygon(new Vertex[] { new Vertex(new Vector3(-1, -1, 1), new Vector3(-0.8402f, 0.2425f, -0.4851f), new Vector2(0.0000f, 0.0000f)), new Vertex(new Vector3(0, 1, 0), new Vector3(-0.8402f, 0.2425f, -0.4851f), new Vector2(0.5000f, 1.0000f)), new Vertex(new Vector3(0, -1, -1), new Vector3(-0.8402f, 0.2425f, -0.4851f), new Vector2(1.0000f, 0.0000f)), }, null, false, false); polygons[3] = new Polygon(new Vertex[] { new Vertex(new Vector3(0, -1, -1), Vector3.down, new Vector2(0.0000f, 0.0000f)), new Vertex(new Vector3(1, -1, 1), Vector3.down, new Vector2(1.0000f, 1.0000f)), new Vertex(new Vector3(-1, -1, 1), Vector3.down, new Vector2(1.0000f, 0.0000f)), }, null, false, false); return(polygons); } float angleDelta = Mathf.PI * 2 / sideCount; for (int i = 0; i < sideCount; i++) { Vector3 normal = new Vector3(Mathf.Sin((i + 0.5f) * angleDelta), 0, Mathf.Cos((i + 0.5f) * angleDelta)); polygons[i] = new Polygon(new Vertex[] { new Vertex(new Vector3(Mathf.Sin(i * angleDelta), -1, Mathf.Cos(i * angleDelta)), normal, new Vector2(i * (1f / sideCount), 0)), new Vertex(new Vector3(Mathf.Sin((i + 1) * angleDelta), -1, Mathf.Cos((i + 1) * angleDelta)), normal, new Vector2((i + 1) * (1f / sideCount), 0)), new Vertex(new Vector3(0, 1, 0), normal, new Vector2((((i + 1) * (1f / sideCount)) + (i * (1f / sideCount))) / 2.0f, 1.0f)), }, null, false, false); } Vertex capCenterVertex = new Vertex(new Vector3(0, -1, 0), Vector3.down, new Vector2(0, 0)); for (int i = 0; i < sideCount; i++) { Vertex vertex1 = new Vertex(new Vector3(Mathf.Sin(i * -angleDelta), -1, Mathf.Cos(i * -angleDelta)), Vector3.down, new Vector2(Mathf.Sin(i * angleDelta), Mathf.Cos(i * angleDelta))); Vertex vertex2 = new Vertex(new Vector3(Mathf.Sin((i + 1) * -angleDelta), -1, Mathf.Cos((i + 1) * -angleDelta)), Vector3.down, new Vector2(Mathf.Sin((i + 1) * angleDelta), Mathf.Cos((i + 1) * angleDelta))); Vertex[] capVertices = new Vertex[] { vertex1, vertex2, capCenterVertex.DeepCopy() }; polygons[sideCount + i] = new Polygon(capVertices, null, false, false); } return(polygons); }