I have a triangular tessellation like the one shown in the figure.
Given N
number of triangles in the tessellation, I have a N X 3 X 3
array which stores (x, y, z)
coordinates of all three vertices of each triangle. My goal is to find for each triangle the neighbouring triangle sharing the same edge. The is an intricate part is the whole setup that I do not repeat the neighbour count. That is if triangle j
was already counted as a neighbour of triangle i
, then triangle i
should not be again counted as neighbour of triangle j
. This way, I would like to have a map storing list of neighbours for each index triangle. If I start with a triangle in index i
, then index i
will have three neighbours, and all others will have two or less. As an illustration suppose I have an array which stores vertices of the triangle:
import numpy as np
vertices = np.array([[[2.0, 1.0, 3.0],[3.0, 1.0, 2.0],[1.2, 2.5, -2.0]],
[[3.0, 1.0, 2.0],[1.0, 2.0, 3.0],[1.2, -2.5, -2.0]],
[[1.0, 2.0, 3.0],[2.0, 1.0, 3.0],[3.0, 1.0, 2.0]],
[[1.0, 2.0, 3.0],[2.0, 1.0, 3.0],[2.2, 2.0, 1.0]],
[[1.0, 2.0, 3.0],[2.2, 2.0, 1.0],[4.0, 1.0, 0.0]],
[[2.0, 1.0, 3.0],[2.2, 2.0, 1.0],[-4.0, 1.0, 0.0]]])
Suppose I start my count from vertex index 2
, i.e. the one with the vertices [[1.0, 2.0, 3.0],[2.0, 1.0, 3.0],[3.0, 1.0, 2.0]]
, then, I would like my output to be something like:
neighbour = [[], [], [0, 1, 3], [4, 5], [], []].
Update: Following the answer from @Ajax1234, I think a good way of storing the output is just like how @Ajax1234 has demonstrated. However, there is ambiguity in that output, in a sense that it is not possible to know whose neighbour is which. Although the example array are not good, I have an actual vertices from icosahedron, then if I start with a given triangle, I am guaranteed to have 3 neighbours for the first one, and two neighbours for rest (until all the triangle counts deplete). In this regard, suppose I have a following array:
vertices1 = [[[2, 1, 3], [3, 1, 2], [1, 2, -2]],
[[3, 1, 2], [1, 2, 3], [1, -2, 2]],
[[1, 2, 3], [2, 1, 3], [3, 1, 2]],
[[1, 2, 3], [2, 1, 3], [2, 2, 1]],
[[1, 2, 3], [2, 2, 1], [4, 1, 0]],
[[2, 1, 3], [2, 2, 1], [-4, 1, 0]],
[[3, 1, 3], [2, 2, 1], [-4, 1, 0]],
[[8, 1, 2], [1, 2, 3], [1, -2, 2]]]
The BFS algorithm shown in the answer below by @Ajax1234 gives the output of
[0, 1, 3, 7, 4, 5, 6]
while if I just swap the position of the last element such that
vertices2 = [[[2, 1, 3], [3, 1, 2], [1, 2, -2]],
[[3, 1, 2], [1, 2, 3], [1, -2, 2]],
[[1, 2, 3], [2, 1, 3], [3, 1, 2]],
[[1, 2, 3], [2, 1, 3], [2, 2, 1]],
[[1, 2, 3], [2, 2, 1], [4, 1, 0]],
[[8, 1, 2], [1, 2, 3], [1, -2, 2]],
[[2, 1, 3], [2, 2, 1], [-4, 1, 0]],
[[3, 1, 3], [2, 2, 1], [-4, 1, 0]]]
which gives an output of
[0, 1, 3, 4, 5, 6, 7].
This is kind of ambiguous, as the positions in the gird have not been changed at all, they were just swapped. Therefore, I would like to have a consistent way the search is carried. For example, first time search of neighbours at index 2 gives [0, 1, 3]
for both vertices1
and vertices2
, now I would like the search to be at index 0, which finds nothing and thus go to next element 1 should find index 7
for vertices1
and index 5
for vertices2
. Thus the current output should be [0, 1, 3, 7]
, [0, 1, 3, 5]
for vertices1
and vertices2
respectively. Next we go to index 3
, and so on. After we have exhausted all the search, the final output for the first should be
[0, 1, 3, 7, 4, 5, 6]
and that for the second should
[0, 1, 3, 5, 4, 6, 7].
What would be the efficient way to achieve this?
The process you are describing sounds similar to a breadth-first search, which can be utilized to find the triangles which are neighbors. The output merely gives the indices, however, since it is unclear as to how the empty lists end up in the final output:
Output:
I figured out the answer, thanks to the guidance of @Ajax1234. There was a small intricacy, based on how you compare the list elements. Here is one working approach:
For an array of size
(3000, 3, 3)
, the code currently takes18
seconds to run. If I add@numba.jit(parallel = True, error_model='numpy')
, then it actually takes30
seconds. It is probably becausequeue
is not supported bynumba
. I would be pleased if any one could suggest how this code could be made faster.Update
There were some redundancies in the code, which has now been removed, and the code run in
14
seconds, instead of30
seconds, for a ofd
of size(4000 X 3 X 3)
. Still not stellar, but a good progress, and the code looks cleaner now.If you are willing to use the
networkx
library, you can take advantage of its fast bfs implementation. I know, adding another dependency is annoying, but the performance gain seems huge, see below.Demo:
You can use trimesh
The functions are documented by comments in the source code. A normal documentation would be realy nice. I also find it not that straight forward when I used it the first time, but if you have the basics it is a powerful and easy to use package.
I assume that the biggest problem is how to get to a clean mesh definitions. If you have only vertex coordinates (like in the stl-format) problems may occur, because it is not well defined at which points two floats are equal.
Example
This gives a simple 2d array which faces share a edge. I would personally use this format for further calculation. If you want to stick to your definition I would create a nx3 numpy array (each triangle should have max. 3 edge-neighbours). The missing values can be filled for example with NaNs or something meaningfull.
I can add an efficient way, if you really want to do that.