forked from Rowland/EG
# Conflicts: # RenderPipelineFile/config/daytime.yaml # scene/scene_manager.py # ui/main_window.py
661 lines
27 KiB
Python
661 lines
27 KiB
Python
# core/terrain_manager.py
|
||
import time
|
||
import urllib
|
||
|
||
from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath
|
||
from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight
|
||
import os
|
||
|
||
from scene import util
|
||
|
||
|
||
class TerrainManager:
|
||
"""地形管理类"""
|
||
|
||
def __init__(self, world):
|
||
self.world = world
|
||
self.terrains = []
|
||
|
||
# core/terrain_manager.py
|
||
def _get_tree_widget(self):
|
||
"""安全获取树形控件"""
|
||
try:
|
||
if (hasattr(self.world, 'interface_manager') and
|
||
hasattr(self.world.interface_manager, 'treeWidget')):
|
||
return self.world.interface_manager.treeWidget
|
||
except AttributeError:
|
||
pass
|
||
return None
|
||
|
||
def createTerrainFromHeightMap(self, heightmap_path, scale=(1, 1, 1)):
|
||
"""从高度图创建地形"""
|
||
# 检查文件是否存在
|
||
if not os.path.exists(heightmap_path):
|
||
print(f"错误: 高度图文件 {heightmap_path} 不存在")
|
||
return None
|
||
|
||
try:
|
||
print(f"🔆 开始创建高度图地形")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_terrains = []
|
||
try:
|
||
parent_item, parent_node = target_parents[0]
|
||
|
||
# 创建GeoMipTerrain对象
|
||
terrain_name = f"terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||
terrain = GeoMipTerrain(terrain_name)
|
||
|
||
# 加载高度图
|
||
height_image = PNMImage(Filename.fromOsSpecific(heightmap_path))
|
||
|
||
# 检查并调整图像尺寸为2的幂次方加1
|
||
width, height = height_image.getXSize(), height_image.getYSize()
|
||
print(f"原始图像尺寸: {width}x{height}")
|
||
|
||
# 找到最接近的有效尺寸
|
||
valid_sizes = [17, 33, 65, 129, 257, 513, 1025, 2049]
|
||
target_size = 129 # 默认尺寸
|
||
|
||
# 选择最接近的尺寸
|
||
max_dim = max(width, height)
|
||
for size in valid_sizes:
|
||
if size >= max_dim:
|
||
target_size = size
|
||
break
|
||
else:
|
||
target_size = valid_sizes[-1] # 使用最大尺寸
|
||
|
||
# 如果需要,调整图像尺寸
|
||
if width != target_size or height != target_size:
|
||
print(f"调整图像尺寸从 {width}x{height} 到 {target_size}x{target_size}")
|
||
# 使用正确的图像缩放方法
|
||
resized_image = PNMImage(target_size, target_size)
|
||
resized_image.quickFilterFrom(height_image)
|
||
height_image = resized_image
|
||
|
||
# 使用正确的方法设置高度图
|
||
terrain.setHeightfield(height_image)
|
||
|
||
# 设置地形参数
|
||
terrain.setBruteforce(True) # 使用LOD
|
||
|
||
terrain.setBlockSize(32)
|
||
# terrain.setNearFarThreshold(50.0,200.0)
|
||
|
||
# 生成地形
|
||
terrain.generate()
|
||
|
||
# 获取地形节点
|
||
terrain_node = terrain.getRoot()
|
||
|
||
if terrain_node.isEmpty():
|
||
print("错误:无法生成有效的地形节点")
|
||
return None
|
||
|
||
terrain_node.setTag("is_scene_element", "1")
|
||
terrain_node.setTag("tree_item_type", "TERRAIN_NODE")
|
||
|
||
node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}"
|
||
terrain_node.setName(node_name)
|
||
|
||
center_offset = (target_size - 1) / 2
|
||
terrain_node.setPos(-center_offset * scale[0], -center_offset * scale[1], -5)
|
||
|
||
# 设置缩放
|
||
terrain_node.setScale(scale[0], scale[1], scale[2])
|
||
|
||
# 将地形添加到场景中
|
||
terrain_node.reparentTo(parent_node)
|
||
|
||
from panda3d.core import BitMask32
|
||
# 设置地形节点的碰撞掩码
|
||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||
# 为地形的所有子节点也设置碰撞掩码
|
||
for child in terrain_node.getChildren():
|
||
child.setCollideMask(BitMask32.bit(2))
|
||
|
||
# 添加材质
|
||
self._applyTerrainMaterial(terrain_node)
|
||
|
||
terrain_node.setPythonTag("selectable", True)
|
||
|
||
# 保存地形信息(包括高度图的副本)
|
||
terrain_info = {
|
||
'terrain': terrain,
|
||
'node': terrain_node,
|
||
'heightmap': heightmap_path,
|
||
'heightfield': height_image, # 保存高度图副本
|
||
'scale': scale,
|
||
'name': node_name
|
||
}
|
||
|
||
self.terrains.append(terrain_info)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建高度图地形: {terrain_name}")
|
||
|
||
# 在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(terrain_node, parent_item, "TERRAIN_NODE")
|
||
if qt_item:
|
||
created_terrains.append((terrain_node, qt_item))
|
||
else:
|
||
created_terrains.append((terrain_node, None))
|
||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建高度图地形: {str(e)}")
|
||
return None
|
||
|
||
# 处理创建结果
|
||
if not created_terrains:
|
||
print("❌ 没有成功创建任何高度图地形")
|
||
return None
|
||
|
||
# 选中最后创建的光源
|
||
if created_terrains:
|
||
last_light_np, last_qt_item = created_terrains[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
# 更新选择和属性面板
|
||
tree_widget.update_selection_and_properties(last_light_np, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_terrains)} 个高度图地形")
|
||
|
||
# 返回值处理
|
||
if len(created_terrains) == 1:
|
||
return created_terrains[0][0] # 单个光源返回NodePath
|
||
else:
|
||
return [light_np for light_np, _ in created_terrains] # 多个光源返回列表
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建高度图地形过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def createFlatTerrain(self, size=(0.3, 0.3), resolution=129):
|
||
"""创建平面地形"""
|
||
try:
|
||
print(f"🔆 开始创建平面地形")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_terrains = []
|
||
|
||
try:
|
||
parent_item, parent_node = target_parents[0]
|
||
# 确保分辨率是2的幂次方加1 (如129, 257, 513等)
|
||
valid_resolutions = [17, 33, 65, 129, 257, 513, 1025]
|
||
if resolution not in valid_resolutions:
|
||
# 找到最接近的有效分辨率
|
||
closest_res = min(valid_resolutions, key=lambda x: abs(x - resolution))
|
||
print(f"警告: 分辨率 {resolution} 不是有效的地形分辨率,使用 {closest_res}")
|
||
resolution = closest_res
|
||
|
||
# 创建GeoMipTerrain对象
|
||
terrain_name = f"flat_terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||
terrain = GeoMipTerrain(terrain_name)
|
||
|
||
# 创建一个平面高度图,尺寸必须是2^n+1
|
||
height_image = PNMImage(resolution, resolution)
|
||
height_image.fill(0.5) # 设置为中等高度 (0.0-1.0范围)
|
||
|
||
# 使用正确的方法设置高度图
|
||
terrain.setHeightfield(height_image)
|
||
terrain.setBruteforce(True) # 设置LOD
|
||
|
||
# 设置地形参数
|
||
terrain.setBlockSize(32) # 设置块大小
|
||
|
||
# 生成地形
|
||
terrain.generate()
|
||
|
||
# 获取地形节点
|
||
terrain_node = terrain.getRoot()
|
||
|
||
if terrain_node.isEmpty():
|
||
print("错误:无法生成有效的平面地形节点")
|
||
return None
|
||
|
||
terrain_node.setTag("is_scene_element", "1")
|
||
terrain_node.setTag("tree_item_type", "TERRAIN_NODE")
|
||
|
||
node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||
terrain_node.setName(node_name)
|
||
|
||
center_offset = (resolution - 1) / 2
|
||
terrain_node.setPos(-center_offset * size[0], -center_offset * size[1], 0)
|
||
|
||
# 将地形添加到场景中
|
||
terrain_node.reparentTo(parent_node)
|
||
|
||
# 为地形添加碰撞体
|
||
from panda3d.core import BitMask32
|
||
# 设置地形节点的碰撞掩码
|
||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||
# 为地形的所有子节点也设置碰撞掩码
|
||
for child in terrain_node.getChildren():
|
||
child.setCollideMask(BitMask32.bit(2))
|
||
|
||
# 添加材质
|
||
self._applyTerrainMaterial(terrain_node)
|
||
|
||
# 保存地形信息(包括高度图)
|
||
terrain_info = {
|
||
'terrain': terrain,
|
||
'node': terrain_node,
|
||
'heightmap': None,
|
||
'heightfield': height_image, # 保存高度图
|
||
'scale': (size[0], size[1], 50),
|
||
'name': node_name
|
||
}
|
||
|
||
self.terrains.append(terrain_info)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建平面地形: {terrain_name}")
|
||
|
||
# 在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(terrain_node, parent_item, "TERRAIN_NODE")
|
||
if qt_item:
|
||
created_terrains.append((terrain_node, qt_item))
|
||
else:
|
||
created_terrains.append((terrain_node, None))
|
||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建平面地形: {str(e)}")
|
||
return None
|
||
|
||
# 处理创建结果
|
||
if not created_terrains:
|
||
print("❌ 没有成功创建任何平面地形")
|
||
return None
|
||
|
||
# 选中最后创建的光源
|
||
if created_terrains:
|
||
last_light_np, last_qt_item = created_terrains[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
# 更新选择和属性面板
|
||
tree_widget.update_selection_and_properties(last_light_np, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_terrains)} 个平面地形")
|
||
|
||
# 返回值处理
|
||
if len(created_terrains) == 1:
|
||
return created_terrains[0][0] # 单个光源返回NodePath
|
||
else:
|
||
return [light_np for light_np, _ in created_terrains] # 多个光源返回列表
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建平面地形过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def _applyTerrainMaterial(self, terrain_node):
|
||
"""为地形应用材质"""
|
||
try:
|
||
# 创建材质
|
||
material = Material()
|
||
material.setAmbient((0.5, 0.5, 0.5, 1.0)) # 环境光反射
|
||
material.setDiffuse((1, 1, 1, 1.0)) # 漫反射(绿色调)
|
||
material.setSpecular((0.2, 0.2, 0.2, 1.0)) # 镜面反射
|
||
material.setShininess(5.0) # 高光强度
|
||
|
||
# 应用材质到地形
|
||
terrain_node.setMaterial(material)
|
||
|
||
# 启用材质
|
||
terrain_node.setAttrib(ColorAttrib.makeDefault())
|
||
|
||
print("✓ 地形材质已应用")
|
||
except Exception as e:
|
||
print(f"应用地形材质时出错: {e}")
|
||
|
||
def updateTerrain(self):
|
||
"""更新所有地形的LOD"""
|
||
for terrain_info in self.terrains:
|
||
terrain = terrain_info['terrain']
|
||
if hasattr(terrain, 'update'):
|
||
terrain.update()
|
||
|
||
def setTerrainLODThreshold(self, terrain_info, threshold):
|
||
"""设置地形LOD阈值"""
|
||
if terrain_info in self.terrains:
|
||
terrain = terrain_info['terrain']
|
||
# GeoMipTerrain可能没有setNearFarThreshold方法,使用其他方式设置LOD
|
||
print("注意: GeoMipTerrain可能不支持直接设置LOD阈值")
|
||
|
||
def getTerrainHeight(self, terrain_info, x, y):
|
||
"""获取地形上指定点的高度"""
|
||
if terrain_info in self.terrains:
|
||
terrain = terrain_info['terrain']
|
||
if hasattr(terrain, 'getElevation'):
|
||
return terrain.getElevation(x, y)
|
||
return 0
|
||
|
||
# core/terrain_manager.py
|
||
|
||
def modifyTerrainHeight(self, terrain_info, x, y, radius, strength, operation="add"):
|
||
"""修改地形高度"""
|
||
if terrain_info not in self.terrains:
|
||
return False
|
||
|
||
try:
|
||
terrain = terrain_info['terrain']
|
||
terrain_node = terrain_info['node'] # 获取地形节点
|
||
|
||
# 直接使用我们保存的高度图数据
|
||
if 'heightfield' not in terrain_info:
|
||
print("地形信息中没有保存高度图数据")
|
||
return False
|
||
|
||
heightmap = terrain_info['heightfield']
|
||
if not heightmap:
|
||
print("无法获取地形高度图数据")
|
||
return False
|
||
|
||
# 获取高度图尺寸
|
||
width = heightmap.getXSize()
|
||
height = heightmap.getYSize()
|
||
|
||
# 获取地形节点信息
|
||
terrain_pos = terrain_node.getPos()
|
||
terrain_scale = terrain_node.getScale()
|
||
|
||
# 正确的坐标转换方法
|
||
# 1. 将世界坐标转换为相对于地形中心的坐标
|
||
relative_x = x - terrain_pos.getX()
|
||
relative_y = y - terrain_pos.getY()
|
||
|
||
# 2. 考虑地形的缩放
|
||
scaled_x = relative_x / terrain_scale.getX()
|
||
scaled_y = relative_y / terrain_scale.getY()
|
||
|
||
# 3. 转换为高度图坐标
|
||
center_offset = (width - 1) / 2
|
||
center_x = int(scaled_x + center_offset)
|
||
center_y = int(scaled_y + center_offset)
|
||
|
||
print(f"坐标转换详情:")
|
||
print(f" 世界坐标: ({x:.2f}, {y:.2f})")
|
||
print(f" 地形位置: ({terrain_pos.getX():.2f}, {terrain_pos.getY():.2f})")
|
||
print(f" 地形缩放: ({terrain_scale.getX():.2f}, {terrain_scale.getY():.2f})")
|
||
print(f" 相对坐标: ({relative_x:.2f}, {relative_y:.2f})")
|
||
print(f" 缩放坐标: ({scaled_x:.2f}, {scaled_y:.2f})")
|
||
print(f" 中心偏移: {center_offset}")
|
||
print(f" 高度图坐标: ({center_x}, {center_y})")
|
||
print(f" 高度图尺寸: {width} x {height}")
|
||
|
||
# 检查中心点是否在有效范围内
|
||
if not (0 <= center_x < width and 0 <= center_y < height):
|
||
print(f"❌ 中心点坐标超出范围: ({center_x}, {center_y})")
|
||
print(f"❌ 有效范围: (0-{width - 1}, 0-{height - 1})")
|
||
return False
|
||
|
||
# 修改半径范围内的点
|
||
modified = False
|
||
|
||
# 使用正确的半径计算(考虑地形缩放)
|
||
effective_radius = max(1, int(radius / max(terrain_scale.getX(), terrain_scale.getY())))
|
||
|
||
print(f"有效编辑半径: {effective_radius}")
|
||
|
||
# 限制编辑半径不超过地形尺寸的1/4,避免性能问题
|
||
effective_radius = min(effective_radius, width // 4, height // 4)
|
||
|
||
for dx in range(-effective_radius, effective_radius + 1):
|
||
for dy in range(-effective_radius, effective_radius + 1):
|
||
# 检查距离
|
||
distance = (dx * dx + dy * dy) ** 0.5
|
||
if distance <= effective_radius:
|
||
# 计算影响权重
|
||
weight = 1.0 - (distance / effective_radius) if effective_radius > 0 else 1.0
|
||
|
||
# 计算目标点坐标
|
||
target_x = center_x + dx
|
||
target_y = center_y + dy
|
||
|
||
# 检查边界
|
||
if 0 <= target_x < width and 0 <= target_y < height:
|
||
# 获取当前高度
|
||
current_height = heightmap.getRed(target_x, target_y)
|
||
|
||
# 根据操作类型修改高度
|
||
if operation == "add":
|
||
new_height = min(1.0, current_height + strength * weight * 0.01)
|
||
elif operation == "subtract":
|
||
new_height = max(0.0, current_height - strength * weight * 0.01)
|
||
elif operation == "set":
|
||
# 平坦化操作,设置为中等高度
|
||
new_height = 0.5
|
||
else:
|
||
new_height = current_height
|
||
|
||
# 设置新高度
|
||
heightmap.setRed(target_x, target_y, new_height)
|
||
heightmap.setGreen(target_x, target_y, new_height)
|
||
heightmap.setBlue(target_x, target_y, new_height)
|
||
modified = True
|
||
|
||
# 如果有修改,则重新生成地形
|
||
if modified:
|
||
# 重新设置高度图并生成地形
|
||
terrain.setHeightfield(heightmap)
|
||
terrain.generate()
|
||
|
||
# 关键修复:重新创建碰撞体,因为在生成地形时会丢失原有碰撞体
|
||
from panda3d.core import BitMask32, CollisionNode, CollisionPlane, Plane, Point3
|
||
# 移除旧的地形碰撞体(如果存在)
|
||
for child in terrain_node.getChildren():
|
||
if isinstance(child.node(), CollisionNode) and child.getName().startswith("terrain_collision_"):
|
||
child.removeNode()
|
||
|
||
# 创建新的地形碰撞体
|
||
# 使用更简单的方法:直接给地形节点添加碰撞体
|
||
terrain_node.setCollideMask(BitMask32.bit(2)) # 确保地形节点本身具有碰撞掩码
|
||
|
||
# 为地形的每个子节点也设置碰撞掩码
|
||
for child in terrain_node.getChildren():
|
||
child.setCollideMask(BitMask32.bit(2))
|
||
|
||
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}, 操作{operation}")
|
||
print(f"✓ 重新设置了地形碰撞体")
|
||
|
||
return modified
|
||
|
||
except Exception as e:
|
||
print(f"修改地形高度时出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def deleteTerrain(self, terrain_info):
|
||
"""删除地形"""
|
||
if terrain_info in self.terrains:
|
||
# 从场景中移除地形节点
|
||
terrain_node = terrain_info['node']
|
||
if terrain_node and not terrain_node.isEmpty():
|
||
tree_widget = self._get_tree_widget()
|
||
if tree_widget:
|
||
tree_widget.delete_items(tree_widget.selectedItems())
|
||
# terrain_node.removeNode()
|
||
#
|
||
# # 从列表中移除
|
||
# self.terrains.remove(terrain_info)
|
||
|
||
# # 更新场景树
|
||
# if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||
# self.world.scene_manager.updateSceneTree()
|
||
|
||
print(f"✓ 地形已删除: {terrain_info.get('name', 'Unknown')}")
|
||
|
||
import gc
|
||
gc.collect()
|
||
|
||
return True
|
||
return False
|
||
|
||
def getTerrainByName(self, name):
|
||
"""根据名称查找地形"""
|
||
for terrain_info in self.terrains:
|
||
if terrain_info.get('name') == name:
|
||
return terrain_info
|
||
return None
|
||
|
||
def getAllTerrainNames(self):
|
||
"""获取所有地形名称"""
|
||
return [terrain_info.get('name', f'Terrain_{i}') for i, terrain_info in enumerate(self.terrains)]
|
||
|
||
def setTerrainColor(self, terrain_info, color):
|
||
"""设置地形颜色"""
|
||
try:
|
||
if terrain_info in self.terrains:
|
||
terrain_node = terrain_info['node']
|
||
if terrain_node:
|
||
# 创建新材质
|
||
material = Material()
|
||
material.setAmbient((color[0] * 0.5, color[1] * 0.5, color[2] * 0.5, 1.0))
|
||
material.setDiffuse((color[0], color[1], color[2], 1.0))
|
||
material.setSpecular((0.2, 0.2, 0.2, 1.0))
|
||
material.setShininess(5.0)
|
||
|
||
# 应用材质
|
||
terrain_node.setMaterial(material)
|
||
print(f"✓ 地形颜色已更新: {color}")
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"设置地形颜色时出错: {e}")
|
||
return False
|
||
|
||
def setTerrainTexture(self, terrain_info, texture_path):
|
||
"""为地形设置纹理"""
|
||
try:
|
||
if terrain_info in self.terrains:
|
||
terrain_node = terrain_info['node']
|
||
if terrain_node and os.path.exists(texture_path):
|
||
texture_path = util.normalize_model_path(texture_path)
|
||
# 加载纹理
|
||
texture = self.world.loader.loadTexture(texture_path)
|
||
if texture:
|
||
# 应用纹理
|
||
terrain_node.setTexture(texture, 1)
|
||
print(f"✓ 地形纹理已应用: {texture_path}")
|
||
return True
|
||
else:
|
||
print(f"✗ 无法加载纹理: {texture_path}")
|
||
else:
|
||
print(f"✗ 纹理文件不存在: {texture_path}")
|
||
return False
|
||
except Exception as e:
|
||
print(f"应用地形纹理时出错: {e}")
|
||
return False
|
||
|
||
def saveTerrainData(self, terrain_info, filename):
|
||
"""保存地形数据到文件"""
|
||
try:
|
||
terrain_node = terrain_info['node']
|
||
heightfield = terrain_info['heightfield']
|
||
|
||
# 保存高度图到文件
|
||
if heightfield:
|
||
# 创建保存路径
|
||
terrain_dir = os.path.join(os.path.dirname(filename), "terrains")
|
||
if not os.path.exists(terrain_dir):
|
||
os.makedirs(terrain_dir)
|
||
|
||
# 生成唯一的地形文件名
|
||
terrain_filename = f"terrain_{terrain_info.get('name', 'unnamed')}_{int(time.time())}.png"
|
||
terrain_path = os.path.join(terrain_dir, terrain_filename)
|
||
|
||
# 保存高度图
|
||
heightfield.write(Filename.fromOsSpecific(terrain_path))
|
||
|
||
# 保存地形信息到标签
|
||
terrain_node.setTag("terrain_heightmap_path", terrain_path)
|
||
terrain_node.setTag("terrain_scale", str(terrain_info['scale']))
|
||
|
||
print(f"✓ 地形数据已保存: {terrain_path}")
|
||
return terrain_path
|
||
return None
|
||
except Exception as e:
|
||
print(f"保存地形数据时出错: {e}")
|
||
return None
|
||
|
||
def _recreateTerrain(self, terrain_node):
|
||
"""重新创建地形"""
|
||
try:
|
||
print(f"重新创建地形: {terrain_node.getName()}")
|
||
|
||
# 获取保存的地形信息
|
||
heightmap_path = terrain_node.getTag("terrain_heightmap_path") if terrain_node.hasTag(
|
||
"terrain_heightmap_path") else None
|
||
scale_str = terrain_node.getTag("terrain_scale") if terrain_node.hasTag("terrain_scale") else "(1, 1, 1)"
|
||
|
||
# 解析缩放信息
|
||
try:
|
||
scale_str = scale_str.strip("()")
|
||
scale_values = [float(x.strip()) for x in scale_str.split(",")]
|
||
scale = (scale_values[0], scale_values[1], scale_values[2]) if len(scale_values) >= 3 else (1, 1, 1)
|
||
except:
|
||
scale = (1, 1, 1)
|
||
|
||
# 恢复位置信息
|
||
if terrain_node.hasTag("transform_pos"):
|
||
try:
|
||
pos_str = terrain_node.getTag("transform_pos")
|
||
pos_str = pos_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()')
|
||
pos_values = [float(x.strip()) for x in pos_str.split(',')]
|
||
if len(pos_values) >= 3:
|
||
terrain_node.setPos(pos_values[0], pos_values[1], pos_values[2])
|
||
except Exception as e:
|
||
print(f"恢复地形位置失败: {e}")
|
||
|
||
# 如果有高度图路径,重新创建地形
|
||
if heightmap_path and os.path.exists(heightmap_path):
|
||
# 使用现有方法重新创建地形
|
||
new_terrain = self.createTerrainFromHeightMap(heightmap_path, scale)
|
||
if new_terrain:
|
||
# 删除旧的地形节点
|
||
if not terrain_node.isEmpty():
|
||
terrain_node.removeNode()
|
||
return new_terrain
|
||
|
||
# 如果没有高度图或创建失败,创建一个平面地形作为替代
|
||
print("使用平面地形作为替代")
|
||
new_terrain = self.createFlatTerrain(size=(scale[0], scale[1]), resolution=129)
|
||
if new_terrain:
|
||
# 删除旧的地形节点
|
||
if not terrain_node.isEmpty():
|
||
terrain_node.removeNode()
|
||
return new_terrain
|
||
|
||
return terrain_node
|
||
except Exception as e:
|
||
print(f"重新创建地形失败: {e}")
|
||
return terrain_node
|
||
|