I had queried this problem many times in the past but never needed to try and solve it until this week. The problem with selecting object/s via given screen rect coordinates within Unity is that if your camera’s view is not orthographic…which in most cases it isn’t, your rectangle, projected collision detection or raycast will need to take the form of a trapezoid shape i.e. matching the camera frustrum, fanning out from the screen rectangle.
You could create a mesh of this shape and try some sort of collision detection, but perhaps if you are using many mesh colliders this may not be an easy task and carry too much overhead.
Here are two pretty useful functions that will solve this task, both functions take two screen coordinates in the form of Vector3’s (as in Input.mousePosition), and an array of GameObjects to test against however they return different results:-
The first function returns an array of GameObjects where the center of the GO’s renderer bounds is within the camera projected rectangle/trapezoid – This method is probably only useful on small non-enclosed meshes however it does allow the user not to have to completely surround objects with the marquee.
The second function returns an array of GameObjects where the meshes local OBB points are within the camera projected rectangle – This method provides much more accuracy with selection however an objects bounds must be completely encompassed by the given screen rect.
One further note is that this example utilises camera.main to refer to the main camera in the scene, so if you want to use this on another camera/view or to be a more flexible function you will need to amend the function to perhaps take the required camera as an argument.
Hope this helps somebody.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
////////////////////////////////////////////////////////MARQUEE SELECTION // Method 1 - GAMEOBJECT BOUNDS CENTRE POINT IN TRAPEZOID // Aims To Find Out Whether a GameObject's Centre Bounds Point is within a Camera Projected Rectangle public static GameObject[] CentreInMarquee(Vector3 P1,Vector3 P2,GameObject[] Items) { P1.z=Camera.main.nearClipPlane; P2.z=Camera.main.farClipPlane; Vector3 NearMin=Camera.main.transform.InverseTransformPoint(Camera.main.ScreenToWorldPoint(P1)); Vector3 NearMax=Camera.main.transform.InverseTransformPoint( Camera.main.ScreenToWorldPoint(new Vector3(P2.x,P2.y,P1.z))); Vector3 FarMin=Camera.main.transform.InverseTransformPoint( Camera.main.ScreenToWorldPoint(new Vector3(P1.x,P1.y,P2.z))); Vector3 FarMax=Camera.main.transform.InverseTransformPoint(Camera.main.ScreenToWorldPoint(P2)); // Quad xz plane Vector2 A1=new Vector2(FarMin.x,FarMin.z); Vector2 B1=new Vector2(FarMax.x,FarMax.z); Vector2 C1=new Vector2(NearMin.x,NearMin.z); Vector2 D1=new Vector2(NearMax.x,NearMax.z); // Quad yz plane Vector2 A2=new Vector2(FarMin.y,FarMin.z); Vector2 B2=new Vector2(FarMax.y,FarMax.z); Vector2 C2=new Vector2(NearMin.y,NearMin.z); Vector2 D2=new Vector2(NearMax.y,NearMax.z); // List<GameObject> Selected=new List<GameObject>(); int i; Vector3 V; Vector2 P; for(i=0;i<Items.Length;++i) { if (Items[i].renderer.enabled) { V=Camera.main.transform.InverseTransformPoint(Items[i].renderer.bounds.center); P=new Vector2(V.x,V.z); if (!PointInTriangle(A1,B1,C1,P)) { if (!PointInTriangle(B1,D1,C1,P)) continue; } P=new Vector2(V.y,V.z); if (!PointInTriangle(A2,B2,C2,P)) { if (!PointInTriangle(B2,D2,C2,P)) continue; } Selected.Add(Items[i]); } } Debug.Log (Selected.Count+" Selected"); return Selected.ToArray(); } // Method 2 - GAMEOBJECT LOCAL OBB BOUNDS IN TRAPEZOID // Aims To Find Out Whether a GameObject's OBB Bounds are within a Camera Projected Rectangle public static GameObject[] BoundsInMarquee(Vector3 P1,Vector3 P2,GameObject[] Items) { P1.z=Camera.main.nearClipPlane; P2.z=Camera.main.farClipPlane; Vector3 NearMin=Camera.main.transform.InverseTransformPoint(Camera.main.ScreenToWorldPoint(P1)); Vector3 NearMax=Camera.main.transform.InverseTransformPoint( Camera.main.ScreenToWorldPoint(new Vector3(P2.x,P2.y,P1.z))); Vector3 FarMin=Camera.main.transform.InverseTransformPoint( Camera.main.ScreenToWorldPoint(new Vector3(P1.x,P1.y,P2.z))); Vector3 FarMax=Camera.main.transform.InverseTransformPoint(Camera.main.ScreenToWorldPoint(P2)); // Quad xz plane Vector2 A1=new Vector2(FarMin.x,FarMin.z); Vector2 B1=new Vector2(FarMax.x,FarMax.z); Vector2 C1=new Vector2(NearMin.x,NearMin.z); Vector2 D1=new Vector2(NearMax.x,NearMax.z); // Quad yz plane Vector2 A2=new Vector2(FarMin.y,FarMin.z); Vector2 B2=new Vector2(FarMax.y,FarMax.z); Vector2 C2=new Vector2(NearMin.y,NearMin.z); Vector2 D2=new Vector2(NearMax.y,NearMax.z); // List<GameObject> Selected=new List<GameObject>(); int i,j; Vector3[] V=new Vector3[8]; Vector2 P; for(i=0;i<Items.Length;++i) { if (Items[i].renderer.enabled) { V[0]=Items[i].GetComponent<MeshFilter>().mesh.bounds.min; V[1]=Items[i].GetComponent<MeshFilter>().mesh.bounds.max; V[2]=new Vector3(V[1].x,V[0].y,V[0].z); V[3]=new Vector3(V[0].x,V[1].y,V[0].z); V[4]=new Vector3(V[1].x,V[1].y,V[0].z); V[5]=new Vector3(V[0].x,V[0].y,V[1].z); V[6]=new Vector3(V[1].x,V[0].y,V[1].z); V[7]=new Vector3(V[0].x,V[1].y,V[1].z); bool found=true; for(j=0;j<8;++j) { V[j]=Camera.main.transform.InverseTransformPoint(Items[i].transform.TransformPoint(V[j])); P=new Vector2(V[j].x,V[j].z); if (!PointInTriangle(A1,B1,C1,P)) { if (!PointInTriangle(B1,D1,C1,P)) {found=false; break;} } P=new Vector2(V[j].y,V[j].z); if (!PointInTriangle(A2,B2,C2,P)) { if (!PointInTriangle(B2,D2,C2,P)) {found=false; break;} } } if (!found) continue; Selected.Add(Items[i]); } } return Selected.ToArray(); } public static bool PointInTriangle(Vector2 A,Vector2 B,Vector2 C,Vector2 P) { //Barycentric Technique Vector2 V0,V1,V2; float d0,d1,d2,d3,d4,invDenom,u,v; V0 = C - A; V1 = B - A; V2 = P - A; // Compute dot products d0 = Vector2.Dot(V0, V0); d1 = Vector2.Dot(V0, V1); d2 = Vector2.Dot(V0, V2); d3 = Vector2.Dot(V1, V1); d4 = Vector2.Dot(V1, V2); // Compute barycentric coordinates invDenom = 1 / (d0 * d3 - d1 * d1); u = (d3 * d2 - d1 * d4) * invDenom; v = (d0 * d4 - d1 * d2) * invDenom; // Check if point is in triangle return (u>0&&v>0&&u+v<1); } |