EG/plugins/user/particle_system/editor/particle_editor.py
2025-12-12 16:16:15 +08:00

692 lines
26 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.

"""
粒子编辑器
提供可视化的粒子效果编辑功能
"""
import json
from typing import Dict, Optional, Any
from panda3d.core import Vec3, Point3
class ParticleEditor:
"""
粒子编辑器
提供实时编辑粒子效果的功能
"""
def __init__(self, world, particle_manager):
"""
初始化粒子编辑器
Args:
world: 3D世界对象
particle_manager: 粒子管理器
"""
self.world = world
self.particle_manager = particle_manager
self.visible = False
# 当前编辑的发射器
self.current_emitter_id = None
self.current_emitter = None
# 编辑器GUI元素
self.gui_elements = []
self.parameter_controls = {}
self.effect_type_buttons = {}
# 预览模式
self.preview_mode = True
self.auto_update = True
# 编辑器状态
self.editor_position = Point3(0, 0, 0)
self.editor_enabled = False
# 编辑参数
self.edit_parameters = {
'emitter_type': 'fire',
'emission_rate': 50,
'particle_lifetime': 2.0,
'start_color': Vec3(1.0, 0.3, 0.0),
'end_color': Vec3(1.0, 0.0, 0.0),
'start_size': 0.2,
'end_size': 0.8,
'velocity': Vec3(0, 0, 2),
'velocity_variation': 0.5,
'gravity_scale': -0.2,
'air_resistance': 0.02,
'bounce_factor': 0.5,
'emitter_shape': 'circle',
'emitter_radius': 0.3,
'rotation_enabled': False,
'rotation_speed': 0.0
}
print("✓ 粒子编辑器初始化完成")
def toggle_visibility(self):
"""切换编辑器可见性"""
if self.visible:
self.hide()
else:
self.show()
def show(self):
"""显示编辑器"""
if self.visible:
return
self.visible = True
self._create_editor_gui()
# 如果没有当前发射器,创建一个默认的
if not self.current_emitter_id:
self.create_new_effect()
print("✓ 粒子编辑器已显示")
def hide(self):
"""隐藏编辑器"""
if not self.visible:
return
self.visible = False
self._cleanup_editor_gui()
print("✓ 粒子编辑器已隐藏")
def _create_editor_gui(self):
"""创建编辑器GUI"""
try:
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
print("⚠ GUI管理器不可用无法创建编辑器界面")
return
gui_manager = self.world.gui_manager
# 创建编辑器面板标题
panel = gui_manager.createGUIButton(
pos=(0.7, 0, 0.95),
text="粒子编辑器",
size=0.06
)
self.gui_elements.append(panel)
# 创建效果类型选择按钮
effect_types = ["火焰", "烟雾", "爆炸", "水花", "魔法", "雪花", "自定义"]
for i, effect_type in enumerate(effect_types):
button = gui_manager.createGUIButton(
pos=(0.7, 0, 0.88 - i * 0.06),
text=effect_type,
size=0.04
)
self.gui_elements.append(button)
self.effect_type_buttons[effect_type] = button
self.world.accept(f"editor_effect_{effect_type}",
lambda t=effect_type: self.set_effect_type(t))
# 创建参数控制面板
self._create_parameter_controls()
# 创建操作按钮
self._create_action_buttons()
except Exception as e:
print(f"⚠ 编辑器GUI创建失败: {e}")
def _create_parameter_controls(self):
"""创建参数控制界面"""
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
return
gui_manager = self.world.gui_manager
# 基础参数控制
parameters = [
("发射率", "emission_rate", 0, 500, 1),
("生命周期", "particle_lifetime", 0.1, 10.0, 0.1),
("起始尺寸", "start_size", 0.01, 5.0, 0.01),
("结束尺寸", "end_size", 0.01, 5.0, 0.01),
("速度变化", "velocity_variation", 0.0, 5.0, 0.1),
("重力缩放", "gravity_scale", -5.0, 5.0, 0.1),
("空气阻力", "air_resistance", 0.0, 1.0, 0.01),
("反弹系数", "bounce_factor", 0.0, 1.0, 0.1),
("发射器半径", "emitter_radius", 0.0, 5.0, 0.1),
("旋转速度", "rotation_speed", -10.0, 10.0, 0.1)
]
for i, (name, param, min_val, max_val, step) in enumerate(parameters):
y_pos = 0.8 - i * 0.055
# 参数标签
label = gui_manager.createGUIButton(
pos=(0.75, 0, y_pos),
text=f"{name}:",
size=0.03
)
self.gui_elements.append(label)
# 参数值显示
value_label = gui_manager.createGUIButton(
pos=(0.88, 0, y_pos),
text=f"{self.edit_parameters[param]:.2f}",
size=0.03
)
self.gui_elements.append(value_label)
# 减少按钮
dec_button = gui_manager.createGUIButton(
pos=(0.83, 0, y_pos),
text="-",
size=0.03
)
self.gui_elements.append(dec_button)
# 增加按钮
inc_button = gui_manager.createGUIButton(
pos=(0.93, 0, y_pos),
text="+",
size=0.03
)
self.gui_elements.append(inc_button)
# 注册事件
self.world.accept(f"dec_{param}", lambda p=param, s=step: self._decrease_parameter(p, s))
self.world.accept(f"inc_{param}", lambda p=param, s=step: self._increase_parameter(p, s))
# 存储控制信息
self.parameter_controls[param] = {
'name': name,
'min': min_val,
'max': max_val,
'step': step,
'value_label': value_label,
'dec_button': dec_button,
'inc_button': inc_button
}
# 布尔型参数
bool_params = [
("启用旋转", "rotation_enabled"),
("自动更新", "auto_update"),
("预览模式", "preview_mode")
]
for i, (name, param) in enumerate(bool_params):
y_pos = 0.25 - i * 0.055
# 参数标签
label = gui_manager.createGUIButton(
pos=(0.75, 0, y_pos),
text=f"{name}:",
size=0.03
)
self.gui_elements.append(label)
# 开关按钮
switch_button = gui_manager.createGUIButton(
pos=(0.88, 0, y_pos),
text="" if self.edit_parameters.get(param, False) else "",
size=0.03
)
self.gui_elements.append(switch_button)
# 注册事件
self.world.accept(f"toggle_{param}", lambda p=param: self._toggle_boolean_parameter(p))
self.parameter_controls[param] = {
'name': name,
'value_label': switch_button,
'type': 'boolean'
}
def _create_action_buttons(self):
"""创建操作按钮"""
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
return
gui_manager = self.world.gui_manager
actions = [
("应用更改", 0.15, self.apply_changes),
("重置参数", 0.09, self.reset_parameters),
("创建新效果", 0.03, self.create_new_effect),
("删除当前", -0.03, self.delete_current_effect),
("保存预设", -0.09, self.save_preset),
("加载预设", -0.15, self.load_preset)
]
for name, y_offset, callback in actions:
button = gui_manager.createGUIButton(
pos=(0.82, 0, y_offset),
text=name,
size=0.035
)
self.gui_elements.append(button)
self.world.accept(f"action_{name}", callback)
def _cleanup_editor_gui(self):
"""清理编辑器GUI"""
try:
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
gui_manager = self.world.gui_manager
for element in self.gui_elements:
gui_manager.deleteGUIElement(element)
# 清理事件监听
for param in self.parameter_controls:
self.world.ignore(f"dec_{param}")
self.world.ignore(f"inc_{param}")
for param in ['rotation_enabled', 'auto_update', 'preview_mode']:
self.world.ignore(f"toggle_{param}")
self.world.ignore("action_应用更改")
self.world.ignore("action_重置参数")
self.world.ignore("action_创建新效果")
self.world.ignore("action_删除当前")
self.world.ignore("action_保存预设")
self.world.ignore("action_加载预设")
self.gui_elements.clear()
self.parameter_controls.clear()
self.effect_type_buttons.clear()
except Exception as e:
print(f"⚠ 编辑器GUI清理失败: {e}")
def _decrease_parameter(self, param_name: str, step: float):
"""减少参数值"""
if param_name in self.edit_parameters:
control = self.parameter_controls[param_name]
current_value = self.edit_parameters[param_name]
new_value = max(control['min'], current_value - step)
self.edit_parameters[param_name] = new_value
# 更新显示
if 'value_label' in control:
control['value_label'].setText(f"{new_value:.2f}")
# 自动应用更改
if self.auto_update:
self.apply_changes()
def _increase_parameter(self, param_name: str, step: float):
"""增加参数值"""
if param_name in self.edit_parameters:
control = self.parameter_controls[param_name]
current_value = self.edit_parameters[param_name]
new_value = min(control['max'], current_value + step)
self.edit_parameters[param_name] = new_value
# 更新显示
if 'value_label' in control:
control['value_label'].setText(f"{new_value:.2f}")
# 自动应用更改
if self.auto_update:
self.apply_changes()
def _toggle_boolean_parameter(self, param_name: str):
"""切换布尔参数"""
if param_name in self.edit_parameters:
current_value = self.edit_parameters[param_name]
new_value = not current_value
self.edit_parameters[param_name] = new_value
# 更新显示
control = self.parameter_controls[param_name]
if 'value_label' in control:
control['value_label'].setText("" if new_value else "")
# 特殊处理某些参数
if param_name == 'auto_update':
self.auto_update = new_value
elif param_name == 'preview_mode':
self.preview_mode = new_value
# 自动应用更改
if self.auto_update:
self.apply_changes()
def create_new_effect(self, effect_type: str = "火焰"):
"""创建新的粒子效果"""
# 删除当前效果
if self.current_emitter_id:
self.particle_manager.remove_emitter(self.current_emitter_id)
# 在摄像机前方创建
camera_pos = self.world.camera.getPos()
camera_forward = self.world.camera.getNetTransform().getMat().getRow3(1)
position = camera_pos + camera_forward * 5
# 根据类型设置参数
self.set_effect_type(effect_type)
# 创建发射器
config = self.edit_parameters.copy()
config['emitter_type'] = effect_type.lower()
self.current_emitter_id = self.particle_manager.create_emitter(
effect_type.lower(),
position,
config
)
self.current_emitter = self.particle_manager.get_emitter(self.current_emitter_id)
self.editor_position = position
print(f"✓ 创建新粒子效果: {effect_type}")
def set_effect_type(self, effect_type: str):
"""设置效果类型"""
self.edit_parameters['emitter_type'] = effect_type.lower()
# 根据类型预设参数
if effect_type == "火焰":
self.edit_parameters.update({
'emission_rate': 50,
'particle_lifetime': 2.0,
'start_color': Vec3(1.0, 0.3, 0.0),
'end_color': Vec3(1.0, 0.0, 0.0),
'start_size': 0.2,
'end_size': 0.8,
'velocity': Vec3(0, 0, 2),
'velocity_variation': 0.5,
'gravity_scale': -0.2,
'emitter_shape': 'circle',
'emitter_radius': 0.3
})
elif effect_type == "烟雾":
self.edit_parameters.update({
'emission_rate': 30,
'particle_lifetime': 4.0,
'start_color': Vec3(0.8, 0.8, 0.8),
'end_color': Vec3(0.3, 0.3, 0.3),
'start_size': 0.3,
'end_size': 1.5,
'velocity': Vec3(0, 0, 1),
'velocity_variation': 0.3,
'gravity_scale': -0.1,
'emitter_shape': 'circle',
'emitter_radius': 0.5
})
elif effect_type == "爆炸":
self.edit_parameters.update({
'emission_rate': 200,
'burst_count': 100,
'particle_lifetime': 1.5,
'start_color': Vec3(1.0, 0.8, 0.0),
'end_color': Vec3(1.0, 0.2, 0.0),
'start_size': 0.1,
'end_size': 0.5,
'velocity': Vec3(0, 0, 0),
'velocity_variation': 2.0,
'gravity_scale': 0.5,
'duration': 0.2,
'emitter_shape': 'sphere',
'emitter_radius': 0.1
})
elif effect_type == "水花":
self.edit_parameters.update({
'emission_rate': 80,
'particle_lifetime': 3.0,
'start_color': Vec3(0.2, 0.6, 1.0),
'end_color': Vec3(0.1, 0.3, 0.8),
'start_size': 0.1,
'end_size': 0.05,
'velocity': Vec3(0, 0, 3),
'velocity_variation': 1.0,
'gravity_scale': 2.0,
'bounce_factor': 0.3,
'emitter_shape': 'circle',
'emitter_radius': 0.2
})
elif effect_type == "魔法":
self.edit_parameters.update({
'emission_rate': 40,
'particle_lifetime': 3.0,
'start_color': Vec3(0.5, 0.2, 1.0),
'end_color': Vec3(0.8, 0.6, 1.0),
'start_size': 0.15,
'end_size': 0.05,
'velocity': Vec3(0, 0, 0.5),
'velocity_variation': 0.8,
'gravity_scale': -0.5,
'rotation_enabled': True,
'rotation_speed': 2.0,
'emitter_shape': 'sphere',
'emitter_radius': 0.8
})
elif effect_type == "雪花":
self.edit_parameters.update({
'emission_rate': 60,
'particle_lifetime': 8.0,
'start_color': Vec3(0.9, 0.9, 1.0),
'end_color': Vec3(0.9, 0.9, 1.0),
'start_size': 0.05,
'end_size': 0.05,
'velocity': Vec3(0, 0, -1),
'velocity_variation': 0.5,
'gravity_scale': 0.8,
'air_resistance': 0.1,
'bounce_factor': 0.2,
'emitter_shape': 'box',
'emitter_dimensions': Vec3(20, 20, 1)
})
# 更新GUI显示
self._update_parameter_display()
def _update_parameter_display(self):
"""更新参数显示"""
for param_name, control in self.parameter_controls.items():
if param_name in self.edit_parameters:
value = self.edit_parameters[param_name]
if control.get('type') == 'boolean':
if 'value_label' in control:
control['value_label'].setText("" if value else "")
else:
if 'value_label' in control:
if isinstance(value, (int, float)):
control['value_label'].setText(f"{value:.2f}")
else:
control['value_label'].setText(str(value))
def apply_changes(self):
"""应用更改到当前效果"""
if not self.current_emitter_id or not self.current_emitter:
print("⚠ 没有选中的效果")
return
# 更新发射器参数
try:
# 更新发射器配置
for param_name, value in self.edit_parameters.items():
if hasattr(self.current_emitter, param_name):
setattr(self.current_emitter, param_name, value)
print("✓ 参数已应用")
except Exception as e:
print(f"⚠ 参数应用失败: {e}")
def reset_parameters(self):
"""重置参数为默认值"""
# 重新设置当前类型参数
current_type = self.edit_parameters.get('emitter_type', 'fire')
self.set_effect_type(current_type.capitalize())
print("✓ 参数已重置")
def delete_current_effect(self):
"""删除当前效果"""
if self.current_emitter_id:
self.particle_manager.remove_emitter(self.current_emitter_id)
self.current_emitter_id = None
self.current_emitter = None
print("✓ 当前效果已删除")
def update_parameter(self, param_name: str, value: float):
"""更新参数值"""
if not self.current_emitter or param_name not in self.parameter_controls:
return
# 更新控制信息
control = self.parameter_controls[param_name]
# 更新GUI显示
if 'value_label' in control:
try:
control['value_label'].setText(f"{value:.2f}")
except:
pass
# 应用到发射器
self._apply_parameter_to_emitter(param_name, value)
if self.auto_update:
print(f"✓ 更新参数 {param_name}: {value}")
def _apply_parameter_to_emitter(self, param_name: str, value: float):
"""将参数应用到发射器"""
if not self.current_emitter:
return
try:
if param_name == "emission_rate":
self.current_emitter.emission_rate = value
elif param_name == "particle_lifetime":
self.current_emitter.particle_lifetime = value
elif param_name == "start_size":
self.current_emitter.start_size = value
elif param_name == "end_size":
self.current_emitter.end_size = value
elif param_name == "velocity_variation":
self.current_emitter.velocity_variation = value
except Exception as e:
print(f"⚠ 参数应用失败 {param_name}: {e}")
def set_emitter_position(self, position: Point3):
"""设置发射器位置"""
self.editor_position = position
if self.current_emitter:
self.current_emitter.set_position(position)
def get_current_config(self) -> Dict[str, Any]:
"""获取当前配置"""
config = self.edit_parameters.copy()
config['emitter_type'] = self.edit_parameters.get('emitter_type', 'fire')
config['position'] = [
self.editor_position.x,
self.editor_position.y,
self.editor_position.z
]
return config
def load_config(self, config: Dict[str, Any]):
"""加载配置"""
try:
# 设置位置
if 'position' in config:
pos = config['position']
position = Point3(pos[0], pos[1], pos[2])
self.set_emitter_position(position)
# 更新编辑参数
for param_name, value in config.items():
if param_name in self.edit_parameters:
self.edit_parameters[param_name] = value
# 更新GUI显示
self._update_parameter_display()
# 应用到当前效果
if self.auto_update:
self.apply_changes()
print("✓ 配置加载完成")
except Exception as e:
print(f"⚠ 配置加载失败: {e}")
def save_preset(self, name: str = "自定义预设") -> bool:
"""保存预设"""
try:
config = self.get_current_config()
# 这里应该调用预设管理器保存
print(f"✓ 保存预设 '{name}':")
print(json.dumps(config, indent=2, default=str))
return True
except Exception as e:
print(f"⚠ 预设保存失败: {e}")
return False
def load_preset(self, name: str = "经典火焰") -> bool:
"""加载预设"""
try:
# 这里应该调用预设管理器加载
# 简化实现:使用默认配置
default_configs = {
"经典火焰": {
'emitter_type': 'fire',
'emission_rate': 50,
'particle_lifetime': 2.0,
'start_color': [1.0, 0.3, 0.0],
'end_color': [1.0, 0.0, 0.0],
'start_size': 0.2,
'end_size': 0.8,
'velocity': [0, 0, 2],
'velocity_variation': 0.5,
'gravity_scale': -0.2,
'emitter_shape': 'circle',
'emitter_radius': 0.3
},
"浓密烟雾": {
'emitter_type': 'smoke',
'emission_rate': 30,
'particle_lifetime': 4.0,
'start_color': [0.8, 0.8, 0.8],
'end_color': [0.3, 0.3, 0.3],
'start_size': 0.3,
'end_size': 1.5,
'velocity': [0, 0, 1],
'velocity_variation': 0.3,
'gravity_scale': -0.1,
'emitter_shape': 'circle',
'emitter_radius': 0.5
}
}
if name in default_configs:
self.load_config(default_configs[name])
print(f"✓ 加载预设 '{name}'")
return True
else:
print(f"⚠ 预设 '{name}' 不存在")
return False
except Exception as e:
print(f"⚠ 预设加载失败: {e}")
return False
def reset_to_defaults(self):
"""重置为默认值"""
if self.current_emitter:
# 重新创建当前类型的发射器
effect_type = self.current_emitter.emitter_type
self.create_new_effect(effect_type.capitalize())
def cleanup(self):
"""清理编辑器资源"""
self.hide()
if self.current_emitter_id:
self.particle_manager.remove_emitter(self.current_emitter_id)
self.current_emitter_id = None
self.current_emitter = None