public static void MakeLowestFaceFlat(this InteractiveScene scene, IObject3D objectToLayFlatGroup) { var preLayFlatMatrix = objectToLayFlatGroup.Matrix; bool firstVertex = true; IObject3D objectToLayFlat = objectToLayFlatGroup; Vector3Float lowestPosition = Vector3Float.PositiveInfinity; Vector3Float sourceVertexPosition = Vector3Float.NegativeInfinity; IObject3D itemToLayFlat = null; Mesh meshWithLowest = null; var items = objectToLayFlat.VisibleMeshes().Where(i => i.OutputType != PrintOutputTypes.Support); if (!items.Any()) { items = objectToLayFlat.VisibleMeshes(); } // Process each child, checking for the lowest vertex foreach (var itemToCheck in items) { var meshToCheck = itemToCheck.Mesh.GetConvexHull(false); if (meshToCheck == null && meshToCheck.Vertices.Count < 3) { continue; } // find the lowest point on the model for (int testIndex = 0; testIndex < meshToCheck.Vertices.Count; testIndex++) { var vertex = meshToCheck.Vertices[testIndex]; var vertexPosition = vertex.Transform(itemToCheck.WorldMatrix()); if (firstVertex) { meshWithLowest = meshToCheck; lowestPosition = vertexPosition; sourceVertexPosition = vertex; itemToLayFlat = itemToCheck; firstVertex = false; } else if (vertexPosition.Z < lowestPosition.Z) { meshWithLowest = meshToCheck; lowestPosition = vertexPosition; sourceVertexPosition = vertex; itemToLayFlat = itemToCheck; } } } if (meshWithLowest == null) { // didn't find any selected mesh return; } int faceToLayFlat = -1; double largestAreaOfAnyFace = 0; var facesSharingLowestVertex = meshWithLowest.Faces .Select((face, i) => new { face, i }) .Where(faceAndIndex => meshWithLowest.Vertices[faceAndIndex.face.v0] == sourceVertexPosition || meshWithLowest.Vertices[faceAndIndex.face.v1] == sourceVertexPosition || meshWithLowest.Vertices[faceAndIndex.face.v2] == sourceVertexPosition) .Select(j => j.i); var lowestFacesByAngle = facesSharingLowestVertex.OrderBy(i => { var face = meshWithLowest.Faces[i]; var worldNormal = face.normal.TransformNormal(itemToLayFlat.WorldMatrix()); return(worldNormal.CalculateAngle(-Vector3Float.UnitZ)); }); // Check all the faces that are connected to the lowest point to find out which one to lay flat. foreach (var faceIndex in lowestFacesByAngle) { var face = meshWithLowest.Faces[faceIndex]; var worldNormal = face.normal.TransformNormal(itemToLayFlat.WorldMatrix()); var worldAngleDegrees = MathHelper.RadiansToDegrees(worldNormal.CalculateAngle(-Vector3Float.UnitZ)); double largestAreaFound = 0; var faceVeretexIndices = new int[] { face.v0, face.v1, face.v2 }; foreach (var vi in faceVeretexIndices) { if (meshWithLowest.Vertices[vi] != lowestPosition) { var planSurfaceArea = 0.0; foreach (var coPlanarFace in meshWithLowest.GetCoplanerFaces(faceIndex)) { planSurfaceArea += meshWithLowest.GetSurfaceArea(coPlanarFace); } if (largestAreaOfAnyFace == 0 || (planSurfaceArea > largestAreaFound && worldAngleDegrees < 45)) { largestAreaFound = planSurfaceArea; } } } if (largestAreaFound > largestAreaOfAnyFace) { largestAreaOfAnyFace = largestAreaFound; faceToLayFlat = faceIndex; } } double maxDistFromLowestZ = 0; var lowestFace = meshWithLowest.Faces[faceToLayFlat]; var lowestFaceIndices = new int[] { lowestFace.v0, lowestFace.v1, lowestFace.v2 }; var faceVertices = new List <Vector3Float>(); foreach (var vertex in lowestFaceIndices) { var vertexPosition = meshWithLowest.Vertices[vertex].Transform(itemToLayFlat.WorldMatrix()); faceVertices.Add(vertexPosition); maxDistFromLowestZ = Math.Max(maxDistFromLowestZ, vertexPosition.Z - lowestPosition.Z); } if (maxDistFromLowestZ > .001) { var xPositive = (faceVertices[1] - faceVertices[0]).GetNormal(); var yPositive = (faceVertices[2] - faceVertices[0]).GetNormal(); var planeNormal = xPositive.Cross(yPositive).GetNormal(); // this code takes the minimum rotation required and looks much better. Quaternion rotation = new Quaternion(planeNormal, new Vector3Float(0, 0, -1)); Matrix4X4 partLevelMatrix = Matrix4X4.CreateRotation(rotation); // rotate it objectToLayFlat.Matrix = objectToLayFlatGroup.ApplyAtBoundsCenter(partLevelMatrix); } if (objectToLayFlatGroup is Object3D object3D) { AxisAlignedBoundingBox bounds = object3D.GetAxisAlignedBoundingBox(Matrix4X4.Identity, (item) => { return(item.OutputType != PrintOutputTypes.Support); }); Vector3 boundsCenter = (bounds.MaxXYZ + bounds.MinXYZ) / 2; object3D.Matrix *= Matrix4X4.CreateTranslation(new Vector3(0, 0, -boundsCenter.Z + bounds.ZSize / 2)); } else { PlatingHelper.PlaceOnBed(objectToLayFlatGroup); } scene.UndoBuffer.Add(new TransformCommand(objectToLayFlatGroup, preLayFlatMatrix, objectToLayFlatGroup.Matrix)); }
public static void MakeLowestFaceFlat(this InteractiveScene scene, IObject3D objectToLayFlatGroup) { bool firstVertex = true; IObject3D objectToLayFlat = objectToLayFlatGroup; IVertex lowestVertex = null; Vector3 lowestVertexPosition = Vector3.Zero; IObject3D itemToLayFlat = null; // Process each child, checking for the lowest vertex foreach (var itemToCheck in objectToLayFlat.VisibleMeshes()) { var meshToCheck = itemToCheck.Mesh.GetConvexHull(false); if (meshToCheck == null && meshToCheck.Vertices.Count < 3) { continue; } // find the lowest point on the model for (int testIndex = 0; testIndex < meshToCheck.Vertices.Count; testIndex++) { var vertex = meshToCheck.Vertices[testIndex]; Vector3 vertexPosition = Vector3.Transform(vertex.Position, itemToCheck.WorldMatrix()); if (firstVertex) { lowestVertex = meshToCheck.Vertices[testIndex]; lowestVertexPosition = vertexPosition; itemToLayFlat = itemToCheck; firstVertex = false; } else if (vertexPosition.Z < lowestVertexPosition.Z) { lowestVertex = meshToCheck.Vertices[testIndex]; lowestVertexPosition = vertexPosition; itemToLayFlat = itemToCheck; } } } if (lowestVertex == null) { // didn't find any selected mesh return; } PolygonMesh.Face faceToLayFlat = null; double lowestAngleOfAnyFace = double.MaxValue; // Check all the faces that are connected to the lowest point to find out which one to lay flat. foreach (var face in lowestVertex.ConnectedFaces()) { double biggestAngleToFaceVertex = double.MinValue; foreach (IVertex faceVertex in face.Vertices()) { if (faceVertex != lowestVertex) { Vector3 faceVertexPosition = Vector3.Transform(faceVertex.Position, itemToLayFlat.WorldMatrix()); Vector3 pointRelLowest = faceVertexPosition - lowestVertexPosition; double xLeg = new Vector2(pointRelLowest.X, pointRelLowest.Y).Length; double yLeg = pointRelLowest.Z; double angle = Math.Atan2(yLeg, xLeg); if (angle > biggestAngleToFaceVertex) { biggestAngleToFaceVertex = angle; } } } if (biggestAngleToFaceVertex < lowestAngleOfAnyFace) { lowestAngleOfAnyFace = biggestAngleToFaceVertex; faceToLayFlat = face; } } double maxDistFromLowestZ = 0; List <Vector3> faceVertices = new List <Vector3>(); foreach (IVertex vertex in faceToLayFlat.Vertices()) { Vector3 vertexPosition = Vector3.Transform(vertex.Position, itemToLayFlat.WorldMatrix()); faceVertices.Add(vertexPosition); maxDistFromLowestZ = Math.Max(maxDistFromLowestZ, vertexPosition.Z - lowestVertexPosition.Z); } if (maxDistFromLowestZ > .001) { Vector3 xPositive = (faceVertices[1] - faceVertices[0]).GetNormal(); Vector3 yPositive = (faceVertices[2] - faceVertices[0]).GetNormal(); Vector3 planeNormal = Vector3.Cross(xPositive, yPositive).GetNormal(); // this code takes the minimum rotation required and looks much better. Quaternion rotation = new Quaternion(planeNormal, new Vector3(0, 0, -1)); Matrix4X4 partLevelMatrix = Matrix4X4.CreateRotation(rotation); // rotate it objectToLayFlat.Matrix = objectToLayFlatGroup.ApplyAtBoundsCenter(partLevelMatrix); } PlatingHelper.PlaceOnBed(objectToLayFlatGroup); }