3

我编写了一个 python 脚本,它从查询点 (p) 中找到表面上最近点的 UV 坐标。曲面由四个线性边定义,这些边由逆时针列出的四个已知点 (p0,p1,p2,p3) 构成。

(请忽略小红球)

我的方法的问题是它非常慢(以低精度阈值进行 5000 次查询大约需要 10 秒。

我正在寻找一种更好的方法来实现我想要的,或者寻找让我的代码更高效的建议。我唯一的限制是它必须用 python 编写。

import numpy as np

# Define constants
LARGE_VALUE=99999999.0
SMALL_VALUE=0.00000001
SUBSAMPLES=10.0

def closestPointOnLineSegment(a,b,c):
    ''' Given two points (a,b) defining a line segment and a query point (c)
        return the closest point on that segment, the distance between
        query and closest points, and a u value derived from the results
    '''

    # Check if c is same as a or b
    ac=c-a
    AC=np.linalg.norm(ac)
    if AC==0.:
        return c,0.,0.

    bc=c-b
    BC=np.linalg.norm(bc)
    if BC==0.:
        return c,0.,1.


    # See if segment length is 0
    ab=b-a
    AB=np.linalg.norm(ab)
    if AB == 0.:
        return a,0.,0.

    # Normalize segment and do edge tests
    ab=ab/AB
    test=np.dot(ac,ab)
    if test < 0.:
        return a,AC,0.
    elif test > AB:
        return b,BC,1.

    # Return closest xyz on segment, distance, and u value
    p=(test*ab)+a
    return p,np.linalg.norm(c-p),(test/AB)




def surfaceWalk(e0,e1,p,v0=0.,v1=1.):
    ''' Walk on the surface along 2 edges, for each sample segment
        look for closest point, recurse until the both sampled edges
        are smaller than SMALL_VALUE
    '''

    edge0=(e1[0]-e0[0])
    edge1=(e1[1]-e0[1])
    len0=np.linalg.norm(edge0*(v1-v0))
    len1=np.linalg.norm(edge1*(v1-v0))

    vMin=v0
    vMax=v1
    closest_d=0.
    closest_u=0.
    closest_v=0.
    ii=0.
    dist=LARGE_VALUE

    for i in range(int(SUBSAMPLES)+1):
        v=v0+((v1-v0)*(i/SUBSAMPLES))
        a=(edge0*v)+e0[0]
        b=(edge1*v)+e0[1]

        closest_p,closest_d,closest_u=closestPointOnLineSegment(a,b,p)

        if closest_d < dist:
            dist=closest_d
            closest_v=v
            ii=i

    # If both edge lengths <= SMALL_VALUE, we're within our precision treshold so return results
    if len0 <= SMALL_VALUE and len1 <= SMALL_VALUE:
        return closest_p,closest_d,closest_u,closest_v

    # Threshold hasn't been met, set v0 anf v1 limits to either side of closest_v and keep recursing
    vMin=v0+((v1-v0)*(np.clip((ii-1),0.,SUBSAMPLES)/SUBSAMPLES))
    vMax=v0+((v1-v0)*(np.clip((ii+1),0.,SUBSAMPLES)/SUBSAMPLES))
    return surfaceWalk(e0,e1,p,vMin,vMax)




def closestPointToPlane(p0,p1,p2,p3,p,debug=True):
    ''' Given four points defining a quad surface (p0,p1,p2,3) and
        a query point p. Find the closest edge and begin walking
        across one end to the next until we find the closest point 
    '''

    # Find the closest edge, we'll use that edge to start our walk
    c,d,u,v=surfaceWalk([p0,p1],[p3,p2],p)
    if debug:
        print 'Closest Point:     %s'%c
        print 'Distance to Point: %s'%d
        print 'U Coord:           %s'%u
        print 'V Coord:           %s'%v

    return c,d,u,v



p0 = np.array([1.15, 0.62, -1.01])
p1 = np.array([1.74, 0.86, -0.88])
p2 = np.array([1.79, 0.40, -1.46])
p3 = np.array([0.91, 0.79, -1.84])
p =  np.array([1.17, 0.94, -1.52])
closestPointToPlane(p0,p1,p2,p3,p)


Closest Point:     [ 1.11588876  0.70474519 -1.52660706]
Distance to Point: 0.241488104197
U Coord:           0.164463481066
V Coord:           0.681959858995
4

1 回答 1

5

如果您的表面看起来是双曲抛物面,您可以将其s上的一个点参数化为:

s = p0 + u * (p1 - p0) + v * (p3 - p0) + u * v * (p2 - p3 - p1 + p0)

以这种方式做事,这条线p0p3有方程u = 0, p1p2is u = 1, p0p1isv = 0p2p3is v = 1。我无法想出一种方法来为最接近的点提出解析表达式p,但scipy.optimize可以为您完成数字工作:

import numpy as np
from scipy.optimize import minimize

p0 = np.array([1.15, 0.62, -1.01])
p1 = np.array([1.74, 0.86, -0.88])
p2 = np.array([1.79, 0.40, -1.46])
p3 = np.array([0.91, 0.79, -1.84])
p =  np.array([1.17, 0.94, -1.52])

def fun(x, p0, p1, p2, p3, p):
    u, v = x
    s = u*(p1-p0) + v*(p3-p0) + u*v*(p2-p3-p1+p0) + p0
    return np.linalg.norm(p - s)

>>> minimize(fun, (0.5, 0.5), (p0, p1, p2, p3, p))
  status: 0
 success: True
    njev: 9
    nfev: 36
     fun: 0.24148810420527048
       x: array([ 0.16446403,  0.68196253])
 message: 'Optimization terminated successfully.'
    hess: array([[ 0.38032445,  0.15919791],
       [ 0.15919791,  0.44908365]])
     jac: array([ -1.27032399e-06,   6.74091280e-06])

的返回minimize是一个对象,您可以通过其属性访问值:

>>> res = minimize(fun, (0.5, 0.5), (p0, p1, p2, p3, p))
>>> res.x # u and v coordinates of the nearest point
array([ 0.16446403,  0.68196253])
>>> res.fun # minimum distance
0.24148810420527048

关于如何在没有 scipy 的情况下寻找解决方案的一些指示... 将s如上参数化的点与通用点连接的向量pp-s. 要找出最近的点,您可以采用两种不同的方式来得出相同的结果:

  1. 计算该向量的长度(p-s)**2,取其导数 wrtu并将v它们等同于零。
  2. 计算在 处与 hypar 相切的两个向量s,可以找到ds/duds/dv,并强制它们的内积p-s为零。

如果你把这些计算出来,你最终会得到两个方程,这需要大量的操作才能得到类似 或 的三次或四次方程uv所以没有精确的解析解,尽管你可以用数值求解仅使用 numpy。一个更简单的选择是计算这些方程,直到得到这两个方程,其中a = p1-p0, b = p3-p0, c = p2-p3-p1+p0, s_ = s-p0, p_ = p-p0

u = (p_ - v*b)*(a + v*c) / (a + v*c)**2
v = (p_ - u*a)*(b + u*c) / (b + u*c)**2

您不能轻易为此提出分析解决方案,但您可以希望,如果您使用这两个关系来迭代试验解决方案,它会收敛。对于您的测试用例,它确实有效:

def solve_uv(x0, p0, p1, p2, p3, p, tol=1e-6, niter=100):
    a = p1 - p0
    b = p3 - p0
    c = p2 - p3 - p1 + p0
    p_ = p - p0
    u, v = x0
    error = None
    while niter and (error is None or error > tol):
        niter -= 1
        u_ = np.dot(p_ - v*b, a + v*c) / np.dot(a + v*c, a + v*c)
        v_ = np.dot(p_ - u*a, b + u*c) / np.dot(b + u*c, b + u*c)
        error = np.linalg.norm([u - u_, v - v_])
        u, v = u_, v_
    return np.array([u, v]), np.linalg.norm(u*a + v*b +u*v*c + p0 - p)

>>> solve_uv([0.5, 0.5], p0, p1, p2, p3, p)
(array([ 0.16446338,  0.68195998]), 0.2414881041967159)

我认为这不能保证收敛,但对于这种特殊情况,它似乎工作得很好,只需要 15 次迭代即可达到要求的容差。

于 2013-09-17T23:32:55.117 回答