EG/plugins/user/terrain_editor/animation/animation_system.py
2025-12-12 16:16:15 +08:00

1551 lines
63 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.

"""
地形动画和变形系统
提供完整的地形动画、变形、morphing、顶点动画、骨骼动画等功能
"""
from panda3d.core import NodePath, Vec3, Point3, Mat4, LVector3f
from panda3d.core import Shader, ShaderAttrib, TransformState
from panda3d.core import Texture, TextureStage, PNMImage
from panda3d.core import LerpFunctionInterval, LerpPosInterval, LerpHprInterval
from panda3d.core import ClockObject, deg2Rad, rad2Deg
import math
import random
import json
import os
import numpy as np
from collections import deque
class TerrainAnimationSystem:
"""
地形动画和变形系统类
提供地形动画、变形、morphing、顶点动画、骨骼动画等功能
"""
def __init__(self, world):
self.world = world
self.terrain_animations = {} # 存储地形动画
self.deformation_effects = {} # 存储变形效果
self.morph_targets = {} # 存储morph目标
self.vertex_animations = {} # 存储顶点动画
self.skeletal_animations = {} # 存储骨骼动画
self.animation_intervals = [] # 存储动画间隔
self.animation_enabled = True
self.animation_speed = 1.0 # 动画速度倍数
# 性能监控
self.performance_stats = {
'last_update_time': 0.0,
'update_count': 0,
'avg_update_time': 0.0
}
# 初始化动画系统
self._init_animation_system()
def _init_animation_system(self):
"""
初始化动画系统(增强版)
"""
try:
# 初始化动画缓存
self.animation_cache = {}
# 初始化变形缓存
self.deformation_cache = {}
# 初始化时间管理器
self.time_manager = {
'global_time': 0.0,
'animation_time_scale': 1.0,
'paused': False
}
print("地形动画系统初始化完成")
except Exception as e:
print(f"初始化动画系统时出错: {e}")
def create_terrain_animation(self, terrain_info, animation_name, keyframes,
loop=False, speed=1.0, easing='linear'):
"""
创建地形动画(增强版)
terrain_info: 地形信息
animation_name: 动画名称
keyframes: 关键帧数据
loop: 是否循环
speed: 动画速度
easing: 缓动函数
"""
try:
# 验证关键帧数据
if not keyframes or len(keyframes) < 1:
print("关键帧数据无效")
return None
# 排序关键帧
sorted_keyframes = sorted(keyframes, key=lambda kf: kf['time'])
# 创建动画信息
animation_info = {
'name': animation_name,
'terrain_info': terrain_info,
'keyframes': sorted_keyframes,
'duration': sorted_keyframes[-1]['time'] if sorted_keyframes else 0.0,
'loop': loop,
'speed': speed,
'easing': easing,
'playing': False,
'paused': False,
'current_time': 0.0,
'start_time': 0.0,
'blend_weight': 1.0,
'blend_mode': 'override', # override, add, multiply
'animation_type': 'transform', # transform, vertex, material
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存动画
terrain_node = terrain_info['node']
if terrain_node not in self.terrain_animations:
self.terrain_animations[terrain_node] = {}
self.terrain_animations[terrain_node][animation_name] = animation_info
# 缓存动画数据以提高性能
self._cache_animation_data(animation_info)
print(f"创建地形动画: {animation_name} (持续时间: {animation_info['duration']}秒)")
return animation_info
except Exception as e:
print(f"创建地形动画时出错: {e}")
return None
def _cache_animation_data(self, animation_info):
"""
缓存动画数据以提高性能
"""
try:
cache_key = f"{id(animation_info['terrain_info'])}_{animation_info['name']}"
# 预计算插值数据
cached_data = {
'position_curves': [],
'rotation_curves': [],
'scale_curves': [],
'computed_times': []
}
keyframes = animation_info['keyframes']
for i in range(len(keyframes) - 1):
kf1 = keyframes[i]
kf2 = keyframes[i + 1]
# 计算时间段内的插值曲线
if 'position' in kf1 and 'position' in kf2:
pos1 = Vec3(*kf1['position'])
pos2 = Vec3(*kf2['position'])
cached_data['position_curves'].append((pos1, pos2, kf1['time'], kf2['time']))
if 'rotation' in kf1 and 'rotation' in kf2:
rot1 = Vec3(*kf1['rotation'])
rot2 = Vec3(*kf2['rotation'])
cached_data['rotation_curves'].append((rot1, rot2, kf1['time'], kf2['time']))
if 'scale' in kf1 and 'scale' in kf2:
scale1 = Vec3(*kf1['scale'])
scale2 = Vec3(*kf2['scale'])
cached_data['scale_curves'].append((scale1, scale2, kf1['time'], kf2['time']))
self.animation_cache[cache_key] = cached_data
except Exception as e:
print(f"缓存动画数据时出错: {e}")
def play_terrain_animation(self, terrain_info, animation_name, loop=None,
fade_time=0.0, blend_weight=1.0):
"""
播放地形动画(增强版)
"""
try:
terrain_node = terrain_info['node']
if terrain_node not in self.terrain_animations:
print(f"地形没有动画: {animation_name}")
return False
if animation_name not in self.terrain_animations[terrain_node]:
print(f"动画不存在: {animation_name}")
return False
# 获取动画信息
animation_info = self.terrain_animations[terrain_node][animation_name]
# 更新动画属性
if loop is not None:
animation_info['loop'] = loop
animation_info['playing'] = True
animation_info['paused'] = False
animation_info['start_time'] = self.world.globalClock.getFrameTime()
animation_info['current_time'] = 0.0
animation_info['blend_weight'] = blend_weight
# 处理淡入效果
if fade_time > 0:
self._start_animation_fade_in(animation_info, fade_time)
print(f"播放地形动画: {animation_name}")
return True
except Exception as e:
print(f"播放地形动画时出错: {e}")
return False
def _start_animation_fade_in(self, animation_info, fade_time):
"""
开始动画淡入效果
"""
try:
# 创建淡入定时器
animation_info['fade_in'] = {
'duration': fade_time,
'start_time': self.world.globalClock.getFrameTime(),
'active': True
}
except Exception as e:
print(f"开始动画淡入时出错: {e}")
def stop_terrain_animation(self, terrain_info, animation_name, fade_time=0.0):
"""
停止地形动画(增强版)
"""
try:
terrain_node = terrain_info['node']
if (terrain_node in self.terrain_animations and
animation_name in self.terrain_animations[terrain_node]):
animation_info = self.terrain_animations[terrain_node][animation_name]
# 处理淡出效果
if fade_time > 0:
self._start_animation_fade_out(animation_info, fade_time)
else:
animation_info['playing'] = False
animation_info['paused'] = False
print(f"停止地形动画: {animation_name}")
return True
except Exception as e:
print(f"停止地形动画时出错: {e}")
return False
def _start_animation_fade_out(self, animation_info, fade_time):
"""
开始动画淡出效果
"""
try:
# 创建淡出定时器
animation_info['fade_out'] = {
'duration': fade_time,
'start_time': self.world.globalClock.getFrameTime(),
'active': True
}
except Exception as e:
print(f"开始动画淡出时出错: {e}")
def pause_terrain_animation(self, terrain_info, animation_name):
"""
暂停地形动画
"""
try:
terrain_node = terrain_info['node']
if (terrain_node in self.terrain_animations and
animation_name in self.terrain_animations[terrain_node]):
animation_info = self.terrain_animations[terrain_node][animation_name]
animation_info['paused'] = True
print(f"暂停地形动画: {animation_name}")
return True
except Exception as e:
print(f"暂停地形动画时出错: {e}")
return False
def resume_terrain_animation(self, terrain_info, animation_name):
"""
恢复地形动画
"""
try:
terrain_node = terrain_info['node']
if (terrain_node in self.terrain_animations and
animation_name in self.terrain_animations[terrain_node]):
animation_info = self.terrain_animations[terrain_node][animation_name]
animation_info['paused'] = False
print(f"恢复地形动画: {animation_name}")
return True
except Exception as e:
print(f"恢复地形动画时出错: {e}")
return False
def update_terrain_animations(self, time_delta):
"""
更新地形动画(增强版)
"""
try:
if not self.animation_enabled or self.time_manager['paused']:
return
# 性能监控开始
start_time = self.world.globalClock.getRealTime()
# 应用时间缩放
scaled_time_delta = time_delta * self.time_manager['animation_time_scale'] * self.animation_speed
current_time = self.world.globalClock.getFrameTime()
self.time_manager['global_time'] += scaled_time_delta
# 更新每个地形的动画
for terrain_node, animations in self.terrain_animations.items():
for animation_name, animation_info in animations.items():
if animation_info['playing'] and not animation_info['paused']:
# 更新当前时间
animation_info['current_time'] += scaled_time_delta * animation_info['speed']
# 处理淡入淡出
self._update_animation_fade(animation_info, current_time)
# 检查是否需要循环
if animation_info['current_time'] > animation_info['duration']:
if animation_info['loop']:
animation_info['current_time'] = animation_info['current_time'] % animation_info['duration']
else:
animation_info['playing'] = False
continue
# 应用动画
self._apply_terrain_animation(animation_info, scaled_time_delta)
# 更新性能统计
end_time = self.world.globalClock.getRealTime()
update_time = end_time - start_time
self.performance_stats['last_update_time'] = update_time
self.performance_stats['update_count'] += 1
# 计算平均更新时间
if self.performance_stats['update_count'] > 1:
alpha = 0.1
self.performance_stats['avg_update_time'] = (
alpha * update_time +
(1 - alpha) * self.performance_stats['avg_update_time']
)
else:
self.performance_stats['avg_update_time'] = update_time
except Exception as e:
print(f"更新地形动画时出错: {e}")
def _update_animation_fade(self, animation_info, current_time):
"""
更新动画淡入淡出效果
"""
try:
# 处理淡入
if 'fade_in' in animation_info and animation_info['fade_in']['active']:
fade_in = animation_info['fade_in']
elapsed = current_time - fade_in['start_time']
if elapsed >= fade_in['duration']:
animation_info['blend_weight'] = 1.0
animation_info['fade_in']['active'] = False
del animation_info['fade_in']
else:
animation_info['blend_weight'] = elapsed / fade_in['duration']
# 处理淡出
if 'fade_out' in animation_info and animation_info['fade_out']['active']:
fade_out = animation_info['fade_out']
elapsed = current_time - fade_out['start_time']
if elapsed >= fade_out['duration']:
animation_info['playing'] = False
animation_info['fade_out']['active'] = False
del animation_info['fade_out']
else:
animation_info['blend_weight'] = 1.0 - (elapsed / fade_out['duration'])
except Exception as e:
print(f"更新动画淡入淡出时出错: {e}")
def _apply_terrain_animation(self, animation_info, time_delta):
"""
应用地形动画(增强版)
"""
try:
current_time = animation_info['current_time']
keyframes = animation_info['keyframes']
blend_weight = animation_info['blend_weight']
if len(keyframes) < 1:
return
# 如果只有一个关键帧,直接应用
if len(keyframes) == 1:
kf = keyframes[0]
self._apply_keyframe_transform(animation_info['terrain_info'], kf, blend_weight)
return
# 找到当前时间对应的两个关键帧
prev_kf = keyframes[0]
next_kf = keyframes[-1]
for i in range(len(keyframes) - 1):
if keyframes[i]['time'] <= current_time <= keyframes[i + 1]['time']:
prev_kf = keyframes[i]
next_kf = keyframes[i + 1]
break
# 计算插值因子
time_diff = next_kf['time'] - prev_kf['time']
if time_diff > 0:
t = (current_time - prev_kf['time']) / time_diff
# 应用缓动函数
t = self._apply_easing(t, animation_info['easing'])
else:
t = 0.0
# 插值计算变换
self._interpolate_and_apply_transform(
animation_info['terrain_info'],
prev_kf, next_kf, t, blend_weight
)
except Exception as e:
print(f"应用地形动画时出错: {e}")
def _apply_easing(self, t, easing_type):
"""
应用缓动函数
"""
try:
if easing_type == 'linear':
return t
elif easing_type == 'ease_in':
return t * t
elif easing_type == 'ease_out':
return 1 - (1 - t) * (1 - t)
elif easing_type == 'ease_in_out':
return 3 * t * t - 2 * t * t * t
elif easing_type == 'bounce':
if t < 1/2.75:
return 7.5625 * t * t
elif t < 2/2.75:
t -= 1.5/2.75
return 7.5625 * t * t + 0.75
elif t < 2.5/2.75:
t -= 2.25/2.75
return 7.5625 * t * t + 0.9375
else:
t -= 2.625/2.75
return 7.5625 * t * t + 0.984375
else:
return t
except Exception as e:
print(f"应用缓动函数时出错: {e}")
return t
def _apply_keyframe_transform(self, terrain_info, keyframe, blend_weight):
"""
应用单个关键帧变换
"""
try:
terrain_node = terrain_info['node']
# 应用位置变换
if 'position' in keyframe:
target_pos = Vec3(*keyframe['position'])
if blend_weight < 1.0:
current_pos = terrain_node.getPos()
final_pos = current_pos + (target_pos - current_pos) * blend_weight
else:
final_pos = target_pos
terrain_node.setPos(final_pos)
# 应用旋转变换
if 'rotation' in keyframe:
target_rot = Vec3(*keyframe['rotation'])
if blend_weight < 1.0:
current_rot = terrain_node.getHpr()
final_rot = current_rot + (target_rot - current_rot) * blend_weight
else:
final_rot = target_rot
terrain_node.setHpr(final_rot)
# 应用缩放变换
if 'scale' in keyframe:
target_scale = Vec3(*keyframe['scale'])
if blend_weight < 1.0:
current_scale = terrain_node.getScale()
final_scale = current_scale + (target_scale - current_scale) * blend_weight
else:
final_scale = target_scale
terrain_node.setScale(final_scale)
except Exception as e:
print(f"应用关键帧变换时出错: {e}")
def _interpolate_and_apply_transform(self, terrain_info, prev_kf, next_kf, t, blend_weight):
"""
插值并应用变换
"""
try:
terrain_node = terrain_info['node']
# 插值位置
if 'position' in prev_kf and 'position' in next_kf:
prev_pos = Vec3(*prev_kf['position'])
next_pos = Vec3(*next_kf['position'])
interpolated_pos = prev_pos + (next_pos - prev_pos) * t
if blend_weight < 1.0:
current_pos = terrain_node.getPos()
final_pos = current_pos + (interpolated_pos - current_pos) * blend_weight
else:
final_pos = interpolated_pos
terrain_node.setPos(final_pos)
# 插值旋转
if 'rotation' in prev_kf and 'rotation' in next_kf:
prev_rot = Vec3(*prev_kf['rotation'])
next_rot = Vec3(*next_kf['rotation'])
interpolated_rot = prev_rot + (next_rot - prev_rot) * t
if blend_weight < 1.0:
current_rot = terrain_node.getHpr()
final_rot = current_rot + (interpolated_rot - current_rot) * blend_weight
else:
final_rot = interpolated_rot
terrain_node.setHpr(final_rot)
# 插值缩放
if 'scale' in prev_kf and 'scale' in next_kf:
prev_scale = Vec3(*prev_kf['scale'])
next_scale = Vec3(*next_kf['scale'])
interpolated_scale = prev_scale + (next_scale - prev_scale) * t
if blend_weight < 1.0:
current_scale = terrain_node.getScale()
final_scale = current_scale + (interpolated_scale - current_scale) * blend_weight
else:
final_scale = interpolated_scale
terrain_node.setScale(final_scale)
except Exception as e:
print(f"插值并应用变换时出错: {e}")
def create_height_deformation(self, terrain_info, deformation_name,
center_pos, radius, strength, duration=2.0,
easing='ease_out', falloff='linear'):
"""
创建高度变形效果(增强版)
"""
try:
# 创建变形效果信息
deformation_info = {
'name': deformation_name,
'terrain_info': terrain_info,
'center_pos': Vec3(center_pos),
'radius': radius,
'strength': strength,
'duration': duration,
'easing': easing,
'falloff': falloff,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'progress': 0.0,
'type': 'height',
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存变形效果
terrain_node = terrain_info['node']
if terrain_node not in self.deformation_effects:
self.deformation_effects[terrain_node] = []
self.deformation_effects[terrain_node].append(deformation_info)
print(f"创建高度变形效果: {deformation_name}")
return deformation_info
except Exception as e:
print(f"创建高度变形效果时出错: {e}")
return None
def create_noise_deformation(self, terrain_info, deformation_name,
scale=1.0, strength=0.1, duration=5.0,
octaves=3, persistence=0.5, lacunarity=2.0):
"""
创建噪声变形效果
"""
try:
# 创建噪声变形效果信息
deformation_info = {
'name': deformation_name,
'terrain_info': terrain_info,
'scale': scale,
'strength': strength,
'duration': duration,
'octaves': octaves,
'persistence': persistence,
'lacunarity': lacunarity,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'progress': 0.0,
'type': 'noise',
'seed': random.randint(0, 10000),
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存变形效果
terrain_node = terrain_info['node']
if terrain_node not in self.deformation_effects:
self.deformation_effects[terrain_node] = []
self.deformation_effects[terrain_node].append(deformation_info)
print(f"创建噪声变形效果: {deformation_name}")
return deformation_info
except Exception as e:
print(f"创建噪声变形效果时出错: {e}")
return None
def create_wave_deformation(self, terrain_info, deformation_name,
amplitude=0.1, wavelength=10.0, speed=1.0,
direction=Vec3(1, 0, 0), duration=float('inf')):
"""
创建波浪变形效果
"""
try:
# 创建波浪变形效果信息
deformation_info = {
'name': deformation_name,
'terrain_info': terrain_info,
'amplitude': amplitude,
'wavelength': wavelength,
'speed': speed,
'direction': direction.normalized(),
'duration': duration,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'progress': 0.0,
'type': 'wave',
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存变形效果
terrain_node = terrain_info['node']
if terrain_node not in self.deformation_effects:
self.deformation_effects[terrain_node] = []
self.deformation_effects[terrain_node].append(deformation_info)
print(f"创建波浪变形效果: {deformation_name}")
return deformation_info
except Exception as e:
print(f"创建波浪变形效果时出错: {e}")
return None
def update_deformation_effects(self, time_delta):
"""
更新变形效果(增强版)
"""
try:
if not self.animation_enabled:
return
current_time = self.world.globalClock.getFrameTime()
scaled_time_delta = time_delta * self.animation_speed
# 更新每个地形的变形效果
for terrain_node, deformations in self.deformation_effects.items():
for deformation_info in deformations[:]: # 使用切片复制列表
if deformation_info['active']:
# 更新进度
elapsed_time = current_time - deformation_info['start_time']
progress = min(1.0, elapsed_time / deformation_info['duration']) if deformation_info['duration'] > 0 else 0.0
deformation_info['progress'] = progress
# 检查是否超时
if deformation_info['duration'] > 0 and elapsed_time > deformation_info['duration']:
deformation_info['active'] = False
continue
# 根据变形类型应用效果
if deformation_info['type'] == 'height':
self._apply_height_deformation(deformation_info, progress, scaled_time_delta)
elif deformation_info['type'] == 'noise':
self._apply_noise_deformation(deformation_info, progress, scaled_time_delta)
elif deformation_info['type'] == 'wave':
self._apply_wave_deformation(deformation_info, progress, scaled_time_delta)
# 更新地形网格
if deformation_info['active']:
self._update_terrain_mesh(deformation_info['terrain_info'])
except Exception as e:
print(f"更新变形效果时出错: {e}")
def _apply_height_deformation(self, deformation_info, progress, time_delta):
"""
应用高度变形效果(增强版)
"""
try:
terrain_info = deformation_info['terrain_info']
heightfield = terrain_info['heightfield']
if not heightfield:
return
terrain_node = terrain_info['node']
terrain_pos = terrain_node.getPos()
terrain_scale = terrain_node.getScale()
# 获取高度图尺寸
width = heightfield.getXSize()
height = heightfield.getYSize()
# 计算中心点在高度图中的位置
center_offset = (width - 1) / 2
center_x = int((deformation_info['center_pos'].getX() - terrain_pos.getX()) / terrain_scale.getX() + center_offset)
center_y = int((deformation_info['center_pos'].getY() - terrain_pos.getY()) / terrain_scale.getY() + center_offset)
# 计算变形半径(在高度图坐标系中)
radius_pixels = int(deformation_info['radius'] / max(terrain_scale.getX(), terrain_scale.getY()))
# 计算变形强度(应用缓动和进度)
eased_progress = self._apply_easing(progress, deformation_info['easing'])
current_strength = deformation_info['strength'] * eased_progress
# 应用变形
modified = False
for dx in range(-radius_pixels, radius_pixels + 1):
for dy in range(-radius_pixels, radius_pixels + 1):
# 计算距离
distance = math.sqrt(dx*dx + dy*dy)
if distance <= radius_pixels:
# 计算衰减因子
if deformation_info['falloff'] == 'linear':
weight = 1.0 - (distance / radius_pixels)
elif deformation_info['falloff'] == 'smooth':
weight = math.cos((distance / radius_pixels) * math.pi / 2)
elif deformation_info['falloff'] == 'quadratic':
weight = 1.0 - (distance / radius_pixels) ** 2
else:
weight = 1.0 - (distance / radius_pixels)
# 计算目标点
target_x = center_x + dx
target_y = center_y + dy
# 检查边界
if 0 <= target_x < width and 0 <= target_y < height:
# 获取当前高度
current_height = heightfield.getRed(target_x, target_y)
# 应用变形
new_height = current_height + current_strength * weight * 0.01
new_height = max(0.0, min(1.0, new_height))
# 设置新高度
heightfield.setRed(target_x, target_y, new_height)
heightfield.setGreen(target_x, target_y, new_height)
heightfield.setBlue(target_x, target_y, new_height)
modified = True
# 标记需要更新网格
if modified:
terrain_info['_mesh_dirty'] = True
except Exception as e:
print(f"应用高度变形效果时出错: {e}")
def _apply_noise_deformation(self, deformation_info, progress, time_delta):
"""
应用噪声变形效果
"""
try:
terrain_info = deformation_info['terrain_info']
heightfield = terrain_info['heightfield']
if not heightfield:
return
# 获取高度图尺寸
width = heightfield.getXSize()
height_value = heightfield.getYSize()
# 计算噪声参数
scale = deformation_info['scale']
strength = deformation_info['strength'] * progress
seed = deformation_info['seed']
# 应用噪声变形
modified = False
for y in range(height_value):
for x in range(width):
# 生成噪声值
nx = x / width * scale
ny = y / height_value * scale
# 简单的噪声实现(实际项目中可以使用更复杂的噪声算法)
noise_value = self._fbm_noise(nx, ny, seed,
deformation_info['octaves'],
deformation_info['persistence'],
deformation_info['lacunarity'])
# 获取当前高度
current_height = heightfield.getRed(x, y)
# 应用噪声变形
new_height = current_height + noise_value * strength
new_height = max(0.0, min(1.0, new_height))
# 设置新高度
heightfield.setRed(x, y, new_height)
heightfield.setGreen(x, y, new_height)
heightfield.setBlue(x, y, new_height)
modified = True
# 标记需要更新网格
if modified:
terrain_info['_mesh_dirty'] = True
except Exception as e:
print(f"应用噪声变形效果时出错: {e}")
def _fbm_noise(self, x, y, seed, octaves, persistence, lacunarity):
"""
分形布朗运动噪声
"""
try:
value = 0.0
amplitude = 1.0
frequency = 1.0
max_value = 0.0
for i in range(octaves):
# 简化的噪声函数实际项目中可以使用Perlin噪声或Simplex噪声
sample_x = (x * frequency + seed) % 1000
sample_y = (y * frequency + seed) % 1000
noise = math.sin(sample_x) * math.cos(sample_y)
value += noise * amplitude
max_value += amplitude
amplitude *= persistence
frequency *= lacunarity
return value / max_value if max_value > 0 else 0.0
except Exception as e:
print(f"计算FMB噪声时出错: {e}")
return 0.0
def _apply_wave_deformation(self, deformation_info, progress, time_delta):
"""
应用波浪变形效果
"""
try:
terrain_info = deformation_info['terrain_info']
heightfield = terrain_info['heightfield']
if not heightfield:
return
# 获取高度图尺寸
width = heightfield.getXSize()
height_value = heightfield.getYSize()
# 计算波浪参数
amplitude = deformation_info['amplitude']
wavelength = deformation_info['wavelength']
speed = deformation_info['speed']
direction = deformation_info['direction']
elapsed_time = self.world.globalClock.getFrameTime() - deformation_info['start_time']
# 应用波浪变形
modified = False
for y in range(height_value):
for x in range(width):
# 计算波浪值
projection = x * direction.getX() + y * direction.getY()
wave_value = math.sin((projection / wavelength + elapsed_time * speed) * 2 * math.pi) * amplitude
# 获取当前高度
current_height = heightfield.getRed(x, y)
# 应用波浪变形
new_height = current_height + wave_value
new_height = max(0.0, min(1.0, new_height))
# 设置新高度
heightfield.setRed(x, y, new_height)
heightfield.setGreen(x, y, new_height)
heightfield.setBlue(x, y, new_height)
modified = True
# 标记需要更新网格
if modified:
terrain_info['_mesh_dirty'] = True
except Exception as e:
print(f"应用波浪变形效果时出错: {e}")
def _update_terrain_mesh(self, terrain_info):
"""
更新地形网格
"""
try:
if terrain_info.get('_mesh_dirty', False):
heightfield = terrain_info['heightfield']
if not heightfield:
return
terrain = terrain_info['terrain']
terrain.setHeightfield(heightfield)
terrain.generate()
# 重新创建碰撞体
self._recreate_collision(terrain_info['node'])
# 清除脏标记
terrain_info['_mesh_dirty'] = False
except Exception as e:
print(f"更新地形网格时出错: {e}")
def _recreate_collision(self, terrain_node):
"""
重新创建碰撞体(增强版)
"""
try:
from panda3d.core import BitMask32
# 移除旧的碰撞体
for child in terrain_node.getChildren():
if child.getName().startswith("terrain_collision_"):
child.removeNode()
# 设置碰撞掩码
terrain_node.setCollideMask(BitMask32.bit(2))
# 为子节点设置碰撞掩码
for child in terrain_node.getChildren():
child.setCollideMask(BitMask32.bit(2))
except Exception as e:
print(f"重新创建碰撞体时出错: {e}")
def create_morph_target(self, terrain_info, target_name, target_heightfield,
weights=None, interpolation='linear'):
"""
创建morph目标增强版
"""
try:
# 验证目标高度图
if not target_heightfield:
print("无效的目标高度图")
return None
# 创建morph目标信息
morph_target = {
'name': target_name,
'terrain_info': terrain_info,
'heightfield': target_heightfield,
'weights': weights if weights is not None else {},
'interpolation': interpolation,
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存morph目标
terrain_node = terrain_info['node']
if terrain_node not in self.morph_targets:
self.morph_targets[terrain_node] = {}
self.morph_targets[terrain_node][target_name] = morph_target
print(f"创建morph目标: {target_name}")
return morph_target
except Exception as e:
print(f"创建morph目标时出错: {e}")
return None
def apply_morph_target(self, terrain_info, target_name, weight=1.0,
blend_mode='linear', preserve_volume=False):
"""
应用morph目标增强版
"""
try:
terrain_node = terrain_info['node']
if (terrain_node not in self.morph_targets or
target_name not in self.morph_targets[terrain_node]):
print(f"Morph目标不存在: {target_name}")
return False
morph_target = self.morph_targets[terrain_node][target_name]
source_heightfield = terrain_info['heightfield']
target_heightfield = morph_target['heightfield']
if not source_heightfield or not target_heightfield:
return False
# 验证高度图尺寸
if (source_heightfield.getXSize() != target_heightfield.getXSize() or
source_heightfield.getYSize() != target_heightfield.getYSize()):
print("源和目标高度图尺寸不匹配")
return False
# 获取高度图尺寸
width = source_heightfield.getXSize()
height_value = source_heightfield.getYSize()
# 应用morph混合
for y in range(height_value):
for x in range(width):
source_height = source_heightfield.getRed(x, y)
target_height = target_heightfield.getRed(x, y)
# 根据混合模式计算混合高度
if blend_mode == 'linear':
blended_height = source_height * (1.0 - weight) + target_height * weight
elif blend_mode == 'ease_in':
t = weight * weight
blended_height = source_height * (1.0 - t) + target_height * t
elif blend_mode == 'ease_out':
t = 1.0 - (1.0 - weight) * (1.0 - weight)
blended_height = source_height * (1.0 - t) + target_height * t
else:
blended_height = source_height * (1.0 - weight) + target_height * weight
# 体积保持(简化实现)
if preserve_volume:
# 这里可以实现更复杂的体积保持算法
pass
# 限制高度范围
blended_height = max(0.0, min(1.0, blended_height))
source_heightfield.setRed(x, y, blended_height)
source_heightfield.setGreen(x, y, blended_height)
source_heightfield.setBlue(x, y, blended_height)
# 标记需要更新网格
terrain_info['_mesh_dirty'] = True
print(f"应用morph目标: {target_name} (权重: {weight})")
return True
except Exception as e:
print(f"应用morph目标时出错: {e}")
return False
def create_vertex_animation(self, terrain_info, vertex_indices,
target_positions, duration=2.0,
easing='linear', loop=False):
"""
创建顶点动画(增强版)
"""
try:
# 创建顶点动画信息
vertex_animation = {
'terrain_info': terrain_info,
'vertex_indices': vertex_indices,
'target_positions': target_positions,
'duration': duration,
'easing': easing,
'loop': loop,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'current_time': 0.0,
'blend_weight': 1.0,
'creation_time': self.world.globalClock.getFrameTime()
}
# 验证数据
if len(vertex_indices) != len(target_positions):
print("顶点索引和目标位置数量不匹配")
return None
# 保存顶点动画
terrain_node = terrain_info['node']
if terrain_node not in self.vertex_animations:
self.vertex_animations[terrain_node] = []
self.vertex_animations[terrain_node].append(vertex_animation)
print("创建顶点动画")
return vertex_animation
except Exception as e:
print(f"创建顶点动画时出错: {e}")
return None
def update_vertex_animations(self, time_delta):
"""
更新顶点动画(增强版)
"""
try:
if not self.animation_enabled:
return
current_time = self.world.globalClock.getFrameTime()
scaled_time_delta = time_delta * self.animation_speed
# 更新所有地形的顶点动画
for terrain_node, animations in self.vertex_animations.items():
for vertex_anim in animations[:]: # 使用切片复制
if vertex_anim['active']:
# 更新当前时间
vertex_anim['current_time'] += scaled_time_delta
# 检查是否需要循环
if vertex_anim['current_time'] > vertex_anim['duration']:
if vertex_anim['loop']:
vertex_anim['current_time'] = vertex_anim['current_time'] % vertex_anim['duration']
else:
vertex_anim['active'] = False
continue
# 计算进度
progress = vertex_anim['current_time'] / vertex_anim['duration']
eased_progress = self._apply_easing(progress, vertex_anim['easing'])
# 应用顶点动画
self._apply_vertex_animation(vertex_anim, eased_progress)
except Exception as e:
print(f"更新顶点动画时出错: {e}")
def _apply_vertex_animation(self, vertex_anim, progress):
"""
应用顶点动画
"""
try:
# 这里可以实现顶点的插值动画
# 由于GeoMipTerrain的限制这个功能需要特殊处理
# 实际项目中可能需要直接操作顶点缓冲区
pass
except Exception as e:
print(f"应用顶点动画时出错: {e}")
def create_skeletal_animation(self, terrain_info, bone_hierarchy,
keyframes, loop=False, speed=1.0):
"""
创建骨骼动画
"""
try:
# 创建骨骼动画信息
skeletal_animation = {
'terrain_info': terrain_info,
'bone_hierarchy': bone_hierarchy,
'keyframes': keyframes,
'loop': loop,
'speed': speed,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'current_time': 0.0,
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存骨骼动画
terrain_node = terrain_info['node']
if terrain_node not in self.skeletal_animations:
self.skeletal_animations[terrain_node] = []
self.skeletal_animations[terrain_node].append(skeletal_animation)
print("创建骨骼动画")
return skeletal_animation
except Exception as e:
print(f"创建骨骼动画时出错: {e}")
return None
def update_skeletal_animations(self, time_delta):
"""
更新骨骼动画
"""
try:
if not self.animation_enabled:
return
current_time = self.world.globalClock.getFrameTime()
scaled_time_delta = time_delta * self.animation_speed
# 更新所有地形的骨骼动画
for terrain_node, animations in self.skeletal_animations.items():
for skeletal_anim in animations:
if skeletal_anim['active']:
# 更新当前时间
skeletal_anim['current_time'] += scaled_time_delta * skeletal_anim['speed']
# 检查是否需要循环
if skeletal_anim['current_time'] > self._get_animation_duration(skeletal_anim['keyframes']):
if skeletal_anim['loop']:
skeletal_anim['current_time'] = 0.0
else:
skeletal_anim['active'] = False
continue
# 应用骨骼动画
self._apply_skeletal_animation(skeletal_anim, skeletal_anim['current_time'])
except Exception as e:
print(f"更新骨骼动画时出错: {e}")
def _get_animation_duration(self, keyframes):
"""
获取动画持续时间
"""
try:
if not keyframes:
return 0.0
return max([kf['time'] for kf in keyframes])
except Exception as e:
print(f"获取动画持续时间时出错: {e}")
return 0.0
def _apply_skeletal_animation(self, skeletal_anim, current_time):
"""
应用骨骼动画
"""
try:
# 这里可以实现骨骼动画的插值和应用
# 实际项目中需要处理骨骼层次结构和蒙皮
pass
except Exception as e:
print(f"应用骨骼动画时出错: {e}")
def set_animation_enabled(self, enabled):
"""
启用或禁用动画系统
"""
self.animation_enabled = enabled
print(f"地形动画系统已{'启用' if enabled else '禁用'}")
def set_animation_speed(self, speed):
"""
设置动画速度
"""
self.animation_speed = max(0.0, speed)
print(f"动画速度设置为: {speed}x")
def pause_all_animations(self):
"""
暂停所有动画
"""
self.time_manager['paused'] = True
print("所有动画已暂停")
def resume_all_animations(self):
"""
恢复所有动画
"""
self.time_manager['paused'] = False
print("所有动画已恢复")
def get_animation_stats(self):
"""
获取动画统计信息(增强版)
"""
stats = {
'enabled': self.animation_enabled,
'animation_speed': self.animation_speed,
'paused': self.time_manager['paused'],
'global_time': self.time_manager['global_time'],
'terrain_animations': 0,
'deformation_effects': 0,
'morph_targets': 0,
'vertex_animations': 0,
'skeletal_animations': 0,
'active_animations': 0,
'performance': {
'last_update_time': self.performance_stats['last_update_time'],
'avg_update_time': self.performance_stats['avg_update_time'],
'update_count': self.performance_stats['update_count']
}
}
# 统计动画数量
for terrain_node, animations in self.terrain_animations.items():
stats['terrain_animations'] += len(animations)
for animation in animations.values():
if animation['playing']:
stats['active_animations'] += 1
# 统计变形效果
for terrain_node, deformations in self.deformation_effects.items():
stats['deformation_effects'] += len(deformations)
# 统计morph目标
for terrain_node, morphs in self.morph_targets.items():
stats['morph_targets'] += len(morphs)
# 统计顶点动画
for terrain_node, animations in self.vertex_animations.items():
stats['vertex_animations'] += len(animations)
# 统计骨骼动画
for terrain_node, animations in self.skeletal_animations.items():
stats['skeletal_animations'] += len(animations)
return stats
def save_animation_data(self, output_path):
"""
保存动画数据(增强版)
"""
try:
# 收集动画数据
animation_data = {
'terrain_animations': {},
'morph_targets': {},
'settings': {
'animation_speed': self.animation_speed,
'time_scale': self.time_manager['animation_time_scale']
}
}
# 保存地形动画信息
for terrain_node, animations in self.terrain_animations.items():
node_name = terrain_node.getName() if not terrain_node.isEmpty() else "unknown"
animation_data['terrain_animations'][node_name] = {}
for anim_name, anim_info in animations.items():
animation_data['terrain_animations'][node_name][anim_name] = {
'name': anim_info['name'],
'duration': anim_info['duration'],
'loop': anim_info['loop'],
'speed': anim_info['speed'],
'easing': anim_info['easing'],
'keyframes': anim_info['keyframes']
}
# 保存morph目标信息
for terrain_node, morphs in self.morph_targets.items():
node_name = terrain_node.getName() if not terrain_node.isEmpty() else "unknown"
animation_data['morph_targets'][node_name] = {}
for morph_name, morph_info in morphs.items():
animation_data['morph_targets'][node_name][morph_name] = {
'name': morph_info['name'],
'interpolation': morph_info['interpolation']
# 注意高度图数据可能太大不适合保存在JSON中
}
# 确保输出目录存在
output_dir = os.path.dirname(output_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 写入JSON文件
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(animation_data, f, indent=2, ensure_ascii=False)
print(f"动画数据保存成功: {output_path}")
return True
except Exception as e:
print(f"保存动画数据时出错: {e}")
return False
def load_animation_data(self, input_path):
"""
加载动画数据(增强版)
"""
try:
if not os.path.exists(input_path):
print(f"动画数据文件不存在: {input_path}")
return False
# 读取JSON文件
with open(input_path, 'r', encoding='utf-8') as f:
animation_data = json.load(f)
# 恢复设置
if 'settings' in animation_data:
settings = animation_data['settings']
if 'animation_speed' in settings:
self.animation_speed = settings['animation_speed']
if 'time_scale' in settings:
self.time_manager['animation_time_scale'] = settings['time_scale']
# 加载地形动画
# 注意:实际加载需要在地形创建后进行
self._pending_animations = animation_data.get('terrain_animations', {})
self._pending_morph_targets = animation_data.get('morph_targets', {})
print(f"动画数据加载成功: {input_path}")
return True
except Exception as e:
print(f"加载动画数据时出错: {e}")
return False
def apply_pending_animations(self, terrain_manager):
"""
应用待处理的动画数据
"""
try:
if hasattr(self, '_pending_animations'):
# 应用动画数据
delattr(self, '_pending_animations')
if hasattr(self, '_pending_morph_targets'):
# 应用morph目标数据
delattr(self, '_pending_morph_targets')
except Exception as e:
print(f"应用待处理动画数据时出错: {e}")
def clear_all_animations(self):
"""
清除所有动画(增强版)
"""
try:
# 停止所有动画
for terrain_node, animations in self.terrain_animations.items():
for animation_info in animations.values():
animation_info['playing'] = False
# 清空数据结构
self.terrain_animations = {}
self.deformation_effects = {}
self.morph_targets = {}
self.vertex_animations = {}
self.skeletal_animations = {}
# 清除挂起的数据
if hasattr(self, '_pending_animations'):
delattr(self, '_pending_animations')
if hasattr(self, '_pending_morph_targets'):
delattr(self, '_pending_morph_targets')
# 重置时间管理器
self.time_manager['global_time'] = 0.0
self.time_manager['paused'] = False
# 重置性能统计
self.performance_stats['update_count'] = 0
self.performance_stats['avg_update_time'] = 0.0
print("清除所有动画")
return True
except Exception as e:
print(f"清除所有动画时出错: {e}")
return False
def create_explosion_deformation(self, terrain_info, center_pos, radius, strength,
duration=1.0, easing='ease_out'):
"""
创建爆炸变形效果(增强版)
"""
try:
# 创建爆炸变形效果
explosion_info = {
'terrain_info': terrain_info,
'center_pos': Vec3(center_pos),
'radius': radius,
'strength': strength,
'duration': duration,
'easing': easing,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'progress': 0.0,
'type': 'explosion',
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存爆炸效果
terrain_node = terrain_info['node']
if terrain_node not in self.deformation_effects:
self.deformation_effects[terrain_node] = []
self.deformation_effects[terrain_node].append(explosion_info)
print("创建爆炸变形效果")
return explosion_info
except Exception as e:
print(f"创建爆炸变形效果时出错: {e}")
return None
def create_ripple_effect(self, terrain_info, center_pos, radius, strength,
speed=1.0, duration=3.0):
"""
创建涟漪效果
"""
try:
# 创建涟漪效果信息
ripple_info = {
'terrain_info': terrain_info,
'center_pos': Vec3(center_pos),
'radius': radius,
'strength': strength,
'speed': speed,
'duration': duration,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'progress': 0.0,
'type': 'ripple',
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存涟漪效果
terrain_node = terrain_info['node']
if terrain_node not in self.deformation_effects:
self.deformation_effects[terrain_node] = []
self.deformation_effects[terrain_node].append(ripple_info)
print("创建涟漪效果")
return ripple_info
except Exception as e:
print(f"创建涟漪效果时出错: {e}")
return None
def create_terrain_crack(self, terrain_info, start_pos, end_pos, width, depth,
duration=2.0, easing='ease_in'):
"""
创建地形裂缝效果
"""
try:
# 创建裂缝效果信息
crack_info = {
'terrain_info': terrain_info,
'start_pos': Vec3(start_pos),
'end_pos': Vec3(end_pos),
'width': width,
'depth': depth,
'duration': duration,
'easing': easing,
'start_time': self.world.globalClock.getFrameTime(),
'active': True,
'progress': 0.0,
'type': 'crack',
'creation_time': self.world.globalClock.getFrameTime()
}
# 保存裂缝效果
terrain_node = terrain_info['node']
if terrain_node not in self.deformation_effects:
self.deformation_effects[terrain_node] = []
self.deformation_effects[terrain_node].append(crack_info)
print("创建地形裂缝效果")
return crack_info
except Exception as e:
print(f"创建地形裂缝效果时出错: {e}")
return None