CAD

Using Voronoi Diagrams to Equally Divide Adjoining Space Between Polygons

Posted by Nabil Kherouf
on October 28, 2024

Using Voronoi Diagrams to Equally Divide Adjoining Space Between Polygons

Problem description

Given a set of arbitrary polygons belonging to different categories, we would like to equally divide the space between polygons and compute the square footage of each category.

The following images show a set of polygons in different color-coded categories. Last image shows what the goal is: a set of shapes representing each category and whose square footage can easily be computed.

Arbitrary polygons with different color-coded categories.
Desired shapes representing polygons in the same category.
Desired square footage of polygons in the same category.

Introduction

After exploring different approaches to compute the square footage, we found that the simplest and most optimal way was to compute the individual polygon contribution (Area of polygon + 1/2 surrounding space) then sum up all the individual contributions in the same category. A new question then arose, how to divide space occupied by a certain polygon and its neighbors?

Following image shows a set of polygons with the shared space equally divided between each polygon and its neighbors.

Equally divided space between polygons

Computing area contribution for each polygon

To find area contribution for each polygon, we need to find the midpoints between the polygon and its direct neighbors. What we’re referring to as centerline in this post is, in fact, the shape that spans all the midpoints between each polygon and its neighbors. We’re using a concept in Computational Geometry called Voronoi Diagrams to find these centerlines.

Voronoi Diagrams

The simplest definition of Voronoi Diagrams is it being the partition of a plane into a set of regions close to each of a set of objects. In other words, given a set of points in a plane, a Voronoi Region/Cell of a point A is an area encompassing points closer to A than any other points in the plane. To use Voronoi Diagrams with a set of polygons, we need to compute a cell for each segment of a given polygon. By performing a union on the resulting Voronoi Cells, we find the centerlines between the polygon and its neighbors (See example below). Please note that these centerlines can also be used for other purposes such as indoor navigation or robot motion.

Following image shows a Voronoi Cell for each segment with matching colors for the cell/segment:

Voronoi Cells/Segments with matching colors

Following images show the Voronoi Cells (in red) associated with the two polygons and the final centerlines in purple after performing the union of the Voronoi Cells:

All Voronoi Cells (in red) for each
polygons
Centerlines (in purple) after union
of Voronoi Cells

Step-by-step process of finding the centerlines

  • Extract segments from each polygon and assign them an id to associate them with their original polygon.
  • Find the Voronoi Cell for each segment passed. We’re using Boost library to perform this operation.
  • Union the Voronoi Cells for segments in each polygon.

The Boost library provides a very efficient and easy-to-use function to compute the Voronoi Diagrams of any set of non-intersecting segments (except endpoints of consecutive segments). By passing the segments of each polygon to the Boost, we’re computing the Voronoi Cell for each segment then we’re performing a boolean union of all resulting Voronoi Cells. As mentioned before, the union will be a shape representing the centerlines between the polygon and its direct neighbors.

Please note that in the following C++ example, our polygons are stored in a collection called vRanges. Also, segments are instances of the Segment_vd class which takes the coordinates of the segment endpoints and the index of the containing polygon as inputs to the constructor.

// building a collection of segments from each polygon 
	for (unsigned index_range=0; index_range<vRanges.size(); index_range++)
	{
		SpecialGondolaRange range = vRanges[index_range];

		vRanges[index_range].bVoronoi_points_found = FALSE;
		if (range.bIgnore)
			continue;

		for (int i=0; i< range.numPnts; i++)
		{
			int curr = i, next = i==range.numPnts-1?0:i+1;
			DPoint3d currP=range.shapePnts[curr];
			DPoint3d nextP=range.shapePnts[next];

			currP.x = currP.x;
			currP.y = currP.y;
			currP.z = 0;

			nextP.x = nextP.x;
			nextP.y = nextP.y;
			nextP.z = 0;

			double dist = mdlVec_distanceXY(&currP, &nextP);

			segments.push_back(Segment_vd(currP.x, currP.y, nextP.x, nextP.y, index_range));	
		}
	}

	printf("--");

	std::vector<Segment_vd> segments_no_intersections;

	// Construction of the Voronoi Diagram.
	voronoi_diagram<double> vd;
	construct_voronoi(points.begin(), points.end(),
		segments.begin(), segments.end(),
		&vd);
	printf("--");

Conclusion

As shown above, Voronoi Diagrams were very essential in resolving our problem of area contribution by generating the midpoints between polygons in our file. Please note that, for this tutorial we’ve used VS2005 and Boost library v1.74.0. The newer versions of Boost (+1.82) require at least C++11 and a more recent release of Visual Studio.

Helpful links:

https://www.boost.org/doc/libs/1_80_0/libs/polygon/doc/voronoi_diagram.htm

https://alexbeutel.com/webgl/voronoi.html

https://mathworld.wolfram.com/VoronoiDiagram.html

Contact us!

Continue Reading

Collision detection inside CAD files

Posted by Nabil Kherouf
on October 28, 2024

Using Collision Detection to Place Non-Intersecting Items

Problem Description

For legibility and presentation purposes, we would like to programmatically append a descriptive label to an item in a CAD file in such a way that it doesn’t collide with the any preexisting labels/items.

Illustrative Example

