テッセレーション内の三角形の数がN
の場合、各三角形の3つの頂点すべてのN X 3 X 3
座標を格納する(x, y, z)
配列があります。私の目標は、各三角形について、同じエッジを共有する隣接する三角形を見つけることです。複雑な部分は、ネイバーカウントを繰り返さないセットアップ全体です。つまり、三角形j
がすでに三角形i
の隣接としてカウントされている場合、三角形i
は再び三角形j
の隣接としてカウントされるべきではありません。このように、各インデックス三角形のネイバーのリストを格納するマップが必要です。インデックスi
の三角形から始めると、インデックスi
には3つの近傍があり、他のすべての近傍は2つ以下になります。例として、三角形の頂点を格納する配列があるとします。
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]]])
頂点インデックス2
からカウントを開始するとします。つまり、頂点が[[1.0, 2.0, 3.0],[2.0, 1.0, 3.0],[3.0, 1.0, 2.0]]
の場合、出力を次のようにします。
neighbour = [[], [], [0, 1, 3], [4, 5], [], []].
更新:@ Ajax1234からの回答に従って、出力を保存する良い方法は、@ Ajax1234が示した方法と同じだと思います。ただし、その出力にはあいまいさがあり、誰の隣人がどれであるかを知ることはできません。配列の例は良くありませんが、私は二十面体からの実際の頂点を持っています、そして私が与えられた三角形から始めるならば、私は最初のもののために3つの隣人と残りのために2つの隣人を持つことが保証されます(すべての三角形の数がなくなるまで) 。この点で、次の配列があるとします。
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]]]
@ Ajax1234によって以下の回答に示されているBFSアルゴリズムは、次の出力を提供します。
[0, 1, 3, 7, 4, 5, 6]
最後の要素の位置を次のように入れ替えるだけの場合
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]]]
これはの出力を与えます
[0, 1, 3, 4, 5, 6, 7].
グリッド内の位置はまったく変更されておらず、交換されただけなので、これは一種のあいまいです。したがって、一貫した検索方法を使用したいと思います。たとえば、インデックス2でネイバーを初めて検索すると、[0, 1, 3]
とvertices1
の両方にvertices2
が表示されます。次に、インデックス0で検索を実行すると、何も検出されないため、次の要素1は、7
のインデックスvertices1
と、5
のインデックスvertices2
を見つける必要があります。したがって、現在の出力は、[0, 1, 3, 7]
および[0, 1, 3, 5]
に対してそれぞれvertices1
、vertices2
である必要があります。次に、インデックス3
などに移動します。すべての検索を使い果たした後、最初の出力の最終出力は次のようになります。
[0, 1, 3, 7, 4, 5, 6]
そしてそれは2番目のはずです
[0, 1, 3, 5, 4, 6, 7].
これを達成するための効率的な方法は何でしょうか?
networkx
ライブラリを使用する場合は、その高速なbfs実装を利用できます。別の依存関係を追加するのは面倒ですが、パフォーマンスが大幅に向上するようです。以下を参照してください。
import numpy as np
from scipy import spatial
import networkx
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]]])
vertices1 = np.array([[[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]]])
def make(N=3000):
"""create a N random points and triangulate"""
points= np.random.uniform(-10, 10, (N, 3))
tri = spatial.Delaunay(points[:, :2])
return points[tri.simplices]
def bfs_tree(triangles, root=0, return_short=True):
"""convert triangle list to graph with vertices = triangles,
edges = pairs of triangles with shared Edge and compute bfs tree
rooted at triangle number root"""
# use the old view as void trick to merge triplets, so they can
# for example be easily compared
tr_as_v = triangles.view(f'V{3*triangles.dtype.itemsize}').reshape(
triangles.shape[:-1])
# for each triangle write out its edges, this involves doubling the
# data becaues each vertex occurs twice
tr2 = np.concatenate([tr_as_v, tr_as_v], axis=1).reshape(-1, 3, 2)
# sort vertices within edges ...
tr2.sort(axis=2)
# ... and glue them together
tr2 = tr2.view(f'V{6*triangles.dtype.itemsize}').reshape(
triangles.shape[:-1])
# to find shared edges, sort them ...
idx = tr2.ravel().argsort()
tr2s = tr2.ravel()[idx]
# ... and then compare consecutive ones
pairs, = np.where(tr2s[:-1] == tr2s[1:])
assert np.all(np.diff(pairs) >= 2)
# these are the edges of the graph, the vertices are implicitly
# named 0, 1, 2, ...
edges = np.concatenate([idx[pairs,None]//3, idx[pairs+1,None]//3], axis=1)
# construct graph ...
G = networkx.Graph(edges.tolist())
# ... and let networkx do its magic
res = networkx.bfs_tree(G, root)
if return_short:
# sort by distance from root and then by actual path
sp = networkx.single_source_shortest_path(res, root)
sp = [sp[i] for i in range(len(sp))]
sp = [(len(p), p) for p in sp]
res = sorted(range(len(res.nodes)), key=sp.__getitem__)
return res
デモ:
# OP's second example:
>>> bfs_tree(vertices1, 2)
[2, 0, 1, 3, 7, 4, 5, 6]
>>>
# large random example
>>> random_data = make()
>>> random_data.shape
(5981, 3, 3)
>>> bfs = bfs_tree(random_data)
# returns instantly
@ Ajax1234の指導のおかげで、私は答えを見つけました。リスト要素の比較方法に基づいて、少し複雑なことがありました。これが1つの実用的なアプローチです:
import numpy as np
from collections import deque
import time
d = [[[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]]]
def bfs(d, start):
queue = deque([d[start]])
seen = [start]
results = []
while queue:
_vertices = queue.popleft()
current = [[i, a] for i, a in enumerate(d) if len([x for x in a if x in _vertices])==2 and i not in seen]
if len(current)>0:
current_array = np.array(current, dtype=object)
current0 = list(current_array[:, 0])
current1 = list(current_array[:, 1])
results.extend(current0)
queue.extend(current1)
seen.extend(current0)
return results
time1 = time.time()
xo = bfs(d, 2)
print(time.time()-time1)
print(bfs(d, 2))
サイズが(3000, 3, 3)
の配列の場合、コードの実行には現在18
秒かかります。 @numba.jit(parallel = True, error_model='numpy')
を追加すると、実際には30
秒かかります。おそらく、queue
がnumba
でサポートされていないことが原因です。このコードをより速くする方法を誰かが提案できれば幸いです。
更新
コードにいくつかの冗長性がありましたが、現在は削除されており、サイズ14
のd
に対して、コードは30
秒ではなく(4000 X 3 X 3)
秒で実行されます。まだ恒星ではありませんが、順調に進んでおり、コードはよりクリーンに見えます。
あなたが説明しているプロセスは、幅優先探索に似た音を出します。これは、隣接する三角形を見つけるために利用できます。ただし、空のリストが最終出力でどのように終了するかが不明なため、出力は単にインデックスを提供します。
from collections import deque
d = [[[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]]]
def bfs(d, start):
queue = deque([d[start]])
seen = [start]
results = []
while queue:
_vertices = queue.popleft()
exists_at = [i for i, a in enumerate(d) if a == _vertices][0]
current = [i for i, a in enumerate(d) if any(c in a for c in _vertices) and i != exists_at and i not in seen]
results.extend(current)
queue.extend([a for i, a in enumerate(d) if any(c in a for c in _vertices) and i != exists_at and i not in seen])
seen.extend(current)
return results
print(bfs(d, 2))
出力:
[0, 1, 3, 4, 5]
関数は、ソースコードのコメントによって文書化されています。通常のドキュメントは本当に素晴らしいでしょう。また、初めて使用したときはそれほど簡単ではありませんが、基本を理解していれば、強力で使いやすいパッケージです。
最大の問題は、クリーンなメッシュ定義を取得する方法だと思います。 (stl形式のように)頂点座標しかない場合、2つのフロートが等しいポイントが明確に定義されていないため、問題が発生する可能性があります。
例
import trimesh
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]]])
#generate_faces
# I assume here that your input format is N x NPoints x xyz
faces=np.arange(vertices.shape[0]*3).reshape(-1,3)
#reshape_vertices (nx3)
vertices=vertices.reshape(-1,3)
#Create mesh object
mesh=trimesh.Trimesh(vertices=vertices, faces=faces)
# Set the tolerance to define which vertices are equal (default 1e-8)
# It is easy to prove that int(5)==int(5) but is 5.000000001 equal to 5.0 or not?
# This depends on the algorithm/ programm from which you have imported the mesh....
# To find a proper value for the tolerance and to heal the mesh if necessary, will
# likely be the most complicated part
trimesh.constants.tol.merge=tol
#merge the vertices
mesh.merge_vertices()
# At this stage you may need some sort of healing algorithm..
# eg. reconstruct the input
mesh.vertices[mesh.faces]
#get for example the faces, vertices
mesh.faces #These are indices to the vertices
mesh.vertices
# get the faces which touch each other on the edges
mesh.face_adjacency
これにより、面がエッジを共有する単純な2D配列が得られます。私は個人的にこのフォーマットをさらに計算するために使用します。定義に固執したい場合は、nx3 numpy配列を作成します(各三角形には最大3つのエッジネイバーが必要です)。欠落している値は、たとえばNaNまたは意味のあるもので埋めることができます。
あなたが本当にそれをしたいのであれば、私は効率的な方法を追加することができます。