【问题标题】:Calculating surface normal in Python using Newell's Method使用 Newell 方法在 Python 中计算表面法线
【发布时间】:2018-10-28 01:34:35
【问题描述】:

我正在尝试根据来自here 的以下伪代码,在 Python 中实现 Newell 方法来计算表面法线向量。

Begin Function CalculateSurfaceNormal (Input Polygon) Returns Vector

   Set Vertex Normal to (0, 0, 0)

   Begin Cycle for Index in [0, Polygon.vertexNumber)

      Set Vertex Current to Polygon.verts[Index]
      Set Vertex Next    to Polygon.verts[(Index plus 1) mod Polygon.vertexNumber]

      Set Normal.x to Sum of Normal.x and (multiply (Current.y minus Next.y) by (Current.z plus Next.z))
      Set Normal.y to Sum of Normal.y and (multiply (Current.z minus Next.z) by (Current.x plus Next.x))
      Set Normal.z to Sum of Normal.z and (multiply (Current.x minus Next.x) by (Current.y plus Next.y))

   End Cycle

   Returning Normalize(Normal)

End Function

这是我的代码:

Point3D = collections.namedtuple('Point3D', 'x y z')

def surface_normal(poly):
    n = [0.0, 0.0, 0.0]

    for i, v_curr in enumerate(poly):
        v_next = poly[(i+1) % len(poly)]
        n[0] += (v_curr.y - v_next.y) * (v_curr.z - v_next.z)
        n[1] += (v_curr.z - v_next.z) * (v_curr.x - v_next.x)
        n[2] += (v_curr.x - v_next.x) * (v_curr.y - v_next.y)

    normalised = [i/sum(n) for i in n]

    return normalised

def test_surface_normal():
    poly = [Point3D(0.0, 0.0, 0.0),
            Point3D(0.0, 1.0, 0.0),
            Point3D(1.0, 1.0, 0.0),
            Point3D(1.0, 0.0, 0.0)]

    assert surface_normal(poly) == [0.0, 0.0, 1.0]

这在标准化步骤中失败,因为此时的n[0.0, 0.0, 0.0]。如果我理解正确,应该是[0.0, 0.0, 1.0](Wolfram Alpha 的confirmed)。

我在这里做错了什么?有没有更好的方法在 python 中计算表面法线?我的多边形将始终是平面的,因此如果有其他方法,纽厄尔方法并不是绝对必要的。

