The last couple of days I've been trying to get a simple algorithm for uniquely determining orthogonal polygons in 3D integer space, having already determined coplanar points.
By the time we got here we know each point is unique and there are no collinear points.
For example, given the coplanar points pts (lists or tuples)
pts = [
[0, 0, 0], [4, 0, 0],
[1, 1, 0], [2, 1, 0], [3, 1, 0], [4, 1, 0],
[1, 2, 0], [2, 2, 0], [3, 2, 0], [4, 2, 0],
[0, 3, 0], [4, 3, 0]
]
# Generate orthogonal polygons
# 3 +-----------+
# | |
# 2 | +__+ +--+
# | | | |
# 1 | +--+ +__+
# | |
# 0 +-----------+
# 0 1 2 3 4
# result = [
# [
# [0, 0, 0], [0, 3, 0], [4, 3, 0], [4, 2, 0],
# [3, 2, 0], [3, 1, 0], [4, 1, 0], [4, 0, 0]
# ], [
# [1, 1, 0], [1, 2, 0], [2, 2, 0], [2, 1, 0]
# ]
# ]
I found the basis of an algorithm for this in a paper as authored by Joseph O'Rourke The following seems to work for the few tests I set up (however it doesn't deal with orientation).
def polygonise(pts: list) -> list:
# axes gives us the varying columns (we can ignore z for this)
axes = {i:set(c) for i,c in enumerate(zip(*pts)) if len(set(c))!=1}
a, b = axes.keys()
# construct dicts of axis-columns
pts_by_axis = {axis: {col: [] for col in cols} for axis,cols in axes.items()}
for pt in pts:
for axis in axes:
pts_by_axis[axis][pt[axis]].append(pt)
# each axis-column (sorted by the other dimension)
# will now be paired off into edges, and constructed into axis-edge-dicts
edges = {}
for axis in axes:
other = a if axis != a else b
for col,pt_list in pts_by_axis[axis].items():
pt_list.sort(key=lambda p:p[other])
for i in range(0,len(pt_list),2):
v1 = tuple(pt_list[i])
v2 = tuple(pt_list[i + 1])
edges[tuple((axis,v1))] = other, v2
edges[tuple((axis,v2))] = other, v1
# now derive the polygons, starting at any point.
axis, edge = next(iter(edges.keys()))
result, poly = [], [edge]
while edges:
if (axis, edge) not in edges:
# This polygon is now closed. keep it, and move to the next one.
result.append(poly[:-1]) # if wanted looped, don't slice
poly = []
axis, edge = next(iter(edges.keys()))
else:
# Get the next edge. change the axis (edge is on a different axis)
axis, new_edge = edges.pop((axis,edge))
# delete any old reversed edge.
del edges[(axis, edge)]
edge = new_edge
# add the edge to the existing polygon
poly.append(edge)
# capture the final polygon.
result.append(poly[:-1]) # if wanted looped, don't slice.
return result
if __name__ == "__main__":
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt
from random import shuffle
pts = [
(0, 0, 0), (4, 0, 0),
(1, 1, 0), (2, 1, 0), (3, 1, 0), (4, 1, 0),
(1, 2, 0), (2, 2, 0), (3, 2, 0), (4, 2, 0),
(0, 3, 0), (4, 3, 0)
]
# shuffle just to make it different.
shuffle(pts)
result = polygonise(pts)
# all the below is just to show the result
fig = plt.figure()
subplot = fig.add_subplot(111, projection='3d')
subplot.axis('off')
subplot.scatter([0.6, 3.1], [0.7, 3.3], [0, 0], alpha=0.0)
subplot.add_collection3d(Poly3DCollection(result, edgecolors='k', facecolors='b', linewidths=1, alpha=0.2))
fig.tight_layout()
plt.show()
I think that the loops could be easier, and I'm not convinced that I need to store edges twice, (which means I have to delete the unused one).
Any ideas for tidying the polygonise() method would be welcome.
J. O'Rourke, "Uniqueness of orthogonal connect-the-dots", Computational Morphology, G.T. Toussaint (editor), Elsevier Science Publishers, B.V.(North-Holland), 1988, 99-104. found here
