Split a triangle into smaller triangles - algorithm

I have a triangulated mesh. I want to limit the maximum edge length. Therefore I take the all triangles with long edges (longer than the limit), and split them into smaller triangles.
My idea is the following:
I split the longest edge in half and get two triangles. If these are also too large I do it recursively. This works nice, because I also split the correspondent adjacent triangle and the vertexes collapse again.
The problem: When there is a acute-angled triangles. The result look a bit weird. Small angles get even smaller, ...
Is there a better way of splitting such triangles.
Another idea is, to split a edge into k equidistant edges, (with k the smallest value, such that edgelength/k < limit).
I can do this on all 3 edges of the triangle. But how should I connect these vertexes?

As you are bothered with small angles and small triangles, I would advise you to use Delaunay triangulation, because one of its properties is that it maximizes the minimal angle and it avoids small triangles.
Delaunay triangulation requires the points as input. Since you don't have this, you could perform the algorithm recursively, splitting lines when they are too long.
The following Python code does exactly what you would like to achieve.
It uses the Delaunay class included in scipy.
def splitViaDelaunay(points, maxLength):
from scipy.spatial import Delaunay
from math import sqrt, ceil
print "Perform Delaunay triangulation with "+str(len(points))+" points"
tri = Delaunay(points)
# get set of edges from the simpleces
edges = set()
for simplex in tri.simplices:
# simplex is one triangle: [ 4 5 17]
edges.add((simplex[0], simplex[1]))
edges.add((simplex[1], simplex[2]))
edges.add((simplex[0], simplex[2]))
# check if all edges are small enough
# and add new points if not
isFinished = True
for edge in edges:
p1, p2 = edge
[x1, y1] = points[p1]
[x2, y2] = points[p2]
length = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
if length > maxLength:
isFinished = False
# split in how many pieces?
nPieces = ceil(length/maxLength)
for piece in range(1, int(nPieces)):
points.append([x1+piece/float(nPieces)*(x2-x1), y1+piece/float(nPieces)*(y2-y1)])
if not isFinished:
splitViaDelaunay(points, maxLength)
Let's try it out.
points = [[0,0], [10,3], [9.5,4]]
splitViaDelaunay(points, 0.5)
It outputs
Perform Delaunay triangulation with 3 points
Perform Delaunay triangulation with 45 points
Perform Delaunay triangulation with 97 points
Perform Delaunay triangulation with 105 points
Let's see the results now in a figure, created via the matplotlib library from python.
def plotPointsViaDelaunayTriangulation(pnts):
from scipy.spatial import Delaunay
import numpy as np
points = np.array(pnts)
tri = Delaunay(points)
import matplotlib.pyplot as plt
plt.triplot(points[:,0], points[:,1], tri.simplices.copy())
plt.plot(points[:,0], points[:,1], 'o')
plt.show()
plotPointsViaDelaunayTriangulation(points)
This is the result:

Related

Obtaining a triangle pair that have a shared edge from triangle set obtained from Delaunay Triangulation

