""" 流体障碍物组件 定义流体模拟中的静态或动态障碍物,支持多种形状和复杂的碰撞检测 """ from panda3d.core import Vec3, Point3 import math class FluidObstacle: """ 流体障碍物类 定义流体模拟中的静态或动态障碍物,支持复杂的碰撞检测和响应 """ # 障碍物类型 TYPE_BOX = "box" TYPE_SPHERE = "sphere" TYPE_CYLINDER = "cylinder" TYPE_CONE = "cone" TYPE_MESH = "mesh" # 三角网格 def __init__(self, position, size, obstacle_type=TYPE_BOX, mesh_data=None): """ 初始化流体障碍物 Args: position (Point3): 障碍物位置 size (Vec3): 障碍物尺寸 obstacle_type (str): 障碍物类型 mesh_data (dict): 网格数据(仅用于网格类型障碍物) """ self.position = position self.size = size self.obstacle_type = obstacle_type self.mesh_data = mesh_data # 用于网格类型障碍物 # 障碍物属性 self.is_dynamic = False # 是否为动态障碍物 self.velocity = Vec3(0, 0, 0) # 动态障碍物速度 self.angular_velocity = Vec3(0, 0, 0) # 角速度 self.rotation = Vec3(0, 0, 0) # 旋转角度 # 物理属性 self.friction = 0.5 # 摩擦系数 self.restitution = 0.3 # 恢复系数 self.density = 1000.0 # 密度 self.mass = 1.0 # 质量 # 碰撞属性 self.collision_margin = 0.05 # 碰撞边缘 self.is_trigger = False # 是否为触发器(不产生物理反应) # 阻力系数 self.drag_coefficient = 0.47 # 阻力系数(球体) print(f"流体障碍物创建完成: {position}, 类型: {obstacle_type}") def update(self, dt): """ 更新障碍物状态 Args: dt (float): 时间步长 """ if self.is_dynamic: # 更新动态障碍物位置 self.position += self.velocity * dt # 更新旋转 self.rotation += self.angular_velocity * dt # 应用重力(如果需要) # self.velocity += Vec3(0, 0, -9.81) * dt # 应用阻尼 self.velocity *= 0.99 self.angular_velocity *= 0.99 def handle_fluid_interaction(self, particle, dt): """ 处理与流体粒子的交互 Args: particle: 流体粒子对象 dt (float): 时间步长 """ # 如果是触发器,只检测不产生物理反应 if self.is_trigger: return self._check_collision(particle) # 检查粒子是否与障碍物相交 collision_info = self._check_collision_detailed(particle) if collision_info['is_colliding']: # 计算碰撞响应 self._resolve_collision_advanced(particle, collision_info) # 应用摩擦力 particle['velocity'] *= (1.0 - self.friction * dt) # 应用阻力 self._apply_drag_force(particle, dt) return collision_info['is_colliding'] def _check_collision_detailed(self, particle): """ 详细的碰撞检测,返回碰撞信息 Args: particle: 流体粒子对象 Returns: dict: 碰撞信息 """ collision_info = { 'is_colliding': False, 'normal': Vec3(0, 0, 0), 'penetration_depth': 0.0, 'contact_point': Point3(0, 0, 0) } if self.obstacle_type == self.TYPE_BOX: return self._check_box_collision(particle) elif self.obstacle_type == self.TYPE_SPHERE: return self._check_sphere_collision(particle) elif self.obstacle_type == self.TYPE_CYLINDER: return self._check_cylinder_collision(particle) elif self.obstacle_type == self.TYPE_CONE: return self._check_cone_collision(particle) elif self.obstacle_type == self.TYPE_MESH and self.mesh_data: return self._check_mesh_collision(particle) return collision_info def _check_box_collision(self, particle): """ 盒状碰撞检测 Args: particle: 流体粒子对象 Returns: dict: 碰撞信息 """ collision_info = { 'is_colliding': False, 'normal': Vec3(0, 0, 0), 'penetration_depth': 0.0, 'contact_point': Point3(0, 0, 0) } pos = particle['position'] half_size = self.size * 0.5 min_bound = self.position - half_size max_bound = self.position + half_size # 检查是否在盒子内 if (min_bound.getX() - self.collision_margin <= pos.getX() <= max_bound.getX() + self.collision_margin and min_bound.getY() - self.collision_margin <= pos.getY() <= max_bound.getY() + self.collision_margin and min_bound.getZ() - self.collision_margin <= pos.getZ() <= max_bound.getZ() + self.collision_margin): collision_info['is_colliding'] = True # 计算法向量和穿透深度 # 找到最近的面 dx_min = abs(pos.getX() - min_bound.getX()) dx_max = abs(pos.getX() - max_bound.getX()) dy_min = abs(pos.getY() - min_bound.getY()) dy_max = abs(pos.getY() - max_bound.getY()) dz_min = abs(pos.getZ() - min_bound.getZ()) dz_max = abs(pos.getZ() - max_bound.getZ()) min_dist = min(dx_min, dx_max, dy_min, dy_max, dz_min, dz_max) if min_dist == dx_min: collision_info['normal'] = Vec3(-1, 0, 0) collision_info['penetration_depth'] = dx_min collision_info['contact_point'] = Point3(min_bound.getX(), pos.getY(), pos.getZ()) elif min_dist == dx_max: collision_info['normal'] = Vec3(1, 0, 0) collision_info['penetration_depth'] = dx_max collision_info['contact_point'] = Point3(max_bound.getX(), pos.getY(), pos.getZ()) elif min_dist == dy_min: collision_info['normal'] = Vec3(0, -1, 0) collision_info['penetration_depth'] = dy_min collision_info['contact_point'] = Point3(pos.getX(), min_bound.getY(), pos.getZ()) elif min_dist == dy_max: collision_info['normal'] = Vec3(0, 1, 0) collision_info['penetration_depth'] = dy_max collision_info['contact_point'] = Point3(pos.getX(), max_bound.getY(), pos.getZ()) elif min_dist == dz_min: collision_info['normal'] = Vec3(0, 0, -1) collision_info['penetration_depth'] = dz_min collision_info['contact_point'] = Point3(pos.getX(), pos.getY(), min_bound.getZ()) else: # dz_max collision_info['normal'] = Vec3(0, 0, 1) collision_info['penetration_depth'] = dz_max collision_info['contact_point'] = Point3(pos.getX(), pos.getY(), max_bound.getZ()) return collision_info def _check_sphere_collision(self, particle): """ 球状碰撞检测 Args: particle: 流体粒子对象 Returns: dict: 碰撞信息 """ collision_info = { 'is_colliding': False, 'normal': Vec3(0, 0, 0), 'penetration_depth': 0.0, 'contact_point': Point3(0, 0, 0) } pos = particle['position'] radius = self.size.getX() * 0.5 center = self.position # 计算粒子到球心的距离 distance_vec = pos - center distance = distance_vec.length() # 检查是否碰撞 if distance <= radius + self.collision_margin: collision_info['is_colliding'] = True # 计算法向量 if distance > 0: collision_info['normal'] = distance_vec.normalized() else: # 如果粒子在球心,使用默认法向量 collision_info['normal'] = Vec3(0, 0, 1) # 计算穿透深度 collision_info['penetration_depth'] = radius + self.collision_margin - distance # 计算接触点 collision_info['contact_point'] = center + collision_info['normal'] * radius return collision_info def _check_cylinder_collision(self, particle): """ 圆柱碰撞检测 Args: particle: 流体粒子对象 Returns: dict: 碰撞信息 """ collision_info = { 'is_colliding': False, 'normal': Vec3(0, 0, 0), 'penetration_depth': 0.0, 'contact_point': Point3(0, 0, 0) } pos = particle['position'] radius = self.size.getX() * 0.5 height = self.size.getZ() center = self.position # 计算在XY平面上到圆柱轴的距离 dx = pos.getX() - center.getX() dy = pos.getY() - center.getY() distance_xy = math.sqrt(dx*dx + dy*dy) # 检查Z轴范围 min_z = center.getZ() - height/2.0 max_z = center.getZ() + height/2.0 in_z_range = min_z - self.collision_margin <= pos.getZ() <= max_z + self.collision_margin # 检查是否在圆柱范围内 if distance_xy <= radius + self.collision_margin and in_z_range: collision_info['is_colliding'] = True # 计算法向量 if distance_xy > 0: # 径向法向量 radial_normal = Vec3(dx, dy, 0).normalized() # 检查是径向碰撞还是轴向碰撞 if pos.getZ() < min_z or pos.getZ() > max_z: # 轴向碰撞 if pos.getZ() < min_z: collision_info['normal'] = Vec3(0, 0, -1) collision_info['contact_point'] = Point3(pos.getX(), pos.getY(), min_z) else: collision_info['normal'] = Vec3(0, 0, 1) collision_info['contact_point'] = Point3(pos.getX(), pos.getY(), max_z) collision_info['penetration_depth'] = min(abs(pos.getZ() - min_z), abs(pos.getZ() - max_z)) else: # 径向碰撞 collision_info['normal'] = radial_normal collision_info['contact_point'] = Point3( center.getX() + radial_normal.getX() * radius, center.getY() + radial_normal.getY() * radius, pos.getZ() ) collision_info['penetration_depth'] = radius + self.collision_margin - distance_xy else: # 粒子在圆柱轴上,使用默认法向量 collision_info['normal'] = Vec3(1, 0, 0) collision_info['contact_point'] = Point3(center.getX() + radius, center.getY(), pos.getZ()) collision_info['penetration_depth'] = radius + self.collision_margin return collision_info def _check_cone_collision(self, particle): """ 圆锥碰撞检测 Args: particle: 流体粒子对象 Returns: dict: 碰撞信息 """ collision_info = { 'is_colliding': False, 'normal': Vec3(0, 0, 0), 'penetration_depth': 0.0, 'contact_point': Point3(0, 0, 0) } pos = particle['position'] radius = self.size.getX() * 0.5 height = self.size.getZ() center = self.position # 相对于圆锥底部的位置 rel_pos = pos - center rel_pos.setZ(rel_pos.getZ() + height/2.0) # 调整坐标系使底部在z=0 # 计算在XY平面上到圆锥轴的距离 distance_xy = math.sqrt(rel_pos.getX()**2 + rel_pos.getY()**2) # 检查是否在圆锥高度范围内 if 0 <= rel_pos.getZ() <= height + self.collision_margin: # 计算圆锥在当前高度的半径 cone_radius_at_height = (rel_pos.getZ() / height) * radius # 检查是否在圆锥内部 if distance_xy <= cone_radius_at_height + self.collision_margin: collision_info['is_colliding'] = True # 计算法向量和接触点 if rel_pos.getZ() <= self.collision_margin: # 底面碰撞 collision_info['normal'] = Vec3(0, 0, -1) collision_info['contact_point'] = Point3(pos.getX(), pos.getY(), center.getZ() - height/2.0) collision_info['penetration_depth'] = self.collision_margin - rel_pos.getZ() elif distance_xy <= self.collision_margin: # 中心轴附近,特殊处理 collision_info['normal'] = Vec3(0, 0, 1) collision_info['contact_point'] = Point3(center.getX(), center.getY(), pos.getZ()) collision_info['penetration_depth'] = self.collision_margin else: # 侧面碰撞 # 计算圆锥表面的法向量 radial_dir = Vec3(rel_pos.getX(), rel_pos.getY(), 0).normalized() surface_normal = Vec3( radial_dir.getX(), radial_dir.getY(), -radius / height # 圆锥的斜率 ).normalized() collision_info['normal'] = surface_normal # 计算接触点 contact_radius = cone_radius_at_height collision_info['contact_point'] = Point3( center.getX() + radial_dir.getX() * contact_radius, center.getY() + radial_dir.getY() * contact_radius, pos.getZ() ) collision_info['penetration_depth'] = cone_radius_at_height + self.collision_margin - distance_xy return collision_info def _check_mesh_collision(self, particle): """ 网格碰撞检测(简化实现) Args: particle: 流体粒子对象 Returns: dict: 碰撞信息 """ collision_info = { 'is_colliding': False, 'normal': Vec3(0, 0, 0), 'penetration_depth': 0.0, 'contact_point': Point3(0, 0, 0) } # 对于网格碰撞检测,需要使用更复杂的算法 # 这里使用简化的包围盒检测 if self.mesh_data and 'vertices' in self.mesh_data: vertices = self.mesh_data['vertices'] if vertices: # 计算网格的包围盒 min_bound = Point3(float('inf'), float('inf'), float('inf')) max_bound = Point3(float('-inf'), float('-inf'), float('-inf')) for vertex in vertices: min_bound.setX(min(min_bound.getX(), vertex.getX())) min_bound.setY(min(min_bound.getY(), vertex.getY())) min_bound.setZ(min(min_bound.getZ(), vertex.getZ())) max_bound.setX(max(max_bound.getX(), vertex.getX())) max_bound.setY(max(max_bound.getY(), vertex.getY())) max_bound.setZ(max(max_bound.getZ(), vertex.getZ())) # 应用位置偏移 min_bound += self.position max_bound += self.position pos = particle['position'] # 简单的包围盒检测 if (min_bound.getX() - self.collision_margin <= pos.getX() <= max_bound.getX() + self.collision_margin and min_bound.getY() - self.collision_margin <= pos.getY() <= max_bound.getY() + self.collision_margin and min_bound.getZ() - self.collision_margin <= pos.getZ() <= max_bound.getZ() + self.collision_margin): collision_info['is_colliding'] = True # 简化的法向量计算 collision_info['normal'] = Vec3(0, 0, 1) collision_info['contact_point'] = pos collision_info['penetration_depth'] = 0.1 return collision_info def _resolve_collision_advanced(self, particle, collision_info): """ 高级碰撞响应 Args: particle: 流体粒子对象 collision_info (dict): 碰撞信息 """ if not collision_info['is_colliding']: return # 获取碰撞信息 normal = collision_info['normal'] penetration_depth = collision_info['penetration_depth'] contact_point = collision_info['contact_point'] # 调整粒子位置以解决穿透 particle['position'] += normal * penetration_depth # 计算碰撞响应 velocity = particle['velocity'] velocity_normal = normal * velocity.dot(normal) velocity_tangent = velocity - velocity_normal # 应用恢复系数 new_velocity_normal = -velocity_normal * self.restitution new_velocity_tangent = velocity_tangent * (1.0 - self.friction) # 更新粒子速度 particle['velocity'] = new_velocity_normal + new_velocity_tangent # 如果是动态障碍物,也要更新障碍物的速度 if self.is_dynamic: # 简化的动量交换 impulse = (velocity_normal - new_velocity_normal) * particle.get('mass', 1.0) self.velocity -= impulse / self.mass def _apply_drag_force(self, particle, dt): """ 应用阻力 Args: particle: 流体粒子对象 dt (float): 时间步长 """ # 计算相对速度 relative_velocity = particle['velocity'] - self.velocity speed = relative_velocity.length() if speed > 0: # 计算阻力大小 # F = 0.5 * ρ * v² * Cd * A fluid_density = 1000.0 # 水的密度 cross_sectional_area = 0.01 # 简化的横截面积 drag_force_magnitude = 0.5 * fluid_density * speed * speed * self.drag_coefficient * cross_sectional_area # 计算阻力方向(与速度方向相反) drag_direction = -relative_velocity.normalized() # 应用阻力到粒子 drag_force = drag_direction * drag_force_magnitude particle['velocity'] += drag_force * dt / particle.get('mass', 1.0) def set_dynamic(self, is_dynamic, velocity=None, angular_velocity=None): """ 设置障碍物是否为动态 Args: is_dynamic (bool): 是否为动态 velocity (Vec3): 线速度 angular_velocity (Vec3): 角速度 """ self.is_dynamic = is_dynamic if velocity: self.velocity = velocity if angular_velocity: self.angular_velocity = angular_velocity def set_physics_properties(self, friction=None, restitution=None, density=None, mass=None): """ 设置物理属性 Args: friction (float): 摩擦系数 restitution (float): 恢复系数 density (float): 密度 mass (float): 质量 """ if friction is not None: self.friction = max(0.0, min(1.0, friction)) if restitution is not None: self.restitution = max(0.0, min(1.0, restitution)) if density is not None: self.density = density if mass is not None: self.mass = mass def set_collision_properties(self, margin=None, is_trigger=None): """ 设置碰撞属性 Args: margin (float): 碰撞边缘 is_trigger (bool): 是否为触发器 """ if margin is not None: self.collision_margin = max(0.0, margin) if is_trigger is not None: self.is_trigger = is_trigger def apply_force(self, force, dt): """ 对动态障碍物施加力 Args: force (Vec3): 力向量 dt (float): 时间步长 """ if self.is_dynamic: # F = ma => a = F/m acceleration = force / self.mass self.velocity += acceleration * dt def apply_torque(self, torque, dt): """ 对动态障碍物施加扭矩 Args: torque (Vec3): 扭矩向量 dt (float): 时间步长 """ if self.is_dynamic: # 简化的扭矩应用 self.angular_velocity += torque * dt / self.mass def get_bounding_box(self): """ 获取障碍物的包围盒 Returns: tuple: (min_bound, max_bound) """ if self.obstacle_type == self.TYPE_BOX: half_size = self.size * 0.5 min_bound = self.position - half_size max_bound = self.position + half_size return (min_bound, max_bound) elif self.obstacle_type == self.TYPE_SPHERE: radius = self.size.getX() * 0.5 min_bound = self.position - Vec3(radius, radius, radius) max_bound = self.position + Vec3(radius, radius, radius) return (min_bound, max_bound) elif self.obstacle_type == self.TYPE_CYLINDER: radius = self.size.getX() * 0.5 height = self.size.getZ() * 0.5 min_bound = self.position - Vec3(radius, radius, height) max_bound = self.position + Vec3(radius, radius, height) return (min_bound, max_bound) elif self.obstacle_type == self.TYPE_CONE: radius = self.size.getX() * 0.5 height = self.size.getZ() * 0.5 min_bound = self.position - Vec3(radius, radius, height) max_bound = self.position + Vec3(radius, radius, height) return (min_bound, max_bound) else: # 默认包围盒 half_size = self.size * 0.5 min_bound = self.position - half_size max_bound = self.position + half_size return (min_bound, max_bound) def get_volume(self): """ 计算障碍物体积 Returns: float: 体积 """ if self.obstacle_type == self.TYPE_BOX: return self.size.getX() * self.size.getY() * self.size.getZ() elif self.obstacle_type == self.TYPE_SPHERE: radius = self.size.getX() * 0.5 return (4.0/3.0) * math.pi * (radius ** 3) elif self.obstacle_type == self.TYPE_CYLINDER: radius = self.size.getX() * 0.5 height = self.size.getZ() return math.pi * (radius ** 2) * height elif self.obstacle_type == self.TYPE_CONE: radius = self.size.getX() * 0.5 height = self.size.getZ() return (1.0/3.0) * math.pi * (radius ** 2) * height else: # 默认按盒状计算 return self.size.getX() * self.size.getY() * self.size.getZ()