Given a CAD drawing of a set of cells with multiple labels placed on the side. Programmatically place a new label “001” without intersecting any existing labels/objects. See images below.

Collision between the “001” label and obstacle1

“001” Label intersecting “obstacle1”

No collision between the “001” label and existing obstacles

“001” label placed in a collision-free manner.

Introduction

To achieve a collision-free placement of the new label for the example above, we’re using a concept in Computational Geometry called Minkowski Sums. The concept of Minkowski Sums of two polygons is defined as a set of points extracted by summing all pairs of points from the 2 polygons.

After creating a concave hull (C.H) for each obstacle and the new label, we’re convolving the C.H of the new label with the C.H of each obstacle at 0,0 origin and 180°. The result of the convolution will represent the collision space for each obstacle and the new label can be placed anywhere outside of the collision space.

Computing the collision-free space for the “001” label in the example above

Summary of the computation steps:

  • Extract the main placement line
  • Compute Minkowski Sums between label and obstacles at 0,0 org and at 180°
  • Clip the resulting polygons from Minkowski Sums from the main placement line

First step is to extract the main placement line (shown in white below) that represents all possible placement points, with or without collision with any existing objects. Next step is to compute the Minkowski Sums of the “001” label with each obstacle at zero origin and at 180°. Results are shown in dashed polygons in the image below. As explained before, the resulting shapes represent the collision space therefore they need to be clipped out of the main placement line (white line). All the other points on the main placement line will be valid candidates for collision-free placement. For simplicity, we will pick the point closest to beginning of the side line.

Main placement line shown in White

Main placement line shown in white. This represents all placement points with or without collision

Dashed polygons representing Minkowski Sums of label and each obstacle

Dashed polygons being the Minkowski Sums of label and each obstacle

Collision-free space shown in blue

All possible collision-free placement points on 2 blue segments

Placement of label on collision-free space

“001” label placed on first valid collision-free point

Sample code

/***************************************
  Descr: Returns the Minkowski Sums between a robot and and set of obstacles
****************************************/
int mcs_minkowskiSums 
(
 VecMsPoints			vRobotPts,		//  => flag hull pts [MU]
 vector<VecMsPoints>	vvObstaclePts,  //  => obstacle pts [MU]
 vector<VecMsPoints>&	rvvMinSums		// <= 
)
{
	polygon_set a, b, c;
	polygon poly;
	std::vector<point> pts_robot;

	removeObstacleIntersections(vvObstaclePts);

  // Convert robot points to UORs only necessary if you're using MDL (Bentley uStation)
	int index =0;
	for each (DPoint3d pt in vRobotPts) // convert to UORs
	{
		DPoint3d	dPt;
		Point3d		iPt;
		
		dPt.x = mdlCnv_masterUnitsToUors(pt.x);
		dPt.y = mdlCnv_masterUnitsToUors(pt.y);
		dPt.z = 0.0;

		mdlCnv_DPointToIPoint (&iPt, &dPt); 
		pts_robot.push_back(point(iPt.x,iPt.y));
	}

	boost::polygon::set_points(poly, pts_robot.begin(), pts_robot.end());
	//boost::geometry::correct(poly);
	poly.size();
	a += poly;

	for each (VecMsPoints vObstaclePts in vvObstaclePts)
	{
	  // Convert obtscale points to UORs. ** only necessary if you're using MDL (Bentley uStation)
		polygon poly1;
		std::vector<point> pts;
		for each (DPoint3d pt in vObstaclePts) // convert to UORs and * -1 for Mink Diff
		{
			DPoint3d	dPt;
			Point3d		iPt;
			
			dPt.x = mdlCnv_masterUnitsToUors(pt.x) * -1.0;
			dPt.y = mdlCnv_masterUnitsToUors(pt.y) * -1.0;
			dPt.z = 0.0;

			mdlCnv_DPointToIPoint (&iPt, &dPt); 
			
			pts.push_back(point(iPt.x,iPt.y));
		}

		boost::polygon::set_points(poly1, pts.begin(), pts.end());
		//boost::geometry::correct(poly1);
		poly1.size();
		b += poly1;
	}

	convolve_two_polygon_sets(c, a, b); // From Boost

	if (c.size() == 0)
		return FALSE;
	if (c.empty())
		return FALSE;

	//printf("\n BEFORE SIMPLE POLYGONS");
	//SimplePolygons simplePolygons;
	using namespace boost::polygon;
	vector<polygon> resultPolygons;
	c.get(resultPolygons);
	
	//for each(SimplePolygon poly in simplePolygons)
	for each(polygon poly in resultPolygons)
		rvvMinSums.push_back(mcs_get_points_poly(begin_points(poly), end_points(poly)));
	
	return TRUE;
}

Conclusion

We have shown in this post how to automatically add a new item to a CAD file without collision with existing items. We used Minkowski Sums for obstacle detection in a CAD file . The application of this concept can however extend beyond CAD/Collision Detection and can be used in numerous other fields such as robot motion, 3D modeling, … etc

Contact us!

Helpful links:

https://cp-algorithms.com/geometry/minkowski.html

https://www.geeksforgeeks.org/what-is-computational-geometry-and-how-is-it-applied-in-solving-geometric-problems

https://www.boost.org/doc/libs/develop/libs/polygon/doc/gtl_minkowski_tutorial.htm

Continue Reading
MENU