676 lines
23 KiB
Python
676 lines
23 KiB
Python
"""
|
||
水体表面模块
|
||
负责水体表面的几何生成和动态更新
|
||
"""
|
||
|
||
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}") |