[学习] 笛卡尔坐标系的任意移动与旋转详解

发布于:2025-07-23 ⋅ 阅读:(16) ⋅ 点赞:(0)

笛卡尔坐标系的任意移动与旋转详解

1. 笛卡尔坐标系基础

笛卡尔坐标系用 ( x , y ) (x,y) (x,y)表示平面内任意点的位置,原点为 ( 0 , 0 ) (0,0) (0,0)。几何图形可视为点的集合。

2. 坐标变换原理
2.1 平移变换

将图形整体移动 ( t x , t y ) (t_x, t_y) (tx,ty)
{ x ′ = x + t x y ′ = y + t y \begin{cases} x' = x + t_x \\ y' = y + t_y \end{cases} {x=x+txy=y+ty

矩阵形式
[ x ′ y ′ ] = [ x y ] + [ t x t y ] \begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} x \\ y \end{bmatrix} + \begin{bmatrix} t_x \\ t_y \end{bmatrix} [xy]=[xy]+[txty]

2.2 旋转变换
  • 绕原点旋转(逆时针为正方向):
    { x ′ = x cos ⁡ θ − y sin ⁡ θ y ′ = x sin ⁡ θ + y cos ⁡ θ \begin{cases} x' = x \cos \theta - y \sin \theta \\ y' = x \sin \theta + y \cos \theta \end{cases} {x=xcosθysinθy=xsinθ+ycosθ

    矩阵形式
    [ x ′ y ′ ] = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] [ x y ] \begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} [xy]=[cosθsinθsinθcosθ][xy]

  • 绕任意点 ( c x , c y ) (c_x, c_y) (cx,cy)旋转

    1. 平移使旋转中心到原点
    2. 绕原点旋转
    3. 平移回原位置
      [ x ′ y ′ ] = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] [ x − c x y − c y ] + [ c x c y ] \begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \end{bmatrix} \begin{bmatrix} x - c_x \\ y - c_y \end{bmatrix} + \begin{bmatrix} c_x \\ c_y \end{bmatrix} [xy]=[cosθsinθsinθcosθ][xcxycy]+[cxcy]
3. 组合变换

复杂变换可分解为平移与旋转的序列操作,矩阵乘法满足结合律:
T total = T translate ⋅ R rotate T_{\text{total}} = T_{\text{translate}} \cdot R_{\text{rotate}} Ttotal=TtranslateRrotate


Python仿真与动态展示

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Polygon
import matplotlib

# 设置全局字体大小
matplotlib.rcParams.update({'font.size': 12})

# 定义三角形顶点 (齐次坐标)
triangle = np.array([[0, 0, 1], 
                     [1, 0, 1], 
                     [0.5, 1, 1]])

# 变换矩阵函数
def translation_matrix(tx, ty):
    return np.array([[1, 0, tx],
                    [0, 1, ty],
                    [0, 0, 1]])

