using System; using System.Collections.Generic; using System.Diagnostics; namespace Nanomesh { // Let's say F = 2V // Halfedge mesh is V * sizeof(vertex) + 3F * sizeof(Halfedge) + F * sizeof(Face) = 16 * 0.5F + 3F * 20 + 4F = 72F // Connected mesh is V * sizeof(Vector3) + 3F * sizeof(Node) + F * sizeof(Face) = 12 * 0.5F + 3F * 12 + 12F = 54F (without attributes) // Connected mesh no face is V * sizeof(Vector3) + 3F * sizeof(Node) = 12 * 0.5F + 3F * 12 = 42F (without attributes) public partial class ConnectedMesh { // Todo : make this private (can only be modified from the inside) public Vector3[] positions; public MetaAttributeList attributes; public Node[] nodes; public Group[] groups; public AttributeDefinition[] attributeDefinitions; public int[] PositionToNode => _positionToNode ?? (_positionToNode = GetPositionToNode()); private int[] _positionToNode; internal int _faceCount; public int FaceCount => _faceCount; public bool AreNodesSiblings(int nodeIndexA, int nodeIndexB) { return nodes[nodeIndexA].position == nodes[nodeIndexB].position; } public int[] GetPositionToNode() { int[] positionToNode = new int[positions.Length]; for (int i = 0; i < positions.Length; i++) { positionToNode[i] = -1; } for (int i = 0; i < nodes.Length; i++) { if (!nodes[i].IsRemoved) { positionToNode[nodes[i].position] = i; } } return positionToNode; } public int GetEdgeCount(int nodeIndex) { return GetRelativesCount(nodeIndex) + 1; } public int GetRelativesCount(int nodeIndex) { int k = 0; int relative = nodeIndex; while ((relative = nodes[relative].relative) != nodeIndex) { k++; } return k; } public int GetSiblingsCount(int nodeIndex) { int k = 0; int sibling = nodeIndex; while ((sibling = nodes[sibling].sibling) != nodeIndex) { k++; } return k; } public int ReconnectSiblings(int nodeIndex) { int sibling = nodeIndex; int lastValid = -1; int firstValid = -1; int position = -1; do { if (nodes[sibling].IsRemoved) { continue; } if (firstValid == -1) { firstValid = sibling; position = nodes[sibling].position; } if (lastValid != -1) { nodes[lastValid].sibling = sibling; nodes[lastValid].position = position; } lastValid = sibling; } while ((sibling = nodes[sibling].sibling) != nodeIndex); if (lastValid == -1) { return -1; // All siblings were removed } // Close the loop nodes[lastValid].sibling = firstValid; nodes[lastValid].position = position; return firstValid; } public int ReconnectSiblings(int nodeIndexA, int nodeIndexB, int position) { int sibling = nodeIndexA; int lastValid = -1; int firstValid = -1; do { if (nodes[sibling].IsRemoved) { continue; } if (firstValid == -1) { firstValid = sibling; //position = nodes[sibling].position; } if (lastValid != -1) { nodes[lastValid].sibling = sibling; nodes[lastValid].position = position; } lastValid = sibling; } while ((sibling = nodes[sibling].sibling) != nodeIndexA); sibling = nodeIndexB; do { if (nodes[sibling].IsRemoved) { continue; } if (firstValid == -1) { firstValid = sibling; //position = nodes[sibling].position; } if (lastValid != -1) { nodes[lastValid].sibling = sibling; nodes[lastValid].position = position; } lastValid = sibling; } while ((sibling = nodes[sibling].sibling) != nodeIndexB); if (lastValid == -1) { return -1; // All siblings were removed } // Close the loop nodes[lastValid].sibling = firstValid; nodes[lastValid].position = position; return firstValid; } public int CollapseEdge(int nodeIndexA, int nodeIndexB) { int posA = nodes[nodeIndexA].position; int posB = nodes[nodeIndexB].position; Debug.Assert(posA != posB, "A and B must have different positions"); Debug.Assert(!nodes[nodeIndexA].IsRemoved); Debug.Assert(!nodes[nodeIndexB].IsRemoved); Debug.Assert(CheckRelatives(nodeIndexA), "A's relatives must be valid"); Debug.Assert(CheckRelatives(nodeIndexB), "B's relatives must be valid"); Debug.Assert(CheckSiblings(nodeIndexA), "A's siblings must be valid"); Debug.Assert(CheckSiblings(nodeIndexB), "B's siblings must be valid"); int siblingOfA = nodeIndexA; do // Iterates over faces around A { bool isFaceTouched = false; int faceEdgeCount = 0; int nodeIndexC = -1; int relativeOfA = siblingOfA; do // Circulate in face { int posC = nodes[relativeOfA].position; if (posC == posB) { isFaceTouched = true; } else if (posC != posA) { nodeIndexC = relativeOfA; } faceEdgeCount++; } while ((relativeOfA = nodes[relativeOfA].relative) != siblingOfA); if (faceEdgeCount != 3) throw new NotImplementedException(); if (isFaceTouched && faceEdgeCount == 3) { // Remove face : Mark nodes as removed an reconnect siblings around C int posC = nodes[nodeIndexC].position; relativeOfA = siblingOfA; do { nodes[relativeOfA].MarkRemoved(); } while ((relativeOfA = nodes[relativeOfA].relative) != siblingOfA); int validNodeAtC = ReconnectSiblings(nodeIndexC); if (_positionToNode != null) { _positionToNode[posC] = validNodeAtC; } _faceCount--; } } while ((siblingOfA = nodes[siblingOfA].sibling) != nodeIndexA); int validNodeAtA = ReconnectSiblings(nodeIndexA, nodeIndexB, posA); if (_positionToNode != null) { _positionToNode[posA] = validNodeAtA; _positionToNode[posB] = -1; } return validNodeAtA; } public double GetEdgeTopo(int nodeIndexA, int nodeIndexB) { if ((uint)nodeIndexA >= (uint)nodes.Length || (uint)nodeIndexB >= (uint)nodes.Length) { return EdgeBorderPenalty; } if (nodes[nodeIndexA].IsRemoved || nodes[nodeIndexB].IsRemoved) { return EdgeBorderPenalty; } int posB = nodes[nodeIndexB].position; int facesAttached = 0; int attrAtA = -1; int attrAtB = -1; double edgeWeight = 0; int siblingOfA = nodeIndexA; do { int relativeOfA = siblingOfA; while ((relativeOfA = nodes[relativeOfA].relative) != siblingOfA) { int posC = nodes[relativeOfA].position; if (posC == posB) { facesAttached++; if (attributes != null) { for (int i = 0; i < attributes.CountPerAttribute; i++) { if (attrAtB != -1 && !attributes.Equals(attrAtB, nodes[relativeOfA].attribute, i)) { edgeWeight += attributeDefinitions[i].weight; } if (attrAtA != -1 && !attributes.Equals(attrAtA, nodes[siblingOfA].attribute, i)) { edgeWeight += attributeDefinitions[i].weight; } } } attrAtB = nodes[relativeOfA].attribute; attrAtA = nodes[siblingOfA].attribute; } } } while ((siblingOfA = nodes[siblingOfA].sibling) != nodeIndexA); if (facesAttached != 2) // Border or non-manifold edge { edgeWeight += EdgeBorderPenalty; } return edgeWeight; } internal static double EdgeBorderPenalty = 1027.007; // TODO : Make it work with any polygon (other than triangle) public Vector3 GetFaceNormal(int nodeIndex) { int posA = nodes[nodeIndex].position; int posB = nodes[nodes[nodeIndex].relative].position; int posC = nodes[nodes[nodes[nodeIndex].relative].relative].position; Vector3 normal = Vector3.Cross( positions[posB] - positions[posA], positions[posC] - positions[posA]); return normal.Normalized; } // TODO : Make it work with any polygon (other than triangle) public double GetFaceArea(int nodeIndex) { int posA = nodes[nodeIndex].position; int posB = nodes[nodes[nodeIndex].relative].position; int posC = nodes[nodes[nodes[nodeIndex].relative].relative].position; Vector3 normal = Vector3.Cross( positions[posB] - positions[posA], positions[posC] - positions[posA]); return 0.5 * normal.Length; } // Only works with triangles ! public double GetAngleRadians(int nodeIndex) { int posA = nodes[nodeIndex].position; int posB = nodes[nodes[nodeIndex].relative].position; int posC = nodes[nodes[nodes[nodeIndex].relative].relative].position; return Vector3.AngleRadians( positions[posB] - positions[posA], positions[posC] - positions[posA]); } public void Compact() { // Rebuild nodes array with only valid nodes { int validNodesCount = 0; for (int i = 0; i < nodes.Length; i++) if (!nodes[i].IsRemoved) validNodesCount++; Node[] newNodes = new Node[validNodesCount]; int k = 0; Dictionary oldToNewNodeIndex = new Dictionary(); for (int i = 0; i < nodes.Length; i++) { if (!nodes[i].IsRemoved) { newNodes[k] = nodes[i]; oldToNewNodeIndex.Add(i, k); k++; } } for (int i = 0; i < newNodes.Length; i++) { newNodes[i].relative = oldToNewNodeIndex[newNodes[i].relative]; newNodes[i].sibling = oldToNewNodeIndex[newNodes[i].sibling]; } nodes = newNodes; } // Remap positions { Dictionary oldToNewPosIndex = new Dictionary(); for (int i = 0; i < nodes.Length; i++) { if (!oldToNewPosIndex.ContainsKey(nodes[i].position)) oldToNewPosIndex.Add(nodes[i].position, oldToNewPosIndex.Count); nodes[i].position = oldToNewPosIndex[nodes[i].position]; } Vector3[] newPositions = new Vector3[oldToNewPosIndex.Count]; foreach (KeyValuePair oldToNewPos in oldToNewPosIndex) { newPositions[oldToNewPos.Value] = positions[oldToNewPos.Key]; } positions = newPositions; } // Remap attributes if (attributes != null) { Dictionary oldToNewAttrIndex = new Dictionary(); for (int i = 0; i < nodes.Length; i++) { if (!oldToNewAttrIndex.ContainsKey(nodes[i].attribute)) oldToNewAttrIndex.Add(nodes[i].attribute, oldToNewAttrIndex.Count); nodes[i].attribute = oldToNewAttrIndex[nodes[i].attribute]; } MetaAttributeList newAttributes = attributes.CreateNew(oldToNewAttrIndex.Count); foreach (KeyValuePair oldToNewAttr in oldToNewAttrIndex) { newAttributes[oldToNewAttr.Value] = attributes[oldToNewAttr.Key]; } attributes = newAttributes; } _positionToNode = null; // Invalid now } public void MergePositions(double tolerance = 0.01) { Dictionary newPositions = new Dictionary(tolerance <= 0 ? null : new Vector3Comparer(tolerance)); for (int i = 0; i < positions.Length; i++) { newPositions.TryAdd(positions[i], newPositions.Count); } for (int i = 0; i < nodes.Length; i++) { nodes[i].position = newPositions[positions[nodes[i].position]]; } positions = new Vector3[newPositions.Count]; foreach (KeyValuePair pair in newPositions) { positions[pair.Value] = pair.Key; } newPositions = null; // Remapping siblings Dictionary posToLastSibling = new Dictionary(); for (int i = 0; i < nodes.Length; i++) { if (posToLastSibling.ContainsKey(nodes[i].position)) { nodes[i].sibling = posToLastSibling[nodes[i].position]; posToLastSibling[nodes[i].position] = i; } else { nodes[i].sibling = -1; posToLastSibling.Add(nodes[i].position, i); } } for (int i = 0; i < nodes.Length; i++) { if (nodes[i].sibling < 0) { // Assign last sibling to close sibling loop nodes[i].sibling = posToLastSibling[nodes[i].position]; } } _positionToNode = null; // Dereference faces that no longer exist for (int i = 0; i < nodes.Length; i++) { if (nodes[i].IsRemoved) { continue; } int lastPos = nodes[i].position; int relative = i; while ((relative = nodes[relative].relative) != i) // Circulate around face { int currPos = nodes[relative].position; if (lastPos == currPos) { RemoveFace(relative); break; } lastPos = currPos; } } } public void MergeAttributes() { Dictionary _uniqueAttributes = new Dictionary(); for (int i = 0; i < nodes.Length; i++) { _uniqueAttributes.TryAdd(attributes[nodes[i].attribute], nodes[i].attribute); } for (int i = 0; i < nodes.Length; i++) { nodes[i].attribute = _uniqueAttributes[attributes[nodes[i].attribute]]; } } public void RemoveFace(int nodeIndex) { int relative = nodeIndex; do { nodes[relative].MarkRemoved(); ReconnectSiblings(relative); } while ((relative = nodes[relative].relative) != nodeIndex); _faceCount--; } public void Scale(double factor) { for (int i = 0; i < positions.Length; i++) { positions[i] = positions[i] * factor; } } public HashSet GetAllEdges() { HashSet edges = new HashSet(); for (int p = 0; p < PositionToNode.Length; p++) { int nodeIndex = PositionToNode[p]; if (nodeIndex < 0) { continue; } int sibling = nodeIndex; do { int firstRelative = nodes[sibling].relative; int secondRelative = nodes[firstRelative].relative; Edge pair = new Edge(nodes[firstRelative].position, nodes[secondRelative].position); edges.Add(pair); } while ((sibling = nodes[sibling].sibling) != nodeIndex); } return edges; } public SharedMesh ToSharedMesh() { // Compating here is an issue if mesh is being decimated :/ //Compact(); SharedMesh mesh = new SharedMesh(); List triangles = new List(); HashSet browsedNodes = new HashSet(); Group[] newGroups = new Group[groups?.Length ?? 0]; mesh.groups = newGroups; mesh.attributeDefinitions = attributeDefinitions; int currentGroup = 0; int indicesInGroup = 0; Dictionary<(int, int), int> perVertexMap = new Dictionary<(int, int), int>(); for (int i = 0; i < nodes.Length; i++) { if (newGroups.Length > 0 && groups[currentGroup].firstIndex == i) { if (currentGroup > 0) { newGroups[currentGroup - 1].indexCount = indicesInGroup; newGroups[currentGroup].firstIndex = indicesInGroup + newGroups[currentGroup - 1].firstIndex; } indicesInGroup = 0; if (currentGroup < groups.Length - 1) { currentGroup++; } } if (nodes[i].IsRemoved) { continue; } indicesInGroup++; if (browsedNodes.Contains(i)) { continue; } // Only works if all elements are triangles int relative = i; do { if (browsedNodes.Add(relative) && !nodes[relative].IsRemoved) { (int position, int attribute) key = (nodes[relative].position, nodes[relative].attribute); perVertexMap.TryAdd(key, perVertexMap.Count); triangles.Add(perVertexMap[key]); } } while ((relative = nodes[relative].relative) != i); } if (newGroups.Length > 0) { newGroups[currentGroup].indexCount = indicesInGroup; } // Positions mesh.positions = new Vector3[perVertexMap.Count]; foreach (KeyValuePair<(int, int), int> mapping in perVertexMap) { mesh.positions[mapping.Value] = positions[mapping.Key.Item1]; } // Attributes if (attributes != null && attributeDefinitions.Length > 0) { mesh.attributes = attributes.CreateNew(perVertexMap.Count); foreach (KeyValuePair<(int, int), int> mapping in perVertexMap) { mesh.attributes[mapping.Value] = attributes[mapping.Key.Item2]; } } mesh.triangles = triangles.ToArray(); return mesh; } } public struct Edge : IEquatable { public int posA; public int posB; public Edge(int posA, int posB) { this.posA = posA; this.posB = posB; } public override int GetHashCode() { unchecked { return posA + posB; } } public override bool Equals(object obj) { return Equals((Edge)obj); } public bool Equals(Edge pc) { if (ReferenceEquals(this, pc)) { return true; } else { return (posA == pc.posA && posB == pc.posB) || (posA == pc.posB && posB == pc.posA); } } public static bool operator ==(Edge x, Edge y) { return x.Equals(y); } public static bool operator !=(Edge x, Edge y) { return !x.Equals(y); } public override string ToString() { return $""; } } }