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

436 lines
17 KiB
Python

"""
高级地形编辑工具
提供噪声生成、侵蚀模拟、傅里叶地形等高级功能
"""
import math
import random
import numpy as np
from panda3d.core import PNMImage
class AdvancedTerrainTools:
"""
高级地形编辑工具类
提供噪声生成、侵蚀模拟、傅里叶地形等高级功能
"""
def __init__(self, world):
self.world = world
def generate_perlin_noise(self, terrain_info, scale=1.0, octaves=4, persistence=0.5, lacunarity=2.0):
"""
生成Perlin噪声地形
"""
try:
terrain_node = terrain_info['node']
heightfield = terrain_info['heightfield']
if not heightfield:
print("无法获取地形高度图数据")
return False
# 获取高度图尺寸
width = heightfield.getXSize()
height = heightfield.getYSize()
# 生成Perlin噪声
for y in range(height):
for x in range(width):
# 计算噪声坐标
nx = x / width * scale
ny = y / height * scale
# 生成分形噪声
noise_value = self._fractal_noise(nx, ny, octaves, persistence, lacunarity)
# 将噪声值映射到0-1范围
normalized_value = (noise_value + 1) / 2
heightfield.setRed(x, y, normalized_value)
heightfield.setGreen(x, y, normalized_value)
heightfield.setBlue(x, y, normalized_value)
# 更新地形
return self._update_terrain(terrain_info)
except Exception as e:
print(f"生成Perlin噪声时出错: {e}")
return False
def _fractal_noise(self, x, y, octaves, persistence, lacunarity):
"""
生成分形噪声
"""
total = 0.0
frequency = 1.0
amplitude = 1.0
maxValue = 0.0 # 用于归一化
for i in range(octaves):
total += self._perlin_noise(x * frequency, y * frequency) * amplitude
maxValue += amplitude
amplitude *= persistence
frequency *= lacunarity
return total / maxValue
def _perlin_noise(self, x, y):
"""
生成Perlin噪声
"""
# 获取整数部分
X = int(x) & 255
Y = int(y) & 255
# 获取小数部分
x -= int(x)
y -= int(y)
# 计算平滑曲线
u = self._fade(x)
v = self._fade(y)
# 计算梯度
a = self._gradient(X, Y, x, y)
b = self._gradient(X + 1, Y, x - 1, y)
c = self._gradient(X, Y + 1, x, y - 1)
d = self._gradient(X + 1, Y + 1, x - 1, y - 1)
# 插值
return self._lerp(v, self._lerp(u, a, b), self._lerp(u, c, d))
def _fade(self, t):
"""
平滑曲线函数
"""
return t * t * t * (t * (t * 6 - 15) + 10)
def _lerp(self, t, a, b):
"""
线性插值
"""
return a + t * (b - a)
def _gradient(self, hash_val, x, y):
"""
梯度函数
"""
h = hash_val & 15
u = x if h < 8 else y
v = y if h < 4 else (x if h in [12, 14] else 0)
return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v)
def simulate_hydraulic_erosion(self, terrain_info, iterations=100, rainfall=0.1, solubility=0.01, evaporation=0.01):
"""
模拟水力侵蚀
"""
try:
heightfield = terrain_info['heightfield']
if not heightfield:
print("无法获取地形高度图数据")
return False
width = heightfield.getXSize()
height = heightfield.getYSize()
# 初始化水流和沉积物数组
water = np.zeros((width, height))
sediment = np.zeros((width, height))
# 执行侵蚀迭代
for iteration in range(iterations):
# 添加降雨
water += rainfall
# 计算水流
for y in range(height):
for x in range(width):
current_height = heightfield.getRed(x, y) + water[x, y]
# 查找最低邻居
lowest_height = current_height
lowest_x, lowest_y = x, y
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
if 0 <= nx < width and 0 <= ny < height:
neighbor_height = heightfield.getRed(nx, ny) + water[nx, ny]
if neighbor_height < lowest_height:
lowest_height = neighbor_height
lowest_x, lowest_y = nx, ny
# 移动水流
if lowest_x != x or lowest_y != y:
flow_amount = min(water[x, y], (current_height - lowest_height) * 0.5)
water[x, y] -= flow_amount
water[lowest_x, lowest_y] += flow_amount
# 溶解物质
dissolve_amount = flow_amount * solubility
sediment[x, y] -= dissolve_amount
sediment[lowest_x, lowest_y] += dissolve_amount
# 沉积和蒸发
for y in range(height):
for x in range(width):
# 蒸发
evaporation_amount = min(water[x, y], evaporation)
water[x, y] -= evaporation_amount
# 沉积
if sediment[x, y] > 0:
deposit_amount = min(sediment[x, y], 0.01)
sediment[x, y] -= deposit_amount
new_height = heightfield.getRed(x, y) + deposit_amount
heightfield.setRed(x, y, new_height)
heightfield.setGreen(x, y, new_height)
heightfield.setBlue(x, y, new_height)
# 更新地形
return self._update_terrain(terrain_info)
except Exception as e:
print(f"模拟水力侵蚀时出错: {e}")
return False
def generate_voronoi_terrain(self, terrain_info, points=100, feature_size=10.0):
"""
生成Voronoi图地形
"""
try:
heightfield = terrain_info['heightfield']
if not heightfield:
print("无法获取地形高度图数据")
return False
width = heightfield.getXSize()
height = heightfield.getYSize()
# 生成随机点
voronoi_points = []
for i in range(points):
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
height_value = random.random()
voronoi_points.append((x, y, height_value))
# 为每个像素分配最近点的高度
for y in range(height):
for x in range(width):
min_distance = float('inf')
closest_height = 0.0
for px, py, ph in voronoi_points:
distance = math.sqrt((x - px)**2 + (y - py)**2)
if distance < min_distance:
min_distance = distance
closest_height = ph
# 应用特征大小
normalized_distance = min(1.0, min_distance / feature_size)
final_height = closest_height * (1.0 - normalized_distance)
heightfield.setRed(x, y, final_height)
heightfield.setGreen(x, y, final_height)
heightfield.setBlue(x, y, final_height)
# 更新地形
return self._update_terrain(terrain_info)
except Exception as e:
print(f"生成Voronoi地形时出错: {e}")
return False
def generate_fourier_terrain(self, terrain_info, frequency_count=10):
"""
生成傅里叶地形
"""
try:
heightfield = terrain_info['heightfield']
if not heightfield:
print("无法获取地形高度图数据")
return False
width = heightfield.getXSize()
height_value = heightfield.getYSize()
# 初始化高度图
for y in range(height_value):
for x in range(width):
heightfield.setRed(x, y, 0.5)
heightfield.setGreen(x, y, 0.5)
heightfield.setBlue(x, y, 0.5)
# 添加不同频率的波
for i in range(frequency_count):
frequency = 2 ** i
amplitude = 1.0 / frequency
for y in range(height_value):
for x in range(width):
# 添加正弦波
wave_x = math.sin(2 * math.pi * x * frequency / width) * amplitude
wave_y = math.sin(2 * math.pi * y * frequency / height_value) * amplitude
current_height = heightfield.getRed(x, y)
new_height = current_height + (wave_x + wave_y) * 0.1
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)
# 更新地形
return self._update_terrain(terrain_info)
except Exception as e:
print(f"生成傅里叶地形时出错: {e}")
return False
def generate_fault_formation(self, terrain_info, iterations=50, displacement=0.1):
"""
生成断层地形
"""
try:
heightfield = terrain_info['heightfield']
if not heightfield:
print("无法获取地形高度图数据")
return False
width = heightfield.getXSize()
height_value = heightfield.getYSize()
# 执行断层迭代
for i in range(iterations):
# 随机生成断层线
angle = random.random() * 2 * math.pi
center_x = random.randint(0, width - 1)
center_y = random.randint(0, height_value - 1)
# 计算断层方向向量
dx = math.cos(angle)
dy = math.sin(angle)
# 移动断层一侧的点
for y in range(height_value):
for x in range(width):
# 计算点到断层线的距离和方向
distance = (x - center_x) * dx + (y - center_y) * dy
if distance > 0:
# 提升这一侧的高度
current_height = heightfield.getRed(x, y)
new_height = current_height + displacement * (iterations - i) / iterations
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)
# 更新地形
return self._update_terrain(terrain_info)
except Exception as e:
print(f"生成断层地形时出错: {e}")
return False
def generate_thermal_erosion(self, terrain_info, iterations=100, talus_angle=0.1):
"""
生成热侵蚀地形
"""
try:
heightfield = terrain_info['heightfield']
if not heightfield:
print("无法获取地形高度图数据")
return False
width = heightfield.getXSize()
height_value = heightfield.getYSize()
# 执行热侵蚀迭代
for iteration in range(iterations):
# 创建高度变化数组
height_changes = np.zeros((width, height_value))
# 计算每个点的侵蚀
for y in range(height_value):
for x in range(width):
current_height = heightfield.getRed(x, y)
# 检查周围点
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
if 0 <= nx < width and 0 <= ny < height_value:
neighbor_height = heightfield.getRed(nx, ny)
height_diff = current_height - neighbor_height
# 如果高度差超过临界角度,发生滑坡
if height_diff > talus_angle:
# 移动部分物质到邻居
move_amount = (height_diff - talus_angle) * 0.1
height_changes[x, y] -= move_amount
height_changes[nx, ny] += move_amount * 0.5 # 部分沉积
# 应用高度变化
for y in range(height_value):
for x in range(width):
if height_changes[x, y] != 0:
current_height = heightfield.getRed(x, y)
new_height = current_height + height_changes[x, y]
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)
# 更新地形
return self._update_terrain(terrain_info)
except Exception as e:
print(f"生成热侵蚀地形时出错: {e}")
return False
def _update_terrain(self, terrain_info):
"""
更新地形
"""
try:
terrain = terrain_info['terrain']
terrain.setHeightfield(terrain_info['heightfield'])
terrain.generate()
# 重新创建碰撞体
self._recreate_collision(terrain_info['node'])
return True
except Exception as e:
print(f"更新地形时出错: {e}")
return False
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}")