示例#1
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="points">The points from which we want to build the convex hull</param>
        /// <param name="removeUnwantedTriangles">At the end of the algorithm, try to remove triangles from the hull that we dont want,
        //such as needles where one edge is much shorter than the other edges in the triangle</param>
        /// <param name="normalizer">Is only needed for debugging</param>
        /// <returns></returns>
        public static HalfEdgeData3 GenerateConvexHull(HashSet <Vector3> points, bool removeUnwantedTriangles, Normalizer3 normalizer = null)
        {
            HalfEdgeData3 convexHull = new HalfEdgeData3();

            //Step 1. Init by making a tetrahedron (triangular pyramid) and remove all points within the tetrahedron
            System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();

            //timer.Start();

            BuildFirstTetrahedron(points, convexHull);

            //timer.Stop();

            //Debug.Log($"Testrahedron {timer.ElapsedMilliseconds/1000f}");

            //Debug.Log(convexHull.faces.Count);

            //return convexHull;

            //Step 2. For each other point:
            // -If the point is within the hull constrcuted so far, remove it
            // - Otherwise, see which triangles are visible to the point and remove them
            //   Then build new triangles from the edges that have no neighbor to the point

            List <Vector3> pointsToAdd = new List <Vector3>(points);

            int removedPointsCounter = 0;

            //int debugCounter = 0;

            foreach (Vector3 p in pointsToAdd)
            {
                //Is this point within the tetrahedron
                bool isWithinHull = _Intersections.PointWithinConvexHull(p, convexHull);

                if (isWithinHull)
                {
                    points.Remove(p);

                    removedPointsCounter += 1;

                    continue;
                }


                //Find visible triangles and edges on the border between the visible and invisible triangles
                HashSet <HalfEdgeFace3> visibleTriangles = null;
                HashSet <HalfEdge3>     borderEdges      = null;

                FindVisibleTrianglesAndBorderEdgesFromPoint(p, convexHull, out visibleTriangles, out borderEdges);


                //Remove all visible triangles
                foreach (HalfEdgeFace3 triangle in visibleTriangles)
                {
                    convexHull.DeleteFace(triangle);
                }


                //Make new triangle by connecting all edges on the border with the point
                //Debug.Log($"Number of border edges: {borderEdges.Count}");
                //int debugStop = 11;

                //Save all ned edges so we can connect them with an opposite edge
                //To make it faster you can use the ideas in the Valve paper to get a sorted list of newEdges
                HashSet <HalfEdge3> newEdges = new HashSet <HalfEdge3>();

                foreach (HalfEdge3 borderEdge in borderEdges)
                {
                    //Each edge is point TO a vertex
                    Vector3 p1 = borderEdge.prevEdge.v.position;
                    Vector3 p2 = borderEdge.v.position;

                    /*
                     * if (debugCounter > debugStop)
                     * {
                     *  Debug.DrawLine(normalizer.UnNormalize(p1).ToVector3(), normalizer.UnNormalize(p2).ToVector3(), Color.white, 2f);
                     *
                     *  Debug.DrawLine(normalizer.UnNormalize(p1).ToVector3(), normalizer.UnNormalize(p).ToVector3(), Color.gray, 2f);
                     *  Debug.DrawLine(normalizer.UnNormalize(p2).ToVector3(), normalizer.UnNormalize(p).ToVector3(), Color.gray, 2f);
                     *
                     *  convexHull.AddTriangle(p2, p1, p);
                     * }
                     * else
                     * {
                     *  //Debug.Log(borderEdge.face);
                     *
                     *  convexHull.AddTriangle(p2, p1, p);
                     * }
                     */

                    //The border edge belongs to a triangle which is invisible
                    //Because triangles are oriented clockwise, we have to add the vertices in the other direction
                    //to build a new triangle with the point
                    HalfEdgeFace3 newTriangle = convexHull.AddTriangle(p2, p1, p);

                    //Connect the new triangle with the opposite edge on the border
                    //When we create the face we give it a reference edge which goes to p2
                    //So the edge we want to connect is the next edge
                    HalfEdge3 edgeToConnect = newTriangle.edge.nextEdge;

                    edgeToConnect.oppositeEdge = borderEdge;
                    borderEdge.oppositeEdge    = edgeToConnect;

                    //Two edges are still not connected, so save those
                    HalfEdge3 e1 = newTriangle.edge;
                    //HalfEdge3 e2 = newTriangle.edge.nextEdge;
                    HalfEdge3 e3 = newTriangle.edge.nextEdge.nextEdge;

                    newEdges.Add(e1);
                    //newEdges.Add(e2);
                    newEdges.Add(e3);
                }

                //timer.Start();
                //Two edges in each triangle are still not connected with an opposite edge
                foreach (HalfEdge3 e in newEdges)
                {
                    if (e.oppositeEdge != null)
                    {
                        continue;
                    }

                    convexHull.TryFindOppositeEdge(e, newEdges);
                }
                //timer.Stop();


                //Connect all new triangles and the triangles on the border,
                //so each edge has an opposite edge or flood filling will be impossible
                //timer.Start();
                //convexHull.ConnectAllEdges();
                //timer.Stop();


                //if (debugCounter > debugStop)
                //{
                //    break;
                //}

                //debugCounter += 1;
            }

            //Debug.Log($"Connect half-edges took {timer.ElapsedMilliseconds/1000f} seconds");

            Debug.Log($"Removed {removedPointsCounter} points during the construction of the hull because they were inside the hull");


            //
            // Clean up
            //

            //Merge concave edges according to the paper


            //Remove unwanted triangles, such as slivers and needles
            //Which is maybe not needed because when you add a Unity convex mesh collider to the result of this algorithm, there are still slivers
            //Unity's mesh collider is also using quads and not just triangles
            //But if you add enough points, so you end up with many points on the hull you can see that Unitys convex mesh collider is not capturing all points, so they must be using some simplification algorithm

            //Run the hull through the mesh simplification algorithm
            if (removeUnwantedTriangles)
            {
                convexHull = MeshSimplification_QEM.Simplify(convexHull, maxEdgesToContract: int.MaxValue, maxError: 0.0001f, normalizeTriangles: true);
            }


            return(convexHull);
        }