I want to obtain triangles which are from triangle set obtained from Delaunay Triangulation. I wrote the following code. How can I obtain triangles which have a shred edge with each other (please see the image)? According to this image, I want to obtain triangle1 and 2 from triangle set obtained from Delaunay Triangulation.
rng default;
P = rand([32 2]);
DT = delaunayTriangulation(P);
triplot(DT)
Short answer: neighbors(DT).
Example:
rng default
P = rand([12 2]);
DT = delaunayTriangulation(P);
IC = incenter(DT);
% visualize incl. ID in the center
figure
triplot(DT)
hold on
text(IC(:,1), IC(:,2), num2str([1:size(IC,1)]'))
% find all neighboring triangles
neighbors(DT)
% for the first triangle
neighbors(DT, 1)

How to convert convex hull vertices into a geopandas polygon

Iam using DBSCAN to cluster coordinates together and then using convexhull to draw 'polygons' around each cluster. I then want to construct geopandas polygons out of my convex hull shapes to be used for spatial joining.
import pandas as pd, numpy as np, matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from scipy.spatial import ConvexHull
Lat=[10,10,20,23,27,28,29,34,11,34,66,22]
Lon=[39,40,23,21,11,29,66,33,55,22,11,55]
D=list(zip(Lat, Lon))
df = pd.DataFrame(D,columns=['LAT','LON'])
X=np.array(df[['LAT', 'LON']])
kms_per_radian = 6371.0088
epsilon = 1500 / kms_per_radian
db = DBSCAN(eps=epsilon, min_samples=3)
model=db.fit(np.radians(X))
cluster_labels = db.labels_
num_clusters = len(set(cluster_labels))
cluster_labels = cluster_labels.astype(float)
cluster_labels[cluster_labels == -1] = np.nan
labels = pd.DataFrame(db.labels_,columns=['CLUSTER_LABEL'])
dfnew=pd.concat([df,labels],axis=1,sort=False)
z=[] #HULL simplices coordinates will be appended here
for i in range (0,num_clusters-1):
dfq=dfnew[dfnew['CLUSTER_LABEL']==i]
Y = np.array(dfq[['LAT', 'LON']])
hull = ConvexHull(Y)
plt.plot(Y[:, 1],Y[:, 0], 'o')
z.append(Y[hull.vertices,:].tolist())
for simplex in hull.simplices:
ploted=plt.plot( Y[simplex, 1], Y[simplex, 0],'k-',c='m')
plt.show()
print(z)
the vertices appended in list[z] represent coordinates of the convex hull however they are not constructed in sequence and closed loop object hence constructing polygon using polygon = Polygon(poin1,point2,point3) will not produce a polygon object. is there a way to construct geopandas polygon object using convex hull vertices in order to use for spatial joining. THanks for your advise.
Instead of generating polygon directly, I would make a MultiPoint out of your coordinates and then generate convex hull around that MultiPoint. That should result in the same geometry, but in properly ordered manner.
Having z as list of lists as you do:
from shapely.geometry import MultiPoint
chulls = []
for hull in z:
chulls.append(MultiPoint(hull).convex_hull)
chulls
[<shapely.geometry.polygon.Polygon at 0x117d50dc0>,
<shapely.geometry.polygon.Polygon at 0x11869aa30>]

Triangulate a set of points with a concave domain

Setup
Given some set of nodes within a convex hull, assume the domain contains one or more concave areas:
where blue dots are points, and the black line illustrates the domain. Assume the points are held as a 2D array points of length n, where n is the number of point-pairs.
Let us then triangulate the points, using something like the Delaunay method from scipy.spatial:
As you can see, one may experience the creation of triangles crossing through the domain.
Question
What is a good algorithmic approach to removing any triangles that span outside of the domain? Ideally but not necessarily, where the simplex edges still preserve the domain shape (i.e., no major gaps where triangles are removed).
Since my question is seeming to continue to get a decent amount of activity, I wanted to follow up with the application that I'm currently using.
Assuming that you have your boundary defined, you can use a ray casting algorithm to determine whether or not the polygon is inside the domain.
To do this:
Take the centroid of each polygon as C_i = (x_i,y_i).
Then, imagine a line L = [C_i,(+inf,y_i)]: that is, a line that spans east past the end of your domain.
For each boundary segment s_i in boundary S, check for intersections with L. If yes, add +1 to an internal counter intersection_count; else, add nothing.
After the count of all intersections between L and s_i for i=1..N are calculated:
if intersection_count % 2 == 0:
return True # triangle outside convex hull
else:
return False # triangle inside convex hull
If your boundary is not explicitly defined, I find it helpful to 'map' the shape onto an boolean array and use a neighbor tracing algorithm to define it. Note that this approach assumes a solid domain and you will need to use a more complex algorithm for domains with 'holes' in them.
Here is some Python code that does what you want.
First, building the alpha shape (see my previous answer):
def alpha_shape(points, alpha, only_outer=True):
"""
Compute the alpha shape (concave hull) of a set of points.
:param points: np.array of shape (n,2) points.
:param alpha: alpha value.
:param only_outer: boolean value to specify if we keep only the outer border or also inner edges.
:return: set of (i,j) pairs representing edges of the alpha-shape. (i,j) are the indices in the points array.
"""
assert points.shape[0] > 3, "Need at least four points"
def add_edge(edges, i, j):
"""
Add a line between the i-th and j-th points,
if not in the list already
"""
if (i, j) in edges or (j, i) in edges:
# already added
assert (j, i) in edges, "Can't go twice over same directed edge right?"
if only_outer:
# if both neighboring triangles are in shape, it's not a boundary edge
edges.remove((j, i))
return
edges.add((i, j))
tri = Delaunay(points)
edges = set()
# Loop over triangles:
# ia, ib, ic = indices of corner points of the triangle
for ia, ib, ic in tri.vertices:
pa = points[ia]
pb = points[ib]
pc = points[ic]
# Computing radius of triangle circumcircle
# www.mathalino.com/reviewer/derivation-of-formulas/derivation-of-formula-for-radius-of-circumcircle
a = np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
b = np.sqrt((pb[0] - pc[0]) ** 2 + (pb[1] - pc[1]) ** 2)
c = np.sqrt((pc[0] - pa[0]) ** 2 + (pc[1] - pa[1]) ** 2)
s = (a + b + c) / 2.0
area = np.sqrt(s * (s - a) * (s - b) * (s - c))
circum_r = a * b * c / (4.0 * area)
if circum_r < alpha:
add_edge(edges, ia, ib)
add_edge(edges, ib, ic)
add_edge(edges, ic, ia)
return edges
To compute the edges of the outer boundary of the alpha shape use the following example call:
edges = alpha_shape(points, alpha=alpha_value, only_outer=True)
Now, after the edges of the outer boundary of the alpha-shape of points have been computed, the following function will determine whether a point (x,y) is inside the outer boundary.
def is_inside(x, y, points, edges, eps=1.0e-10):
intersection_counter = 0
for i, j in edges:
assert abs((points[i,1]-y)*(points[j,1]-y)) > eps, 'Need to handle these end cases separately'
y_in_edge_domain = ((points[i,1]-y)*(points[j,1]-y) < 0)
if y_in_edge_domain:
upper_ind, lower_ind = (i,j) if (points[i,1]-y) > 0 else (j,i)
upper_x = points[upper_ind, 0]
upper_y = points[upper_ind, 1]
lower_x = points[lower_ind, 0]
lower_y = points[lower_ind, 1]
# is_left_turn predicate is evaluated with: sign(cross_product(upper-lower, p-lower))
cross_prod = (upper_x - lower_x)*(y-lower_y) - (upper_y - lower_y)*(x-lower_x)
assert abs(cross_prod) > eps, 'Need to handle these end cases separately'
point_is_left_of_segment = (cross_prod > 0.0)
if point_is_left_of_segment:
intersection_counter = intersection_counter + 1
return (intersection_counter % 2) != 0
On the input shown in the above figure (taken from my previous answer) the call is_inside(1.5, 0.0, points, edges) will return True, whereas is_inside(1.5, 3.0, points, edges) will return False.
Note that the is_inside function above does not handle degenerate cases. I added two assertions to detect such cases (you can define any epsilon value that fits your application). In many applications this is sufficient, but if not and you encounter these end cases, they need to be handled separately.
See, for example, here on robustness and precision issues when implementing geometric algorithms.
One of Classic DT algorithms generates first a bounding triangle, then adds all new triangles sorted by x, then prunes out all triangles having a vertex in the supertriangle.
At least from the provided image one can derive the heuristics of pruning out also some triangles having all vertices on the concave hull. Without a proof, the triangles to be pruned out have a negative area when their vertices are sorted in the same order as the concave hull is defined.
This may need the concave hull to be inserted as well, and to be pruned out.
Since my question is seeming to continue to get a decent amount of activity, I wanted to follow up with the application that I'm currently using.
Assuming that you have your boundary defined, you can use a ray casting algorithm to determine whether or not the polygon is inside the domain.
To do this:
Take the centroid of each polygon as C_i = (x_i,y_i).
Then, imagine a line L = [C_i,(+inf,y_i)]: that is, a line that spans east past the end of your domain.
For each boundary segment s_i in boundary S, check for intersections with L. If yes, add +1 to an internal counter intersection_count; else, add nothing.
After the count of all intersections between L and s_i for i=1..N are calculated:
if intersection_count % 2 == 0:
return True # triangle outside convex hull
else:
return False # triangle inside convex hull
If your boundary is not explicitly defined, I find it helpful to 'map' the shape onto an boolean array and use a neighbor tracing algorithm to define it. Note that this approach assumes a solid domain and you will need to use a more complex algorithm for domains with 'holes' in them.
You can try a constrained delaunay algorithm for example with sloan algoritm or cgal library.
[1] A Brute-Force Constrained Delaunay Triangulation?
A simple but elegant way is to loop over the triangels and check wether they are within our domain or not. The shapely package could do the trick for you.
for more on this please check the following post: https://gis.stackexchange.com/a/352442
Note that triangulation in shapely is also implemented, even for MultiPoin objects.
I used it, the performance was amazing and the code was only like five lines.
Compute the triangles centroid an check if it's inside the polygon using this algorithm.

Visualize distance matrix as a graph

I am doing a clustering task and I have a distance matrix. I wish to visualize this distance matrix as a 2D graph. Please let me know if there is any way to do it online or in programming languages like R or python.
My distance matrix is as follows,
I used the classical Multidimensional scaling functionality (in R) and obtained a 2D plot that looks like:
But What I am looking for is a graph with nodes and weighted edges running between them.
Possibility 1
I assume, that you want a 2dimensional graph, where distances between nodes positions are the same as provided by your table.
In python, you can use networkx for such applications. In general there are manymethods of doing so, remember, that all of them are just approximations (as in general it is not possible to create a 2 dimensional representataion of points given their pairwise distances) They are some kind of stress-minimizatin (or energy-minimization) approximations, trying to find the "reasonable" representation with similar distances as those provided.
As an example you can consider a four point example (with correct, discrete metric applied):
p1 p2 p3 p4
---------------
p1 0 1 1 1
p2 1 0 1 1
p3 1 1 0 1
p4 1 1 1 0
In general, drawing actual "graph" is redundant, as you have fully connected one (each pair of nodes is connected) so it should be sufficient to draw just points.
Python example
import networkx as nx
import numpy as np
import string
dt = [('len', float)]
A = np.array([(0, 0.3, 0.4, 0.7),
(0.3, 0, 0.9, 0.2),
(0.4, 0.9, 0, 0.1),
(0.7, 0.2, 0.1, 0)
])*10
A = A.view(dt)
G = nx.from_numpy_matrix(A)
G = nx.relabel_nodes(G, dict(zip(range(len(G.nodes())),string.ascii_uppercase)))
G = nx.to_agraph(G)
G.node_attr.update(color="red", style="filled")
G.edge_attr.update(color="blue", width="2.0")
G.draw('distances.png', format='png', prog='neato')
In R you can try multidimensional scaling
# Classical MDS
# N rows (objects) x p columns (variables)
# each row identified by a unique row name
d <- dist(mydata) # euclidean distances between the rows
fit <- cmdscale(d,eig=TRUE, k=2) # k is the number of dim
fit # view results
# plot solution
x <- fit$points[,1]
y <- fit$points[,2]
plot(x, y, xlab="Coordinate 1", ylab="Coordinate 2",
main="Metric MDS", type="n")
text(x, y, labels = row.names(mydata), cex=.7)
Possibility 2
You just want to draw a graph with labeled edges
Again, networkx can help:
import networkx as nx
# Create a graph
G = nx.Graph()
# distances
D = [ [0, 1], [1, 0] ]
labels = {}
for n in range(len(D)):
for m in range(len(D)-(n+1)):
G.add_edge(n,n+m+1)
labels[ (n,n+m+1) ] = str(D[n][n+m+1])
pos=nx.spring_layout(G)
nx.draw(G, pos)
nx.draw_networkx_edge_labels(G,pos,edge_labels=labels,font_size=30)
import pylab as plt
plt.show()
Multidimensional scaling (MDS) is exactly what you want. See here and here for more.
You did not mentioned if you want a 2 dimensional graph or not. I suppose that you want to build a graph on 2 dimensions due to the fact that you need that for visualization. Considering that you have to be aware that for the most of the graphs this is simply not possible.
What can be probably done is to approximate somehow the values from distance matrix, something like small values to have relative small edges and big values to have a relative big length.
With all previous considerations one option would be graphviz. See neato function.
In general what you are interested in is force-directed drawing. See wikipedia for further reference.
You can use d3js Force Directed Graph and configure distance between nodes. d3js force layout has some clustering capability to separate nodes with similar distances. Here's an example with values as distance between nodes:
http://vida.io/documents/SyT7DREdQmGSpsBkK
Another way to visualize is to use same distance between nodes but different line thickness. In that case, you'd want to calculate stroke-width based on values:
.style("stroke-width", function(d) { return Math.sqrt(d.value / 50); });

Hole in a Polygon

I need to create a 3D model of a cube with a circular hole punched at the center of one face passing completely through the cube to the opposite side. I am able to generate the vertices for the faces and for the holes.
Four of the faces (untouched by the hole) can be modeled as a single triangle strip. The inside of the hole can also be modeled as a single triangle strip.
How do I generate the index buffer for the faces with the holes? Is there a standard algorithm(s) to do this?
I am using Direct3D but ideas from elsewhere are welcome.
To generate the index-buffer you want, you could do like this. Thinking in 2D with the face in question as a square with vertices (±1, ±1), and the hole as a circle in the middle.
You walk along the edge of the circle, dividing it into some number of segments.
For each vertex, you project it onto the surrounding square with (x/M,y/M), where M is max(abs(x),abs(y)). M is the absolute value of the biggest coordinate, so this will scale (x,y) so that the biggest coordinate is ±1.
This line you also divide into some number of segments.
The segments of two succeeding lines you join pairwise as faces.
This is an example, subdividing the circle into 64 segments, and each ray into 8 segments. You can choose the numbers to match your requirements.
alt text http://pici.se/pictures/AVhcssRRz.gif
Here is some Python code that demonstrates this:
from math import sin, cos, pi
from itertools import izip
def pairs(iterable):
"""Yields the previous and the current item on each iteration.
"""
last = None
for item in iterable:
if last is not None:
yield last, item
last = item
def circle(radius, subdiv):
"""Yields coordinates of a circle.
"""
for angle in xrange(0,subdiv+1):
x = radius * cos(angle * 2 * pi / subdiv)
y = radius * sin(angle * 2 * pi / subdiv)
yield x, y
def line(x0,y0,x1,y1,subdiv):
"""Yields coordinates of a line.
"""
for t in xrange(subdiv+1):
x = (subdiv - t)*x0 + t*x1
y = (subdiv - t)*y0 + t*y1
yield x/subdiv, y/subdiv
def tesselate_square_with_hole((x,y),(w,h), radius=0.5, subdiv_circle=64, subdiv_ray=8):
"""Yields quads of a tesselated square with a circluar hole.
"""
for (x0,y0),(x1,y1) in pairs(circle(radius,subdiv_circle)):
M0 = max(abs(x0),abs(y0))
xM0, yM0 = x0/M0, y0/M0
M1 = max(abs(x1),abs(y1))
xM1, yM1 = x1/M1, y1/M1
L1 = line(x0,y0,xM0,yM0,subdiv_ray)
L2 = line(x1,y1,xM1,yM1,subdiv_ray)
for ((xa,ya),(xb,yb)),((xc,yc),(xd,yd)) in pairs(izip(L1,L2)):
yield ((x+xa*w/2,y+ya*h/2),
(x+xb*w/2,y+yb*h/2),
(x+xc*w/2,y+yc*h/2),
(x+xd*w/2,y+yd*h/2))
import pygame
def main():
"""Simple pygame program that displays the tesselated figure.
"""
print('Calculating faces...')
faces = list(tesselate_square_with_hole((150,150),(200,200),0.5,64,8))
print('done')
pygame.init()
pygame.display.set_mode((300,300))
surf = pygame.display.get_surface()
running = True
while running:
need_repaint = False
for event in pygame.event.get() or [pygame.event.wait()]:
if event.type == pygame.QUIT:
running = False
elif event.type in (pygame.VIDEOEXPOSE, pygame.VIDEORESIZE):
need_repaint = True
if need_repaint:
print('Repaint')
surf.fill((255,255,255))
for pa,pb,pc,pd in faces:
# draw a single quad with corners (pa,pb,pd,pc)
pygame.draw.aalines(surf,(0,0,0),True,(pa,pb,pd,pc),1)
pygame.display.flip()
try:
main()
finally:
pygame.quit()
You want to look up Tessellation which is the area of math that deals with what MizardX is showing.Folks in 3D Graphcs have to deal with this all the time and there are a variety of tessellation algorithms to take a face with a hole and calculate the triangles needed to render it.
Modern hardware usually can't render concave polygons correctly.
Specifically, there usually isn't even a way to define a polygon with a hole.
You'll need to find a triangulation of the plane around the hole somehow. The best way is probably to create triangles from a vertex of the hole to the nearest vertices of the rectangular face. This will probably create some very thin triangles. if that's not a problem then you're done. if it is then you'll need some mesh fairing/optimization algorithm to create nice looking triangles.
Is alpha blending out of the question? If not, just texture the sides with holes using a texture that has transparency in the middle. You have to do more rendering of polygons since you can't take advantage of drawing front-to-back and ignoring covered faces, but it might be faster than having a lot tiny triangles.
I'm imagining 4 triangle fans coming from the 4 corners of the square.
Just a thought -
If you're into cheating (as done many times in games), you can always construct a regular cube but have the texture for the two faces you desire with a hole (alpha = 0), you can then either clip it in the shader, or blend it (in which case you need to render with Z sort).
You get the inside hole by constructing an inner cylinder facing inwards and with no caps.
Of course this will only work if the geometry is not important to you.

Resources