/// <summary> /// Find the all faces intersected by InWorldRay on this pb_Object. /// </summary> /// <param name="InWorldRay">A ray in world space.</param> /// <param name="mesh">The ProBuilder object to raycast against.</param> /// <param name="hits">If the mesh was intersected, hits contains all intersection point RaycastHit information.</param> /// <param name="cullingMode">What sides of triangles does the ray intersect with.</param> /// <param name="ignore">Optional collection of faces to ignore when raycasting.</param> /// <returns>True if the ray intersects with the mesh, false if not.</returns> internal static bool FaceRaycast( Ray InWorldRay, ProBuilderMesh mesh, out List <RaycastHit> hits, CullingMode cullingMode, HashSet <Face> 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 <RaycastHit>(); // 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); }
/// <summary> /// Attempt to calculate the UV transform for a face. In cases where the face is auto unwrapped /// (manualUV = false), this returns the offset, rotation, and scale from <see cref="Face.uv"/>. If the face is /// manually unwrapped, a transform will be calculated by trying to match an unmodified planar projection to the /// current UVs. The results /// </summary> /// <returns></returns> internal static UVTransform GetUVTransform(ProBuilderMesh mesh, Face face) { Projection.PlanarProject(mesh.positionsInternal, face.indexesInternal, Math.Normal(mesh, face), s_UVTransformProjectionBuffer); return(CalculateDelta(mesh.texturesInternal, face.indexesInternal, s_UVTransformProjectionBuffer, null)); }
internal static bool FaceRaycastBothCullModes(Ray worldRay, ProBuilderMesh mesh, ref SimpleTuple <Face, Vector3> back, ref SimpleTuple <Face, Vector3> 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); }
static Mesh BuildVertexMesh(ProBuilderMesh pb, Dictionary <uint, SimpleTuple <ProBuilderMesh, int> > map, ref uint index) { int length = System.Math.Min(pb.sharedVerticesInternal.Length, ushort.MaxValue / 4 - 1); Vector3[] t_billboards = new Vector3[length * 4]; Vector2[] t_uvs = new Vector2[length * 4]; Vector2[] t_uv2 = new Vector2[length * 4]; Color[] t_col = new Color[length * 4]; int[] t_tris = new int[length * 6]; int n = 0; int t = 0; Vector3 up = Vector3.up; Vector3 right = Vector3.right; for (int i = 0; i < length; i++) { Vector3 v = pb.positionsInternal[pb.sharedVerticesInternal[i][0]]; t_billboards[t + 0] = v; t_billboards[t + 1] = v; t_billboards[t + 2] = v; t_billboards[t + 3] = v; t_uvs[t + 0] = Vector3.zero; t_uvs[t + 1] = Vector3.right; t_uvs[t + 2] = Vector3.up; t_uvs[t + 3] = Vector3.one; t_uv2[t + 0] = -up - right; t_uv2[t + 1] = -up + right; t_uv2[t + 2] = up - right; t_uv2[t + 3] = up + right; t_tris[n + 0] = t + 0; t_tris[n + 1] = t + 1; t_tris[n + 2] = t + 2; t_tris[n + 3] = t + 1; t_tris[n + 4] = t + 3; t_tris[n + 5] = t + 2; Color32 color = EncodeRGBA(index); map.Add(index++, new SimpleTuple <ProBuilderMesh, int>(pb, i)); t_col[t + 0] = color; t_col[t + 1] = color; t_col[t + 2] = color; t_col[t + 3] = color; t += 4; n += 6; } Mesh mesh = new Mesh(); mesh.name = "Vertex Billboard"; mesh.vertices = t_billboards; mesh.uv = t_uvs; mesh.uv2 = t_uv2; mesh.colors = t_col; mesh.triangles = t_tris; return(mesh); }
/// <summary> /// Creates a SceneSelection object in the [Edge editing mode](../manual/modes.html) from the specified mesh. /// </summary> /// <param name="mesh">The ProBuilderMesh containing the edge to select.</param> /// <param name="edge">The Edge to set as the SceneSelection.</param> public SceneSelection(ProBuilderMesh mesh, Edge edge) : this(mesh, new List <Edge>() { edge }) { }
internal static void CreateFaceMesh(ProBuilderMesh mesh, Mesh target) { target.Clear(); target.vertices = mesh.positionsInternal; target.triangles = mesh.selectedFacesInternal.SelectMany(x => x.indexes).ToArray(); }
/// <summary> /// Calculates the tangents for a mesh. /// </summary> /// <param name="mesh">The mesh to calculate tangents for.</param> public static void CalculateTangents(ProBuilderMesh mesh) { int vc = mesh.vertexCount; if (!mesh.HasArrays(MeshArrays.Tangent)) { mesh.tangentsInternal = new Vector4[vc]; } if (!mesh.HasArrays(MeshArrays.Position) || !mesh.HasArrays(MeshArrays.Texture0)) { return; } // http://answers.unity3d.com/questions/7789/calculating-tangents-vector4.html Vector3[] normals = mesh.GetNormals(); var positions = mesh.positionsInternal; var textures = mesh.texturesInternal; var tan1 = new Vector3[vc]; var tan2 = new Vector3[vc]; var tangents = mesh.tangentsInternal; foreach (var face in mesh.facesInternal) { int[] triangles = face.indexesInternal; for (int a = 0, c = triangles.Length; a < c; a += 3) { long i1 = triangles[a + 0]; long i2 = triangles[a + 1]; long i3 = triangles[a + 2]; Vector3 v1 = positions[i1]; Vector3 v2 = positions[i2]; Vector3 v3 = positions[i3]; Vector2 w1 = textures[i1]; Vector2 w2 = textures[i2]; Vector2 w3 = textures[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float r = 1.0f / (s1 * t2 - s2 * t1); Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; } } for (long a = 0; a < vc; ++a) { Vector3 n = normals[a]; Vector3 t = Math.EnsureUnitVector(tan1[a]); Vector3.OrthoNormalize(ref n, ref t); tangents[a].x = t.x; tangents[a].y = t.y; tangents[a].z = t.z; tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f; } }
/// <summary> /// Generate smoothing groups for a set of faces by comparing adjacent faces with normal differences less than angleThreshold (in degrees). /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="faces">Faces to be considered for smoothing.</param> /// <param name="angleThreshold">The maximum angle difference in degrees between adjacent face normals for the shared edge to be considered smooth.</param> public static void ApplySmoothingGroups(ProBuilderMesh mesh, IEnumerable <Face> faces, float angleThreshold) { ApplySmoothingGroups(mesh, faces, angleThreshold, null); }
internal static void ApplySmoothingGroups(ProBuilderMesh mesh, IEnumerable <Face> faces, float angleThreshold, Vector3[] normals) { if (mesh == null || faces == null) { throw new System.ArgumentNullException("mesh"); } // Reset the selected faces to no smoothing group bool anySmoothed = false; foreach (Face face in faces) { if (face.smoothingGroup != smoothingGroupNone) { anySmoothed = true; } face.smoothingGroup = Smoothing.smoothingGroupNone; } // if a set of normals was not supplied, get a new set of normals // with no prior smoothing groups applied. if (normals == null) { if (anySmoothed) { mesh.mesh.normals = null; } normals = mesh.GetNormals(); } float threshold = Mathf.Abs(Mathf.Cos(Mathf.Clamp(angleThreshold, 0f, 89.999f) * Mathf.Deg2Rad)); HashSet <int> used = new HashSet <int>(mesh.facesInternal.Select(x => x.smoothingGroup)); int group = GetNextUnusedSmoothingGroup(1, used); HashSet <Face> processed = new HashSet <Face>(); List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces, true); try { foreach (WingedEdge wing in wings) { // Already part of a group if (!processed.Add(wing.face)) { continue; } wing.face.smoothingGroup = group; if (FindSoftEdgesRecursive(normals, wing, threshold, processed)) { used.Add(group); group = GetNextUnusedSmoothingGroup(group, used); } else { wing.face.smoothingGroup = Smoothing.smoothingGroupNone; } } } catch (System.StackOverflowException e) { Debug.LogWarning("Smoothing has been aborted: Too many edges in the analyzed mesh"); } }
internal static void Unwrap(ProBuilderMesh mesh, Face face, Vector3 projection = default) { Projection.PlanarProject(mesh, face, projection != Vector3.zero? projection : Vector3.zero); ApplyUVSettings(mesh.texturesInternal, face.distinctIndexesInternal, face.uv); }
internal static string SanityCheck(ProBuilderMesh mesh) { return(SanityCheck(mesh.GetVertices())); }
internal static void BuildVertexMeshLegacy(ProBuilderMesh mesh, Mesh target, IList <int> indexes) { const ushort k_MaxPointCount = ushort.MaxValue / 4; int billboardCount = indexes.Count; if (billboardCount > k_MaxPointCount) { billboardCount = k_MaxPointCount; } var positions = mesh.positionsInternal; Vector3[] t_billboards = new Vector3[billboardCount * 4]; Vector3[] t_nrm = new Vector3[billboardCount * 4]; Vector2[] t_uvs = new Vector2[billboardCount * 4]; Vector2[] t_uv2 = new Vector2[billboardCount * 4]; int[] t_tris = new int[billboardCount * 6]; int n = 0; int t = 0; Vector3 up = Vector3.up; Vector3 right = Vector3.right; for (int i = 0; i < billboardCount; i++) { t_billboards[t + 0] = positions[indexes[i]]; t_billboards[t + 1] = positions[indexes[i]]; t_billboards[t + 2] = positions[indexes[i]]; t_billboards[t + 3] = positions[indexes[i]]; t_uvs[t + 0] = Vector3.zero; t_uvs[t + 1] = Vector3.right; t_uvs[t + 2] = Vector3.up; t_uvs[t + 3] = Vector3.one; t_uv2[t + 0] = -up - right; t_uv2[t + 1] = -up + right; t_uv2[t + 2] = up - right; t_uv2[t + 3] = up + right; t_nrm[t + 0] = Vector3.forward; t_nrm[t + 1] = Vector3.forward; t_nrm[t + 2] = Vector3.forward; t_nrm[t + 3] = Vector3.forward; t_tris[n + 0] = t + 0; t_tris[n + 1] = t + 1; t_tris[n + 2] = t + 2; t_tris[n + 3] = t + 1; t_tris[n + 4] = t + 3; t_tris[n + 5] = t + 2; t += 4; n += 6; } target.Clear(); target.vertices = t_billboards; target.normals = t_nrm; target.uv = t_uvs; target.uv2 = t_uv2; target.triangles = t_tris; }
public NonVersionedEditScope(ProBuilderMesh mesh) { m_Mesh = mesh; m_VersionIndex = mesh.versionIndex; }
/// <summary> /// Creates a SceneSelection object in the [Face editing mode](../manual/modes.html) from the specified mesh. /// </summary> /// <param name="mesh">The ProBuilderMesh containing the face to select.</param> /// <param name="face">The Face to set as the SceneSelection.</param> public SceneSelection(ProBuilderMesh mesh, Face face) : this(mesh, new List <Face>() { face }) { }
/// <summary> /// 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. /// </summary> /// <param name="worldRay"></param> /// <param name="mesh"></param> /// <param name="hit"></param> /// <param name="ignore"></param> /// <returns></returns> internal static bool FaceRaycast(Ray worldRay, ProBuilderMesh mesh, out RaycastHit hit, HashSet <Face> ignore = null) { return(FaceRaycast(worldRay, mesh, out hit, Mathf.Infinity, CullingMode.Back, ignore)); }
internal static Vector3 GetActiveElementPosition(ProBuilderMesh mesh, IEnumerable <Face> faces) { return(mesh.transform.TransformPoint(Math.GetBounds(mesh.positionsInternal, faces.Last().distinctIndexesInternal).center)); }
/// <summary> /// Find the nearest face intersected by InWorldRay on this pb_Object. /// </summary> /// <param name="worldRay">A ray in world space.</param> /// <param name="mesh">The ProBuilder object to raycast against.</param> /// <param name="hit">If the mesh was intersected, hit contains information about the intersect point in local coordinate space.</param> /// <param name="distance">The distance from the ray origin to the intersection point.</param> /// <param name="cullingMode">Which sides of a face are culled when hit testing. Default is back faces are culled.</param> /// <param name="ignore">Optional collection of faces to ignore when raycasting.</param> /// <returns>True if the ray intersects with the mesh, false if not.</returns> internal static bool FaceRaycast(Ray worldRay, ProBuilderMesh mesh, out RaycastHit hit, float distance, CullingMode cullingMode, HashSet <Face> 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 Vector3 GetActiveElementPosition(ProBuilderMesh mesh, IEnumerable <Edge> edges) { var edge = edges.Last(); return(mesh.transform.TransformPoint(Math.GetBounds(mesh.positionsInternal, new int[] { edge.a, edge.b }).center)); }
/// <summary> /// Calculates the normals for a mesh, taking into account smoothing groups. /// </summary> /// <param name="mesh">The mesh to calculate normals for.</param> public static void CalculateNormals(ProBuilderMesh mesh) { CalculateHardNormals(mesh); var sharedVertices = mesh.sharedVerticesInternal; var faces = mesh.facesInternal; // CalculateHardNormals ensures that normals array is initialized var normals = mesh.normalsInternal; int smoothGroupMax = 24; // s_CachedIntArray acts as the smoothingGroup lookup for each vertex ClearIntArray(mesh.vertexCount); // Create a lookup of each triangles smoothing group. for (int i = 0, c = mesh.faceCount; i < c; i++) { var face = faces[i]; var indices = face.distinctIndexesInternal; for (int n = 0, d = indices.Length; n < d; n++) { s_CachedIntArray[indices[n]] = face.smoothingGroup; if (face.smoothingGroup >= smoothGroupMax) { smoothGroupMax = face.smoothingGroup + 1; } } } // Increase buffers size if we have more smoothing groups than usual. if (smoothGroupMax > s_SmoothAvg.Length) { Array.Resize(ref s_SmoothAvg, smoothGroupMax); Array.Resize(ref s_SmoothAvgCount, smoothGroupMax); } // For each sharedIndexes group (individual vertex), find vertices that are in the same smoothing // group and average their normals. for (var i = 0; i < sharedVertices.Length; i++) { for (var n = 0; n < smoothGroupMax; n++) { s_SmoothAvg[n].x = 0f; s_SmoothAvg[n].y = 0f; s_SmoothAvg[n].z = 0f; s_SmoothAvgCount[n] = 0f; } for (var n = 0; n < sharedVertices[i].Count; n++) { int index = sharedVertices[i][n]; int group = s_CachedIntArray[index]; // Ideally this should only continue on group == NONE, but historically negative values have also // been treated as no smoothing. if (group <= Smoothing.smoothingGroupNone || (group > Smoothing.smoothRangeMax && group < Smoothing.hardRangeMax)) { continue; } s_SmoothAvg[group].x += normals[index].x; s_SmoothAvg[group].y += normals[index].y; s_SmoothAvg[group].z += normals[index].z; s_SmoothAvgCount[group] += 1f; } for (int n = 0; n < sharedVertices[i].Count; n++) { int index = sharedVertices[i][n]; int group = s_CachedIntArray[index]; if (group <= Smoothing.smoothingGroupNone || (group > Smoothing.smoothRangeMax && group < Smoothing.hardRangeMax)) { continue; } normals[index].x = s_SmoothAvg[group].x / s_SmoothAvgCount[group]; normals[index].y = s_SmoothAvg[group].y / s_SmoothAvgCount[group]; normals[index].z = s_SmoothAvg[group].z / s_SmoothAvgCount[group]; normals[index] = Math.EnsureUnitVector(normals[index]); } } }
internal static Vector3 GetActiveElementPosition(ProBuilderMesh mesh, IEnumerable <int> vertices) { return(mesh.transform.TransformPoint(mesh.positionsInternal[vertices.First()])); }
/// <summary> /// Find a plane that best fits a set of faces within a texture group. /// </summary> /// <returns>A plane that best matches the layout of the points array.</returns> internal static Plane FindBestPlane(ProBuilderMesh mesh, int textureGroup) { float xx = 0f, xy = 0f, xz = 0f, yy = 0f, yz = 0f, zz = 0f; if (mesh == null) { throw new System.ArgumentNullException("mesh"); } Vector3 c = Vector3.zero; int len = 0; Vector3[] positions = mesh.positionsInternal; int faceCount = mesh.faceCount; Face[] faces = mesh.facesInternal; for (int faceIndex = 0; faceIndex < faceCount; faceIndex++) { if (faces[faceIndex].textureGroup != textureGroup) { continue; } int[] indexes = faces[faceIndex].distinctIndexesInternal; for (int index = 0, indexCount = indexes.Length; index < indexCount; index++) { c.x += positions[indexes[index]].x; c.y += positions[indexes[index]].y; c.z += positions[indexes[index]].z; len++; } } c.x /= len; c.y /= len; c.z /= len; for (int faceIndex = 0; faceIndex < faceCount; faceIndex++) { if (faces[faceIndex].textureGroup != textureGroup) { continue; } int[] indexes = faces[faceIndex].distinctIndexesInternal; for (int index = 0, indexCount = indexes.Length; index < indexCount; index++) { Vector3 r = positions[indexes[index]] - c; xx += r.x * r.x; xy += r.x * r.y; xz += r.x * r.z; yy += r.y * r.y; yz += r.y * r.z; zz += r.z * r.z; } } float det_x = yy * zz - yz * yz; float det_y = xx * zz - xz * xz; float det_z = xx * yy - xy * xy; Vector3 n = Vector3.zero; if (det_x > det_y && det_x > det_z) { n.x = 1f; n.y = (xz * yz - xy * zz) / det_x; n.z = (xy * yz - xz * yy) / det_x; } else if (det_y > det_z) { n.x = (yz * xz - xy * zz) / det_y; n.y = 1f; n.z = (xy * xz - yz * xx) / det_y; } else { n.x = (yz * xy - xz * yy) / det_z; n.y = (xz * xy - yz * xx) / det_z; n.z = 1f; } n.Normalize(); return(new Plane(n, c)); }
/// <summary> /// Builds a list of predecessors from a given face index to all other faces /// Uses the Djikstra pathfinding algorithm /// </summary> /// <param name="mesh">The mesh of the object</param> /// <param name="start">The index of the starting face</param> /// <returns>A list of predecessors from a face index to all other faces</returns> private static int[] Dijkstra(ProBuilderMesh mesh, int start) { HashSet <int> visited = new HashSet <int>(); HashSet <int> toVisit = new HashSet <int>(); if (s_cachedMesh != mesh || s_cachedFacesCount != mesh.faceCount) { s_cachedWings = WingedEdge.GetWingedEdges(mesh, true); s_cachedFacesIndex.Clear(); s_cachedFacesCount = mesh.faceCount; for (int i = 0; i < mesh.facesInternal.Length; i++) { s_cachedFacesIndex.Add(mesh.facesInternal[i], i); } } int wingCount = s_cachedWings.Count; float[] weights = new float[wingCount]; int[] predecessors = new int[wingCount]; for (int i = 0; i < wingCount; i++) { weights[i] = float.MaxValue; predecessors[i] = -1; } int current = start; weights[current] = 0; visited.Add(current); // Construct the paths between the start face and every other faces while (visited.Count < wingCount) { var currentWing = s_cachedWings[current]; var otherWing = currentWing; // Update the weight array for each face next to the current one do { var opposite = otherWing.opposite; if (opposite == null) { otherWing = otherWing.next; continue; } var idx = s_cachedFacesIndex[opposite.face]; var weight = GetWeight(current, idx, mesh); // Change the predecessor and weight if the new path found if shorter if (weights[current] + weight < weights[idx]) { weights[idx] = weights[current] + weight; predecessors[idx] = current; } // Add the face to the ones we can visit next, if not yet visited if (!toVisit.Contains(idx) && !visited.Contains(idx)) { toVisit.Add(idx); } otherWing = otherWing.next; } while (otherWing != currentWing); // This means there is an isolated face if (toVisit.Count == 0) { return(predecessors); } // Look for the next face to visit, choosing the one with less weight float min = float.MaxValue; foreach (var i in toVisit) { if (weights[i] < min) { min = weights[i]; current = i; } } visited.Add(current); toVisit.Remove(current); } return(predecessors); }
/// <summary> /// Create a new list of WingedEdge values for a ProBuilder mesh. /// </summary> /// <param name="mesh">Target ProBuilderMesh.</param> /// <param name="faces">Which faces to include in the WingedEdge list.</param> /// <param name="oneWingPerFace">If `oneWingPerFace` is true the returned list will contain a single winged edge per-face (but still point to all edges).</param> /// <returns>A new list of WingedEdge values gathered from faces.</returns> public static List <WingedEdge> GetWingedEdges(ProBuilderMesh mesh, IEnumerable <Face> faces, bool oneWingPerFace = false) { if (mesh == null) { throw new ArgumentNullException("mesh"); } var lookup = mesh.sharedVertexLookup; List <WingedEdge> winged = new List <WingedEdge>(); Dictionary <Edge, WingedEdge> opposites = new Dictionary <Edge, WingedEdge>(); foreach (Face f in faces) { List <Edge> edges = SortEdgesByAdjacency(f); int edgeLength = edges.Count; WingedEdge first = null, prev = null; for (int n = 0; n < edgeLength; n++) { Edge e = edges[n]; WingedEdge w = new WingedEdge(); w.edge = new EdgeLookup(lookup[e.a], lookup[e.b], e.a, e.b); w.face = f; if (n < 1) { first = w; } if (n > 0) { w.previous = prev; prev.next = w; } if (n == edgeLength - 1) { w.next = first; first.previous = w; } prev = w; WingedEdge opp; if (opposites.TryGetValue(w.edge.common, out opp)) { opp.opposite = w; w.opposite = opp; } else { w.opposite = null; opposites.Add(w.edge.common, w); } if (!oneWingPerFace || n < 1) { winged.Add(w); } } } return(winged); }
/// <summary> /// Creates a SceneSelection object in the [Vertex editing mode](../manual/modes.html) from the specified mesh. /// </summary> /// <param name="mesh">The ProBuilderMesh containing the vertex to select.</param> /// <param name="vertex">The index of the vertex to set as the SceneSelection.</param> public SceneSelection(ProBuilderMesh mesh, int vertex) : this(mesh, new List <int>() { vertex }) { }