We have code that reads in a DXF/DWG file (drawing file) and then our own geometry library that converts everything into point, line, arcs, circles, and etc. objects. Then we have a Shape object which will hold one or many entities. Shapes can also contain other shapes as children and those shapes can have children.
We have basic checks that do the typical checks like if an entity touches another entity or intersects it and are generally perform well so we haven't included that code here.
In the software when we load a drawing file we do the following:
- Read in the file, consume the entities into objects
- Up convert each entity into a shape object, circles are 1:1, lines and everything else get marked as 'Irregular' shape objects
- Build an array of shapes / merge shapes by checking which entities 'touch' each other, so anything touching gets grouped into a single shape
- simple checks like are first/last points the same
- intersections
- standard checks pulled out of geometry text books and other geometry libraries
- Once we have a list of shapes we do parent/child analysis (right now it only goes one level deep) so instead of ending up with let's say a dozen shapes from a hundred entities we know some are shapes inside of a shape, so we may only have one true 'shape' in the drawing while other shapes are nested underneath it as children or we could have multiple shapes in a drawing that are not within each other
Overall it performed pretty good but once we added more complex drawings into the mix we realized some authors don't do single polylines but may have dozens or hundreds of lines in a drawing and then you could also have dozens or hundreds of circles showing cuts/holes in it. Then on top of that the average number of drawings the code deals with could be 10-30 files. So now we are seeing an average use case and the code not scaling to handle it and the step #2 is more of a brute force algorithm and can take 4+ seconds to run when there's hundreds of entities in a file. The algorithm / code mainly just looks at all entities and tries to identify which ones are touching which ones. I'm looking for ideas to improve the algorithm. I am trying to write more geometry focused code vs cad code, the cad code that reads the file is separate and just builds geometry to work with, then we go into geometry and do generic geometry work that isn't cad based but universal like hull analysis, total distances, want to also integrate with other open source libraries and leverage stronger analysis techniques on the drawing data and eventually even do image recognition which translates into geometry then back into cad by keeping libraries de-coupled.
It loops over all the entities checking if an entity touches (checking first/last points, intersection algorithms, standard geometry checks that are pretty quick), if so it adds it to the shape and keeps going then restarts all over again creating new shapes or adding to existing ones until it's passed over all the entities and all of the shapes made. And when creating shapes and looking at new entities, it loops all over the entities in the shape checking if they touch again so that's why it doesn't scale well and it's not the best piece of code.
Ignoring the 'touching' checks and all Invalidate does is update internals like length calculations and things like that, the issue is below.
Any suggestions on how to improve this algorithm for speed and efficiency when dealing with hundreds of entities?
//we can end up with disjointed shapes, this scan keeps passing until we have no
//more merges to try and perform every time we find a match we restart
int idx = 0;
uint kickout = 0;
bool no_more = false; bool restart = false;
//nothing at all?
if (shapes.Count() <= 0)
return shapes;
do
{
int i = -1;
Shape s1 = shapes[idx];
if (s1.Entities.Count() > 0)
{
for (i = idx + 1; i < shapes.Count(); i++)
{
Shape s2 = shapes[i];
if (s2.Entities.Count() > 0)
{
foreach (Entity e in s2.Entities)
{
if (s1.Touches(e))
{
//as a group, they all touch each other so they must
//all connect. Invalidate later (for speed)
s1.Add(s2);
s2.Clear(); //empty
shapes.Remove(s2); //empty from collection
restart = true;
break;
}
}
}
}
}
//check next shape
if (i == shapes.Count() || i == -1)
{
idx++;
//idx %= shapes.Count(); //b/c we are manipulating contents
}
if (idx == shapes.Count() && !restart)
{
no_more = true;
}
if (idx == shapes.Count() && restart)
{
//re-pass, we have to keep going in case we missed a
//joining segment/point
idx = 0; restart = false;
}
//something went wrong here, don't freeze but we need to capture this
kickout++;
if (kickout + 1 == uint.MaxValue) //pretty high limit for checking
{
Debug.WriteLine("CRITICAL ERROR, OVERFLOW DETECTED IN SHAPE ANALYSIS!");
throw new OverflowException("CRITICAL ERROR, OVERFLOW DETECTED IN SHAPE ANALYSIS!");
}
} while (!no_more);
Shape
a collection class ? we need to see the related code for it. \$\endgroup\$