Как проверить, находится ли точка в тетраэдре или нет?

Я знаю все координаты тетраэдра и точку, которую я хотел бы определить. Так кто-нибудь знает, как это сделать? Я попытался определить принадлежность точки к каждому треугольнику тетраэдра, и если это верно для всех треугольников, то точка находится в тетраэдре. Но это абсолютно неправильно.

3 ответа

Решение

Вы определяете тетраэдр четырьмя вершинами. A B C и D Следовательно, вы также можете иметь 4 треугольника, определяющих поверхность тетраэдра.

Вы знаете, черт возьми, если точка P находится на другой стороне плоскости. Нормаль каждой плоскости направлена ​​в сторону от центра тетраэдра. Так что вам просто нужно протестировать против 4 самолетов.

Ваше уравнение плоскости выглядит так: a*x+b*y+c*z+d=0 Просто заполните значения точек (x y z). Если знак результата>0, точка находится на той же стороне, что и нормаль, результат == 0, точка лежит на плоскости, и в вашем случае вам нужен третий вариант. <0 означает, что он находится на задней стороне самолета. Если это выполнено для всех 4-х плоскостей, ваша точка находится внутри тетраэдра

Для каждой плоскости тетраэдра проверьте, находится ли точка на той же стороне, что и остальная вершина:

bool SameSide(v1, v2, v3, v4, p)
{
    normal := cross(v2 - v1, v3 - v1)
    dotV4 := dot(normal, v4 - v1)
    dotP := dot(normal, p - v1)
    return Math.Sign(dotV4) == Math.Sign(dotP);
}

И вам нужно проверить это для каждого самолета:

bool PointInTetrahedron(v1, v2, v3, v4, p)
{
    return SameSide(v1, v2, v3, v4, p) &&
           SameSide(v2, v3, v4, v1, p) &&
           SameSide(v3, v4, v1, v2, p) &&
           SameSide(v4, v1, v2, v3, p);               
}

Начиная с решения Хьюга, вот более простой и (даже) более эффективный вариант:

import numpy as np

def tetraCoord(A,B,C,D):
  # Almost the same as Hugues' function, 
  # except it does not involve the homogeneous coordinates.
  v1 = B-A ; v2 = C-A ; v3 = D-A
  mat = np.array((v1,v2,v3)).T
  # mat is 3x3 here
  M1 = np.linalg.inv(mat)
  return(M1)

def pointInside(v1,v2,v3,v4,p):
  # Find the transform matrix from orthogonal to tetrahedron system
  M1=tetraCoord(v1,v2,v3,v4)
  # apply the transform to P (v1 is the origin)
  newp = M1.dot(p-v1)
  # perform test
  return (np.all(newp>=0) and np.all(newp <=1) and np.sum(newp)<=1)

В системе координат, связанной с тетраэдром, противоположная грань от начала координат (обозначенная здесь v1) характеризуется как x+y+z=1. Таким образом, для полупространства с той же стороны, что и v1 от этой грани, выполняется x+y+z<1.

Для сравнения, вот полный код для сравнения методов, предложенных Нико, Хьюгом и мной:

import numpy as np
import time

def sameside(v1,v2,v3,v4,p):
    normal = np.cross(v2-v1, v3-v1)
    return (np.dot(normal, v4-v1) * np.dot(normal, p-v1) > 0)

# Nico's solution
def pointInside_Nico(v1,v2,v3,v4,p):   
    return sameside(v1, v2, v3, v4, p) and sameside(v2, v3, v4, v1, p) and sameside(v3, v4, v1, v2, p) and sameside(v4, v1, v2, v3, p)      

# Hugues' solution
def tetraCoord(A,B,C,D):
    v1 = B-A ; v2 = C-A ; v3 = D-A
    # mat defines an affine transform from the tetrahedron to the orthogonal system
    mat = np.concatenate((np.array((v1,v2,v3,A)).T, np.array([[0,0,0,1]])))
    # The inverse matrix does the opposite (from orthogonal to tetrahedron)
    M1 = np.linalg.inv(mat)
    return(M1)

