436 lines
17 KiB
Python
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}") |