static EdgeAndDistance GetNearestEdgeOnMesh(ProBuilderMesh mesh, Vector3 mousePosition)
        {
            Ray ray = UHandleUtility.GUIPointToWorldRay(mousePosition);

            var res = new EdgeAndDistance()
            {
                edge     = Edge.Empty,
                distance = Mathf.Infinity
            };

            SimpleTuple <Face, Vector3> s_DualCullModeRaycastBackFace  = new SimpleTuple <Face, Vector3>();
            SimpleTuple <Face, Vector3> s_DualCullModeRaycastFrontFace = new SimpleTuple <Face, Vector3>();

            // get the nearest hit face and point for both cull mode front and back, then prefer the result that is nearest the camera.
            if (PHandleUtility.FaceRaycastBothCullModes(ray, mesh, ref s_DualCullModeRaycastBackFace, ref s_DualCullModeRaycastFrontFace))
            {
                Vector3[] v = mesh.positionsInternal;

                if (s_DualCullModeRaycastBackFace.item1 != null)
                {
                    foreach (var edge in s_DualCullModeRaycastBackFace.item1.edgesInternal)
                    {
                        float d = UHandleUtility.DistancePointLine(s_DualCullModeRaycastBackFace.item2, v[edge.a], v[edge.b]);

                        if (d < res.distance)
                        {
                            res.edge     = edge;
                            res.distance = d;
                        }
                    }
                }

                if (s_DualCullModeRaycastFrontFace.item1 != null)
                {
                    var a = mesh.transform.TransformPoint(s_DualCullModeRaycastBackFace.item2);
                    var b = mesh.transform.TransformPoint(s_DualCullModeRaycastFrontFace.item2);
                    var c = SceneView.lastActiveSceneView.camera.transform.position;

                    if (Vector3.Distance(c, b) < Vector3.Distance(c, a))
                    {
                        foreach (var edge in s_DualCullModeRaycastFrontFace.item1.edgesInternal)
                        {
                            float d = UHandleUtility.DistancePointLine(s_DualCullModeRaycastFrontFace.item2, v[edge.a], v[edge.b]);

                            if (d < res.distance)
                            {
                                res.edge     = edge;
                                res.distance = d;
                            }
                        }
                    }
                }

                if (res.edge.IsValid())
                {
                    res.distance = UHandleUtility.DistanceToLine(
                        mesh.transform.TransformPoint(v[res.edge.a]),
                        mesh.transform.TransformPoint(v[res.edge.b]));
                }
            }

            return(res);
        }
        private static EdgeAndDistance GetNearestEdgeOnMesh(Camera camera, ProBuilderMesh mesh, Vector3 mousePosition)
        {
            Ray ray = camera.ScreenPointToRay(mousePosition);

            var res = new EdgeAndDistance()
            {
                edge     = Edge.Empty,
                distance = Mathf.Infinity
            };

            SimpleTuple <Face, Vector3> dualCullModeRaycastBackFace  = new SimpleTuple <Face, Vector3>();
            SimpleTuple <Face, Vector3> dualCullModeRaycastFrontFace = new SimpleTuple <Face, Vector3>();

            // get the nearest hit face and point for both cull mode front and back, then prefer the result that is nearest the camera.
            if (FaceRaycastBothCullModes(ray, mesh, ref dualCullModeRaycastBackFace, ref dualCullModeRaycastFrontFace))
            {
                IList <Vector3> v = mesh.positions;

                if (dualCullModeRaycastBackFace.item1 != null)
                {
                    foreach (var edge in dualCullModeRaycastBackFace.item1.edges)
                    {
                        float d = DistanceToLine(dualCullModeRaycastBackFace.item2, v[edge.a], v[edge.b]);

                        if (d < res.distance)
                        {
                            res.edge     = edge;
                            res.distance = d;
                        }
                    }
                }

                if (dualCullModeRaycastFrontFace.item1 != null)
                {
                    Vector3 a = mesh.transform.TransformPoint(dualCullModeRaycastBackFace.item2);
                    Vector3 b = mesh.transform.TransformPoint(dualCullModeRaycastFrontFace.item2);
                    Vector3 c = camera.transform.position;

                    if (Vector3.Distance(c, b) < Vector3.Distance(c, a))
                    {
                        foreach (var edge in dualCullModeRaycastFrontFace.item1.edges)
                        {
                            float d = DistanceToLine(dualCullModeRaycastFrontFace.item2, v[edge.a], v[edge.b]);

                            if (d < res.distance)
                            {
                                res.edge     = edge;
                                res.distance = d;
                            }
                        }
                    }
                }

                if (res.edge.IsValid())
                {
                    res.distance = DistanceToLine(camera, mousePosition,
                                                  mesh.transform.TransformPoint(v[res.edge.a]),
                                                  mesh.transform.TransformPoint(v[res.edge.b]));
                }
            }

            return(res);
        }
        public static float PickEdge(Camera camera, Vector3 mousePosition, float maxDistance, GameObject pickedObject, IEnumerable <ProBuilderMesh> meshes, ref SceneSelection selection)
        {
            selection.Clear();
            selection.gameObject = pickedObject;
            ProBuilderMesh hoveredMesh = selection.gameObject != null?selection.gameObject.GetComponent <ProBuilderMesh>() : null;

            float bestDistance           = maxDistance;
            float unselectedBestDistance = maxDistance;

            //bool hoveredIsInSelection = meshes.Contains(hoveredMesh);
            //const bool allowUnselected = true;

            //if (hoveredMesh != null && (allowUnselected || hoveredIsInSelection))
            if (hoveredMesh != null)
            {
                EdgeAndDistance tup = GetNearestEdgeOnMesh(camera, hoveredMesh, mousePosition);

                if (tup.edge.IsValid() && tup.distance < maxDistance)
                {
                    selection.gameObject   = hoveredMesh.gameObject;
                    selection.mesh         = hoveredMesh;
                    selection.edge         = tup.edge;
                    unselectedBestDistance = tup.distance;

                    // if it's in the selection, it automatically wins as best. if not, treat this is a fallback.
                    //if (hoveredIsInSelection)
                    {
                        return(tup.distance);
                    }
                }
            }

            foreach (ProBuilderMesh mesh in meshes)
            {
                Transform       trs       = mesh.transform;
                IList <Vector3> positions = mesh.positions;
                m_edges.Clear();

                //When the pointer is over another object, apply a modifier to the distance to prefer picking the object hovered over the currently selected
                //var distMultiplier = (hoveredMesh == mesh || hoveredMesh == null) ? 1.0f : pickerPrefs.offPointerMultiplier;
                const float distMultiplier = 1.0f;

                foreach (Face face in mesh.faces)
                {
                    foreach (Edge edge in face.edges)
                    {
                        int x = edge.a;
                        int y = edge.b;


                        float d = DistanceToLine(camera, mousePosition,
                                                 trs.TransformPoint(positions[x]),
                                                 trs.TransformPoint(positions[y]));

                        d *= distMultiplier;

                        if (d == bestDistance)
                        {
                            m_edges.Add(new Edge(x, y));
                        }
                        else if (d < bestDistance)
                        {
                            m_edges.Clear();
                            m_edges.Add(new Edge(x, y));

                            selection.gameObject = mesh.gameObject;
                            selection.mesh       = mesh;
                            selection.edge       = new Edge(x, y);
                            bestDistance         = d;
                        }
                    }
                }

                //If more than 1 edge is closest, the closest is one of the vertex.
                //Get closest edge to the camera.
                if (m_edges.Count > 1)
                {
                    selection.edge = GetClosestEdgeToCamera(camera, mousePosition, positions, m_edges);
                }
            }

            if (selection.gameObject != null)
            {
                if (bestDistance < maxDistance)
                {
                    return(bestDistance);
                }

                return(Mathf.Infinity);// unselectedBestDistance;
            }

            return(Mathf.Infinity);
        }
        public static float PickEdge(Camera camera, Vector3 mousePosition, float maxDistance, GameObject pickedObject, IEnumerable <ProBuilderMesh> meshes, bool depthTest, ref SceneSelection selection)
        {
            selection.Clear();
            selection.gameObject = pickedObject;
            ProBuilderMesh hoveredMesh = selection.gameObject != null?selection.gameObject.GetComponent <ProBuilderMesh>() : null;

            float bestDistance           = maxDistance;
            float unselectedBestDistance = maxDistance;

            if (hoveredMesh != null)
            {
                EdgeAndDistance tup = GetNearestEdgeOnMesh(camera, hoveredMesh, mousePosition);

                if (tup.edge.IsValid() && tup.distance < maxDistance)
                {
                    selection.gameObject   = hoveredMesh.gameObject;
                    selection.mesh         = hoveredMesh;
                    selection.edge         = tup.edge;
                    unselectedBestDistance = tup.distance;

                    // if it's in the selection, it automatically wins as best. if not, treat this is a fallback.
                    //if (hoveredIsInSelection)
                    {
                        return(tup.distance);
                    }
                }
            }

            ProBuilderMesh pickedMesh = pickedObject != null?pickedObject.GetComponent <ProBuilderMesh>() : null;

            HashSet <ProBuilderMesh> hs = new HashSet <ProBuilderMesh>();

            foreach (ProBuilderMesh mesh in meshes)
            {
                if (!hs.Contains(mesh))
                {
                    hs.Add(mesh);
                }
            }

            if (pickedMesh != null && !hs.Contains(pickedMesh))
            {
                hs.Add(pickedMesh);
            }

            foreach (ProBuilderMesh mesh in hs)
            {
                Transform       trs       = mesh.transform;
                IList <Vector3> positions = mesh.positions;
                m_edges.Clear();

                const float distMultiplier = 1.0f;

                foreach (Face face in mesh.faces)
                {
                    foreach (Edge edge in face.edges)
                    {
                        int x = edge.a;
                        int y = edge.b;

                        Vector3 projectedPoint;
                        Vector3 p0 = trs.TransformPoint(positions[x]);
                        Vector3 p1 = trs.TransformPoint(positions[y]);
                        float   d  = MathHelper.DistanceToLine(camera, mousePosition,
                                                               p0,
                                                               p1, out projectedPoint);

                        if (depthTest)
                        {
                            Ray     ray = camera.ScreenPointToRay(projectedPoint);
                            Vector3 cpl0;
                            Vector3 cpl1;

                            if (MathHelper.ClosestPointsOnTwoLines(out cpl0, out cpl1, ray.origin, ray.direction, p0, p1 - p0))
                            {
                                if (PointIsOccluded(camera, mesh, cpl1))
                                {
                                    continue;
                                }
                            }
                        }

                        d *= distMultiplier;

                        if (d == bestDistance)
                        {
                            m_edges.Add(new Edge(x, y));
                        }
                        else if (d < bestDistance)
                        {
                            m_edges.Clear();
                            m_edges.Add(new Edge(x, y));

                            selection.gameObject = mesh.gameObject;
                            selection.mesh       = mesh;
                            selection.edge       = new Edge(x, y);
                            bestDistance         = d;
                        }
                    }
                }

                //If more than 1 edge is closest, the closest is one of the vertex.
                //Get closest edge to the camera.
                if (m_edges.Count > 1)
                {
                    selection.edge = GetClosestEdgeToCamera(camera, mousePosition, positions, m_edges);
                }
            }

            if (selection.gameObject != null)
            {
                if (bestDistance < maxDistance)
                {
                    return(bestDistance);
                }

                return(Mathf.Infinity);// unselectedBestDistance;
            }

            return(Mathf.Infinity);
        }