def pointInside_Hugues(v1,v2,v3,v4,p):
    # Find the transform matrix from orthogonal to tetrahedron system
    M1=tetraCoord(v1,v2,v3,v4)
    # apply the transform to P
    p1 = np.append(p,1)
    newp = M1.dot(p1)
    # perform test
    return(np.all(newp>=0) and np.all(newp <=1) and sameside(v2,v3,v4,v1,p))    

# Proposed solution
def tetraCoord_Dorian(A,B,C,D):
    v1 = B-A ; v2 = C-A ; v3 = D-A
    # mat defines an affine transform from the tetrahedron to the orthogonal system
    mat = np.array((v1,v2,v3)).T
    # The inverse matrix does the opposite (from orthogonal to tetrahedron)
    M1 = np.linalg.inv(mat)
    return(M1) 

def pointInside_Dorian(v1,v2,v3,v4,p):
    # Find the transform matrix from orthogonal to tetrahedron system
    M1=tetraCoord_Dorian(v1,v2,v3,v4)
    # apply the transform to P
    newp = M1.dot(p-v1)
    # perform test
    return (np.all(newp>=0) and np.all(newp <=1) and np.sum(newp)<=1)

npt=100000    
Pt=np.random.rand(npt,3)
A=np.array([0.1, 0.1, 0.1])
B=np.array([0.9, 0.2, 0.1])
C=np.array([0.1, 0.9, 0.2])
D=np.array([0.3, 0.3, 0.9])

inTet_Nico=np.zeros(shape=(npt,1),dtype=bool)
inTet_Hugues=inTet_Nico
inTet_Dorian=inTet_Nico

start_time = time.time()
for i in range(0,npt):
    inTet_Nico[i]=pointInside_Nico(A,B,C,D,Pt[i,:])
print("--- %s seconds ---" % (time.time() - start_time)) # https://stackru.com/questions/1557571/how-do-i-get-time-of-a-python-programs-execution

start_time = time.time()
for i in range(0,npt):
    inTet_Hugues[i]=pointInside_Hugues(A,B,C,D,Pt[i,:])
print("--- %s seconds ---" % (time.time() - start_time))   

start_time = time.time()    
for i in range(0,npt):
    inTet_Dorian[i]=pointInside_Dorian(A,B,C,D,Pt[i,:])
print("--- %s seconds ---" % (time.time() - start_time)) 

И вот результаты по времени работы:

--- 15.621951341629028 seconds ---
--- 8.97989797592163 seconds ---
--- 4.597853660583496 seconds ---

Учитывая 4 точки A,B,C,D, определяющие невырожденный тетраэдр, и точку P для проверки, одним из способов будет преобразование координат P в систему координат тетраэдра, например, взяв A в качестве начала координат, и векторы BA, CA, DA как единичные векторы.

В этой системе координат все координаты P находятся в диапазоне от 0 до 1, если он находится внутри P, но он также может находиться в любом месте преобразованного куба, определенного в начале координат и 3 единичных векторах. Один из способов утверждать, что P находится внутри (A,B,C,D), состоит в том, чтобы по очереди взять в качестве начала точки (A, B, C и D) и другие три точки, чтобы определить новую систему координат. Этот тест, повторенный 4 раза, эффективен, но его можно улучшить.

Наиболее эффективно преобразовать координаты только один раз и повторно использовать функцию SameSide, как было предложено ранее, например, взять A в качестве источника, преобразовать в систему координат (A,B,C,D), P и A должны лежать на одной и той же сторона плоскости (B,C,D).

Ниже приведена реализация этого теста для numpy/python. Испытания показывают, что этот метод в 2-3 раза быстрее, чем метод плоскостей.

import numpy as np

