【问题标题】:Sort 4 3D coordinates in a winding order in any given direction在任何给定方向上按缠绕顺序对 4 个 3D 坐标进行排序
【发布时间】:2022-01-06 23:46:24
【问题描述】:

我需要按如下图所示的缠绕顺序对选择的 3D 坐标进行排序。右下角的顶点应该是数组的第一个元素,左下角的顶点应该是数组的最后一个元素。考虑到相机面向这些点的任何方向以及这些点的任何方向,这需要工作。由于“左上”、“右下”等是相对的,我假设我可以使用相机作为参考点?我们也可以假设所有 4 个点都是共面的。

我正在使用 Blender API(编写一个 Blender 插件),如果有必要,我可以访问相机的视图矩阵。从数学上讲,如果可以的话,这甚至可能吗?也许我把事情复杂化了?

由于 Blender API 在 Python 中,我将其标记为 Python,但我可以使用伪代码或根本没有代码。我主要关心如何在数学上处理这个问题,因为我不知道从哪里开始。

【问题讨论】:

  • 你保证四个点共面吗?
  • 您是否有参考资料来解释相机的“视图矩阵”究竟代表什么?我假设它是一个旋转矩阵,但准确理解“它旋转什么”对于确定上、左、右、下的位置很重要。
  • 是的,在这种情况下,我们可以保证这些点是共面的,我会将其添加到帖子中。看来view_matrix 确实是一个旋转矩阵,但docs 也让我可以访问view_rotation,它是一个始终标准化的四元数。我还可以访问文档状态为window_matrix * view_matrixperspective_matrix,也许这也会有帮助?
  • 显然,我对view_matrix 的看法是错误的。我问过一个 Blender 的人,他们说它本质上可以让你从世界空间转换到相机空间。
  • 这个article 解释得很好。 “视图矩阵:这个矩阵将顶点从世界空间变换到视图空间。这个矩阵是相机变换矩阵的逆矩阵。”

标签: python algorithm math


【解决方案1】:

由于您假设这四个点是共面的,您需要做的就是找到质心,计算从质心到每个点的向量,然后按向量的角度对点进行排序。

import numpy as np

def sort_points(pts):
    centroid = np.sum(pts, axis=0) / pts.shape[0]
    vector_from_centroid = pts - centroid
    vector_angle = np.arctan2(vector_from_centroid[:, 1], vector_from_centroid[:, 0]) 
    sort_order = np.argsort(vector_angle) # Find the indices that give a sorted vector_angle array

    # Apply sort_order to original pts array. 
    # Also returning centroid and angles so I can plot it for illustration. 
    return (pts[sort_order, :], centroid, vector_angle[sort_order])

这个函数在假设点是二维的情况下计算角度,但是如果你有共面点,那么在公共平面上找到坐标并消除第三个坐标应该很容易。

让我们编写一个快速绘图函数来绘制我们的点:

from matplotlib import pyplot as plt

def plot_points(pts, centroid=None, angles=None, fignum=None):
    fig = plt.figure(fignum)
    plt.plot(pts[:, 0], pts[:, 1], 'or')
    if centroid is not None:
        plt.plot(centroid[0], centroid[1], 'ok')
        
    for i in range(pts.shape[0]):
        lstr = f"pt{i}"
        if angles is not None:
            lstr += f" ang: {angles[i]:.3f}"
        plt.text(pts[i, 0], pts[i, 1], lstr)
    
    return fig

现在让我们测试一下:

随机点:

pts = np.random.random((4, 2))
spts, centroid, angles = sort_points(pts)
plot_points(spts, centroid, angles)

矩形中的点:

pts = np.array([[0, 0],  # pt0
                [10, 5], # pt2
                [10, 0], # pt1
                [0, 5]]) # pt3
spts, centroid, angles = sort_points(pts)
plot_points(spts, centroid, angles)


找到包含我们点的平面的法向量很容易,它只是连接两对点的向量的(归一化)叉积:

plane_normal = np.cross(pts[1, :] - pts[0, :], pts[2, :] - pts[0, :])
plane_normal = plane_normal / np.linalg.norm(plane_normal)

