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

1228 lines
44 KiB
Python
Raw 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.

"""
调试和可视化工具模块
负责物理系统的调试可视化和性能分析
"""
from panda3d.core import Vec3, Point3, Vec4, LVector3f
from panda3d.core import NodePath, CardMaker, TextNode, LineSegs
from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
from panda3d.core import GeomTriangles, Geom, GeomNode
from panda3d.core import ClockObject, ConfigVariableBool
import math
import time
from collections import deque
class PhysicsDebug:
"""
物理调试类
提供物理系统的可视化和调试功能
"""
def __init__(self, physics_world, render_node):
"""
初始化物理调试工具
Args:
physics_world (PhysicsWorld): 物理世界对象
render_node (NodePath): 渲染节点
"""
self.physics_world = physics_world
self.render_node = render_node
# 调试可视化设置
self.debug_enabled = ConfigVariableBool('physics-debug-enabled', False).getValue()
self.show_constraints = ConfigVariableBool('physics-show-constraints', True).getValue()
self.show_aabbs = ConfigVariableBool('physics-show-aabbs', False).getValue()
self.show_contacts = ConfigVariableBool('physics-show-contacts', False).getValue()
self.show_normals = ConfigVariableBool('physics-show-normals', False).getValue()
self.show_velocities = ConfigVariableBool('physics-show-velocities', False).getValue()
self.show_forces = ConfigVariableBool('physics-show-forces', False).getValue()
self.show_axes = ConfigVariableBool('physics-show-axes', False).getValue()
self.show_mass_centers = ConfigVariableBool('physics-show-mass-centers', False).getValue()
self.show_collision_shapes = ConfigVariableBool('physics-show-collision-shapes', True).getValue()
self.show_sleeping_states = ConfigVariableBool('physics-show-sleeping-states', True).getValue()
self.show_particle_trails = ConfigVariableBool('physics-show-particle-trails', False).getValue()
# 可视化节点
self.debug_root = self.render_node.attachNewNode("PhysicsDebug")
self.constraint_node = self.debug_root.attachNewNode("Constraints")
self.aabb_node = self.debug_root.attachNewNode("AABBs")
self.contact_node = self.debug_root.attachNewNode("Contacts")
self.normal_node = self.debug_root.attachNewNode("Normals")
self.velocity_node = self.debug_root.attachNewNode("Velocities")
self.force_node = self.debug_root.attachNewNode("Forces")
self.axes_node = self.debug_root.attachNewNode("Axes")
self.mass_center_node = self.debug_root.attachNewNode("MassCenters")
self.collision_shape_node = self.debug_root.attachNewNode("CollisionShapes")
self.sleeping_node = self.debug_root.attachNewNode("SleepingStates")
self.particle_trail_node = self.debug_root.attachNewNode("ParticleTrails")
# 调试颜色配置
self.colors = {
'static': Vec4(0.5, 0.5, 0.5, 0.7), # 灰色 - 静态物体
'dynamic': Vec4(0.0, 1.0, 0.0, 0.7), # 绿色 - 动态物体
'kinematic': Vec4(0.0, 0.0, 1.0, 0.7), # 蓝色 - 运动物体
'sleeping': Vec4(0.3, 0.3, 0.3, 0.5), # 深灰色 - 休眠物体
'constraint': Vec4(1.0, 1.0, 0.0, 0.8), # 黄色 - 约束
'contact': Vec4(1.0, 0.0, 0.0, 0.8), # 红色 - 接触点
'normal': Vec4(0.0, 1.0, 1.0, 0.8), # 青色 - 法线
'velocity': Vec4(1.0, 0.5, 0.0, 0.8), # 橙色 - 速度
'force': Vec4(1.0, 0.0, 1.0, 0.8), # 紫色 - 力
'positive_axis': Vec4(1.0, 0.0, 0.0, 0.9), # 红色 - X轴
'negative_axis': Vec4(0.0, 1.0, 0.0, 0.9), # 绿色 - Y轴
'neutral_axis': Vec4(0.0, 0.0, 1.0, 0.9), # 蓝色 - Z轴
'mass_center': Vec4(1.0, 1.0, 0.0, 0.9), # 黄色 - 质心
'particle_trail': Vec4(0.8, 0.8, 1.0, 0.6), # 浅蓝色 - 粒子轨迹
}
# 可视化参数
self.line_thickness = 2.0
self.axis_length = 1.0
self.velocity_scale = 0.1
self.force_scale = 0.01
self.contact_point_size = 0.1
self.normal_scale = 0.5
self.aabb_line_style = 'solid' # solid, dashed, dotted
self.collision_shape_style = 'wireframe' # wireframe, solid, transparent
# 性能监控
self.debug_update_frequency = 30 # 每秒更新次数
self.debug_update_counter = 0
self.last_debug_update = 0.0
# 调试文本显示
self.debug_text_enabled = True
self.debug_text_node = None
self.debug_text = ""
# 碰撞可视化历史
self.contact_history = deque(maxlen=100)
self.collision_history = deque(maxlen=50)
# 性能统计显示
self.stats_display_enabled = True
self.stats_text_node = None
# 初始化调试文本
self._init_debug_text()
# 根据初始状态显示或隐藏
if self.debug_enabled:
self.debug_root.show()
else:
self.debug_root.hide()
print("物理调试工具初始化完成")
def _init_debug_text(self):
"""
初始化调试文本显示
"""
# 创建调试文本节点
self.debug_text_node = TextNode("PhysicsDebugText")
self.debug_text_node.setText("")
self.debug_text_node.setTextColor(1, 1, 1, 1) # 白色
self.debug_text_node.setAlign(TextNode.ALeft)
self.debug_text_node.setFontSize(16)
# 创建文本节点路径
text_np = self.render_node.attachNewNode(self.debug_text_node)
text_np.setScale(0.05) # 调整文本大小
text_np.setPos(-1.5, 0, 0.9) # 屏幕左上角
text_np.setBin("fixed", 100) # 确保在最上层
text_np.setDepthWrite(False)
text_np.setDepthTest(False)
# 创建性能统计文本节点
self.stats_text_node = TextNode("PhysicsStatsText")
self.stats_text_node.setText("")
self.stats_text_node.setTextColor(1, 1, 1, 1) # 白色
self.stats_text_node.setAlign(TextNode.ARight)
self.stats_text_node.setFontSize(14)
# 创建统计文本节点路径
stats_np = self.render_node.attachNewNode(self.stats_text_node)
stats_np.setScale(0.04) # 调整文本大小
stats_np.setPos(1.5, 0, 0.9) # 屏幕右上角
stats_np.setBin("fixed", 100)
stats_np.setDepthWrite(False)
stats_np.setDepthTest(False)
def enable_debug(self, enabled=True):
"""
启用或禁用调试可视化
Args:
enabled (bool): 是否启用调试
"""
self.debug_enabled = enabled
if enabled:
self.debug_root.show()
else:
self.debug_root.hide()
def set_debug_options(self, show_constraints=None, show_aabbs=None,
show_contacts=None, show_normals=None, show_velocities=None,
show_forces=None, show_axes=None, show_mass_centers=None,
show_collision_shapes=None, show_sleeping_states=None,
show_particle_trails=None):
"""
设置调试选项
Args:
show_constraints (bool): 是否显示约束
show_aabbs (bool): 是否显示包围盒
show_contacts (bool): 是否显示接触点
show_normals (bool): 是否显示法线
show_velocities (bool): 是否显示速度
show_forces (bool): 是否显示力
show_axes (bool): 是否显示坐标轴
show_mass_centers (bool): 是否显示质心
show_collision_shapes (bool): 是否显示碰撞形状
show_sleeping_states (bool): 是否显示休眠状态
show_particle_trails (bool): 是否显示粒子轨迹
"""
if show_constraints is not None:
self.show_constraints = show_constraints
if show_constraints:
self.constraint_node.show()
else:
self.constraint_node.hide()
if show_aabbs is not None:
self.show_aabbs = show_aabbs
if show_aabbs:
self.aabb_node.show()
else:
self.aabb_node.hide()
if show_contacts is not None:
self.show_contacts = show_contacts
if show_contacts:
self.contact_node.show()
else:
self.contact_node.hide()
if show_normals is not None:
self.show_normals = show_normals
if show_normals:
self.normal_node.show()
else:
self.normal_node.hide()
if show_velocities is not None:
self.show_velocities = show_velocities
if show_velocities:
self.velocity_node.show()
else:
self.velocity_node.hide()
if show_forces is not None:
self.show_forces = show_forces
if show_forces:
self.force_node.show()
else:
self.force_node.hide()
if show_axes is not None:
self.show_axes = show_axes
if show_axes:
self.axes_node.show()
else:
self.axes_node.hide()
if show_mass_centers is not None:
self.show_mass_centers = show_mass_centers
if show_mass_centers:
self.mass_center_node.show()
else:
self.mass_center_node.hide()
if show_collision_shapes is not None:
self.show_collision_shapes = show_collision_shapes
if show_collision_shapes:
self.collision_shape_node.show()
else:
self.collision_shape_node.hide()
if show_sleeping_states is not None:
self.show_sleeping_states = show_sleeping_states
if show_sleeping_states:
self.sleeping_node.show()
else:
self.sleeping_node.hide()
if show_particle_trails is not None:
self.show_particle_trails = show_particle_trails
if show_particle_trails:
self.particle_trail_node.show()
else:
self.particle_trail_node.hide()
def update_debug_visualization(self):
"""
更新调试可视化
"""
if not self.debug_enabled:
return
# 控制更新频率
current_time = ClockObject.getGlobalClock().getFrameTime()
if current_time - self.last_debug_update < 1.0 / self.debug_update_frequency:
return
self.last_debug_update = current_time
self.debug_update_counter += 1
# 清除之前的可视化
self._clear_debug_visualization()
# 可视化刚体
self._visualize_rigid_bodies()
# 可视化约束
if self.show_constraints:
self._visualize_constraints()
# 可视化接触点
if self.show_contacts:
self._visualize_contacts()
# 可视化法线
if self.show_normals:
self._visualize_normals()
# 可视化速度
if self.show_velocities:
self._visualize_velocities()
# 可视化力
if self.show_forces:
self._visualize_forces()
# 可视化坐标轴
if self.show_axes:
self._visualize_axes()
# 可视化质心
if self.show_mass_centers:
self._visualize_mass_centers()
# 可视化碰撞形状
if self.show_collision_shapes:
self._visualize_collision_shapes()
# 可视化休眠状态
if self.show_sleeping_states:
self._visualize_sleeping_states()
# 可视化粒子轨迹
if self.show_particle_trails:
self._visualize_particle_trails()
# 更新调试文本
if self.debug_text_enabled:
self._update_debug_text()
# 更新性能统计
if self.stats_display_enabled:
self._update_stats_display()
def _clear_debug_visualization(self):
"""
清除调试可视化
"""
# 清除所有子节点
for child in self.constraint_node.getChildren():
child.removeNode()
for child in self.aabb_node.getChildren():
child.removeNode()
for child in self.contact_node.getChildren():
child.removeNode()
for child in self.normal_node.getChildren():
child.removeNode()
for child in self.velocity_node.getChildren():
child.removeNode()
for child in self.force_node.getChildren():
child.removeNode()
for child in self.axes_node.getChildren():
child.removeNode()
for child in self.mass_center_node.getChildren():
child.removeNode()
for child in self.collision_shape_node.getChildren():
child.removeNode()
for child in self.sleeping_node.getChildren():
child.removeNode()
for child in self.particle_trail_node.getChildren():
child.removeNode()
def _visualize_rigid_bodies(self):
"""
可视化刚体
"""
for rigid_body in self.physics_world.rigid_bodies:
# 获取刚体的AABB
aabb = rigid_body.bullet_body.getAabb()
min_point = aabb.getMin()
max_point = aabb.getMax()
# 创建包围盒可视化
if self.show_aabbs:
self._draw_aabb(min_point, max_point, rigid_body)
def _draw_aabb(self, min_point, max_point, rigid_body):
"""
绘制AABB包围盒
Args:
min_point (Point3): 最小点
max_point (Point3): 最大点
rigid_body (RigidBody): 刚体对象
"""
# 确定颜色
if rigid_body.body_type == rigid_body.STATIC:
color = self.colors['static']
elif rigid_body.body_type == rigid_body.KINEMATIC:
color = self.colors['kinematic']
else:
color = self.colors['dynamic']
# 如果显示休眠状态且刚体休眠,使用休眠颜色
if self.show_sleeping_states and not rigid_body.bullet_body.isActive():
color = self.colors['sleeping']
# 创建线条几何体
vformat = GeomVertexFormat.getV3c4()
vdata = GeomVertexData('aabb', vformat, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, 'vertex')
color_writer = GeomVertexWriter(vdata, 'color')
# 添加8个顶点
vertices = [
Point3(min_point.getX(), min_point.getY(), min_point.getZ()),
Point3(max_point.getX(), min_point.getY(), min_point.getZ()),
Point3(max_point.getX(), max_point.getY(), min_point.getZ()),
Point3(min_point.getX(), max_point.getY(), min_point.getZ()),
Point3(min_point.getX(), min_point.getY(), max_point.getZ()),
Point3(max_point.getX(), min_point.getY(), max_point.getZ()),
Point3(max_point.getX(), max_point.getY(), max_point.getZ()),
Point3(min_point.getX(), max_point.getY(), max_point.getZ())
]
for vert in vertices:
vertex.addData3(vert)
color_writer.addData4(color)
# 创建线条
geom = Geom(vdata)
lines = GeomLines(Geom.UHStatic)
# 添加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 start, end in edges:
lines.addVertex(start)
lines.addVertex(end)
lines.closePrimitive()
geom.addPrimitive(lines)
# 创建节点
node = GeomNode('aabb_geom')
node.addGeom(geom)
# 添加到场景
aabb_np = self.aabb_node.attachNewNode(node)
aabb_np.setRenderModeWireframe()
def _visualize_constraints(self):
"""
可视化约束
"""
for constraint_info in self.physics_world.constraints:
constraint = constraint_info['constraint']
body_a = constraint_info['body_a']
body_b = constraint_info['body_b']
# 获取连接点
if hasattr(constraint, 'getPivotA'):
pivot_a = constraint.getPivotA()
pivot_b = constraint.getPivotB()
# 转换到世界坐标
world_pivot_a = body_a.physics_node.getTransform().getPos() + pivot_a
world_pivot_b = body_b.physics_node.getTransform().getPos() + pivot_b
# 绘制连接线
self._draw_line(world_pivot_a, world_pivot_b, self.colors['constraint'])
def _visualize_contacts(self):
"""
可视化接触点
"""
# 注意Bullet不直接提供接触点信息需要通过碰撞回调获取
# 这里使用简化的实现,从历史记录中获取
for contact_info in list(self.contact_history):
position = contact_info['position']
normal = contact_info['normal']
# 绘制接触点
self._draw_point(position, self.colors['contact'], self.contact_point_size)
# 绘制法线
if self.show_normals:
end_point = position + normal * self.normal_scale
self._draw_line(position, end_point, self.colors['normal'])
def _visualize_normals(self):
"""
可视化法线
"""
# 从接触历史中绘制法线
for contact_info in list(self.contact_history):
position = contact_info['position']
normal = contact_info['normal']
end_point = position + normal * self.normal_scale
self._draw_line(position, end_point, self.colors['normal'])
def _visualize_velocities(self):
"""
可视化速度
"""
for rigid_body in self.physics_world.rigid_bodies:
velocity = rigid_body.bullet_body.getLinearVelocity()
if velocity.length() > 0.1: # 只显示大于阈值的速度
position = rigid_body.physics_node.getPos()
end_point = position + velocity * self.velocity_scale
self._draw_line(position, end_point, self.colors['velocity'])
# 添加箭头
self._draw_arrow_head(end_point, velocity, self.colors['velocity'])
def _visualize_forces(self):
"""
可视化力
"""
# 从刚体历史中获取力信息
for rigid_body in self.physics_world.rigid_bodies:
# 这里需要从刚体中获取力历史,简化实现
pass
def _visualize_axes(self):
"""
可视化坐标轴
"""
for rigid_body in self.physics_world.rigid_bodies:
position = rigid_body.physics_node.getPos()
rotation = rigid_body.physics_node.getHpr()
# 创建坐标系变换
from panda3d.core import TransformState
transform = TransformState.makePosHpr(position, rotation)
# X轴红色
x_end = position + transform.getQuat().getRight() * self.axis_length
self._draw_line(position, x_end, self.colors['positive_axis'])
self._draw_arrow_head(x_end, transform.getQuat().getRight(), self.colors['positive_axis'])
# Y轴绿色
y_end = position + transform.getQuat().getForward() * self.axis_length
self._draw_line(position, y_end, self.colors['negative_axis'])
self._draw_arrow_head(y_end, transform.getQuat().getForward(), self.colors['negative_axis'])
# Z轴蓝色
z_end = position + transform.getQuat().getUp() * self.axis_length
self._draw_line(position, z_end, self.colors['neutral_axis'])
self._draw_arrow_head(z_end, transform.getQuat().getUp(), self.colors['neutral_axis'])
def _visualize_mass_centers(self):
"""
可视化质心
"""
for rigid_body in self.physics_world.rigid_bodies:
# 获取质心位置
center_of_mass = rigid_body.bullet_body.getCenterOfMassTransform().getPos()
world_position = rigid_body.physics_node.getTransform().getPos() + center_of_mass
# 绘制质心点
self._draw_point(world_position, self.colors['mass_center'], 0.05)
def _visualize_collision_shapes(self):
"""
可视化碰撞形状
"""
for rigid_body in self.physics_world.rigid_bodies:
# 获取碰撞形状信息并绘制
# 这里需要更复杂的实现来准确绘制不同形状
pass
def _visualize_sleeping_states(self):
"""
可视化休眠状态
"""
# 休眠状态已经在其他可视化中体现,这里可以添加额外的标识
for rigid_body in self.physics_world.rigid_bodies:
if not rigid_body.bullet_body.isActive():
# 在休眠物体上添加特殊标记
position = rigid_body.physics_node.getPos()
self._draw_point(position + Vec3(0, 0, 0.1), self.colors['sleeping'], 0.03)
def _visualize_particle_trails(self):
"""
可视化粒子轨迹
"""
# 从粒子系统中获取轨迹信息
for particle_system in self.physics_world.particles:
# 绘制每个粒子的轨迹
# 这里需要访问粒子系统的轨迹数据
pass
def _draw_line(self, start, end, color, thickness=None):
"""
绘制线条
Args:
start (Point3): 起点
end (Point3): 终点
color (Vec4): 颜色
thickness (float): 线条粗细
"""
if thickness is None:
thickness = self.line_thickness
# 使用LineSegs绘制线条
segs = LineSegs('debug_line')
segs.setThickness(thickness)
segs.setColor(color)
segs.moveTo(start)
segs.drawTo(end)
# 创建节点
node = segs.create()
line_np = self.debug_root.attachNewNode(node)
def _draw_point(self, position, color, size=0.1):
"""
绘制点
Args:
position (Point3): 位置
color (Vec4): 颜色
size (float): 点大小
"""
# 创建一个小球体表示点
cm = CardMaker('debug_point')
cm.setFrame(-size, size, -size, size)
point_np = self.debug_root.attachNewNode(cm.generate())
point_np.setPos(position)
point_np.setColor(color)
def _draw_arrow_head(self, tip_position, direction, color, size=0.1):
"""
绘制箭头头部
Args:
tip_position (Point3): 箭头顶点位置
direction (Vec3): 方向向量
color (Vec4): 颜色
size (float): 箭头大小
"""
# 标准化方向向量
direction.normalize()
# 计算垂直向量
perpendicular = direction.cross(Vec3(0, 0, 1))
if perpendicular.length() < 0.001:
perpendicular = direction.cross(Vec3(1, 0, 0))
perpendicular.normalize()
# 计算箭头翼点
wing1 = tip_position - direction * size + perpendicular * size * 0.5
wing2 = tip_position - direction * size - perpendicular * size * 0.5
# 绘制箭头翼
self._draw_line(tip_position, wing1, color)
self._draw_line(tip_position, wing2, color)
def _update_debug_text(self):
"""
更新调试文本
"""
if not self.debug_text_node:
return
# 构建调试文本
debug_lines = []
# 添加基本统计
stats = self.physics_world.get_performance_stats()
debug_lines.append(f"Physics Debug Info:")
debug_lines.append(f" FPS: {stats.get('current_fps', 0):.1f}")
debug_lines.append(f" Update Time: {stats.get('last_update_time', 0)*1000:.2f}ms")
debug_lines.append(f" Objects: {len(self.physics_world.rigid_bodies)} rigid bodies")
debug_lines.append(f" Constraints: {len(self.physics_world.constraints)} constraints")
debug_lines.append(f" Particles: {len(self.physics_world.particles)} systems")
# 添加活动物体统计
active_count = sum(1 for body in self.physics_world.rigid_bodies if body.bullet_body.isActive())
sleeping_count = len(self.physics_world.rigid_bodies) - active_count
debug_lines.append(f" Active Bodies: {active_count}")
debug_lines.append(f" Sleeping Bodies: {sleeping_count}")
# 更新文本节点
self.debug_text = '\n'.join(debug_lines)
self.debug_text_node.setText(self.debug_text)
def _update_stats_display(self):
"""
更新性能统计显示
"""
if not self.stats_text_node:
return
# 获取性能统计
stats = self.physics_world.get_performance_stats()
# 构建统计文本
stat_lines = []
stat_lines.append("Performance Stats:")
stat_lines.append(f"Avg Update: {stats.get('avg_update_time', 0)*1000:.2f}ms")
stat_lines.append(f"Max Update: {stats.get('max_update_time', 0)*1000:.2f}ms")
stat_lines.append(f"Min Update: {stats.get('min_update_time', 0)*1000:.2f}ms")
stat_lines.append(f"Objects: {stats.get('objects', {}).get('rigid_bodies', 0)} bodies")
stat_lines.append(f"Constraints: {stats.get('objects', {}).get('constraints', 0)}")
stat_lines.append(f"Particles: {stats.get('objects', {}).get('particles', 0)}")
# 更新统计文本节点
stats_text = '\n'.join(stat_lines)
self.stats_text_node.setText(stats_text)
def add_contact_point(self, position, normal, impulse):
"""
添加接触点信息(供外部调用)
Args:
position (Point3): 接触点位置
normal (Vec3): 接触法线
impulse (float): 冲量
"""
contact_info = {
'position': position,
'normal': normal,
'impulse': impulse,
'timestamp': time.time()
}
self.contact_history.append(contact_info)
def add_collision_event(self, body_a, body_b, contact_points):
"""
添加碰撞事件(供外部调用)
Args:
body_a (RigidBody): 第一个刚体
body_b (RigidBody): 第二个刚体
contact_points (list): 接触点列表
"""
collision_info = {
'body_a': body_a,
'body_b': body_b,
'contact_points': contact_points,
'timestamp': time.time()
}
self.collision_history.append(collision_info)
def set_line_thickness(self, thickness):
"""
设置线条粗细
Args:
thickness (float): 线条粗细
"""
self.line_thickness = max(0.1, thickness)
def set_axis_length(self, length):
"""
设置坐标轴长度
Args:
length (float): 坐标轴长度
"""
self.axis_length = max(0.1, length)
def set_velocity_scale(self, scale):
"""
设置速度可视化缩放
Args:
scale (float): 速度缩放因子
"""
self.velocity_scale = max(0.001, scale)
def set_force_scale(self, scale):
"""
设置力可视化缩放
Args:
scale (float): 力缩放因子
"""
self.force_scale = max(0.0001, scale)
def set_contact_point_size(self, size):
"""
设置接触点大小
Args:
size (float): 接触点大小
"""
self.contact_point_size = max(0.01, size)
def set_normal_scale(self, scale):
"""
设置法线可视化缩放
Args:
scale (float): 法线缩放因子
"""
self.normal_scale = max(0.01, scale)
def enable_debug_text(self, enabled=True):
"""
启用或禁用调试文本显示
Args:
enabled (bool): 是否启用调试文本
"""
self.debug_text_enabled = enabled
if self.debug_text_node:
if enabled:
self.debug_text_node.show()
else:
self.debug_text_node.hide()
def enable_stats_display(self, enabled=True):
"""
启用或禁用性能统计显示
Args:
enabled (bool): 是否启用性能统计显示
"""
self.stats_display_enabled = enabled
if self.stats_text_node:
if enabled:
self.stats_text_node.show()
else:
self.stats_text_node.hide()
def set_debug_update_frequency(self, frequency):
"""
设置调试更新频率
Args:
frequency (int): 更新频率Hz
"""
self.debug_update_frequency = max(1, frequency)
def hide_debug_visualization(self):
"""
隐藏调试可视化
"""
self.debug_root.hide()
def show_debug_visualization(self):
"""
显示调试可视化
"""
if self.debug_enabled:
self.debug_root.show()
def destroy(self):
"""
销毁调试工具
"""
# 清除所有可视化
self._clear_debug_visualization()
# 移除文本节点
if self.debug_text_node and not self.debug_text_node.isEmpty():
self.debug_text_node.removeNode()
if self.stats_text_node and not self.stats_text_node.isEmpty():
self.stats_text_node.removeNode()
# 移除节点
if self.debug_root and not self.debug_root.isEmpty():
self.debug_root.removeNode()
# 清空历史记录
self.contact_history.clear()
self.collision_history.clear()
print("物理调试工具已销毁")
class PhysicsProfiler:
"""
物理性能分析器
监控和分析物理系统的性能
"""
def __init__(self, physics_world):
"""
初始化性能分析器
Args:
physics_world (PhysicsWorld): 物理世界对象
"""
self.physics_world = physics_world
# 性能数据
self.update_times = deque(maxlen=1000)
self.rigid_body_counts = deque(maxlen=1000)
self.constraint_counts = deque(maxlen=1000)
self.particle_counts = deque(maxlen=1000)
self.collision_counts = deque(maxlen=1000)
self.active_body_counts = deque(maxlen=1000)
self.sleeping_body_counts = deque(maxlen=1000)
# 分析参数
self.max_history = 1000 # 最大历史记录数
self.sampling_interval = 1.0 # 采样间隔(秒)
self.last_sample_time = 0.0
# 性能阈值
self.warning_threshold = 0.016 # 警告阈值(秒)
self.error_threshold = 0.033 # 错误阈值(秒)
# 统计计数器
self.total_updates = 0
self.total_collisions = 0
self.total_constraints_solved = 0
self.peak_performance = {
'max_update_time': 0.0,
'max_rigid_bodies': 0,
'max_constraints': 0,
'max_particles': 0,
'max_collisions': 0
}
print("物理性能分析器初始化完成")
def record_frame(self):
"""
记录帧数据
"""
current_time = time.time()
# 检查采样间隔
if current_time - self.last_sample_time < self.sampling_interval:
return
self.last_sample_time = current_time
# 获取性能统计
stats = self.physics_world.get_performance_stats()
# 记录更新时间
update_time = stats.get('last_update_time', 0.0)
self.update_times.append(update_time)
# 更新峰值性能记录
if update_time > self.peak_performance['max_update_time']:
self.peak_performance['max_update_time'] = update_time
# 记录对象数量
objects = stats.get('objects', {})
rigid_bodies = objects.get('rigid_bodies', 0)
constraints = objects.get('constraints', 0)
particles = objects.get('particles', 0)
self.rigid_body_counts.append(rigid_bodies)
self.constraint_counts.append(constraints)
self.particle_counts.append(particles)
# 更新峰值记录
if rigid_bodies > self.peak_performance['max_rigid_bodies']:
self.peak_performance['max_rigid_bodies'] = rigid_bodies
if constraints > self.peak_performance['max_constraints']:
self.peak_performance['max_constraints'] = constraints
if particles > self.peak_performance['max_particles']:
self.peak_performance['max_particles'] = particles
# 记录活动和休眠物体数量
active_count = 0
sleeping_count = 0
for body in self.physics_world.rigid_bodies:
if body.bullet_body.isActive():
active_count += 1
else:
sleeping_count += 1
self.active_body_counts.append(active_count)
self.sleeping_body_counts.append(sleeping_count)
# 更新总计数器
self.total_updates += 1
# 检查性能警告
self._check_performance_warnings(update_time)
def _check_performance_warnings(self, update_time):
"""
检查性能警告
Args:
update_time (float): 更新时间
"""
if update_time > self.error_threshold:
print(f"[ERROR] Physics update time exceeded error threshold: {update_time*1000:.2f}ms")
elif update_time > self.warning_threshold:
print(f"[WARNING] Physics update time exceeded warning threshold: {update_time*1000:.2f}ms")
def get_average_update_time(self):
"""
获取平均更新时间
Returns:
float: 平均更新时间(秒)
"""
if not self.update_times:
return 0.0
return sum(self.update_times) / len(self.update_times)
def get_max_update_time(self):
"""
获取最大更新时间
Returns:
float: 最大更新时间(秒)
"""
if not self.update_times:
return 0.0
return max(self.update_times)
def get_min_update_time(self):
"""
获取最小更新时间
Returns:
float: 最小更新时间(秒)
"""
if not self.update_times:
return 0.0
return min(self.update_times)
def get_average_fps(self):
"""
获取平均帧率
Returns:
float: 平均帧率
"""
avg_update_time = self.get_average_update_time()
if avg_update_time > 0:
return 1.0 / avg_update_time
return 0.0
def get_object_counts(self):
"""
获取对象数量统计
Returns:
dict: 对象数量统计
"""
return {
'rigid_bodies': len(self.physics_world.rigid_bodies),
'constraints': len(self.physics_world.constraints),
'vehicles': len(self.physics_world.vehicles),
'particles': sum([ps.get_particle_count()
for ps in self.physics_world.particles]),
'soft_bodies': len(self.physics_world.soft_bodies),
'ghost_objects': len(self.physics_world.ghost_objects)
}
def get_average_object_counts(self):
"""
获取平均对象数量
Returns:
dict: 平均对象数量统计
"""
if not self.rigid_body_counts:
return self.get_object_counts()
return {
'rigid_bodies': sum(self.rigid_body_counts) / len(self.rigid_body_counts),
'constraints': sum(self.constraint_counts) / len(self.constraint_counts),
'particles': sum(self.particle_counts) / len(self.particle_counts),
'active_bodies': sum(self.active_body_counts) / len(self.active_body_counts) if self.active_body_counts else 0,
'sleeping_bodies': sum(self.sleeping_body_counts) / len(self.sleeping_body_counts) if self.sleeping_body_counts else 0
}
def get_performance_report(self):
"""
获取性能报告
Returns:
dict: 性能报告
"""
return {
'average_update_time': self.get_average_update_time(),
'max_update_time': self.get_max_update_time(),
'min_update_time': self.get_min_update_time(),
'average_fps': self.get_average_fps(),
'current_fps': 1.0 / self.get_average_update_time() if self.get_average_update_time() > 0 else 0,
'object_counts': self.get_object_counts(),
'average_object_counts': self.get_average_object_counts(),
'peak_performance': self.peak_performance.copy(),
'total_updates': self.total_updates,
'total_collisions': self.total_collisions,
'history_length': len(self.update_times),
'sampling_interval': self.sampling_interval
}
def get_performance_analysis(self):
"""
获取性能分析
Returns:
dict: 性能分析结果
"""
report = self.get_performance_report()
# 计算性能等级
avg_update_time = report['average_update_time']
if avg_update_time <= 0.008: # 8ms
performance_grade = "Excellent"
elif avg_update_time <= 0.016: # 16ms
performance_grade = "Good"
elif avg_update_time <= 0.024: # 24ms
performance_grade = "Acceptable"
elif avg_update_time <= 0.033: # 33ms
performance_grade = "Poor"
else:
performance_grade = "Critical"
# 分析瓶颈
bottlenecks = []
if report['object_counts']['rigid_bodies'] > 500:
bottlenecks.append("High rigid body count")
if report['object_counts']['constraints'] > 100:
bottlenecks.append("High constraint count")
if report['object_counts']['particles'] > 1000:
bottlenecks.append("High particle count")
if report['average_object_counts']['active_bodies'] > 200:
bottlenecks.append("Too many active bodies")
return {
'performance_grade': performance_grade,
'bottlenecks': bottlenecks,
'recommendations': self._generate_recommendations(report),
'report': report
}
def _generate_recommendations(self, report):
"""
生成性能优化建议
Args:
report (dict): 性能报告
Returns:
list: 优化建议列表
"""
recommendations = []
# 基于性能等级的建议
if report['performance_grade'] == "Poor":
recommendations.append("Consider reducing physics complexity")
elif report['performance_grade'] == "Critical":
recommendations.append("URGENT: Physics performance critical - immediate optimization needed")
# 基于对象数量的建议
if report['object_counts']['rigid_bodies'] > 500:
recommendations.append("Reduce rigid body count through batching or simplification")
if report['object_counts']['constraints'] > 100:
recommendations.append("Optimize constraint usage - consider removing unnecessary constraints")
if report['object_counts']['particles'] > 1000:
recommendations.append("Reduce particle system complexity or limit particle count")
# 基于活动物体的建议
avg_active = report['average_object_counts']['active_bodies']
if avg_active > 200:
recommendations.append("Improve sleeping thresholds to reduce active bodies")
# 基于更新时间的建议
if report['average_update_time'] > 0.016:
recommendations.append("Profile physics update to identify slow operations")
recommendations.append("Consider multithreading if available")
return recommendations
def reset_statistics(self):
"""
重置统计信息
"""
self.update_times.clear()
self.rigid_body_counts.clear()
self.constraint_counts.clear()
self.particle_counts.clear()
self.collision_counts.clear()
self.active_body_counts.clear()
self.sleeping_body_counts.clear()
self.total_updates = 0
self.total_collisions = 0
self.total_constraints_solved = 0
# 重置峰值记录(保留一些)
self.peak_performance['max_update_time'] = 0.0
self.peak_performance['max_collisions'] = 0
print("性能统计已重置")
def export_statistics(self, filepath):
"""
导出统计信息到文件
Args:
filepath (str): 导出文件路径
"""
import json
# 构建统计数据
stats_data = {
'performance_report': self.get_performance_report(),
'performance_analysis': self.get_performance_analysis(),
'raw_data': {
'update_times': list(self.update_times),
'rigid_body_counts': list(self.rigid_body_counts),
'constraint_counts': list(self.constraint_counts),
'particle_counts': list(self.particle_counts),
'collision_counts': list(self.collision_counts),
'active_body_counts': list(self.active_body_counts),
'sleeping_body_counts': list(self.sleeping_body_counts)
},
'export_timestamp': time.time(),
'export_date': time.strftime('%Y-%m-%d %H:%M:%S')
}
# 导出到JSON文件
try:
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(stats_data, f, indent=2, ensure_ascii=False)
print(f"性能统计已导出到: {filepath}")
except Exception as e:
print(f"导出性能统计失败: {e}")
def destroy(self):
"""
销毁性能分析器
"""
self.reset_statistics()
print("物理性能分析器已销毁")