diff --git a/Assets/Scripts/CLOiSimPlugins/Modules/Base/CLOiSimPlugin.cs b/Assets/Scripts/CLOiSimPlugins/Modules/Base/CLOiSimPlugin.cs index 5f4b860c..7f342b90 100644 --- a/Assets/Scripts/CLOiSimPlugins/Modules/Base/CLOiSimPlugin.cs +++ b/Assets/Scripts/CLOiSimPlugins/Modules/Base/CLOiSimPlugin.cs @@ -27,7 +27,7 @@ public abstract partial class CLOiSimPlugin : MonoBehaviour, ICLOiSimPlugin private Pose pluginPose = Pose.identity; - private SDF.Plugin pluginParameters; + private SDF.Plugin _pluginParameters; private List allocatedDevicePorts = new List(); private List allocatedDeviceHashKeys = new List(); @@ -36,9 +36,13 @@ public abstract partial class CLOiSimPlugin : MonoBehaviour, ICLOiSimPlugin protected abstract void OnAwake(); protected abstract void OnStart(); - protected virtual void OnPluginLoad() { } protected virtual void OnReset() { } + /// + /// This method should be called in Awake() + /// + protected virtual void OnPluginLoad() { } + protected void OnDestroy() { thread.Dispose(); @@ -55,12 +59,20 @@ public void ChangePluginType(in ICLOiSimPlugin.Type targetType) public void SetPluginParameters(in SDF.Plugin plugin) { - pluginParameters = plugin; + _pluginParameters = plugin; } public SDF.Plugin GetPluginParameters() { - return pluginParameters; + return _pluginParameters; + } + + public void StorePluginParametersInAttachedDevices() + { + foreach (var device in attachedDevices.Values) + { + device.SetPluginParameters(_pluginParameters); + } } void Awake() @@ -68,16 +80,15 @@ void Awake() SetCustomHandleRequestMessage(); OnAwake(); + + StorePluginParametersInAttachedDevices(); + + OnPluginLoad(); } // Start is called before the first frame update void Start() { - foreach (var device in attachedDevices.Values) - { - device.SetPluginParameters(pluginParameters); - } - StorePose(); if (string.IsNullOrEmpty(modelName)) @@ -87,11 +98,9 @@ void Start() if (string.IsNullOrEmpty(partsName)) { - partsName = pluginParameters.Name; + partsName = _pluginParameters.Name; } - OnPluginLoad(); - OnStart(); thread.Start(); diff --git a/Assets/Scripts/Devices/DepthCamera.cs b/Assets/Scripts/Devices/DepthCamera.cs index 01e2cfb5..afe0d26f 100644 --- a/Assets/Scripts/Devices/DepthCamera.cs +++ b/Assets/Scripts/Devices/DepthCamera.cs @@ -99,6 +99,7 @@ protected override void SetupTexture() var width = camParameter.image.width; var height = camParameter.image.height; var format = CameraData.GetPixelFormat(camParameter.image.format); + GraphicsFormat graphicFormat; switch (format) { diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation.meta new file mode 100644 index 00000000..17f4bf8f --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37c77271a4ab584e89ad131483035f9d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangle.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangle.cs new file mode 100644 index 00000000..13ffe8fd --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangle.cs @@ -0,0 +1,110 @@ +// Copyright 2021 Alejandro Villalba Avila +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +using System.Collections.Generic; + +namespace Game.Utils.Triangulation +{ + /// + /// Data tha describe a triangle and its context in a triangulation. + /// + public unsafe struct DelaunayTriangle + { + /// + /// The indices of the points that define the triangle. + /// + public fixed int p[3]; + + /// + /// The indices of the triangles that are adjacent. + /// + public fixed int adjacent[3]; + + private const int NO_ADJACENT_TRIANGLE = -1; + + /// + /// Constructor that receives 3 vertex indices. + /// + /// The index of the first vertex. + /// The index of the second vertex. + /// The index of the third vertex. + public DelaunayTriangle(int point0, int point1, int point2) + { + p[0] = point0; + p[1] = point1; + p[2] = point2; + + adjacent[0] = NO_ADJACENT_TRIANGLE; + adjacent[1] = NO_ADJACENT_TRIANGLE; + adjacent[2] = NO_ADJACENT_TRIANGLE; + } + + /// + /// Constructor that receives all the data. + /// + /// The index of the first vertex. + /// The index of the second vertex. + /// The index of the third vertex. + /// The index of the triangle that is adjacent in the first edge. + /// The index of the triangle that is adjacent in the second edge. + /// The index of the triangle that is adjacent in the third edge. + public DelaunayTriangle(int point0, int point1, int point2, int adjacent0, int adjacent1, int adjacent2) + { + p[0] = point0; + p[1] = point1; + p[2] = point2; + + adjacent[0] = adjacent0; + adjacent[1] = adjacent1; + adjacent[2] = adjacent2; + } + +#if UNITY_EDITOR + + /// + /// Gets the content of the triangle vertices array, which cannot be watch by Visual Studio unless you convert it to a list. + /// + public List DebugP + { + get + { + List debugArray = new List(3); + for (int i = 0; i < 3; ++i) + { + debugArray.Add(p[i]); + } + return debugArray; + } + } + + /// + /// Gets the content of the triangle adjacents array, which cannot be watch by Visual Studio unless you convert it to a list. + /// + public List DebugAdjacent + { + get + { + List debugArray = new List(3); + for (int i = 0; i < 3; ++i) + { + debugArray.Add(adjacent[i]); + } + return debugArray; + } + } + +#endif + + } +} + diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangle.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangle.cs.meta new file mode 100644 index 00000000..857e954e --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8220c564c207baf42bf02305ccc62421 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleEdge.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleEdge.cs new file mode 100644 index 00000000..00aeb700 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleEdge.cs @@ -0,0 +1,57 @@ +// Copyright 2021 Alejandro Villalba Avila +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +namespace Game.Utils.Triangulation +{ + /// + /// Data that describes the edge of a triangle. + /// + public struct DelaunayTriangleEdge + { + /// + /// The index of the triangle. + /// + public int TriangleIndex; + + /// + /// The local index of the edge in the triangle (0, 1 or 2). + /// + public int EdgeIndex; + + /// + /// The index of the first vertex that form the edge. + /// + public int EdgeVertexA; + + /// + /// The index of the second vertex that form the edge. + /// + public int EdgeVertexB; + + /// + /// Constructor that receives all the data. + /// + /// The index of the triangle. + /// The local index of the edge (0, 1 or 2). + /// The index of the first vertex that form the edge. + /// The index of the second vertex that form the edge. + public DelaunayTriangleEdge(int triangleIndex, int edgeIndex, int edgeVertexA, int edgeVertexB) + { + TriangleIndex = triangleIndex; + EdgeIndex = edgeIndex; + EdgeVertexA = edgeVertexA; + EdgeVertexB = edgeVertexB; + } + } +} + diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleEdge.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleEdge.cs.meta new file mode 100644 index 00000000..f4c67796 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleEdge.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2ef0846a79319a45b9e180b8e0acae3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleSet.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleSet.cs new file mode 100644 index 00000000..f7a14b41 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleSet.cs @@ -0,0 +1,608 @@ +// Copyright 2021 Alejandro Villalba Avila +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +using Game.Utils.Math; +using System.Collections.Generic; +using UnityEngine; + +namespace Game.Utils.Triangulation +{ + /// + /// Stores data related to triangles, their vertices and their adjacency and provides methods to gather and process that data. + /// + public unsafe class DelaunayTriangleSet + { + /// + /// The indices of the adjacent triangles of every triangle, so there are 3 indices per triangle, and each index is the position of the triangle in groups of 3. + /// + protected List m_adjacentTriangles; + + /// + /// The indices of the vertices of every triangle, so there are 3 indices per triangle, and each index is the position of the point in the points array. + /// + protected List m_triangleVertices; + + /// + /// The real points in the 2D space. + /// + protected List m_points; + + // Indicates that the index of a vertex, edge or triangle is not defined or was not found + protected const int NOT_FOUND = -1; + + // Indicates that there is no adjacent triangle + protected const int NO_ADJACENT_TRIANGLE = -1; + + /// + /// Constructor that receives the expected number of triangles to store. It will reserve memory accordingly. + /// + /// The expected number of triangles to store. + public DelaunayTriangleSet(int expectedTriangles) + { + m_adjacentTriangles = new List(expectedTriangles * 3); + m_triangleVertices = new List(expectedTriangles * 3); + m_points = new List(expectedTriangles); + } + + /// + /// Removes all the data stored in the buffers, while keeping the memory. + /// + public void Clear() + { + m_adjacentTriangles.Clear(); + m_triangleVertices.Clear(); + m_points.Clear(); ; + } + + /// + /// Modifies the capacity of the buffer, reserving new memory if necessary, according to the new expected number of triangles. + /// + /// The expected number of triangles to store. + public void SetCapacity(int expectedTriangles) + { + if (m_adjacentTriangles.Capacity < expectedTriangles * 3) + { + m_adjacentTriangles.Capacity = expectedTriangles * 3; + } + + if (m_triangleVertices.Capacity < expectedTriangles * 3) + { + m_triangleVertices.Capacity = expectedTriangles * 3; + } + + if (m_triangleVertices.Capacity < expectedTriangles * 3) + { + m_points.Capacity = expectedTriangles; + } + + } + + /// + /// Gets all the points of the stored triangles. + /// + public List Points + { + get + { + return m_points; + } + } + + /// + /// Gets the indices of the vertices of all the stored triangles. + /// + public List Triangles + { + get + { + return m_triangleVertices; + } + } + + /// + /// Gets the amount triangles store. + /// + public int TriangleCount + { + get + { + return m_triangleVertices.Count / 3; + } + } + + /// + /// Forms a new triangle using the existing points. + /// + /// All the data that describe triangle. + /// The index of the new triangle. + public int AddTriangle(DelaunayTriangle newTriangle) + { + m_adjacentTriangles.Add(newTriangle.adjacent[0]); + m_adjacentTriangles.Add(newTriangle.adjacent[1]); + m_adjacentTriangles.Add(newTriangle.adjacent[2]); + m_triangleVertices.Add(newTriangle.p[0]); + m_triangleVertices.Add(newTriangle.p[1]); + m_triangleVertices.Add(newTriangle.p[2]); + + return TriangleCount - 1; + } + + /// + /// Adds a new point to the triangle set. This does neither form triangles nor edges. + /// + /// The new point. + /// The index of the point. + public int AddPoint(Vector2 point) + { + m_points.Add(point); + return m_points.Count - 1; + } + + /// + /// Forms a new triangle using new points. + /// + /// The point for the first vertex. + /// The point for the second vertex. + /// The point for the third vertex. + /// The index of the first adjacent triangle. + /// The index of the second adjacent triangle. + /// The index of the third adjacent triangle. + /// The index of the new triangle. + public int AddTriangle(Vector2 p0, Vector2 p1, Vector2 p2, int adjacentTriangle0, int adjacentTriangle1, int adjacentTriangle2) + { + m_adjacentTriangles.Add(adjacentTriangle0); + m_adjacentTriangles.Add(adjacentTriangle1); + m_adjacentTriangles.Add(adjacentTriangle2); + m_triangleVertices.Add(AddPoint(p0)); + m_triangleVertices.Add(AddPoint(p1)); + m_triangleVertices.Add(AddPoint(p2)); + + return TriangleCount - 1; + } + + /// + /// Given the index of a point, it obtains all the existing triangles that share that point. + /// + /// The index of the point that is a vertex of the triangles. + /// The indices of the triangles that have that point as one of their vertices. No elements will be removed from the list. + public void GetTrianglesWithVertex(int vertexIndex, List outputTriangles) + { + for (int i = 0; i < TriangleCount; ++i) + { + for (int j = 0; j < 3; ++j) + { + if (m_triangleVertices[i * 3 + j] == vertexIndex) + { + outputTriangles.Add(i); + break; + } + } + } + } + + /// + /// Gets the points of a triangle. + /// + /// The index of the triangle. + /// The triangle. + public Triangle2D GetTrianglePoints(int triangleIndex) + { + return new Triangle2D(m_points[m_triangleVertices[triangleIndex * 3]], + m_points[m_triangleVertices[triangleIndex * 3 + 1]], + m_points[m_triangleVertices[triangleIndex * 3 + 2]]); + } + + /// + /// Gets the data of a triangle. + /// + /// The index of the triangle. + /// The triangle data. + public DelaunayTriangle GetTriangle(int triangleIndex) + { + return new DelaunayTriangle(m_triangleVertices[triangleIndex * 3], + m_triangleVertices[triangleIndex * 3 + 1], + m_triangleVertices[triangleIndex * 3 + 2], + m_adjacentTriangles[triangleIndex * 3], + m_adjacentTriangles[triangleIndex * 3 + 1], + m_adjacentTriangles[triangleIndex * 3 + 2]); + } + + /// + /// Given the outline of a closed polygon, expressed as a list of vertices, it finds all the triangles that lay inside of the figure. + /// + /// The outline, a list of vertex indices sorted counter-clockwise. + /// The list where the triangles found inside the polygon will be added. No elements are removed from this list. + public void GetTrianglesInPolygon(List polygonOutline, List outputTrianglesInPolygon) + { + // This method assumes that the edges of the triangles to find were created using the same vertex order + // It also assumes all triangles are inside a supertriangle, so no adjacent triangles are -1 + + Stack adjacentTriangles = new Stack(); + + // First it gets all the triangles of the outline + for (int i = 0; i < polygonOutline.Count; ++i) + { + // For every edge, it gets the inner triangle that contains such edge + DelaunayTriangleEdge triangleEdge = FindTriangleThatContainsEdge(polygonOutline[i], polygonOutline[(i + 1) % polygonOutline.Count]); + + // A triangle may form a corner, with 2 consecutive outline edges. This avoids adding it twice + if (outputTrianglesInPolygon.Count > 0 && + (outputTrianglesInPolygon[outputTrianglesInPolygon.Count - 1] == triangleEdge.TriangleIndex || // Is the last added triangle the same as current? + outputTrianglesInPolygon[0] == triangleEdge.TriangleIndex)) // Is the first added triangle the same as the current, which is the last to be added (closes the polygon)? + { + continue; + } + + outputTrianglesInPolygon.Add(triangleEdge.TriangleIndex); + + int previousOutlineEdgeVertexA = polygonOutline[(i + polygonOutline.Count - 1) % polygonOutline.Count]; + int previousOutlineEdgeVertexB = polygonOutline[i]; + int nextOutlineEdgeVertexA = polygonOutline[(i + 1) % polygonOutline.Count]; + int nextOutlineEdgeVertexB = polygonOutline[(i + 2) % polygonOutline.Count]; + + for (int j = 1; j < 3; ++j) // For the 2 adjacent triangles of the other 2 edges + { + int adjacentTriangle = m_adjacentTriangles[triangleEdge.TriangleIndex * 3 + (triangleEdge.EdgeIndex + j) % 3]; + bool isAdjacentTriangleInOutline = false; + + // Compares the contiguous edges of the outline, to the right and to the left of the current one, flipped and not flipped, with the adjacent triangle's edges + for (int k = 0; k < 3; ++k) + { + int currentTriangleEdgeVertexA = m_triangleVertices[adjacentTriangle * 3 + k]; + int currentTriangleEdgeVertexB = m_triangleVertices[adjacentTriangle * 3 + (k + 1) % 3]; + + if ((currentTriangleEdgeVertexA == previousOutlineEdgeVertexA && currentTriangleEdgeVertexB == previousOutlineEdgeVertexB) || + (currentTriangleEdgeVertexA == previousOutlineEdgeVertexB && currentTriangleEdgeVertexB == previousOutlineEdgeVertexA) || + (currentTriangleEdgeVertexA == nextOutlineEdgeVertexA && currentTriangleEdgeVertexB == nextOutlineEdgeVertexB) || + (currentTriangleEdgeVertexA == nextOutlineEdgeVertexB && currentTriangleEdgeVertexB == nextOutlineEdgeVertexA)) + { + isAdjacentTriangleInOutline = true; + } + } + + if (!isAdjacentTriangleInOutline && !outputTrianglesInPolygon.Contains(adjacentTriangle)) + { + adjacentTriangles.Push(adjacentTriangle); + } + + } + } + + // Then it propagates by adjacency, stopping when an adjacent triangle has already been included in the list + // Since all the outline triangles have been added previously, it will not propagate outside of the polygon + while (adjacentTriangles.Count > 0) + { + int currentTriangle = adjacentTriangles.Pop(); + + // The triangle may have been added already in a previous iteration + if (outputTrianglesInPolygon.Contains(currentTriangle)) + { + continue; + } + + for (int i = 0; i < 3; ++i) + { + int adjacentTriangle = m_adjacentTriangles[currentTriangle * 3 + i]; + + if (adjacentTriangle != NO_ADJACENT_TRIANGLE && !outputTrianglesInPolygon.Contains(adjacentTriangle)) + { + adjacentTriangles.Push(adjacentTriangle); + } + } + + outputTrianglesInPolygon.Add(currentTriangle); + } + } + + /// + /// Calculates which edges of the triangulation intersect with a proposed line segment AB. + /// + /// The first point of the line segment. + /// The second point of the line segment. + /// The index of the triangle from which to start searching for intersections. + /// The list where the intersected triangle edges will be added. No elements will be removed from this list. + public void GetIntersectingEdges(Vector2 lineEndpointA, Vector2 lineEndpointB, int startTriangle, List intersectingEdges) + { + bool isTriangleContainingBFound = false; + int triangleIndex = startTriangle; + + while (!isTriangleContainingBFound) + { + //DrawTriangle(triangleIndex, Color.green); + + bool hasCrossedEdge = false; + int tentativeAdjacentTriangle = NO_ADJACENT_TRIANGLE; + + for (int i = 0; i < 3; ++i) + { + if (m_points[m_triangleVertices[triangleIndex * 3 + i]] == lineEndpointB || + m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]] == lineEndpointB) + { + isTriangleContainingBFound = true; + break; + } + + if (MathUtils.IsPointToTheRightOfEdge(m_points[m_triangleVertices[triangleIndex * 3 + i]], m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]], lineEndpointB)) + { + tentativeAdjacentTriangle = i; + + //Debug.DrawLine(m_points[m_triangleVertices[triangleIndex * 3 + i]], m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]], Color.green, 10.0f); + + Vector2 intersectionPoint; + + if (MathUtils.IntersectionBetweenLines(m_points[m_triangleVertices[triangleIndex * 3 + i]], + m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]], + lineEndpointA, + lineEndpointB, + out intersectionPoint)) + { + hasCrossedEdge = true; + + intersectingEdges.Add(new DelaunayTriangleEdge(NOT_FOUND, NOT_FOUND, m_triangleVertices[triangleIndex * 3 + i], m_triangleVertices[triangleIndex * 3 + (i + 1) % 3])); + + //Debug.DrawLine(m_points[m_triangleVertices[triangleIndex * 3 + i]], m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]], Color.yellow, 10.0f); + //const float xlineLength = 0.008f; + //Debug.DrawRay(intersectionPoint - new Vector2(xlineLength * 0.5f, xlineLength * 0.5f), new Vector2(xlineLength, xlineLength), Color.red, 10.0f); + //Debug.DrawRay(intersectionPoint + new Vector2(-xlineLength * 0.5f, xlineLength * 0.5f), new Vector2(xlineLength, -xlineLength), Color.red, 10.0f); + + // The point is in the exterior of the triangle (vertices are sorted CCW, the right side is always the exterior from the perspective of the A->B edge) + triangleIndex = m_adjacentTriangles[triangleIndex * 3 + i]; + + break; + } + + } + } + + // Continue searching at a different adjacent triangle + if (!hasCrossedEdge) + { + triangleIndex = m_adjacentTriangles[triangleIndex * 3 + tentativeAdjacentTriangle]; + } + } + } + + /// + /// Gets a point by its index. + /// + /// The index of the point. + /// The point that corresponds to the index. + public Vector2 GetPointByIndex(int pointIndex) + { + return m_points[pointIndex]; + } + + /// + /// Gets the index of a point, if there is any that coincides with it in the triangulation. + /// + /// The point that is expected to exist already. + /// The index of the point. If the point does not exist, -1 is returned. + public int GetIndexOfPoint(Vector2 point) + { + int index = 0; + + while (index < m_points.Count && m_points[index] != point) + { + ++index; + } + + return index == m_points.Count ? -1 : index; + } + + /// + /// Given an edge AB, it searches for the triangle that has an edge with the same vertices in the same order. + /// + /// + /// Remember that the vertices of a triangle are sorted counter-clockwise. + /// + /// The index of the first vertex of the edge. + /// The index of the second vertex of the edge. + /// The data of the triangle. + public DelaunayTriangleEdge FindTriangleThatContainsEdge(int edgeVertexA, int edgeVertexB) + { + DelaunayTriangleEdge foundTriangle = new DelaunayTriangleEdge(NOT_FOUND, NOT_FOUND, edgeVertexA, edgeVertexB); + + for (int i = 0; i < TriangleCount; ++i) + { + for (int j = 0; j < 3; ++j) + { + if (m_triangleVertices[i * 3 + j] == edgeVertexA && m_triangleVertices[i * 3 + (j + 1) % 3] == edgeVertexB) + { + foundTriangle.TriangleIndex = i; + foundTriangle.EdgeIndex = j; + break; + } + } + } + + return foundTriangle; + } + + /// + /// Given a point, it searches for a triangle that contains it. + /// + /// The point expected to be contained by a triangle. + /// The index of the first triangle to check. + /// The index of the triangle that contains the point. + public int FindTriangleThatContainsPoint(Vector2 point, int startTriangle) + { + bool isTriangleFound = false; + int triangleIndex = startTriangle; + int checkedTriangles = 0; + + while (!isTriangleFound && checkedTriangles < TriangleCount) + { + isTriangleFound = true; + + for (int i = 0; i < 3; ++i) + { + if (MathUtils.IsPointToTheRightOfEdge(m_points[m_triangleVertices[triangleIndex * 3 + i]], m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]], point)) + { + // The point is in the exterior of the triangle (vertices are sorted CCW, the right side is always the exterior from the perspective of the A->B edge) + triangleIndex = m_adjacentTriangles[triangleIndex * 3 + i]; + + isTriangleFound = false; + break; + } + } + + checkedTriangles++; + } + + if (checkedTriangles >= TriangleCount && TriangleCount > 1) + { + Debug.LogWarning("Unable to find a triangle that contains the point (" + point.ToString("F6") + "), starting at triangle " + startTriangle + ". Are you generating very small triangles?"); + } + + return triangleIndex; + } + + /// + /// Given an edge AB, it searches for a triangle that contains the first point and the beginning of the edge. + /// + /// The index of the first point. + /// The index of the second point. + /// The index of the triangle that contains the first line endpoint. + public int FindTriangleThatContainsLineEndpoint(int endpointAIndex, int endpointBIndex) + { + List trianglesWithEndpoint = new List(); + GetTrianglesWithVertex(endpointAIndex, trianglesWithEndpoint); + + int foundTriangle = NOT_FOUND; + Vector2 endpointA = m_points[endpointAIndex]; + Vector2 endpointB = m_points[endpointBIndex]; + //Debug.DrawLine(endpointA + Vector2.up * 0.01f, endpointB + Vector2.up * 0.01f, Color.yellow, 10.0f); + + for (int i = 0; i < trianglesWithEndpoint.Count; ++i) + { + //DelaunayTriangle triangleDebug = GetTriangle(trianglesWithEndpoint[i]); + //List pointsDebug = triangleDebug.DebugP; + + int vertexPositionInTriangle = m_triangleVertices[trianglesWithEndpoint[i] * 3] == endpointAIndex ? 0 + : m_triangleVertices[trianglesWithEndpoint[i] * 3 + 1] == endpointAIndex ? 1 + : 2; + Vector2 triangleEdgePoint1 = m_points[m_triangleVertices[trianglesWithEndpoint[i] * 3 + (vertexPositionInTriangle + 1) % 3]]; + Vector2 triangleEdgePoint2 = m_points[m_triangleVertices[trianglesWithEndpoint[i] * 3 + (vertexPositionInTriangle + 2) % 3]]; + + // Is the line in the angle between the 2 contiguous edges of the triangle? + if (MathUtils.IsPointToTheRightOfEdge(triangleEdgePoint1, endpointA, endpointB) && + MathUtils.IsPointToTheRightOfEdge(endpointA, triangleEdgePoint2, endpointB)) + { + foundTriangle = trianglesWithEndpoint[i]; + break; + } + } + + return foundTriangle; + } + + /// + /// Stores the adjacency data of a triangle. + /// + /// The index of the triangle whose adjacency data is to be written. + /// The adjacency data, 3 triangle indices sorted counter-clockwise. + public void SetTriangleAdjacency(int triangleIndex, int* adjacentsToTriangle) + { + for (int i = 0; i < 3; ++i) + { + m_adjacentTriangles[triangleIndex * 3 + i] = adjacentsToTriangle[i]; + } + } + + /// + /// Given a triangle, it searches for an adjacent triangle and replaces it with another adjacent triangle. + /// + /// The index of the triangle whose adjacency data is to be modified. + /// The index of the adjacent triangle to be replaced. + /// The new index of an adjacent triangle that will replace the existing one. + public void ReplaceAdjacent(int triangleIndex, int oldAdjacentTriangle, int newAdjacentTriangle) + { + for (int i = 0; i < 3; ++i) + { + if (m_adjacentTriangles[triangleIndex * 3 + i] == oldAdjacentTriangle) + { + m_adjacentTriangles[triangleIndex * 3 + i] = newAdjacentTriangle; + } + } + } + + /// + /// Replaces all the data of a given triangle. The index of the triangle will remain the same. + /// + /// The index of the triangle whose data is to be replaced. + /// The new data that will replace the existing one. + public void ReplaceTriangle(int triangleToReplace, DelaunayTriangle newTriangle) + { + for (int i = 0; i < 3; ++i) + { + m_triangleVertices[triangleToReplace * 3 + i] = newTriangle.p[i]; + m_adjacentTriangles[triangleToReplace * 3 + i] = newTriangle.adjacent[i]; + } + } + + public void DrawTriangle(int triangleIndex, Color color) + { + for (int i = 0; i < 3; ++i) + { + Debug.DrawLine(m_points[m_triangleVertices[triangleIndex * 3 + i]], m_points[m_triangleVertices[triangleIndex * 3 + (i + 1) % 3]], color, 10.0f); + } + } + + public void LogDump() + { + for (int i = 0; i < TriangleCount; ++i) + { + string logEntry = "Triangle " + i + "("; + + for (int j = 0; j < 3; ++j) + { + logEntry += m_triangleVertices[i * 3 + j]; + + if (j < 2) + { + logEntry += ", "; + } + } + + logEntry += ")-A("; + + for (int j = 0; j < 3; ++j) + { + logEntry += m_adjacentTriangles[i * 3 + j]; + + if (j < 2) + { + logEntry += ", "; + } + } + + logEntry += ")-v("; + + for (int j = 0; j < 3; ++j) + { + logEntry += m_points[m_triangleVertices[i * 3 + j]].ToString("F6"); + + if (j < 2) + { + logEntry += ", "; + } + } + + logEntry += ")"; + + Debug.Log(logEntry); + } + } + } +} + diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleSet.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleSet.cs.meta new file mode 100644 index 00000000..f055c6ed --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangleSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92ab04265cf4b4e449f99e538eb21d22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangulation.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangulation.cs new file mode 100644 index 00000000..4614a053 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangulation.cs @@ -0,0 +1,864 @@ +// Copyright 2021 Alejandro Villalba Avila +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +using Game.Utils.Math; +using System.Collections.Generic; +using UnityEngine; + +namespace Game.Utils.Triangulation +{ + /// + /// Encapsulates the entire constrained Delaunay triangulation algorithm, according to S. W. Sloan's proposal, and stores the resulting triangulation. + /// Instantiate this class and call Triangulate to obtain the triangulation of a point cloud. + /// + public unsafe class DelaunayTriangulation + { + /// + /// Gets the metadata of all the generated triangles. + /// + public DelaunayTriangleSet TriangleSet + { + get + { + return m_triangleSet; + } + } + + /// + /// Gets the triangles generated by the Triangulate method that should be discarded, those that are inside holes or exclusively belong to the supertriangle. + /// + public List DiscardedTriangles + { + get + { + return m_trianglesToRemove; + } + } + + // The bin grid used for optimizing the search of triangles that contain a points + protected PointBinGrid m_grid; + + // The metadata of all the generated triangles + protected DelaunayTriangleSet m_triangleSet; + + // The stack of adjacent triangles, used when checking for the Delaunay constraint + protected Stack m_adjacentTriangleStack; + + // A stack, parallel to the adjacent triangles stack, that contains the local index [0, 2] of the edge shared among the adjacent triangle + // of the other stack and the triangle that was processed before it + protected Stack m_adjacentTriangleEdgeStack; + + // Indicates that the index of a vertex, edge or triangle is not defined or was not found + protected const int NOT_FOUND = -1; + + // Indicates that there is no adjacent triangle + protected const int NO_ADJACENT_TRIANGLE = -1; + + // The list of triangles to be discarded (holes and supertriangle) + protected List m_trianglesToRemove = new List(); + + // The bounding box of the main point cloud + protected Bounds m_mainPointCloudBounds = new Bounds(); + + /// + /// Generates the triangulation of a point cloud that fulfills the Delaunay constraint. It allows the creation of holes in the + /// triangulation, formed by closed polygons that do not overlap each other. + /// + /// The main point cloud. It must contain, at least, 3 points. + /// Optional. When it is greater than zero, all the triangles of the main point cloud will be tessellated until none of them occupies + /// an area greater than this value. + /// Optional. The list of holes. Each hole must be defined by a closed polygon formed by consecutive points sorted counter-clockwise. + /// It does not matter if the polugons are convex or concave. It is preferable that holes lay inside the main point cloud. + public void Triangulate(List inputPoints, float maximumAreaTesselation = 0.0f, List> constrainedEdges = null) + { + //float startTime = Time.realtimeSinceStartup; + + // Initialize containers + if (m_triangleSet == null) + { + m_triangleSet = new DelaunayTriangleSet(inputPoints.Count - 2); + } + else + { + m_triangleSet.Clear(); + m_triangleSet.SetCapacity(inputPoints.Count - 2); + } + + if (m_adjacentTriangleStack == null) + { + m_adjacentTriangleStack = new Stack(inputPoints.Count - 2); + m_adjacentTriangleEdgeStack = new Stack(inputPoints.Count - 2); + } + else + { + m_adjacentTriangleStack.Clear(); + m_adjacentTriangleEdgeStack.Clear(); + } + + if (m_trianglesToRemove == null) + { + m_trianglesToRemove = new List(); + } + else + { + m_trianglesToRemove.Clear(); + } + + // 1: Normalization + m_mainPointCloudBounds = CalculateBoundsWithLeftBottomCornerAtOrigin(inputPoints); + + List normalizedPoints = new List(inputPoints); + NormalizePoints(normalizedPoints, m_mainPointCloudBounds); + + //DelaunayTriangulation.DrawPoints(normalizedPoints, 30.0f); + + // 2: Addition of points to the space partitioning grid + Bounds normalizedCloudBounds = CalculateBoundsWithLeftBottomCornerAtOrigin(normalizedPoints); + m_grid = new PointBinGrid(Mathf.CeilToInt(Mathf.Sqrt(Mathf.Sqrt(inputPoints.Count))), normalizedCloudBounds.size); + + for (int i = 0; i < normalizedPoints.Count; ++i) + { + m_grid.AddPoint(normalizedPoints[i]); + } + + //m_grid.DrawGrid(new Color(0.0f, 0.0f, 1.0f, 0.2f), 10.0f); + + // 3: Supertriangle initialization + Triangle2D supertriangle = new Triangle2D(new Vector2(-100.0f, -100.0f), new Vector2(100.0f, -100.0f), new Vector2(0.0f, 100.0f)); // CCW + + m_triangleSet.AddTriangle(supertriangle.p0, supertriangle.p1, supertriangle.p2, NO_ADJACENT_TRIANGLE, NO_ADJACENT_TRIANGLE, NO_ADJACENT_TRIANGLE); + + // 4: Adding points to the Triangle set and Triangulation + // Points are added one at a time, and points that are close together are inserted together because they are sorted in the grid, + // so a later step for finding their containing triangle is faster + for (int i = 0; i < m_grid.Cells.Length; ++i) + { + // If the cell contains a bin with points... + if (m_grid.Cells[i] != null) + { + // All the points in the bin are added together, one by one + for (int j = 0; j < m_grid.Cells[i].Count; ++j) + { + AddPointToTriangulation(m_grid.Cells[i][j]); + } + } + } + + if (maximumAreaTesselation > 0.0f) + { + Tesselate(maximumAreaTesselation); + } + + // 5: Holes creation (constrained edges) + if (constrainedEdges != null) + { + List> constrainedEdgeIndices = new List>(); + + // Adds the points of all the polygons to the triangulation + for (int i = 0; i < constrainedEdges.Count; ++i) + { + // 5.1: Normalize + List normalizedConstrainedEdges = new List(constrainedEdges[i]); + NormalizePoints(normalizedConstrainedEdges, m_mainPointCloudBounds); + + List polygonEdgeIndices = new List(normalizedConstrainedEdges.Count); + + // 5.2: Add the points to the Triangle set + for (int j = 0; j < normalizedConstrainedEdges.Count - 0; ++j) + { + if (normalizedConstrainedEdges[j] == normalizedConstrainedEdges[(j + 1) % normalizedConstrainedEdges.Count]) + { + Debug.LogWarning($"The list of constrained edges contains a zero-length edge (2 consecutive coinciding points, indices {j} and {(j + 1) % normalizedConstrainedEdges.Count}). It will be ignored."); + continue; + } + + //Debug.DrawLine(normalizedConstrainedEdges[j], normalizedConstrainedEdges[(j + 1) % normalizedConstrainedEdges.Count], Color.cyan, 5.0f); + + int addedPointIndex = AddPointToTriangulation(normalizedConstrainedEdges[j]); + polygonEdgeIndices.Add(addedPointIndex); + } + + constrainedEdgeIndices.Add(polygonEdgeIndices); + } + + // 5.3: Create the constrained edges + for (int i = 0; i < constrainedEdgeIndices.Count; ++i) + { + for (int j = 0; j < constrainedEdgeIndices[i].Count - 0; ++j) + { + AddConstrainedEdgeToTriangulation(constrainedEdgeIndices[i][j], constrainedEdgeIndices[i][(j + 1) % constrainedEdgeIndices[i].Count]); + } + } + + // 5.4: Identify all the triangles in the polygon + for (int i = 0; i < constrainedEdgeIndices.Count; ++i) + { + m_triangleSet.GetTrianglesInPolygon(constrainedEdgeIndices[i], m_trianglesToRemove); + } + + // Remove all the triangles left that are not part of the main cloud + // TODO: How? + } + + // 6: Supertriangle removal + GetSupertriangleTriangles(m_trianglesToRemove); + + // 7: Denormalization + DenormalizePoints(m_triangleSet.Points, m_mainPointCloudBounds); + + //for (int i = 0; i < m_trianglesToRemove.Count; ++i) + //{ + // m_triangleSet.DrawTriangle(m_trianglesToRemove[i], Color.magenta); + //} + + //Debug.Log("Total time: " + (Time.realtimeSinceStartup - startTime).ToString("F6")); + + //m_triangles.LogDump(); + + // Debug.Log("Total" + m_triangleSet.TriangleCount); + for (var i = 0; i < m_triangleSet.TriangleCount; ++i) + { + var trianglePoint = m_triangleSet.GetTrianglePoints(i); + var p0 = trianglePoint.p0; + var p1 = trianglePoint.p1; + var p2 = trianglePoint.p2; + + var centerPoint = (p0 + p1 + p2) / 3f; + + if (IsPointInsidePolygon(centerPoint, inputPoints) == false) + { + m_trianglesToRemove.Add(i); + } + } + + m_trianglesToRemove.Sort(); + + } + + // Function to check if a point is inside the polygon + private bool IsPointInsidePolygon(in Vector2 testPoint, in IReadOnlyList polygonVertices) + { + bool inside = false; + int j = polygonVertices.Count - 1; + for (int i = 0; i < polygonVertices.Count; j = i++) + { + if (((polygonVertices[i].y > testPoint.y) != (polygonVertices[j].y > testPoint.y)) && + (testPoint.x < (polygonVertices[j].x - polygonVertices[i].x) * (testPoint.y - polygonVertices[i].y) / (polygonVertices[j].y - polygonVertices[i].y) + polygonVertices[i].x)) + { + inside = !inside; + } + } + return inside; + } + + /// + /// Reads the triangles generated by the Triangulate method, discarding all those triangles that are inside a hole or belong to the supertriangle. + /// + /// The list to which the triangles will be added. No elements will be removed from this list. + public void GetTrianglesDiscardingHoles(List outputTriangles) + { + if (outputTriangles.Capacity < m_triangleSet.TriangleCount) + { + outputTriangles.Capacity = m_triangleSet.TriangleCount; + } + + // 8: Output filtering + for (int i = 0; i < m_triangleSet.TriangleCount; ++i) + { + var isTriangleToBeRemoved = false; + + // Is the triangle in the "To Remove" list? + for (int j = 0; j < m_trianglesToRemove.Count; ++j) + { + if (m_trianglesToRemove[j] >= i) + { + //m_trianglesToRemove.RemoveAt(j); + isTriangleToBeRemoved = m_trianglesToRemove[j] == i; + break; + } + } + + if (!isTriangleToBeRemoved) + { + var triangle = m_triangleSet.GetTriangle(i); + outputTriangles.Add(new Triangle2D(m_triangleSet.Points[triangle.p[0]], m_triangleSet.Points[triangle.p[1]], m_triangleSet.Points[triangle.p[2]])); + } + } + } + + /// + /// Reads all the triangles generated by the Triangulate method, without discarding any. + /// + /// The list to which the triangles will be added. No elements will be removed from this list. + public void GetAllTriangles(List outputTriangles) + { + if (outputTriangles.Capacity < m_triangleSet.TriangleCount) + { + outputTriangles.Capacity = m_triangleSet.TriangleCount; + } + + for (int i = 0; i < m_triangleSet.TriangleCount; ++i) + { + DelaunayTriangle triangle = m_triangleSet.GetTriangle(i); + outputTriangles.Add(new Triangle2D(m_triangleSet.Points[triangle.p[0]], m_triangleSet.Points[triangle.p[1]], m_triangleSet.Points[triangle.p[2]])); + } + } + + /// + /// Adds a point to the triangulation, which implies splitting a triangle into 3 pieces and checking that all triangles still fulfill the Delaunay constraint. + /// + /// + /// If the point coincides in space with an existing point, nothing will be done and the index of the existing point will be returned. + /// + /// The point to add to the triangulation. + /// The index of the new point in the triangle set. + private int AddPointToTriangulation(Vector2 pointToInsert) + { + // Note: Adjacent triangle, opposite to the inserted point, is always at index 1 + // Note 2: Adjacent triangles are stored CCW automatically, their index matches the index of the first vertex in every edge, and it is known that vertices are stored CCW + + // 4.1: Check point existence + int existingPointIndex = m_triangleSet.GetIndexOfPoint(pointToInsert); + + if (existingPointIndex != NOT_FOUND) + { + return existingPointIndex; + } + + // 4.2: Search containing triangle + int containingTriangleIndex = m_triangleSet.FindTriangleThatContainsPoint(pointToInsert, m_triangleSet.TriangleCount - 1); // Start at the last added triangle + + DelaunayTriangle containingTriangle = m_triangleSet.GetTriangle(containingTriangleIndex); + + // 4.3: Store the point + // Inserting a new point into a triangle splits it into 3 pieces, 3 new triangles + int insertedPoint = m_triangleSet.AddPoint(pointToInsert); + + // 4.4: Create 2 triangles + DelaunayTriangle newTriangle1 = new DelaunayTriangle(insertedPoint, containingTriangle.p[0], containingTriangle.p[1]); + newTriangle1.adjacent[0] = NO_ADJACENT_TRIANGLE; + newTriangle1.adjacent[1] = containingTriangle.adjacent[0]; + newTriangle1.adjacent[2] = containingTriangleIndex; + int triangle1Index = m_triangleSet.AddTriangle(newTriangle1); + + DelaunayTriangle newTriangle2 = new DelaunayTriangle(insertedPoint, containingTriangle.p[2], containingTriangle.p[0]); + newTriangle2.adjacent[0] = containingTriangleIndex; + newTriangle2.adjacent[1] = containingTriangle.adjacent[2]; + newTriangle2.adjacent[2] = NO_ADJACENT_TRIANGLE; + int triangle2Index = m_triangleSet.AddTriangle(newTriangle2); + + // Sets adjacency between the 2 new triangles + newTriangle1.adjacent[0] = triangle2Index; + newTriangle2.adjacent[2] = triangle1Index; + m_triangleSet.SetTriangleAdjacency(triangle1Index, newTriangle1.adjacent); + m_triangleSet.SetTriangleAdjacency(triangle2Index, newTriangle2.adjacent); + + // Sets the adjacency of the triangles that were adjacent to the original containing triangle + if (newTriangle1.adjacent[1] != NO_ADJACENT_TRIANGLE) + { + m_triangleSet.ReplaceAdjacent(newTriangle1.adjacent[1], containingTriangleIndex, triangle1Index); + } + + if (newTriangle2.adjacent[1] != NO_ADJACENT_TRIANGLE) + { + m_triangleSet.ReplaceAdjacent(newTriangle2.adjacent[1], containingTriangleIndex, triangle2Index); + } + + // 4.5: Transform containing triangle into the third + // Original triangle is transformed into the third triangle after the point has split the containing triangle into 3 + containingTriangle.p[0] = insertedPoint; + containingTriangle.adjacent[0] = triangle1Index; + containingTriangle.adjacent[2] = triangle2Index; + m_triangleSet.ReplaceTriangle(containingTriangleIndex, containingTriangle); + + // 4.6: Add new triangles to a stack + // Triangles that contain the inserted point are added to the stack for them to be processed by the Delaunay swapping algorithm + if (containingTriangle.adjacent[1] != NO_ADJACENT_TRIANGLE) // If they do not have an opposite triangle in the outter edge, there is no need to check the Delaunay constraint for it + { + m_adjacentTriangleStack.Push(containingTriangleIndex); + m_adjacentTriangleEdgeStack.Push(1); + } + + if (newTriangle1.adjacent[1] != NO_ADJACENT_TRIANGLE) + { + m_adjacentTriangleStack.Push(triangle1Index); + m_adjacentTriangleEdgeStack.Push(1); + } + + if (newTriangle2.adjacent[1] != NO_ADJACENT_TRIANGLE) + { + m_adjacentTriangleStack.Push(triangle2Index); + m_adjacentTriangleEdgeStack.Push(1); + } + + // 4.7: Check Delaunay constraint + FulfillDelaunayConstraint(m_adjacentTriangleStack, m_adjacentTriangleEdgeStack); + + return insertedPoint; + } + + /// + /// Process a stack of triangles checking whether they fulfill the Delaunay constraint with respect to their adjacents, swapping edges if they do not. + /// The adjacent triangles of the processed triangles are added to the stack too, so the check propagates until they all fulfill the condition. + /// + /// Initial set of triangles to check. + /// The local index (0 to 2) of the edges shared among the triangles in adjacentTrianglesToProcess and the triangles that preceded + /// them at the moment they were added. There is one edge per triangle. + private void FulfillDelaunayConstraint(Stack adjacentTrianglesToProcess, Stack adjacentTriangleEdges) + { + while (adjacentTrianglesToProcess.Count > 0) + { + int currentTriangleToSwap = adjacentTrianglesToProcess.Pop(); + DelaunayTriangle triangle = m_triangleSet.GetTriangle(currentTriangleToSwap); + + int OPPOSITE_TRIANGLE_INDEX = adjacentTriangleEdges.Pop(); + + if (triangle.adjacent[OPPOSITE_TRIANGLE_INDEX] == NO_ADJACENT_TRIANGLE) + { + continue; + } + + int NOT_IN_EDGE_VERTEX_INDEX = (OPPOSITE_TRIANGLE_INDEX + 2) % 3; + Vector2 triangleVertexNotInEdge = m_triangleSet.GetPointByIndex(triangle.p[NOT_IN_EDGE_VERTEX_INDEX]); + + DelaunayTriangle oppositeTriangle = m_triangleSet.GetTriangle(triangle.adjacent[OPPOSITE_TRIANGLE_INDEX]); + Triangle2D oppositeTrianglePoints = m_triangleSet.GetTrianglePoints(triangle.adjacent[OPPOSITE_TRIANGLE_INDEX]); + + if (MathUtils.IsPointInsideCircumcircle(oppositeTrianglePoints.p0, oppositeTrianglePoints.p1, oppositeTrianglePoints.p2, triangleVertexNotInEdge)) + { + // Finds the edge of the opposite triangle that is shared with the other triangle, this edge will be swapped + int sharedEdgeVertexLocalIndex = GetSharedEdge(oppositeTriangle, currentTriangleToSwap); + + // Adds the 2 triangles that were adjacent to the opposite triangle, to be processed too + int oppositeAdjacent0 = oppositeTriangle.adjacent[(sharedEdgeVertexLocalIndex + 1) % 3]; + if (oppositeAdjacent0 != NO_ADJACENT_TRIANGLE && !adjacentTrianglesToProcess.Contains(oppositeAdjacent0)) + { + adjacentTrianglesToProcess.Push(oppositeAdjacent0); + int neighborEdge = GetSharedEdge(m_triangleSet.GetTriangle(oppositeAdjacent0), triangle.adjacent[OPPOSITE_TRIANGLE_INDEX]); + adjacentTriangleEdges.Push(neighborEdge); + } + + int oppositeAdjacent1 = oppositeTriangle.adjacent[(sharedEdgeVertexLocalIndex + 2) % 3]; + if (oppositeAdjacent1 != NO_ADJACENT_TRIANGLE && !adjacentTrianglesToProcess.Contains(oppositeAdjacent1)) + { + adjacentTrianglesToProcess.Push(oppositeAdjacent1); + int neighborEdge = GetSharedEdge(m_triangleSet.GetTriangle(oppositeAdjacent1), triangle.adjacent[OPPOSITE_TRIANGLE_INDEX]); + adjacentTriangleEdges.Push(neighborEdge); + } + + int triangleAdjacent0 = triangle.adjacent[NOT_IN_EDGE_VERTEX_INDEX]; + if (triangleAdjacent0 != NO_ADJACENT_TRIANGLE && !adjacentTrianglesToProcess.Contains(triangleAdjacent0)) + { + adjacentTrianglesToProcess.Push(triangleAdjacent0); + int neighborEdge = GetSharedEdge(m_triangleSet.GetTriangle(triangleAdjacent0), currentTriangleToSwap); + adjacentTriangleEdges.Push(neighborEdge); + } + + int triangleAdjacent1 = triangle.adjacent[(NOT_IN_EDGE_VERTEX_INDEX + 2) % 3]; + if (triangleAdjacent1 != NO_ADJACENT_TRIANGLE && !adjacentTrianglesToProcess.Contains(triangleAdjacent1)) + { + adjacentTrianglesToProcess.Push(triangleAdjacent1); + int neighborEdge = GetSharedEdge(m_triangleSet.GetTriangle(triangleAdjacent1), currentTriangleToSwap); + adjacentTriangleEdges.Push(neighborEdge); + } + + // 4.8: Swap edges + SwapEdges(currentTriangleToSwap, triangle, NOT_IN_EDGE_VERTEX_INDEX, oppositeTriangle, sharedEdgeVertexLocalIndex); + } + } + } + + /// + /// Finds the the index of the edge (0 to 2) of a triangle that is shared with another triangle. + /// + /// The triangle whose edge is to be returned. + /// The index of the adjacent triangle. + /// The index of the shared edge in the first triangle, from 0 to 2. + private int GetSharedEdge(DelaunayTriangle triangle, int adjacentTriangle) + { + for (int sharedEdgeVertexLocalIndex = 0; sharedEdgeVertexLocalIndex < 3; ++sharedEdgeVertexLocalIndex) + { + if (triangle.adjacent[sharedEdgeVertexLocalIndex] == adjacentTriangle) + { + return sharedEdgeVertexLocalIndex; + } + } + + return NO_ADJACENT_TRIANGLE; + } + + /// + /// Given 2 adjacent triangles, it replaces the shared edge with a new edge that joins both opposite vertices. For example, triangles ABC-CBD would become ADC-ABD. + /// + /// + /// For the main triangle, its shared edge vertex is moved so the new shared edge vertex is 1 position behind / or 2 forward (if it was 1, now the shared edge is 0). + /// + /// The index of the main triangle. + /// Data about the main triangle. + /// The local index of the vertex that is not in the shared edge, in the main triangle. + /// Data about the triangle that opposes the main triangle. + /// The local index of the vertex where the shared edge begins, in the opposite triangle. + private void SwapEdges(int mainTriangleIndex, DelaunayTriangle mainTriangle, int notInEdgeVertexLocalIndex, DelaunayTriangle oppositeTriangle, int oppositeTriangleSharedEdgeVertexLocalIndex) + { + //List debugP = triangle.DebugP; + //List debugA = triangle.DebugAdjacent; + //List debugP2 = oppositeTriangle.DebugP; + //List debugA2 = oppositeTriangle.DebugAdjacent; + + int oppositeVertex = (oppositeTriangleSharedEdgeVertexLocalIndex + 2) % 3; + + // 2 _|_ a + // A2 _ | _ + // _ | _ + // 0 _ A1 | _ c (opposite vertex) + // _ | _ + // _ | _ + // A0 _ |_ + // | + // 1 b + + // 2 _|_ + // A2 _ _ A1 + // _ _ + // 0 _________A0_______ 1 + // a _ _ c + // _ _ + // _ _ + // | b + // + + // Only one vertex of each triangle is moved + int oppositeTriangleIndex = mainTriangle.adjacent[(notInEdgeVertexLocalIndex + 1) % 3]; + mainTriangle.p[(notInEdgeVertexLocalIndex + 1) % 3] = oppositeTriangle.p[oppositeVertex]; + oppositeTriangle.p[oppositeTriangleSharedEdgeVertexLocalIndex] = mainTriangle.p[notInEdgeVertexLocalIndex]; + oppositeTriangle.adjacent[oppositeTriangleSharedEdgeVertexLocalIndex] = mainTriangle.adjacent[notInEdgeVertexLocalIndex]; + mainTriangle.adjacent[notInEdgeVertexLocalIndex] = oppositeTriangleIndex; + mainTriangle.adjacent[(notInEdgeVertexLocalIndex + 1) % 3] = oppositeTriangle.adjacent[oppositeVertex]; + oppositeTriangle.adjacent[oppositeVertex] = mainTriangleIndex; + + m_triangleSet.ReplaceTriangle(mainTriangleIndex, mainTriangle); + m_triangleSet.ReplaceTriangle(oppositeTriangleIndex, oppositeTriangle); + + // Adjacent triangles are updated too + if (mainTriangle.adjacent[(notInEdgeVertexLocalIndex + 1) % 3] != NO_ADJACENT_TRIANGLE) + { + m_triangleSet.ReplaceAdjacent(mainTriangle.adjacent[(notInEdgeVertexLocalIndex + 1) % 3], oppositeTriangleIndex, mainTriangleIndex); + } + + if (oppositeTriangle.adjacent[oppositeTriangleSharedEdgeVertexLocalIndex] != NO_ADJACENT_TRIANGLE) + { + m_triangleSet.ReplaceAdjacent(oppositeTriangle.adjacent[oppositeTriangleSharedEdgeVertexLocalIndex], mainTriangleIndex, oppositeTriangleIndex); + } + } + + /// + /// Adds an edge to the triangulation in such a way that it keeps there even if it form triangles that do not fulfill the Delaunay constraint. + /// If the edge already exists, nothing will be done. + /// + /// + /// The order in which the vertices of the edges are provided is important, as the edge may be part of a polygon whose vertices are sorted CCW. + /// + /// The index of the first vertex of the edge, in the existing triangulation. + /// The index of the second vertex of the edge, in the existing triangulation. + private void AddConstrainedEdgeToTriangulation(int endpointAIndex, int endpointBIndex) + { + // Detects if the edge already exists + if (m_triangleSet.FindTriangleThatContainsEdge(endpointAIndex, endpointBIndex).TriangleIndex != NOT_FOUND) + { + return; + } + + Vector2 edgeEndpointA = m_triangleSet.GetPointByIndex(endpointAIndex); + Vector2 edgeEndpointB = m_triangleSet.GetPointByIndex(endpointBIndex); + + // 5.3.1: Search for the triangle that contains the beginning of the new edge + int triangleContainingA = m_triangleSet.FindTriangleThatContainsLineEndpoint(endpointAIndex, endpointBIndex); + + + // 5.3.2: Get all the triangle edges intersected by the constrained edge + List intersectedTriangleEdges = new List(); + m_triangleSet.GetIntersectingEdges(edgeEndpointA, edgeEndpointB, triangleContainingA, intersectedTriangleEdges); + + List newEdges = new List(); + + while (intersectedTriangleEdges.Count > 0) + { + DelaunayTriangleEdge currentIntersectedTriangleEdge = intersectedTriangleEdges[intersectedTriangleEdges.Count - 1]; + intersectedTriangleEdges.RemoveAt(intersectedTriangleEdges.Count - 1); + + // 5.3.3: Form quadrilaterals and swap intersected edges + // Deduces the data for both triangles + currentIntersectedTriangleEdge = m_triangleSet.FindTriangleThatContainsEdge(currentIntersectedTriangleEdge.EdgeVertexA, currentIntersectedTriangleEdge.EdgeVertexB); + DelaunayTriangle intersectedTriangle = m_triangleSet.GetTriangle(currentIntersectedTriangleEdge.TriangleIndex); + DelaunayTriangle oppositeTriangle = m_triangleSet.GetTriangle(intersectedTriangle.adjacent[currentIntersectedTriangleEdge.EdgeIndex]); + Triangle2D trianglePoints = m_triangleSet.GetTrianglePoints(currentIntersectedTriangleEdge.TriangleIndex); + + // Gets the opposite vertex of adjacent triangle, knowing the fisrt vertex of the shared edge + int oppositeVertex = NOT_FOUND; + + //List debugP = intersectedTriangle.DebugP; + //List debugA = intersectedTriangle.DebugAdjacent; + //List debugP2 = oppositeTriangle.DebugP; + //List debugA2 = oppositeTriangle.DebugAdjacent; + + int oppositeSharedEdgeVertex = NOT_FOUND; // The first vertex in the shared edge of the opposite triangle + + for (int j = 0; j < 3; ++j) + { + if (oppositeTriangle.p[j] == intersectedTriangle.p[(currentIntersectedTriangleEdge.EdgeIndex + 1) % 3]) // Comparing with the endpoint B of the edge, since the edge AB is BA in the adjacent triangle + { + oppositeVertex = oppositeTriangle.p[(j + 2) % 3]; + oppositeSharedEdgeVertex = j; + break; + } + } + + Vector2 oppositePoint = m_triangleSet.GetPointByIndex(oppositeVertex); + + if (MathUtils.IsQuadrilateralConvex(trianglePoints.p0, trianglePoints.p1, trianglePoints.p2, oppositePoint)) + { + // Swap + int notInEdgeTriangleVertex = (currentIntersectedTriangleEdge.EdgeIndex + 2) % 3; + SwapEdges(currentIntersectedTriangleEdge.TriangleIndex, intersectedTriangle, notInEdgeTriangleVertex, oppositeTriangle, oppositeSharedEdgeVertex); + + // Refreshes triangle data after swapping + intersectedTriangle = m_triangleSet.GetTriangle(currentIntersectedTriangleEdge.TriangleIndex); + + //oppositeTriangle = m_triangles.GetTriangle(intersectedTriangle.adjacent[(currentIntersectedTriangleEdge.EdgeIndex + 2) % 3]); + //debugP = intersectedTriangle.DebugP; + //debugA = intersectedTriangle.DebugAdjacent; + //debugP2 = oppositeTriangle.DebugP; + //debugA2 = oppositeTriangle.DebugAdjacent; + + // Check new diagonal against the intersecting edge + Vector2 intersectionPoint; + int newTriangleSharedEdgeVertex = (currentIntersectedTriangleEdge.EdgeIndex + 2) % 3; // Read SwapEdges method to understand the +2 + Vector2 newTriangleSharedEdgePointA = m_triangleSet.GetPointByIndex(intersectedTriangle.p[newTriangleSharedEdgeVertex]); + Vector2 newTriangleSharedEdgePointB = m_triangleSet.GetPointByIndex(intersectedTriangle.p[(newTriangleSharedEdgeVertex + 1) % 3]); + + DelaunayTriangleEdge newEdge = new DelaunayTriangleEdge(NOT_FOUND, NOT_FOUND, intersectedTriangle.p[newTriangleSharedEdgeVertex], intersectedTriangle.p[(newTriangleSharedEdgeVertex + 1) % 3]); + + if (newTriangleSharedEdgePointA != edgeEndpointB && newTriangleSharedEdgePointB != edgeEndpointB && // Watch out! It thinks the line intersects with the edge when an endpoint coincides with a triangle vertex, this problem is avoided thanks to this conditions + newTriangleSharedEdgePointA != edgeEndpointA && newTriangleSharedEdgePointB != edgeEndpointA && + MathUtils.IntersectionBetweenLines(edgeEndpointA, edgeEndpointB, newTriangleSharedEdgePointA, newTriangleSharedEdgePointB, out intersectionPoint)) + { + // New triangles edge still intersects with the constrained edge, so it is returned to the list + intersectedTriangleEdges.Insert(0, newEdge); + } + else + { + newEdges.Add(newEdge); + } + } + else + { + // Back to the list + intersectedTriangleEdges.Insert(0, currentIntersectedTriangleEdge); + } + } + + // 5.3.4. Check Delaunay constraint and swap edges + for (int i = 0; i < newEdges.Count; ++i) + { + // Checks if the constrained edge coincides with the new edge + Vector2 triangleEdgePointA = m_triangleSet.GetPointByIndex(newEdges[i].EdgeVertexA); + Vector2 triangleEdgePointB = m_triangleSet.GetPointByIndex(newEdges[i].EdgeVertexB); + + if ((triangleEdgePointA == edgeEndpointA && triangleEdgePointB == edgeEndpointB) || + (triangleEdgePointB == edgeEndpointA && triangleEdgePointA == edgeEndpointB)) + { + continue; + } + + // Deduces the data for both triangles + DelaunayTriangleEdge currentEdge = m_triangleSet.FindTriangleThatContainsEdge(newEdges[i].EdgeVertexA, newEdges[i].EdgeVertexB); + DelaunayTriangle currentEdgeTriangle = m_triangleSet.GetTriangle(currentEdge.TriangleIndex); + int triangleVertexNotShared = (currentEdge.EdgeIndex + 2) % 3; + Vector2 trianglePointNotShared = m_triangleSet.GetPointByIndex(currentEdgeTriangle.p[triangleVertexNotShared]); + DelaunayTriangle oppositeTriangle = m_triangleSet.GetTriangle(currentEdgeTriangle.adjacent[currentEdge.EdgeIndex]); + Triangle2D oppositeTrianglePoints = m_triangleSet.GetTrianglePoints(currentEdgeTriangle.adjacent[currentEdge.EdgeIndex]); + + //List debugP = currentEdgeTriangle.DebugP; + //List debugA = currentEdgeTriangle.DebugAdjacent; + //List debugP2 = oppositeTriangle.DebugP; + //List debugA2 = oppositeTriangle.DebugAdjacent; + + if (MathUtils.IsPointInsideCircumcircle(oppositeTrianglePoints.p0, oppositeTrianglePoints.p1, oppositeTrianglePoints.p2, trianglePointNotShared)) + { + // Finds the edge of the opposite triangle that is shared with the other triangle, this edge will be swapped + int sharedEdgeVertexLocalIndex = 0; + + for (; sharedEdgeVertexLocalIndex < 3; ++sharedEdgeVertexLocalIndex) + { + if (oppositeTriangle.adjacent[sharedEdgeVertexLocalIndex] == currentEdge.TriangleIndex) + { + break; + } + } + + // Swap + SwapEdges(currentEdge.TriangleIndex, currentEdgeTriangle, triangleVertexNotShared, oppositeTriangle, sharedEdgeVertexLocalIndex); + } + } + + //Debug.DrawLine(edgeEndpointA, edgeEndpointB, Color.magenta, 10.0f); + } + + /// + /// Gets all the triangles that contain any of the vertices of the supertriangle. + /// + /// The triangles of the supertriangle. + private void GetSupertriangleTriangles(List outputTriangles) + { + for (int i = 0; i < 3; ++i) // Vertices of the supertriangle + { + List trianglesThatShareVertex = new List(); + m_triangleSet.GetTrianglesWithVertex(i, trianglesThatShareVertex); + + for (int j = 0; j < trianglesThatShareVertex.Count; ++j) + { + if (!outputTriangles.Contains(trianglesThatShareVertex[j])) + { + outputTriangles.Add(trianglesThatShareVertex[j]); + } + } + } + } + + /// + /// Calculates the bounds of a point cloud, in such a way that the minimum position becomes the center of the box. + /// + /// The points whose bound is to be calculated. + /// The bounds that contains all the points. + private Bounds CalculateBoundsWithLeftBottomCornerAtOrigin(List points) + { + Vector2 newMin = new Vector2(float.MaxValue, float.MaxValue); + Vector2 newMax = new Vector2(float.MinValue, float.MinValue); + + for (int i = 0; i < points.Count; ++i) + { + if (points[i].x > newMax.x) + { + newMax.x = points[i].x; + } + + if (points[i].y > newMax.y) + { + newMax.y = points[i].y; + } + + if (points[i].x < newMin.x) + { + newMin.x = points[i].x; + } + + if (points[i].y < newMin.y) + { + newMin.y = points[i].y; + } + } + + Vector2 size = new Vector2(Mathf.Abs(newMax.x - newMin.x), Mathf.Abs(newMax.y - newMin.y)); + + return new Bounds(size * 0.5f + newMin, size); + } + + /// + /// Normalizes a list of points according to a bounding box so all of them lay between the coordinates [0,0] and [1,1], while they conserve their + /// relative position with respect to the others. + /// + /// The input points to normalize. The points in the list will be updated. + /// The bounding box in which the normalization is based. + private void NormalizePoints(List inputOutputPoints, Bounds bounds) + { + float maximumDimension = Mathf.Max(bounds.size.x, bounds.size.y); + + for (int i = 0; i < inputOutputPoints.Count; ++i) + { + inputOutputPoints[i] = (inputOutputPoints[i] - (Vector2)bounds.min) / maximumDimension; + } + } + + /// + /// Denormalizes a list of points according to a bounding box so all of them lay between the coordinates determined by such box, while they conserve their + /// relative position with respect to the others. + /// + /// The points to denormalize. They are expected to be previously normalized. The points in the list will be updated. + /// The bounding box in which the denormalization is based. + private void DenormalizePoints(List inputOutputPoints, Bounds bounds) + { + float maximumDimension = Mathf.Max(bounds.size.x, bounds.size.y); + + for (int i = 0; i < inputOutputPoints.Count; ++i) + { + inputOutputPoints[i] = inputOutputPoints[i] * maximumDimension + (Vector2)bounds.min; + } + } + + /// + /// For each triangle, it splits its edges in 2 pieces, generating 4 subtriangles. The operation is repeated until none of them has an area greater than the desired value. + /// + /// + /// The triangles that exclusively belong to the supertriangle will be ignored. + /// + /// The maximum area all the triangles will have after the tessellation. + protected void Tesselate(float maximumTriangleArea) + { + int i = 2; // Skips supertriangle + + while (i < m_triangleSet.TriangleCount - 1) + { + ++i; + + // Skips all the Supertriangle triangles + bool isSupertriangle = false; + DelaunayTriangle triangleData = m_triangleSet.GetTriangle(i); + + for (int j = 0; j < 3; ++j) + { + if (triangleData.p[j] == 0 || triangleData.p[j] == 1 || triangleData.p[j] == 2) // 0, 1 and 2 are vertices of the supertriangle + { + isSupertriangle = true; + break; + } + } + + if (isSupertriangle) + { + continue; + } + + Triangle2D trianglePoints = m_triangleSet.GetTrianglePoints(i); + float triangleArea = MathUtils.CalculateTriangleArea(trianglePoints.p0, trianglePoints.p1, trianglePoints.p2); + + if (triangleArea > maximumTriangleArea) + { + AddPointToTriangulation(trianglePoints.p0 + (trianglePoints.p1 - trianglePoints.p0) * 0.5f); + AddPointToTriangulation(trianglePoints.p1 + (trianglePoints.p2 - trianglePoints.p1) * 0.5f); + AddPointToTriangulation(trianglePoints.p2 + (trianglePoints.p0 - trianglePoints.p2) * 0.5f); + + i = 2; // The tesselation restarts + } + } + } + + public static void DrawPoints(List points, float duration) + { + for (int i = 0; i < points.Count; ++i) + { + Debug.DrawRay(points[i], Vector2.up * 0.2f, Color.red, duration); + Debug.DrawRay(points[i], Vector2.right * 0.2f, Color.green, duration); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangulation.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangulation.cs.meta new file mode 100644 index 00000000..52527b5f --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/DelaunayTriangulation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6b1b29c5f20ceb44994208c0c20e29f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/MathUtils.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/MathUtils.cs new file mode 100644 index 00000000..541fd975 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/MathUtils.cs @@ -0,0 +1,233 @@ +using UnityEngine; + +namespace Game.Utils.Math +{ + public static class MathUtils + { + /// + /// Calculates the determinan of a 3 columns x 3 rows matrix. + /// + /// The element at position (0, 0). + /// The element at position (1, 0). + /// The element at position (2, 0). + /// The element at position (0, 1). + /// The element at position (1, 1). + /// The element at position (2, 1). + /// The element at position (0, 2). + /// The element at position (1, 2). + /// The element at position (2, 2). + /// The determinant. + public static float CalculateMatrix3x3Determinant(float m00, float m10, float m20, + float m01, float m11, float m21, + float m02, float m12, float m22) + { + return m00 * m11 * m22 + m10 * m21 * m02 + m20 * m01 * m12 - m20 * m11 * m02 - m10 * m01 * m22 - m00 * m21 * m12; + } + + /// + /// Checks whether a point lays on the right side of an edge. + /// + /// The first point of the edge. + /// The second point of the edge. + /// The point to check. + /// True if the point is on the right side; False if the point is on the left side or is contained in the edge. + public static bool IsPointToTheRightOfEdge(Vector2 edgeEndpointA, Vector2 edgeEndpointB, Vector2 point) + { + Vector2 aToB = (edgeEndpointB - edgeEndpointA).normalized; // Normalization is quite important to avoid precision loss! + Vector2 aToP = (point - edgeEndpointA).normalized; + Vector3 ab_x_p = Vector3.Cross(aToB, aToP); + return ab_x_p.z < -0.0001f; // Note: Due to extremely small negative values were causing wrong results, a tolerance is used instead of zero + } + + /// + /// Checks whether a point is contained in a triangle. The vertices of the triangle must be sorted counter-clockwise. + /// + /// The first vertex of the triangle. + /// The second vertex of the triangle. + /// The third vertex of the triangle. + /// The point that may be contained. + /// True if the point is contained in the triangle; false otherwise. + public static bool IsPointInsideTriangle(Vector2 triangleP0, Vector2 triangleP1, Vector2 triangleP2, Vector2 pointToCheck) + { + Vector3 ab_x_p = Vector3.Cross(triangleP1 - triangleP0, pointToCheck); + Vector3 bc_x_p = Vector3.Cross(triangleP2 - triangleP1, pointToCheck); + Vector3 ca_x_p = Vector3.Cross(triangleP0 - triangleP2, pointToCheck); + + return ab_x_p.z == bc_x_p.z && ab_x_p.z == ca_x_p.z; + } + + // https://gamedev.stackexchange.com/questions/71328/how-can-i-add-and-subtract-convex-polygons + public static bool IsPointInsideCircumcircle(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 pointToCheck) + { + // This first part will simplify how we calculate the determinant + float a = p0.x - pointToCheck.x; + float d = p1.x - pointToCheck.x; + float g = p2.x - pointToCheck.x; + + float b = p0.y - pointToCheck.y; + float e = p1.y - pointToCheck.y; + float h = p2.y - pointToCheck.y; + + float c = a * a + b * b; + float f = d * d + e * e; + float i = g * g + h * h; + + float determinant = (a * e * i) + (b * f * g) + (c * d * h) - (g * e * c) - (h * f * a) - (i * d * b); + + return determinant >= 0; // zero means on the perimeter + } + + /// + /// Calculates whether 2 line segments intersect and the intersection point. + /// + /// The first point of the first segment. + /// The second point of the first segment. + /// The first point of the second segment. + /// The second point of the second segment. + /// The intersection point, if any. + /// True if the line segment intersect; False otherwise. + public static bool IntersectionBetweenLines(Vector2 endpointA1, Vector2 endpointB1, Vector2 endpointA2, Vector2 endpointB2, out Vector2 intersectionPoint) + { + // https://stackoverflow.com/questions/4543506/algorithm-for-intersection-of-2-lines + + intersectionPoint = new Vector2(float.MaxValue, float.MaxValue); + + bool isLine1Vertical = endpointB1.x == endpointA1.x; + bool isLine2Vertical = endpointB2.x == endpointA2.x; + + float x = float.MaxValue; + float y = float.MaxValue; + + if (isLine1Vertical && !isLine2Vertical) + { + // First it calculates the standard form (Ax + By = C) + float m2 = (endpointB2.y - endpointA2.y) / (endpointB2.x - endpointA2.x); + + float A2 = m2; + float C2 = endpointA2.x * m2 - endpointA2.y; + + x = endpointA1.x; + y = m2 * endpointA1.x - C2; + } + else if (isLine2Vertical && !isLine1Vertical) + { + // First it calculates the standard form (Ax + By = C) + float m1 = (endpointB1.y - endpointA1.y) / (endpointB1.x - endpointA1.x); + + float A1 = m1; + float C1 = endpointA1.x * m1 - endpointA1.y; + + x = endpointA2.x; + y = m1 * endpointA2.x - C1; + } + else if (!isLine1Vertical && !isLine2Vertical) + { + // First it calculates the standard form of both lines (Ax + By = C) + float m1 = (endpointB1.y - endpointA1.y) / (endpointB1.x - endpointA1.x); + + float A1 = m1; + float B1 = -1.0f; + float C1 = endpointA1.x * m1 - endpointA1.y; + + float m2 = (endpointB2.y - endpointA2.y) / (endpointB2.x - endpointA2.x); + + float A2 = m2; + float B2 = -1.0f; + float C2 = endpointA2.x * m2 - endpointA2.y; + + float determinant = A1 * B2 - A2 * B1; + + if (determinant == 0) + { + // Lines do not intersect + return false; + } + + x = (B2 * C1 - B1 * C2) / determinant; + y = (A1 * C2 - A2 * C1) / determinant; + } + // else : no intersection + + bool result = false; + + //Debug.DrawLine(endpointA1, new Vector2(x, y), Color.yellow, 10.0f); + //Debug.DrawLine(endpointA2, new Vector2(x, y), Color.yellow, 10.0f); + + // Checks whether the point is in the segment determined by the endpoints of both lines + if (x <= Mathf.Max(endpointA1.x, endpointB1.x) && x >= Mathf.Min(endpointA1.x, endpointB1.x) && + y <= Mathf.Max(endpointA1.y, endpointB1.y) && y >= Mathf.Min(endpointA1.y, endpointB1.y) && + x <= Mathf.Max(endpointA2.x, endpointB2.x) && x >= Mathf.Min(endpointA2.x, endpointB2.x) && + y <= Mathf.Max(endpointA2.y, endpointB2.y) && y >= Mathf.Min(endpointA2.y, endpointB2.y)) + { + intersectionPoint.x = x; + intersectionPoint.y = y; + result = true; + } + + return result; + } + + public static bool IsTriangleVerticesCW(Vector2 point0, Vector2 point1, Vector2 point2) + { + return CalculateMatrix3x3Determinant(point0.x, point0.y, 1.0f, + point1.x, point1.y, 1.0f, + point2.x, point2.y, 1.0f) < 0.0f; + } + + //Is a quadrilateral convex? Assume no 3 points are colinear and the shape doesnt look like an hourglass + public static bool IsQuadrilateralConvex(Vector2 a, Vector2 b, Vector2 c, Vector2 d) + { + bool isConvex = false; + + bool abc = IsTriangleVerticesCW(a, b, c); + bool abd = IsTriangleVerticesCW(a, b, d); + bool bcd = IsTriangleVerticesCW(b, c, d); + bool cad = IsTriangleVerticesCW(c, a, d); + + if (abc && abd && bcd & !cad) + { + isConvex = true; + } + else if (abc && abd && !bcd & cad) + { + isConvex = true; + } + else if (abc && !abd && bcd & cad) + { + isConvex = true; + } + //The opposite sign, which makes everything inverted + else if (!abc && !abd && !bcd & cad) + { + isConvex = true; + } + else if (!abc && !abd && bcd & !cad) + { + isConvex = true; + } + else if (!abc && abd && !bcd & !cad) + { + isConvex = true; + } + + + return isConvex; + } + + /// + /// Calcualtes the area of a triangle, according to its 3 vertices. + /// + /// + /// It does not matter whether the vertices are sorted counter-clockwise. + /// + /// The first vertex. + /// The second vertex. + /// The third vertex. + /// The area of the triangle. + public static float CalculateTriangleArea(Vector2 p0, Vector2 p1, Vector2 p2) + { + return Vector3.Cross(p1 - p0, p2 - p0).magnitude * 0.5f; + } + } +} + diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/MathUtils.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/MathUtils.cs.meta new file mode 100644 index 00000000..7b94492a --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/MathUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f26eee00ac82bb440afde5247acdf530 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/PointBinGrid.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/PointBinGrid.cs new file mode 100644 index 00000000..c862260d --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/PointBinGrid.cs @@ -0,0 +1,107 @@ +// Copyright 2021 Alejandro Villalba Avila +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +using System.Collections.Generic; +using UnityEngine; + +namespace Game.Utils.Triangulation +{ + /// + /// A data structure that sorts a list of points by their proximity. It is a grid that divides the 2D space in NxN cells, + /// each of them acting as a "bin" that contains points. + /// + public class PointBinGrid + { + /// + /// The cells of the grid. Each of them may or may not store a bin with points. + /// + public List[] Cells; + + // The size of a cell in 2D space. + private Vector2 m_cellSize; + + // The size of the grid in the 2D space. + private Vector2 m_gridSize; // Xmax, Ymax + + // The amount of cells per side. + private int m_cellsPerSide; // n + + /// + /// Constructore that receives the amount of cells per side of the grid, and the size of the grid in the 2D space. + /// + /// The amount of cells per side of the grid. + /// The size of the grid in the 2D space. + public PointBinGrid(int cellsPerSide, Vector2 gridSize) + { + Cells = new List[cellsPerSide * cellsPerSide]; + m_cellSize = gridSize / cellsPerSide; + m_gridSize = gridSize; + m_cellsPerSide = cellsPerSide; + } + + /// + /// Adds a point to a bin of the grid, according to its position in 2D space. + /// + /// The point to add. + public void AddPoint(Vector2 newPoint) + { + int rowIndex = (int)(0.99f * m_cellsPerSide * newPoint.y / m_gridSize.y); // i + int columnIndex = (int)(0.99f * m_cellsPerSide * newPoint.x / m_gridSize.x); // j + + int binIndex = 0; // b + + if (rowIndex % 2 == 0) + { + binIndex = rowIndex * m_cellsPerSide + columnIndex + 1; + } + else + { + binIndex = (rowIndex + 1) * m_cellsPerSide - columnIndex; + } + + binIndex--; // zero-based index + + if (Cells[binIndex] == null) + { + Cells[binIndex] = new List(); + } + + Cells[binIndex].Add(newPoint); + + //DrawPointAddition(newPoint, columnIndex, rowIndex); + } + + public void DrawGrid(Color color, float duration) + { + for (int i = 0; i < m_cellsPerSide; ++i) + { + Debug.DrawRay(new Vector3(0.0f, i * m_cellSize.y, 1.0f), Vector2.right * m_gridSize.x, color, duration); + + for (int j = 0; j < m_cellsPerSide; ++j) + { + Debug.DrawRay(new Vector3(j * m_cellSize.x, 0.0f, 1.0f), Vector2.up * m_gridSize.y, color, duration); + } + } + + Debug.DrawRay(new Vector3(0.0f, m_cellsPerSide * m_cellSize.y, 1.0f), Vector2.right * m_gridSize.x, color, duration); + Debug.DrawRay(new Vector3(m_cellsPerSide * m_cellSize.x, 0.0f, 1.0f), Vector2.up * m_gridSize.y, color, duration); + } + + protected void DrawPointAddition(Vector2 point, int columnIndex, int rowIndex) + { + Vector2 cellBottomLeftCorner = new Vector2(columnIndex * m_cellSize.x, rowIndex * m_cellSize.y); + Debug.DrawLine(point, cellBottomLeftCorner + m_cellSize * 0.5f, Color.cyan, 5.0f); + } + } +} + diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/PointBinGrid.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/PointBinGrid.cs.meta new file mode 100644 index 00000000..c92813f2 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/PointBinGrid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ab024f34c6d9124a8635551b6cbfa14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/Triangle2D.cs b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/Triangle2D.cs new file mode 100644 index 00000000..22f01011 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/Triangle2D.cs @@ -0,0 +1,68 @@ +// Copyright 2021 Alejandro Villalba Avila +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +using UnityEngine; + +namespace Game.Utils.Math +{ + /// + /// A 2D triangle. + /// + public struct Triangle2D + { + /// + /// The first vertex. + /// + public Vector2 p0; + + /// + /// The second vertex. + /// + public Vector2 p1; + + /// + /// The third vertex. + /// + public Vector2 p2; + + /// + /// Constructor that receives the 3 vertices. + /// + /// The first vertex. + /// The second vertex. + /// The third vertex. + public Triangle2D(Vector2 point0, Vector2 point1, Vector2 point2) + { + p0 = point0; + p1 = point1; + p2 = point2; + } + + /// + /// Gets a vertex by its index. + /// + /// The index of the vertex, from 0 to 2. + /// The vertex. + public Vector2 this[int index] + { + get + { + Debug.Assert(index >= 0 && index < 4, "The index of the triangle vertex must be in the range [0, 2]."); + + return index == 0 ? p0 : index == 1 ? p1 : p2; + } + } + + } +} + diff --git a/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/Triangle2D.cs.meta b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/Triangle2D.cs.meta new file mode 100644 index 00000000..232b9c9f --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/ConstrainedDelaunayTriangulation/Triangle2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bb919dd848aef74a9141fab2393201b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/MeshExtrusion.cs b/Assets/Scripts/Tools/Mesh/MeshExtrusion.cs new file mode 100644 index 00000000..60804a16 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/MeshExtrusion.cs @@ -0,0 +1,283 @@ +using UnityEngine; +using System.Collections; + +public class MeshExtrusion +{ + /* + * An algorithm to extrude an arbitrary mesh along a number of sections. + * The mesh extrusion has 2 steps: + * 1. Extracting an edge representation from an arbitrary mesh + * - A general edge extraction algorithm is employed. + * (Same algorithm as traditionally used for stencil shadows) + * Once all unique edges are found, all edges that connect to only one triangle are extracted. + * Thus we end up with the outline of the mesh. + * 2. extruding the mesh from the edge representation. + * We simply generate a segments joining the edges + */ + + public class Edge + { + // The indiex to each vertex + public int[] vertexIndex = new int[2]; + // The index into the face. + // (faceindex[0] == faceindex[1] means the edge connects to only one triangle) + public int[] faceIndex = new int[2]; + } + + public static void ExtrudeMesh(Mesh srcMesh, Mesh extrudedMesh, Matrix4x4[] extrusion, bool invertFaces) + { + Edge[] edges = BuildManifoldEdges(srcMesh); + ExtrudeMesh(srcMesh, extrudedMesh, extrusion, edges, invertFaces); + } + + public static void ExtrudeMesh(Mesh srcMesh, Mesh extrudedMesh, Matrix4x4[] extrusion, Edge[] edges, bool invertFaces) + { + int extrudedVertexCount = edges.Length * 2 * extrusion.Length; + int triIndicesPerStep = edges.Length * 6; + int extrudedTriIndexCount = triIndicesPerStep * (extrusion.Length - 1); + + Vector3[] inputVertices = srcMesh.vertices; + Vector2[] inputUV = srcMesh.uv; + int[] inputTriangles = srcMesh.triangles; + + Vector3[] vertices = new Vector3[extrudedVertexCount + srcMesh.vertexCount * 2]; + Vector2[] uvs = new Vector2[vertices.Length]; + int[] triangles = new int[extrudedTriIndexCount + inputTriangles.Length * 2]; + + // Build extruded vertices + int v = 0; + for (int i = 0; i < extrusion.Length; i++) + { + Matrix4x4 matrix = extrusion[i]; + float vcoord = (float)i / (extrusion.Length - 1); + foreach (Edge e in edges) + { + vertices[v + 0] = matrix.MultiplyPoint(inputVertices[e.vertexIndex[0]]); + vertices[v + 1] = matrix.MultiplyPoint(inputVertices[e.vertexIndex[1]]); + + uvs[v + 0] = new Vector2(inputUV[e.vertexIndex[0]].x, vcoord); + uvs[v + 1] = new Vector2(inputUV[e.vertexIndex[1]].x, vcoord); + + v += 2; + } + } + + // Build cap vertices + // * The bottom mesh we scale along it's negative extrusion direction. This way extruding a half sphere results in a capsule. + for (int c = 0; c < 2; c++) + { + Matrix4x4 matrix = extrusion[c == 0 ? 0 : extrusion.Length - 1]; + int firstCapVertex = c == 0 ? extrudedVertexCount : extrudedVertexCount + inputVertices.Length; + for (int i = 0; i < inputVertices.Length; i++) + { + vertices[firstCapVertex + i] = matrix.MultiplyPoint(inputVertices[i]); + uvs[firstCapVertex + i] = inputUV[i]; + } + } + + // Build extruded triangles + for (int i = 0; i < extrusion.Length - 1; i++) + { + int baseVertexIndex = (edges.Length * 2) * i; + int nextVertexIndex = (edges.Length * 2) * (i + 1); + for (int e = 0; e < edges.Length; e++) + { + int triIndex = i * triIndicesPerStep + e * 6; + + triangles[triIndex + 0] = baseVertexIndex + e * 2; + triangles[triIndex + 1] = nextVertexIndex + e * 2; + triangles[triIndex + 2] = baseVertexIndex + e * 2 + 1; + triangles[triIndex + 3] = nextVertexIndex + e * 2; + triangles[triIndex + 4] = nextVertexIndex + e * 2 + 1; + triangles[triIndex + 5] = baseVertexIndex + e * 2 + 1; + } + } + + // build cap triangles + int triCount = inputTriangles.Length / 3; + // Top + { + int firstCapVertex = extrudedVertexCount; + int firstCapTriIndex = extrudedTriIndexCount; + for (int i = 0; i < triCount; i++) + { + triangles[i * 3 + firstCapTriIndex + 0] = inputTriangles[i * 3 + 1] + firstCapVertex; + triangles[i * 3 + firstCapTriIndex + 1] = inputTriangles[i * 3 + 2] + firstCapVertex; + triangles[i * 3 + firstCapTriIndex + 2] = inputTriangles[i * 3 + 0] + firstCapVertex; + } + } + + // Bottom + { + int firstCapVertex = extrudedVertexCount + inputVertices.Length; + int firstCapTriIndex = extrudedTriIndexCount + inputTriangles.Length; + for (int i = 0; i < triCount; i++) + { + triangles[i * 3 + firstCapTriIndex + 0] = inputTriangles[i * 3 + 0] + firstCapVertex; + triangles[i * 3 + firstCapTriIndex + 1] = inputTriangles[i * 3 + 2] + firstCapVertex; + triangles[i * 3 + firstCapTriIndex + 2] = inputTriangles[i * 3 + 1] + firstCapVertex; + } + } + + if (invertFaces) + { + for (int i = 0; i < triangles.Length / 3; i++) + { + int temp = triangles[i * 3 + 0]; + triangles[i * 3 + 0] = triangles[i * 3 + 1]; + triangles[i * 3 + 1] = temp; + } + } + + extrudedMesh.Clear(); + extrudedMesh.name = "extruded"; + extrudedMesh.vertices = vertices; + extrudedMesh.uv = uvs; + extrudedMesh.triangles = triangles; + extrudedMesh.RecalculateNormals(); + } + + /// Builds an array of edges that connect to only one triangle. + /// In other words, the outline of the mesh + public static Edge[] BuildManifoldEdges(Mesh mesh) + { + // Build a edge list for all unique edges in the mesh + Edge[] edges = BuildEdges(mesh.vertexCount, mesh.triangles); + + // We only want edges that connect to a single triangle + ArrayList culledEdges = new ArrayList(); + foreach (Edge edge in edges) + { + if (edge.faceIndex[0] == edge.faceIndex[1]) + { + culledEdges.Add(edge); + } + } + + return culledEdges.ToArray(typeof(Edge)) as Edge[]; + } + + /// Builds an array of unique edges + /// This requires that your mesh has all vertices welded. However on import, Unity has to split + /// vertices at uv seams and normal seams. Thus for a mesh with seams in your mesh you + /// will get two edges adjoining one triangle. + /// Often this is not a problem but you can fix it by welding vertices + /// and passing in the triangle array of the welded vertices. + public static Edge[] BuildEdges(int vertexCount, int[] triangleArray) + { + int maxEdgeCount = triangleArray.Length; + int[] firstEdge = new int[vertexCount + maxEdgeCount]; + int nextEdge = vertexCount; + int triangleCount = triangleArray.Length / 3; + + for (int a = 0; a < vertexCount; a++) + firstEdge[a] = -1; + + // First pass over all triangles. This finds all the edges satisfying the + // condition that the first vertex index is less than the second vertex index + // when the direction from the first vertex to the second vertex represents + // a counterclockwise winding around the triangle to which the edge belongs. + // For each edge found, the edge index is stored in a linked list of edges + // belonging to the lower-numbered vertex index i. This allows us to quickly + // find an edge in the second pass whose higher-numbered vertex index is i. + Edge[] edgeArray = new Edge[maxEdgeCount]; + + int edgeCount = 0; + for (int a = 0; a < triangleCount; a++) + { + int i1 = triangleArray[a * 3 + 2]; + for (int b = 0; b < 3; b++) + { + int i2 = triangleArray[a * 3 + b]; + if (i1 < i2) + { + Edge newEdge = new Edge(); + newEdge.vertexIndex[0] = i1; + newEdge.vertexIndex[1] = i2; + newEdge.faceIndex[0] = a; + newEdge.faceIndex[1] = a; + edgeArray[edgeCount] = newEdge; + + int edgeIndex = firstEdge[i1]; + if (edgeIndex == -1) + { + firstEdge[i1] = edgeCount; + } + else + { + while (true) + { + int index = firstEdge[nextEdge + edgeIndex]; + if (index == -1) + { + firstEdge[nextEdge + edgeIndex] = edgeCount; + break; + } + + edgeIndex = index; + } + } + + firstEdge[nextEdge + edgeCount] = -1; + edgeCount++; + } + + i1 = i2; + } + } + + // Second pass over all triangles. This finds all the edges satisfying the + // condition that the first vertex index is greater than the second vertex index + // when the direction from the first vertex to the second vertex represents + // a counterclockwise winding around the triangle to which the edge belongs. + // For each of these edges, the same edge should have already been found in + // the first pass for a different triangle. Of course we might have edges with only one triangle + // in that case we just add the edge here + // So we search the list of edges + // for the higher-numbered vertex index for the matching edge and fill in the + // second triangle index. The maximum number of comparisons in this search for + // any vertex is the number of edges having that vertex as an endpoint. + + for (int a = 0; a < triangleCount; a++) + { + int i1 = triangleArray[a * 3 + 2]; + for (int b = 0; b < 3; b++) + { + int i2 = triangleArray[a * 3 + b]; + if (i1 > i2) + { + bool foundEdge = false; + for (int edgeIndex = firstEdge[i2]; edgeIndex != -1; edgeIndex = firstEdge[nextEdge + edgeIndex]) + { + Edge edge = edgeArray[edgeIndex]; + if ((edge.vertexIndex[1] == i1) && (edge.faceIndex[0] == edge.faceIndex[1])) + { + edgeArray[edgeIndex].faceIndex[1] = a; + foundEdge = true; + break; + } + } + + if (!foundEdge) + { + Edge newEdge = new Edge(); + newEdge.vertexIndex[0] = i1; + newEdge.vertexIndex[1] = i2; + newEdge.faceIndex[0] = a; + newEdge.faceIndex[1] = a; + edgeArray[edgeCount] = newEdge; + edgeCount++; + } + } + + i1 = i2; + } + } + + Edge[] compactedEdges = new Edge[edgeCount]; + for (int e = 0; e < edgeCount; e++) + compactedEdges[e] = edgeArray[e]; + + return compactedEdges; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Tools/Mesh/MeshExtrusion.cs.meta b/Assets/Scripts/Tools/Mesh/MeshExtrusion.cs.meta new file mode 100644 index 00000000..fbf0fd67 --- /dev/null +++ b/Assets/Scripts/Tools/Mesh/MeshExtrusion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 19b0d15f298a38c30845da3fdbec9a38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/Mesh/ProceduralHeightmap.cs b/Assets/Scripts/Tools/Mesh/ProceduralHeightmap.cs index 7a098653..cab64ddb 100644 --- a/Assets/Scripts/Tools/Mesh/ProceduralHeightmap.cs +++ b/Assets/Scripts/Tools/Mesh/ProceduralHeightmap.cs @@ -49,14 +49,21 @@ private static void UpdateHeightMap(TerrainData terrainData, in Texture2D height private static Texture2D GenerateTexture(in string uri) { // Debug.Log(uri); - var img = System.Drawing.Image.FromFile(uri); var texture = new Texture2D(1, 1); + try + { + var img = System.Drawing.Image.FromFile(uri); - // TODO: Support GeoTIFF - using (var ms = new MemoryStream()) + // TODO: Support GeoTIFF + using (var ms = new MemoryStream()) + { + img.Save(ms, img.RawFormat); + ImageConversion.LoadImage(texture, ms.ToArray(), false); + } + } + catch (Exception e) { - img.Save(ms, img.RawFormat); - ImageConversion.LoadImage(texture, ms.ToArray(), false); + Debug.LogWarning(e.Message); } return texture; diff --git a/Assets/Scripts/Tools/Mesh/ProceduralMesh.cs b/Assets/Scripts/Tools/Mesh/ProceduralMesh.cs index ca4e5797..4526c3a4 100644 --- a/Assets/Scripts/Tools/Mesh/ProceduralMesh.cs +++ b/Assets/Scripts/Tools/Mesh/ProceduralMesh.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using UnityEngine; +using Game.Utils.Math; +using Game.Utils.Triangulation; /// https://wiki.unity3d.com/index.php/ProceduralPrimitives public class ProceduralMesh @@ -689,4 +691,107 @@ public static Mesh CreateCapsule(in float radius = 0.5f, in float length = 2f, i return mesh; } + + public static Mesh CreatePolylines(in SDF.Polylines polylines) + { + // Enables tesselation (before calculating constrained edges) when greater than zero. + // It subdivides the triangles until each of them has an area smaller than this value. + const float TesselationMaximumTriangleArea = 0.0f; + + // distance tolerance between 2 points. + // This is used when creating a list of distinct points in the polylines. + const float tolerance = 1e-4f; + + var height = polylines.Count == 0 ? 1f : (float)polylines[0].height; + var halfHeight = height * 0.5f; + + // close all the loops + foreach (var polyline in polylines) + { + // does the poly ends with the first point? + var first = polyline.point[0]; + var last = polyline.point[polyline.point.Count - 1]; + var distance = (first.X - last.X) * (first.X - last.X) + (first.Y - last.Y) * (first.Y - last.Y); + + // within range + if (distance > tolerance * tolerance) + { + polyline.point.Add(first); + Debug.LogWarning("add the first point at the ends"); + } + } + + var pointsToTriangulate = new List(); + if (polylines.Count > 0) + { + foreach (var point in polylines[0].point) + { + pointsToTriangulate.Add(new Vector2((float)point.Y, (float)point.X)); + } + } + + List> constrainedEdgePoints = null; + if (polylines.Count > 1) + { + constrainedEdgePoints = new List>(); + + for (int i = 1; i < polylines.Count; i++) + { + var points = new List(); + foreach (var point in polylines[i].point) + { + points.Add(new Vector2((float)point.Y, (float)point.X)); + } + constrainedEdgePoints.Add(points); + } + } + + var outputTriangles = new List(); + var triangulator = new DelaunayTriangulation(); + triangulator.Triangulate(pointsToTriangulate, TesselationMaximumTriangleArea, constrainedEdgePoints); + triangulator.GetTrianglesDiscardingHoles(outputTriangles); + + var planeMesh = CreateMeshFromTriangles(outputTriangles); + + var extrudedMesh = new Mesh(); + extrudedMesh.name = "Polyline"; + + var extrusion = new Matrix4x4[2] + { + Matrix4x4.TRS(Vector3.forward * -halfHeight, Quaternion.identity, Vector3.one), + Matrix4x4.TRS(Vector3.forward * halfHeight, Quaternion.identity, Vector3.one) + }; + MeshExtrusion.ExtrudeMesh(planeMesh, extrudedMesh, extrusion, false); + + return extrudedMesh; + } + + private static Mesh CreateMeshFromTriangles(IReadOnlyList triangles) + { + var vertices = new List(triangles.Count * 3); + var indices = new List(triangles.Count * 3); + + for (var i = 0; i < triangles.Count; ++i) + { + vertices.Add(triangles[i].p0); + vertices.Add(triangles[i].p1); + vertices.Add(triangles[i].p2); + indices.Add(i * 3 + 2); // Changes order + indices.Add(i * 3 + 1); + indices.Add(i * 3); + } + + var uvs = new Vector2[vertices.Count]; + for (int i = 0; i < uvs.Length; i++) + { + uvs[i] = new Vector2(vertices[i].x, vertices[i].y); + } + + var mesh = new Mesh(); + mesh.subMeshCount = 1; + mesh.SetVertices(vertices); + mesh.SetIndices(indices, MeshTopology.Triangles, 0); + mesh.SetUVs(0, uvs); + return mesh; + } } \ No newline at end of file diff --git a/Assets/Scripts/Tools/SDF/Implement/Implement.Collision.cs b/Assets/Scripts/Tools/SDF/Implement/Implement.Collision.cs index b5fb6341..2017c0e6 100644 --- a/Assets/Scripts/Tools/SDF/Implement/Implement.Collision.cs +++ b/Assets/Scripts/Tools/SDF/Implement/Implement.Collision.cs @@ -16,7 +16,7 @@ public class Collision { public static readonly int PlaneLayerIndex = UE.LayerMask.NameToLayer("Plane"); - private static readonly bool UseVHACD = true; // Expreimental parameters + private static readonly bool UseVHACD = true; // Experimental parameters private static readonly float ThresholdFrictionCombineMultiply = 0.01f; private static readonly float DynamicFrictionRatio = 0.95f; @@ -41,7 +41,6 @@ private static void KeepUnmergedMeshes(in UE.MeshFilter[] meshFilters) } } -#if ENABLE_MERGE_COLLIDER private static void MergeCollider(in UE.GameObject targetObject) { var geometryWorldToLocalMatrix = targetObject.transform.worldToLocalMatrix; @@ -63,9 +62,8 @@ private static void MergeCollider(in UE.GameObject targetObject) mergedMeshCollider.sharedMesh = mergedMesh; mergedMeshCollider.convex = false; mergedMeshCollider.cookingOptions = CookingOptions; - mergedMeshCollider.hideFlags |= UE.HideFlags.NotEditable; + // mergedMeshCollider.hideFlags |= UE.HideFlags.NotEditable; } -#endif public static void Make(UE.GameObject targetObject) { @@ -92,15 +90,20 @@ public static void Make(UE.GameObject targetObject) #endif } - foreach (var meshFilter in meshFilters) + RemoveRenderers(meshFilters); + } + } + + private static void RemoveRenderers(UE.MeshFilter[] meshFilters) + { + foreach (var meshFilter in meshFilters) + { + var meshRenderer = meshFilter.GetComponent(); + if (meshRenderer != null) { - var meshRenderer = meshFilter.GetComponent(); - if (meshRenderer != null) - { - UE.GameObject.Destroy(meshRenderer); - } - UE.GameObject.Destroy(meshFilter); + UE.GameObject.Destroy(meshRenderer); } + UE.GameObject.Destroy(meshFilter); } } diff --git a/Assets/Scripts/Tools/SDF/Implement/Implement.Geometry.cs b/Assets/Scripts/Tools/SDF/Implement/Implement.Geometry.cs index e4983802..c6d47a5d 100644 --- a/Assets/Scripts/Tools/SDF/Implement/Implement.Geometry.cs +++ b/Assets/Scripts/Tools/SDF/Implement/Implement.Geometry.cs @@ -6,19 +6,7 @@ using UE = UnityEngine; using Debug = UnityEngine.Debug; - -// using UnityEngine; -// using System; -// using UnityEngine.Rendering; -// using UENet = UnityEngine.Networking; -// using UnityEngine; -// using UnityEngine.Networking; -// using System.Collections; - -// using UnityEngine; -// using System.Collections; -// using UnityEngine.Networking; - +using System; namespace SDF { @@ -76,26 +64,16 @@ public static void GenerateMeshObject(in SDF.ShapeType shape, in UE.GameObject t var box = shape as SDF.Box; var scale = SDF2Unity.Scale(box.size); mesh = ProceduralMesh.CreateBox(scale.x, scale.y, scale.z); - - var boxCollider = createdObject.AddComponent(); - boxCollider.size = scale; } else if (shape is SDF.Sphere) { var sphere = shape as SDF.Sphere; mesh = ProceduralMesh.CreateSphere((float)sphere.radius); - - var sphereCollider = createdObject.AddComponent(); - sphereCollider.radius = (float)sphere.radius; } else if (shape is SDF.Capsule) { var capsule = shape as SDF.Capsule; mesh = ProceduralMesh.CreateCapsule((float)capsule.radius, (float)capsule.length); - - var capsuleCollider = createdObject.AddComponent(); - capsuleCollider.radius = (float)capsule.radius; - capsuleCollider.height = (float)capsule.length; } else if (shape is SDF.Cylinder) { @@ -108,6 +86,10 @@ public static void GenerateMeshObject(in SDF.ShapeType shape, in UE.GameObject t var normal = SDF2Unity.Normal(plane.normal); mesh = ProceduralMesh.CreatePlane((float)plane.size.X, (float)plane.size.Y, normal); } + else if (shape is SDF.Polylines) + { + mesh = ProceduralMesh.CreatePolylines(shape as SDF.Polylines); + } else { Debug.Log("Wrong ShapeType!!!"); diff --git a/Assets/Scripts/Tools/SDF/Import/Import.Collision.cs b/Assets/Scripts/Tools/SDF/Import/Import.Collision.cs index 81d09862..5de7025d 100644 --- a/Assets/Scripts/Tools/SDF/Import/Import.Collision.cs +++ b/Assets/Scripts/Tools/SDF/Import/Import.Collision.cs @@ -43,11 +43,26 @@ protected override void AfterImportCollision(in SDF.Collision collision, in Syst var geometryObject = (collisionObject.transform.childCount == 0) ? collisionObject : collisionObject.transform.GetChild(0).gameObject; Implement.Collision.Make(geometryObject); - if (collision.GetGeometry().GetShapeType().Equals(typeof(Plane))) + var shape = collision.GetGeometry().GetShape(); + var shapeType = shape.GetType(); + + var existingMeshCollider = geometryObject.GetComponent(); + + if (shapeType.Equals(typeof(Plane))) { collisionObject.layer = Implement.Collision.PlaneLayerIndex; - var collider = collisionObject.GetComponentInChildren(); - collider.convex = false; + existingMeshCollider.convex = false; + } + else + { + if (EnhanceCollisionPerformance(shapeType, shape, existingMeshCollider)) + { + var meshColliders = geometryObject.GetComponentsInChildren(); + for (var index = 0; index < meshColliders.Length; index++) + { + UE.GameObject.Destroy(meshColliders[index]); + } + } } #if UNITY_EDITOR @@ -62,6 +77,41 @@ protected override void AfterImportCollision(in SDF.Collision collision, in Syst Implement.Collision.SetSurfaceFriction(collision.GetSurface(), collisionObject); } + + private bool EnhanceCollisionPerformance( + in System.Type shapeType, + in ShapeType shape, + UE.MeshCollider meshCollider) + { + if (shapeType.Equals(typeof(Box))) + { + var box = shape as SDF.Box; + var scale = SDF2Unity.Scale(box.size); + + var boxCollider = meshCollider.gameObject.AddComponent(); + boxCollider.size = scale; + return true; + } + else if (shapeType.Equals(typeof(Sphere))) + { + var sphere = shape as SDF.Sphere; + + var sphereCollider = meshCollider.gameObject.AddComponent(); + sphereCollider.radius = (float)sphere.radius; + return true; + } + else if (shapeType.Equals(typeof(Capsule))) + { + var capsule = shape as SDF.Capsule; + + var capsuleCollider = meshCollider.gameObject.AddComponent(); + capsuleCollider.radius = (float)capsule.radius; + capsuleCollider.height = (float)capsule.length; + return true; + } + + return false; + } } } } \ No newline at end of file diff --git a/Assets/Scripts/Tools/SDF/Import/Import.Geometry.cs b/Assets/Scripts/Tools/SDF/Import/Import.Geometry.cs index 07e6c7f2..1e54be7d 100644 --- a/Assets/Scripts/Tools/SDF/Import/Import.Geometry.cs +++ b/Assets/Scripts/Tools/SDF/Import/Import.Geometry.cs @@ -20,8 +20,8 @@ protected override void ImportGeometry(in SDF.Geometry geometry, in System.Objec } var targetObject = (parentObject as UE.GameObject); - var t = geometry.GetShapeType(); var shape = geometry.GetShape(); + var t = shape.GetType(); if (t != null && t.Equals(typeof(SDF.Mesh))) { diff --git a/Assets/Scripts/Tools/SDF/Import/Import.Plugin.cs b/Assets/Scripts/Tools/SDF/Import/Import.Plugin.cs index 28bbdde3..79b64c57 100644 --- a/Assets/Scripts/Tools/SDF/Import/Import.Plugin.cs +++ b/Assets/Scripts/Tools/SDF/Import/Import.Plugin.cs @@ -34,6 +34,9 @@ protected override void ImportPlugin(in SDF.Plugin plugin, in System.Object pare return; } + // temporary deactivate for passing plugin parameters + targetObject.SetActive(false); + var pluginComponent = targetObject.AddComponent(pluginType); var pluginObject = pluginComponent as CLOiSimPlugin; @@ -53,6 +56,8 @@ protected override void ImportPlugin(in SDF.Plugin plugin, in System.Object pare { Debug.LogError($"[Plugin] failed to add : {plugin.Name}"); } + + targetObject.SetActive(true); } } } diff --git a/Assets/Scripts/Tools/SDF/Parser/Color.cs b/Assets/Scripts/Tools/SDF/Parser/Color.cs new file mode 100644 index 00000000..c72d3a1d --- /dev/null +++ b/Assets/Scripts/Tools/SDF/Parser/Color.cs @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 LG Electronics Inc. + * + * SPDX-License-Identifier: MIT + */ + +using System; + +namespace SDF +{ + public class Color + { + public double R = 0.0; + public double G = 0.0; + public double B = 0.0; + public double A = 1.0; + + public void FromString(string value) + { + if (string.IsNullOrEmpty(value)) + return; + + value = value.Trim(); + + var tmp = value.Split(' '); + + if (tmp.Length < 3) + return; + + R = (double)Convert.ChangeType(tmp[0], TypeCode.Double); + G = (double)Convert.ChangeType(tmp[1], TypeCode.Double); + B = (double)Convert.ChangeType(tmp[2], TypeCode.Double); + + if (tmp.Length != 4) + return; + + A = (double)Convert.ChangeType(tmp[3], TypeCode.Double); + } + + public Color() + : this(0.0, 0.0, 0.0, 1.0) + { } + + public Color(string value) + { + FromString(value); + } + + public Color(double r, double g, double b, double a) + { + R = r; + G = g; + B = b; + A = a; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Tools/SDF/Parser/Color.cs.meta b/Assets/Scripts/Tools/SDF/Parser/Color.cs.meta new file mode 100644 index 00000000..8ca197f9 --- /dev/null +++ b/Assets/Scripts/Tools/SDF/Parser/Color.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81c9c1f434aa4b828bd7de56ed95cc80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Tools/SDF/Parser/Geometry.cs b/Assets/Scripts/Tools/SDF/Parser/Geometry.cs index 47e4533e..58db2577 100644 --- a/Assets/Scripts/Tools/SDF/Parser/Geometry.cs +++ b/Assets/Scripts/Tools/SDF/Parser/Geometry.cs @@ -34,14 +34,12 @@ protected override void ParseElements() } else if (IsValidNode("box")) { - Type = "box"; shape = new Box(); var sizeStr = GetValue("box/size"); (shape as Box).size.FromString(sizeStr); } else if (IsValidNode("mesh")) { - Type = "mesh"; shape = new Mesh(); var mesh = (shape as Mesh); mesh.uri = GetValue("mesh/uri"); @@ -66,20 +64,17 @@ protected override void ParseElements() } else if (IsValidNode("sphere")) { - Type = "sphere"; shape = new Sphere(); (shape as Sphere).radius = GetValue("sphere/radius"); } else if (IsValidNode("cylinder")) { - Type = "cylinder"; shape = new Cylinder(); (shape as Cylinder).radius = GetValue("cylinder/radius"); (shape as Cylinder).length = GetValue("cylinder/length"); } else if (IsValidNode("plane")) { - Type = "plane"; shape = new Plane(); var normal = GetValue("plane/normal"); (shape as Plane).normal.FromString(normal); @@ -89,7 +84,6 @@ protected override void ParseElements() } else if (IsValidNode("image")) { - Type = "image"; shape = new Image(); (shape as Image).uri = GetValue("image/uri"); @@ -100,7 +94,6 @@ protected override void ParseElements() } else if (IsValidNode("heightmap")) { - Type = "heightmap"; shape = new Heightmap(); (shape as Heightmap).uri = GetValue("heightmap/uri"); @@ -184,14 +177,30 @@ protected override void ParseElements() } else if (IsValidNode("polyline")) { - Console.WriteLine("Currently not supported"); - empty = true; + shape = new Polylines(); + + var polylineList = GetNodes("polyline"); + foreach (XmlNode polylineNode in polylineList) + { + var polyline = new Polyline(polylineNode); + if (polyline.GetValues("point", out var pointList)) + { + foreach (var pointstr in pointList) + { + var point = new Vector2(pointstr); + polyline.point.Add(point); + } + } + + polyline.height = polyline.GetValue("height"); + + (shape as Polylines).Add(polyline); + } } #region SDF_1.7_feature else if (IsValidNode("capsule")) { - Type = "capsule"; shape = new Capsule(); (shape as Capsule).radius = GetValue("capsule/radius"); (shape as Capsule).length = GetValue("capsule/length"); @@ -201,7 +210,6 @@ protected override void ParseElements() #region SDF_1.8_feature else if (IsValidNode("ellipsoid")) { - Type = "ellipsoid"; shape = new Ellipsoid(); var radii = GetValue("ellipsoid/radii"); @@ -222,16 +230,17 @@ protected override void ParseElements() empty = true; Console.WriteLine("missing mesh type"); } + + if (shape != null) + { + this.Type = shape.Type(); + // Console.WriteLine(Type); + } } public ShapeType GetShape() { return shape; } - - public Type GetShapeType() - { - return shape?.GetType(); - } } } \ No newline at end of file diff --git a/Assets/Scripts/Tools/SDF/Parser/Material.cs b/Assets/Scripts/Tools/SDF/Parser/Material.cs index 635b406e..1fdd4f22 100644 --- a/Assets/Scripts/Tools/SDF/Parser/Material.cs +++ b/Assets/Scripts/Tools/SDF/Parser/Material.cs @@ -6,57 +6,9 @@ using System.Collections.Generic; using System.Xml; -using System; namespace SDF { - public class Color - { - public double R = 0.0; - public double G = 0.0; - public double B = 0.0; - public double A = 1.0; - - public void FromString(string value) - { - if (string.IsNullOrEmpty(value)) - return; - - value = value.Trim(); - - var tmp = value.Split(' '); - - if (tmp.Length < 3) - return; - - R = (double)Convert.ChangeType(tmp[0], TypeCode.Double); - G = (double)Convert.ChangeType(tmp[1], TypeCode.Double); - B = (double)Convert.ChangeType(tmp[2], TypeCode.Double); - - if (tmp.Length > 4) - return; - - A = (double)Convert.ChangeType(tmp[3], TypeCode.Double); - } - - public Color() - : this(0.0, 0.0, 0.0, 1.0) - { } - - public Color(string value) - { - FromString(value); - } - - public Color(double r, double g, double b, double a) - { - R = r; - G = g; - B = b; - A = a; - } - } - public class Material : Entity { public class Script diff --git a/Assets/Scripts/Tools/SDF/Parser/Root.cs b/Assets/Scripts/Tools/SDF/Parser/Root.cs index 8c9628e0..26af338e 100644 --- a/Assets/Scripts/Tools/SDF/Parser/Root.cs +++ b/Assets/Scripts/Tools/SDF/Parser/Root.cs @@ -259,11 +259,11 @@ public void UpdateResourceModelTable() Console.Write($"Total Models: {resourceModelTable.Count}"); } - // Converting media/file uri + // Converting media/file uri private void ConvertPathToAbsolutePath(in string targetElement) { var nodeList = _doc.SelectNodes($"//{targetElement}"); - // Console.Write("Target:" + targetElement + ", Num Of uri nodes: " + nodeList.Count); + Console.Write("Target:" + targetElement + ", Num Of uri nodes: " + nodeList.Count); foreach (XmlNode node in nodeList) { var uri = node.InnerText; diff --git a/Assets/Scripts/Tools/SDF/Parser/ShapeTypes.cs b/Assets/Scripts/Tools/SDF/Parser/ShapeTypes.cs index a3fb3ee1..55f4f385 100644 --- a/Assets/Scripts/Tools/SDF/Parser/ShapeTypes.cs +++ b/Assets/Scripts/Tools/SDF/Parser/ShapeTypes.cs @@ -5,16 +5,23 @@ */ using System.Collections.Generic; +using System.Xml; namespace SDF { public interface ShapeType { + public string Type(); } public class Box : ShapeType { public Vector3 size = new Vector3(1, 1, 1); + + public string Type() + { + return "box"; + } } public class Cylinder : ShapeType @@ -24,11 +31,21 @@ public class Cylinder : ShapeType // Description: Length of the cylinder along the z axis public double length = 1; + + public string Type() + { + return "cylinder"; + } } public class Ellipsoid : ShapeType { public Vector3 radii = new Vector3(); + + public string Type() + { + return "ellipsoid"; + } } // Description: A heightmap based on a 2d grayscale image. @@ -78,6 +95,11 @@ public class Blend // Description: Samples per heightmap datum. For rasterized heightmaps, this indicates the number of samples to take per pixel. Using a higher value, e.g. 2, will generally improve the quality of the heightmap but lower performance. public uint sampling = 1; + + public string Type() + { + return "heightmap"; + } } public class Image : ShapeType @@ -87,6 +109,11 @@ public class Image : ShapeType public int threshold = 200; public double height = 1d; public int granularity = 1; + + public string Type() + { + return "image"; + } } public class Mesh : ShapeType @@ -95,28 +122,61 @@ public class Mesh : ShapeType public string submesh_name = null; public bool submesh_center = false; public Vector3 scale = new Vector3(); + + public string Type() + { + return "mesh"; + } } public class Plane : ShapeType { public Vector3 normal = new Vector3(); public Vector2 size = new Vector2(); + + public string Type() + { + return "plane"; + } } - public class Polyline : ShapeType + public class Polyline : Entity { public List> point = new List>(); public double height = 1; + + public Polyline(XmlNode _node) + : base(_node) + { + } + } + + public class Polylines : List, ShapeType + { + public string Type() + { + return "polyline"; + } } public class Sphere : ShapeType { public double radius; + + public string Type() + { + return "sphere"; + } } public class Capsule : ShapeType { public double radius; public double length; + + public string Type() + { + return "capsule"; + } } } \ No newline at end of file diff --git a/Assets/Scripts/UI/RuntimeGizmo/TransformGizmo.cs b/Assets/Scripts/UI/RuntimeGizmo/TransformGizmo.cs index c1e8287a..7ed77598 100644 --- a/Assets/Scripts/UI/RuntimeGizmo/TransformGizmo.cs +++ b/Assets/Scripts/UI/RuntimeGizmo/TransformGizmo.cs @@ -516,9 +516,11 @@ float CalculateSnapAmount(in float snapValue, in float currentAmount, out float void GetTarget() { - if (nearAxis == Axis.None && !Input.GetKey(KeyCode.LeftControl) && Input.GetMouseButtonDown(0)) + if (nearAxis == Axis.None && + !Input.GetKey(KeyCode.LeftControl) && Input.GetMouseButtonDown(0)) { - if (Physics.Raycast(myCamera.ScreenPointToRay(Input.mousePosition), out var hitInfo, Mathf.Infinity)) + var ray = myCamera.ScreenPointToRay(Input.mousePosition); + if (Physics.Raycast(ray, out var hitInfo, Mathf.Infinity)) { Transform target = null; var hitObject = hitInfo.transform; @@ -526,6 +528,7 @@ void GetTarget() _lockRotation = false; if (hitObject.CompareTag("Props") || + hitObject.CompareTag("Actor") || (hitObject.CompareTag("Road") && Input.GetKey(KeyCode.LeftAlt))) { if (hitObject.CompareTag("Road")) @@ -537,41 +540,32 @@ void GetTarget() } else { - var hitParentActor = hitObject?.GetComponent(); - - if (hitParentActor != null && hitParentActor.CompareTag("Actor")) + // To avoid plane object + var isPlaneObject = hitObject.gameObject.layer.Equals(SDF.Implement.Collision.PlaneLayerIndex); + var collisionHelper = hitObject.GetComponentInChildren(); + if (collisionHelper != null) { - target = hitParentActor.transform; + isPlaneObject |= collisionHelper.gameObject.layer.Equals(SDF.Implement.Collision.PlaneLayerIndex); } - else + + if (!isPlaneObject) { - // To avoid plane object - var isPlaneObject = hitObject.gameObject.layer.Equals(SDF.Implement.Collision.PlaneLayerIndex); - var collisionHelper = hitObject.GetComponentInChildren(); - if (collisionHelper != null) - { - isPlaneObject |= collisionHelper.gameObject.layer.Equals(SDF.Implement.Collision.PlaneLayerIndex); - } + var hitParentLinkHelper = hitObject?.GetComponentInParent(); + var hitRootModelHelper = hitParentLinkHelper?.RootModel; - if (!isPlaneObject) + if (hitRootModelHelper != null && hitParentLinkHelper.Model != null) { - var hitParentLinkHelper = hitObject?.GetComponentInParent(); - var hitRootModelHelper = hitParentLinkHelper?.RootModel; - - if (hitRootModelHelper != null && hitParentLinkHelper.Model != null) + if (!(hitRootModelHelper.isStatic || hitParentLinkHelper.Model.isStatic)) { - if (!(hitRootModelHelper.isStatic || hitParentLinkHelper.Model.isStatic)) - { - target = (hitRootModelHelper.hasRootArticulationBody) ? hitRootModelHelper.transform : hitParentLinkHelper.Model.transform; - } - } - // Select static object(non articulation body) only when left alt is pressed - else if (hitParentLinkHelper == null && Input.GetKey(KeyCode.LeftAlt)) - { - target = hitObject.transform; + target = (hitRootModelHelper.hasRootArticulationBody) ? hitRootModelHelper.transform : hitParentLinkHelper.Model.transform; } - // Debug.Log(hitParentObject.name + " Selected!!!!"); } + // Select static object(non articulation body) only when left alt is pressed + else if (hitParentLinkHelper == null && Input.GetKey(KeyCode.LeftAlt)) + { + target = hitObject.transform; + } + // Debug.Log(hitParentObject.name + " Selected!!!!"); } } diff --git a/LICENSE-3RD-PARTY b/LICENSE-3RD-PARTY index f707169b..8d14cef0 100644 --- a/LICENSE-3RD-PARTY +++ b/LICENSE-3RD-PARTY @@ -62,6 +62,8 @@ Copyright (c) 2010-2020 sta.blockhead Newtonsoft.Json 12.0.3 (MIT) Copyright (c) 2007 James Newton-King +QThund/ConstrainedDelaunayTriangulation (MIT) +Copyright (c) 2021 Alejandro Villalba Avila Please refer to the oss-pkg-info.yaml file for the link to download the source code of each Open Source Software. _________________________________________________________________________________________________________________________ diff --git a/Packages/manifest.json b/Packages/manifest.json index a916857b..5dc6b8d4 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -7,10 +7,10 @@ "com.unity.render-pipelines.universal": "14.0.11", "com.unity.robotics.vhacd": "https://github.com/Unity-Technologies/VHACD.git?path=/com.unity.robotics.vhacd", "com.unity.searcher": "4.9.2", - "com.unity.splines": "2.5.2", + "com.unity.splines": "2.6.0", "com.unity.terrain-tools": "5.0.4", "com.unity.textmeshpro": "3.0.8", - "com.unity.toolchain.linux-x86_64": "2.0.6", + "com.unity.toolchain.linux-x86_64": "2.0.9", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 3a3a3a07..deb156f7 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -102,7 +102,7 @@ } }, "com.unity.splines": { - "version": "2.5.2", + "version": "2.6.0", "depth": 0, "source": "registry", "dependencies": { @@ -113,18 +113,18 @@ "url": "https://packages.unity.com" }, "com.unity.sysroot": { - "version": "2.0.7", + "version": "2.0.10", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.sysroot.linux-x86_64": { - "version": "2.0.6", + "version": "2.0.9", "depth": 1, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.7" + "com.unity.sysroot": "2.0.10" }, "url": "https://packages.unity.com" }, @@ -148,12 +148,12 @@ "url": "https://packages.unity.com" }, "com.unity.toolchain.linux-x86_64": { - "version": "2.0.6", + "version": "2.0.9", "depth": 0, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.7", - "com.unity.sysroot.linux-x86_64": "2.0.6" + "com.unity.sysroot": "2.0.10", + "com.unity.sysroot.linux-x86_64": "2.0.9" }, "url": "https://packages.unity.com" }, diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index 344fa31d..34a728fb 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -12,10 +12,11 @@ MonoBehaviour: m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: - m_EnablePreviewPackages: 0 - m_EnablePackageDependencies: 0 + m_EnablePreReleasePackages: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 oneTimeWarningShown: 0 m_Registries: - m_Id: main @@ -24,19 +25,12 @@ MonoBehaviour: m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 + m_ConfigSource: 0 m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: - m_ErrorMessage: - m_Original: - m_Id: - m_Name: - m_Url: - m_Scopes: [] - m_IsDefault: 0 - m_Capabilities: 0 m_Modified: 0 - m_Name: - m_Url: - m_Scopes: [] - m_SelectedScopeIndex: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -830 + m_OriginalInstanceId: -832 + m_LoadAssets: 0 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 4c84c7c0..2997f80c 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -139,7 +139,7 @@ PlayerSettings: loadStoreDebugModeEnabled: 0 visionOSBundleVersion: 1.0 tvOSBundleVersion: 1.0 - bundleVersion: 4.5.0 + bundleVersion: 4.5.1 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 @@ -233,6 +233,7 @@ PlayerSettings: iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 + metalCompileShaderBinary: 0 iOSRenderExtraFrameOnPause: 0 iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: @@ -626,7 +627,8 @@ PlayerSettings: webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 webGLPowerPreference: 2 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + Standalone: additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: @@ -637,7 +639,7 @@ PlayerSettings: Standalone: 0 incrementalIl2cppBuild: {} suppressCommonWarnings: 1 - allowUnsafeCode: 0 + allowUnsafeCode: 1 useDeterministicCompilation: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index e3269c93..9efb20c7 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.25f1 -m_EditorVersionWithRevision: 2022.3.25f1 (530ae0ba3889) +m_EditorVersion: 2022.3.27f1 +m_EditorVersionWithRevision: 2022.3.27f1 (73effa14754f)