1390 lines
46 KiB
Python
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
|
|
} |