using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace UnityEngine.ProBuilder { /// /// Static methods for working with ProBuilderMesh objects in an editor. /// public static class HandleUtility { /// /// Convert a screen point (0,0 bottom left, in pixels) to a GUI point (0,0 top left, in points). /// /// /// /// /// internal static Vector3 ScreenToGuiPoint(this Camera camera, Vector3 point, float pixelsPerPoint) { return new Vector3(point.x / pixelsPerPoint, (camera.pixelHeight - point.y) / pixelsPerPoint, point.z); } /// /// Find a triangle intersected by InRay on InMesh. InRay is in world space. /// Returns the index in mesh.faces of the hit face, or -1. Optionally can ignore backfaces. /// /// /// /// /// /// internal static bool FaceRaycast(Ray worldRay, ProBuilderMesh mesh, out RaycastHit hit, HashSet ignore = null) { return FaceRaycast(worldRay, mesh, out hit, Mathf.Infinity, CullingMode.Back, ignore); } /// /// Find the nearest face intersected by InWorldRay on this pb_Object. /// /// A ray in world space. /// The ProBuilder object to raycast against. /// If the mesh was intersected, hit contains information about the intersect point in local coordinate space. /// The distance from the ray origin to the intersection point. /// Which sides of a face are culled when hit testing. Default is back faces are culled. /// Optional collection of faces to ignore when raycasting. /// True if the ray intersects with the mesh, false if not. internal static bool FaceRaycast(Ray worldRay, ProBuilderMesh mesh, out RaycastHit hit, float distance, CullingMode cullingMode, HashSet ignore = null) { // Transform ray into model space worldRay.origin -= mesh.transform.position; // Why doesn't worldToLocalMatrix apply translation? worldRay.origin = mesh.transform.worldToLocalMatrix * worldRay.origin; worldRay.direction = mesh.transform.worldToLocalMatrix * worldRay.direction; var positions = mesh.positionsInternal; var faces = mesh.facesInternal; float OutHitPoint = Mathf.Infinity; int OutHitFace = -1; Vector3 OutNrm = Vector3.zero; // Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. for (int i = 0, fc = faces.Length; i < fc; ++i) { if (ignore != null && ignore.Contains(faces[i])) continue; int[] indexes = mesh.facesInternal[i].indexesInternal; for (int j = 0, ic = indexes.Length; j < ic; j += 3) { Vector3 a = positions[indexes[j + 0]]; Vector3 b = positions[indexes[j + 1]]; Vector3 c = positions[indexes[j + 2]]; Vector3 nrm = Vector3.Cross(b - a, c - a); float dot = Vector3.Dot(worldRay.direction, nrm); bool skip = false; switch (cullingMode) { case CullingMode.Front: if (dot < 0f) skip = true; break; case CullingMode.Back: if (dot > 0f) skip = true; break; } var dist = 0f; Vector3 point; if (!skip && Math.RayIntersectsTriangle(worldRay, a, b, c, out dist, out point)) { if (dist > OutHitPoint || dist > distance) continue; OutNrm = nrm; OutHitFace = i; OutHitPoint = dist; } } } hit = new RaycastHit(OutHitPoint, worldRay.GetPoint(OutHitPoint), OutNrm, OutHitFace); return OutHitFace > -1; } internal static bool FaceRaycastBothCullModes(Ray worldRay, ProBuilderMesh mesh, ref SimpleTuple back, ref SimpleTuple front) { // Transform ray into model space worldRay.origin -= mesh.transform.position; // Why doesn't worldToLocalMatrix apply translation? worldRay.origin = mesh.transform.worldToLocalMatrix * worldRay.origin; worldRay.direction = mesh.transform.worldToLocalMatrix * worldRay.direction; var positions = mesh.positionsInternal; var faces = mesh.facesInternal; back.item1 = null; front.item1 = null; float backDistance = Mathf.Infinity; float frontDistance = Mathf.Infinity; // Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. for (int i = 0, fc = faces.Length; i < fc; ++i) { int[] indexes = mesh.facesInternal[i].indexesInternal; for (int j = 0, ic = indexes.Length; j < ic; j += 3) { Vector3 a = positions[indexes[j + 0]]; Vector3 b = positions[indexes[j + 1]]; Vector3 c = positions[indexes[j + 2]]; float dist; Vector3 point; if (Math.RayIntersectsTriangle(worldRay, a, b, c, out dist, out point)) { if (dist < backDistance || dist < frontDistance) { Vector3 nrm = Vector3.Cross(b - a, c - a); float dot = Vector3.Dot(worldRay.direction, nrm); if (dot < 0f) { if (dist < backDistance) { backDistance = dist; back.item1 = faces[i]; } } else { if (dist < frontDistance) { frontDistance = dist; front.item1 = faces[i]; } } } } } } if (back.item1 != null) back.item2 = worldRay.GetPoint(backDistance); if (front.item1 != null) front.item2 = worldRay.GetPoint(frontDistance); return back.item1 != null || front.item1 != null; } /// /// Find the all faces intersected by InWorldRay on this pb_Object. /// /// A ray in world space. /// The ProBuilder object to raycast against. /// If the mesh was intersected, hits contains all intersection point RaycastHit information. /// What sides of triangles does the ray intersect with. /// Optional collection of faces to ignore when raycasting. /// True if the ray intersects with the mesh, false if not. internal static bool FaceRaycast( Ray InWorldRay, ProBuilderMesh mesh, out List hits, CullingMode cullingMode, HashSet ignore = null) { // Transform ray into model space InWorldRay.origin -= mesh.transform.position; // Why doesn't worldToLocalMatrix apply translation? InWorldRay.origin = mesh.transform.worldToLocalMatrix * InWorldRay.origin; InWorldRay.direction = mesh.transform.worldToLocalMatrix * InWorldRay.direction; Vector3[] vertices = mesh.positionsInternal; hits = new List(); // Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. for (int CurFace = 0; CurFace < mesh.facesInternal.Length; ++CurFace) { if (ignore != null && ignore.Contains(mesh.facesInternal[CurFace])) continue; int[] indexes = mesh.facesInternal[CurFace].indexesInternal; for (int CurTriangle = 0; CurTriangle < indexes.Length; CurTriangle += 3) { Vector3 a = vertices[indexes[CurTriangle + 0]]; Vector3 b = vertices[indexes[CurTriangle + 1]]; Vector3 c = vertices[indexes[CurTriangle + 2]]; var dist = 0f; Vector3 point; if (Math.RayIntersectsTriangle(InWorldRay, a, b, c, out dist, out point)) { Vector3 nrm = Vector3.Cross(b - a, c - a); float dot; // vars used in loop switch (cullingMode) { case CullingMode.Front: dot = Vector3.Dot(InWorldRay.direction, nrm); if (dot > 0f) goto case CullingMode.FrontBack; break; case CullingMode.Back: dot = Vector3.Dot(InWorldRay.direction, nrm); if (dot < 0f) goto case CullingMode.FrontBack; break; case CullingMode.FrontBack: hits.Add(new RaycastHit(dist, InWorldRay.GetPoint(dist), nrm, CurFace)); break; } continue; } } } return hits.Count > 0; } /// /// Transform a ray from world space to a transform local space. /// /// /// /// internal static Ray InverseTransformRay(this Transform transform, Ray InWorldRay) { Vector3 o = InWorldRay.origin; o -= transform.position; o = transform.worldToLocalMatrix * o; Vector3 d = transform.worldToLocalMatrix.MultiplyVector(InWorldRay.direction); return new Ray(o, d); } /// /// Find the nearest triangle intersected by InWorldRay on this mesh. /// /// /// /// /// internal static bool MeshRaycast(Ray InWorldRay, GameObject gameObject, out RaycastHit hit, float distance = Mathf.Infinity) { var meshFilter = gameObject.GetComponent(); var mesh = meshFilter != null ? meshFilter.sharedMesh : null; if (!mesh) { hit = default(RaycastHit); return false; } var transform = gameObject.transform; var ray = transform.InverseTransformRay(InWorldRay); return MeshRaycast(ray, mesh.vertices, mesh.triangles, out hit, distance); } /// /// Cast a ray (in model space) against a mesh. /// /// /// /// /// /// /// internal static bool MeshRaycast(Ray InRay, Vector3[] mesh, int[] triangles, out RaycastHit hit, float distance = Mathf.Infinity) { // float dot; // vars used in loop float hitDistance = Mathf.Infinity; Vector3 hitNormal = new Vector3(0f, 0f, 0f); // vars used in loop Vector3 a, b, c; int hitFace = -1; Vector3 o = InRay.origin, d = InRay.direction; // Iterate faces, testing for nearest hit to ray origin. for (int CurTri = 0; CurTri < triangles.Length; CurTri += 3) { a = mesh[triangles[CurTri + 0]]; b = mesh[triangles[CurTri + 1]]; c = mesh[triangles[CurTri + 2]]; if (Math.RayIntersectsTriangle2(o, d, a, b, c, ref distance, ref hitNormal)) { hitFace = CurTri / 3; hitDistance = distance; break; } } hit = new RaycastHit(hitDistance, InRay.GetPoint(hitDistance), hitNormal, hitFace); return hitFace > -1; } /// /// Returns true if this point in world space is occluded by a triangle on this object. /// /// This is very slow, do not use. /// /// /// /// internal static bool PointIsOccluded(Camera cam, ProBuilderMesh pb, Vector3 worldPoint) { Vector3 dir = (cam.transform.position - worldPoint).normalized; // move the point slightly towards the camera to avoid colliding with its own triangle Ray ray = new Ray(worldPoint + dir * .0001f, dir); RaycastHit hit; return FaceRaycast(ray, pb, out hit, Vector3.Distance(cam.transform.position, worldPoint), CullingMode.Front); } /// /// Collects coincident vertices and returns a rotation calculated from the average normal and bitangent. /// /// The target mesh. /// Vertex indices to consider in the rotation calculations. /// A rotation calculated from the average normal of each vertex. public static Quaternion GetRotation(ProBuilderMesh mesh, IEnumerable indices) { if (!mesh.HasArrays(MeshArrays.Normal)) Normals.CalculateNormals(mesh); if (!mesh.HasArrays(MeshArrays.Tangent)) Normals.CalculateTangents(mesh); var normals = mesh.normalsInternal; var tangents = mesh.tangentsInternal; var nrm = Vector3.zero; var tan = Vector4.zero; float count = 0; foreach (var index in indices) { var n = normals[index]; var t = tangents[index]; nrm.x += n.x; nrm.y += n.y; nrm.z += n.z; tan.x += t.x; tan.y += t.y; tan.z += t.z; tan.w += t.w; count++; } nrm.x /= count; nrm.y /= count; nrm.z /= count; tan.x /= count; tan.y /= count; tan.z /= count; tan.w /= count; if (nrm == Vector3.zero || tan == Vector4.zero) return mesh.transform.rotation; var bit = Vector3.Cross(nrm, tan * tan.w); return mesh.transform.rotation * Quaternion.LookRotation(nrm, bit); } /// /// Get a rotation suitable for orienting a handle or gizmo relative to the element selection. /// /// The target mesh. /// The type of to calculate. /// Faces to consider in the rotation calculations. Only used when /// is . /// A rotation appropriate to the orientation and element selection. public static Quaternion GetFaceRotation(ProBuilderMesh mesh, HandleOrientation orientation, IEnumerable faces) { if (mesh == null) return Quaternion.identity; switch (orientation) { case HandleOrientation.ActiveElement: if (mesh.selectedFaceCount < 1) goto case HandleOrientation.ActiveObject; var face = faces.First(); // Intentionally not using coincident vertices here. We want the normal of just the face, not an // average of it's neighbors. return GetRotation(mesh, face.distinctIndexesInternal); case HandleOrientation.ActiveObject: return mesh.transform.rotation; default: return Quaternion.identity; } } /// /// Get a rotation suitable for orienting a handle or gizmo relative to the element selection. /// /// The target mesh. /// The type of to calculate. /// Edges to consider in the rotation calculations. Only used when /// is . /// A rotation appropriate to the orientation and element selection. public static Quaternion GetEdgeRotation(ProBuilderMesh mesh, HandleOrientation orientation, IEnumerable edges) { if (mesh == null) return Quaternion.identity; switch (orientation) { case HandleOrientation.ActiveElement: if (mesh.selectedEdgeCount < 1) goto case HandleOrientation.ActiveObject; // Getting an average of the edge normals isn't very helpful in real world uses, so we just use the // first selected edge for orientation. // This function accepts an enumerable because in the future we may want to do something more // sophisticated, and it's convenient because selections are stored as collections. var face = EdgeUtility.GetFace(mesh, edges.First()); if (face == null) goto case HandleOrientation.ActiveObject; Normal nrm = Math.NormalTangentBitangent(mesh, face); if (nrm.normal == Vector3.zero || nrm.bitangent == Vector3.zero) goto case HandleOrientation.ActiveObject; return mesh.transform.rotation * Quaternion.LookRotation(nrm.normal, nrm.bitangent); case HandleOrientation.ActiveObject: return mesh.transform.rotation; default: return Quaternion.identity; } } /// /// Get a rotation suitable for orienting a handle or gizmo relative to the element selection. /// /// The target mesh. /// The type of to calculate. /// Edges to consider in the rotation calculations. Only used when /// is . /// A rotation appropriate to the orientation and element selection. public static Quaternion GetVertexRotation(ProBuilderMesh mesh, HandleOrientation orientation, IEnumerable vertices) { if (mesh == null) return Quaternion.identity; switch (orientation) { case HandleOrientation.ActiveElement: if (mesh.selectedVertexCount < 1) goto case HandleOrientation.ActiveObject; return GetRotation(mesh, vertices); case HandleOrientation.ActiveObject: return mesh.transform.rotation; default: return Quaternion.identity; } } } }