def sameside(v1,v2,v3,v4,p):
    normal = np.cross(v2-v1, v3-v1)
    return ((np.dot(normal, v4-v1)*p.dot(normal, p-v1) > 0)

def tetraCoord(A,B,C,D):
    v1 = B-A ; v2 = C-A ; v3 = D-A
    # mat defines an affine transform from the tetrahedron to the orthogonal system
    mat = np.concatenate((np.array((v1,v2,v3,A)).T, np.array([[0,0,0,1]])))
    # The inverse matrix does the opposite (from orthogonal to tetrahedron)
    M1 = np.linalg.inv(mat)
    return(M1)

def pointInsideT(v1,v2,v3,v4,p):
    # Find the transform matrix from orthogonal to tetrahedron system
    M1=tetraCoord(v1,v2,v3,v4)
    # apply the transform to P
    p1 = np.append(p,1)
    newp = M1.dot(p1)
    # perform test
    return(np.all(newp>=0) and np.all(newp <=1) and sameside(v2,v3,v4,v1,p))

Я векторизовал решения Дориана и Хьюза, чтобы взять на вход весь массив точек. Я также переместил функцию tetraCoord за пределы функции pointsInside и переименовал обе, так как не было смысла вызывать ее для каждой точки.

На моем компьютере решение и пример @Dorian работают за 2,5 секунды. По тем же данным, мой работает почти в тысячу раз быстрее - 0,003 секунды. Если кому-то по какой-то причине требуется еще большая скорость, импорт пакета GPU cupy как "np" переводит его в диапазон 100 микросекунд.

import time
# alternatively, import cupy as np if len(points)>1e7 and GPU
import numpy as np 

def Tetrahedron(vertices):
    """
    Given a list of the xyz coordinates of the vertices of a tetrahedron, 
    return tetrahedron coordinate system
    """
    origin, *rest = vertices
    mat = (np.array(rest) - origin).T
    tetra = np.linalg.inv(mat)
    return tetra, origin

def pointInside(point, tetra, origin):
    """
    Takes a single point or array of points, as well as tetra and origin objects returned by 
    the Tetrahedron function.
    Returns a boolean or boolean array indicating whether the point is inside the tetrahedron.
    """
    newp = np.matmul(tetra, (point-origin).T).T
    return np.all(newp>=0, axis=-1) & np.all(newp <=1, axis=-1) & (np.sum(newp, axis=-1) <=1)

npt=10000000
points = np.random.rand(npt,3)
# Coordinates of vertices A, B, C and D
A=np.array([0.1, 0.1, 0.1])
B=np.array([0.9, 0.2, 0.1])
C=np.array([0.1, 0.9, 0.2])
D=np.array([0.3, 0.3, 0.9])

start_time = time.time()
vertices = [A, B, C, D]
tetra, origin = Tetrahedron(vertices)
inTet = pointInside(points, tetra, origin)
print("--- %s seconds ---" % (time.time() - start_time)) 

Благодаря сценарию тестового примера Дориана я мог работать над еще одним решением и быстро сравнивать его с уже существующими.

интуиция

для треугольника ABC и точки P, если соединить P с углами, чтобы получить векторы PA, PB,PC, и сравнить два треугольника X и Y, которые охватывают PA,PC и PB,PC, то точка P находится внутри треугольник ABC, если X и Y перекрываются.

Или, другими словами, невозможно построить вектор PA путем линейного объединения PC и PB только с положительными коэффициентами, если P находится в треугольнике ABC.

С этого момента я попытался перенести его на случай тетраэдра и прочитал здесь, что можно проверить, являются ли векторы линейно независимыми, проверив, что определитель матрицы, построенной из векторов в виде столбцов, не равен нулю. Я пробовал разные подходы с использованием детерминантов и наткнулся на один:

Пусть PA, PB, PC, PD - это соединения P с точками тетраэдра ABCD (т.е. PA = A - P и т. Д.). вычислить детерминанты detA = det(PB PC PD), detB, detC и detD (как detA).

Тогда точка P лежит внутри тетраэдра, натянутого на ABCD, если:

detA> 0 и detB < 0 и detC > 0 и detD < 0

или

detA < 0 и detB > 0 и detC < 0 и detD > 0

поэтому детерминанты меняют знак, начиная с отрицательного или начиная с положительного.

Это работает? По всей видимости. Почему это работает? Я не знаю, по крайней мере, не могу это доказать. Может быть, здесь нам поможет кто-нибудь с лучшими математическими навыками.

(РЕДАКТИРОВАТЬ: на самом деле барицентрические координаты могут быть определены с использованием этих определителей, и, в конце концов, барицентрические координаты должны суммироваться до единицы. Это похоже на сравнение объемов тетраэдров, которые охватывают комбинации P с точками A,B,C,D с объемом самого тетраэдра ABCD. Объясненный случай с соблюдением детерминантных знаков до сих пор неясно, работает ли он в целом и я не рекомендую его)

Я изменил тестовый пример, чтобы не проверять n точек Pi на один тетраэдр T, а проверять n точек Pi на n тетраэдров Ti. Все ответы по-прежнему дают правильные результаты. Я думаю, что причина, по которой этот подход работает быстрее, в том, что он не требует обращения матрицы. Я оставил подход TomNorway, реализованный с одним тетраэдром, и оставляю векторизацию этого нового подхода другим, поскольку я не так хорошо знаком с python и numpy.

import numpy as np
import time

def sameside(v1,v2,v3,v4,p):
    normal = np.cross(v2-v1, v3-v1)
    return (np.dot(normal, v4-v1) * np.dot(normal, p-v1) > 0)

# Nico's solution
def pointInside_Nico(v1,v2,v3,v4,p):   
    return sameside(v1, v2, v3, v4, p) and sameside(v2, v3, v4, v1, p) and sameside(v3, v4, v1, v2, p) and sameside(v4, v1, v2, v3, p)      

# Hugues' solution
def tetraCoord(A,B,C,D):
    v1 = B-A ; v2 = C-A ; v3 = D-A
    # mat defines an affine transform from the tetrahedron to the orthogonal system
    mat = np.concatenate((np.array((v1,v2,v3,A)).T, np.array([[0,0,0,1]])))
    # The inverse matrix does the opposite (from orthogonal to tetrahedron)
    M1 = np.linalg.inv(mat)
    return(M1)

def pointInside_Hugues(v1,v2,v3,v4,p):
    # Find the transform matrix from orthogonal to tetrahedron system
    M1=tetraCoord(v1,v2,v3,v4)
    # apply the transform to P
    p1 = np.append(p,1)
    newp = M1.dot(p1)
    # perform test
    return(np.all(newp>=0) and np.all(newp <=1) and sameside(v2,v3,v4,v1,p))    

#Dorian's solution
def tetraCoord_Dorian(A,B,C,D):
    v1 = B-A ; v2 = C-A ; v3 = D-A
    # mat defines an affine transform from the tetrahedron to the orthogonal system
    mat = np.array((v1,v2,v3)).T
    # The inverse matrix does the opposite (from orthogonal to tetrahedron)
    M1 = np.linalg.inv(mat)
    return(M1) 

def pointInside_Dorian(v1,v2,v3,v4,p):
    # Find the transform matrix from orthogonal to tetrahedron system
    M1=tetraCoord_Dorian(v1,v2,v3,v4)
    # apply the transform to P
    newp = M1.dot(p-v1)
    # perform test
    return (np.all(newp>=0) and np.all(newp <=1) and np.sum(newp)<=1)

#TomNorway's solution adapted to cope with n tetrahedrons

def Tetrahedron(vertices):
    """
    Given a list of the xyz coordinates of the vertices of a tetrahedron, 
    return tetrahedron coordinate system
    """
    origin, *rest = vertices
    mat = (np.array(rest) - origin).T
    tetra = np.linalg.inv(mat)
    return tetra, origin

def pointInside(point, tetra, origin):
    """
    Takes a single point or array of points, as well as tetra and origin objects returned by 
    the Tetrahedron function.
    Returns a boolean or boolean array indicating whether the point is inside the tetrahedron.
    """
    newp = np.matmul(tetra, (point-origin).T).T
    return np.all(newp>=0, axis=-1) & np.all(newp <=1, axis=-1) & (np.sum(newp, axis=-1) <=1)



# Proposed solution
def det3x3_Philipp(b,c,d):
    return b[0]*c[1]*d[2] + c[0]*d[1]*b[2] + d[0]*b[1]*c[2] - d[0]*c[1]*b[2] - c[0]*b[1]*d[2] - b[0]*d[1]*c[2]

def pointInside_Philipp(v0,v1,v2,v3,p):
    a = v0 - p
    b = v1 - p
    c = v2 - p
    d = v3 - p
    detA = det3x3_Philipp(b,c,d)
    detB = det3x3_Philipp(a,c,d)
    detC = det3x3_Philipp(a,b,d)
    detD = det3x3_Philipp(a,b,c)
    ret0 = detA > 0.0 and detB < 0.0 and detC > 0.0 and detD < 0.0
    ret1 = detA < 0.0 and detB > 0.0 and detC < 0.0 and detD > 0.0
    return ret0 or ret1


npt=100000
Pt= np.array([ np.array([p[0]-0.5,p[1]-0.5,p[2]-0.5]) for p in np.random.rand(npt,3)])
A=np.array([ np.array([p[0]-0.5,p[1]-0.5,p[2]-0.5]) for p in np.random.rand(npt,3)])
B=np.array([ np.array([p[0]-0.5,p[1]-0.5,p[2]-0.5]) for p in np.random.rand(npt,3)])
C=np.array([ np.array([p[0]-0.5,p[1]-0.5,p[2]-0.5]) for p in np.random.rand(npt,3)])
D=np.array([ np.array([p[0]-0.5,p[1]-0.5,p[2]-0.5]) for p in np.random.rand(npt,3)])

inTet_Nico=np.zeros(shape=(npt,1),dtype=bool)
inTet_Hugues=np.copy(inTet_Nico)
inTet_Dorian=np.copy(inTet_Nico)
inTet_Philipp=np.copy(inTet_Nico)



print("non vectorized, n points, different tetrahedrons:")

start_time = time.time()
for i in range(0,npt):
    inTet_Nico[i]=pointInside_Nico(A[i,:],B[i,:],C[i,:],D[i,:],Pt[i,:])
print("Nico's:   --- %s seconds ---" % (time.time() - start_time)) # https://stackru.com/questions/1557571/how-do-i-get-time-of-a-python-programs-execution

start_time = time.time()
for i in range(0,npt):
    inTet_Hugues[i]=pointInside_Hugues(A[i,:],B[i,:],C[i,:],D[i,:],Pt[i,:])
print("Hugues':  --- %s seconds ---" % (time.time() - start_time))   

start_time = time.time()    
for i in range(0,npt):
    inTet_Dorian[i]=pointInside_Dorian(A[i,:],B[i,:],C[i,:],D[i,:],Pt[i,:])
print("Dorian's: --- %s seconds ---" % (time.time() - start_time))

start_time = time.time()
for i in range(0,npt):
    inTet_Philipp[i]=pointInside_Philipp(A[i,:],B[i,:],C[i,:],D[i,:],Pt[i,:])
print("Philipp's:--- %s seconds ---" % (time.time() - start_time))   

print("vectorized, n points, 1 tetrahedron:")

start_time = time.time()
vertices = [A[0], B[0], C[0], D[0]]
tetra, origin = Tetrahedron(vertices)
inTet_Tom = pointInside(Pt, tetra, origin)
print("TomNorway's: --- %s seconds ---" % (time.time() - start_time)) 


for i in range(0,npt):
    assert inTet_Hugues[i] == inTet_Nico[i]
    assert inTet_Dorian[i] == inTet_Hugues[i]
    #assert inTet_Tom[i] == inTet_Dorian[i] can not compare because Tom implements 1 tetra instead of n
    assert inTet_Philipp[i] == inTet_Dorian[i]

'''errors = 0
for i in range(0,npt):
    if ( inTet_Philipp[i] != inTet_Dorian[i]):
        errors = errors + 1 
print("errors " + str(errors))'''

Полученные результаты:

non vectorized, n points, different tetrahedrons:
Nico's:   --- 25.439453125 seconds ---
Hugues':  --- 28.724457263946533 seconds ---
Dorian's: --- 15.006574153900146 seconds ---
Philipp's:--- 4.389788389205933 seconds ---
vectorized, n points, 1 tetrahedron:
TomNorway's: --- 0.008165121078491211 seconds ---
Другие вопросы по тегам