"""
流体障碍物组件
定义流体模拟中的静态或动态障碍物，支持多种形状和复杂的碰撞检测
"""

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()