""" 粒子工具函数 提供粒子系统相关的实用工具函数 """ import math import random from typing import List, Tuple, Optional from panda3d.core import Vec3, Point3, LVector3f, Quat class ParticleUtils: """ 粒子系统工具类 提供各种实用的粒子计算和生成函数 """ @staticmethod def lerp(a: float, b: float, t: float) -> float: """ 线性插值 Args: a: 起始值 b: 结束值 t: 插值参数 (0-1) Returns: 插值结果 """ return a + (b - a) * t @staticmethod def lerp_vec3(a: Vec3, b: Vec3, t: float) -> Vec3: """ Vec3线性插值 Args: a: 起始向量 b: 结束向量 t: 插值参数 (0-1) Returns: 插值结果向量 """ return Vec3( ParticleUtils.lerp(a.x, b.x, t), ParticleUtils.lerp(a.y, b.y, t), ParticleUtils.lerp(a.z, b.z, t) ) @staticmethod def smooth_step(t: float) -> float: """ 平滑步进函数 Args: t: 输入值 (0-1) Returns: 平滑后的值 """ t = max(0.0, min(1.0, t)) return t * t * (3.0 - 2.0 * t) @staticmethod def ease_in_out(t: float) -> float: """ 缓入缓出函数 Args: t: 输入值 (0-1) Returns: 缓动后的值 """ if t < 0.5: return 2.0 * t * t else: return -1.0 + (4.0 - 2.0 * t) * t @staticmethod def random_in_sphere(radius: float = 1.0) -> Vec3: """ 在球体内生成随机点 Args: radius: 球体半径 Returns: 随机点坐标 """ # 使用拒绝采样法 while True: x = random.uniform(-1, 1) y = random.uniform(-1, 1) z = random.uniform(-1, 1) if x*x + y*y + z*z <= 1.0: return Vec3(x, y, z) * radius @staticmethod def random_on_sphere(radius: float = 1.0) -> Vec3: """ 在球面上生成随机点 Args: radius: 球体半径 Returns: 随机点坐标 """ # 使用正态分布方法 x = random.gauss(0, 1) y = random.gauss(0, 1) z = random.gauss(0, 1) length = math.sqrt(x*x + y*y + z*z) if length > 0: return Vec3(x/length, y/length, z/length) * radius else: return Vec3(0, 0, radius) @staticmethod def random_in_circle(radius: float = 1.0) -> Vec3: """ 在圆形内生成随机点 (Z=0) Args: radius: 圆形半径 Returns: 随机点坐标 """ angle = random.uniform(0, 2 * math.pi) r = random.uniform(0, radius) return Vec3(r * math.cos(angle), r * math.sin(angle), 0) @staticmethod def random_on_circle(radius: float = 1.0) -> Vec3: """ 在圆周上生成随机点 (Z=0) Args: radius: 圆形半径 Returns: 随机点坐标 """ angle = random.uniform(0, 2 * math.pi) return Vec3(radius * math.cos(angle), radius * math.sin(angle), 0) @staticmethod def random_in_box(dimensions: Vec3) -> Vec3: """ 在盒子内生成随机点 Args: dimensions: 盒子尺寸 Returns: 随机点坐标 """ return Vec3( random.uniform(-dimensions.x/2, dimensions.x/2), random.uniform(-dimensions.y/2, dimensions.y/2), random.uniform(-dimensions.z/2, dimensions.z/2) ) @staticmethod def random_cone_direction(direction: Vec3, cone_angle: float) -> Vec3: """ 在锥形范围内生成随机方向 Args: direction: 锥形中心方向 cone_angle: 锥角 (弧度) Returns: 随机方向向量 """ # 生成锥形内的随机方向 phi = random.uniform(0, 2 * math.pi) cos_theta = random.uniform(math.cos(cone_angle), 1.0) sin_theta = math.sqrt(1.0 - cos_theta * cos_theta) # 局部坐标系中的方向 local_dir = Vec3( sin_theta * math.cos(phi), sin_theta * math.sin(phi), cos_theta ) # 转换到世界坐标系 # 简化实现:假设direction是(0,0,1) if abs(direction.z - 1.0) < 0.001: return local_dir elif abs(direction.z + 1.0) < 0.001: return Vec3(local_dir.x, -local_dir.y, -local_dir.z) else: # 需要完整的旋转变换 # 这里使用简化版本 return local_dir @staticmethod def calculate_billboard_rotation(position: Point3, camera_pos: Point3) -> Quat: """ 计算广告牌旋转 Args: position: 广告牌位置 camera_pos: 摄像机位置 Returns: 旋转四元数 """ to_camera = camera_pos - position to_camera.normalize() # 计算旋转四元数 up = Vec3(0, 0, 1) right = to_camera.cross(up) if right.lengthSquared() < 0.001: # 摄像机在正上方或正下方 right = Vec3(1, 0, 0) right.normalize() up = right.cross(to_camera) up.normalize() # 构建旋转矩阵并转换为四元数 # 简化实现 return Quat() @staticmethod def apply_noise(value: float, noise_strength: float, time: float = 0.0) -> float: """ 应用噪声到数值 Args: value: 原始值 noise_strength: 噪声强度 time: 时间参数 Returns: 添加噪声后的值 """ # 简单的伪随机噪声 noise = math.sin(value * 12.9898 + time * 78.233) * 43758.5453 noise = noise - math.floor(noise) # 取小数部分 noise = (noise - 0.5) * 2.0 # 转换到 -1 到 1 return value + noise * noise_strength @staticmethod def calculate_drag_force(velocity: Vec3, drag_coefficient: float, air_density: float = 1.225) -> Vec3: """ 计算阻力 Args: velocity: 速度向量 drag_coefficient: 阻力系数 air_density: 空气密度 Returns: 阻力向量 """ speed_squared = velocity.lengthSquared() if speed_squared < 0.001: return Vec3(0, 0, 0) speed = math.sqrt(speed_squared) drag_magnitude = 0.5 * air_density * drag_coefficient * speed_squared # 阻力方向与速度相反 drag_direction = -velocity / speed return drag_direction * drag_magnitude @staticmethod def calculate_buoyancy_force(volume: float, fluid_density: float = 1000.0, gravity: float = 9.81) -> float: """ 计算浮力 Args: volume: 物体体积 fluid_density: 流体密度 gravity: 重力加速度 Returns: 浮力大小 """ return volume * fluid_density * gravity @staticmethod def distance_squared(a: Point3, b: Point3) -> float: """ 计算两点间距离的平方 Args: a: 点A b: 点B Returns: 距离的平方 """ dx = a.x - b.x dy = a.y - b.y dz = a.z - b.z return dx*dx + dy*dy + dz*dz @staticmethod def clamp(value: float, min_val: float, max_val: float) -> float: """ 限制数值范围 Args: value: 输入值 min_val: 最小值 max_val: 最大值 Returns: 限制后的值 """ return max(min_val, min(max_val, value)) @staticmethod def wrap_angle(angle: float) -> float: """ 将角度限制在 -π 到 π 范围内 Args: angle: 输入角度 (弧度) Returns: 限制后的角度 """ while angle > math.pi: angle -= 2 * math.pi while angle < -math.pi: angle += 2 * math.pi return angle @staticmethod def create_color_gradient(colors: List[Vec3], positions: List[float], t: float) -> Vec3: """ 创建颜色渐变 Args: colors: 颜色列表 positions: 位置列表 (0-1) t: 插值参数 (0-1) Returns: 插值后的颜色 """ if not colors or not positions or len(colors) != len(positions): return Vec3(1, 1, 1) if len(colors) == 1: return colors[0] # 找到插值区间 for i in range(len(positions) - 1): if t <= positions[i + 1]: # 在区间 [i, i+1] 内插值 local_t = (t - positions[i]) / (positions[i + 1] - positions[i]) return ParticleUtils.lerp_vec3(colors[i], colors[i + 1], local_t) # 超出范围,返回最后一个颜色 return colors[-1] @staticmethod def create_size_curve(sizes: List[float], positions: List[float], t: float) -> float: """ 创建尺寸曲线 Args: sizes: 尺寸列表 positions: 位置列表 (0-1) t: 插值参数 (0-1) Returns: 插值后的尺寸 """ if not sizes or not positions or len(sizes) != len(positions): return 1.0 if len(sizes) == 1: return sizes[0] # 找到插值区间 for i in range(len(positions) - 1): if t <= positions[i + 1]: # 在区间 [i, i+1] 内插值 local_t = (t - positions[i]) / (positions[i + 1] - positions[i]) return ParticleUtils.lerp(sizes[i], sizes[i + 1], local_t) # 超出范围,返回最后一个尺寸 return sizes[-1] @staticmethod def generate_spiral_positions(count: int, radius: float, height: float, turns: float = 2.0) -> List[Point3]: """ 生成螺旋形位置 Args: count: 点的数量 radius: 螺旋半径 height: 螺旋高度 turns: 螺旋圈数 Returns: 位置列表 """ positions = [] for i in range(count): t = i / max(1, count - 1) angle = t * turns * 2 * math.pi z = t * height x = radius * math.cos(angle) y = radius * math.sin(angle) positions.append(Point3(x, y, z)) return positions @staticmethod def calculate_particle_bounds(positions: List[Point3]) -> Tuple[Point3, Point3]: """ 计算粒子边界框 Args: positions: 粒子位置列表 Returns: (最小点, 最大点) """ if not positions: return Point3(0, 0, 0), Point3(0, 0, 0) min_pos = Point3(positions[0]) max_pos = Point3(positions[0]) for pos in positions[1:]: min_pos.x = min(min_pos.x, pos.x) min_pos.y = min(min_pos.y, pos.y) min_pos.z = min(min_pos.z, pos.z) max_pos.x = max(max_pos.x, pos.x) max_pos.y = max(max_pos.y, pos.y) max_pos.z = max(max_pos.z, pos.z) return min_pos, max_pos