/// <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); }