/// <summary>
        /// Picks the <see cref="Face" /> objects contained within a rect (rectangular selection).
        /// </summary>
        /// <param name="cam">Use this camera to evaluate whether any face object(s) are inside the `rect`.</param>
        /// <param name="rect">Rect is in GUI space, where 0,0 is the top left of the screen, and `width = cam.pixelWidth / pointsPerPixel`.</param>
        /// <param name="selectable">The collection of objects to check for selectability. ProBuilder verifies whether these objects fall inside the `rect`.</param>
        /// <param name="options">The options that define whether to include mesh elements that are hidden and whether to consider elements that only partially fall inside the `rect`.</param>
        /// <param name="pixelsPerPoint">
        /// Scale the render texture to match `rect` coordinates. By default, ProBuilder doesn't scale the texture (the default value is set to 1.0),
        /// but you can specify <see cref="UnityEditor.EditorGUIUtility.pixelsPerPoint" /> if you need to use a different number of screen pixels per point.</param>
        /// <returns>A dictionary of ProBuilderMesh and Face objects that are in the selection `rect`. </returns>
        public static Dictionary <ProBuilderMesh, HashSet <Face> > PickFacesInRect(
            Camera cam,
            Rect rect,
            IList <ProBuilderMesh> selectable,
            PickerOptions options,
            float pixelsPerPoint = 1f)
        {
            if (options.depthTest && options.rectSelectMode == RectSelectMode.Partial)
            {
                return(SelectionPickerRenderer.PickFacesInRect(
                           cam,
                           rect,
                           selectable,
                           (int)(cam.pixelWidth / pixelsPerPoint),
                           (int)(cam.pixelHeight / pixelsPerPoint)));
            }

            var selected = new Dictionary <ProBuilderMesh, HashSet <Face> >();

            foreach (var pb in selectable)
            {
                if (!pb.selectable)
                {
                    continue;
                }

                HashSet <Face> selectedFaces = new HashSet <Face>();
                Transform      trs           = pb.transform;
                Vector3[]      positions     = pb.positionsInternal;
                Vector3[]      screenPoints  = new Vector3[pb.vertexCount];

                for (int nn = 0; nn < pb.vertexCount; nn++)
                {
                    screenPoints[nn] = cam.ScreenToGuiPoint(cam.WorldToScreenPoint(trs.TransformPoint(positions[nn])), pixelsPerPoint);
                }

                for (int n = 0; n < pb.facesInternal.Length; n++)
                {
                    Face face = pb.facesInternal[n];

                    // rect select = complete
                    if (options.rectSelectMode == RectSelectMode.Complete)
                    {
                        // face is behind the camera
                        if (screenPoints[face.indexesInternal[0]].z < cam.nearClipPlane)
                        {
                            continue;
                        }

                        // only check the first index per quad, and if it checks out, then check every other point
                        if (rect.Contains(screenPoints[face.indexesInternal[0]]))
                        {
                            bool nope = false;

                            for (int q = 1; q < face.distinctIndexesInternal.Length; q++)
                            {
                                int index = face.distinctIndexesInternal[q];

                                if (screenPoints[index].z < cam.nearClipPlane || !rect.Contains(screenPoints[index]))
                                {
                                    nope = true;
                                    break;
                                }
                            }

                            if (!nope)
                            {
                                if (!options.depthTest ||
                                    !HandleUtility.PointIsOccluded(cam, pb, trs.TransformPoint(Math.Average(positions, face.distinctIndexesInternal))))
                                {
                                    selectedFaces.Add(face);
                                }
                            }
                        }
                    }
                    // rect select = partial
                    else
                    {
                        Bounds2D poly     = new Bounds2D(screenPoints, face.edgesInternal);
                        bool     overlaps = false;

                        if (poly.Intersects(rect))
                        {
                            // if rect contains one point of polygon, it overlaps
                            for (int nn = 0; nn < face.distinctIndexesInternal.Length && !overlaps; nn++)
                            {
                                Vector3 p = screenPoints[face.distinctIndexesInternal[nn]];
                                overlaps = p.z > cam.nearClipPlane && rect.Contains(p);
                            }

                            // if polygon contains one point of rect, it overlaps. otherwise check for edge intersections
                            if (!overlaps)
                            {
                                Vector2 tl = new Vector2(rect.xMin, rect.yMax);
                                Vector2 tr = new Vector2(rect.xMax, rect.yMax);
                                Vector2 bl = new Vector2(rect.xMin, rect.yMin);
                                Vector2 br = new Vector2(rect.xMax, rect.yMin);

                                overlaps = Math.PointInPolygon(screenPoints, poly, face.edgesInternal, tl);
                                if (!overlaps)
                                {
                                    overlaps = Math.PointInPolygon(screenPoints, poly, face.edgesInternal, tr);
                                }
                                if (!overlaps)
                                {
                                    overlaps = Math.PointInPolygon(screenPoints, poly, face.edgesInternal, br);
                                }
                                if (!overlaps)
                                {
                                    overlaps = Math.PointInPolygon(screenPoints, poly, face.edgesInternal, bl);
                                }

                                // if any polygon edge intersects rect
                                for (int nn = 0; nn < face.edgesInternal.Length && !overlaps; nn++)
                                {
                                    if (Math.GetLineSegmentIntersect(tr, tl, screenPoints[face.edgesInternal[nn].a], screenPoints[face.edgesInternal[nn].b]))
                                    {
                                        overlaps = true;
                                    }
                                    else if (Math.GetLineSegmentIntersect(tl, bl, screenPoints[face.edgesInternal[nn].a], screenPoints[face.edgesInternal[nn].b]))
                                    {
                                        overlaps = true;
                                    }
                                    else if (Math.GetLineSegmentIntersect(bl, br, screenPoints[face.edgesInternal[nn].a], screenPoints[face.edgesInternal[nn].b]))
                                    {
                                        overlaps = true;
                                    }
                                    else if (Math.GetLineSegmentIntersect(br, tl, screenPoints[face.edgesInternal[nn].a], screenPoints[face.edgesInternal[nn].b]))
                                    {
                                        overlaps = true;
                                    }
                                }
                            }
                        }

                        // don't test occlusion since that case is handled special
                        if (overlaps)
                        {
                            selectedFaces.Add(face);
                        }
                    }
                }

                selected.Add(pb, selectedFaces);
            }

            return(selected);
        }
        /// <summary>
        /// Picks the <see cref="Edge" /> objects contained within a rect (rectangular selection).
        /// </summary>
        /// <param name="cam">Use this camera to evaluate whether any edge object(s) are inside the `rect`.</param>
        /// <param name="rect">Rect is in GUI space, where 0,0 is the top left of the screen, and `width = cam.pixelWidth / pointsPerPixel`.</param>
        /// <param name="selectable">The collection of objects to check for selectability. ProBuilder verifies whether these objects fall inside the `rect`.</param>
        /// <param name="options">The options that define whether to include mesh elements that are hidden and whether to consider elements that only partially fall inside the `rect`.</param>
        /// <param name="pixelsPerPoint">
        /// Scale the render texture to match `rect` coordinates. By default, ProBuilder doesn't scale the texture (the default value is set to 1.0),
        /// but you can specify <see cref="UnityEditor.EditorGUIUtility.pixelsPerPoint" /> if you need to use a different number of screen pixels per point.</param>
        /// <returns>A dictionary of ProBuilderMesh and Edge objects that are in the selection `rect`. </returns>
        public static Dictionary <ProBuilderMesh, HashSet <Edge> > PickEdgesInRect(
            Camera cam,
            Rect rect,
            IList <ProBuilderMesh> selectable,
            PickerOptions options,
            float pixelsPerPoint = 1f)
        {
            if (options.depthTest && options.rectSelectMode == RectSelectMode.Partial)
            {
                return(SelectionPickerRenderer.PickEdgesInRect(
                           cam,
                           rect,
                           selectable,
                           true,
                           (int)(cam.pixelWidth / pixelsPerPoint),
                           (int)(cam.pixelHeight / pixelsPerPoint)));
            }

            var selected = new Dictionary <ProBuilderMesh, HashSet <Edge> >();

            foreach (var pb in selectable)
            {
                if (!pb.selectable)
                {
                    continue;
                }

                Transform trs           = pb.transform;
                var       selectedEdges = new HashSet <Edge>();

                for (int i = 0, fc = pb.faceCount; i < fc; i++)
                {
                    var edges = pb.facesInternal[i].edgesInternal;

                    for (int n = 0, ec = edges.Length; n < ec; n++)
                    {
                        var edge = edges[n];

                        var posA = trs.TransformPoint(pb.positionsInternal[edge.a]);
                        var posB = trs.TransformPoint(pb.positionsInternal[edge.b]);

                        Vector3 a = cam.ScreenToGuiPoint(cam.WorldToScreenPoint(posA), pixelsPerPoint);
                        Vector3 b = cam.ScreenToGuiPoint(cam.WorldToScreenPoint(posB), pixelsPerPoint);

                        switch (options.rectSelectMode)
                        {
                        case RectSelectMode.Complete:
                        {
                            // if either of the positions are clipped by the camera we cannot possibly select both, skip it
                            if ((a.z < cam.nearClipPlane || b.z < cam.nearClipPlane))
                            {
                                continue;
                            }

                            if (rect.Contains(a) && rect.Contains(b))
                            {
                                if (!options.depthTest || !HandleUtility.PointIsOccluded(cam, pb, (posA + posB) * .5f))
                                {
                                    selectedEdges.Add(edge);
                                }
                            }

                            break;
                        }

                        case RectSelectMode.Partial:
                        {
                            // partial + depth test is covered earlier
                            if (Math.RectIntersectsLineSegment(rect, a, b))
                            {
                                selectedEdges.Add(edge);
                            }

                            break;
                        }
                        }
                    }
                }

                selected.Add(pb, selectedEdges);
            }

            return(selected);
        }