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/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; }
}
}
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 2526112ab..e4d64fce4 100644
--- a/Xbim.Common/Geometry/XbimShapeGeometry.cs
+++ b/Xbim.Common/Geometry/XbimShapeGeometry.cs
@@ -170,6 +170,10 @@ public IEnumerable Faces
///
byte[] _shapeData;
+ ///
+ /// The volume if available
+ ///
+ double? _volume;
///
@@ -410,7 +414,15 @@ public override string ToString()
IVector3D IXbimShapeGeometryData.LocalShapeDisplacement => LocalShapeDisplacement;
public XbimVector3D? LocalShapeDisplacement { get; set; }
-
+ public double? Volume
+ {
+ get {
+ return _volume;
+ }
+ set {
+ _volume = value;
+ }
+ }
}
public class WexBimMeshFace
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 3adfa2ded..251d06fea 100644
--- a/Xbim.Tessellator/XbimTessellator.cs
+++ b/Xbim.Tessellator/XbimTessellator.cs
@@ -7,29 +7,29 @@
using Xbim.Ifc4.Interfaces;
using Xbim.Common.XbimExtensions;
using Xbim.Ifc4.MeasureResource;
-using Xbim.Common.Collections;
+using Microsoft.Extensions.Logging;
namespace Xbim.Tessellator
{
-
public class XbimTessellator
{
private readonly IModel _model;
private readonly XbimGeometryType _geometryType;
private readonly bool _reduceLargeCoordinates;
+ private readonly ILogger _logger;
- public XbimTessellator(IModel model, XbimGeometryType geometryType, bool reduceLargeCoordinates = true)
+ public XbimTessellator(IModel model, XbimGeometryType geometryType, bool reduceLargeCoordinates = true, ILogger logger = null)
{
_model = model;
_geometryType = geometryType;
_reduceLargeCoordinates = reduceLargeCoordinates;
+ _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
@@ -53,7 +53,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;
@@ -63,17 +63,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);
@@ -89,27 +86,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);
}
@@ -135,167 +132,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));
- }
-
-
-
+ boundingBox.Union(mesh.BoundingBox);
+ }
- int triangleIndex = 0;
-
- 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
@@ -313,72 +238,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)
- {
- if (!_reduceLargeCoordinates)
- return false;
-
- 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;
@@ -412,120 +337,50 @@ 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;
-
- using (var ms = new MemoryStream(0x4000))
- using (var binaryWriter = new BinaryWriter(ms))
- {
- 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;
- }
-
+ if (!_reduceLargeCoordinates)
+ return false;
- //now write out the faces
+ return coordinate > _model.ModelFactors.OneMilliMeter * 999999;
+ }
- 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
+ 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)
+ {
+ 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;
@@ -533,19 +388,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];
@@ -572,25 +426,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);
@@ -624,20 +465,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 (status.HasFlag(XbimTriangulationStatus.IsOpenBody))
+ issueText.Add("has open body");
+
+ if (status.HasFlag(XbimTriangulationStatus.WasInvertedBody))
+ issueText.Add("has been inverted to reflect a positive volume");
+
+ 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));
+ }
+ }
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();
@@ -645,16 +539,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)
@@ -664,7 +583,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..a667f0073 100644
--- a/Xbim.Tessellator/XbimTriangulatedMesh.cs
+++ b/Xbim.Tessellator/XbimTriangulatedMesh.cs
@@ -1,16 +1,26 @@
-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,
+ HasFaultyOrUnconnectedTriangles = 4,
+ IsEmptyBody = 9
+ }
public class XbimTriangulatedMesh
{
- public struct XbimTriangle
+ public struct XbimTriangle
{
readonly XbimContourVertexCollection _vertices;
readonly XbimTriangleEdge[] _edges;
@@ -28,8 +38,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 +46,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 +62,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 +114,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 +150,148 @@ 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.HasFaultyOrUnconnectedTriangles;
+
+ 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();
+ if (null == candidate)
+ return XbimTriangulationStatus.IsEmptyBody;
+
+ 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
+ {
+ 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 edge in t)
+ 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 +355,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 +366,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 +377,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 +400,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 +461,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 +470,7 @@ public void AddTriangle(int p1, int p2, int p3, int faceId)
_faces.Add(faceId, triangleList);
}
triangleList.Add(edgeList);
-
+ return edgeList;
}
///
@@ -449,7 +504,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 +558,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 +599,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