/** * Initialize a new qe_Mesh with @InGameObject. Must have a valid meshfilter and mesh. * GameObject will have it's MeshFilter.sharedMesh property set to the clone mesh for editing. * Call qe_Mesh.Revert() to undo this change (and destroy the cloned mesh in the process). */ public static qe_Mesh Create(GameObject InGameObject) { qe_Mesh qmesh = ScriptableObject.CreateInstance <qe_Mesh>(); qmesh.hideFlags = HideFlags.DontSave; qmesh.gameObject = InGameObject; qmesh.transform = qmesh.gameObject.transform; qmesh.originalMesh = InGameObject.GetComponent <MeshFilter>().sharedMesh; qmesh.source = qe_Editor_Utility.GetMeshGUID(qmesh.originalMesh, ref qmesh.originalMeshGUID); // Copy mesh from InMesh. qmesh.cloneMesh = qe_Mesh_Utility.Clone(InGameObject.GetComponent <MeshFilter>().sharedMesh); Undo.RegisterCreatedObjectUndo(qmesh.cloneMesh, "Open Quick Edit"); Undo.RecordObject(qmesh, "Open Quick Edit"); qmesh.Apply(); qmesh.handlesRenderer = (qe_HandleRenderer)Undo.AddComponent(InGameObject, typeof(qe_HandleRenderer)); qmesh.handlesRenderer.hideFlags = HideFlags.HideAndDontSave; qmesh.handlesRenderer.mesh = new Mesh(); qmesh.handlesRenderer.mesh.hideFlags = HideFlags.HideAndDontSave; qmesh.handlesRenderer.material = null; qmesh.CacheElements(); return(qmesh); }
public static void RebuildUV2(qe_Mesh mesh) { Unwrapping.GenerateSecondaryUVSet(mesh.cloneMesh); // GenerateSecondaryUVSet may add vertices, so rebuild the // internal stores. mesh.CacheElements(); }
/** * Remove @triangles from this mesh. */ public static bool DeleteTriangles(qe_Mesh mesh, List <qe_Triangle> triangles) { List <qe_Triangle> trianglesToRemove = new List <qe_Triangle>(triangles); trianglesToRemove.Distinct(); if (trianglesToRemove.Count == mesh.faces.Length) { Debug.LogWarning("Cannot delete every triangle on a mesh!"); return(false); } int subMeshCount = mesh.cloneMesh.subMeshCount; for (int i = 0; i < subMeshCount; i++) { List <int> remove = new List <int>(); List <int> tris = mesh.GetIndices(i).ToList(); for (int n = 0; n < tris.Count; n += 3) { int index = trianglesToRemove.IndexOf(tris[n], tris[n + 1], tris[n + 2]); if (index > -1) { remove.Add(n + 0); remove.Add(n + 1); remove.Add(n + 2); trianglesToRemove.RemoveAt(index); } } remove.Sort(); List <int> rebuilt = new List <int>(); int removeIndex = 0; for (int n = 0; n < tris.Count; n++) { if (removeIndex < remove.Count && n == remove[removeIndex]) { removeIndex++; continue; } rebuilt.Add(tris[n]); } mesh.SetIndices(i, rebuilt.ToArray()); } RemoveUnusedVertices(ref mesh.cloneMesh); mesh.CacheElements(); return(true); }
/** * Translate all @triangles by @offset. @offset is in local coordinates. */ public static void TranslateVertices(this qe_Mesh mesh, int[] triangles, Vector3 offset) { Vector3[] verts = mesh.vertices; for (int i = 0; i < triangles.Length; i++) { verts[triangles[i]] += offset; } mesh.vertices = verts; }
/** * Returns true if this point in world space is occluded by a triangle on this object. */ public static bool PointIsOccluded(qe_Mesh mesh, Vector3 worldPoint) { Camera cam = SceneView.lastActiveSceneView.camera; 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); qe_RaycastHit hit; return(MeshRaycast(ray, mesh, out hit, Vector3.Distance(cam.transform.position, worldPoint), Culling.Back)); }
/** * \brief Given a triangle index, locate buddy indices and move all vertices to this new position */ public static void SetSharedVertexPosition(this qe_Mesh mesh, int index, Vector3 position) { List <int> all = mesh.sharedTriangles[mesh.triangleLookup[index]]; Vector3[] v = mesh.vertices; for (int i = 0; i < all.Count; i++) { v[all[i]] = position; } mesh.vertices = v; }
/** * Iterate all collider components on this mesh and recalculate their size. */ public static void RebuildColliders(qe_Mesh mesh) { Mesh m = mesh.cloneMesh; foreach (Collider c in mesh.gameObject.GetComponents <Collider>()) { System.Type t = c.GetType(); if (t == typeof(BoxCollider)) { ((BoxCollider)c).center = m.bounds.center; ((BoxCollider)c).size = m.bounds.size; } else if (t == typeof(SphereCollider)) { ((SphereCollider)c).center = m.bounds.center; ((SphereCollider)c).radius = Mathf.Max(Mathf.Max(m.bounds.extents.x, m.bounds.extents.y), m.bounds.extents.z); } else if (t == typeof(CapsuleCollider)) { ((CapsuleCollider)c).center = m.bounds.center; Vector2 xy = new Vector2(m.bounds.extents.x, m.bounds.extents.z); ((CapsuleCollider)c).radius = Mathf.Max(xy.x, xy.y); ((CapsuleCollider)c).height = m.bounds.size.y; } else if (t == typeof(WheelCollider)) { ((WheelCollider)c).center = m.bounds.center; ((WheelCollider)c).radius = Mathf.Max(Mathf.Max(m.bounds.extents.x, m.bounds.extents.y), m.bounds.extents.z); } else if (t == typeof(MeshCollider)) { mesh.gameObject.GetComponent <MeshCollider>().sharedMesh = null; // this is stupid. mesh.gameObject.GetComponent <MeshCollider>().sharedMesh = m; } } }
/** * */ public static bool SnapVertices(qe_Mesh mesh, List <int> selected) { /* * Vector3 v = mesh.cloneMesh.vertices[selected[0]]; * * Vector3[] verts = mesh.cloneMesh.vertices; * for (int a = 1; a < selected.Count; a++) * { * for (int b = 0; b < verts.Length;b++) * if (verts[selected[a]] == verts[b]) * verts[b] = v; * } */ Vector3[] verts = mesh.cloneMesh.vertices; List <int> trueSelected = new List <int>(); Vector3 v = mesh.cloneMesh.vertices[selected[0]]; for (int b = 0; b < verts.Length; b++) { for (int a = 1; a < selected.Count; a++) { if (verts[selected[a]] == verts[b]) { trueSelected.Add(b); } } } foreach (int i in trueSelected) { verts[i] = v; } mesh.cloneMesh.vertices = verts; mesh.CacheElements(); // float d = Vector3.Distance(v,verts[selected[0]]); //Debug.Log(">>> " + selected.Count+" / "+c+"/"+d); return(true); }
/** * Do this crazy undo dance because calling Undo.RecordObject(UnityEngine.mesh, "") is insanely slow. */ public static void RecordMeshUndo(qe_Mesh mesh, string message) { Mesh m = qe_Mesh_Utility.Clone(mesh.cloneMesh); Undo.RegisterCreatedObjectUndo(m, message); Mesh old = mesh.cloneMesh; Undo.RecordObject(mesh, message); { mesh.cloneMesh = m; mesh.Apply(); } Undo.DestroyObjectImmediate(old); #if UNITY_5 Undo.SetCurrentGroupName(message); #endif }
public static void Facetize(qe_Mesh qmesh) { Mesh mesh = qmesh.cloneMesh; int triangleCount = mesh.triangles.Length; bool boneWeights_isNull = mesh.boneWeights.NullOrEmpty(); bool colors_isNull = mesh.colors.NullOrEmpty(); bool colors32_isNull = mesh.colors32.NullOrEmpty(); bool normals_isNull = mesh.normals.NullOrEmpty(); bool tangents_isNull = mesh.tangents.NullOrEmpty(); bool uv_isNull = mesh.uv.NullOrEmpty(); bool uv2_isNull = mesh.uv2.NullOrEmpty(); #if UNITY_5 bool uv3_isNull = mesh.uv3.NullOrEmpty(); bool uv4_isNull = mesh.uv4.NullOrEmpty(); #endif bool vertices_isNull = mesh.vertices.NullOrEmpty(); BoneWeight[] boneWeights = boneWeights_isNull ? null : new BoneWeight[triangleCount]; Color[] colors = colors_isNull ? null : new Color[triangleCount]; Color32[] colors32 = colors32_isNull ? null : new Color32[triangleCount]; Vector3[] normals = normals_isNull ? null : new Vector3[triangleCount]; Vector4[] tangents = tangents_isNull ? null : new Vector4[triangleCount]; Vector2[] uv = uv_isNull ? null : new Vector2[triangleCount]; Vector2[] uv2 = uv2_isNull ? null : new Vector2[triangleCount]; #if UNITY_5 Vector2[] uv3 = uv3_isNull ? null : new Vector2[triangleCount]; Vector2[] uv4 = uv4_isNull ? null : new Vector2[triangleCount]; #endif Vector3[] vertices = new Vector3[triangleCount]; // cache mesh arrays because accessing them through the reference is slooooow Vector3[] mVertices = mesh.vertices; BoneWeight[] mBoneWeights = mesh.boneWeights; Color[] mColors = mesh.colors; Color32[] mColors32 = mesh.colors32; Vector3[] mNormals = mesh.normals; Vector4[] mTangents = mesh.tangents; Vector2[] mUv = mesh.uv; Vector2[] mUv2 = mesh.uv2; #if UNITY_5 Vector2[] mUv3 = mesh.uv3; Vector2[] mUv4 = mesh.uv4; #endif int index = 0; int[][] triangles = new int[mesh.subMeshCount][]; for (int i = 0; i < mesh.subMeshCount; i++) { triangles[i] = qmesh.GetIndices(i); for (int t = 0; t < triangles[i].Length; t++) { int n = triangles[i][t]; if (!boneWeights_isNull) { boneWeights[index] = mBoneWeights[n]; } if (!colors_isNull) { colors[index] = mColors[n]; } if (!colors32_isNull) { colors32[index] = mColors32[n]; } if (!normals_isNull) { normals[index] = mNormals[n]; } if (!tangents_isNull) { tangents[index] = mTangents[n]; } if (!uv_isNull) { uv[index] = mUv[n]; } if (!uv2_isNull) { uv2[index] = mUv2[n]; } #if UNITY_5 if (!uv3_isNull) { uv3[index] = mUv3[n]; } if (!uv4_isNull) { uv4[index] = mUv4[n]; } #endif if (!vertices_isNull) { vertices[index] = mVertices[n]; } triangles[i][t] = index; index++; } } mesh.vertices = vertices; mesh.boneWeights = boneWeights; mesh.colors = colors; mesh.colors32 = colors32; mesh.normals = normals; mesh.tangents = tangents; mesh.uv = uv; mesh.uv2 = uv2; #if UNITY_5 mesh.uv3 = uv3; mesh.uv4 = uv4; #endif for (int i = 0; i < mesh.subMeshCount; i++) { qmesh.SetIndices(i, triangles[i]); } mesh.RecalculateNormals(); qmesh.CacheElements(); }
/** * */ public static bool SmoothTriangles(qe_Mesh mesh, List <qe_Triangle> triangles) { List <Vector3> distinctVerts = new List <Vector3>(); List <int> tris = new List <int>(); int i = 0; foreach (var t in triangles) { for (int a = 0; a < 3; a++) { Vector3 v = mesh.vertices[t.indices[a]]; int oldIdx = -1; for (int b = 0; b < distinctVerts.Count; b++) { if (v == distinctVerts[b]) { oldIdx = b; break; } } if (oldIdx == -1) { oldIdx = distinctVerts.Count; distinctVerts.Add(v); } tris.Add(oldIdx); i++; } } Debug.Log("Distict verts: " + distinctVerts.Count); Mesh tmpMesh = new Mesh(); tmpMesh.SetVertices(distinctVerts); tmpMesh.SetTriangles(tris, 0); tmpMesh.RecalculateNormals(); Debug.Log("New normals: " + tmpMesh.normals.Length); Vector3[] norms = mesh.cloneMesh.normals; i = 0; foreach (var t in triangles) { for (int a = 0; a < 3; a++) { int di = tris[i]; Vector3 n = tmpMesh.normals[di]; norms[t.indices[a]] = n; i++; } } mesh.cloneMesh.normals = norms; mesh.CacheElements(); return(true); //mesh.cloneMesh.RecalculateNormals(); }
public static void RebuildNormals(qe_Mesh mesh) { mesh.cloneMesh.RecalculateNormals(); }
/** * Find the nearest triangle intersected by InWorldRay on this pb_Object. InWorldRay is in world space. * @hit contains information about the hit point. @distance limits how far from @InWorldRay.origin the hit * point may be. @cullingMode determines what face orientations are tested (Culling.Front only tests front * faces, Culling.Back only tests back faces, and Culling.FrontBack tests both). */ public static bool MeshRaycast(Ray InWorldRay, qe_Mesh mesh, out qe_RaycastHit hit, float distance, Culling cullingMode) { /** * 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.vertices; float dist = 0f; Vector3 point = Vector3.zero; float OutHitPoint = Mathf.Infinity; float dot; // vars used in loop Vector3 nrm; // vars used in loop int OutHitFace = -1; Vector3 OutNrm = Vector3.zero; /** * Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. */ for(int CurFace = 0; CurFace < mesh.faces.Length; ++CurFace) { int[] Indices = mesh.faces[CurFace].indices; for(int CurTriangle = 0; CurTriangle < Indices.Length; CurTriangle += 3) { Vector3 a = vertices[Indices[CurTriangle+0]]; Vector3 b = vertices[Indices[CurTriangle+1]]; Vector3 c = vertices[Indices[CurTriangle+2]]; nrm = Vector3.Cross(b-a, c-a); dot = Vector3.Dot(InWorldRay.direction, nrm); bool ignore = false; switch(cullingMode) { case Culling.Front: if(dot > 0f) ignore = true; break; case Culling.Back: if(dot < 0f) ignore = true; break; } if(!ignore && qe_Math.RayIntersectsTriangle(InWorldRay, a, b, c, out dist, out point)) { if(dist > OutHitPoint || dist > distance) continue; OutNrm = nrm; OutHitFace = CurFace; OutHitPoint = dist; continue; } } } hit = new qe_RaycastHit(OutHitPoint, InWorldRay.GetPoint(OutHitPoint), OutNrm, OutHitFace); return OutHitFace > -1; }
/** * Returns true if this point in world space is occluded by a triangle on this object. */ public static bool PointIsOccluded(qe_Mesh mesh, Vector3 worldPoint) { Camera cam = SceneView.lastActiveSceneView.camera; 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); qe_RaycastHit hit; return MeshRaycast(ray, mesh, out hit, Vector3.Distance(cam.transform.position, worldPoint), Culling.Back); }
public static Vector3 Normal(this qe_Mesh mesh, qe_Triangle tri) { return(Normal(mesh.vertices[tri.x], mesh.vertices[tri.y], mesh.vertices[tri.z])); }
public static bool VertexRaycast(Vector2 mousePosition, int rectSize, ElementCache selection, out int index) { qe_Mesh mesh = selection.mesh; float bestDistance = Mathf.Infinity; float distance = 0f; index = -1; GameObject go = HandleUtility.PickGameObject(mousePosition, false); if (go == null || go != selection.transform.gameObject) { Camera cam = SceneView.lastActiveSceneView.camera; int width = Screen.width; int height = Screen.height; Rect mouseRect = new Rect(mousePosition.x - (rectSize / 2f), mousePosition.y - (rectSize / 2f), rectSize, rectSize); List <int> user = (List <int>)selection.mesh.GetUserIndices(); for (int i = 0; i < user.Count; i++) { if (mouseRect.Contains(HandleUtility.WorldToGUIPoint(selection.verticesInWorldSpace[user[i]]))) { Vector3 v = cam.WorldToScreenPoint(selection.verticesInWorldSpace[user[i]]); distance = Vector2.Distance(mousePosition, v); if (distance < bestDistance) { if (v.z <= 0 || v.x < 0 || v.y < 0 || v.x > width || v.y > height) { continue; } if (PointIsOccluded(mesh, selection.verticesInWorldSpace[user[i]])) { continue; } index = user[i]; bestDistance = Vector2.Distance(v, mousePosition); } } } } else { // Test culling List <qe_RaycastHit> hits; Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition); if (MeshRaycast(ray, mesh, out hits, Mathf.Infinity, Culling.FrontBack)) { // Sort from nearest hit to farthest hits.Sort((x, y) => x.Distance.CompareTo(y.Distance)); // Find the nearest edge in the hit faces Vector3[] v = mesh.vertices; for (int i = 0; i < hits.Count; i++) { if (PointIsOccluded(mesh, mesh.transform.TransformPoint(hits[i].Point))) { continue; } foreach (int tri in mesh.faces[hits[i].FaceIndex].indices) { float d = Vector3.Distance(hits[i].Point, v[tri]); if (d < bestDistance) { bestDistance = d; index = tri; } } if (Vector3.Dot(ray.direction, mesh.transform.TransformDirection(hits[i].Normal)) < 0f) { break; } } if (index > -1 && Vector2.Distance(mousePosition, HandleUtility.WorldToGUIPoint(selection.verticesInWorldSpace[index])) > rectSize * 1.3f) { index = -1; } } } if (index > -1) { index = mesh.ToUserIndex(index); } return(index > -1); }
/** * Checks if mouse is over an edge, and if so, returns true setting @edge. */ public static bool EdgeRaycast(Vector2 mousePosition, ElementCache selection, out qe_Edge edge) { qe_Mesh mesh = selection.mesh; Vector3 v0, v1; float bestDistance = Mathf.Infinity; float distance = 0f; edge = null; GameObject go = HandleUtility.PickGameObject(mousePosition, false); if (go == null || go != selection.transform.gameObject) { qe_Edge[] edges = mesh.userEdges; int width = Screen.width; int height = Screen.height; for (int i = 0; i < edges.Length; i++) { v0 = selection.verticesInWorldSpace[edges[i].x]; v1 = selection.verticesInWorldSpace[edges[i].y]; distance = HandleUtility.DistanceToLine(v0, v1); if (distance < bestDistance && distance < MAX_EDGE_SELECT_DISTANCE) // && !PointIsOccluded(mesh, (v0+v1)*.5f) ) { Vector3 vs0 = SceneView.lastActiveSceneView.camera.WorldToScreenPoint(v0); // really simple frustum check (will fail on edges that have vertices outside the frustum but is visible) if (vs0.z <= 0 || vs0.x < 0 || vs0.y < 0 || vs0.x > width || vs0.y > height) { continue; } Vector3 vs1 = SceneView.lastActiveSceneView.camera.WorldToScreenPoint(v1); if (vs1.z <= 0 || vs1.x < 0 || vs1.y < 0 || vs1.x > width || vs1.y > height) { continue; } bestDistance = distance; edge = edges[i]; } } } else { // Test culling List <qe_RaycastHit> hits; Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition); if (MeshRaycast(ray, mesh, out hits, Mathf.Infinity, Culling.FrontBack)) { // Sort from nearest hit to farthest hits.Sort((x, y) => x.Distance.CompareTo(y.Distance)); // Find the nearest edge in the hit faces Vector3[] v = mesh.vertices; for (int i = 0; i < hits.Count; i++) { if (PointIsOccluded(mesh, mesh.transform.TransformPoint(hits[i].Point))) { continue; } foreach (qe_Edge e in mesh.faces[hits[i].FaceIndex].GetEdges()) { float d = HandleUtility.DistancePointLine(hits[i].Point, v[e.x], v[e.y]); if (d < bestDistance) { bestDistance = d; edge = e; } } if (Vector3.Dot(ray.direction, mesh.transform.TransformDirection(hits[i].Normal)) < 0f) { break; } } if (edge != null && HandleUtility.DistanceToLine(mesh.transform.TransformPoint(v[edge.x]), mesh.transform.TransformPoint(v[edge.y])) > MAX_EDGE_SELECT_DISTANCE) { edge = null; } else { edge.x = mesh.ToUserIndex(edge.x); edge.y = mesh.ToUserIndex(edge.y); } } } return(edge != null); }
/** * Find the all triangles intersected by InWorldRay on this pb_Object. InWorldRay is in world space. * @hit contains information about the hit point. @distance limits how far from @InWorldRay.origin the hit * point may be. @cullingMode determines what face orientations are tested (Culling.Front only tests front * faces, Culling.Back only tests back faces, and Culling.FrontBack tests both). */ public static bool MeshRaycast(Ray InWorldRay, qe_Mesh qe, out List <qe_RaycastHit> hits, float distance, Culling cullingMode) { /** * Transform ray into model space */ InWorldRay.origin -= qe.transform.position; // Why doesn't worldToLocalMatrix apply translation? InWorldRay.origin = qe.transform.worldToLocalMatrix * InWorldRay.origin; InWorldRay.direction = qe.transform.worldToLocalMatrix * InWorldRay.direction; Vector3[] vertices = qe.vertices; float dist = 0f; Vector3 point = Vector3.zero; float dot; // vars used in loop Vector3 nrm; // vars used in loop hits = new List <qe_RaycastHit>(); /** * Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. */ for (int CurFace = 0; CurFace < qe.faces.Length; ++CurFace) { int[] Indices = qe.faces[CurFace].indices; for (int CurTriangle = 0; CurTriangle < Indices.Length; CurTriangle += 3) { Vector3 a = vertices[Indices[CurTriangle + 0]]; Vector3 b = vertices[Indices[CurTriangle + 1]]; Vector3 c = vertices[Indices[CurTriangle + 2]]; if (qe_Math.RayIntersectsTriangle(InWorldRay, a, b, c, out dist, out point)) { nrm = Vector3.Cross(b - a, c - a); switch (cullingMode) { case Culling.Front: dot = Vector3.Dot(InWorldRay.direction, -nrm); if (dot > 0f) { goto case Culling.FrontBack; } break; case Culling.Back: dot = Vector3.Dot(InWorldRay.direction, nrm); if (dot > 0f) { goto case Culling.FrontBack; } break; case Culling.FrontBack: hits.Add(new qe_RaycastHit(dist, InWorldRay.GetPoint(dist), nrm, CurFace)); break; } continue; } } } return(hits.Count > 0); }
/** * Find the nearest triangle intersected by InWorldRay on this pb_Object. InWorldRay is in world space. * @hit contains information about the hit point. @distance limits how far from @InWorldRay.origin the hit * point may be. @cullingMode determines what face orientations are tested (Culling.Front only tests front * faces, Culling.Back only tests back faces, and Culling.FrontBack tests both). */ public static bool MeshRaycast(Ray InWorldRay, qe_Mesh mesh, out qe_RaycastHit hit, float distance, Culling cullingMode) { /** * 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.vertices; float dist = 0f; Vector3 point = Vector3.zero; float OutHitPoint = Mathf.Infinity; float dot; // vars used in loop Vector3 nrm; // vars used in loop int OutHitFace = -1; Vector3 OutNrm = Vector3.zero; /** * Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. */ for (int CurFace = 0; CurFace < mesh.faces.Length; ++CurFace) { int[] Indices = mesh.faces[CurFace].indices; for (int CurTriangle = 0; CurTriangle < Indices.Length; CurTriangle += 3) { Vector3 a = vertices[Indices[CurTriangle + 0]]; Vector3 b = vertices[Indices[CurTriangle + 1]]; Vector3 c = vertices[Indices[CurTriangle + 2]]; nrm = Vector3.Cross(b - a, c - a); dot = Vector3.Dot(InWorldRay.direction, nrm); bool ignore = false; switch (cullingMode) { case Culling.Front: if (dot > 0f) { ignore = true; } break; case Culling.Back: if (dot < 0f) { ignore = true; } break; } if (!ignore && qe_Math.RayIntersectsTriangle(InWorldRay, a, b, c, out dist, out point)) { if (dist > OutHitPoint || dist > distance) { continue; } OutNrm = nrm; OutHitFace = CurFace; OutHitPoint = dist; continue; } } } hit = new qe_RaycastHit(OutHitPoint, InWorldRay.GetPoint(OutHitPoint), OutNrm, OutHitFace); return(OutHitFace > -1); }
/** * 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. */ public static bool MeshRaycast(Ray InWorldRay, qe_Mesh mesh, out qe_RaycastHit hit) { return MeshRaycast(InWorldRay, mesh, out hit, Mathf.Infinity, Culling.Front); }
/** * Returns true if save was successfull, false if user-cancelled or otherwise failed. */ public static bool SaveMeshAssetIfNecessary(qe_Mesh mesh) { string save_path = DO_NOT_SAVE; switch (mesh.source) { case ModelSource.Asset: int saveChanges = EditorUtility.DisplayDialogComplex( "Save Changes", "Save changes to edited mesh?", "Save", // DIALOG_OK "Cancel", // DIALOG_CANCEL "Save As"); // DIALOG_ALT if (saveChanges == DIALOG_OK) { save_path = AssetDatabase.GetAssetPath(mesh.originalMesh); } else if (saveChanges == DIALOG_ALT) { save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.cloneMesh.name + ".asset", "asset", "Save edited mesh to"); } else { return(false); } break; case ModelSource.Imported: case ModelSource.Scene: default: // @todo make sure path is in Assets/ save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.cloneMesh.name + ".asset", "asset", "Save edited mesh to"); break; } if (!save_path.Equals(DO_NOT_SAVE) && !string.IsNullOrEmpty(save_path)) { Object existing = AssetDatabase.LoadMainAssetAtPath(save_path); if (existing != null) { qe_Mesh_Utility.Copy((Mesh)existing, mesh.cloneMesh); GameObject.DestroyImmediate(mesh.cloneMesh); } else { AssetDatabase.CreateAsset(mesh.cloneMesh, save_path); } AssetDatabase.Refresh(); MeshFilter mf = mesh.gameObject.GetComponent <MeshFilter>(); SkinnedMeshRenderer mr = mesh.gameObject.GetComponent <SkinnedMeshRenderer>(); if (mf != null) { mf.sharedMesh = (Mesh)AssetDatabase.LoadAssetAtPath(save_path, typeof(Mesh)); } else if (mr != null) { mr.sharedMesh = (Mesh)AssetDatabase.LoadAssetAtPath(save_path, typeof(Mesh)); } return(true); } // Save was canceled return(false); }
/** * Find the all triangles intersected by InWorldRay on this pb_Object. InWorldRay is in world space. * @hit contains information about the hit point. @distance limits how far from @InWorldRay.origin the hit * point may be. @cullingMode determines what face orientations are tested (Culling.Front only tests front * faces, Culling.Back only tests back faces, and Culling.FrontBack tests both). */ public static bool MeshRaycast(Ray InWorldRay, qe_Mesh qe, out List<qe_RaycastHit> hits, float distance, Culling cullingMode) { /** * Transform ray into model space */ InWorldRay.origin -= qe.transform.position; // Why doesn't worldToLocalMatrix apply translation? InWorldRay.origin = qe.transform.worldToLocalMatrix * InWorldRay.origin; InWorldRay.direction = qe.transform.worldToLocalMatrix * InWorldRay.direction; Vector3[] vertices = qe.vertices; float dist = 0f; Vector3 point = Vector3.zero; float dot; // vars used in loop Vector3 nrm; // vars used in loop hits = new List<qe_RaycastHit>(); /** * Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces. */ for(int CurFace = 0; CurFace < qe.faces.Length; ++CurFace) { int[] Indices = qe.faces[CurFace].indices; for(int CurTriangle = 0; CurTriangle < Indices.Length; CurTriangle += 3) { Vector3 a = vertices[Indices[CurTriangle+0]]; Vector3 b = vertices[Indices[CurTriangle+1]]; Vector3 c = vertices[Indices[CurTriangle+2]]; if(qe_Math.RayIntersectsTriangle(InWorldRay, a, b, c, out dist, out point)) { nrm = Vector3.Cross(b-a, c-a); switch(cullingMode) { case Culling.Front: dot = Vector3.Dot(InWorldRay.direction, -nrm); if(dot > 0f) goto case Culling.FrontBack; break; case Culling.Back: dot = Vector3.Dot(InWorldRay.direction, nrm); if(dot > 0f) goto case Culling.FrontBack; break; case Culling.FrontBack: hits.Add( new qe_RaycastHit(dist, InWorldRay.GetPoint(dist), nrm, CurFace)); break; } continue; } } } return hits.Count > 0; }
/** * 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. */ public static bool MeshRaycast(Ray InWorldRay, qe_Mesh mesh, out qe_RaycastHit hit) { return(MeshRaycast(InWorldRay, mesh, out hit, Mathf.Infinity, Culling.Front)); }
/** * Returns true if save was successfull, false if user-cancelled or otherwise failed. */ public static bool SaveMeshAssetIfNecessary(qe_Mesh mesh) { string save_path = DO_NOT_SAVE; switch( mesh.source ) { case ModelSource.Asset: int saveChanges = EditorUtility.DisplayDialogComplex( "Save Changes", "Save changes to edited mesh?", "Save", // DIALOG_OK "Cancel", // DIALOG_CANCEL "Save As"); // DIALOG_ALT if( saveChanges == DIALOG_OK ) save_path = AssetDatabase.GetAssetPath(mesh.originalMesh); else if( saveChanges == DIALOG_ALT ) save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.cloneMesh.name + ".asset", "asset", "Save edited mesh to"); else return false; break; case ModelSource.Imported: case ModelSource.Scene: default: // @todo make sure path is in Assets/ save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.cloneMesh.name + ".asset", "asset", "Save edited mesh to"); break; } if( !save_path.Equals(DO_NOT_SAVE) && !string.IsNullOrEmpty(save_path) ) { Object existing = AssetDatabase.LoadMainAssetAtPath(save_path); if( existing != null ) { qe_Mesh_Utility.Copy( (Mesh)existing, mesh.cloneMesh); GameObject.DestroyImmediate(mesh.cloneMesh); } else { AssetDatabase.CreateAsset(mesh.cloneMesh, save_path ); } AssetDatabase.Refresh(); mesh.gameObject.GetComponent<MeshFilter>().sharedMesh = (Mesh)AssetDatabase.LoadAssetAtPath(save_path, typeof(Mesh)); return true; } // Save was canceled return false; }
/** * Translates @offset into local space and moves all vertices pointed to by triangles that amount. */ public static void TranslateVertices_World(this qe_Mesh mesh, int[] triangles, Vector3 offset) { mesh.TranslateVertices(triangles, mesh.transform.worldToLocalMatrix * offset); }