示例#2
0
        //Initialize by making 2 triangles by using three points, so its a flat triangle with a face on each side
        //We could use the ideas from Quickhull to make the start triangle as big as possible
        //Then find a point which is the furthest away as possible from these triangles
        //Add that point and you have a tetrahedron (triangular pyramid)
        public static void BuildFirstTetrahedron(HashSet <Vector3> points, HalfEdgeData3 convexHull)
        {
            //Of all points, find the two points that are furthes away from each other
            Edge3 eFurthestApart = FindEdgeFurthestApart(points);

            //Remove the two points we found
            points.Remove(eFurthestApart.p1);
            points.Remove(eFurthestApart.p2);


            //Find a point which is the furthest away from this edge
            //TODO: Is this point also on the AABB? So we don't have to search all remaining points...
            Vector3 pointFurthestAway = FindPointFurthestFromEdge(eFurthestApart, points);

            //Remove the point
            points.Remove(pointFurthestAway);


            //Display the triangle
            //Debug.DrawLine(eFurthestApart.p1.ToVector3(), eFurthestApart.p2.ToVector3(), Color.white, 1f);
            //Debug.DrawLine(eFurthestApart.p1.ToVector3(), pointFurthestAway.ToVector3(), Color.blue, 1f);
            //Debug.DrawLine(eFurthestApart.p2.ToVector3(), pointFurthestAway.ToVector3(), Color.blue, 1f);


            //Now we can build two triangles
            //It doesnt matter how we build these triangles as long as they are opposite
            //But the normal matters, so make sure it is calculated so the triangles are ordered clock-wise while the normal is pointing out
            Vector3 p1 = eFurthestApart.p1;
            Vector3 p2 = eFurthestApart.p2;
            Vector3 p3 = pointFurthestAway;

            convexHull.AddTriangle(p1, p2, p3);
            convexHull.AddTriangle(p1, p3, p2);

            //Debug.Log(convexHull.faces.Count);

            /*
             * foreach (HalfEdgeFace3 f in convexHull.faces)
             * {
             *  TestAlgorithmsHelpMethods.DebugDrawTriangle(f, Color.white, Color.red);
             * }
             */

            //Find the point which is furthest away from the triangle (this point cant be co-planar)
            List <HalfEdgeFace3> triangles = new List <HalfEdgeFace3>(convexHull.faces);

            //Just pick one of the triangles
            HalfEdgeFace3 triangle = triangles[0];

            //Build a plane
            Plane3 plane = new Plane3(triangle.edge.v.position, triangle.edge.v.normal);

            //Find the point furthest away from the plane
            Vector3 p4 = FindPointFurthestAwayFromPlane(points, plane);

            //Remove the point
            points.Remove(p4);

            //Debug.DrawLine(p1.ToVector3(), p4.ToVector3(), Color.green, 1f);
            //Debug.DrawLine(p2.ToVector3(), p4.ToVector3(), Color.green, 1f);
            //Debug.DrawLine(p3.ToVector3(), p4.ToVector3(), Color.green, 1f);

            //Now we have to remove one of the triangles == the triangle the point is outside of
            HalfEdgeFace3 triangleToRemove = triangles[0];
            HalfEdgeFace3 triangleToKeep   = triangles[1];

            //This means the point is inside the triangle-plane, so we have to switch
            //We used triangle #0 to generate the plane
            if (_Geometry.GetSignedDistanceFromPointToPlane(p4, plane) < 0f)
            {
                triangleToRemove = triangles[1];
                triangleToKeep   = triangles[0];
            }

            //Delete the triangle
            convexHull.DeleteFace(triangleToRemove);

            //Build three new triangles

            //The triangle we keep is ordered clock-wise:
            Vector3 p1_opposite = triangleToKeep.edge.v.position;
            Vector3 p2_opposite = triangleToKeep.edge.nextEdge.v.position;
            Vector3 p3_opposite = triangleToKeep.edge.nextEdge.nextEdge.v.position;

            //But we are looking at it from the back-side,
            //so we add those vertices counter-clock-wise to make the new triangles clock-wise
            convexHull.AddTriangle(p1_opposite, p3_opposite, p4);
            convexHull.AddTriangle(p3_opposite, p2_opposite, p4);
            convexHull.AddTriangle(p2_opposite, p1_opposite, p4);

            //Make sure all opposite edges are connected
            convexHull.ConnectAllEdgesSlow();

            //Debug.Log(convexHull.faces.Count);

            //Display what weve got so far
            //foreach (HalfEdgeFace3 f in convexHull.faces)
            //{
            //    TestAlgorithmsHelpMethods.DebugDrawTriangle(f, Color.white, Color.red);
            //}

            /*
             * //Now we might as well remove all the points that are within the tetrahedron because they are not on the hull
             * //But this is slow if we have many points and none of them are inside
             * HashSet<MyVector3> pointsToRemove = new HashSet<MyVector3>();
             *
             * foreach (MyVector3 p in points)
             * {
             *  bool isWithinConvexHull = _Intersections.PointWithinConvexHull(p, convexHull);
             *
             *  if (isWithinConvexHull)
             *  {
             *      pointsToRemove.Add(p);
             *  }
             * }
             *
             * Debug.Log($"Removed {pointsToRemove.Count} points because they were within the tetrahedron");
             *
             * foreach (MyVector3 p in pointsToRemove)
             * {
             *  points.Remove(p);
             * }
             */
        }
        //Remove flat tetrahedrons (a vertex in a triangle)
        private static bool RemoveFlatTetrahedrons(HalfEdgeData3 meshData, Normalizer3 normalizer = null)
        {
            HashSet <HalfEdgeVertex3> vertices = meshData.verts;

            bool foundFlatTetrahedron = false;

            foreach (HalfEdgeVertex3 vertex in vertices)
            {
                HashSet <HalfEdge3> edgesGoingToVertex = vertex.GetEdgesPointingToVertex(meshData);

                if (edgesGoingToVertex.Count == 3)
                {
                    //Find the vertices of the triangle covering this vertex clock-wise
                    HalfEdgeVertex3 v1 = vertex.edge.v;
                    HalfEdgeVertex3 v2 = vertex.edge.prevEdge.oppositeEdge.v;
                    HalfEdgeVertex3 v3 = vertex.edge.oppositeEdge.nextEdge.v;

                    //Build a plane
                    MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(v3.position - v2.position, v1.position - v2.position));

                    Plane3 plane = new Plane3(v1.position, normal);

                    //Find the distance from the vertex to the plane
                    float distance = _Geometry.GetSignedDistanceFromPointToPlane(vertex.position, plane);

                    distance = Mathf.Abs(distance);

                    if (distance < FLAT_TETRAHEDRON_DISTANCE)
                    {
                        //Debug.Log("Found flat tetrahedron");

                        Vector3 p1 = normalizer.UnNormalize(v1.position).ToVector3();
                        Vector3 p2 = normalizer.UnNormalize(v2.position).ToVector3();
                        Vector3 p3 = normalizer.UnNormalize(v3.position).ToVector3();

                        TestAlgorithmsHelpMethods.DebugDrawTriangle(p1, p2, p3, normal.ToVector3(), Color.blue, Color.red);

                        foundFlatTetrahedron = true;

                        //Save the opposite edges
                        HashSet <HalfEdge3> oppositeEdges = new HashSet <HalfEdge3>();

                        oppositeEdges.Add(v1.edge.oppositeEdge);
                        oppositeEdges.Add(v2.edge.oppositeEdge);
                        oppositeEdges.Add(v3.edge.oppositeEdge);

                        //Remove the three triangles
                        foreach (HalfEdge3 e in edgesGoingToVertex)
                        {
                            meshData.DeleteFace(e.face);
                        }

                        //Add the new triangle (could maybe connect it ourselves)
                        HalfEdgeFace3 newTriangle = meshData.AddTriangle(v1.position, v2.position, v3.position, findOppositeEdge: false);

                        meshData.TryFindOppositeEdge(newTriangle.edge, oppositeEdges);
                        meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge, oppositeEdges);
                        meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge.nextEdge, oppositeEdges);

                        break;
                    }
                }
            }

            return(foundFlatTetrahedron);
        }