EG/plugins/user/fluid_simulation/water/water_surface.py
2025-12-12 16:16:15 +08:00

676 lines
23 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 time
from typing import Dict, Any, List, Optional, Tuple
import math
import numpy as np
class WaterSurface:
"""
水体表面管理器
负责水体表面的几何生成、动态更新和渲染准备
"""
def __init__(self, plugin):
"""
初始化水体表面管理器
Args:
plugin: 水体和流体模拟插件实例
"""
self.plugin = plugin
self.enabled = False
self.initialized = False
# 网格配置
self.mesh_config = {
'resolution_x': 128,
'resolution_z': 128,
'tessellation_level': 1,
'patch_size': 10.0,
'lod_enabled': True,
'lod_distance': 50.0
}
# 表面顶点数据
self.vertices = []
self.normals = []
self.uvs = []
self.indices = []
# 表面高度场
self.height_field = None
self.normal_field = None
# 动态更新配置
self.update_config = {
'update_frequency': 60.0, # Hz
'last_update_time': 0.0,
'needs_update': True
}
# 纹理坐标配置
self.uv_config = {
'scale_u': 1.0,
'scale_v': 1.0,
'scroll_u': 0.0,
'scroll_v': 0.0,
'scroll_speed_u': 0.1,
'scroll_speed_v': 0.05
}
# 几何统计
self.geometry_stats = {
'vertex_count': 0,
'triangle_count': 0,
'patch_count': 0,
'updates_performed': 0
}
# 性能优化
self.optimization_settings = {
'frustum_culling': True,
'occlusion_culling': False,
'dynamic_lod': True,
'vertex_cache': True
}
print("✓ 水体表面管理器已创建")
def initialize(self) -> bool:
"""
初始化水体表面管理器
Returns:
是否初始化成功
"""
try:
# 初始化高度场
self._initialize_height_field()
# 生成初始网格
self._generate_initial_mesh()
self.initialized = True
print("✓ 水体表面管理器初始化完成")
return True
except Exception as e:
print(f"✗ 水体表面管理器初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def enable(self) -> bool:
"""
启用水体表面管理器
Returns:
是否启用成功
"""
try:
if not self.initialized:
print("✗ 水体表面管理器未初始化")
return False
self.enabled = True
print("✓ 水体表面管理器已启用")
return True
except Exception as e:
print(f"✗ 水体表面管理器启用失败: {e}")
import traceback
traceback.print_exc()
return False
def disable(self):
"""禁用水体表面管理器"""
try:
self.enabled = False
print("✓ 水体表面管理器已禁用")
except Exception as e:
print(f"✗ 水体表面管理器禁用失败: {e}")
import traceback
traceback.print_exc()
def finalize(self):
"""清理水体表面管理器资源"""
try:
self.disable()
self._clear_mesh_data()
self.initialized = False
print("✓ 水体表面管理器资源已清理")
except Exception as e:
print(f"✗ 水体表面管理器资源清理失败: {e}")
import traceback
traceback.print_exc()
def update(self, dt: float):
"""
更新水体表面状态
Args:
dt: 时间增量
"""
try:
if not self.enabled:
return
# 更新时间
self.update_config['last_update_time'] += dt
# 检查是否需要更新
update_interval = 1.0 / self.update_config['update_frequency']
if self.update_config['last_update_time'] >= update_interval:
# 更新表面几何
self._update_surface_geometry(dt)
# 更新纹理坐标
self._update_texture_coordinates(dt)
# 重置更新计时器
self.update_config['last_update_time'] = 0.0
self.update_config['needs_update'] = False
# 更新统计信息
self.geometry_stats['updates_performed'] += 1
except Exception as e:
print(f"✗ 水体表面管理器更新失败: {e}")
import traceback
traceback.print_exc()
def _initialize_height_field(self):
"""初始化高度场"""
try:
res_x = self.mesh_config['resolution_x']
res_z = self.mesh_config['resolution_z']
# 初始化高度场
self.height_field = np.zeros((res_x, res_z))
# 初始化法线场
self.normal_field = np.zeros((res_x, res_z, 3))
print("✓ 高度场初始化完成")
except Exception as e:
print(f"✗ 高度场初始化失败: {e}")
raise
def _generate_initial_mesh(self):
"""生成初始网格"""
try:
self._clear_mesh_data()
res_x = self.mesh_config['resolution_x']
res_z = self.mesh_config['resolution_z']
patch_size = self.mesh_config['patch_size']
# 生成顶点
for i in range(res_x):
for j in range(res_z):
x = (i - res_x / 2) * patch_size / res_x
z = (j - res_z / 2) * patch_size / res_z
y = self.height_field[i, j] if self.height_field is not None else 0.0
self.vertices.append((x, y, z))
# 生成UV坐标
u = i / (res_x - 1) * self.uv_config['scale_u']
v = j / (res_z - 1) * self.uv_config['scale_v']
self.uvs.append((u, v))
# 生成索引(三角形列表)
for i in range(res_x - 1):
for j in range(res_z - 1):
# 第一个三角形
self.indices.append(i * res_z + j)
self.indices.append((i + 1) * res_z + j)
self.indices.append(i * res_z + (j + 1))
# 第二个三角形
self.indices.append((i + 1) * res_z + j)
self.indices.append((i + 1) * res_z + (j + 1))
self.indices.append(i * res_z + (j + 1))
# 计算法线
self._calculate_normals()
# 更新统计信息
self.geometry_stats['vertex_count'] = len(self.vertices)
self.geometry_stats['triangle_count'] = len(self.indices) // 3
self.geometry_stats['patch_count'] = (res_x - 1) * (res_z - 1)
print("✓ 初始网格生成完成")
except Exception as e:
print(f"✗ 初始网格生成失败: {e}")
raise
def _clear_mesh_data(self):
"""清理网格数据"""
try:
self.vertices.clear()
self.normals.clear()
self.uvs.clear()
self.indices.clear()
if self.height_field is not None:
self.height_field.fill(0)
if self.normal_field is not None:
self.normal_field.fill(0)
except Exception as e:
print(f"✗ 网格数据清理失败: {e}")
def _update_surface_geometry(self, dt: float):
"""
更新表面几何
Args:
dt: 时间增量
"""
try:
if not self.vertices or self.height_field is None:
return
res_x = self.mesh_config['resolution_x']
res_z = self.mesh_config['resolution_z']
# 更新顶点高度
for i in range(res_x):
for j in range(res_z):
vertex_index = i * res_z + j
if vertex_index < len(self.vertices):
x, y, z = self.vertices[vertex_index]
# 从高度场获取新高度
new_height = self.height_field[i, j]
self.vertices[vertex_index] = (x, new_height, z)
# 重新计算法线
self._calculate_normals()
except Exception as e:
print(f"✗ 表面几何更新失败: {e}")
def _calculate_normals(self):
"""计算顶点法线"""
try:
if not self.vertices or not self.indices:
return
# 清除旧法线
self.normals.clear()
# 初始化法线数组
vertex_normals = [np.array([0.0, 0.0, 0.0]) for _ in self.vertices]
# 计算每个三角形的法线并累加到顶点
for i in range(0, len(self.indices), 3):
i0 = self.indices[i]
i1 = self.indices[i + 1]
i2 = self.indices[i + 2]
# 获取三角形顶点
v0 = np.array(self.vertices[i0])
v1 = np.array(self.vertices[i1])
v2 = np.array(self.vertices[i2])
# 计算边向量
edge1 = v1 - v0
edge2 = v2 - v0
# 计算法线(叉积)
normal = np.cross(edge1, edge2)
# 归一化
normal_length = np.linalg.norm(normal)
if normal_length > 0:
normal = normal / normal_length
# 累加到顶点法线
vertex_normals[i0] += normal
vertex_normals[i1] += normal
vertex_normals[i2] += normal
# 归一化顶点法线并存储
for normal in vertex_normals:
normal_length = np.linalg.norm(normal)
if normal_length > 0:
normal = normal / normal_length
self.normals.append(tuple(normal))
except Exception as e:
print(f"✗ 法线计算失败: {e}")
def _update_texture_coordinates(self, dt: float):
"""
更新纹理坐标
Args:
dt: 时间增量
"""
try:
# 更新滚动偏移
self.uv_config['scroll_u'] += self.uv_config['scroll_speed_u'] * dt
self.uv_config['scroll_v'] += self.uv_config['scroll_speed_v'] * dt
# 保持在[0,1)范围内
self.uv_config['scroll_u'] %= 1.0
self.uv_config['scroll_v'] %= 1.0
# 更新UV坐标
if self.uvs:
res_x = self.mesh_config['resolution_x']
res_z = self.mesh_config['resolution_z']
for i in range(res_x):
for j in range(res_z):
vertex_index = i * res_z + j
if vertex_index < len(self.uvs):
u = (i / (res_x - 1) + self.uv_config['scroll_u']) * self.uv_config['scale_u']
v = (j / (res_z - 1) + self.uv_config['scroll_v']) * self.uv_config['scale_v']
self.uvs[vertex_index] = (u % 1.0, v % 1.0)
except Exception as e:
print(f"✗ 纹理坐标更新失败: {e}")
def update_height_field(self, new_height_field: np.ndarray):
"""
更新高度场
Args:
new_height_field: 新的高度场数据
"""
try:
if self.height_field is None:
self._initialize_height_field()
# 复制新高度场数据
if new_height_field.shape == self.height_field.shape:
self.height_field[:, :] = new_height_field[:, :]
else:
# 如果尺寸不匹配,进行插值
from scipy.interpolate import interp2d
old_x = np.linspace(0, 1, self.height_field.shape[0])
old_y = np.linspace(0, 1, self.height_field.shape[1])
new_x = np.linspace(0, 1, new_height_field.shape[0])
new_y = np.linspace(0, 1, new_height_field.shape[1])
interpolator = interp2d(old_x, old_y, new_height_field, kind='linear')
self.height_field[:, :] = interpolator(old_x, old_y)
# 标记需要更新
self.update_config['needs_update'] = True
except Exception as e:
print(f"✗ 高度场更新失败: {e}")
def generate_wave_pattern(self, wave_parameters: Dict[str, float],
time_value: float) -> np.ndarray:
"""
生成波浪模式
Args:
wave_parameters: 波浪参数字典
time_value: 时间值
Returns:
高度场数组
"""
try:
if self.height_field is None:
self._initialize_height_field()
res_x, res_z = self.height_field.shape
patch_size = self.mesh_config['patch_size']
# 获取波浪参数
amplitude = wave_parameters.get('amplitude', 1.0)
wavelength = wave_parameters.get('wavelength', 10.0)
speed = wave_parameters.get('speed', 1.0)
direction = wave_parameters.get('direction', 0.0) # 弧度
# 计算波向量
wave_vector_x = 2 * math.pi / wavelength * math.cos(direction)
wave_vector_z = 2 * math.pi / wavelength * math.sin(direction)
# 生成波浪
for i in range(res_x):
for j in range(res_z):
# 计算世界坐标
x = (i - res_x / 2) * patch_size / res_x
z = (j - res_z / 2) * patch_size / res_z
# 计算相位
phase = wave_vector_x * x + wave_vector_z * z - speed * time_value
# 计算波浪高度
height = amplitude * math.sin(phase)
self.height_field[i, j] = height
return self.height_field.copy()
except Exception as e:
print(f"✗ 波浪模式生成失败: {e}")
# 返回零高度场
return np.zeros((res_x, res_z)) if self.height_field is not None else np.zeros((1, 1))
def generate_gerstner_waves(self, waves: List[Dict[str, float]],
time_value: float) -> np.ndarray:
"""
生成Gerstner波浪更真实的波浪模型
Args:
waves: 波浪参数列表,每个包含'amplitude', 'wavelength', 'speed', 'direction'
time_value: 时间值
Returns:
高度场数组
"""
try:
if self.height_field is None:
self._initialize_height_field()
res_x, res_z = self.height_field.shape
patch_size = self.mesh_config['patch_size']
# 清零高度场
self.height_field.fill(0)
# 为每个波浪累加贡献
for wave in waves:
amplitude = wave.get('amplitude', 1.0)
wavelength = wave.get('wavelength', 10.0)
speed = wave.get('speed', 1.0)
direction = wave.get('direction', 0.0)
# 计算波数和频率
wavenumber = 2 * math.pi / wavelength
frequency = speed * wavenumber
# 计算波向量
kx = wavenumber * math.cos(direction)
kz = wavenumber * math.sin(direction)
# 为每个点计算波浪贡献
for i in range(res_x):
for j in range(res_z):
# 计算世界坐标
x = (i - res_x / 2) * patch_size / res_x
z = (j - res_z / 2) * patch_size / res_z
# 计算相位
phase = kx * x + kz * z - frequency * time_value
# Gerstner波浪公式
height_contribution = amplitude * math.sin(phase)
self.height_field[i, j] += height_contribution
return self.height_field.copy()
except Exception as e:
print(f"✗ Gerstner波浪生成失败: {e}")
return np.zeros((res_x, res_z)) if self.height_field is not None else np.zeros((1, 1))
def get_mesh_data(self) -> Dict[str, List[Tuple[float, float, float]]]:
"""
获取网格数据
Returns:
包含顶点、法线、UV坐标和索引的字典
"""
return {
'vertices': self.vertices.copy(),
'normals': self.normals.copy(),
'uvs': self.uvs.copy(),
'indices': self.indices.copy()
}
def get_height_at(self, x: float, z: float) -> float:
"""
获取指定位置的高度
Args:
x, z: 世界坐标
Returns:
高度值
"""
try:
if self.height_field is None:
return 0.0
res_x, res_z = self.height_field.shape
patch_size = self.mesh_config['patch_size']
# 转换到网格坐标
grid_x = (x / patch_size + 0.5) * res_x
grid_z = (z / patch_size + 0.5) * res_z
# 边界检查
if grid_x < 0 or grid_x >= res_x or grid_z < 0 or grid_z >= res_z:
return 0.0
# 简单的双线性插值
i = int(grid_x)
j = int(grid_z)
fx = grid_x - i
fz = grid_z - j
# 边界处理
i1 = min(i + 1, res_x - 1)
j1 = min(j + 1, res_z - 1)
# 双线性插值
h00 = self.height_field[i, j]
h10 = self.height_field[i1, j]
h01 = self.height_field[i, j1]
h11 = self.height_field[i1, j1]
h0 = h00 * (1 - fx) + h10 * fx
h1 = h01 * (1 - fx) + h11 * fx
return h0 * (1 - fz) + h1 * fz
except Exception as e:
print(f"✗ 高度获取失败: {e}")
return 0.0
def set_mesh_config(self, config: Dict[str, Any]):
"""
设置网格配置
Args:
config: 网格配置字典
"""
try:
old_res_x = self.mesh_config['resolution_x']
old_res_z = self.mesh_config['resolution_z']
self.mesh_config.update(config)
new_res_x = self.mesh_config['resolution_x']
new_res_z = self.mesh_config['resolution_z']
# 如果分辨率改变,重新生成网格
if old_res_x != new_res_x or old_res_z != new_res_z:
self._generate_initial_mesh()
print(f"✓ 网格配置已更新: {self.mesh_config}")
except Exception as e:
print(f"✗ 网格配置设置失败: {e}")
def get_mesh_config(self) -> Dict[str, Any]:
"""
获取网格配置
Returns:
网格配置字典
"""
return self.mesh_config.copy()
def set_uv_config(self, config: Dict[str, float]):
"""
设置UV配置
Args:
config: UV配置字典
"""
try:
self.uv_config.update(config)
print(f"✓ UV配置已更新: {self.uv_config}")
except Exception as e:
print(f"✗ UV配置设置失败: {e}")
def get_uv_config(self) -> Dict[str, float]:
"""
获取UV配置
Returns:
UV配置字典
"""
return self.uv_config.copy()
def get_geometry_stats(self) -> Dict[str, int]:
"""
获取几何统计信息
Returns:
几何统计字典
"""
return self.geometry_stats.copy()
def reset_geometry_stats(self):
"""重置几何统计信息"""
try:
self.geometry_stats = {
'vertex_count': len(self.vertices),
'triangle_count': len(self.indices) // 3,
'patch_count': (self.mesh_config['resolution_x'] - 1) * (self.mesh_config['resolution_z'] - 1),
'updates_performed': 0
}
print("✓ 几何统计信息已重置")
except Exception as e:
print(f"✗ 几何统计信息重置失败: {e}")
def apply_displacement_map(self, displacement_map: np.ndarray):
"""
应用位移贴图
Args:
displacement_map: 位移贴图数据
"""
try:
self.update_height_field(displacement_map)
print("✓ 位移贴图已应用")
except Exception as e:
print(f"✗ 位移贴图应用失败: {e}")