def rotation_matrix(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([[c, -s, 0],
                    [s, c, 0],
                    [0, 0, 1]])

def rotation_around_point(theta, cx, cy):
    T1 = translation_matrix(-cx, -cy)
    R = rotation_matrix(theta)
    T2 = translation_matrix(cx, cy)
    return T2 @ R @ T1

# 创建三个独立的画布
fig1, ax1 = plt.subplots(figsize=(8, 6))
fig2, ax2 = plt.subplots(figsize=(8, 6))
fig3, ax3 = plt.subplots(figsize=(8, 6))

# 设置每个画布的标题和坐标范围
fig1.suptitle('平移变换演示', fontsize=16)
fig2.suptitle('绕原点旋转变换演示', fontsize=16)
fig3.suptitle('绕任意点旋转变换演示', fontsize=16)

# 设置坐标范围
for ax in [ax1, ax2, ax3]:
    ax.set_xlim(-3, 4)
    ax.set_ylim(-3, 4)
    ax.grid(True, linestyle='--', alpha=0.7)
    ax.set_aspect('equal')
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')

# 初始化每个画布的三角形
triangle1 = Polygon(triangle[:, :2], fill=None, edgecolor='blue', linewidth=2.5, alpha=0.9)
triangle2 = Polygon(triangle[:, :2], fill=None, edgecolor='blue', linewidth=2.5, alpha=0.9)
triangle3 = Polygon(triangle[:, :2], fill=None, edgecolor='blue', linewidth=2.5, alpha=0.9)

ax1.add_patch(triangle1)
ax2.add_patch(triangle2)
ax3.add_patch(triangle3)

# 添加顶点标签
def add_vertex_labels(ax, vertices):
    ax.text(vertices[0, 0], vertices[0, 1], 'A', fontsize=14, ha='right', va='bottom', 
            weight='bold', color='darkred', bbox=dict(facecolor='white', alpha=0.7, edgecolor='none', boxstyle='round,pad=0.2'))
    ax.text(vertices[1, 0], vertices[1, 1], 'B', fontsize=14, ha='left', va='bottom', 
            weight='bold', color='darkgreen', bbox=dict(facecolor='white', alpha=0.7, edgecolor='none', boxstyle='round,pad=0.2'))
    ax.text(vertices[2, 0], vertices[2, 1], 'C', fontsize=14, ha='center', va='top', 
            weight='bold', color='darkblue', bbox=dict(facecolor='white', alpha=0.7, edgecolor='none', boxstyle='round,pad=0.2'))

add_vertex_labels(ax1, triangle)
add_vertex_labels(ax2, triangle)
add_vertex_labels(ax3, triangle)

# 创建轨迹线 (每个顶点一种颜色)
trail_length = 50  # 拖尾轨迹长度

# 使用渐变色轨迹
traj1_A, = ax1.plot([], [], 'r-', lw=2.0, alpha=1.0, label='A')
traj1_B, = ax1.plot([], [], 'g-', lw=2.0, alpha=1.0, label='B')
traj1_C, = ax1.plot([], [], 'b-', lw=2.0, alpha=1.0, label='C')
ax1.legend(title='顶点轨迹', loc='upper right', fontsize=10)

traj2_A, = ax2.plot([], [], 'r-', lw=2.0, alpha=1.0, label='A')
traj2_B, = ax2.plot([], [], 'g-', lw=2.0, alpha=1.0, label='B')
traj2_C, = ax2.plot([], [], 'b-', lw=2.0, alpha=1.0, label='C')
ax2.legend(title='顶点轨迹', loc='upper right', fontsize=10)

traj3_A, = ax3.plot([], [], 'r-', lw=2.0, alpha=1.0, label='A')
traj3_B, = ax3.plot([], [], 'g-', lw=2.0, alpha=1.0, label='B')
traj3_C, = ax3.plot([], [], 'b-', lw=2.0, alpha=1.0, label='C')
ax3.legend(title='顶点轨迹', loc='upper right', fontsize=10)

# 存储轨迹数据
traj_data1 = {'A': {'x': [], 'y': []}, 'B': {'x': [], 'y': []}, 'C': {'x': [], 'y': []}}
traj_data2 = {'A': {'x': [], 'y': []}, 'B': {'x': [], 'y': []}, 'C': {'x': [], 'y': []}}
traj_data3 = {'A': {'x': [], 'y': []}, 'B': {'x': [], 'y': []}, 'C': {'x': [], 'y': []}}

# 平移动画更新函数
def update_translation(frame):
    # 平移参数
    tx = frame * 0.1
    ty = frame * 0.05
    M = translation_matrix(tx, ty)
    
    # 应用变换
    transformed = (M @ triangle.T).T
    
    # 更新三角形
    triangle1.set_xy(transformed[:, :2])
    
    # 更新轨迹数据 - 拖尾方式,保留最近50个点
    # 顶点A
    traj_data1['A']['x'].append(transformed[0, 0])
    traj_data1['A']['y'].append(transformed[0, 1])
    if len(traj_data1['A']['x']) > trail_length:
        traj_data1['A']['x'] = traj_data1['A']['x'][-trail_length:]
        traj_data1['A']['y'] = traj_data1['A']['y'][-trail_length:]
    
    # 顶点B
    traj_data1['B']['x'].append(transformed[1, 0])
    traj_data1['B']['y'].append(transformed[1, 1])
    if len(traj_data1['B']['x']) > trail_length:
        traj_data1['B']['x'] = traj_data1['B']['x'][-trail_length:]
        traj_data1['B']['y'] = traj_data1['B']['y'][-trail_length:]
    
    # 顶点C
    traj_data1['C']['x'].append(transformed[2, 0])
    traj_data1['C']['y'].append(transformed[2, 1])
    if len(traj_data1['C']['x']) > trail_length:
        traj_data1['C']['x'] = traj_data1['C']['x'][-trail_length:]
        traj_data1['C']['y'] = traj_data1['C']['y'][-trail_length:]
    
    # 更新轨迹线
    traj1_A.set_data(traj_data1['A']['x'], traj_data1['A']['y'])
    traj1_B.set_data(traj_data1['B']['x'], traj_data1['B']['y'])
    traj1_C.set_data(traj_data1['C']['x'], traj_data1['C']['y'])
    
    # 更新顶点标签位置
    ax1.texts[0].set_position((transformed[0, 0], transformed[0, 1]))
    ax1.texts[1].set_position((transformed[1, 0], transformed[1, 1]))
    ax1.texts[2].set_position((transformed[2, 0], transformed[2, 1]))
    
    ax1.set_title(f"平移: tx={tx:.1f}, ty={ty:.1f}", fontsize=14)
    return triangle1, traj1_A, traj1_B, traj1_C

# 绕原点旋转动画更新函数(360°持续旋转)
def update_rotation_origin(frame):
    # 旋转参数 - 持续旋转
    theta = frame * 0.05  # 每帧增加0.05弧度
    
    # 计算旋转矩阵
    M = rotation_matrix(theta)
    
    # 应用变换
    transformed = (M @ triangle.T).T
    
    # 更新三角形
    triangle2.set_xy(transformed[:, :2])
    
    # 更新轨迹数据 - 拖尾方式,保留最近50个点
    # 顶点A
    traj_data2['A']['x'].append(transformed[0, 0])
    traj_data2['A']['y'].append(transformed[0, 1])
    if len(traj_data2['A']['x']) > trail_length:
        traj_data2['A']['x'] = traj_data2['A']['x'][-trail_length:]
        traj_data2['A']['y'] = traj_data2['A']['y'][-trail_length:]
    
    # 顶点B
    traj_data2['B']['x'].append(transformed[1, 0])
    traj_data2['B']['y'].append(transformed[1, 1])
    if len(traj_data2['B']['x']) > trail_length:
        traj_data2['B']['x'] = traj_data2['B']['x'][-trail_length:]
        traj_data2['B']['y'] = traj_data2['B']['y'][-trail_length:]
    
    # 顶点C
    traj_data2['C']['x'].append(transformed[2, 0])
    traj_data2['C']['y'].append(transformed[2, 1])
    if len(traj_data2['C']['x']) > trail_length:
        traj_data2['C']['x'] = traj_data2['C']['x'][-trail_length:]
        traj_data2['C']['y'] = traj_data2['C']['y'][-trail_length:]
    
    # 更新轨迹线
    traj2_A.set_data(traj_data2['A']['x'], traj_data2['A']['y'])
    traj2_B.set_data(traj_data2['B']['x'], traj_data2['B']['y'])
    traj2_C.set_data(traj_data2['C']['x'], traj_data2['C']['y'])
    
    # 更新顶点标签位置
    ax2.texts[0].set_position((transformed[0, 0], transformed[0, 1]))
    ax2.texts[1].set_position((transformed[1, 0], transformed[1, 1]))
    ax2.texts[2].set_position((transformed[2, 0], transformed[2, 1]))
    
    # 显示当前角度(转换为度数)
    degrees = np.degrees(theta) % 360
    ax2.set_title(f"绕原点旋转: {degrees:.0f}°", fontsize=14)
    return triangle2, traj2_A, traj2_B, traj2_C

# 绕任意点旋转动画更新函数(360°持续旋转)
def update_rotation_arbitrary(frame):
    # 旋转参数 - 持续旋转
    theta = frame * 0.05  # 每帧增加0.05弧度
    cx, cy = 1.5, 0.5  # 旋转中心
    
    # 标记旋转中心(只在第一帧标记)
    if frame == 0:
        ax3.plot(cx, cy, 'ro', markersize=10, alpha=0.9)
        ax3.text(cx, cy+0.4, '旋转中心', ha='center', va='bottom', 
                fontsize=12, weight='bold', color='red',
                bbox=dict(facecolor='white', alpha=0.7, edgecolor='none', boxstyle='round,pad=0.2'))
    
    # 计算绕任意点旋转的变换矩阵
    M = rotation_around_point(theta, cx, cy)
    
    # 应用变换
    transformed = (M @ triangle.T).T
    
    # 更新三角形
    triangle3.set_xy(transformed[:, :2])
    
    # 更新轨迹数据 - 拖尾方式,保留最近50个点
    # 顶点A
    traj_data3['A']['x'].append(transformed[0, 0])
    traj_data3['A']['y'].append(transformed[0, 1])
    if len(traj_data3['A']['x']) > trail_length:
        traj_data3['A']['x'] = traj_data3['A']['x'][-trail_length:]
        traj_data3['A']['y'] = traj_data3['A']['y'][-trail_length:]
    
    # 顶点B
    traj_data3['B']['x'].append(transformed[1, 0])
    traj_data3['B']['y'].append(transformed[1, 1])
    if len(traj_data3['B']['x']) > trail_length:
        traj_data3['B']['x'] = traj_data3['B']['x'][-trail_length:]
        traj_data3['B']['y'] = traj_data3['B']['y'][-trail_length:]
    
    # 顶点C
    traj_data3['C']['x'].append(transformed[2, 0])
    traj_data3['C']['y'].append(transformed[2, 1])
    if len(traj_data3['C']['x']) > trail_length:
        traj_data3['C']['x'] = traj_data3['C']['x'][-trail_length:]
        traj_data3['C']['y'] = traj_data3['C']['y'][-trail_length:]
    
    # 更新轨迹线
    traj3_A.set_data(traj_data3['A']['x'], traj_data3['A']['y'])
    traj3_B.set_data(traj_data3['B']['x'], traj_data3['B']['y'])
    traj3_C.set_data(traj_data3['C']['x'], traj_data3['C']['y'])
    
    # 更新顶点标签位置
    ax3.texts[0].set_position((transformed[0, 0], transformed[0, 1]))
    ax3.texts[1].set_position((transformed[1, 0], transformed[1, 1]))
    ax3.texts[2].set_position((transformed[2, 0], transformed[2, 1]))
    
    # 显示当前角度(转换为度数)
    degrees = np.degrees(theta) % 360
    ax3.set_title(f"绕点({cx},{cy})旋转: {degrees:.0f}°", fontsize=14)
    return triangle3, traj3_A, traj3_B, traj3_C

# 创建三个独立的动画
ani1 = FuncAnimation(fig1, update_translation, frames=30, interval=50, blit=True)
# 旋转动画设置为无限循环
ani2 = FuncAnimation(fig2, update_rotation_origin, frames=200, interval=50, blit=True, repeat=True)
ani3 = FuncAnimation(fig3, update_rotation_arbitrary, frames=200, interval=50, blit=True, repeat=True)

# 调整窗口位置避免重叠
fig1.canvas.manager.window.wm_geometry("+100+100")
fig2.canvas.manager.window.wm_geometry("+600+100")
fig3.canvas.manager.window.wm_geometry("+1100+100")

plt.tight_layout()
plt.show()
动画说明
  1. 平移阶段(0-30帧):三角形沿向量 ( 2 , 1.5 ) (2,1.5) (2,1.5)移动
    在这里插入图片描述

  2. 绕原点旋转(31-60帧):三角形绕 ( 0 , 0 ) (0,0) (0,0)逆时针旋转360°
    在这里插入图片描述

  3. 绕任意点旋转(61-90帧):三角形绕红点 ( 1.5 , 0.8 ) (1.5,0.8) (1.5,0.8)逆时针旋转360°
    在这里插入图片描述

关键数学原理
  • 平移是向量加法
  • 旋转是矩阵乘法
  • 绕任意点旋转 = 平移至原点 → 旋转 → 平移回原位

通过组合基本变换,可实现复杂刚体运动仿真,广泛应用于机器人学、计算机图形学等领域。


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)



网站公告

今日签到

点亮在社区的每一天
去签到