From 12420a7458da3b0d09326b08f9785c028662ef29 Mon Sep 17 00:00:00 2001 From: Bernold Kraft Date: Tue, 19 Nov 2019 13:37:35 +0100 Subject: [PATCH 1/5] Adding valid volume and volume distinction props marked simple volumes as nullable, if not closed and set having an additional prop to identify partially closed volumes --- Xbim.Common/Geometry/IXbimGeometryObject.cs | 7 ++++--- Xbim.Common/Geometry/IXbimGeometryObjectSet.cs | 5 ++++- Xbim.Common/Geometry/IXbimShapeGeometryData.cs | 5 +++++ Xbim.Common/Geometry/IXbimSolid.cs | 3 +-- Xbim.Common/Geometry/IXbimSolidSet.cs | 4 ++++ Xbim.Common/Geometry/XbimRegionCollection.cs | 6 ++++++ Xbim.Common/Geometry/XbimShapeGeometry.cs | 14 +++++++++++++- 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Xbim.Common/Geometry/IXbimGeometryObject.cs b/Xbim.Common/Geometry/IXbimGeometryObject.cs index 50d809dd3..674727e9b 100644 --- a/Xbim.Common/Geometry/IXbimGeometryObject.cs +++ b/Xbim.Common/Geometry/IXbimGeometryObject.cs @@ -30,8 +30,9 @@ public interface IXbimGeometryObject : IDisposable /// Gets or sets an arbitrary object value that can be used to store custom information about this element /// object Tag { get; set; } - - - + /// + /// Returns the volume which is closed and valid, if there's an existing closed shell. + /// + double? Volume { get; } } } diff --git a/Xbim.Common/Geometry/IXbimGeometryObjectSet.cs b/Xbim.Common/Geometry/IXbimGeometryObjectSet.cs index e2f2279c7..60a4dd9ca 100644 --- a/Xbim.Common/Geometry/IXbimGeometryObjectSet.cs +++ b/Xbim.Common/Geometry/IXbimGeometryObjectSet.cs @@ -32,7 +32,10 @@ public interface IXbimGeometryObjectSet : IEnumerable, IXbi /// Converts the object to a string in BRep format /// String ToBRep { get; } - + /// + /// Returns the partial volume of the set which is closed and valid. + /// + double VolumeValid { get; } } } diff --git a/Xbim.Common/Geometry/IXbimShapeGeometryData.cs b/Xbim.Common/Geometry/IXbimShapeGeometryData.cs index 508189a5d..e74eee559 100644 --- a/Xbim.Common/Geometry/IXbimShapeGeometryData.cs +++ b/Xbim.Common/Geometry/IXbimShapeGeometryData.cs @@ -50,5 +50,10 @@ public interface IXbimShapeGeometryData /// LocalShapeDisplacement and should be added to placement of the shape in the product. /// IVector3D LocalShapeDisplacement { get; } + + /// + /// Returns the volume which is closed and valid, if there's an existing closed shell. + /// + double? Volume { get; } } } diff --git a/Xbim.Common/Geometry/IXbimSolid.cs b/Xbim.Common/Geometry/IXbimSolid.cs index 2748d997e..250af3f63 100644 --- a/Xbim.Common/Geometry/IXbimSolid.cs +++ b/Xbim.Common/Geometry/IXbimSolid.cs @@ -12,8 +12,7 @@ public interface IXbimSolid : IXbimGeometryObject, IEquatable IXbimShellSet Shells { get; } IXbimFaceSet Faces { get; } IXbimEdgeSet Edges { get; } - IXbimVertexSet Vertices { get; } - double Volume { get; } + IXbimVertexSet Vertices { get; } double SurfaceArea { get; } bool IsPolyhedron { get; } IXbimSolidSet Cut(IXbimSolidSet toCut, double tolerance, ILogger logger=null); diff --git a/Xbim.Common/Geometry/IXbimSolidSet.cs b/Xbim.Common/Geometry/IXbimSolidSet.cs index a4865ddd8..b6986ed94 100644 --- a/Xbim.Common/Geometry/IXbimSolidSet.cs +++ b/Xbim.Common/Geometry/IXbimSolidSet.cs @@ -25,5 +25,9 @@ public interface IXbimSolidSet : IEnumerable, IXbimGeometryObject /// Converts the object to a string in BRep format /// String ToBRep { get; } + /// + /// Returns the partial volume of the set which is closed and valid. + /// + double VolumeValid { get; } } } diff --git a/Xbim.Common/Geometry/XbimRegionCollection.cs b/Xbim.Common/Geometry/XbimRegionCollection.cs index a2a1abe87..ba96603e3 100644 --- a/Xbim.Common/Geometry/XbimRegionCollection.cs +++ b/Xbim.Common/Geometry/XbimRegionCollection.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -282,5 +283,10 @@ byte[] IXbimShapeGeometryData.ShapeData } IVector3D IXbimShapeGeometryData.LocalShapeDisplacement => null; + + /// + ///Returns the sum of bounding box volumes of this region collection. + /// + public double? Volume => this.Select(r => r.ToXbimRect3D().Volume).Sum(); } } diff --git a/Xbim.Common/Geometry/XbimShapeGeometry.cs b/Xbim.Common/Geometry/XbimShapeGeometry.cs index 9d92a5619..4dd67f2da 100644 --- a/Xbim.Common/Geometry/XbimShapeGeometry.cs +++ b/Xbim.Common/Geometry/XbimShapeGeometry.cs @@ -91,6 +91,10 @@ public class XbimShapeGeometry : IXbimShapeGeometryData /// byte[] _shapeData; + /// + /// The volume if available + /// + double? _volume; /// @@ -331,6 +335,14 @@ public override string ToString() IVector3D IXbimShapeGeometryData.LocalShapeDisplacement => LocalShapeDisplacement; public XbimVector3D? LocalShapeDisplacement { get; set; } - + public double? Volume + { + get { + return _volume; + } + set { + _volume = value; + } + } } } From 379ad1ee5d97a89d7c315a5129512cb1642bd4f5 Mon Sep 17 00:00:00 2001 From: Bernold Kraft Date: Tue, 19 Nov 2019 14:26:53 +0100 Subject: [PATCH 2/5] Adding valid volume prop to IXbimShellSet --- Xbim.Common/Geometry/IXbimShellSet.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Xbim.Common/Geometry/IXbimShellSet.cs b/Xbim.Common/Geometry/IXbimShellSet.cs index 40119b556..8153714d7 100644 --- a/Xbim.Common/Geometry/IXbimShellSet.cs +++ b/Xbim.Common/Geometry/IXbimShellSet.cs @@ -20,5 +20,10 @@ public interface IXbimShellSet : IEnumerable, IXbimGeometryObject /// /// void Union(double tolerance); + + /// + /// Returns the partial volume of the set which is closed and valid. + /// + double VolumeValid { get; } } } From 6e2fa9e5c128a62554d10ae31110cc57ec96d010 Mon Sep 17 00:00:00 2001 From: Bernold Kraft Date: Thu, 5 Dec 2019 13:33:06 +0100 Subject: [PATCH 3/5] Refactoring XbimTessellator and XbimTriangulatedMesh Improves the mesh validation and a more stable alignment of orientation --- Xbim.Tessellator/MeshUtils.cs | 20 +- Xbim.Tessellator/XbimTessellator.cs | 529 ++++++++++------------- Xbim.Tessellator/XbimTriangulatedMesh.cs | 470 ++++++++++---------- 3 files changed, 494 insertions(+), 525 deletions(-) diff --git a/Xbim.Tessellator/MeshUtils.cs b/Xbim.Tessellator/MeshUtils.cs index 46801cbcc..7c366c8fd 100644 --- a/Xbim.Tessellator/MeshUtils.cs +++ b/Xbim.Tessellator/MeshUtils.cs @@ -36,6 +36,10 @@ using System.Collections.Generic; using System.Diagnostics; +using Xbim.Common.Geometry; +using Xbim.Common.XbimExtensions; +using Xbim.Ifc4.MeasureResource; + namespace Xbim.Tessellator { public struct Vec3EqualityComparer : IEqualityComparer @@ -106,7 +110,19 @@ public static bool Colinear( Vec3 a, Vec3 b, Vec3 c) // ReSharper restore CompareOfFloatsByEqualityOperator } - + public Vec3(XbimTriplet triple) + { + X = triple.A; + Y = triple.B; + Z = triple.C; + } + + public Vec3(XbimPoint3D p) + { + X = p.X; + Y = p.Y; + Z = p.Z; + } public Vec3(double x, double y, double z) { @@ -151,6 +167,7 @@ public bool IsValid { get { return Length2 > 0; } } + public static double Angle(ref Vec3 v1, ref Vec3 v2) { double cosinus; @@ -163,6 +180,7 @@ public static double Angle(ref Vec3 v1, ref Vec3 v2) if (cosinus < 0.0) return Math.PI - Math.Asin(sinus); return Math.Asin(sinus); } + public static void Neg(ref Vec3 v) { v.X = -v.X; diff --git a/Xbim.Tessellator/XbimTessellator.cs b/Xbim.Tessellator/XbimTessellator.cs index b0b2fbeff..55390e988 100644 --- a/Xbim.Tessellator/XbimTessellator.cs +++ b/Xbim.Tessellator/XbimTessellator.cs @@ -7,26 +7,28 @@ using Xbim.Ifc4.Interfaces; using Xbim.Common.XbimExtensions; using Xbim.Ifc4.MeasureResource; -using Xbim.Common.Collections; +using Microsoft.Extensions.Logging; +using System.Text; namespace Xbim.Tessellator { - public class XbimTessellator { private readonly IModel _model; private readonly XbimGeometryType _geometryType; - public XbimTessellator(IModel model, XbimGeometryType geometryType) + private readonly ILogger _logger; + + public XbimTessellator(IModel model, XbimGeometryType geometryType, ILogger logger = null) { _model = model; _geometryType = geometryType; + _logger = logger; } - public IXbimShapeGeometryData Mesh(IXbimGeometryObject geomObject) - { - return new XbimShapeGeometry(); - - } + /// + /// Issue reporting mask. Default reports only open bodies. + /// + public XbimTriangulationStatus ReportMask { get; set; } = XbimTriangulationStatus.IsOpenBody; /// /// Returns true if the object can be meshed by the tesselator, if it cannot create an IXbimGeometryObject @@ -50,7 +52,7 @@ public XbimShapeGeometry Mesh(IIfcRepresentationItem shape) var sbm = shape as IIfcShellBasedSurfaceModel; if (sbm != null) return Mesh(sbm); var cfs = shape as IIfcConnectedFaceSet; - if (cfs != null) return Mesh(cfs); + if (cfs != null) return Mesh(cfs, false); var fbr = shape as IIfcFacetedBrep; if (fbr != null) return Mesh(fbr); var tfs = shape as IIfcTriangulatedFaceSet; @@ -60,17 +62,14 @@ public XbimShapeGeometry Mesh(IIfcRepresentationItem shape) throw new ArgumentException("Unsupported representation type for tessellation, " + shape.GetType().Name); } - public XbimShapeGeometry Mesh(IIfcFaceBasedSurfaceModel faceBasedModel) { var faceSets = new List>(); foreach (var faceSet in faceBasedModel.FbsmFaces) faceSets.Add(faceSet.CfsFaces.ToList()); - return Mesh(faceSets, faceBasedModel.EntityLabel, (float)faceBasedModel.Model.ModelFactors.Precision); + return Mesh(faceSets, faceBasedModel.EntityLabel, (float)faceBasedModel.Model.ModelFactors.Precision, false); } - - public XbimShapeGeometry Mesh(IIfcShellBasedSurfaceModel shellBasedModel) { return Mesh(shellBasedModel.SbsmBoundary, shellBasedModel.EntityLabel, (float)shellBasedModel.Model.ModelFactors.Precision); @@ -86,27 +85,27 @@ public XbimShapeGeometry Mesh(IEnumerable shellSet, int entityLabel, if (closedShell != null) shells.Add(closedShell.CfsFaces.ToList()); else if (openShell != null) shells.Add(openShell.CfsFaces.ToList()); } - return Mesh(shells, entityLabel, precision); + return Mesh(shells, entityLabel, precision, false); } - public XbimShapeGeometry Mesh(IIfcConnectedFaceSet connectedFaceSet) + public XbimShapeGeometry Mesh(IIfcConnectedFaceSet connectedFaceSet, bool isIntentiallyClosed) { var faces = new List>(); faces.Add(connectedFaceSet.CfsFaces.ToList()); - return Mesh(faces, connectedFaceSet.EntityLabel, (float)connectedFaceSet.Model.ModelFactors.Precision); + return Mesh(faces, connectedFaceSet.EntityLabel, (float)connectedFaceSet.Model.ModelFactors.Precision, isIntentiallyClosed); } public XbimShapeGeometry Mesh(IIfcFacetedBrep fBRepModel) { - return Mesh(fBRepModel.Outer); + return Mesh(fBRepModel.Outer, true); } - public XbimShapeGeometry Mesh(IEnumerable> facesList, int entityLabel, float precision) + public XbimShapeGeometry Mesh(IEnumerable> facesList, int entityLabel, float precision, bool isIntentiallyClosed) { if (_geometryType == XbimGeometryType.PolyhedronBinary) - return MeshPolyhedronBinary(facesList, entityLabel, precision); + return MeshPolyhedronBinary(facesList, entityLabel, precision, isIntentiallyClosed); if (_geometryType == XbimGeometryType.Polyhedron) - return MeshPolyhedronText(facesList, entityLabel, precision); + return MeshPolyhedronText(facesList, entityLabel, precision, isIntentiallyClosed); throw new Exception("Illegal Geometry type, " + _geometryType); } @@ -132,167 +131,95 @@ private XbimShapeGeometry MeshPolyhedronText(IIfcTriangulatedFaceSet triangulati { throw new NotImplementedException(); } + private XbimShapeGeometry MeshPolyhedronText(IIfcPolygonalFaceSet triangulation) { throw new NotImplementedException(); } + private XbimShapeGeometry MeshPolyhedronBinary(IIfcPolygonalFaceSet tess) { var faces = new List>(); faces.Add(new XbimPolygonalFaceSet(tess)); - return Mesh(faces, tess.EntityLabel, (float)tess.Model.ModelFactors.Precision); + return Mesh(faces, tess.EntityLabel, (float)tess.Model.ModelFactors.Precision, tess.Closed ?? false); } private XbimShapeGeometry MeshPolyhedronBinary(IIfcTriangulatedFaceSet triangulation) { + var mesh = Triangulate(triangulation); + return ToBinaryShapeGeometry(mesh); + } + private XbimShapeGeometry ToBinaryShapeGeometry(params XbimTriangulatedMesh[] meshes) + { XbimShapeGeometry shapeGeometry = new XbimShapeGeometry(); shapeGeometry.Format = XbimGeometryType.PolyhedronBinary; using (var ms = new MemoryStream(0x4000)) using (var binaryWriter = new BinaryWriter(ms)) { + // Write out header + uint verticesCount = 0; + uint triangleCount = 0; + uint facesCount = 0; + var boundingBox = XbimRect3D.Empty; + bool isAllVolumeDefined = true; - // Prepare the header - uint verticesCount = (uint)triangulation.Coordinates.CoordList.Count(); - uint triangleCount = (uint)triangulation.CoordIndex.Count(); - uint facesCount = 1;//at the moment just write one face for all triangles, may change to support styed faces in future - shapeGeometry.BoundingBox = XbimRect3D.Empty; - - //Write out the header - binaryWriter.Write((byte)1); //stream format version - // ReSharper disable once RedundantCast - - //now write out the faces - if (triangulation.Normals.Any()) //we have normals so obey them + foreach (var mesh in meshes) { - List> normalIndex = new List>(); - bool hasPnIndex = triangulation.PnIndex.Any(); - if (hasPnIndex) - { + verticesCount += mesh.VertexCount; + triangleCount += mesh.TriangleCount; + facesCount += (uint)mesh.Faces.Count; + shapeGeometry.Volume += mesh.Volume; + isAllVolumeDefined &= mesh.Volume.HasValue; - if (triangulation.PnIndex is List>) //the list of triplets has not been flattened - { - foreach (var item in triangulation.PnIndex as List>) - { - normalIndex.Add(item); - } - - } - else - { - for (int i = 0; i < triangulation.PnIndex.Count; i += 3) - { - var item = new List() { triangulation.PnIndex[i], triangulation.PnIndex[i + 1], triangulation.PnIndex[i + 2] }; - normalIndex.Add(item); - } - } - } + if (boundingBox.IsEmpty) + boundingBox = mesh.BoundingBox; else - { - foreach (var item in triangulation.CoordIndex) - { - normalIndex.Add(item); - } - } - binaryWriter.Write(verticesCount); //number of vertices - binaryWriter.Write(triangleCount); //number of triangles - - XbimRect3D bb = XbimRect3D.Empty; - // use first point as a local origin for large coordinates. It doesn't matter if - // we use min, max or centroid for this. - var origin = triangulation.Coordinates?.CoordList.FirstOrDefault()?.AsTriplet() ?? new XbimTriplet(); - var isLarge = IsLarge(origin.A) || IsLarge(origin.B) || IsLarge(origin.C); - if (isLarge) - { - shapeGeometry.LocalShapeDisplacement = new XbimVector3D(origin.A, origin.B, origin.C); - } - - var points = isLarge ? - triangulation.Coordinates.CoordList.Select(c => c.AsTriplet()).Select(t => new XbimTriplet { A = t.A - origin.A, B = t.B - origin.B, C = t.C - origin.C }) : - triangulation.Coordinates.CoordList.Select(c => c.AsTriplet()); - foreach (var pt in points) - { - binaryWriter.Write((float)pt.A); - binaryWriter.Write((float)pt.B); - binaryWriter.Write((float)pt.C); - - var rect = new XbimRect3D(pt.A, pt.B, pt.C, 0, 0, 0); - bb.Union(rect); - } - - binaryWriter.Write(facesCount); - - shapeGeometry.BoundingBox = bb; - Int32 numTrianglesInFace = triangulation.CoordIndex.Count(); - binaryWriter.Write(-numTrianglesInFace); //not a planar face so make negative - var packedNormals = new List(triangulation.Normals.Count()); - foreach (var normal in triangulation.Normals) - { - var tpl = normal.AsTriplet(); - packedNormals.Add(new XbimPackedNormal(tpl.A, tpl.B, tpl.C)); - } - - - - - int triangleIndex = 0; + boundingBox.Union(mesh.BoundingBox); + } - foreach (var triangle in triangulation.CoordIndex) - { - var triangleTpl = triangle.AsTriplet(); - var normalsIndexTpl = normalIndex[triangleIndex].AsTriplet(); + if (!isAllVolumeDefined) + // Reset, if there are non-closed bodies within the collection + shapeGeometry.Volume = null; + binaryWriter.Write((byte)1); //stream format version + // ReSharper disable once RedundantCast + binaryWriter.Write((UInt32)verticesCount); //number of vertices + binaryWriter.Write(triangleCount); //number of triangles - WriteIndex(binaryWriter, (uint)triangleTpl.A - 1, (uint)verticesCount); - packedNormals[(int)normalsIndexTpl.A - 1].Write(binaryWriter); + // use minimum bbox as a local origin + var origin = boundingBox.Min; + var isLarge = IsLarge(origin.X) || IsLarge(origin.Y) || IsLarge(origin.Z); - WriteIndex(binaryWriter, (uint)triangleTpl.B - 1, (uint)verticesCount); - packedNormals[(int)normalsIndexTpl.B - 1].Write(binaryWriter); + var vertices = isLarge ? + meshes.SelectMany(t => t.Vertices).Select(v => new Vec3(v.X - origin.X, v.Y - origin.Y, v.Z - origin.Z)) : + meshes.SelectMany(t => t.Vertices); - WriteIndex(binaryWriter, (uint)triangleTpl.C - 1, (uint)verticesCount); - packedNormals[(int)normalsIndexTpl.C - 1].Write(binaryWriter); - triangleIndex++; - } + foreach (var v in vertices) + { + binaryWriter.Write((float)v.X); + binaryWriter.Write((float)v.Y); + binaryWriter.Write((float)v.Z); + } + if (isLarge) + { + var bb = boundingBox; + shapeGeometry.BoundingBox = new XbimRect3D(bb.X - origin.X, bb.Y - origin.Y, bb.Z - origin.Z, bb.SizeX, bb.SizeY, bb.SizeZ); + shapeGeometry.LocalShapeDisplacement = new XbimVector3D(origin.X, origin.Y, origin.Z); } - else //we need to calculate normals to get a better surface fit + else { - var triangulatedMesh = Triangulate(triangulation); - verticesCount = triangulatedMesh.VertexCount; - triangleCount = triangulatedMesh.TriangleCount; - - binaryWriter.Write(verticesCount); //number of vertices - binaryWriter.Write(triangleCount); //number of triangles - - // use minimum bbox as a local origin - var origin = triangulatedMesh.BoundingBox.Min; - var isLarge = IsLarge(origin.X) || IsLarge(origin.Y) || IsLarge(origin.Z); - - var vertices = isLarge ? - triangulatedMesh.Vertices.Select(v => new Vec3(v.X - origin.X, v.Y - origin.Y, v.Z - origin.Z)) : - triangulatedMesh.Vertices; - foreach (var vert in vertices) - { - binaryWriter.Write((float)vert.X); - binaryWriter.Write((float)vert.Y); - binaryWriter.Write((float)vert.Z); - } - - if (isLarge) - { - var bb = triangulatedMesh.BoundingBox; - shapeGeometry.BoundingBox = new XbimRect3D(bb.X - origin.X, bb.Y - origin.Y, bb.Z - origin.Z, bb.SizeX, bb.SizeY, bb.SizeZ); - shapeGeometry.LocalShapeDisplacement = new XbimVector3D(origin.X, origin.Y, origin.Z); - } - else - { - shapeGeometry.BoundingBox = triangulatedMesh.BoundingBox; - } + shapeGeometry.BoundingBox = boundingBox; + } - facesCount = (uint)triangulatedMesh.Faces.Count; - binaryWriter.Write((UInt32)facesCount); - foreach (var faceGroup in triangulatedMesh.Faces) + // Write faces + binaryWriter.Write(facesCount); + uint verticesOffset = 0; + foreach (var mesh in meshes) + { + foreach (var faceGroup in mesh.Faces) { var numTrianglesInFace = faceGroup.Value.Count; //we need to fix this @@ -310,69 +237,72 @@ private XbimShapeGeometry MeshPolyhedronBinary(IIfcTriangulatedFaceSet triangula triangle[0].PackedNormal.Write(binaryWriter); first = false; } - WriteIndex(binaryWriter, (uint)triangle[0].StartVertexIndex, verticesCount); - if (!planar) triangle[0].PackedNormal.Write(binaryWriter); - - WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.StartVertexIndex, verticesCount); - if (!planar) triangle[0].NextEdge.PackedNormal.Write(binaryWriter); - - WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.NextEdge.StartVertexIndex, verticesCount); - if (!planar) triangle[0].NextEdge.NextEdge.PackedNormal.Write(binaryWriter); + WriteIndex(binaryWriter, (uint)triangle[0].StartVertexIndex + verticesOffset, verticesCount); + if (!planar) + triangle[0].PackedNormal.Write(binaryWriter); + WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.StartVertexIndex + verticesOffset, verticesCount); + if (!planar) + triangle[0].NextEdge.PackedNormal.Write(binaryWriter); + WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.NextEdge.StartVertexIndex + verticesOffset, verticesCount); + if (!planar) + triangle[0].NextEdge.NextEdge.PackedNormal.Write(binaryWriter); } } + verticesOffset += mesh.VertexCount; } binaryWriter.Flush(); ((IXbimShapeGeometryData)shapeGeometry).ShapeData = ms.ToArray(); } return shapeGeometry; - - } - - private bool IsLarge(double coordinate) - { - return coordinate > _model.ModelFactors.OneMilliMeter * 999999; } - private XbimShapeGeometry MeshPolyhedronText(IEnumerable> facesList, int entityLabel, float precision) + private XbimShapeGeometry ToTextShapeGeometry(params XbimTriangulatedMesh[] meshes) { var shapeGeometry = new XbimShapeGeometry(); shapeGeometry.Format = XbimGeometryType.Polyhedron; + using (var ms = new MemoryStream(0x4000)) using (TextWriter textWriter = new StreamWriter(ms)) { - var faceLists = facesList.ToList(); - var triangulations = new List(faceLists.Count); - foreach (var faceList in faceLists) - triangulations.Add(TriangulateFaces(faceList, entityLabel, precision)); - // Write out header uint verticesCount = 0; uint triangleCount = 0; uint facesCount = 0; var boundingBox = XbimRect3D.Empty; - foreach (var triangulatedMesh in triangulations) + bool isAllVolumeDefined = true; + + foreach (var mesh in meshes) { - verticesCount += triangulatedMesh.VertexCount; - triangleCount += triangulatedMesh.TriangleCount; - facesCount += (uint)triangulatedMesh.Faces.Count; - if (boundingBox.IsEmpty) boundingBox = triangulatedMesh.BoundingBox; - else boundingBox.Union(triangulatedMesh.BoundingBox); + verticesCount += mesh.VertexCount; + triangleCount += mesh.TriangleCount; + facesCount += (uint)mesh.Faces.Count; + shapeGeometry.Volume += mesh.Volume; + isAllVolumeDefined &= mesh.Volume.HasValue; + + if (boundingBox.IsEmpty) + boundingBox = mesh.BoundingBox; + else + boundingBox.Union(mesh.BoundingBox); } + if (!isAllVolumeDefined) + // Reset, if there are non-closed bodies within the collection + shapeGeometry.Volume = null; + textWriter.WriteLine("P {0} {1} {2} {3} {4}", 2, verticesCount, facesCount, triangleCount, 0); //write out vertices and normals textWriter.Write("V"); - foreach (var p in triangulations.SelectMany(t => t.Vertices)) + foreach (var p in meshes.SelectMany(t => t.Vertices)) textWriter.Write(" {0},{1},{2}", p.X, p.Y, p.Z); textWriter.WriteLine(); //now write out the faces uint verticesOffset = 0; - foreach (var triangulatedMesh in triangulations) + foreach (var mesh in meshes) { - foreach (var faceGroup in triangulatedMesh.Faces) + foreach (var faceGroup in mesh.Faces) { textWriter.Write("T"); int currentNormal = -1; @@ -406,120 +336,47 @@ private XbimShapeGeometry MeshPolyhedronText(IEnumerable> facesL } textWriter.WriteLine(); } - verticesOffset += triangulatedMesh.VertexCount; + verticesOffset += mesh.VertexCount; } textWriter.Flush(); shapeGeometry.BoundingBox = boundingBox; ((IXbimShapeGeometryData)shapeGeometry).ShapeData = ms.ToArray(); } return shapeGeometry; + } - private XbimShapeGeometry MeshPolyhedronBinary(IEnumerable> facesList, int entityLabel, float precision) + private bool IsLarge(double coordinate) { - XbimShapeGeometry shapeGeometry = new XbimShapeGeometry(); - shapeGeometry.Format = XbimGeometryType.PolyhedronBinary; + return coordinate > _model.ModelFactors.OneMilliMeter * 999999; + } - using (var ms = new MemoryStream(0x4000)) - using (var binaryWriter = new BinaryWriter(ms)) + private XbimShapeGeometry MeshPolyhedronText(IEnumerable> facesList, int entityLabel, float precision, bool isIntentionallyClosed) + { + var meshes = new XbimTriangulatedMesh[facesList.Count()]; + int index = 0; + foreach (var faceList in facesList) { - var faceLists = facesList.ToList(); - var triangulatedMeshes = new List(faceLists.Count); - foreach (var faceList in faceLists) - { - triangulatedMeshes.Add(TriangulateFaces(faceList, entityLabel, precision)); - } - - // Write out header - uint verticesCount = 0; - uint triangleCount = 0; - uint facesCount = 0; - var boundingBox = XbimRect3D.Empty; - foreach (var triangulatedMesh in triangulatedMeshes) - { - verticesCount += triangulatedMesh.VertexCount; - triangleCount += triangulatedMesh.TriangleCount; - facesCount += (uint)triangulatedMesh.Faces.Count; - if (boundingBox.IsEmpty) - boundingBox = triangulatedMesh.BoundingBox; - else - boundingBox.Union(triangulatedMesh.BoundingBox); - } - - binaryWriter.Write((byte)1); //stream format version - // ReSharper disable once RedundantCast - binaryWriter.Write((UInt32)verticesCount); //number of vertices - binaryWriter.Write(triangleCount); //number of triangles - - // use minimum bbox as a local origin - var origin = boundingBox.Min; - var isLarge = IsLarge(origin.X) || IsLarge(origin.Y) || IsLarge(origin.Z); - - var vertices = isLarge ? - triangulatedMeshes.SelectMany(t => t.Vertices).Select(v => new Vec3(v.X - origin.X, v.Y - origin.Y, v.Z - origin.Z)): - triangulatedMeshes.SelectMany(t => t.Vertices); - foreach (var v in vertices) - { - binaryWriter.Write((float)v.X); - binaryWriter.Write((float)v.Y); - binaryWriter.Write((float)v.Z); - } - if (isLarge) - { - var bb = boundingBox; - shapeGeometry.BoundingBox = new XbimRect3D(bb.X - origin.X, bb.Y - origin.Y, bb.Z - origin.Z, bb.SizeX, bb.SizeY, bb.SizeZ); - shapeGeometry.LocalShapeDisplacement = new XbimVector3D(origin.X, origin.Y, origin.Z); - } - else - { - shapeGeometry.BoundingBox = boundingBox; - } - - - //now write out the faces - - binaryWriter.Write(facesCount); - uint verticesOffset = 0; - int invalidNormal = ushort.MaxValue; - foreach (var triangulatedMesh in triangulatedMeshes) - { - foreach (var faceGroup in triangulatedMesh.Faces) - { - var numTrianglesInFace = faceGroup.Value.Count; - //we need to fix this - var planar = invalidNormal != faceGroup.Key; //we have a mesh of faces that all have the same normals at their vertices - if (!planar) numTrianglesInFace *= -1; //set flag to say multiple normals + meshes[index] = TriangulateFaces(faceList, entityLabel, precision, isIntentionallyClosed); + index++; + } - // ReSharper disable once RedundantCast - binaryWriter.Write((Int32)numTrianglesInFace); + return ToTextShapeGeometry(meshes); + } - bool first = true; - foreach (var triangle in faceGroup.Value) - { - if (planar && first) - { - triangle[0].PackedNormal.Write(binaryWriter); - first = false; - } - WriteIndex(binaryWriter, (uint)triangle[0].StartVertexIndex + verticesOffset, verticesCount); - if (!planar) - triangle[0].PackedNormal.Write(binaryWriter); - WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.StartVertexIndex + verticesOffset, verticesCount); - if (!planar) triangle[0].NextEdge.PackedNormal.Write(binaryWriter); - WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.NextEdge.StartVertexIndex + verticesOffset, - verticesCount); - if (!planar) triangle[0].NextEdge.NextEdge.PackedNormal.Write(binaryWriter); - } - } - verticesOffset += triangulatedMesh.VertexCount; - } - binaryWriter.Flush(); - ((IXbimShapeGeometryData)shapeGeometry).ShapeData = ms.ToArray(); + private XbimShapeGeometry MeshPolyhedronBinary(IEnumerable> facesList, int entityLabel, float precision, bool isIntentionallyClosed) + { + var meshes = new XbimTriangulatedMesh[facesList.Count()]; + int index = 0; + foreach (var faceList in facesList) + { + meshes[index] = TriangulateFaces(faceList, entityLabel, precision, isIntentionallyClosed); + index++; } - return shapeGeometry; + return ToBinaryShapeGeometry(meshes); } - private XbimTriangulatedMesh TriangulateFaces(IList ifcFaces, int entityLabel, float precision) + private XbimTriangulatedMesh TriangulateFaces(IList ifcFaces, int entityLabel, float precision, bool isIntentiallyClosed) { var faceId = 0; @@ -527,19 +384,18 @@ private XbimTriangulatedMesh TriangulateFaces(IList ifcFaces, int enti var triangulatedMesh = new XbimTriangulatedMesh(faceCount, precision); foreach (var ifcFace in ifcFaces) { - - //improves performance and reduces memory load + // improves performance and reduces memory load var tess = new Tess(); var contours = new List(/*Count?*/); - foreach (var bound in ifcFace.Bounds) //build all the loops + foreach (var bound in ifcFace.Bounds) // build all the loops { var polyLoop = bound.Bound as IIfcPolyLoop; - if (polyLoop == null) continue; //skip empty faces + if (polyLoop == null) continue; // skip empty faces var polygon = polyLoop.Polygon; - if (polygon.Count < 3) continue; //skip non-polygonal faces + if (polygon.Count < 3) continue; // skip non-polygonal faces var is3D = (polygon[0].Dim == 3); var contour = new ContourVertex[polygon.Count]; @@ -566,25 +422,12 @@ private XbimTriangulatedMesh TriangulateFaces(IList ifcFaces, int enti if (contours.Any()) { - if (contours.Count == 1 && contours[0].Length == 3) //its a triangle just grab it + if (contours.Count == 1 && contours[0].Length == 3) // its a triangle just grab it { triangulatedMesh.AddTriangle(contours[0][0].Data, contours[0][1].Data, contours[0][2].Data, faceId); faceId++; } - //else - //if (contours.Count == 1 && contours[0].Length == 4) //its a quad just grab it - //{ - // foreach (var v in contours[0]) - // { - // Console.WriteLine("{0:F4} ,{1:F4}, {2:F4}", v.Position.X, v.Position.Y, v.Position.Z); - - // } - // Console.WriteLine(""); - // triangulatedMesh.AddTriangle(contours[0][0].Data, contours[0][1].Data, contours[0][3].Data, faceId); - // triangulatedMesh.AddTriangle(contours[0][3].Data, contours[0][1].Data, contours[0][2].Data, faceId); - // faceId++; - //} - else //it is multi-sided and may have holes + else // it is multi-sided and may have holes { tess.AddContours(contours); @@ -618,20 +461,73 @@ private XbimTriangulatedMesh TriangulateFaces(IList ifcFaces, int enti } } - triangulatedMesh.UnifyFaceOrientation(entityLabel); + var status = triangulatedMesh.UnifyMeshOrientation(isIntentiallyClosed, true); + ReportStatus(entityLabel, triangulatedMesh.Validate(status)); + return triangulatedMesh; } + private void ReportStatus(int entityLabel, XbimTriangulationStatus status) + { + if (XbimTriangulationStatus.NoIssues != (status & ReportMask)) + { + List issueText = new List(); + + if (XbimTriangulationStatus.IsOpenBody == (XbimTriangulationStatus.IsOpenBody & status)) + issueText.Add("has open body"); + + if (XbimTriangulationStatus.WasInvertedBody == (XbimTriangulationStatus.WasInvertedBody & status)) + issueText.Add("has been inverted to reflect a positive volume"); + + if (XbimTriangulationStatus.HasFaultyTriangles == (XbimTriangulationStatus.HasFaultyTriangles & status)) + issueText.Add("contains unconnected or faulty triangles not displayed"); + + _logger.LogWarning("Shape validation result of #{0}: {1}", entityLabel, string.Join(", ", issueText)); + } + } private XbimTriangulatedMesh Triangulate(IIfcTriangulatedFaceSet triangulation) { - var faceId = 0; - var entityLabel = triangulation.EntityLabel; + // Use a single face only + const int faceId = 0; + var precision = (float)triangulation.Model.ModelFactors.Precision; var faceCount = triangulation.CoordIndex.Count(); var triangulatedMesh = new XbimTriangulatedMesh(faceCount, precision); - //add all the vertices in to the mesh + // Add all the vertices in to the mesh var vertices = new List(triangulation.Coordinates.CoordList.Count()); + + // If normals are provided + List> nTriplets = triangulation.Normals.Select(n => n.AsTriplet()).ToList(); + List> normalIndex = new List>(); + bool hasPnIndex = triangulation.PnIndex.Any(); + if (hasPnIndex) + { + if (triangulation.PnIndex is List>) //the list of triplets has not been flattened + { + foreach (var item in triangulation.PnIndex as List>) + { + normalIndex.Add(item); + } + } + else + { + for (int i = 0; i < triangulation.PnIndex.Count; i += 3) + { + var item = new List() { triangulation.PnIndex[i], triangulation.PnIndex[i + 1], triangulation.PnIndex[i + 2] }; + normalIndex.Add(item); + } + } + } + else + { + foreach (var item in triangulation.CoordIndex) + { + normalIndex.Add(item); + } + } + + // Add coordinates foreach (var coord in triangulation.Coordinates.CoordList) { var tpl = coord.AsTriplet(); @@ -639,16 +535,41 @@ private XbimTriangulatedMesh Triangulate(IIfcTriangulatedFaceSet triangulation) var idx = triangulatedMesh.AddVertex(v); vertices.Add(idx); } + + // Create triangles + int tIndex = 0; foreach (var triangleFace in triangulation.CoordIndex) { var tpl = triangleFace.AsTriplet(); - triangulatedMesh.AddTriangle(vertices[(int)tpl.A - 1], vertices[(int)tpl.B - 1], vertices[(int)tpl.C - 1], faceId); + var edges = triangulatedMesh.AddTriangle(vertices[(int)tpl.A - 1], vertices[(int)tpl.B - 1], vertices[(int)tpl.C - 1], faceId); + + // If there are normals given + if (nTriplets.Count > 0) + { + var normalsIndexTpl = normalIndex[tIndex].AsTriplet(); + // Use given normals + edges[0].Normal = new Vec3(nTriplets[(int)normalsIndexTpl.A - 1]); + edges[1].Normal = new Vec3(nTriplets[(int)normalsIndexTpl.B - 1]); + edges[2].Normal = new Vec3(nTriplets[(int)normalsIndexTpl.C - 1]); + } + + tIndex++; } - triangulatedMesh.UnifyFaceOrientation(entityLabel); + + // If not marked as closed, don't go for orientation alignment + XbimTriangulationStatus? status = null; + if (triangulation.Closed ?? false) + // It makes no sense to align a mesh which isn't meant to have an orientation + status = triangulatedMesh.UnifyMeshOrientation(true, nTriplets.Count == 0); + else + // So only balance normals (and if not provided already, compute normals) + triangulatedMesh.BalanceNormals(nTriplets.Count == 0); + + ReportStatus(triangulation.EntityLabel, triangulatedMesh.Validate(status)); + return triangulatedMesh; } - private void WriteIndex(BinaryWriter bw, UInt32 index, UInt32 maxInt) { if (maxInt <= 0xFF) @@ -658,7 +579,5 @@ private void WriteIndex(BinaryWriter bw, UInt32 index, UInt32 maxInt) else bw.Write(index); } - - } } \ No newline at end of file diff --git a/Xbim.Tessellator/XbimTriangulatedMesh.cs b/Xbim.Tessellator/XbimTriangulatedMesh.cs index 80647f051..ecb4fb71a 100644 --- a/Xbim.Tessellator/XbimTriangulatedMesh.cs +++ b/Xbim.Tessellator/XbimTriangulatedMesh.cs @@ -1,16 +1,25 @@ -using System; +using Microsoft.Extensions.Logging; + +using System; using System.Collections.Generic; using System.Linq; + using Xbim.Common.Geometry; -using Xbim.Tessellator; namespace Xbim.Tessellator { - + [Flags] + public enum XbimTriangulationStatus + { + NoIssues = 0, + IsOpenBody = 1, + WasInvertedBody = 2, + HasFaultyTriangles = 4 + } public class XbimTriangulatedMesh { - public struct XbimTriangle + public struct XbimTriangle { readonly XbimContourVertexCollection _vertices; readonly XbimTriangleEdge[] _edges; @@ -28,8 +37,7 @@ public bool IsEmpty public XbimVector3D Normal { - get - { + get { var p1 = _vertices[_edges[0].StartVertexIndex].Position; var p2 = _vertices[_edges[0].NextEdge.StartVertexIndex].Position; var p3 = _vertices[_edges[0].NextEdge.NextEdge.StartVertexIndex].Position; @@ -37,14 +45,13 @@ public XbimVector3D Normal var b = new XbimPoint3D(p2.X, p2.Y, p2.Z); var c = new XbimPoint3D(p3.X, p3.Y, p3.Z); var cv = XbimVector3D.CrossProduct(b - a, c - a); - cv=cv.Normalized(); + cv = cv.Normalized(); return cv; - } + } } public XbimPackedNormal PackedNormal { - get - { + get { return new XbimPackedNormal(Normal); } } @@ -54,42 +61,44 @@ public XbimPackedNormal PackedNormal private readonly List _faultyTriangles = new List(); private Dictionary> _faces; private readonly XbimContourVertexCollection _vertices; - + double _minX = double.PositiveInfinity; double _minY = double.PositiveInfinity; double _minZ = double.PositiveInfinity; double _maxX = double.NegativeInfinity; double _maxY = double.NegativeInfinity; double _maxZ = double.NegativeInfinity; - + + public double? Volume { get; private set; } + public XbimTriangulatedMesh(int faceCount, float precision) { var edgeCount = (int)(faceCount * 1.5); _lookupList = new Dictionary(edgeCount); _faces = new Dictionary>(faceCount); _vertices = new XbimContourVertexCollection(precision); - + } public uint TriangleCount { - get - { + get { uint triangleCount = 0; foreach (var face in _faces.Values) triangleCount += (uint)face.Count; return triangleCount; } } + public IEnumerable Triangles { - get - { - return from edgeListList in _faces.Values - from edges in edgeListList + get { + return from edgeListList in _faces.Values + from edges in edgeListList select new XbimTriangle(edges, _vertices); } } + public List FaultyTriangles { get { return _faultyTriangles; } @@ -104,27 +113,26 @@ public XbimVector3D TriangleNormal(XbimTriangleEdge edge) { var p1 = _vertices[edge.StartVertexIndex].Position; var p2 = _vertices[edge.NextEdge.StartVertexIndex].Position; - var p3 = _vertices[edge.NextEdge.NextEdge.StartVertexIndex].Position; - var a = new XbimPoint3D(p1.X,p1.Y,p1.Z); - var b = new XbimPoint3D(p2.X,p2.Y,p2.Z); - var c = new XbimPoint3D(p3.X,p3.Y,p3.Z); - var cv = XbimVector3D.CrossProduct(b - a, c - a ); - cv=cv.Normalized(); + var p3 = _vertices[edge.NextEdge.NextEdge.StartVertexIndex].Position; + var a = new XbimPoint3D(p1.X, p1.Y, p1.Z); + var b = new XbimPoint3D(p2.X, p2.Y, p2.Z); + var c = new XbimPoint3D(p3.X, p3.Y, p3.Z); + var cv = XbimVector3D.CrossProduct(b - a, c - a); + cv = cv.Normalized(); return cv; } public Dictionary> Faces { - get - { + get { return _faces; } } - + private bool AddEdge(XbimTriangleEdge edge) { - + var key = edge.Key; if (!_lookupList.ContainsKey(key)) { @@ -141,97 +149,145 @@ private bool AddEdge(XbimTriangleEdge edge) edges[0].AdjacentEdge = edge; edge.AdjacentEdge = edges[0]; } - + return true; } - delegate bool IsMaxDelegate(ContourVertex p); /// - /// Orientates edges to orientate in a uniform direction + /// Computes the approximate volume of the shape /// - /// - public void UnifyFaceOrientation(int entityLabel) + /// If less than 0, the mesh is in reverse orientation + private double ComputeTetrahedralVolume() { - XbimTriangleEdge[] extremeTriangle = FindExtremeTriangle(); - if (extremeTriangle == null) return; - if (!IsFacingOutward(extremeTriangle[0])) - extremeTriangle[0].Reverse(); - var triangles = new List - { - extremeTriangle - }; - extremeTriangle[0].Freeze(); + double total = 0; + Vec3 centroid = new Vec3(Centroid); - do + foreach (var triangles in _faces.Values) { - triangles = UnifyConnectedTriangles(triangles); - } while (triangles.Any()); + total += triangles.Select(t => + { + var e = t[0]; + + var p01 = _vertices[e.StartVertexIndex].Position; + Vec3 p1; + Vec3.Sub(ref p01, ref centroid, out p1); // Avoid big number crunching + + var p02 = _vertices[e.NextEdge.StartVertexIndex].Position; + Vec3 p2; + Vec3.Sub(ref p02, ref centroid, out p2); // Avoid big number crunching + + var p03 = _vertices[e.NextEdge.NextEdge.StartVertexIndex].Position; + Vec3 p3; + Vec3.Sub(ref p03, ref centroid, out p3); // Avoid big number crunching + + Vec3 area; + Vec3.Cross(ref p2, ref p3, out area); + double volume; + Vec3.Dot(ref p1, ref area, out volume); + return volume / 6.0; + }).Sum(); + } - //doing the extreme edge first should do all connected + return total; + } - foreach (var xbimEdges in _faces.Values.SelectMany(el => el).Where(e => !e[0].Frozen)) //check any rogue elements - { - if (!IsFacingOutward(xbimEdges[0])) - xbimEdges[0].Reverse(); - triangles = new List { new[] { xbimEdges[0], xbimEdges[0].NextEdge, xbimEdges[0].NextEdge.NextEdge } }; - xbimEdges[0].Freeze(); - do - { - triangles = UnifyConnectedTriangles(triangles); - } while (triangles.Any()); + /// + /// Performs a topological validation if not done yet. + /// + /// Some initially known status + /// An enhanced status + public XbimTriangulationStatus Validate(XbimTriangulationStatus? preStatus = null) + { + if (preStatus == null) + preStatus = IsTopologicallyClosed ? XbimTriangulationStatus.NoIssues : XbimTriangulationStatus.IsOpenBody; - } - BalanceNormals(); + if (FaultyTriangles.Count > 0) + preStatus |= XbimTriangulationStatus.HasFaultyTriangles; + + return preStatus.Value; } - private XbimTriangleEdge[] FindExtremeTriangle() + /// + /// Orientates edges to orientate in a uniform direction + /// + /// Whether the mesh wraps a closed body by intention + /// Compute normals + /// A validation status + public XbimTriangulationStatus UnifyMeshOrientation(bool isIntentiallyClosed, bool computeNormals) { - //find the biggest - var sizeX = _maxX - _minX; - var sizeY = _maxY - _minY; - var sizeZ = _maxZ - _minZ; + // Do first run for aligning orientation in either direction + var candidate = _faces.Values.FirstOrDefault().ToList(); + do + { + candidate = UnifyConnectedTriangles(candidate); + } while (candidate.Any()); - IsMaxDelegate isMax; - if (sizeX >= sizeY && sizeX >= sizeZ) isMax = p => Math.Abs(p.Position.X - _maxX) < 1e-9; - else if (sizeY >= sizeX && sizeY >= sizeZ) isMax = p => Math.Abs(p.Position.Y - _maxY) < 1e-9; - else isMax = p => Math.Abs(p.Position.Z - _maxZ) < 1e-9; - - foreach (var face in _faces.Values) + foreach (var edge in _faces.Values.SelectMany(f => f.Select(e => e.First())).Where(e => !e.Frozen)) //check any rogue elements { - //find the extreme triangle - foreach (var t in face) + var isolated = new List { + new[] { edge, edge.NextEdge, edge.NextEdge.NextEdge } + }; + edge.Freeze(); + do { - foreach (var edge in t) + isolated = UnifyConnectedTriangles(isolated); + } while (isolated.Any()); + } + + XbimTriangulationStatus status = IsTopologicallyClosed ? XbimTriangulationStatus.NoIssues : XbimTriangulationStatus.IsOpenBody; + // If intentially closed overrides detected topological gaps and attempts to compute a volume + if (isIntentiallyClosed || XbimTriangulationStatus.NoIssues == status) + { + // Compute volume and reverse mesh if less than 0 + Volume = ComputeTetrahedralVolume(); + if (Volume < 0) + { + foreach (var t in _faces.Values.SelectMany(f => f.Select(e => e.First()))) { - if (isMax(_vertices[edge.StartVertexIndex]) - && !Vec3.Colinear(_vertices[edge.StartVertexIndex].Position, _vertices[edge.NextEdge.StartVertexIndex].Position, _vertices[edge.NextEdge.NextEdge.StartVertexIndex].Position)) - { - return t; - } + t.Unfreeze(); + t.Reverse(); } - } + Volume = -Volume; + // Force recalculation of normals since the orientation has been changed + computeNormals = true; + status |= XbimTriangulationStatus.WasInvertedBody; + } } - return null; + BalanceNormals(computeNormals); + return status; } - public void BalanceNormals() + /// + /// Whether the mesh is topologically closed. True if each edge has an adjacent edge + /// + public bool IsTopologicallyClosed + { + get => !_faces.Values.Any(f => f.Any(t => t.Any(e => e.AdjacentEdge == null))); + } + + /// + /// Run smoothing of face normals with optionally recomputing normals + /// + /// Forces a complete update + /// Maximum deflection angle between two faces having a "smooth" seam + public void BalanceNormals(bool forceNormalsUpdate, double minAngle = Math.PI / 5) { - const double minAngle = Math.PI / 5; - //set up the base normals - foreach (var faceGroup in Faces) + if (forceNormalsUpdate) { - foreach (var triangle in faceGroup.Value) + foreach (var faceGroup in Faces) { - ComputeTriangleNormal(triangle); + foreach (var triangle in faceGroup.Value) + { + ComputeTriangleNormal(triangle); + } } } - var edgesAtVertex = _faces.Values.SelectMany(el => el).SelectMany(e => e).Where(e => e != null).GroupBy(k => k.StartVertexIndex); foreach (var edges in edgesAtVertex) - { + { //create a set of faces to divide the point into a set of connected faces var faceSet = new List>();//the first face set at this point @@ -295,8 +351,8 @@ public void BalanceNormals() if (visited.Contains(nextConnectedEdge.EdgeId)) break; //we are looping or at the start //if the edge is sharp start a new face - var angle = nextConnectedEdge.Angle; - if ( angle > minAngle && nextConnectedEdge.Normal.IsValid) + var angle = nextConnectedEdge.Angle; + if (angle > minAngle && nextConnectedEdge.Normal.IsValid) { face = new List(); faceSet.Add(face); @@ -306,7 +362,7 @@ public void BalanceNormals() } while (nextConnectedEdge != null); //move on to next face - } + } //we have our smoothing groups foreach (var vertexEdges in faceSet.Where(f => f.Count > 1)) @@ -317,25 +373,21 @@ public void BalanceNormals() if (edge.Normal.IsValid) { Vec3.AddTo(ref vertexNormal, ref edge.Normal); - } + } } Vec3.Normalize(ref vertexNormal); foreach (var edge in vertexEdges) - edge.Normal = vertexNormal; + edge.Normal = vertexNormal; } } - + //now regroup faces - _faces = _faces.Values.SelectMany(v => v).GroupBy(t=>ComputeTrianglePackedNormalInt(t)).ToDictionary(k=>k.Key,v=>v.ToList()); - + _faces = _faces.Values.SelectMany(v => v).GroupBy(t => ComputeTrianglePackedNormalInt(t)).ToDictionary(k => k.Key, v => v.ToList()); } - - - private List UnifyConnectedTriangles(List triangles) { var nextCandidates = new List(); @@ -344,32 +396,31 @@ private List UnifyConnectedTriangles(List UnifyConnectedTriangles(List /// Adds the triangle using the three ints as inidices into the vertext collection /// - /// - /// - /// - /// - public void AddTriangle(int p1, int p2, int p3, int faceId) + /// First index + /// Second index + /// Third index + /// The face ID + public XbimTriangleEdge[] AddTriangle(int p1, int p2, int p3, int faceId) { - + var e1 = new XbimTriangleEdge(p1); var e2 = new XbimTriangleEdge(p2); var e3 = new XbimTriangleEdge(p3); e1.NextEdge = e2; e2.NextEdge = e3; e3.NextEdge = e1; - + var edgeList = new[] { e1, e2, e3 }; bool faulty = !AddEdge(e1); @@ -406,7 +457,7 @@ public void AddTriangle(int p1, int p2, int p3, int faceId) RemoveEdge(e2); faulty = true; } - if (faulty) + if (faulty) FaultyTriangles.Add(edgeList); List triangleList; if (!_faces.TryGetValue(faceId, out triangleList)) @@ -415,7 +466,7 @@ public void AddTriangle(int p1, int p2, int p3, int faceId) _faces.Add(faceId, triangleList); } triangleList.Add(edgeList); - + return edgeList; } /// @@ -449,7 +500,7 @@ public bool ComputeTriangleNormal(XbimTriangleEdge[] edges) (by - ay) * (cz - az) - (bz - az) * (cy - ay), (bz - az) * (cx - ax) - (bx - ax) * (cz - az), (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) - ); + ); if (Vec3.Normalize(ref v)) { edges[0].Normal = v; @@ -503,7 +554,7 @@ public int AddVertex(Vec3 v) public void AddVertex(Vec3 v, ref ContourVertex contourVertex) { - if (_vertices.Contains(v)) + if (_vertices.Contains(v)) contourVertex = _vertices[v]; else { @@ -544,126 +595,107 @@ public XbimVector3D PointingOutwardFrom(XbimPoint3D point3D) v = v.Normalized(); return v; } + } - /// - /// Returns true if the triangle that contains the edge is facing away from the centroid of the mesh - /// - /// - public bool IsFacingOutward(XbimTriangleEdge edge) + /// + /// Edge class for triangular meshes only + /// + public class XbimTriangleEdge + { + public int StartVertexIndex; + public XbimTriangleEdge NextEdge; + public XbimTriangleEdge AdjacentEdge; + public Vec3 Normal; + private bool _frozen; + public int EndVertexIndex { get { return NextEdge.StartVertexIndex; } } + + public XbimTriangleEdge(int p1) { - //find the centroid of the triangle - var p1 = _vertices[edge.StartVertexIndex].Position; - var p2 = _vertices[edge.NextEdge.StartVertexIndex].Position; - var p3 = _vertices[edge.NextEdge.NextEdge.StartVertexIndex].Position; - var centroid = new XbimPoint3D((p1.X + p2.X + p3.X) / 3, (p1.Y + p2.Y + p3.Y) / 3, (p1.Z + p2.Z + p3.Z) / 3); - var normal = TriangleNormal(edge); - var vecOut = PointingOutwardFrom(centroid); - var dot = vecOut.DotProduct(normal); - return dot > 0; + StartVertexIndex = p1; } - } - -} - -/// -/// Edge class for triangular meshes only -/// -public class XbimTriangleEdge -{ - public int StartVertexIndex; - public XbimTriangleEdge NextEdge; - public XbimTriangleEdge AdjacentEdge; - public Vec3 Normal; - private bool _frozen; - public int EndVertexIndex { get { return NextEdge.StartVertexIndex; } } - public XbimTriangleEdge(int p1) - { - StartVertexIndex = p1; - } + public bool Frozen + { + get { return _frozen; } + } - public bool Frozen - { - get { return _frozen; } - - } + internal void Unfreeze() + { + _frozen = false; + } - /// - /// Returns the angle of this edge, 0 if the edge has no adjacent edge or the the normals are invalid, returns -1 if invalid - /// - public double Angle - { - get + /// + /// Returns the angle of this edge, 0 if the edge has no adjacent edge or the the normals are invalid, returns -1 if invalid + /// + public double Angle { - - if (AdjacentEdge!=null && Normal.IsValid && AdjacentEdge.NextEdge.Normal.IsValid) - return Vec3.Angle(ref Normal, ref AdjacentEdge.NextEdge.Normal); - return 0; - } - - } + get { + if (AdjacentEdge != null && Normal.IsValid && AdjacentEdge.NextEdge.Normal.IsValid) + return Vec3.Angle(ref Normal, ref AdjacentEdge.NextEdge.Normal); + return 0; + } - public void Freeze() - { - _frozen = true; - NextEdge._frozen=true; - NextEdge.NextEdge._frozen = true; - } + } + public void Freeze() + { + _frozen = true; + NextEdge._frozen = true; + NextEdge.NextEdge._frozen = true; + } - public void Reverse() - { - if (!_frozen) - { - var p1 = StartVertexIndex; - var p2 = NextEdge.StartVertexIndex; - var p3 = NextEdge.NextEdge.StartVertexIndex; - StartVertexIndex = p2; - NextEdge.StartVertexIndex = p3; - NextEdge.NextEdge.StartVertexIndex = p1; - var prevEdge = NextEdge.NextEdge; - prevEdge.NextEdge = NextEdge; - NextEdge.NextEdge = this; - NextEdge = prevEdge; - } - - } + public void Reverse() + { + if (!_frozen) + { + var p1 = StartVertexIndex; + var p2 = NextEdge.StartVertexIndex; + var p3 = NextEdge.NextEdge.StartVertexIndex; + StartVertexIndex = p2; + NextEdge.StartVertexIndex = p3; + NextEdge.NextEdge.StartVertexIndex = p1; + var prevEdge = NextEdge.NextEdge; + prevEdge.NextEdge = NextEdge; + NextEdge.NextEdge = this; + NextEdge = prevEdge; + // Invert normal + Vec3.Neg(ref Normal); + } + } - /// - /// The ID of the edge, unique for all edges between vertices - /// - public long EdgeId - { - get + /// + /// The ID of the edge, unique for all edges between vertices + /// + public long EdgeId { - long a = StartVertexIndex; - a <<= 32; - return (a | (uint)EndVertexIndex); + get { + long a = StartVertexIndex; + a <<= 32; + return (a | (uint)EndVertexIndex); + } } - } - public bool IsEmpty { get { return EdgeId == 0; } } + public bool IsEmpty { get { return EdgeId == 0; } } - /// - /// The key for the edge, this is the same for both directions of an edge - /// - public long Key - { - get + /// + /// The key for the edge, this is the same for both directions of an edge + /// + public long Key { - long left = Math.Max(StartVertexIndex, EndVertexIndex); - left <<= 32; - long right = Math.Min(StartVertexIndex, EndVertexIndex); - return (left | right); + get { + long left = Math.Max(StartVertexIndex, EndVertexIndex); + left <<= 32; + long right = Math.Min(StartVertexIndex, EndVertexIndex); + return (left | right); + } } - } - public XbimPackedNormal PackedNormal - { - get + public XbimPackedNormal PackedNormal { - return new XbimPackedNormal(Normal.X,Normal.Y,Normal.Z); + get { + return new XbimPackedNormal(Normal.X, Normal.Y, Normal.Z); + } } } -} +} \ No newline at end of file From 06aaa07e8cb3d60f63af25d8da37abc62f1a0f6d Mon Sep 17 00:00:00 2001 From: Bernold Kraft Date: Thu, 19 Dec 2019 14:57:25 +0100 Subject: [PATCH 4/5] Fixing query of empty faces list for triangulated surfaces In case of an empty IfcTriangulatedFaceSet --- Xbim.Tessellator/XbimTriangulatedMesh.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Xbim.Tessellator/XbimTriangulatedMesh.cs b/Xbim.Tessellator/XbimTriangulatedMesh.cs index ecb4fb71a..0596409ac 100644 --- a/Xbim.Tessellator/XbimTriangulatedMesh.cs +++ b/Xbim.Tessellator/XbimTriangulatedMesh.cs @@ -14,7 +14,8 @@ public enum XbimTriangulationStatus NoIssues = 0, IsOpenBody = 1, WasInvertedBody = 2, - HasFaultyTriangles = 4 + HasFaultyTriangles = 4, + IsEmptyBody = 9 } public class XbimTriangulatedMesh @@ -216,7 +217,10 @@ public XbimTriangulationStatus Validate(XbimTriangulationStatus? preStatus = nul public XbimTriangulationStatus UnifyMeshOrientation(bool isIntentiallyClosed, bool computeNormals) { // Do first run for aligning orientation in either direction - var candidate = _faces.Values.FirstOrDefault().ToList(); + var candidate = _faces.Values.FirstOrDefault(); + if (null == candidate) + return XbimTriangulationStatus.IsEmptyBody; + do { candidate = UnifyConnectedTriangles(candidate); From 08d62ffe0194ada12261b6ca38b13c72b3d5db29 Mon Sep 17 00:00:00 2001 From: Bernold Kraft Date: Mon, 30 Nov 2020 09:52:33 +0100 Subject: [PATCH 5/5] Using system flag enum evaluation --- Xbim.Tessellator/XbimTessellator.cs | 7 +++---- Xbim.Tessellator/XbimTriangulatedMesh.cs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Xbim.Tessellator/XbimTessellator.cs b/Xbim.Tessellator/XbimTessellator.cs index 55390e988..09a095315 100644 --- a/Xbim.Tessellator/XbimTessellator.cs +++ b/Xbim.Tessellator/XbimTessellator.cs @@ -8,7 +8,6 @@ using Xbim.Common.XbimExtensions; using Xbim.Ifc4.MeasureResource; using Microsoft.Extensions.Logging; -using System.Text; namespace Xbim.Tessellator { @@ -473,13 +472,13 @@ private void ReportStatus(int entityLabel, XbimTriangulationStatus status) { List issueText = new List(); - if (XbimTriangulationStatus.IsOpenBody == (XbimTriangulationStatus.IsOpenBody & status)) + if (status.HasFlag(XbimTriangulationStatus.IsOpenBody)) issueText.Add("has open body"); - if (XbimTriangulationStatus.WasInvertedBody == (XbimTriangulationStatus.WasInvertedBody & status)) + if (status.HasFlag(XbimTriangulationStatus.WasInvertedBody)) issueText.Add("has been inverted to reflect a positive volume"); - if (XbimTriangulationStatus.HasFaultyTriangles == (XbimTriangulationStatus.HasFaultyTriangles & status)) + if (status.HasFlag(XbimTriangulationStatus.HasFaultyOrUnconnectedTriangles)) issueText.Add("contains unconnected or faulty triangles not displayed"); _logger.LogWarning("Shape validation result of #{0}: {1}", entityLabel, string.Join(", ", issueText)); diff --git a/Xbim.Tessellator/XbimTriangulatedMesh.cs b/Xbim.Tessellator/XbimTriangulatedMesh.cs index 0596409ac..a667f0073 100644 --- a/Xbim.Tessellator/XbimTriangulatedMesh.cs +++ b/Xbim.Tessellator/XbimTriangulatedMesh.cs @@ -14,7 +14,7 @@ public enum XbimTriangulationStatus NoIssues = 0, IsOpenBody = 1, WasInvertedBody = 2, - HasFaultyTriangles = 4, + HasFaultyOrUnconnectedTriangles = 4, IsEmptyBody = 9 } @@ -203,7 +203,7 @@ public XbimTriangulationStatus Validate(XbimTriangulationStatus? preStatus = nul preStatus = IsTopologicallyClosed ? XbimTriangulationStatus.NoIssues : XbimTriangulationStatus.IsOpenBody; if (FaultyTriangles.Count > 0) - preStatus |= XbimTriangulationStatus.HasFaultyTriangles; + preStatus |= XbimTriangulationStatus.HasFaultyOrUnconnectedTriangles; return preStatus.Value; }