You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Matplotlib 3D多边形旋转后发生扭曲的问题求助

Matplotlib 3D多边形旋转后发生扭曲的问题求助

我现在在做一个3D物体旋转的功能:通过指定物体的一个面,让这个面的法向量对齐到目标向量。如果目标向量是平行于x、y或者z轴的,旋转出来的效果完全正常,但只要换成任意非轴向的向量,整个物体就会变得扭曲变形。

比如我代码里第104行的那个目标向量,运行后就能看到扭曲的问题。我猜测是不是需要额外加个偏移或者修正,但完全摸不着头绪。有没有大佬能帮我找找问题出在哪呀?

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np

# Create a new figure with 3D projection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')


def rotate_shape_to_normal(vertices, face_indices, target_normal):
    """
    Rotate vertices so that the face specified by face_indices becomes normal to target_normal.

    :param vertices: List or numpy array of 3D points defining the shape
    :param face_indices: Indices of the vertices that form the face to align
    :param target_normal: The vector to which the face should be normal
    :return: Rotated vertices
    """
    vertices = np.array(vertices)  # Convert to numpy array if it isn't already
    face_vertices = vertices[face_indices]

    # Calculate the normal vector of the face
    v1 = face_vertices[1] - face_vertices[0]
    v2 = face_vertices[2] - face_vertices[0]
    face_normal = np.cross(v1, v2)
    print(face_normal)
    ax.quiver(*face_vertices[0], *face_normal, color='r', length=2)
    ax.quiver(*face_vertices[0], *target_normal, color='b', length=2)
    face_normal = face_normal / np.linalg.norm(face_normal)  # Normalize

    # Normalize target_normal for consistency
    target_normal = target_normal / np.linalg.norm(target_normal)

    # Compute the rotation axis (perpendicular to both vectors)
    rotation_axis = np.cross(face_normal, target_normal)
    rotation_axis = rotation_axis / np.linalg.norm(rotation_axis)# if np.linalg.norm(rotation_axis) > 0 else np.array([1, 0, 0])  # Default to x-axis if parallel
    print("rotation_axis", rotation_axis)
    # Compute the angle between the vectors
    cos_theta = np.dot(face_normal, target_normal)
    print("cos_theta", np.degrees(cos_theta))
    theta = np.arccos(np.clip(cos_theta, -1.0, 1.0))  # Clip to avoid floating-point issues
    print("theta", np.degrees(theta))

    # Check if vectors are already aligned or opposite
    if np.isclose(theta, 0) or np.isclose(theta, np.pi):
        return vertices  # No rotation needed

    # Rodrigues' rotation formula
    K = np.array([
        [0, -rotation_axis[2], rotation_axis[1]],
        [rotation_axis[2], 0, -rotation_axis[0]],
        [-rotation_axis[1], rotation_axis[0], 0]
    ])
    rotation_matrix = np.sin(theta) * K + (1 - np.cos(theta)) * np.outer(rotation_axis, rotation_axis)


    # Apply the rotation to all vertices
    return np.dot(vertices, rotation_matrix.T)


# Define the vertices for a simple satellite model:
vertices = np.array([
    # Main body (cube)
    (0, 0, 0),  # 0 - Base, front-left
    (1, 0, 0),  # 1 - Base, front-right
    (1, 1, 0),  # 2 - Base, back-right
    (0, 1, 0),  # 3 - Base, back-left

    (0, 0, 1),  # 4 - Top, front-left
    (1, 0, 1),  # 5 - Top, front-right
    (1, 1, 1),  # 6 - Top, back-right
    (0, 1, 1),  # 7 - Top, back-left

    # Solar panels
    # Left panel
    (0.5, .25, -1.5), # 8
    (0.5, .75, -1.5), # 9
    (0.5, .75, 0), # 10
    (0.5, .25, 0), # 11

    # Right panel
    (0.5, .25, 2.5),  # 12
    (0.5, .75, 2.5),  # 13
    (0.5, .75, 1),  # 14
    (0.5, .25, 1)   # 15

])



# Translate spacecraft to origin
vertices = [(x - .5, y - .5, z - .5) for x, y, z in vertices]

# Original points
ax.scatter(vertices[5][0], vertices[5][1],vertices[5][2], color='green', s=25)
ax.scatter(vertices[1][0], vertices[1][1],vertices[1][2], color='green', s=25)
ax.scatter(vertices[4][0], vertices[4][1],vertices[4][2], color='green', s=25)


# Rotate spacecraft to point at specified vector
face_to_align = [5, 1, 4] # Indexes of points that define the face I want to be normal to the specified vector (-Y)
#vector = np.array([0, 0, 1])  # This rotation doesn't skew anything
vector = np.array([0.56167836, 0.76075023, 0.32523301]) # This rotation skews everything

vertices = rotate_shape_to_normal(vertices, face_to_align, vector)

# Rotated points
ax.scatter(vertices[5][0], vertices[5][1],vertices[5][2], color='blue', s=25)
ax.scatter(vertices[1][0], vertices[1][1],vertices[1][2], color='blue', s=25)
ax.scatter(vertices[4][0], vertices[4][1],vertices[4][2], color='blue', s=25)


# Faces for the cube
cube_faces = [
    [vertices[0], vertices[1], vertices[5], vertices[4]],  # (-Y)
    [vertices[1], vertices[2], vertices[6], vertices[5]],  # (+X)
    [vertices[2], vertices[3], vertices[7], vertices[6]],  # (+Y)
    [vertices[3], vertices[0], vertices[4], vertices[7]],  # (-X)
    [vertices[0], vertices[1], vertices[2], vertices[3]],  # (-Z)
    [vertices[4], vertices[5], vertices[6], vertices[7]],  # (+Z)
]

# Solar panel faces - note these are just rectangles
left_panel = [vertices[8], vertices[9], vertices[10], vertices[11]]
right_panel = [vertices[12], vertices[13], vertices[14], vertices[15]]

# Combine all faces
faces = cube_faces + [left_panel, right_panel]


# Create Poly3DCollection
poly3d = Poly3DCollection(faces, alpha=0.7)
poly3d.set_edgecolor('k')

# Set face color for different parts
poly3d.set_facecolor([[1, 0, 0], [.7, .7, .7], [.7, .7, .7], [.7, .7, .7], [.7, .7, .7], [.7, .7, .7], [0, 0.5, 1], [0, 0.5, 1]])
ax.add_collection3d(poly3d)

# Set the aspect ratio to ensure it looks like a cube
ax.set_box_aspect((1, 1, 1))

# Remove axes for a cleaner look
#ax.set_axis_off()

# Set limits to see everything
ax.set_xlim(-1, 2)
ax.set_ylim(-1, 2)
ax.set_zlim(-1, 2)

# Origin
ax.scatter(0, 0, 0, color='red', s=25)


ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

# Adjust view for better visualization
ax.view_init(elev=20., azim=-45)

plt.show()

备注:内容来源于stack exchange,提问作者dreed75

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:35:31