【问题讨论】:

    标签: python 3d geometry


    【解决方案1】:

    好吧,这个问题实际上是一个愚蠢的问题。

    像这样的行:

    n[0] += (v_curr.y - v_next.y) * (v_curr.z - v_next.z)
    

    应该是:

    n[0] += (v_curr.y - v_next.y) * (v_curr.z + v_next.z) 
    

    第二组括号中的值应该相加,而不是相减。

    【讨论】:

      【解决方案2】:

      如果您想要 Newell 方法的替代方法,您可以使用 2 个非平行向量的叉积。这应该适用于您提供的任何平面形状。我知道理论说它适用于凸多边形,但我们在 Wolfram Alpha 上看到的示例会为偶数凹多边形返回适当的表面法线(例如 bowtie 多边形)。

      【讨论】:

      • 是的,我得到了除以零 - 因为我试图规范化 [0,0,0] 这不是一个有效的向量。那不应该是代码中那个时候的值。我所说的平面,是指第一个。 3D 空间中的 2D 多边形。
      • 好吧,我也明白了(我通过使用这个来避免它--normalised = [i/sum(n) if sum(n)!=0.0 else 0 for i in n]--只是为了克服它)。你的多边形会一直是凸的,还是有可能不是?
      • 它们可以是任何形状,但不是自相交的。您的解决方法可能会失败,例如使用向量 [-0.5, 0.0, 0.5]。我使用的是 try: except: 块,但如果算法正常工作,则不需要它。
      • 我仍然会努力让它发挥作用,但你关于在某些情况下失败的观点是正确的。这应该适用于所有情况,只是为了在您的代码中快速避免除零:normalised = [i/sum(n) if len(list(set(n)))!=1 or sum(n)!=0 else 0.0 for i in n]
      • 我看到了你的编辑,但想打个电话,JIC。我打算推荐 Cross Product 方法,但我相信它只适用于凸多边形,这就是我在上面询问的原因。您可能需要在凹多边形上对其进行测试,以确保在这些情况下不会出错。
      【解决方案3】:

      我认为有必要澄清一些关于上面 Newell 方法的信息实现的几点。在第一次阅读https://stackoverflow.com/users/1706564/jamie-bull 更新时,我认为符号的变化仅涉及第一个等式。但事实上它们都是。其次,提供一个比较叉积的方法。最后,处理除以零的问题 - 但可以只除以 epsilon。我也使用 numpy 代替。

      import numpy as np
      
      def surface_normal_newell(poly):
      
          n = np.array([0.0, 0.0, 0.0])
      
          for i, v_curr in enumerate(poly):
              v_next = poly[(i+1) % len(poly),:]
              n[0] += (v_curr[1] - v_next[1]) * (v_curr[2] + v_next[2]) 
              n[1] += (v_curr[2] - v_next[2]) * (v_curr[0] + v_next[0])
              n[2] += (v_curr[0] - v_next[0]) * (v_curr[1] + v_next[1])
      
          norm = np.linalg.norm(n)
          if norm==0:
              raise ValueError('zero norm')
          else:
              normalised = n/norm
      
          return normalised
      
      
      def surface_normal_cross(poly):
      
          n = np.cross(poly[1,:]-poly[0,:],poly[2,:]-poly[0,:])
      
          norm = np.linalg.norm(n)
          if norm==0:
              raise ValueError('zero norm')
          else:
              normalised = n/norm
      
          return normalised
      
      
      def test_surface_normal3():
          """ should return:
      
              Newell:
              Traceback (most recent call last):
                File "demo_newells_surface_normals.py", line 96, in <module>
                  test_surface_normal3()
                File "demo_newells_surface_normals.py", line 58, in test_surface_normal3
                  print "Newell:", surface_normal_newell(poly) 
                File "demo_newells_surface_normals.py", line 24, in surface_normal_newell
                  raise ValueError('zero norm')
              ValueError: zero norm
          """
          poly = np.array([[1.0,0.0,0.0],
                           [1.0,0.0,0.0],
                           [1.0,0.0,0.0]])
          print "Newell:", surface_normal_newell(poly) 
      
      
      def test_surface_normal2():
          """ should return:
      
              Newell: [ 0.08466675 -0.97366764 -0.21166688]
              Cross : [ 0.08466675 -0.97366764 -0.21166688]
          """
          poly = np.array([[6.0,1.0,4.0],
                           [7.0,0.0,9.0],
                           [1.0,1.0,2.0]])
          print "Newell:", surface_normal_newell(poly)
          print "Cross :", surface_normal_cross(poly)
      
      
      def test_surface_normal1():
          """ should return:
      
              Newell: [ 0.  0. -1.]
              Cross : [ 0.  0. -1.]
          """
          poly = np.array([[0.0,1.0,0.0],
                           [1.0,1.0,0.0],
                           [1.0,0.0,0.0]])
          print "Newell:", surface_normal_newell(poly) 
          print "Cross :", surface_normal_cross(poly)
      
      
      print "Test 1:"
      test_surface_normal1()
      print "\n"
      
      print "Test 2:"
      test_surface_normal2()
      print "\n"
      
      print "Test 3:"
      test_surface_normal3()
      

      【讨论】:

      • 我正在尝试将 surface_normal_cross 函数转换为 PHP。到目前为止,您的实现使用非标准逻辑(意味着 python 中的内置函数)我无法理解。是否可以添加更多信息..
      猜你喜欢
      • 1970-01-01
      • 2021-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-20
      相关资源
      最近更新 更多