manimgeo.math.angles 源代码
from .base import close, array2float
from logging import getLogger
from typing import Optional
import numpy as np
logger = getLogger(__name__)
[文档]
@array2float
def angle_3p_countclockwise(start: np.ndarray, center: np.ndarray, end: np.ndarray) -> float:
"""
计算三维空间中三点构成的角度,按照右手系从 (start - center) 向量到 (end - center) 向量的旋转角度
Returns: `float`, 弧度值,`[0, 2*pi)`
"""
vec1 = start - center
vec2 = end - center
# 检查零向量
norm_vec1 = float(np.linalg.norm(vec1))
norm_vec2 = float(np.linalg.norm(vec2))
if close(norm_vec1, 0) or close(norm_vec2, 0):
logger.warning(f"无法计算角度:向量不能为零向量:{vec1}, {vec2}")
raise ValueError(f"无法计算角度:向量不能为零向量:{vec1}, {vec2}")
u1 = vec1 / norm_vec1
u2 = vec2 / norm_vec2
# 计算点积 (cos(theta) 的分子)
dot_product = np.dot(u1, u2)
# 计算叉积向量 (sin(theta) 的方向和大小)
cross_product_vec = np.cross(u1, u2)
# 叉积向量的模长 (sin(theta) 的绝对值)
sin_abs = float(np.linalg.norm(cross_product_vec))
# 检查三点共线
if close(sin_abs, 0):
# 如果叉积模长为0,说明 u1 和 u2 共线
if dot_product >= 0: # 同向
return 0.0
else: # 反向
return np.pi
# 在局部二维平面内计算角度
# x_prime = u1 (归一化的 vec1)
# z_prime = cross_product_vec / sin_abs (归一化的法向量)
# y_prime = np.cross(z_prime, x_prime) (与 x_prime 垂直,且在平面内)
# 将 u2 投影到这个局部坐标系
local_x_axis = u1
x_component_in_local_plane = np.dot(u2, local_x_axis)
y_component_in_local_plane = np.dot(u2, np.cross(cross_product_vec / sin_abs, local_x_axis))
angle_rad = np.arctan2(y_component_in_local_plane, x_component_in_local_plane)
if angle_rad < 0:
angle_rad += 2 * np.pi
return angle_rad
[文档]
@array2float
def point_3p_countclockwise(start: np.ndarray, center: np.ndarray, angle_rad: float, axis_vec: Optional[np.ndarray] = None) -> np.ndarray:
"""
根据始点、中心点和角度,按逆时针方向计算终点
- `start`: 始点
- `center`: 中心点
- `angle_rad`: 角度(弧度),逆时针为正
- `axis_vec`: 旋转轴向量(默认使用 z 轴)
Returns: 终点坐标
"""
vec1 = start - center
if axis_vec is None:
axis_vec = np.array([0.0, 0.0, 1.0])
else:
# 确保轴向量是单位向量
norm_axis = np.linalg.norm(axis_vec)
if not close(float(norm_axis), 0): # 使用 close 来判断是否为零向量
axis_vec = axis_vec / norm_axis
else:
logger.warning("旋转轴向量不能为零向量")
raise ValueError("旋转轴向量不能为零向量")
norm_vec1 = float(np.linalg.norm(vec1))
if close(norm_vec1, 0):
# 始点与中心重合,直接返回始点
logger.warning(f"始点与中心重合:{start}, {center}")
return start.copy()
# 对于2D情况的特殊处理
if len(vec1) == 2:
# 2D旋转矩阵
cos_angle = np.cos(angle_rad)
sin_angle = np.sin(angle_rad)
rotation_matrix = np.array([
[cos_angle, -sin_angle],
[sin_angle, cos_angle]
])
rotated_vec = rotation_matrix @ vec1
return center + rotated_vec
# 3D情况:使用Rodrigues旋转公式
if len(vec1) == 3:
# Rodrigues旋转公式
cos_angle = np.cos(angle_rad)
sin_angle = np.sin(angle_rad)
rotated_vec = (vec1 * cos_angle + np.cross(axis_vec, vec1) * sin_angle + axis_vec * np.dot(axis_vec, vec1) * (1 - cos_angle))
return center + rotated_vec
raise ValueError(f"不支持的维度:{len(vec1)}")