-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance problem with tessellation #531
Comments
Hi @christianstroh. You have gone to a great depth with this already. Any suggestion for the actual optimisation? We find the triangulation in IFC files is often not very good. This code attempts to improve it by removing redundant edges. Do you have any other suggestions? |
Hi. I haven't done this myself yet. In my free time I could spend some time looking at what state of the art algorithms there are. |
I think it would be easier if you share the test harness you've built (and affected model)? It's tricky to see the hotspot from the screenshots. Not sure how much it will affect the performance but it looks like you're in Debug mode so won't be optimised. On a cursory inspection I fail to see how |
The diagsession file is too large to be an attachment (35 MB). Can I send it to you by email? I loaded the IFC file in release mode, which unfortunately isn't any faster. The hot spot appears to be the call to XbimTriangulatedMesh.AddTriangle() within XbimTessellator.TriangulateFaces() - 62%. The very performant state of the art algorithm is said to be the 'Delaunay triangulation'. This also works in three dimensions. |
This optimized code loads the file approximately 25% faster (128 instead of 171 seconds). I use Parallel.ForEach(), lock() - since XbimTriangulatedMesh doesn't seem to be thread-safe and Interlocked.Increment() for incrementing the integer. private readonly object lockObject = new object();
private XbimTriangulatedMesh TriangulateFaces(IList<IIfcFace> ifcFaces, int entityLabel, float precision)
{
int faceId = 0;
var faceCount = ifcFaces.Count;
var triangulatedMesh = new XbimTriangulatedMesh(faceCount, precision);
Parallel.ForEach(ifcFaces, ifcFace =>
{
//improves performance and reduces memory load
var tess = new Tess();
var contours = new List<ContourVertex[]>(/*Count?*/);
foreach (var bound in ifcFace.Bounds) //build all the loops
{
var polyLoop = bound.Bound as IIfcPolyLoop;
if (polyLoop == null) continue; //skip empty faces
var polygon = polyLoop.Polygon;
if (polygon.Count < 3) continue; //skip non-polygonal faces
var is3D = (polygon[0].Dim == 3);
var contour = new ContourVertex[polygon.Count];
if (bound.Orientation)
{
for (var j = 0; j < polygon.Count; j++)
{
var v = new Vec3(polygon[j].X, polygon[j].Y, is3D ? polygon[j].Z : 0);
lock (lockObject)
{
triangulatedMesh.AddVertex(v, ref contour[j]);
}
}
}
else
{
var i = 0;
for (var j = polygon.Count - 1; j >= 0; j--)
{
var v = new Vec3(polygon[j].X, polygon[j].Y, is3D ? polygon[j].Z : 0);
lock (lockObject)
{
triangulatedMesh.AddVertex(v, ref contour[i]);
}
i++;
}
}
contours.Add(contour);
}
if (contours.Any())
{
if (contours.Count == 1 && contours[0].Length == 3) //its a triangle just grab it
{
lock (lockObject)
{
triangulatedMesh.AddTriangle(contours[0][0].Data, contours[0][1].Data, contours[0][2].Data, faceId);
}
Interlocked.Increment(ref faceId);
//faceId++;
}
else //it is multi-sided and may have holes
{
tess.AddContours(contours);
tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3);
var faceIndices = new List<int>(tess.ElementCount * 3);
var elements = tess.Elements;
var contourVerts = tess.Vertices;
for (var j = 0; j < tess.ElementCount * 3; j++)
{
var idx = contourVerts[elements[j]].Data;
if (idx < 0) //WE HAVE INSERTED A POINT
{
//add it to the mesh
lock (lockObject)
{
triangulatedMesh.AddVertex(contourVerts[elements[j]].Position, ref contourVerts[elements[j]]);
}
}
faceIndices.Add(contourVerts[elements[j]].Data);
}
if (faceIndices.Count > 0)
{
for (var j = 0; j < tess.ElementCount; j++)
{
var p1 = faceIndices[j * 3];
var p2 = faceIndices[j * 3 + 1];
var p3 = faceIndices[j * 3 + 2];
lock (lockObject)
{
triangulatedMesh.AddTriangle(p1, p2, p3, faceId);
}
}
Interlocked.Increment(ref faceId);
//faceId++;
}
}
}
});
triangulatedMesh.UnifyFaceOrientation(entityLabel);
return triangulatedMesh;
} |
It feels like adding parallelism is probably dodging the issue here. And as you see you're only getting 25% improvement. Fundamentally I'd suggest it's an algorithm problem. I did a bit of analysis with some small but relatively complex test models using BenchmarkDotNet. A majority of the time is down in the area Martin hinted at:
So it seems these 'fixup' routines are adding 50%+ to the execution time on complex triangulated meshes, and also generating a lot of allocations. For now I've added some optional switches to skip them. We need to review their effectiveness. Sadly there's not a lot of test coverage to help with refactoring @christianstroh rather than share the session are you able to provide a model and we can do some further investigation? Free free to send via a fileshare app or OneDrive/GoogleDrive. |
I'll give feedback about the model. |
Unfortunately, our customer does not want to pass the ifc file on to third parties. However, I noticed in the viewer that the model only has a single geometry, instead of individual objects that could be selected. Maybe the tessellation doesn't handle this well. |
No problem. If it helps we can do an NDA -just email me (See my profile) But you can take a look yourself: If you build it locally you can add your client model in the 'test class I'd probably reduce To run the benchmarking just run the RunBenchmark.BAT script |
Please excuse the late reply, I was on vacation for several weeks. I'm afraid our customer won't go along with an NDA either. The model consists of a single geometry with many pipes and valves. Maybe the tessellation of these curves is too detailed. I'll try your branch. |
I had to stop the program after several hours. Even with Iterations set to 1. We are trying to generate an IFC file with such geometries. We may be able to reproduce the problem with this. |
If it's taking hours for a single iteration, it sounds like one the following
If you're able to enable verbose logging it may shed light on what's going on and which element(s) are causing the performance issues Are you able to extract the problem element using the approach I mentioned above? (The IFC Stripping feature in XbimXplorer) That will create an IFC with just a single element in it. In 95% of circumstance these complex meshes have come from an object library not the design team. You may even be able to find it. If you can locate the item publicly point me at it and we'll take a look using the publicly available data. |
We will try to find out which object(s) are causing the long loading. My colleague knows about 3D modeling and will try to export only parts of the geometry. No single object immediately stands out as the culprit. |
There is a performance issue when loading IFC files with tessellation.
Loading a specific file takes several minutes.
I ran Visual Studio's Performance Profiler to see where the hotspot was in the code.
It looks like the suspect is the tessellation.
I can provide the diagsession file if needed.
BIMvision loads the same file much faster - perhaps performance can be optimized here.
Assemblies and versions affected:
Project Xbim.Tessellator in solution Xbim.Essentials (master branch).
Steps (or code) to reproduce the issue:
Load a specific ifc file with XbimXplorer.
Minimal file to reproduce the issue:
IFC files need to be zipped to be uploaded. Then just drag & drop here
Expected behavior:
Faster loading.
Actual behavior or exception details:
It takes a long time to load.
The text was updated successfully, but these errors were encountered: