SR-ARPOD/Plots/fig2_trajectory_3d_v2.py
2026-04-01 22:48:53 +08:00

203 lines
7.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
图 2v2三维轨迹可视化 — 100 条轨迹版。
相对 fig2_trajectory_3d.py 的改动:
- 轨迹数量10 → 100
- 目标器标记:大方块 → 小圆点 + 十字准星线
- 视线锥:提升可见性(半透明锥面 + 更粗的边缘线)
用法:
python -m Plots.fig2_trajectory_3d_v2
"""
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from Plots.plot_config import apply_style, save_fig, COLORS, DOUBLE_COL
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.cm as cm
def draw_safety_sphere(ax, center, radius, color='red', alpha=0.08):
"""绘制防碰撞球面。"""
u = np.linspace(0, 2 * np.pi, 30)
v = np.linspace(0, np.pi, 20)
xs = center[0] + radius * np.outer(np.cos(u), np.sin(v))
ys = center[1] + radius * np.outer(np.sin(u), np.sin(v))
zs = center[2] + radius * np.outer(np.ones_like(u), np.cos(v))
ax.plot_surface(xs, ys, zs, alpha=alpha, color=color, linewidth=0)
def draw_los_cone(ax, apex, theta_deg, length, n_lines=36, alpha=0.10):
"""
绘制视线锥。锥尖在 apex沿 -y 方向展开。
v2: 增大 alpha、线数和线宽以提升可见性增加半透明锥面。
"""
theta = np.radians(theta_deg)
r = length * np.tan(theta)
t = np.linspace(0, 2 * np.pi, n_lines, endpoint=False)
# 锥面线(更粗、更高 alpha
for ti in t:
x_end = apex[0] + r * np.cos(ti)
z_end = apex[2] + r * np.sin(ti)
y_end = apex[1] - length
ax.plot([apex[0], x_end], [apex[1], y_end], [apex[2], z_end],
color=COLORS['yellow'], alpha=0.25, linewidth=0.4)
# 锥底圆(加粗)
circle_t = np.linspace(0, 2 * np.pi, 100)
cx = apex[0] + r * np.cos(circle_t)
cz = apex[2] + r * np.sin(circle_t)
cy = np.full_like(cx, apex[1] - length)
ax.plot(cx, cy, cz, color=COLORS['yellow'], alpha=0.45, linewidth=0.8)
# 半透明锥面(用三角面片填充,增强锥体感)
n_seg = 48
t_seg = np.linspace(0, 2 * np.pi, n_seg + 1)
for j in range(n_seg):
tri = [
[apex[0], apex[1], apex[2]],
[apex[0] + r * np.cos(t_seg[j]), apex[1] - length, apex[2] + r * np.sin(t_seg[j])],
[apex[0] + r * np.cos(t_seg[j+1]), apex[1] - length, apex[2] + r * np.sin(t_seg[j+1])],
]
face = Poly3DCollection([tri], alpha=0.04, facecolor=COLORS['yellow'],
edgecolor='none')
ax.add_collection3d(face)
def draw_target_crosshair(ax, pos, size=12, color='k'):
"""
用小圆点 + 三轴短线(十字准星)标记目标器位置,
比大方块/菱形更精确、更美观。
"""
ax.scatter(*pos, marker='o', s=30, c=color, zorder=12)
half = size
for dx, dy, dz in [(half,0,0), (0,half,0), (0,0,half)]:
ax.plot([pos[0]-dx, pos[0]+dx],
[pos[1]-dy, pos[1]+dy],
[pos[2]-dz, pos[2]+dz],
color=color, linewidth=0.7, alpha=0.6, zorder=11)
def plot_trajectory_3d_v2(
data_path='Plots/data/eval_100full_trajectories.npz',
out_dir='Plots',
):
"""绘制 3D 轨迹图100 条版本)。"""
apply_style()
# 环境参数
x_h = np.array([0.0, -60.0, 0.0])
rho_safe = 15.0
theta_los_deg = 60.0
if not os.path.exists(data_path):
print(f"数据文件不存在: {data_path}")
print("请先运行带 100 条完整轨迹的评估脚本。")
return
data = np.load(data_path, allow_pickle=True)
n_traj = int(data.get('n_full_saved', 100))
fig = plt.figure(figsize=(DOUBLE_COL, DOUBLE_COL * 0.85))
ax = fig.add_subplot(111, projection='3d')
# ── 绘制安全约束 ──
draw_safety_sphere(ax, [0, 0, 0], rho_safe, color='red', alpha=0.06)
draw_los_cone(ax, [0, 0, 0], theta_los_deg, length=900, alpha=0.10)
# ── 标记保持点 ──
ax.scatter(*x_h, marker='*', s=100, c=COLORS['red'], zorder=10,
label=r'Hold point $\mathbf{x}_h$')
# ── 标记目标器(十字准星) ──
draw_target_crosshair(ax, [0, 0, 0], size=18, color=COLORS['black'])
# ── 绘制轨迹 ──
n_success = 0
n_fail = 0
n_timeout = 0
for i in range(n_traj):
key = f'traj{i}_states'
if key not in data:
continue
states = data[key]
reason = str(data.get(f'traj{i}_reason', 'none'))
x, y, z = states[:, 0], states[:, 1], states[:, 2]
if reason == 'success':
color = COLORS['blue']
alpha = 0.55
lw = 0.5
n_success += 1
elif reason == 'collision':
color = COLORS['red']
alpha = 0.7
lw = 0.6
n_fail += 1
else:
color = COLORS['grey']
alpha = 0.25
lw = 0.3
n_timeout += 1
# 轨迹线
ax.plot(x, y, z, color=color, alpha=alpha, linewidth=lw)
# 起始点(小圆点)
ax.scatter(x[0], y[0], z[0], marker='o', s=6, c=color, alpha=0.5,
edgecolors='none')
# ── 后向接近半空间指示线 ──
lim = 250
ax.plot([-lim, lim], [0, 0], [0, 0], 'k--', alpha=0.15, linewidth=0.3)
# ── 坐标轴标签 ──
ax.set_xlabel(r'$x$ (radial) [m]', labelpad=6)
ax.set_ylabel(r'$y$ (along-track) [m]', labelpad=6)
ax.set_zlabel(r'$z$ (normal) [m]', labelpad=6)
# ── 视角 ──
ax.view_init(elev=25, azim=-55)
# ── 图例 ──
from matplotlib.lines import Line2D
legend_elements = [
Line2D([0], [0], color=COLORS['blue'], lw=1,
label=f'Success ({n_success})'),
Line2D([0], [0], color=COLORS['grey'], lw=1,
label=f'Timeout ({n_timeout})'),
Line2D([0], [0], marker='*', color='w', markerfacecolor=COLORS['red'],
markersize=8, label=r'Hold point $\mathbf{x}_h$'),
Line2D([0], [0], marker='o', color='w', markerfacecolor=COLORS['black'],
markersize=5, label='Target'),
]
# 只在有碰撞轨迹时才加红色图例
if n_fail > 0:
legend_elements.insert(1,
Line2D([0], [0], color=COLORS['red'], lw=1,
label=f'Collision ({n_fail})'))
ax.legend(handles=legend_elements, loc='upper left', fontsize=6,
framealpha=0.9)
# ── 刻度 ──
ax.tick_params(axis='both', which='major', labelsize=6, pad=2)
save_fig(fig, 'fig2_trajectory_3d_v2', out_dir)
plt.close(fig)
print(f"✓ 图 2 (v2) 完成 — {n_success} success / {n_fail} collision / "
f"{n_timeout} timeout (共 {n_traj} 轨迹)")
if __name__ == '__main__':
plot_trajectory_3d_v2()