9-3 二维坐标系转换
二维坐标系转换(2D Changing Coordinate Systems)
在前面的章节中,我们使用矩阵来变换点——对同一个坐标系中的点施加旋转、平移和缩放。但在实际应用中,常常需要处理多个坐标系之间的关系:例如游戏中的"世界坐标"与"角色本地坐标",或者机器人学中的"基座标系"与"关节坐标系"。
坐标系转换(Changing Coordinate Systems)的核心思想是:同一个点在不同坐标系中有不同的坐标表示,而矩阵可以描述这些表示之间的转换关系。这不是"移动点",而是"换一种视角来看同一个点"。
这一概念是场景图(Scene Graph)、骨骼动画(Skeletal Animation)和机器人运动学(Robot Kinematics)的基础。
坐标系(Coordinate Frame)
一个二维坐标系(Coordinate Frame)由两个要素定义:
- 原点(Origin):坐标系的原点在世界坐标中的位置 (ox, oy)
- 基向量(Basis Vectors):两个方向向量,分别表示该坐标系的 x 轴方向和 y 轴方向
世界坐标系 (World Frame):
- 原点在 (0, 0)
- x 轴方向 (1, 0),y 轴方向 (0, 1)
本地坐标系 (Local Frame):
- 原点在世界坐标 (ox, oy)
- x 轴方向 (îx, îy),y 轴方向 (ĵx, ĵy)
用矩阵来表示一个坐标系:
| îx ĵx ox |
| îy ĵy oy |
| 0 0 1 |
- 前两列
[îx, îy]和[ĵx, ĵy]是基向量(basis vectors),描述坐标轴的方向和缩放 - 第三列
[ox, oy]是原点在世界坐标中的位置 - 第三行
[0, 0, 1]是齐次坐标的固定行
关键理解:这个矩阵同时也是"从本地坐标转换到世界坐标"的变换矩阵。
本地坐标到世界坐标(Local → World)
假设有一个本地坐标系,其原点在世界坐标 (3, 2),x 轴方向为 (1, 0)、y 轴方向为 (0, 1)(即未旋转,仅平移)。一个本地坐标为 (1, 1) 的点,在世界坐标中的位置为:
Frame Matrix: Local point: World point:
| 1 0 3 | | 1 | | 1*1 + 0*1 + 3 | | 4 |
| 0 1 2 | × | 1 | = | 0*1 + 1*1 + 2 | = | 3 |
| 0 0 1 | | 1 | | 1 | | 1 |
这就是"本地坐标 → 世界坐标"的转换:将本地坐标点 (1, 1) 转换为世界坐标 (4, 3)。
更一般的例子:一个旋转过的本地坐标系。原点在世界 (2, 1),坐标轴旋转了 90 度(逆时针)。此时:
- 旋转 90 度后,x 轴方向变为 (0, 1),y 轴方向变为 (-1, 0)
- 坐标系矩阵为:
| 0 -1 2 |
| 1 0 1 |
| 0 0 1 |
本地坐标 (1, 0) 的点,在世界坐标中:
| 0 -1 2 | | 1 | | 0*1 + (-1)*0 + 2 | | 2 |
| 1 0 1 | × | 0 | = | 1*1 + 0*0 + 1 | = | 2 |
| 0 0 1 | | 1 | | 1 | | 1 |
结果:本地 (1, 0) 对应世界 (2, 2)。直观理解:本地 x 轴方向是世界的"上"方向,所以沿本地 x 轴走 1 单位,在世界中是 y 方向 +1。
世界坐标到本地坐标(World → Local)
已知世界坐标,要求本地坐标,需要使用坐标系矩阵的逆矩阵(Inverse Matrix):
P_local = Frame^(-1) × P_world
对于上面的旋转 90 度的坐标系(原点 (2, 1)),求世界坐标 (2, 2) 对应的本地坐标:
Frame = | 0 -1 2 |
| 1 0 1 |
| 0 0 1 |
Frame^(-1) = | 0 1 -1 | (旋转逆 = 旋转 -90 度)
| -1 0 2 | (平移逆 = 反向平移)
| 0 0 1 |
Frame^(-1) × (2, 2, 1):
| 0 1 -1 | | 2 | | 0*2 + 1*2 + (-1) | | 1 |
| -1 0 2 | × | 2 | = | (-1)*2 + 0*2 + 2 | = | 0 |
| 0 0 1 | | 1 | | 1 | | 1 |
结果为 (1, 0),与之前的计算完全吻合。
对于仿射变换矩阵(最后一行为 [0, 0, 1]),逆矩阵的计算公式为:
| a b tx |^(-1) 1 | d -b (b*ty - d*tx) |
| c d ty | = ------- × | -c a (c*tx - a*ty) |
| 0 0 1 | det | 0 0 det |
其中 det = a*d - b*c(2x2 子矩阵的行列式)
C++ 实现
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
// 3x3 matrix for coordinate frame
struct Frame2D {
double m[3][3];
// Create frame from origin and angle (radians)
static Frame2D fromOriginAngle(double ox, double oy, double angle) {
Frame2D f;
double c = cos(angle), s = sin(angle);
// Column 1: x-axis direction (rotated)
f.m[0][0] = c; f.m[1][0] = s;
// Column 2: y-axis direction (rotated)
f.m[0][1] = -s; f.m[1][1] = c;
// Column 3: origin position
f.m[0][2] = ox; f.m[1][2] = oy;
// Row 3: homogeneous row
f.m[2][0] = 0; f.m[2][1] = 0; f.m[2][2] = 1;
return f;
}
// Local to world: P_world = Frame * P_local
void localToWorld(double lx, double ly, double& wx, double& wy) const {
wx = m[0][0] * lx + m[0][1] * ly + m[0][2];
wy = m[1][0] * lx + m[1][1] * ly + m[1][2];
}
// World to local: P_local = Frame^(-1) * P_world
void worldToLocal(double wx, double wy, double& lx, double& ly) const {
double det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
double dx = wx - m[0][2];
double dy = wy - m[1][2];
lx = ( m[1][1] * dx - m[0][1] * dy) / det;
ly = (-m[1][0] * dx + m[0][0] * dy) / det;
}
};
int main() {
double ox = 2, oy = 1;
double angle = M_PI / 2; // 90 degrees
Frame2D frame = Frame2D::fromOriginAngle(ox, oy, angle);
cout << fixed << setprecision(4);
cout << "Frame origin: (" << ox << ", " << oy << ")" << endl;
cout << "Frame angle: 90 degrees" << endl;
cout << "Frame matrix:" << endl;
for (int i = 0; i < 3; i++) {
cout << " | ";
for (int j = 0; j < 3; j++)
cout << setw(6) << frame.m[i][j] << " ";
cout << "|" << endl;
}
// Local to world
double lx = 1, ly = 0;
double wx, wy;
frame.localToWorld(lx, ly, wx, wy);
cout << "\nLocal (" << lx << ", " << ly << ") -> World ("
<< wx << ", " << wy << ")" << endl;
// World to local (inverse)
double lx2, ly2;
frame.worldToLocal(wx, wy, lx2, ly2);
cout << "World (" << wx << ", " << wy << ") -> Local ("
<< lx2 << ", " << ly2 << ")" << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <math.h>
typedef double Frame2D[3][3];
// Create frame from origin and angle (radians)
void frame2d_from_origin_angle(double ox, double oy, double angle, Frame2D f) {
double c = cos(angle), s = sin(angle);
f[0][0] = c; f[1][0] = s; f[2][0] = 0;
f[0][1] = -s; f[1][1] = c; f[2][1] = 0;
f[0][2] = ox; f[1][2] = oy; f[2][2] = 1;
}
// Local to world: P_world = Frame * P_local
void frame2d_local_to_world(Frame2D f, double lx, double ly,
double* wx, double* wy) {
*wx = f[0][0] * lx + f[0][1] * ly + f[0][2];
*wy = f[1][0] * lx + f[1][1] * ly + f[1][2];
}
// World to local: P_local = Frame^(-1) * P_world
void frame2d_world_to_local(Frame2D f, double wx, double wy,
double* lx, double* ly) {
double det = f[0][0] * f[1][1] - f[0][1] * f[1][0];
double dx = wx - f[0][2];
double dy = wy - f[1][2];
*lx = ( f[1][1] * dx - f[0][1] * dy) / det;
*ly = (-f[1][0] * dx + f[0][0] * dy) / det;
}
int main() {
double ox = 2, oy = 1;
double angle = M_PI / 2;
Frame2D frame;
frame2d_from_origin_angle(ox, oy, angle, frame);
printf("Frame origin: (%g, %g)\n", ox, oy);
printf("Frame angle: 90 degrees\n");
double lx = 1, ly = 0;
double wx, wy;
frame2d_local_to_world(frame, lx, ly, &wx, &wy);
printf("\nLocal (%g, %g) -> World (%g, %g)\n", lx, ly, wx, wy);
double lx2, ly2;
frame2d_world_to_local(frame, wx, wy, &lx2, &ly2);
printf("World (%g, %g) -> Local (%g, %g)\n", wx, wy, lx2, ly2);
return 0;
}
Python 实现
import math
class Frame2D:
"""2D coordinate frame defined by origin and rotation angle."""
def __init__(self, ox, oy, angle):
"""Create frame from origin (ox, oy) and rotation angle (radians)."""
c, s = math.cos(angle), math.sin(angle)
# Column 1: x-axis, Column 2: y-axis, Column 3: origin
self.m = [
[c, -s, ox],
[s, c, oy],
[0, 0, 1]
]
def local_to_world(self, lx, ly):
"""Convert local coordinates to world coordinates."""
wx = self.m[0][0] * lx + self.m[0][1] * ly + self.m[0][2]
wy = self.m[1][0] * lx + self.m[1][1] * ly + self.m[1][2]
return wx, wy
def world_to_local(self, wx, wy):
"""Convert world coordinates to local coordinates."""
det = self.m[0][0] * self.m[1][1] - self.m[0][1] * self.m[1][0]
dx = wx - self.m[0][2]
dy = wy - self.m[1][2]
lx = ( self.m[1][1] * dx - self.m[0][1] * dy) / det
ly = (-self.m[1][0] * dx + self.m[0][0] * dy) / det
return lx, ly
ox, oy = 2, 1
angle = math.pi / 2
frame = Frame2D(ox, oy, angle)
print(f"Frame origin: ({ox}, {oy})")
print("Frame angle: 90 degrees")
lx, ly = 1, 0
wx, wy = frame.local_to_world(lx, ly)
print(f"\nLocal ({lx}, {ly}) -> World ({wx}, {wy})")
lx2, ly2 = frame.world_to_local(wx, wy)
print(f"World ({wx}, {wy}) -> Local ({lx2}, {ly2})")
Go 实现
package main
import (
"fmt"
"math"
)
// Frame2D represents a 2D coordinate frame as a 3x3 matrix
type Frame2D [3][3]float64
// newFrame2D creates a frame from origin (ox, oy) and rotation angle (radians)
func newFrame2D(ox, oy, angle float64) Frame2D {
c, s := math.Cos(angle), math.Sin(angle)
return Frame2D{
{c, -s, ox},
{s, c, oy},
{0, 0, 1},
}
}
// localToWorld converts local coordinates to world coordinates
func (f Frame2D) localToWorld(lx, ly float64) (float64, float64) {
wx := f[0][0]*lx + f[0][1]*ly + f[0][2]
wy := f[1][0]*lx + f[1][1]*ly + f[1][2]
return wx, wy
}
// worldToLocal converts world coordinates to local coordinates
func (f Frame2D) worldToLocal(wx, wy float64) (float64, float64) {
det := f[0][0]*f[1][1] - f[0][1]*f[1][0]
dx := wx - f[0][2]
dy := wy - f[1][2]
lx := (f[1][1]*dx - f[0][1]*dy) / det
ly := (-f[1][0]*dx + f[0][0]*dy) / det
return lx, ly
}
func main() {
ox, oy := 2.0, 1.0
angle := math.Pi / 2
frame := newFrame2D(ox, oy, angle)
fmt.Printf("Frame origin: (%g, %g)\n", ox, oy)
fmt.Println("Frame angle: 90 degrees")
lx, ly := 1.0, 0.0
wx, wy := frame.localToWorld(lx, ly)
fmt.Printf("\nLocal (%g, %g) -> World (%g, %g)\n", lx, ly, wx, wy)
lx2, ly2 := frame.worldToLocal(wx, wy)
fmt.Printf("World (%g, %g) -> Local (%g, %g)\n", wx, wy, lx2, ly2)
}
运行该程序将输出:
Frame origin: (2, 1)
Frame angle: 90 degrees
Local (1, 0) -> World (2, 2)
World (2, 2) -> Local (1, 0)
嵌套坐标系(Nested Coordinate Frames)
坐标系转换最强大的地方在于处理嵌套的层级坐标系。例如:
世界坐标系 (World)
└── 房间坐标系 (Room)
└── 桌子坐标系 (Table)
└── 杯子坐标系 (Cup)
杯子在桌子上的本地坐标,需要经过多次转换才能得到世界坐标:
P_world = M_room × M_table × M_cup × P_cup_local
每一层只关心自己相对于父级坐标系的关系,这种分层设计极大地简化了复杂场景的管理。
以一个具体例子说明:
- 房间原点在世界 (10, 5),未旋转
- 桌子原点在房间 (3, 2),未旋转
- 杯子原点在桌子 (1, 1),旋转 90 度
- 杯子中有一个点,本地坐标 (0.5, 0)
Step 1: 杯子→桌子
M_cup = | 0 -1 1 | 本地点 (0.5, 0)
| 1 0 1 |
| 0 0 1 |
M_cup × (0.5, 0, 1) = (0*0.5 + (-1)*0 + 1, 1*0.5 + 0*0 + 1, 1)
= (1, 1.5, 1) ← 桌子坐标
Step 2: 桌子→房间
M_table = | 1 0 3 | 桌子点 (1, 1.5)
| 0 1 2 |
| 0 0 1 |
M_table × (1, 1.5, 1) = (1+3, 1.5+2, 1)
= (4, 3.5, 1) ← 房间坐标
Step 3: 房间→世界
M_room = | 1 0 10 | 房间点 (4, 3.5)
| 0 1 5 |
| 0 0 1 |
M_room × (4, 3.5, 1) = (4+10, 3.5+5, 1)
= (14, 8.5, 1) ← 世界坐标
结果:杯中的本地点 (0.5, 0) 在世界坐标中为 (14, 8.5)。
C++ 实现
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
// 3x3 matrix for coordinate frames
struct Frame2D {
double m[3][3];
static Frame2D fromOriginAngle(double ox, double oy, double angle) {
Frame2D f;
double c = cos(angle), s = sin(angle);
f.m[0][0] = c; f.m[0][1] = -s; f.m[0][2] = ox;
f.m[1][0] = s; f.m[1][1] = c; f.m[1][2] = oy;
f.m[2][0] = 0; f.m[2][1] = 0; f.m[2][2] = 1;
return f;
}
// Matrix multiplication: this * other
Frame2D multiply(const Frame2D& other) const {
Frame2D result;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
result.m[i][j] = 0;
for (int k = 0; k < 3; k++)
result.m[i][j] += m[i][k] * other.m[k][j];
}
return result;
}
// Transform point
void transform(double x, double y, double& ox, double& oy) const {
ox = m[0][0] * x + m[0][1] * y + m[0][2];
oy = m[1][0] * x + m[1][1] * y + m[1][2];
}
};
int main() {
// Build the frame hierarchy
Frame2D room = Frame2D::fromOriginAngle(10, 5, 0); // World offset
Frame2D table = Frame2D::fromOriginAngle(3, 2, 0); // Room offset
Frame2D cup = Frame2D::fromOriginAngle(1, 1, M_PI / 2); // Table offset, rotated
// Composite: M_room * M_table * M_cup
Frame2D tableInWorld = room.multiply(table);
Frame2D cupInWorld = tableInWorld.multiply(cup);
double lx = 0.5, ly = 0;
double wx, wy;
cupInWorld.transform(lx, ly, wx, wy);
cout << fixed << setprecision(1);
cout << "Cup local point: (" << lx << ", " << ly << ")" << endl;
cout << "World point: (" << wx << ", " << wy << ")" << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <math.h>
typedef double Frame2D[3][3];
void frame2d_from_origin_angle(double ox, double oy, double angle, Frame2D f) {
double c = cos(angle), s = sin(angle);
f[0][0] = c; f[0][1] = -s; f[0][2] = ox;
f[1][0] = s; f[1][1] = c; f[1][2] = oy;
f[2][0] = 0; f[2][1] = 0; f[2][2] = 1;
}
void frame2d_multiply(Frame2D A, Frame2D B, Frame2D result) {
Frame2D temp = {{0}};
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
for (int k = 0; k < 3; k++)
temp[i][j] += A[i][k] * B[k][j];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
result[i][j] = temp[i][j];
}
void frame2d_transform(Frame2D f, double x, double y, double* ox, double* oy) {
*ox = f[0][0] * x + f[0][1] * y + f[0][2];
*oy = f[1][0] * x + f[1][1] * y + f[1][2];
}
int main() {
Frame2D room, table, cup;
frame2d_from_origin_angle(10, 5, 0, room);
frame2d_from_origin_angle(3, 2, 0, table);
frame2d_from_origin_angle(1, 1, M_PI / 2, cup);
Frame2D tableInWorld, cupInWorld;
frame2d_multiply(room, table, tableInWorld);
frame2d_multiply(tableInWorld, cup, cupInWorld);
double lx = 0.5, ly = 0;
double wx, wy;
frame2d_transform(cupInWorld, lx, ly, &wx, &wy);
printf("Cup local point: (%g, %g)\n", lx, ly);
printf("World point: (%g, %g)\n", wx, wy);
return 0;
}
Python 实现
import math
class Frame2D:
"""2D coordinate frame."""
def __init__(self, ox, oy, angle):
c, s = math.cos(angle), math.sin(angle)
self.m = [[c, -s, ox], [s, c, oy], [0, 0, 1]]
def multiply(self, other):
result = Frame2D(0, 0, 0)
for i in range(3):
for j in range(3):
result.m[i][j] = sum(
self.m[i][k] * other.m[k][j] for k in range(3)
)
return result
def transform(self, x, y):
ox = self.m[0][0]*x + self.m[0][1]*y + self.m[0][2]
oy = self.m[1][0]*x + self.m[1][1]*y + self.m[1][2]
return ox, oy
# Build frame hierarchy
room = Frame2D(10, 5, 0)
table = Frame2D(3, 2, 0)
cup = Frame2D(1, 1, math.pi / 2)
# Composite: Room * Table * Cup
cup_in_world = room.multiply(table).multiply(cup)
lx, ly = 0.5, 0
wx, wy = cup_in_world.transform(lx, ly)
print(f"Cup local point: ({lx}, {ly})")
print(f"World point: ({wx}, {wy})")
Go 实现
package main
import (
"fmt"
"math"
)
type Frame2D [3][3]float64
func newFrame2D(ox, oy, angle float64) Frame2D {
c, s := math.Cos(angle), math.Sin(angle)
return Frame2D{
{c, -s, ox},
{s, c, oy},
{0, 0, 1},
}
}
func (a Frame2D) multiply(b Frame2D) Frame2D {
var result Frame2D
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
result[i][j] += a[i][k] * b[k][j]
}
}
}
return result
}
func (f Frame2D) transform(x, y float64) (float64, float64) {
ox := f[0][0]*x + f[0][1]*y + f[0][2]
oy := f[1][0]*x + f[1][1]*y + f[1][2]
return ox, oy
}
func main() {
room := newFrame2D(10, 5, 0)
table := newFrame2D(3, 2, 0)
cup := newFrame2D(1, 1, math.Pi/2)
cupInWorld := room.multiply(table).multiply(cup)
lx, ly := 0.5, 0.0
wx, wy := cupInWorld.transform(lx, ly)
fmt.Printf("Cup local point: (%g, %g)\n", lx, ly)
fmt.Printf("World point: (%g, %g)\n", wx, wy)
}
运行该程序将输出:
Cup local point: (0.5, 0)
World point: (14, 8.5)
坐标系转换 vs 点变换
坐标系转换和点变换使用的是同一个矩阵,但解释方式不同:
| 概念 | 操作 | 含义 |
|---|---|---|
| 点变换 | P' = M × P | 将点 P 移动到新位置 P'(同一个坐标系) |
| 坐标系转换 | P_global = M × P_local | 同一个点在两个坐标系中的不同表示 |
这两者的数学运算完全相同——都是 3x3 矩阵乘以齐次坐标向量。区别在于我们如何理解结果:
- 点变换视角:世界坐标系是固定的,点在"移动"
- 坐标系转换视角:点是固定的,我们换了一个"角度"来描述它
这种对偶性(Duality)在实际应用中非常有用。例如,将一个物体从 A 处移到 B 处(点变换),等价于将坐标系从 B 处切换到 A 处(坐标系转换)。矩阵相同,只是解读不同。
沿坐标轴方向的移动
在本地坐标系中,"前进 1 单位"意味着沿着本地 x 轴方向移动。这在游戏开发中极为常见——角色"向前走"就是沿本地 x 轴移动,但需要知道这对应世界坐标的哪个方向。
// 角色面向 θ 方向,前进 d 单位
world_dx = d * cos(θ)
world_dy = d * sin(θ)
new_x = old_x + world_dx
new_y = old_y + world_dy
这实际上就是坐标系矩阵的第一列(本地 x 轴在世界中的方向)乘以移动距离 d,再加上当前位置。
C++ 实现
#include <iostream>
#include <cmath>
using namespace std;
int main() {
double x = 3, y = 2; // Current position
double heading = M_PI / 4; // 45 degrees (northeast)
double distance = 2; // Move forward 2 units
// Move along local x-axis (heading direction)
double dx = distance * cos(heading);
double dy = distance * sin(heading);
double nx = x + dx;
double ny = y + dy;
cout << "Start: (" << x << ", " << y << ")" << endl;
cout << "Heading: 45 degrees" << endl;
cout << "Distance: " << distance << endl;
cout << "New pos: (" << nx << ", " << ny << ")" << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <math.h>
int main() {
double x = 3, y = 2;
double heading = M_PI / 4;
double distance = 2;
double dx = distance * cos(heading);
double dy = distance * sin(heading);
double nx = x + dx;
double ny = y + dy;
printf("Start: (%g, %g)\n", x, y);
printf("Heading: 45 degrees\n");
printf("Distance: %g\n", distance);
printf("New pos: (%g, %g)\n", nx, ny);
return 0;
}
Python 实现
import math
x, y = 3, 2
heading = math.pi / 4 # 45 degrees
distance = 2
dx = distance * math.cos(heading)
dy = distance * math.sin(heading)
nx = x + dx
ny = y + dy
print(f"Start: ({x}, {y})")
print("Heading: 45 degrees")
print(f"Distance: {distance}")
print(f"New pos: ({nx}, {ny})")
Go 实现
package main
import (
"fmt"
"math"
)
func main() {
x, y := 3.0, 2.0
heading := math.Pi / 4
distance := 2.0
dx := distance * math.Cos(heading)
dy := distance * math.Sin(heading)
nx := x + dx
ny := y + dy
fmt.Printf("Start: (%g, %g)\n", x, y)
fmt.Println("Heading: 45 degrees")
fmt.Printf("Distance: %g\n", distance)
fmt.Printf("New pos: (%g, %g)\n", nx, ny)
}
运行该程序将输出:
Start: (3, 2)
Heading: 45 degrees
Distance: 2
New pos: (4.41421, 3.41421)
二维坐标系转换的性质
矩阵即坐标系
一个 3x3 仿射变换矩阵可以同时理解为两种含义:
- 变换矩阵:描述如何变换点
- 坐标系描述:描述一个本地坐标系的朝向和位置
| îx ĵx ox | = | x轴方向 y轴方向 原点位置 |
| îy ĵy oy |
| 0 0 1 |
链式法则
嵌套坐标系的转换遵循链式法则:
P_world = M1 × M2 × M3 × ... × Mn × P_local
矩阵从右到左依次作用于点。可以将部分矩阵预先相乘(pre-multiply),减少运行时的计算量:
M_total = M1 × M2 × M3
P_world = M_total × P_local // 只需一次矩阵乘法
逆变换
从世界坐标回到本地坐标需要逆矩阵:
P_local = M^(-1) × P_world
对于只包含旋转和平移的坐标系(刚体变换),逆矩阵的计算非常高效:
| R t |^(-1) | R^T -R^T × t |
| 0 1 | = | 0 1 |
其中 R^T 是旋转矩阵的转置(也是其逆矩阵),不需要真正的矩阵求逆。
应用场景
| 应用 | 说明 |
|---|---|
| 游戏开发 | 世界坐标 ↔ 角色/物体本地坐标 |
| 机器人学 | 基座标系 ↔ 各关节坐标系 |
| 场景图 | 父节点 ↔ 子节点坐标系 |
| GUI 布局 | 窗口坐标 ↔ 控件本地坐标 |
| SVG/CSS | 用户空间 ↔ 设备空间 |

浙公网安备 33010602011771号