现在,要找到这个平面上所有点的投影,我们需要知道这个平面上新坐标系的“原点”和基础。让我们假设第一个点是原点,x轴连接第一个点到第二个点,由于我们知道z轴(平面法线)和x轴,我们可以计算y轴。

new_origin = pts[0, :]
new_x = pts[1, :] - pts[0, :]
new_x = new_x / np.linalg.norm(new_x)

new_y = np.cross(plane_normal, new_x)

现在,点在新平面上的投影由this answer 给出:

proj_x = np.dot(pts - new_origin, new_x)
proj_y = np.dot(pts - new_origin, new_y)

现在你有了二维点。运行上面的代码对它们进行排序。

【讨论】:

  • "这个函数计算角度假设点是二维的,但是如果你有共面点,那么在公共平面上找到坐标并消除第三个应该很容易坐标。” 我觉得这很可疑。我认为 2d 中的排序点是这个问题中比较容易的部分;在给定点的 3d 坐标和相机矩阵的情况下正确识别平面及其方向是这个问题中比较棘手的部分。
  • 你的方法很有意义,也很有趣。但是,我无法消除第三个坐标。你能再解释一下吗?我也对“普通平面”是什么感到困惑。
  • @Stef 在发布我的评论之前我没有看到您的评论。我猜你在这里提到的是:“在给定点的 3d 坐标和相机矩阵的情况下正确识别平面及其方向”是我现在正在努力的部分。在消除第三个坐标之前,需要先采取这一步,对吗?
  • @ColtonFox 我假设你已经知道如何做到这一点,因为你说这些点是共面的。我正在编辑以展示如何做到这一点。如果你想在此期间做一些阅读,这里有一个参考:stackoverflow.com/q/9605556/843953。您可以通过计算叉积(p3 - p1) x (p2 - p1)找到平面的法向量。
  • @ColtonFox 你能确认你开始的点是共面的吗?或者你想让我也讨论一下如何将这些点投影到平面上?
【解决方案2】:

经过几个小时,我终于找到了解决方案。 @Pranav Hosangadi 的解决方案适用于事物的 2D 方面。但是,我在使用他的解决方案的第二部分将 3D 坐标投影到 2D 坐标时遇到了麻烦。我还尝试按照this 答案中的描述投影坐标,但它没有按预期工作。然后我发现了一个名为location_3d_to_region_2d()(参见docs)的API 函数,顾名思义,它获取给定3D 坐标的2D 屏幕坐标(以像素为单位)。首先,我不需要将任何东西“投影”到 2D 中,让屏幕坐标工作得非常好,而且要简单得多。从那时起,我可以使用 Pranav 的函数对坐标进行排序,并稍作调整,以按照我第一篇文章的屏幕截图中所示的顺序得到它,我希望它以列表而不是 NumPy 数组的形式返回。

import bpy
from bpy_extras.view3d_utils import location_3d_to_region_2d
import numpy

def sort_points(pts):
    """Sort 4 points in a winding order"""
    pts = numpy.array(pts)
    centroid = numpy.sum(pts, axis=0) / pts.shape[0]
    vector_from_centroid = pts - centroid
    vector_angle = numpy.arctan2(
        vector_from_centroid[:, 1], vector_from_centroid[:, 0])
    # Find the indices that give a sorted vector_angle array
    sort_order = numpy.argsort(-vector_angle)

    # Apply sort_order to original pts array.
    return list(sort_order)

# Get 2D screen coords of selected vertices
region = bpy.context.region
region_3d = bpy.context.space_data.region_3d

corners2d = []
for corner in selected_verts:
    corners2d.append(location_3d_to_region_2d(
        region, region_3d, corner))

# Sort the 2d points in a winding order
sort_order = sort_points(corners2d)
sorted_corners = [selected_verts[i] for i in sort_order]

感谢 Pranav 花费时间和耐心帮助我解决这个问题!

【讨论】:

    猜你喜欢
    • 2021-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-05
    • 2020-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多