EG/plugins/user/soft_body_cloth_physics/debug/cloth_debug.py
2025-10-30 11:46:41 +08:00

1390 lines
46 KiB
Python

"""
调试可视化工具
提供软体和布料物理模拟的调试可视化功能
包含完整的可视化和性能监控功能
"""
from panda3d.core import NodePath, LineSegs, Vec3, Vec4, Point3
from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
from panda3d.core import GeomLines, Geom, GeomNode, TextNode
from panda3d.core import TransparencyAttrib
from typing import List, Dict, Any, Optional, Tuple
import math
class ClothDebugVisualizer:
"""
布料调试可视化器
提供布料物理模拟的可视化调试功能,包括网格、固定点、力、速度等
"""
# 可视化模式
MODE_WIREFRAME = "wireframe"
MODE_POINTS = "points"
MODE_SOLID = "solid"
# 颜色主题
COLOR_THEME_DEFAULT = "default"
COLOR_THEME_HEATMAP = "heatmap"
COLOR_THEME_STRESS = "stress"
def __init__(self, render_node: NodePath):
"""
初始化调试可视化器
Args:
render_node: 渲染节点
"""
self.render_node = render_node
self.debug_root = render_node.attachNewNode("cloth_debug_visualizer")
self.enabled = False
# 可视化标志
self.show_flags = {
'mesh': True,
'anchors': True,
'forces': False,
'velocities': False,
'normals': False,
'tears': True,
'collisions': False,
'bounding_box': False,
'stress': False,
'strain': False
}
# 可视化参数
self.visualization_params = {
'mesh_color': Vec4(0.0, 1.0, 0.0, 0.7), # 绿色半透明
'anchor_color': Vec4(1.0, 0.0, 0.0, 1.0), # 红色
'force_color': Vec4(1.0, 1.0, 0.0, 1.0), # 黄色
'velocity_color': Vec4(0.0, 0.0, 1.0, 1.0), # 蓝色
'normal_color': Vec4(1.0, 0.5, 0.0, 1.0), # 橙色
'tear_color': Vec4(1.0, 0.0, 1.0, 1.0), # 紫色
'collision_color': Vec4(1.0, 0.5, 0.0, 1.0), # 橙色
'line_thickness': 2.0,
'point_size': 5.0,
'vector_scale': 1.0,
'transparency': 0.7
}
# 性能监控
self.performance_stats = {
'last_update_time': 0.0,
'update_count': 0,
'rendered_objects': 0,
'total_vertices': 0,
'fps': 0.0
}
# 可视化模式
self.visualization_mode = self.MODE_WIREFRAME
self.color_theme = self.COLOR_THEME_DEFAULT
# 统计信息显示
self.stats_node = None
self.show_stats = False
def enable(self):
"""
启用调试可视化
"""
self.enabled = True
self.debug_root.show()
print("✓ 布料调试可视化已启用")
def disable(self):
"""
禁用调试可视化
"""
self.enabled = False
self.debug_root.hide()
print("✓ 布料调试可视化已禁用")
def set_visibility_flags(self, **kwargs):
"""
设置可视化标志
Args:
**kwargs: 可视化标志键值对
"""
for key, value in kwargs.items():
if key in self.show_flags:
self.show_flags[key] = value
def set_visualization_params(self, **kwargs):
"""
设置可视化参数
Args:
**kwargs: 可视化参数键值对
"""
for key, value in kwargs.items():
if key in self.visualization_params:
self.visualization_params[key] = value
def set_visualization_mode(self, mode: str):
"""
设置可视化模式
Args:
mode: 可视化模式
"""
if mode in [self.MODE_WIREFRAME, self.MODE_POINTS, self.MODE_SOLID]:
self.visualization_mode = mode
def set_color_theme(self, theme: str):
"""
设置颜色主题
Args:
theme: 颜色主题
"""
if theme in [self.COLOR_THEME_DEFAULT, self.COLOR_THEME_HEATMAP, self.COLOR_THEME_STRESS]:
self.color_theme = theme
def enable_statistics(self, show: bool = True):
"""
启用统计信息显示
Args:
show: 是否显示统计信息
"""
self.show_stats = show
if show and not self.stats_node:
self._create_stats_display()
elif not show and self.stats_node:
self.stats_node.removeNode()
self.stats_node = None
def visualize_cloth_mesh(self, cloth_info: Dict[str, Any]):
"""
可视化布料网格
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['mesh']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "mesh")
# 获取软体节点
soft_body = cloth_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
links = soft_body.get_links()
if not nodes or not links:
return
# 创建线条段
lines = LineSegs()
lines.setThickness(self.visualization_params['line_thickness'])
lines.setColor(self.visualization_params['mesh_color'])
# 根据颜色主题设置颜色
base_color = self.visualization_params['mesh_color']
# 绘制链接线
for link in links:
node_a = nodes[link.get_node_a()]
node_b = nodes[link.get_node_b()]
pos_a = node_a.get_position()
pos_b = node_b.get_position()
# 根据模式调整颜色
if self.color_theme == self.COLOR_THEME_STRESS:
# 根据应力调整颜色
stress = link.get_bbending() # 简化处理
color_factor = min(1.0, stress * 2.0)
color = Vec4(
base_color[0] + color_factor * (1.0 - base_color[0]),
base_color[1] * (1.0 - color_factor),
base_color[2] * (1.0 - color_factor),
base_color[3]
)
lines.setColor(color)
elif self.color_theme == self.COLOR_THEME_HEATMAP:
# 根据位置高度调整颜色
height_factor = (pos_a[2] + pos_b[2]) / 2.0
normalized_height = max(0.0, min(1.0, (height_factor + 10.0) / 20.0))
lines.setColor(Vec4(normalized_height, 1.0 - normalized_height, 0.5, base_color[3]))
else:
lines.setColor(base_color)
lines.moveTo(pos_a)
lines.drawTo(pos_b)
# 创建可视化节点
mesh_geom = lines.create()
mesh_node = self.debug_root.attachNewNode(mesh_geom)
mesh_node.setName(f"cloth_mesh_debug_{id(cloth_info)}")
cloth_info['_debug_mesh_node'] = mesh_node
# 应用透明度
mesh_node.setTransparency(TransparencyAttrib.MAlpha)
self.performance_stats['total_vertices'] += len(nodes)
except Exception as e:
print(f"⚠ 可视化布料网格失败: {e}")
def visualize_anchors(self, cloth_info: Dict[str, Any]):
"""
可视化固定点
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['anchors']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "anchors")
# 获取固定点信息
anchors = cloth_info.get('anchors', [])
if not anchors:
return
# 创建点表示固定点
lines = LineSegs()
lines.setThickness(self.visualization_params['point_size'])
lines.setColor(self.visualization_params['anchor_color'])
# 绘制固定点
for anchor in anchors:
vertex_index = anchor.get('vertex_index', 0)
# 获取软体节点
soft_body = cloth_info.get('soft_body')
if soft_body:
nodes = soft_body.get_nodes()
if vertex_index < len(nodes):
pos = nodes[vertex_index].get_position()
lines.moveTo(pos)
lines.drawTo(pos) # 绘制点
# 创建可视化节点
anchor_geom = lines.create()
anchor_node = self.debug_root.attachNewNode(anchor_geom)
anchor_node.setName(f"cloth_anchors_debug_{id(cloth_info)}")
cloth_info['_debug_anchor_node'] = anchor_node
except Exception as e:
print(f"⚠ 可视化固定点失败: {e}")
def visualize_forces(self, cloth_info: Dict[str, Any]):
"""
可视化力
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['forces']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "forces")
# 获取软体节点
soft_body = cloth_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
if not nodes:
return
# 创建力向量可视化
lines = LineSegs()
lines.setThickness(1.0)
lines.setColor(self.visualization_params['force_color'])
# 绘制力向量
scale = self.visualization_params['vector_scale'] * 0.1
for i, node in enumerate(nodes):
pos = node.get_position()
# 简化处理:使用速度作为力的近似
velocity = node.get_velocity()
force_vector = velocity * scale
if force_vector.length() > 0.01: # 只显示显著的力
lines.moveTo(pos)
lines.drawTo(pos + force_vector)
# 绘制箭头
self._draw_arrow_head(lines, pos, pos + force_vector, 0.1)
# 创建可视化节点
force_geom = lines.create()
force_node = self.debug_root.attachNewNode(force_geom)
force_node.setName(f"cloth_forces_debug_{id(cloth_info)}")
cloth_info['_debug_force_node'] = force_node
except Exception as e:
print(f"⚠ 可视化布料力失败: {e}")
def visualize_velocities(self, cloth_info: Dict[str, Any]):
"""
可视化速度
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['velocities']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "velocities")
# 获取软体节点
soft_body = cloth_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
if not nodes:
return
# 创建速度向量可视化
lines = LineSegs()
lines.setThickness(1.0)
lines.setColor(self.visualization_params['velocity_color'])
# 绘制速度向量
scale = self.visualization_params['vector_scale'] * 0.05
for i, node in enumerate(nodes):
pos = node.get_position()
velocity = node.get_velocity()
velocity_vector = velocity * scale
if velocity_vector.length() > 0.005: # 只显示显著的速度
lines.moveTo(pos)
lines.drawTo(pos + velocity_vector)
# 绘制箭头
self._draw_arrow_head(lines, pos, pos + velocity_vector, 0.05)
# 创建可视化节点
velocity_geom = lines.create()
velocity_node = self.debug_root.attachNewNode(velocity_geom)
velocity_node.setName(f"cloth_velocities_debug_{id(cloth_info)}")
cloth_info['_debug_velocity_node'] = velocity_node
except Exception as e:
print(f"⚠ 可视化布料速度失败: {e}")
def visualize_normals(self, cloth_info: Dict[str, Any]):
"""
可视化法线
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['normals']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "normals")
# 获取软体节点
soft_body = cloth_info.get('soft_body')
if not soft_body:
return
# 获取面信息
faces = soft_body.get_faces()
if not faces:
return
# 创建法线可视化
lines = LineSegs()
lines.setThickness(1.0)
lines.setColor(self.visualization_params['normal_color'])
# 绘制面法线
scale = self.visualization_params['vector_scale'] * 0.2
for face in faces:
# 简化处理:计算面的中心和法线
# 实际实现需要更精确的计算
pass
# 创建可视化节点
normal_geom = lines.create()
normal_node = self.debug_root.attachNewNode(normal_geom)
normal_node.setName(f"cloth_normals_debug_{id(cloth_info)}")
cloth_info['_debug_normal_node'] = normal_node
except Exception as e:
print(f"⚠ 可视化布料法线失败: {e}")
def visualize_tears(self, cloth_info: Dict[str, Any]):
"""
可视化撕裂
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['tears']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "tears")
# 获取撕裂信息
tears = cloth_info.get('tears', [])
if not tears:
return
# 创建撕裂可视化
lines = LineSegs()
lines.setThickness(self.visualization_params['line_thickness'] * 2)
lines.setColor(self.visualization_params['tear_color'])
# 绘制撕裂线
for tear_info in tears:
tear_point = tear_info.get('point', Point3(0, 0, 0))
tear_radius = tear_info.get('radius', 0.1)
# 绘制撕裂圆
self._draw_circle(lines, tear_point, tear_radius, 16)
# 创建可视化节点
tear_geom = lines.create()
tear_node = self.debug_root.attachNewNode(tear_geom)
tear_node.setName(f"cloth_tears_debug_{id(cloth_info)}")
cloth_info['_debug_tear_node'] = tear_node
except Exception as e:
print(f"⚠ 可视化布料撕裂失败: {e}")
def visualize_collisions(self, cloth_info: Dict[str, Any]):
"""
可视化碰撞
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['collisions']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "collisions")
# 获取碰撞信息
collisions = cloth_info.get('collisions', [])
if not collisions:
return
# 创建碰撞可视化
lines = LineSegs()
lines.setThickness(self.visualization_params['point_size'])
lines.setColor(self.visualization_params['collision_color'])
# 绘制碰撞点
for collision_info in collisions:
collision_point = collision_info.get('point', Point3(0, 0, 0))
lines.moveTo(collision_point)
lines.drawTo(collision_point)
# 创建可视化节点
collision_geom = lines.create()
collision_node = self.debug_root.attachNewNode(collision_geom)
collision_node.setName(f"cloth_collisions_debug_{id(cloth_info)}")
cloth_info['_debug_collision_node'] = collision_node
except Exception as e:
print(f"⚠ 可视化布料碰撞失败: {e}")
def visualize_bounding_box(self, cloth_info: Dict[str, Any]):
"""
可视化包围盒
Args:
cloth_info: 布料对象信息
"""
if not self.enabled or not self.show_flags['bounding_box']:
return
try:
# 清除之前的可视化
self._clear_cloth_visualization(cloth_info, "bounding_box")
# 获取软体节点
soft_body = cloth_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
if not nodes:
return
# 计算包围盒
min_pos = Point3(float('inf'), float('inf'), float('inf'))
max_pos = Point3(float('-inf'), float('-inf'), float('-inf'))
for node in nodes:
pos = node.get_position()
min_pos.set(
min(min_pos[0], pos[0]),
min(min_pos[1], pos[1]),
min(min_pos[2], pos[2])
)
max_pos.set(
max(max_pos[0], pos[0]),
max(max_pos[1], pos[1]),
max(max_pos[2], pos[2])
)
# 创建包围盒可视化
lines = LineSegs()
lines.setThickness(1.0)
lines.setColor(Vec4(0.5, 0.5, 1.0, 0.5)) # 蓝色半透明
# 绘制包围盒
self._draw_box(lines, min_pos, max_pos)
# 创建可视化节点
bbox_geom = lines.create()
bbox_node = self.debug_root.attachNewNode(bbox_geom)
bbox_node.setName(f"cloth_bbox_debug_{id(cloth_info)}")
cloth_info['_debug_bbox_node'] = bbox_node
except Exception as e:
print(f"⚠ 可视化布料包围盒失败: {e}")
def update_visualization(self, cloth_info: Dict[str, Any]):
"""
更新布料可视化
Args:
cloth_info: 布料对象信息
"""
if not self.enabled:
return
# 更新各种可视化
if self.show_flags['mesh']:
self.visualize_cloth_mesh(cloth_info)
if self.show_flags['anchors']:
self.visualize_anchors(cloth_info)
if self.show_flags['forces']:
self.visualize_forces(cloth_info)
if self.show_flags['velocities']:
self.visualize_velocities(cloth_info)
if self.show_flags['normals']:
self.visualize_normals(cloth_info)
if self.show_flags['tears']:
self.visualize_tears(cloth_info)
if self.show_flags['collisions']:
self.visualize_collisions(cloth_info)
if self.show_flags['bounding_box']:
self.visualize_bounding_box(cloth_info)
# 更新性能统计
self.performance_stats['update_count'] += 1
def clear_cloth_visualization(self, cloth_info: Dict[str, Any]):
"""
清除布料可视化
Args:
cloth_info: 布料对象信息
"""
self._clear_cloth_visualization(cloth_info, "all")
def _clear_cloth_visualization(self, cloth_info: Dict[str, Any],
visualization_type: str):
"""
清除布料可视化(内部方法)
Args:
cloth_info: 布料对象信息
visualization_type: 可视化类型
"""
try:
visualization_map = {
"mesh": '_debug_mesh_node',
"anchors": '_debug_anchor_node',
"forces": '_debug_force_node',
"velocities": '_debug_velocity_node',
"normals": '_debug_normal_node',
"tears": '_debug_tear_node',
"collisions": '_debug_collision_node',
"bounding_box": '_debug_bbox_node'
}
if visualization_type == "all":
# 清除所有可视化
for key in visualization_map.values():
node = cloth_info.get(key)
if node and not node.isEmpty():
node.removeNode()
if key in cloth_info:
del cloth_info[key]
else:
# 清除特定可视化
key = visualization_map.get(visualization_type)
if key:
node = cloth_info.get(key)
if node and not node.isEmpty():
node.removeNode()
if key in cloth_info:
del cloth_info[key]
except Exception as e:
print(f"⚠ 清除布料可视化失败: {e}")
def _draw_arrow_head(self, lines: LineSegs, start: Point3, end: Point3, size: float):
"""
绘制箭头头部
Args:
lines: 线段对象
start: 起点
end: 终点
size: 箭头大小
"""
direction = end - start
length = direction.length()
if length == 0:
return
direction.normalize()
# 计算垂直向量
if abs(direction.dot(Vec3(0, 0, 1))) < 0.999:
up_vector = Vec3(0, 0, 1)
else:
up_vector = Vec3(1, 0, 0)
right_vector = direction.cross(up_vector)
right_vector.normalize()
up_vector = right_vector.cross(direction)
up_vector.normalize()
# 绘制箭头
arrow_point1 = end - direction * size + right_vector * size * 0.5
arrow_point2 = end - direction * size - right_vector * size * 0.5
lines.moveTo(end)
lines.drawTo(arrow_point1)
lines.moveTo(end)
lines.drawTo(arrow_point2)
def _draw_circle(self, lines: LineSegs, center: Point3, radius: float, segments: int):
"""
绘制圆形
Args:
lines: 线段对象
center: 圆心
radius: 半径
segments: 分段数
"""
for i in range(segments):
angle1 = (i / segments) * 2 * math.pi
angle2 = ((i + 1) / segments) * 2 * math.pi
point1 = center + Point3(math.cos(angle1) * radius, math.sin(angle1) * radius, 0)
point2 = center + Point3(math.cos(angle2) * radius, math.sin(angle2) * radius, 0)
lines.moveTo(point1)
lines.drawTo(point2)
def _draw_box(self, lines: LineSegs, min_pos: Point3, max_pos: Point3):
"""
绘制包围盒
Args:
lines: 线段对象
min_pos: 最小点
max_pos: 最大点
"""
# 8个顶点
vertices = [
Point3(min_pos[0], min_pos[1], min_pos[2]), # 0
Point3(max_pos[0], min_pos[1], min_pos[2]), # 1
Point3(max_pos[0], max_pos[1], min_pos[2]), # 2
Point3(min_pos[0], max_pos[1], min_pos[2]), # 3
Point3(min_pos[0], min_pos[1], max_pos[2]), # 4
Point3(max_pos[0], min_pos[1], max_pos[2]), # 5
Point3(max_pos[0], max_pos[1], max_pos[2]), # 6
Point3(min_pos[0], max_pos[1], max_pos[2]) # 7
]
# 12条边
edges = [
(0, 1), (1, 2), (2, 3), (3, 0), # 底面
(4, 5), (5, 6), (6, 7), (7, 4), # 顶面
(0, 4), (1, 5), (2, 6), (3, 7) # 垂直边
]
for edge in edges:
lines.moveTo(vertices[edge[0]])
lines.drawTo(vertices[edge[1]])
def _create_stats_display(self):
"""
创建统计信息显示
"""
# 创建文本节点
text_node = TextNode('cloth_debug_stats')
text_node.setText("Cloth Debug Stats")
text_node.setAlign(TextNode.ALeft)
text_node.setTextColor(1, 1, 0, 1) # 黄色
# 创建节点路径
self.stats_node = self.debug_root.attachNewNode(text_node)
self.stats_node.setScale(0.1)
self.stats_node.setPos(-2, 0, 2) # 屏幕左上角
def update_stats_display(self):
"""
更新统计信息显示
"""
if self.stats_node and self.show_stats:
text = f"Cloth Debug Stats\n"
text += f"Objects: {self.performance_stats['rendered_objects']}\n"
text += f"Vertices: {self.performance_stats['total_vertices']}\n"
text += f"Updates: {self.performance_stats['update_count']}\n"
text += f"FPS: {self.performance_stats['fps']:.1f}"
self.stats_node.node().setText(text)
def cleanup(self):
"""
清理所有调试可视化
"""
if not self.debug_root.isEmpty():
self.debug_root.removeNode()
if self.stats_node:
self.stats_node.removeNode()
self.stats_node = None
print("✓ 布料调试可视化已清理")
class SoftBodyDebugVisualizer:
"""
软体调试可视化器
提供软体物理模拟的可视化调试功能
"""
def __init__(self, render_node: NodePath):
"""
初始化调试可视化器
Args:
render_node: 渲染节点
"""
self.render_node = render_node
self.debug_root = render_node.attachNewNode("soft_body_debug_visualizer")
self.enabled = False
# 可视化标志
self.show_flags = {
'mesh': True,
'forces': False,
'velocities': False,
'normals': False,
'collisions': False,
'bounding_box': False,
'mass_distribution': False,
'constraints': False
}
# 可视化参数
self.visualization_params = {
'mesh_color': Vec4(1.0, 0.0, 0.0, 0.7), # 红色半透明
'force_color': Vec4(1.0, 1.0, 0.0, 1.0), # 黄色
'velocity_color': Vec4(0.0, 0.0, 1.0, 1.0), # 蓝色
'normal_color': Vec4(1.0, 0.5, 0.0, 1.0), # 橙色
'collision_color': Vec4(1.0, 0.5, 0.0, 1.0), # 橙色
'constraint_color': Vec4(0.5, 0.0, 1.0, 1.0), # 紫色
'line_thickness': 2.0,
'point_size': 5.0,
'vector_scale': 1.0,
'transparency': 0.7
}
# 性能监控
self.performance_stats = {
'last_update_time': 0.0,
'update_count': 0,
'rendered_objects': 0,
'total_vertices': 0,
'fps': 0.0
}
# 统计信息显示
self.stats_node = None
self.show_stats = False
def enable(self):
"""
启用调试可视化
"""
self.enabled = True
self.debug_root.show()
print("✓ 软体调试可视化已启用")
def disable(self):
"""
禁用调试可视化
"""
self.enabled = False
self.debug_root.hide()
print("✓ 软体调试可视化已禁用")
def set_visibility_flags(self, **kwargs):
"""
设置可视化标志
Args:
**kwargs: 可视化标志键值对
"""
for key, value in kwargs.items():
if key in self.show_flags:
self.show_flags[key] = value
def set_visualization_params(self, **kwargs):
"""
设置可视化参数
Args:
**kwargs: 可视化参数键值对
"""
for key, value in kwargs.items():
if key in self.visualization_params:
self.visualization_params[key] = value
def enable_statistics(self, show: bool = True):
"""
启用统计信息显示
Args:
show: 是否显示统计信息
"""
self.show_stats = show
if show and not self.stats_node:
self._create_stats_display()
elif not show and self.stats_node:
self.stats_node.removeNode()
self.stats_node = None
def visualize_soft_body_mesh(self, soft_info: Dict[str, Any]):
"""
可视化软体网格
Args:
soft_info: 软体对象信息
"""
if not self.enabled or not self.show_flags['mesh']:
return
try:
# 清除之前的可视化
self._clear_soft_body_visualization(soft_info, "mesh")
# 获取软体节点
soft_body = soft_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
links = soft_body.get_links()
if not nodes or not links:
return
# 创建线条段
lines = LineSegs()
lines.setThickness(self.visualization_params['line_thickness'])
lines.setColor(self.visualization_params['mesh_color'])
# 绘制链接线
for link in links:
node_a = nodes[link.get_node_a()]
node_b = nodes[link.get_node_b()]
pos_a = node_a.get_position()
pos_b = node_b.get_position()
lines.moveTo(pos_a)
lines.drawTo(pos_b)
# 创建可视化节点
mesh_geom = lines.create()
mesh_node = self.debug_root.attachNewNode(mesh_geom)
mesh_node.setName(f"soft_body_mesh_debug_{id(soft_info)}")
soft_info['_debug_mesh_node'] = mesh_node
# 应用透明度
mesh_node.setTransparency(TransparencyAttrib.MAlpha)
self.performance_stats['total_vertices'] += len(nodes)
except Exception as e:
print(f"⚠ 可视化软体网格失败: {e}")
def visualize_soft_body_forces(self, soft_info: Dict[str, Any]):
"""
可视化软体力
Args:
soft_info: 软体对象信息
"""
if not self.enabled or not self.show_flags['forces']:
return
try:
# 清除之前的可视化
self._clear_soft_body_visualization(soft_info, "forces")
# 获取软体节点
soft_body = soft_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
if not nodes:
return
# 创建力向量可视化
lines = LineSegs()
lines.setThickness(1.0)
lines.setColor(self.visualization_params['force_color'])
# 绘制力向量
scale = self.visualization_params['vector_scale'] * 0.1
for i, node in enumerate(nodes):
pos = node.get_position()
# 简化处理:使用速度作为力的近似
velocity = node.get_velocity()
force_vector = velocity * scale
if force_vector.length() > 0.01: # 只显示显著的力
lines.moveTo(pos)
lines.drawTo(pos + force_vector)
# 绘制箭头
self._draw_arrow_head(lines, pos, pos + force_vector, 0.1)
# 创建可视化节点
force_geom = lines.create()
force_node = self.debug_root.attachNewNode(force_geom)
force_node.setName(f"soft_body_forces_debug_{id(soft_info)}")
soft_info['_debug_force_node'] = force_node
except Exception as e:
print(f"⚠ 可视化软体力失败: {e}")
def visualize_soft_body_velocities(self, soft_info: Dict[str, Any]):
"""
可视化软体速度
Args:
soft_info: 软体对象信息
"""
if not self.enabled or not self.show_flags['velocities']:
return
try:
# 清除之前的可视化
self._clear_soft_body_visualization(soft_info, "velocities")
# 获取软体节点
soft_body = soft_info.get('soft_body')
if not soft_body:
return
# 获取节点信息
nodes = soft_body.get_nodes()
if not nodes:
return
# 创建速度向量可视化
lines = LineSegs()
lines.setThickness(1.0)
lines.setColor(self.visualization_params['velocity_color'])
# 绘制速度向量
scale = self.visualization_params['vector_scale'] * 0.05
for i, node in enumerate(nodes):
pos = node.get_position()
velocity = node.get_velocity()
velocity_vector = velocity * scale
if velocity_vector.length() > 0.005: # 只显示显著的速度
lines.moveTo(pos)
lines.drawTo(pos + velocity_vector)
# 绘制箭头
self._draw_arrow_head(lines, pos, pos + velocity_vector, 0.05)
# 创建可视化节点
velocity_geom = lines.create()
velocity_node = self.debug_root.attachNewNode(velocity_geom)
velocity_node.setName(f"soft_body_velocities_debug_{id(soft_info)}")
soft_info['_debug_velocity_node'] = velocity_node
except Exception as e:
print(f"⚠ 可视化软体速度失败: {e}")
def visualize_constraints(self, soft_info: Dict[str, Any]):
"""
可视化约束
Args:
soft_info: 软体对象信息
"""
if not self.enabled or not self.show_flags['constraints']:
return
try:
# 清除之前的可视化
self._clear_soft_body_visualization(soft_info, "constraints")
# 获取约束信息
constraints = soft_info.get('constraints', [])
if not constraints:
return
# 创建约束可视化
lines = LineSegs()
lines.setThickness(self.visualization_params['line_thickness'])
lines.setColor(self.visualization_params['constraint_color'])
# 绘制约束线
for constraint_info in constraints:
# 简化处理
pass
# 创建可视化节点
constraint_geom = lines.create()
constraint_node = self.debug_root.attachNewNode(constraint_geom)
constraint_node.setName(f"soft_body_constraints_debug_{id(soft_info)}")
soft_info['_debug_constraint_node'] = constraint_node
except Exception as e:
print(f"⚠ 可视化软体约束失败: {e}")
def update_visualization(self, soft_info: Dict[str, Any]):
"""
更新软体可视化
Args:
soft_info: 软体对象信息
"""
if not self.enabled:
return
# 更新各种可视化
if self.show_flags['mesh']:
self.visualize_soft_body_mesh(soft_info)
if self.show_flags['forces']:
self.visualize_soft_body_forces(soft_info)
if self.show_flags['velocities']:
self.visualize_soft_body_velocities(soft_info)
if self.show_flags['constraints']:
self.visualize_constraints(soft_info)
# 更新性能统计
self.performance_stats['update_count'] += 1
def clear_soft_body_visualization(self, soft_info: Dict[str, Any]):
"""
清除软体可视化
Args:
soft_info: 软体对象信息
"""
self._clear_soft_body_visualization(soft_info, "all")
def _clear_soft_body_visualization(self, soft_info: Dict[str, Any],
visualization_type: str):
"""
清除软体可视化(内部方法)
Args:
soft_info: 软体对象信息
visualization_type: 可视化类型
"""
try:
visualization_map = {
"mesh": '_debug_mesh_node',
"forces": '_debug_force_node',
"velocities": '_debug_velocity_node',
"constraints": '_debug_constraint_node'
}
if visualization_type == "all":
# 清除所有可视化
for key in visualization_map.values():
node = soft_info.get(key)
if node and not node.isEmpty():
node.removeNode()
if key in soft_info:
del soft_info[key]
else:
# 清除特定可视化
key = visualization_map.get(visualization_type)
if key:
node = soft_info.get(key)
if node and not node.isEmpty():
node.removeNode()
if key in soft_info:
del soft_info[key]
except Exception as e:
print(f"⚠ 清除软体可视化失败: {e}")
def _draw_arrow_head(self, lines: LineSegs, start: Point3, end: Point3, size: float):
"""
绘制箭头头部
Args:
lines: 线段对象
start: 起点
end: 终点
size: 箭头大小
"""
direction = end - start
length = direction.length()
if length == 0:
return
direction.normalize()
# 计算垂直向量
if abs(direction.dot(Vec3(0, 0, 1))) < 0.999:
up_vector = Vec3(0, 0, 1)
else:
up_vector = Vec3(1, 0, 0)
right_vector = direction.cross(up_vector)
right_vector.normalize()
up_vector = right_vector.cross(direction)
up_vector.normalize()
# 绘制箭头
arrow_point1 = end - direction * size + right_vector * size * 0.5
arrow_point2 = end - direction * size - right_vector * size * 0.5
lines.moveTo(end)
lines.drawTo(arrow_point1)
lines.moveTo(end)
lines.drawTo(arrow_point2)
def _create_stats_display(self):
"""
创建统计信息显示
"""
# 创建文本节点
text_node = TextNode('soft_body_debug_stats')
text_node.setText("Soft Body Debug Stats")
text_node.setAlign(TextNode.ALeft)
text_node.setTextColor(1, 0, 0, 1) # 红色
# 创建节点路径
self.stats_node = self.debug_root.attachNewNode(text_node)
self.stats_node.setScale(0.1)
self.stats_node.setPos(-2, 0, 1) # 屏幕左上角
def update_stats_display(self):
"""
更新统计信息显示
"""
if self.stats_node and self.show_stats:
text = f"Soft Body Debug Stats\n"
text += f"Objects: {self.performance_stats['rendered_objects']}\n"
text += f"Vertices: {self.performance_stats['total_vertices']}\n"
text += f"Updates: {self.performance_stats['update_count']}\n"
text += f"FPS: {self.performance_stats['fps']:.1f}"
self.stats_node.node().setText(text)
def cleanup(self):
"""
清理所有调试可视化
"""
if not self.debug_root.isEmpty():
self.debug_root.removeNode()
if self.stats_node:
self.stats_node.removeNode()
self.stats_node = None
print("✓ 软体调试可视化已清理")
class PhysicsDebugManager:
"""
物理调试管理器
统一管理布料和软体的调试可视化
"""
def __init__(self, render_node: NodePath):
"""
初始化物理调试管理器
Args:
render_node: 渲染节点
"""
self.render_node = render_node
self.cloth_visualizer = ClothDebugVisualizer(render_node)
self.soft_body_visualizer = SoftBodyDebugVisualizer(render_node)
self.enabled = False
# 全局设置
self.global_settings = {
'show_statistics': False,
'update_frequency': 1, # 每帧更新
'auto_cleanup': True
}
def enable(self):
"""
启用物理调试
"""
self.enabled = True
self.cloth_visualizer.enable()
self.soft_body_visualizer.enable()
print("✓ 物理调试已启用")
def disable(self):
"""
禁用物理调试
"""
self.enabled = False
self.cloth_visualizer.disable()
self.soft_body_visualizer.disable()
print("✓ 物理调试已禁用")
def set_global_settings(self, **kwargs):
"""
设置全局调试设置
Args:
**kwargs: 设置键值对
"""
for key, value in kwargs.items():
if key in self.global_settings:
self.global_settings[key] = value
def set_cloth_visibility(self, **kwargs):
"""
设置布料可视化标志
Args:
**kwargs: 可视化标志键值对
"""
self.cloth_visualizer.set_visibility_flags(**kwargs)
def set_soft_body_visibility(self, **kwargs):
"""
设置软体可视化标志
Args:
**kwargs: 可视化标志键值对
"""
self.soft_body_visualizer.set_visibility_flags(**kwargs)
def set_cloth_params(self, **kwargs):
"""
设置布料可视化参数
Args:
**kwargs: 参数键值对
"""
self.cloth_visualizer.set_visualization_params(**kwargs)
def set_soft_body_params(self, **kwargs):
"""
设置软体可视化参数
Args:
**kwargs: 参数键值对
"""
self.soft_body_visualizer.set_visualization_params(**kwargs)
def update_all_visualizations(self, cloth_objects: List[Dict[str, Any]],
soft_body_objects: List[Dict[str, Any]]):
"""
更新所有可视化
Args:
cloth_objects: 布料对象列表
soft_body_objects: 软体对象列表
"""
if not self.enabled:
return
# 更新布料可视化
for cloth_info in cloth_objects:
self.cloth_visualizer.update_visualization(cloth_info)
# 更新软体可视化
for soft_info in soft_body_objects:
self.soft_body_visualizer.update_visualization(soft_info)
# 更新统计显示
if self.global_settings['show_statistics']:
self.cloth_visualizer.update_stats_display()
self.soft_body_visualizer.update_stats_display()
def clear_all_visualizations(self, cloth_objects: List[Dict[str, Any]],
soft_body_objects: List[Dict[str, Any]]):
"""
清除所有可视化
Args:
cloth_objects: 布料对象列表
soft_body_objects: 软体对象列表
"""
# 清除布料可视化
for cloth_info in cloth_objects:
self.cloth_visualizer.clear_cloth_visualization(cloth_info)
# 清除软体可视化
for soft_info in soft_body_objects:
self.soft_body_visualizer.clear_soft_body_visualization(soft_info)
def cleanup(self):
"""
清理所有调试可视化
"""
self.cloth_visualizer.cleanup()
self.soft_body_visualizer.cleanup()
print("✓ 物理调试已清理")
def get_debug_stats(self) -> Dict[str, Any]:
"""
获取调试统计信息
Returns:
统计信息字典
"""
return {
'cloth_stats': self.cloth_visualizer.performance_stats,
'soft_body_stats': self.soft_body_visualizer.performance_stats,
'global_settings': self.global_settings,
'enabled': self.enabled
}