1158 lines
42 KiB
Python
1158 lines
42 KiB
Python
"""
|
|
粒子系统模块
|
|
负责物理粒子的创建和管理
|
|
"""
|
|
|
|
from panda3d.core import Vec3, Point3, LVector3f, ClockObject
|
|
from panda3d.bullet import BulletRigidBodyNode, BulletSphereShape, BulletBoxShape
|
|
import random
|
|
import math
|
|
import time
|
|
from collections import deque
|
|
|
|
|
|
class ParticleSystem:
|
|
"""
|
|
粒子系统类
|
|
管理大量物理粒子的创建、更新和销毁
|
|
"""
|
|
|
|
# 粒子类型常量
|
|
TYPE_DEFAULT = 'default'
|
|
TYPE_SMOKE = 'smoke'
|
|
TYPE_FIRE = 'fire'
|
|
TYPE_WATER = 'water'
|
|
TYPE_DEBRIS = 'debris'
|
|
TYPE_EXPLOSION = 'explosion'
|
|
TYPE_SPARK = 'spark'
|
|
TYPE_BUBBLE = 'bubble'
|
|
TYPE_LEAF = 'leaf'
|
|
TYPE_SNOW = 'snow'
|
|
TYPE_RAIN = 'rain'
|
|
|
|
def __init__(self, physics_world, max_particles=1000, particle_type=TYPE_DEFAULT):
|
|
"""
|
|
初始化粒子系统
|
|
|
|
Args:
|
|
physics_world (PhysicsWorld): 物理世界对象
|
|
max_particles (int): 最大粒子数
|
|
particle_type (str): 粒子类型
|
|
"""
|
|
self.physics_world = physics_world
|
|
self.max_particles = max_particles
|
|
self.particle_type = particle_type
|
|
|
|
# 粒子容器
|
|
self.particles = []
|
|
self.active_particles = []
|
|
self.inactive_particles = deque() # 对象池
|
|
|
|
# 粒子发射器参数
|
|
self.emission_rate = 100 # 每秒发射粒子数
|
|
self.emission_accumulator = 0.0
|
|
self.emission_burst_count = 0 # 爆发粒子数
|
|
self.emission_burst_timer = 0.0
|
|
self.emission_burst_interval = 0.0 # 爆发间隔
|
|
|
|
# 粒子默认属性
|
|
self.particle_mass = 0.1
|
|
self.particle_radius = 0.1
|
|
self.particle_size = Vec3(0.1, 0.1, 0.1) # 非球形粒子尺寸
|
|
self.particle_lifetime = 5.0
|
|
self.particle_color = Vec3(1, 1, 1)
|
|
self.particle_shape_type = 'sphere' # sphere, box, capsule
|
|
|
|
# 发射器属性
|
|
self.emitter_position = Point3(0, 0, 0)
|
|
self.emitter_velocity = Vec3(0, 0, 0)
|
|
self.emitter_spread = 0.1
|
|
self.emitter_radius = 0.0
|
|
self.emitter_shape = 'point' # point, circle, box, sphere
|
|
self.emitter_dimensions = Vec3(1, 1, 1) # 发射器尺寸
|
|
|
|
# 物理属性
|
|
self.gravity_scale = 1.0
|
|
self.air_resistance = 0.01
|
|
self.bounce_factor = 0.5
|
|
self.friction = 0.1
|
|
self.restitution = 0.3
|
|
self.linear_damping = 0.01
|
|
self.angular_damping = 0.01
|
|
|
|
# 碰撞属性
|
|
self.collision_enabled = True
|
|
self.collision_group = 1
|
|
self.collision_mask = 0xFFFFFFFF
|
|
self.collision_response = True # 是否响应碰撞
|
|
|
|
# 高级粒子属性
|
|
self.particle_trail_enabled = False
|
|
self.particle_trail_length = 10
|
|
self.particle_fade_enabled = True
|
|
self.particle_size_variation = 0.2
|
|
self.particle_velocity_variation = 0.3
|
|
self.particle_rotation_enabled = False
|
|
self.particle_rotation_speed = 0.0
|
|
self.particle_rotation_variation = 0.5
|
|
|
|
# 环境影响
|
|
self.wind_enabled = False
|
|
self.wind_vector = Vec3(0, 0, 0)
|
|
self.wind_turbulence = 0.0
|
|
self.temperature_effect = 0.0 # 温度影响(浮力等)
|
|
|
|
# 特殊效果
|
|
self.vortex_enabled = False
|
|
self.vortex_center = Point3(0, 0, 0)
|
|
self.vortex_strength = 0.0
|
|
self.vortex_axis = Vec3(0, 0, 1)
|
|
|
|
# 粒子生命周期行为
|
|
self.size_over_lifetime = None # [(time, scale), ...]
|
|
self.color_over_lifetime = None # [(time, color), ...]
|
|
self.force_over_lifetime = None # [(time, force), ...]
|
|
|
|
# 粒子子发射器
|
|
self.sub_emitter_enabled = False
|
|
self.sub_emitter_rate = 0.0
|
|
self.sub_emitter_count = 1
|
|
self.sub_emitter_lifetime = 1.0
|
|
|
|
# 性能优化
|
|
self.sleeping_threshold = 0.1 # 速度低于此值时休眠
|
|
self.culling_distance = 100.0 # 距离摄像机超过此距离时剔除
|
|
self.batch_processing = True # 批量处理
|
|
|
|
# 统计信息
|
|
self.emitted_count = 0
|
|
self.recycled_count = 0
|
|
self.collision_count = 0
|
|
self.max_active_particles = 0
|
|
|
|
# 时间控制
|
|
self.time_scale = 1.0
|
|
self.paused = False
|
|
|
|
# 粒子材质
|
|
self.particle_material = None
|
|
|
|
# 粒子纹理(如果需要渲染)
|
|
self.particle_texture = None
|
|
self.particle_blend_mode = 'alpha' # alpha, additive, multiply
|
|
|
|
# 粒子死亡回调
|
|
self.death_callbacks = []
|
|
|
|
# 粒子碰撞回调
|
|
self.collision_callbacks = []
|
|
|
|
print(f"粒子系统初始化完成,最大粒子数: {max_particles},类型: {particle_type}")
|
|
|
|
def set_emitter_properties(self, position=None, velocity=None, spread=None, radius=None,
|
|
shape=None, dimensions=None):
|
|
"""
|
|
设置发射器属性
|
|
|
|
Args:
|
|
position (Point3): 发射器位置
|
|
velocity (Vec3): 发射器基础速度
|
|
spread (float): 发射扩散角度
|
|
radius (float): 发射器半径
|
|
shape (str): 发射器形状
|
|
dimensions (Vec3): 发射器尺寸
|
|
"""
|
|
if position is not None:
|
|
self.emitter_position = position
|
|
if velocity is not None:
|
|
self.emitter_velocity = velocity
|
|
if spread is not None:
|
|
self.emitter_spread = spread
|
|
if radius is not None:
|
|
self.emitter_radius = radius
|
|
if shape is not None:
|
|
self.emitter_shape = shape
|
|
if dimensions is not None:
|
|
self.emitter_dimensions = dimensions
|
|
|
|
def set_particle_properties(self, mass=None, radius=None, size=None, lifetime=None,
|
|
color=None, shape_type=None):
|
|
"""
|
|
设置粒子属性
|
|
|
|
Args:
|
|
mass (float): 粒子质量
|
|
radius (float): 粒子半径(球形)
|
|
size (Vec3): 粒子尺寸(非球形)
|
|
lifetime (float): 粒子生命周期
|
|
color (Vec3): 粒子颜色
|
|
shape_type (str): 粒子形状类型
|
|
"""
|
|
if mass is not None:
|
|
self.particle_mass = mass
|
|
if radius is not None:
|
|
self.particle_radius = radius
|
|
if size is not None:
|
|
self.particle_size = size
|
|
if lifetime is not None:
|
|
self.particle_lifetime = lifetime
|
|
if color is not None:
|
|
self.particle_color = color
|
|
if shape_type is not None:
|
|
self.particle_shape_type = shape_type
|
|
|
|
def set_physics_properties(self, gravity_scale=None, air_resistance=None,
|
|
bounce_factor=None, friction=None, restitution=None,
|
|
linear_damping=None, angular_damping=None):
|
|
"""
|
|
设置物理属性
|
|
|
|
Args:
|
|
gravity_scale (float): 重力缩放
|
|
air_resistance (float): 空气阻力
|
|
bounce_factor (float): 弹跳因子
|
|
friction (float): 摩擦系数
|
|
restitution (float): 恢复系数
|
|
linear_damping (float): 线性阻尼
|
|
angular_damping (float): 角阻尼
|
|
"""
|
|
if gravity_scale is not None:
|
|
self.gravity_scale = gravity_scale
|
|
if air_resistance is not None:
|
|
self.air_resistance = air_resistance
|
|
if bounce_factor is not None:
|
|
self.bounce_factor = bounce_factor
|
|
if friction is not None:
|
|
self.friction = friction
|
|
if restitution is not None:
|
|
self.restitution = restitution
|
|
if linear_damping is not None:
|
|
self.linear_damping = linear_damping
|
|
if angular_damping is not None:
|
|
self.angular_damping = angular_damping
|
|
|
|
def set_collision_properties(self, enabled=None, group=None, mask=None, response=None):
|
|
"""
|
|
设置碰撞属性
|
|
|
|
Args:
|
|
enabled (bool): 是否启用碰撞
|
|
group (int): 碰撞组
|
|
mask (int): 碰撞掩码
|
|
response (bool): 是否响应碰撞
|
|
"""
|
|
if enabled is not None:
|
|
self.collision_enabled = enabled
|
|
if group is not None:
|
|
self.collision_group = group
|
|
if mask is not None:
|
|
self.collision_mask = mask
|
|
if response is not None:
|
|
self.collision_response = response
|
|
|
|
def set_environmental_effects(self, wind_enabled=None, wind_vector=None,
|
|
wind_turbulence=None, temperature_effect=None):
|
|
"""
|
|
设置环境影响
|
|
|
|
Args:
|
|
wind_enabled (bool): 是否启用风
|
|
wind_vector (Vec3): 风向量
|
|
wind_turbulence (float): 风湍流
|
|
temperature_effect (float): 温度影响
|
|
"""
|
|
if wind_enabled is not None:
|
|
self.wind_enabled = wind_enabled
|
|
if wind_vector is not None:
|
|
self.wind_vector = wind_vector
|
|
if wind_turbulence is not None:
|
|
self.wind_turbulence = wind_turbulence
|
|
if temperature_effect is not None:
|
|
self.temperature_effect = temperature_effect
|
|
|
|
def set_special_effects(self, vortex_enabled=None, vortex_center=None,
|
|
vortex_strength=None, vortex_axis=None):
|
|
"""
|
|
设置特殊效果
|
|
|
|
Args:
|
|
vortex_enabled (bool): 是否启用涡流
|
|
vortex_center (Point3): 涡流中心
|
|
vortex_strength (float): 涡流强度
|
|
vortex_axis (Vec3): 涡流轴向
|
|
"""
|
|
if vortex_enabled is not None:
|
|
self.vortex_enabled = vortex_enabled
|
|
if vortex_center is not None:
|
|
self.vortex_center = vortex_center
|
|
if vortex_strength is not None:
|
|
self.vortex_strength = vortex_strength
|
|
if vortex_axis is not None:
|
|
self.vortex_axis = vortex_axis
|
|
|
|
def set_lifetime_behaviors(self, size_curve=None, color_curve=None, force_curve=None):
|
|
"""
|
|
设置粒子生命周期行为
|
|
|
|
Args:
|
|
size_curve (list): 尺寸随时间变化曲线 [(time, scale), ...]
|
|
color_curve (list): 颜色随时间变化曲线 [(time, color), ...]
|
|
force_curve (list): 力随时间变化曲线 [(time, force), ...]
|
|
"""
|
|
if size_curve is not None:
|
|
self.size_over_lifetime = size_curve
|
|
if color_curve is not None:
|
|
self.color_over_lifetime = color_curve
|
|
if force_curve is not None:
|
|
self.force_over_lifetime = force_curve
|
|
|
|
def set_sub_emitter(self, enabled=None, rate=None, count=None, lifetime=None):
|
|
"""
|
|
设置粒子子发射器
|
|
|
|
Args:
|
|
enabled (bool): 是否启用子发射器
|
|
rate (float): 子发射器速率
|
|
count (int): 每次发射的子粒子数
|
|
lifetime (float): 子粒子生命周期
|
|
"""
|
|
if enabled is not None:
|
|
self.sub_emitter_enabled = enabled
|
|
if rate is not None:
|
|
self.sub_emitter_rate = rate
|
|
if count is not None:
|
|
self.sub_emitter_count = count
|
|
if lifetime is not None:
|
|
self.sub_emitter_lifetime = lifetime
|
|
|
|
def set_performance_options(self, sleeping_threshold=None, culling_distance=None,
|
|
batch_processing=None):
|
|
"""
|
|
设置性能优化选项
|
|
|
|
Args:
|
|
sleeping_threshold (float): 休眠阈值
|
|
culling_distance (float): 剔除距离
|
|
batch_processing (bool): 批量处理
|
|
"""
|
|
if sleeping_threshold is not None:
|
|
self.sleeping_threshold = sleeping_threshold
|
|
if culling_distance is not None:
|
|
self.culling_distance = culling_distance
|
|
if batch_processing is not None:
|
|
self.batch_processing = batch_processing
|
|
|
|
def set_emission_rate(self, rate):
|
|
"""
|
|
设置发射速率
|
|
|
|
Args:
|
|
rate (float): 每秒发射粒子数
|
|
"""
|
|
self.emission_rate = max(0, rate)
|
|
|
|
def set_emission_burst(self, count, interval=0.0):
|
|
"""
|
|
设置爆发发射
|
|
|
|
Args:
|
|
count (int): 每次爆发的粒子数
|
|
interval (float): 爆发间隔(秒)
|
|
"""
|
|
self.emission_burst_count = count
|
|
self.emission_burst_interval = interval
|
|
|
|
def emit_particle(self, position=None, velocity=None, lifetime=None, color=None):
|
|
"""
|
|
发射单个粒子
|
|
|
|
Args:
|
|
position (Point3): 粒子位置
|
|
velocity (Vec3): 粒子速度
|
|
lifetime (float): 粒子生命周期
|
|
color (Vec3): 粒子颜色
|
|
|
|
Returns:
|
|
dict: 粒子信息字典
|
|
"""
|
|
# 检查是否达到最大粒子数
|
|
if len(self.active_particles) >= self.max_particles:
|
|
return None
|
|
|
|
# 从对象池获取粒子或创建新粒子
|
|
if self.inactive_particles:
|
|
particle_info = self.inactive_particles.popleft()
|
|
self.recycled_count += 1
|
|
else:
|
|
particle_info = self._create_new_particle()
|
|
|
|
# 设置粒子属性
|
|
if position is None:
|
|
position = self._calculate_emission_position()
|
|
if velocity is None:
|
|
velocity = self._calculate_emission_velocity()
|
|
if lifetime is None:
|
|
lifetime = self.particle_lifetime * random.uniform(
|
|
1.0 - self.particle_lifetime * 0.2,
|
|
1.0 + self.particle_lifetime * 0.2
|
|
)
|
|
if color is None:
|
|
color = self.particle_color
|
|
|
|
# 初始化粒子状态
|
|
particle_info['position'] = position
|
|
particle_info['velocity'] = velocity
|
|
particle_info['lifetime'] = lifetime
|
|
particle_info['age'] = 0.0
|
|
particle_info['color'] = color
|
|
particle_info['initial_size'] = particle_info['size']
|
|
particle_info['initial_color'] = color
|
|
particle_info['initial_velocity'] = velocity
|
|
particle_info['rotation'] = random.uniform(0, 360) if self.particle_rotation_enabled else 0.0
|
|
particle_info['rotation_speed'] = self.particle_rotation_speed * random.uniform(
|
|
1.0 - self.particle_rotation_variation,
|
|
1.0 + self.particle_rotation_variation
|
|
)
|
|
particle_info['trail'] = [] if self.particle_trail_enabled else None
|
|
particle_info['collisions'] = 0
|
|
particle_info['sleeping'] = False
|
|
particle_info['culled'] = False
|
|
|
|
# 添加到物理世界
|
|
self.physics_world.bullet_world.attachRigidBody(particle_info['body'])
|
|
|
|
# 设置物理属性
|
|
particle_info['body'].setLinearVelocity(velocity)
|
|
particle_info['body'].setFriction(self.friction)
|
|
particle_info['body'].setRestitution(self.restitution)
|
|
particle_info['body'].setLinearDamping(self.linear_damping)
|
|
particle_info['body'].setAngularDamping(self.angular_damping)
|
|
|
|
# 设置碰撞属性
|
|
if self.collision_enabled:
|
|
particle_info['body'].setCollideFilterGroup(self.collision_group)
|
|
particle_info['body'].setCollideFilterMask(self.collision_mask)
|
|
else:
|
|
particle_info['body'].setCollideFilterGroup(0)
|
|
particle_info['body'].setCollideFilterMask(0)
|
|
|
|
# 设置位置
|
|
particle_info['node'].setPos(position)
|
|
|
|
# 添加到活动粒子列表
|
|
self.active_particles.append(particle_info)
|
|
self.emitted_count += 1
|
|
|
|
# 更新统计
|
|
self.max_active_particles = max(self.max_active_particles, len(self.active_particles))
|
|
|
|
return particle_info
|
|
|
|
def _create_new_particle(self):
|
|
"""
|
|
创建新粒子
|
|
|
|
Returns:
|
|
dict: 粒子信息字典
|
|
"""
|
|
# 创建粒子形状
|
|
if self.particle_shape_type == 'sphere':
|
|
size = self.particle_radius * random.uniform(
|
|
1.0 - self.particle_size_variation,
|
|
1.0 + self.particle_size_variation
|
|
)
|
|
particle_shape = BulletSphereShape(size)
|
|
elif self.particle_shape_type == 'box':
|
|
size = self.particle_size * random.uniform(
|
|
1.0 - self.particle_size_variation,
|
|
1.0 + self.particle_size_variation
|
|
)
|
|
particle_shape = BulletBoxShape(size * 0.5)
|
|
else:
|
|
# 默认使用球形
|
|
size = self.particle_radius * random.uniform(
|
|
1.0 - self.particle_size_variation,
|
|
1.0 + self.particle_size_variation
|
|
)
|
|
particle_shape = BulletSphereShape(size)
|
|
|
|
# 创建粒子刚体
|
|
particle_body = BulletRigidBodyNode(f'Particle_{len(self.particles)}')
|
|
particle_body.addShape(particle_shape)
|
|
particle_body.setMass(self.particle_mass)
|
|
|
|
# 创建粒子节点
|
|
from panda3d.core import NodePath
|
|
dummy_np = NodePath("dummy")
|
|
particle_node = dummy_np.attachNewNode(particle_body)
|
|
|
|
# 创建粒子信息
|
|
particle_info = {
|
|
'node': particle_node,
|
|
'body': particle_body,
|
|
'shape': particle_shape,
|
|
'position': Point3(0, 0, 0),
|
|
'velocity': Vec3(0, 0, 0),
|
|
'lifetime': self.particle_lifetime,
|
|
'age': 0.0,
|
|
'color': self.particle_color,
|
|
'size': size,
|
|
'mass': self.particle_mass,
|
|
'radius': size if self.particle_shape_type == 'sphere' else None,
|
|
'type': self.particle_type,
|
|
'material': self.particle_material,
|
|
'creation_time': time.time(),
|
|
}
|
|
|
|
self.particles.append(particle_info)
|
|
return particle_info
|
|
|
|
def _calculate_emission_position(self):
|
|
"""
|
|
计算发射位置
|
|
|
|
Returns:
|
|
Point3: 发射位置
|
|
"""
|
|
if self.emitter_shape == 'point':
|
|
return self.emitter_position
|
|
elif self.emitter_shape == 'circle':
|
|
angle = random.uniform(0, 2 * math.pi)
|
|
distance = random.uniform(0, self.emitter_radius)
|
|
offset = Point3(
|
|
math.cos(angle) * distance,
|
|
math.sin(angle) * distance,
|
|
0
|
|
)
|
|
return self.emitter_position + offset
|
|
elif self.emitter_shape == 'box':
|
|
offset = Point3(
|
|
random.uniform(-self.emitter_dimensions.getX() * 0.5, self.emitter_dimensions.getX() * 0.5),
|
|
random.uniform(-self.emitter_dimensions.getY() * 0.5, self.emitter_dimensions.getY() * 0.5),
|
|
random.uniform(-self.emitter_dimensions.getZ() * 0.5, self.emitter_dimensions.getZ() * 0.5)
|
|
)
|
|
return self.emitter_position + offset
|
|
elif self.emitter_shape == 'sphere':
|
|
# 球面均匀分布
|
|
theta = random.uniform(0, 2 * math.pi)
|
|
phi = math.acos(2 * random.uniform(0, 1) - 1)
|
|
radius = self.emitter_radius
|
|
offset = Point3(
|
|
radius * math.sin(phi) * math.cos(theta),
|
|
radius * math.sin(phi) * math.sin(theta),
|
|
radius * math.cos(phi)
|
|
)
|
|
return self.emitter_position + offset
|
|
else:
|
|
return self.emitter_position
|
|
|
|
def _calculate_emission_velocity(self):
|
|
"""
|
|
计算发射速度
|
|
|
|
Returns:
|
|
Vec3: 发射速度
|
|
"""
|
|
# 基础速度
|
|
base_velocity = self.emitter_velocity.copy()
|
|
|
|
# 添加随机扩散
|
|
if self.emitter_spread > 0:
|
|
spread_angle = random.uniform(0, self.emitter_spread)
|
|
spread_direction = random.uniform(0, 2 * math.pi)
|
|
spread_offset = Vec3(
|
|
math.cos(spread_direction) * math.sin(spread_angle),
|
|
math.sin(spread_direction) * math.sin(spread_angle),
|
|
math.cos(spread_angle)
|
|
)
|
|
base_velocity += spread_offset * base_velocity.length()
|
|
|
|
# 添加速度变化
|
|
if self.particle_velocity_variation > 0:
|
|
variation = random.uniform(
|
|
1.0 - self.particle_velocity_variation,
|
|
1.0 + self.particle_velocity_variation
|
|
)
|
|
base_velocity *= variation
|
|
|
|
# 添加随机速度分量
|
|
random_velocity = Vec3(
|
|
random.uniform(-1, 1),
|
|
random.uniform(-1, 1),
|
|
random.uniform(-1, 1)
|
|
) * base_velocity.length() * 0.1
|
|
|
|
return base_velocity + random_velocity
|
|
|
|
def update(self, dt):
|
|
"""
|
|
更新粒子系统
|
|
|
|
Args:
|
|
dt (float): 时间增量
|
|
"""
|
|
if self.paused:
|
|
return
|
|
|
|
# 应用时间缩放
|
|
dt *= self.time_scale
|
|
|
|
# 更新现有粒子
|
|
for particle in self.active_particles[:]: # 使用副本避免迭代时修改
|
|
self._update_particle(particle, dt)
|
|
|
|
# 发射新粒子
|
|
self._emit_particles(dt)
|
|
|
|
# 处理爆发发射
|
|
self._handle_burst_emission(dt)
|
|
|
|
def _update_particle(self, particle, dt):
|
|
"""
|
|
更新单个粒子
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
dt (float): 时间增量
|
|
"""
|
|
# 更新粒子年龄
|
|
particle['age'] += dt
|
|
|
|
# 检查生命周期
|
|
if particle['age'] >= particle['lifetime']:
|
|
self._destroy_particle(particle)
|
|
return
|
|
|
|
# 检查是否休眠
|
|
velocity = particle['body'].getLinearVelocity()
|
|
if velocity.length() < self.sleeping_threshold:
|
|
particle['sleeping'] = True
|
|
else:
|
|
particle['sleeping'] = False
|
|
|
|
# 如果休眠,跳过物理更新
|
|
if particle['sleeping']:
|
|
return
|
|
|
|
# 应用重力
|
|
if self.gravity_scale > 0:
|
|
gravity_force = Vec3(0, 0, -9.81) * self.gravity_scale * particle['mass']
|
|
particle['body'].applyCentralForce(gravity_force)
|
|
|
|
# 应用空气阻力
|
|
if self.air_resistance > 0:
|
|
drag_force = -velocity * self.air_resistance
|
|
particle['body'].applyCentralForce(drag_force)
|
|
|
|
# 应用风力
|
|
if self.wind_enabled and self.wind_vector.length() > 0:
|
|
wind_force = self.wind_vector * particle['mass']
|
|
if self.wind_turbulence > 0:
|
|
turbulence = Vec3(
|
|
random.uniform(-1, 1),
|
|
random.uniform(-1, 1),
|
|
random.uniform(-1, 1)
|
|
) * self.wind_turbulence
|
|
wind_force += turbulence
|
|
particle['body'].applyCentralForce(wind_force)
|
|
|
|
# 应用涡流力
|
|
if self.vortex_enabled and self.vortex_strength > 0:
|
|
self._apply_vortex_force(particle)
|
|
|
|
# 应用温度影响(浮力等)
|
|
if self.temperature_effect != 0:
|
|
# 简化的浮力计算
|
|
buoyancy_force = Vec3(0, 0, 1) * self.temperature_effect * particle['mass']
|
|
particle['body'].applyCentralForce(buoyancy_force)
|
|
|
|
# 应用生命周期力
|
|
if self.force_over_lifetime:
|
|
self._apply_lifetime_force(particle)
|
|
|
|
# 更新轨迹
|
|
if self.particle_trail_enabled and particle['trail'] is not None:
|
|
position = particle['node'].getPos()
|
|
particle['trail'].append(position)
|
|
if len(particle['trail']) > self.particle_trail_length:
|
|
particle['trail'].pop(0)
|
|
|
|
# 更新旋转
|
|
if self.particle_rotation_enabled:
|
|
particle['rotation'] += particle['rotation_speed'] * dt
|
|
|
|
# 更新尺寸
|
|
if self.size_over_lifetime:
|
|
self._update_particle_size(particle)
|
|
|
|
# 更新颜色
|
|
if self.particle_fade_enabled or self.color_over_lifetime:
|
|
self._update_particle_color(particle)
|
|
|
|
# 处理子发射器
|
|
if self.sub_emitter_enabled and self.sub_emitter_rate > 0:
|
|
self._handle_sub_emitter(particle, dt)
|
|
|
|
def _apply_vortex_force(self, particle):
|
|
"""
|
|
应用涡流力
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
"""
|
|
position = particle['node'].getPos()
|
|
to_center = self.vortex_center - position
|
|
distance = to_center.length()
|
|
|
|
if distance > 0:
|
|
# 径向力(向中心吸引)
|
|
radial_force = to_center.normalized() * self.vortex_strength / (distance + 1.0)
|
|
|
|
# 切向力(绕轴旋转)
|
|
tangent_dir = self.vortex_axis.cross(to_center).normalized()
|
|
tangent_force = tangent_dir * self.vortex_strength
|
|
|
|
total_force = (radial_force + tangent_force) * particle['mass']
|
|
particle['body'].applyCentralForce(total_force)
|
|
|
|
def _apply_lifetime_force(self, particle):
|
|
"""
|
|
应用生命周期力
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
"""
|
|
normalized_age = particle['age'] / particle['lifetime']
|
|
|
|
# 在力曲线上插值
|
|
for i in range(len(self.force_over_lifetime) - 1):
|
|
time1, force1 = self.force_over_lifetime[i]
|
|
time2, force2 = self.force_over_lifetime[i + 1]
|
|
|
|
if time1 <= normalized_age <= time2:
|
|
t = (normalized_age - time1) / (time2 - time1) if time2 != time1 else 0
|
|
interpolated_force = force1 + (force2 - force1) * t
|
|
particle['body'].applyCentralForce(interpolated_force * particle['mass'])
|
|
break
|
|
|
|
def _update_particle_size(self, particle):
|
|
"""
|
|
更新粒子尺寸
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
"""
|
|
normalized_age = particle['age'] / particle['lifetime']
|
|
|
|
# 在尺寸曲线上插值
|
|
for i in range(len(self.size_over_lifetime) - 1):
|
|
time1, scale1 = self.size_over_lifetime[i]
|
|
time2, scale2 = self.size_over_lifetime[i + 1]
|
|
|
|
if time1 <= normalized_age <= time2:
|
|
t = (normalized_age - time1) / (time2 - time1) if time2 != time1 else 0
|
|
interpolated_scale = scale1 + (scale2 - scale1) * t
|
|
|
|
# 更新粒子尺寸(这个功能需要与渲染系统集成)
|
|
# 这里只是更新记录,实际渲染需要在渲染系统中实现
|
|
if isinstance(particle['size'], (int, float)):
|
|
particle['size'] = particle['initial_size'] * interpolated_scale
|
|
break
|
|
|
|
def _update_particle_color(self, particle):
|
|
"""
|
|
更新粒子颜色
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
"""
|
|
normalized_age = particle['age'] / particle['lifetime']
|
|
|
|
# 淡出效果
|
|
if self.particle_fade_enabled:
|
|
fade_factor = 1.0 - (normalized_age * normalized_age) # 二次淡出
|
|
particle['color'] = particle['initial_color'] * fade_factor
|
|
|
|
# 生命周期颜色变化
|
|
if self.color_over_lifetime:
|
|
for i in range(len(self.color_over_lifetime) - 1):
|
|
time1, color1 = self.color_over_lifetime[i]
|
|
time2, color2 = self.color_over_lifetime[i + 1]
|
|
|
|
if time1 <= normalized_age <= time2:
|
|
t = (normalized_age - time1) / (time2 - time1) if time2 != time1 else 0
|
|
interpolated_color = color1 + (color2 - color1) * t
|
|
if self.particle_fade_enabled:
|
|
# 结合淡出效果
|
|
particle['color'] = interpolated_color * (1.0 - normalized_age)
|
|
else:
|
|
particle['color'] = interpolated_color
|
|
break
|
|
|
|
def _handle_sub_emitter(self, particle, dt):
|
|
"""
|
|
处理子发射器
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
dt (float): 时间增量
|
|
"""
|
|
# 简化的子发射器实现
|
|
pass # 需要更复杂的实现
|
|
|
|
def _emit_particles(self, dt):
|
|
"""
|
|
发射粒子
|
|
|
|
Args:
|
|
dt (float): 时间增量
|
|
"""
|
|
# 基于发射速率发射粒子
|
|
self.emission_accumulator += dt * self.emission_rate
|
|
while self.emission_accumulator >= 1.0 and len(self.active_particles) < self.max_particles:
|
|
self.emit_particle()
|
|
self.emission_accumulator -= 1.0
|
|
|
|
def _handle_burst_emission(self, dt):
|
|
"""
|
|
处理爆发发射
|
|
|
|
Args:
|
|
dt (float): 时间增量
|
|
"""
|
|
if self.emission_burst_count > 0 and self.emission_burst_interval > 0:
|
|
self.emission_burst_timer += dt
|
|
if self.emission_burst_timer >= self.emission_burst_interval:
|
|
for _ in range(min(self.emission_burst_count, self.max_particles - len(self.active_particles))):
|
|
self.emit_particle()
|
|
self.emission_burst_timer = 0.0
|
|
|
|
def _destroy_particle(self, particle):
|
|
"""
|
|
销毁粒子
|
|
|
|
Args:
|
|
particle (dict): 粒子信息
|
|
"""
|
|
if particle in self.active_particles:
|
|
# 从物理世界移除
|
|
self.physics_world.bullet_world.removeRigidBody(particle['body'])
|
|
|
|
# 移除节点
|
|
if particle['node'] and not particle['node'].isEmpty():
|
|
particle['node'].removeNode()
|
|
|
|
# 从活动列表移除
|
|
self.active_particles.remove(particle)
|
|
|
|
# 添加到对象池
|
|
self.inactive_particles.append(particle)
|
|
|
|
# 调用死亡回调
|
|
for callback in self.death_callbacks:
|
|
try:
|
|
callback(particle)
|
|
except Exception as e:
|
|
print(f"粒子死亡回调错误: {e}")
|
|
|
|
def get_particle_count(self):
|
|
"""
|
|
获取活动粒子数量
|
|
|
|
Returns:
|
|
int: 活动粒子数量
|
|
"""
|
|
return len(self.active_particles)
|
|
|
|
def get_total_emitted(self):
|
|
"""
|
|
获取总发射粒子数
|
|
|
|
Returns:
|
|
int: 总发射粒子数
|
|
"""
|
|
return self.emitted_count
|
|
|
|
def get_statistics(self):
|
|
"""
|
|
获取粒子系统统计信息
|
|
|
|
Returns:
|
|
dict: 统计信息
|
|
"""
|
|
return {
|
|
'active_particles': len(self.active_particles),
|
|
'total_emitted': self.emitted_count,
|
|
'recycled_particles': self.recycled_count,
|
|
'collision_count': self.collision_count,
|
|
'max_active_particles': self.max_active_particles,
|
|
'emission_rate': self.emission_rate,
|
|
'particle_type': self.particle_type,
|
|
'system_age': time.time() - (self.particles[0]['creation_time'] if self.particles else time.time()),
|
|
}
|
|
|
|
def clear_particles(self):
|
|
"""
|
|
清除所有粒子
|
|
"""
|
|
# 销毁所有活动粒子
|
|
for particle in self.active_particles[:]:
|
|
self._destroy_particle(particle)
|
|
|
|
# 清空对象池
|
|
self.inactive_particles.clear()
|
|
|
|
# 清空粒子列表
|
|
self.particles.clear()
|
|
self.active_particles.clear()
|
|
|
|
# 重置统计信息
|
|
self.emitted_count = 0
|
|
self.recycled_count = 0
|
|
self.collision_count = 0
|
|
self.max_active_particles = 0
|
|
|
|
# 重置发射器
|
|
self.emission_accumulator = 0.0
|
|
self.emission_burst_timer = 0.0
|
|
|
|
print("所有粒子已清除")
|
|
|
|
def pause(self, paused=True):
|
|
"""
|
|
暂停或恢复粒子系统
|
|
|
|
Args:
|
|
paused (bool): 是否暂停
|
|
"""
|
|
self.paused = paused
|
|
|
|
def set_time_scale(self, scale):
|
|
"""
|
|
设置时间缩放
|
|
|
|
Args:
|
|
scale (float): 时间缩放因子
|
|
"""
|
|
self.time_scale = max(0.0, scale)
|
|
|
|
def add_death_callback(self, callback):
|
|
"""
|
|
添加粒子死亡回调
|
|
|
|
Args:
|
|
callback (function): 回调函数
|
|
"""
|
|
if callback not in self.death_callbacks:
|
|
self.death_callbacks.append(callback)
|
|
|
|
def remove_death_callback(self, callback):
|
|
"""
|
|
移除粒子死亡回调
|
|
|
|
Args:
|
|
callback (function): 回调函数
|
|
"""
|
|
if callback in self.death_callbacks:
|
|
self.death_callbacks.remove(callback)
|
|
|
|
def add_collision_callback(self, callback):
|
|
"""
|
|
添加粒子碰撞回调
|
|
|
|
Args:
|
|
callback (function): 回调函数
|
|
"""
|
|
if callback not in self.collision_callbacks:
|
|
self.collision_callbacks.append(callback)
|
|
|
|
def remove_collision_callback(self, callback):
|
|
"""
|
|
移除粒子碰撞回调
|
|
|
|
Args:
|
|
callback (function): 回调函数
|
|
"""
|
|
if callback in self.collision_callbacks:
|
|
self.collision_callbacks.remove(callback)
|
|
|
|
def create_explosion(self, position, particle_count=50, explosion_force=100.0,
|
|
particle_radius=None, particle_lifetime=None, explosion_radius=5.0):
|
|
"""
|
|
创建爆炸效果
|
|
|
|
Args:
|
|
position (Point3): 爆炸位置
|
|
particle_count (int): 粒子数量
|
|
explosion_force (float): 爆炸力
|
|
particle_radius (float): 粒子半径
|
|
particle_lifetime (float): 粒子生命周期
|
|
explosion_radius (float): 爆炸半径
|
|
"""
|
|
if particle_radius is None:
|
|
particle_radius = self.particle_radius
|
|
if particle_lifetime is None:
|
|
particle_lifetime = self.particle_lifetime
|
|
|
|
for i in range(particle_count):
|
|
# 随机方向
|
|
direction = Vec3(
|
|
random.uniform(-1, 1),
|
|
random.uniform(-1, 1),
|
|
random.uniform(0, 1)
|
|
)
|
|
direction.normalize()
|
|
|
|
# 粒子速度
|
|
velocity = direction * random.uniform(explosion_force * 0.5, explosion_force)
|
|
|
|
# 粒子位置(在爆炸半径内)
|
|
offset = direction * random.uniform(0, explosion_radius)
|
|
particle_position = position + offset
|
|
|
|
# 创建粒子
|
|
particle_shape = BulletSphereShape(particle_radius * random.uniform(0.5, 2.0))
|
|
particle_body = BulletRigidBodyNode(f'ExplosionParticle_{self.emitted_count + i}')
|
|
particle_body.addShape(particle_shape)
|
|
particle_body.setMass(self.particle_mass * 0.5) # 爆炸粒子质量较小
|
|
|
|
# 设置碰撞属性
|
|
if self.collision_enabled:
|
|
particle_body.setCollideFilterGroup(self.collision_group)
|
|
particle_body.setCollideFilterMask(self.collision_mask)
|
|
|
|
# 创建粒子节点
|
|
from panda3d.core import NodePath
|
|
dummy_np = NodePath("dummy")
|
|
particle_node = dummy_np.attachNewNode(particle_body)
|
|
particle_node.setPos(particle_position)
|
|
|
|
# 添加到物理世界
|
|
self.physics_world.bullet_world.attachRigidBody(particle_body)
|
|
|
|
# 应用初始力
|
|
particle_body.setLinearVelocity(velocity)
|
|
|
|
# 创建粒子信息
|
|
particle_info = {
|
|
'node': particle_node,
|
|
'body': particle_body,
|
|
'shape': particle_shape,
|
|
'position': particle_position,
|
|
'velocity': velocity,
|
|
'lifetime': particle_lifetime * random.uniform(0.5, 1.5),
|
|
'age': 0.0,
|
|
'color': Vec3(1, random.uniform(0.5, 1), 0), # 橙色/红色
|
|
'size': particle_radius,
|
|
'mass': self.particle_mass * 0.5,
|
|
'radius': particle_radius,
|
|
'type': self.TYPE_EXPLOSION,
|
|
'material': self.particle_material,
|
|
'creation_time': time.time(),
|
|
'rotation': random.uniform(0, 360),
|
|
'rotation_speed': random.uniform(-10, 10),
|
|
'trail': [] if self.particle_trail_enabled else None,
|
|
'collisions': 0,
|
|
'sleeping': False,
|
|
'culled': False
|
|
}
|
|
|
|
self.particles.append(particle_info)
|
|
self.active_particles.append(particle_info)
|
|
self.emitted_count += 1
|
|
|
|
print(f"创建爆炸效果,位置: {position}, 粒子数: {particle_count}")
|
|
|
|
def create_fire_effect(self, position, particle_count=30, rise_speed=5.0,
|
|
particle_radius=None, particle_lifetime=None):
|
|
"""
|
|
创建火焰效果
|
|
|
|
Args:
|
|
position (Point3): 火焰位置
|
|
particle_count (int): 粒子数量
|
|
rise_speed (float): 上升速度
|
|
particle_radius (float): 粒子半径
|
|
particle_lifetime (float): 粒子生命周期
|
|
"""
|
|
if particle_radius is None:
|
|
particle_radius = self.particle_radius
|
|
if particle_lifetime is None:
|
|
particle_lifetime = self.particle_lifetime * 2.0 # 火焰粒子寿命更长
|
|
|
|
for i in range(particle_count):
|
|
# 粒子位置(在火焰底部附近)
|
|
offset = Vec3(
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(0, 1)
|
|
)
|
|
particle_position = position + offset
|
|
|
|
# 粒子速度(向上)
|
|
velocity = Vec3(
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(rise_speed * 0.8, rise_speed * 1.2)
|
|
)
|
|
|
|
# 创建粒子
|
|
particle_shape = BulletSphereShape(particle_radius * random.uniform(0.8, 1.5))
|
|
particle_body = BulletRigidBodyNode(f'FireParticle_{self.emitted_count + i}')
|
|
particle_body.addShape(particle_shape)
|
|
particle_body.setMass(self.particle_mass * 0.1) # 火焰粒子质量很小
|
|
|
|
# 设置物理属性(火焰粒子受重力影响较小)
|
|
particle_body.setFriction(0.1)
|
|
particle_body.setRestitution(0.1)
|
|
|
|
# 创建粒子节点
|
|
from panda3d.core import NodePath
|
|
dummy_np = NodePath("dummy")
|
|
particle_node = dummy_np.attachNewNode(particle_body)
|
|
particle_node.setPos(particle_position)
|
|
|
|
# 添加到物理世界
|
|
self.physics_world.bullet_world.attachRigidBody(particle_body)
|
|
|
|
# 应用初始速度
|
|
particle_body.setLinearVelocity(velocity)
|
|
|
|
# 创建粒子信息
|
|
particle_info = {
|
|
'node': particle_node,
|
|
'body': particle_body,
|
|
'shape': particle_shape,
|
|
'position': particle_position,
|
|
'velocity': velocity,
|
|
'lifetime': particle_lifetime * random.uniform(0.8, 1.2),
|
|
'age': 0.0,
|
|
'color': Vec3(1, random.uniform(0.3, 0.7), 0), # 黄色/橙色
|
|
'size': particle_radius,
|
|
'mass': self.particle_mass * 0.1,
|
|
'radius': particle_radius,
|
|
'type': self.TYPE_FIRE,
|
|
'material': self.particle_material,
|
|
'creation_time': time.time(),
|
|
'rotation': random.uniform(0, 360),
|
|
'rotation_speed': random.uniform(-5, 5),
|
|
'trail': [] if self.particle_trail_enabled else None,
|
|
'collisions': 0,
|
|
'sleeping': False,
|
|
'culled': False
|
|
}
|
|
|
|
self.particles.append(particle_info)
|
|
self.active_particles.append(particle_info)
|
|
self.emitted_count += 1
|
|
|
|
print(f"创建火焰效果,位置: {position}, 粒子数: {particle_count}")
|
|
|
|
def destroy(self):
|
|
"""
|
|
销毁粒子系统
|
|
"""
|
|
self.clear_particles()
|
|
self.death_callbacks.clear()
|
|
self.collision_callbacks.clear()
|
|
print("粒子系统已销毁") |