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