4. 空间圆弧插补
任务目标
给定三维空间中不共线的三个点,确定其所在平面上的圆弧(圆心、半径、起始角、终止角及旋转方向),并按指定步长输出插补点序列,用于机器人末端沿空间圆弧轨迹运动。
实现思路
三点确定空间圆弧的核心步骤:
- 由三点构成两个向量,叉乘得到平面法向量,进而建立以圆心为原点的局部坐标系(
u轴指向起点,v轴在平面内垂直于u); - 圆心通过三点共面条件与到三点等距条件联立求解;
- 在局部坐标系中将圆弧参数化为角度
θ,按弧长或角度步长插值,再变换回世界坐标系。
关键完整代码
import numpy as np
def spatial_arc_interp(p1, p2, p3, step_angle=np.deg2rad(1.0)):
"""
空间圆弧插补
参数:
p1, p2, p3: 不共线三点, shape=(3,)
step_angle: 插补角度步长(弧度)
返回:
arc_points: 插补点序列, shape=(N, 3)
center: 圆心坐标
radius: 圆弧半径
total_angle: 圆弧总角度(弧度)
"""
p1 = np.asarray(p1, dtype=float)
p2 = np.asarray(p2, dtype=float)
p3 = np.asarray(p3, dtype=float)
# 1. 计算平面法向量
v1 = p2 - p1
v2 = p3 - p1
n = np.cross(v1, v2)
norm_n = np.linalg.norm(n)
if norm_n < 1e-12:
raise ValueError("三点共线,无法确定圆弧")
n = n / norm_n
# 2. 求解圆心: 圆心在p1,p2,p3所在平面上, 且到三点距离相等
# 圆心满足: (c - p1)·(p2-p1) = |p2-p1|²/2
# (c - p1)·(p3-p1) = |p3-p1|²/2
# (c - p1)·n = 0
A = np.array([
[v1[0], v1[1], v1[2]],
[v2[0], v2[1], v2[2]],
[n[0], n[1], n[2]]
])
b = np.array([
np.dot(v1, v1) / 2.0,
np.dot(v2, v2) / 2.0,
0.0
])
center = np.linalg.solve(A, b) + p1
# 3. 建立局部坐标系
u = (p1 - center) / np.linalg.norm(p1 - center) # 指向起点
w = n # 平面法向
v = np.cross(w, u) # 平面内垂直于u
# 4. 计算起始角和终止角
start_angle = 0.0
# p2和p3在局部坐标系中的角度
def angle_of_point(p):
d = p - center
x = np.dot(d, u)
y = np.dot(d, v)
return np.arctan2(y, x)
a2 = angle_of_point(p2)
a3 = angle_of_point(p3)
# 判断旋转方向: 从p1出发经过p2到达p3
# 若a2在0~a3之间(绕w正方向), 则正向旋转; 否则反向
if a2 > 0 and a2 < a3:
end_angle = a3
direction = 1.0
else:
end_angle = a3 - 2 * np.pi if a3 > 0 else a3
direction = -1.0
total_angle = abs(end_angle - start_angle)
radius = np.linalg.norm(p1 - center)
# 5. 插补
num_steps = max(int(np.ceil(total_angle / step_angle)), 2)
angles = np.linspace(start_angle, end_angle, num_steps)
arc_points = []
for theta in angles:
point = center + radius * (np.cos(theta) * u + np.sin(theta) * v)
arc_points.append(point)
return np.array(arc_points), center, radius, total_angle
# ===== 使用示例 =====
if __name__ == "__main__":
# 定义三个不共线的空间点
p1 = np.array([0.0, 0.0, 0.0])
p2 = np.array([1.0, 0.0, 0.0])
p3 = np.array([0.0, 1.0, 0.0])
pts, c, r, ang = spatial_arc_interp(p1, p2, p3, step_angle=np.deg2rad(5.0))
print(f"圆心: {c}")
print(f"半径: {r:.4f}")
print(f"总角度: {np.rad2deg(ang):.2f}°")
print(f"插补点数: {len(pts)}")
print("前3个插补点:")
for pt in pts[:3]:
print(f" {pt}")
运行与验证
运行方式:将代码保存为 spatial_arc.py,执行 python spatial_arc.py。
验证方法:
- 示例中三点位于
z=0平面,预期圆心为(0.5, 0.5, 0),半径为√2/2 ≈ 0.7071,总角度为90°; - 检查输出插补点是否满足:①所有点均在三点确定的平面上;②每个点到圆心的距离等于半径(误差<1e-6);③首点为
p1,末点为p3; - 将插补点用matplotlib绘制,观察是否形成经过三点的平滑圆弧。
# 验证代码(可附加运行)
# 检查点到圆心距离是否等于半径
dists = np.linalg.norm(pts - c, axis=1)
assert np.allclose(dists, r, atol=1e-6), "半径误差过大"
# 检查首末点
assert np.allclose(pts[0], p1), "起点不匹配"
assert np.allclose(pts[-1], p3), "终点不匹配"
print("验证通过: 所有插补点均在圆弧上, 且首末点正确")
常见问题
- 三点近似共线:法向量模长接近0时程序报错,实际应用中应检查三点最小夹角,建议大于1°;
- 旋转方向判断:本实现按“从p1经p2到p3”的路径确定方向,若三点顺序改变,圆弧路径随之改变;
- 整圆插补:当终点与起点重合时(三点无法确定唯一圆弧),需额外指定圆心或旋转圈数,本案例不覆盖该场景。