1
0
forked from Rowland/EG

3d_image和2d_image

根据高度图创建地形
This commit is contained in:
Hector 2025-08-27 17:17:54 +08:00
parent c66b9003f5
commit afb4a4bb50
10 changed files with 2342 additions and 443 deletions

File diff suppressed because one or more lines are too long

View File

@ -119,6 +119,9 @@ class EventHandler:
if not evt:
print("事件为空")
return
if self.world.currentTool == "地形编辑":
self._handleTerrainEdit(evt,"add")
return
# 获取鼠标点击的位置
x = evt.get('x', 0)
@ -260,7 +263,103 @@ class EventHandler:
print(f"清理碰撞检测节点失败: {str(e)}")
print("=== 鼠标左键事件处理结束 ===\n")
def mousePressEventRight(self,evt):
"""处理鼠标右键按下事件"""
print(f"当前工具: {self.world.currentTool}")
# 检查是否是地形编辑模式
if self.world.currentTool == "地形编辑":
self._handleTerrainEdit(evt, "subtract") # 降低地形
return
# 其他右键处理逻辑可以在这里添加
print("鼠标右键事件处理")
def _handleTerrainEdit(self,evt,operation):
try:
x = evt.get('x',0)
y = evt.get('y',0)
winWidth,winHeight = self.world.getWindowSize()
mx = 2.0 * x/float(winWidth) - 1.0
my = 1.0 -2.0*y/float(winHeight)
nearPoint = Point3()
farPoint = Point3()
self.world.cam.node().getLens().extrude(Point2(mx, my), nearPoint, farPoint)
worldNearPoint = self.world.render.getRelativePoint(self.world.cam, nearPoint)
worldFarPoint = self.world.render.getRelativePoint(self.world.cam, farPoint)
picker = CollisionTraverser()
queue = CollisionHandlerQueue()
pickerNode = CollisionNode('terrain_edit_ray')
pickerNP = self.world.cam.attachNewNode(pickerNode)
from panda3d.core import BitMask32
pickerNode.setFromCollideMask(BitMask32.allOn()) # 检查所有碰撞
# 使用相机坐标系的点创建射线
direction = farPoint - nearPoint
direction.normalize()
pickerNode.addSolid(CollisionRay(nearPoint, direction))
picker.addCollider(pickerNP, queue)
picker.traverse(self.world.render)
print(f"地形碰撞检测结果数量: {queue.getNumEntries()}")
# 射线检测结果处理
hitPos = None
hitNode = None
if queue.getNumEntries() > 0:
# 遍历所有碰撞结果,找到地形节点
for i in range(queue.getNumEntries()):
entry = queue.getEntry(i)
collided_node = entry.getIntoNodePath()
print(f"碰撞到节点: {collided_node.getName()}")
# 检查是否是地形节点
for terrain_info in self.world.terrain_manager.terrains:
terrain_node = terrain_info['node']
if collided_node == terrain_node or terrain_node.isAncestorOf(collided_node):
hitPos = entry.getSurfacePoint(self.world.render)
hitNode = collided_node
print(f"找到地形节点: {terrain_node.getName()}")
# 修改地形高度
x_pos, y_pos = hitPos.getX(), hitPos.getY()
success = self.world.modifyTerrainHeight(
terrain_info, x_pos, y_pos, radius=3.0, strength=0.3, operation=operation)
if success:
print(f"✓ 地形编辑成功: {operation} at ({x_pos:.2f}, {y_pos:.2f})")
# 显示射线
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
else:
print("✗ 地形编辑失败")
break
if hitPos:
break
if not hitPos:
print("没有检测到地形碰撞")
# 显示射线(无碰撞)
self.showClickRay(worldNearPoint, worldFarPoint)
# 清理碰撞检测节点
pickerNP.removeNode()
print("地形编辑处理完成")
except Exception as e:
print(f"地形编辑处理出错: {e}")
import traceback
traceback.print_exc()
def _handleSelectionClick(self, hitNode):
"""处理选择工具的点击事件"""
print(f"开始处理选择点击,碰撞节点: {hitNode.getName()}")

View File

@ -53,14 +53,14 @@ class SelectionSystem:
# 高亮相关
self.gizmoHighlightAxis = None
self.gizmo_colors = {
"x": (1, 0, 0, 0), # 红色
"y": (0, 1, 0, 0), # 绿色
"z": (0, 0, 1, 0) # 蓝色
"x": (1, 0, 0, 1.0), # 红色
"y": (0, 1, 0, 1.0), # 绿色
"z": (0, 0, 1, 1.00) # 蓝色
}
self.gizmo_highlight_colors = {
"x": (1, 1, 0, 0), # 黄色高亮
"y": (1, 1, 0, 0), # 黄色高亮
"z": (1, 1, 0, 0) # 黄色高亮
"x": (1.0, 1.0, 0.0, 1.0), # 黄色高亮
"y": (1.0, 1.0, 0.0, 1.0), # 黄色高亮
"z": (1.0, 1.0, 0.0, 1.0) # 黄色高亮
}
print("✓ 选择和变换系统初始化完成")
@ -465,63 +465,74 @@ class SelectionSystem:
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
axis_Rotnodes = [self.gizmoRotXAxis, self.gizmoRotYAxis, self.gizmoRotZAxis]
# 设置坐标轴主节点的渲染属性
if self.gizmo:
self.gizmo.setLightOff() # 禁用光照
self.gizmo.setShaderOff() # 禁用着色器
self.gizmo.setFogOff() # 禁用雾效
self.gizmo.setBin("fixed", 40) # 设置为fixed渲染层级数值越大越优先
self.gizmo.setDepthWrite(False) # 禁用深度写入
self.gizmo.setDepthTest(False) # 禁用深度测试,确保始终可见
# 设置各轴节点的渲染属性
for axis_node in axis_nodes:
if axis_node:
#禁用光照和阴影
axis_node.setLightOff()
axis_node.setShaderOff()
axis_node.setFogOff()
#设置渲染层级,确保大多数对象之前渲染
axis_node.setBin("fixed",30)
#axis_node.setDepthWrite(False)
#axis_node.setDepthTest(True)
axis_node.setBin("fixed", 40) # 与主节点相同优先级
axis_node.setDepthWrite(False) # 禁用深度写入
axis_node.setDepthTest(False) # 禁用深度测试
# 设置旋转轴节点的渲染属性
for axis_rotnode in axis_Rotnodes:
if axis_rotnode:
axis_rotnode.setLightOff()
axis_rotnode.setShaderOff()
axis_rotnode.setFogOff()
axis_rotnode.setBin("fixed",30)
#axis_rotnode.setDepthWrite(False)
#axis_rotnode.setDepthTest(True)
axis_rotnode.setBin("fixed", 40) # 与主节点相同优先级
axis_rotnode.setDepthWrite(False) # 禁用深度写入
axis_rotnode.setDepthTest(False) # 禁用深度测试
# 收集所有handle节点
arrow_nodes = []
if self.gizmoXAxis:
x_handle = self.gizmoXAxis.find("x_handle")
if x_handle:
if x_handle and not x_handle.isEmpty():
arrow_nodes.append(x_handle)
if self.gizmoYAxis:
y_handle = self.gizmoYAxis.find("y_handle")
if y_handle:
if y_handle and not y_handle.isEmpty():
arrow_nodes.append(y_handle)
if self.gizmoZAxis:
z_handle = self.gizmoZAxis.find("z_handle")
if z_handle:
if z_handle and not z_handle.isEmpty():
arrow_nodes.append(z_handle)
rot_nodes = []
if self.gizmoRotXAxis:
x_rHandle = self.gizmoRotXAxis.find("x_handle")
if x_rHandle:
if x_rHandle and not x_rHandle.isEmpty():
rot_nodes.append(x_rHandle)
if self.gizmoRotYAxis:
y_rHandle = self.gizmoRotYAxis.find("y_handle")
if y_rHandle:
if y_rHandle and not y_rHandle.isEmpty():
rot_nodes.append(y_rHandle)
if self.gizmoRotZAxis:
z_rHandle = self.gizmoRotZAxis.find("z_handle")
if z_rHandle:
if z_rHandle and not z_rHandle.isEmpty():
rot_nodes.append(z_rHandle)
# 设置handle节点的渲染属性
for arrow_node in arrow_nodes:
if arrow_node:
arrow_node.setLightOff()
arrow_node.setShaderOff()
arrow_node.setFogOff()
arrow_node.setBin("fixed",31)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(False)
#启用透明度S
arrow_node.setBin("fixed", 41) # 略高于主节点,确保最优先显示
arrow_node.setDepthWrite(False)
arrow_node.setDepthTest(False)
# 启用透明度支持
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
for rot_node in rot_nodes:
@ -529,20 +540,12 @@ class SelectionSystem:
rot_node.setLightOff()
rot_node.setShaderOff()
rot_node.setFogOff()
rot_node.setBin("fixed",31)
#rot_node.setDepthWrite(False)
#rot_node.setDepthTest(False)
#启用透明度S
rot_node.setBin("fixed", 41) # 略高于主节点,确保最优先显示
rot_node.setDepthWrite(False)
rot_node.setDepthTest(False)
# 启用透明度支持
rot_node.setTransparency(TransparencyAttrib.MAlpha)
if self.gizmo:
self.gizmo.setLightOff()
self.gizmo.setShaderOff()
self.gizmo.setFogOff()
self.gizmo.setBin("fixed",29)
# self.gizmo.setDepthWrite(False)
#self.gizmo.setDepthTest(False)
except Exception as e:
print(f"设置坐标轴渲染属性失败: {str(e)}")
@ -647,7 +650,7 @@ class SelectionSystem:
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
#安区地更新朝向
# 安区地更新朝向
if is_scale_tool:
#self.gizmo.setHpr(self.gizmoTarget.getHpr())
@ -799,12 +802,27 @@ class SelectionSystem:
# 创建或获取材质
mat = Material()
# 设置材质属性 - 使用自发光确保在RenderPipeline下可见
mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
mat.setDiffuse(Vec4(0, 0, 0, 1))
#mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
mat.set_roughness(1)
# # 设置材质属性 - 使用自发光确保在RenderPipeline下可见
# mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
# mat.setDiffuse(Vec4(0, 0, 0, 1))
# #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
# mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
# mat.set_roughness(1)
# 设置材质属性 - 使用更自然的颜色,避免过亮的自发光
adjusted_color = Vec4(
min(color[0]*20, 1.0),
min(color[1]*20, 1.0),
min(color[2]*20, 1.0),
color[3]
)
mat.setBaseColor(adjusted_color)
# mat.setDiffuse(adjusted_color * 0.8) # 稍微降低漫反射亮度
# mat.setAmbient(adjusted_color * 0.3) # 设置环境光反射
# mat.setSpecular(Vec4(0.3, 0.3, 0.3, 1.0)) # 适度的镜面反射
# mat.setShininess(25.0) # 适中的高光强度
mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
# 应用材质
handle_node.setMaterial(mat, 1)
@ -820,9 +838,9 @@ class SelectionSystem:
handle_node.setShaderOff() # 禁用着色器
handle_node.setFogOff() # 禁用雾效果
handle_node.setBin("fixed",31)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(True)
handle_node.setBin("fixed",41)
handle_node.setDepthWrite(False)
handle_node.setDepthTest(False)
# 保存材质引用以便后续修改
if axis == "x":
@ -835,9 +853,9 @@ class SelectionSystem:
axis_node.setLightOff()
axis_node.setShaderOff()
axis_node.setFogOff()
axis_node.setBin("fixed", 30)
axis_node.setBin("fixed", 40)
axis_node.setDepthWrite(False)
axis_node.setDepthTest(True)
axis_node.setDepthTest(False)
except Exception as e:
print(f"设置坐标轴颜色失败: {str(e)}")
@ -889,12 +907,28 @@ class SelectionSystem:
# 创建或获取材质
mat = Material()
# 设置材质属性 - 使用自发光确保在RenderPipeline下可见
mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
mat.setDiffuse(Vec4(0, 0, 0, 1))
#mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
mat.set_roughness(1)
# # 设置材质属性 - 使用自发光确保在RenderPipeline下可见
# mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
# mat.setDiffuse(Vec4(0, 0, 0, 1))
# #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
# mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
# mat.set_roughness(1)
# 设置材质属性 - 使用更自然的颜色,避免过亮的自发光
# 将颜色值控制在合理范围内
adjusted_color = Vec4(
min(color[0], 1.0),
min(color[1], 1.0),
min(color[2], 1.0),
color[3]
)
mat.setBaseColor(adjusted_color)
#mat.setDiffuse(adjusted_color * 1) # 稍微降低漫反射亮度
#mat.setAmbient(adjusted_color * 0.4) # 设置环境光反射
# mat.setSpecular(Vec4(0.4, 0.4, 0.4, 1.0)) # 适度的镜面反射
# mat.setShininess(1.0) # 适中的高光强度
mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
# 应用材质
handle_node.setMaterial(mat, 1)
@ -910,9 +944,9 @@ class SelectionSystem:
handle_node.setShaderOff() # 禁用着色器
handle_node.setFogOff() # 禁用雾效果
handle_node.setBin("fixed",31)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(True)
handle_node.setBin("fixed",41)
handle_node.setDepthWrite(False)
handle_node.setDepthTest(False)
# 保存材质引用以便后续修改
if axis == "x":
@ -925,9 +959,9 @@ class SelectionSystem:
axis_node.setLightOff()
axis_node.setShaderOff()
axis_node.setFogOff()
axis_node.setBin("fixed", 30)
#axis_node.setDepthWrite(False)
#axis_node.setDepthTest(True)
axis_node.setBin("fixed", 40)
axis_node.setDepthWrite(False)
axis_node.setDepthTest(False)
except Exception as e:
print(f"设置坐标轴颜色失败: {str(e)}")
@ -964,6 +998,8 @@ class SelectionSystem:
# 稍微放大以增强视觉效果
axis_nodes[axis].setScale(1.1)
self.gizmoHighlightAxis = axis
except Exception as e:
print(f"鼠标进入坐标轴处理失败: {e}")
@ -990,6 +1026,9 @@ class SelectionSystem:
if axis in axis_nodes and axis_nodes[axis]:
axis_nodes[axis].setScale(1.0)
if self.gizmoHighlightAxis == axis:
self.gizmoHighlightAxis = None
except Exception as e:
print(f"鼠标离开坐标轴处理失败: {e}")
@ -1435,8 +1474,12 @@ class SelectionSystem:
print("拖拽更新失败: 没有坐标轴起始位置")
return
is_scale_tool = self.world.tool_manager.isScaleTool()
is_rotate_tool = self.world.tool_manager.isRotateTool()
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
is_rotate_tool = self.world.tool_manager.isRotateTool() if self.world.tool_manager else False
is_gui_element = (hasattr(self.gizmoTarget,'getTag') and
self.gizmoTarget.getTag("is_gui_element") == "1")
# 计算鼠标移动距离(屏幕像素)
mouseDeltaX = mouseX - self.dragStartMousePos[0]
@ -1446,22 +1489,35 @@ class SelectionSystem:
scale_factor = 1.0 + (mouseDeltaX + mouseDeltaY) * 0.01
start_scale = getattr(self,'gizmoTargetStartScale',Vec3(1,1,1))
target_hpr = self.gizmoTarget.getHpr()
if self.dragGizmoAxis == "x":
new_scale = Vec3(start_scale.x * scale_factor,start_scale.y,start_scale.z)
elif self.dragGizmoAxis == "y":
new_scale = Vec3(start_scale.x,start_scale.y*scale_factor,start_scale.z)
elif self.dragGizmoAxis == "z":
z_scale_factor = 1.0 - (mouseDeltaX + mouseDeltaY)*0.01
new_scale = Vec3(start_scale.x,start_scale.y,start_scale.z*z_scale_factor)
if is_gui_element:
if self.dragGizmoAxis == "x":
new_scale = Vec3(start_scale.x * scale_factor,start_scale.y,start_scale.z)
elif self.dragGizmoAxis == "y":
new_scale = Vec3(start_scale.x, start_scale.y * scale_factor, start_scale.z)
elif self.dragGizmoAxis == "z":
new_scale = Vec3(start_scale.x, start_scale.y, start_scale.z * scale_factor)
else:
new_scale = Vec3(start_scale.x * scale_factor,
start_scale.y * scale_factor,
start_scale.z * scale_factor)
else:
new_scale = Vec3(start_scale.x * scale_factor,
start_scale * scale_factor,
start_scale.z * scale_factor)
#应用新缩放值
# 普通3D模型的缩放处理
if self.dragGizmoAxis == "x":
new_scale = Vec3(start_scale.x * scale_factor, start_scale.y, start_scale.z)
elif self.dragGizmoAxis == "y":
new_scale = Vec3(start_scale.x, start_scale.y * scale_factor, start_scale.z)
elif self.dragGizmoAxis == "z":
z_scale_factor = 1.0 - (mouseDeltaX + mouseDeltaY) * 0.01
new_scale = Vec3(start_scale.x, start_scale.y, start_scale.z * z_scale_factor)
else:
new_scale = Vec3(start_scale.x * scale_factor,
start_scale.y * scale_factor,
start_scale.z * scale_factor)
# 应用新缩放值
self.gizmoTarget.setScale(new_scale)
#实时更新属性面板
# 安全地更新属性面板
#self._safeUpdatePropertyPanel()
self.world.property_panel.refreshModelValues(self.gizmoTarget)
return
elif is_rotate_tool:
@ -1481,6 +1537,7 @@ class SelectionSystem:
start_hpr.y + rotation_amount,
start_hpr.z + rotation_amount)
self.gizmoTarget.setHpr(new_hpr)
#self._safeUpdatePropertyPanel()
self.world.property_panel.refreshModelValues(self.gizmoTarget)
return
@ -1647,6 +1704,7 @@ class SelectionSystem:
# 实时更新属性面板
self.world.property_panel.refreshModelValues(self.gizmoTarget)
#self._safeUpdatePropertyPanel()
# 每次拖拽都输出调试信息(但限制频率)
if not hasattr(self, '_last_drag_debug_time'):
@ -1663,6 +1721,39 @@ class SelectionSystem:
import traceback
traceback.print_exc()
def _safeUpdatePropertyPanel(self):
"""安全地更新属性面板"""
try:
# 检查属性面板是否存在且有效
if (hasattr(self.world, 'property_panel') and
self.world.property_panel is not None):
# 检查目标节点是否有效
if (self.gizmoTarget is not None and
not self.gizmoTarget.isEmpty()):
# 检查是否有refreshModelValues方法
if hasattr(self.world.property_panel, 'refreshModelValues'):
self.world.property_panel.refreshModelValues(self.gizmoTarget)
# 如果是GUI元素可能需要特殊的处理
elif (hasattr(self.gizmoTarget, 'getTag') and
self.gizmoTarget.getTag("is_gui_element") == "1" and
hasattr(self.world.property_panel, 'updateGUIPropertyPanel')):
# 对于GUI元素可能需要重新构建整个面板
if (hasattr(self.world, 'treeWidget') and
self.world.treeWidget is not None):
current_item = self.world.treeWidget.currentItem()
if current_item is not None:
self.world.property_panel.updatePropertyPanel(current_item)
except RuntimeError as e:
if "wrapped C/C++ object" in str(e):
# 忽略控件已被删除的错误
print("警告: 属性面板控件已被删除,跳过更新")
else:
print(f"更新属性面板时出错: {e}")
except Exception as e:
print(f"更新属性面板失败: {e}")
def stopGizmoDrag(self):
"""停止坐标轴拖拽"""
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
@ -1690,19 +1781,34 @@ class SelectionSystem:
def updateSelection(self, nodePath):
"""更新选择状态"""
print(f"\n=== 更新选择状态 ===")
print(f"新选择的节点: {nodePath.getName() if nodePath else 'None'}")
# 如果正在删除节点,避免更新选择
if hasattr(self, '_deleting_node') and self._deleting_node:
print("正在删除节点,跳过选择更新")
print("=== 选择状态更新完成 ===\n")
return
node_name = "None"
if nodePath and not nodePath.isEmpty():
node_name = nodePath.getName()
print(f"新选择的节点: {node_name}")
self.selectedNode = nodePath
# 添加兼容性属性
self.selectedObject = nodePath
if nodePath:
print(f"开始为节点 {nodePath.getName()} 创建选择框和坐标轴...")
if nodePath and not nodePath.isEmpty():
node_name = nodePath.getName()
print(f"开始为节点 {node_name} 创建选择框和坐标轴...")
# 创建选择框
print("创建选择框...")
self.createSelectionBox(nodePath)
if self.selectionBox:
print(f"✓ 选择框创建成功: {self.selectionBox.getName()}")
box_name = "Unknown"
if self.selectionBox and not self.selectionBox.isEmpty():
box_name = self.selectionBox.getName()
print(f"✓ 选择框创建成功: {box_name}")
else:
print("× 选择框创建失败")
@ -1710,11 +1816,14 @@ class SelectionSystem:
print("创建坐标轴...")
self.createGizmo(nodePath)
if self.gizmo:
print(f"✓ 坐标轴创建成功: {self.gizmo.getName()}")
gizmo_name = "Unknown"
if self.gizmo and not self.gizmo.isEmpty():
gizmo_name = self.gizmo.getName()
print(f"✓ 坐标轴创建成功: {gizmo_name}")
else:
print("× 坐标轴创建失败")
print(f"✓ 选中了节点: {nodePath.getName()}")
print(f"✓ 选中了节点: {node_name}")
else:
print("清除选择...")
self.clearSelectionBox()
@ -1732,10 +1841,10 @@ class SelectionSystem:
return self.selectedNode is not None
def checkAndClearIfTargetDeleted(self):
if self.gizmoTarget and self.gizmoTarget.isEmpty():
if (self.gizmoTarget and self.gizmoTarget.isEmpty()):
self.clearGizmo()
if self.selectionBoxTarget and self.selectionBoxTarget.isEmpty():
if (self.selectionBoxTarget and self.selectionBoxTarget.isEmpty()):
self.clearSelectionBox()
def setupGizmoCollision(self):

378
core/terrain_manager.py Normal file
View File

@ -0,0 +1,378 @@
# core/terrain_manager.py
from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath
from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight
import os
class TerrainManager:
"""地形管理类"""
def __init__(self, world):
self.world = world
self.terrains = []
# core/terrain_manager.py
def createTerrainFromHeightMap(self, heightmap_path, scale=(1, 1, 1)):
"""从高度图创建地形"""
# 检查文件是否存在
if not os.path.exists(heightmap_path):
print(f"错误: 高度图文件 {heightmap_path} 不存在")
return None
try:
# 创建GeoMipTerrain对象
terrain_name = f"terrain_{len(self.terrains)}"
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]
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(False) # 使用LOD
# 生成地形
terrain.generate()
# 获取地形节点
terrain_node = terrain.getRoot()
# 设置缩放
terrain_node.setScale(scale[0], scale[1], scale[2])
# 将地形添加到场景中
terrain_node.reparentTo(self.world.render)
# 为地形节点设置名称,便于在场景树中识别
terrain_node.setName(f"Terrain_{os.path.basename(heightmap_path)}")
from panda3d.core import BitMask32
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
# 添加材质
self._applyTerrainMaterial(terrain_node)
# 保存地形信息(包括高度图的副本)
terrain_info = {
'terrain': terrain,
'node': terrain_node,
'heightmap': heightmap_path,
'heightfield': height_image, # 保存高度图副本
'scale': scale,
'name': f"Terrain_{os.path.basename(heightmap_path)}"
}
self.terrains.append(terrain_info)
# 更新场景树
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
self.world.scene_manager.updateSceneTree()
print(f"✓ 成功从 {heightmap_path} 创建地形")
return terrain_info
except Exception as e:
print(f"创建地形时出错: {e}")
import traceback
traceback.print_exc()
return None
def createFlatTerrain(self, size=(100, 100), resolution=129):
"""创建平面地形"""
try:
# 确保分辨率是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)}"
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(False) # 使用LOD
# 设置地形参数
terrain.setBlockSize(32) # 设置块大小
# 生成地形
terrain.generate()
# 获取地形节点
terrain_node = terrain.getRoot()
# 设置缩放以达到指定大小
terrain_node.setScale(size[0], size[1], 50) # Z轴缩放更大以显示地形起伏
# 将地形添加到场景中
terrain_node.reparentTo(self.world.render)
# 为地形节点设置名称,便于在场景树中识别
terrain_node.setName(f"FlatTerrain_{len(self.terrains)}")
# 为地形添加碰撞体
from panda3d.core import BitMask32
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
# 添加材质
self._applyTerrainMaterial(terrain_node)
# 保存地形信息(包括高度图)
terrain_info = {
'terrain': terrain,
'node': terrain_node,
'heightmap': None,
'heightfield': height_image, # 保存高度图
'scale': (size[0], size[1], 50),
'name': f"FlatTerrain_{len(self.terrains)}"
}
self.terrains.append(terrain_info)
# 更新场景树
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
self.world.scene_manager.updateSceneTree()
print(f"✓ 成功创建平面地形,大小: {size}, 分辨率: {resolution}")
return terrain_info
except Exception as e:
print(f"创建平面地形时出错: {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.makeDefaultColor())
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']
# 直接使用我们保存的高度图数据
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()
# 转换坐标到高度图空间
center_x = int((x / terrain_info['scale'][0]) * width + width / 2)
center_y = int((y / terrain_info['scale'][1]) * height + height / 2)
# 修改半径范围内的点
modified = False
for dx in range(-int(radius), int(radius) + 1):
for dy in range(-int(radius), int(radius) + 1):
# 检查距离
distance = (dx * dx + dy * dy) ** 0.5
if distance <= radius:
# 计算影响权重
weight = 1.0 - (distance / radius)
# 计算目标点坐标
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 = current_height + strength * weight * 0.1
elif operation == "subtract":
new_height = current_height - strength * weight * 0.1
elif operation == "set":
new_height = strength
else:
new_height = current_height
# 限制高度范围
new_height = max(0.0, min(1.0, new_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()
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}")
# 更新场景树
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
self.world.scene_manager.updateSceneTree()
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:
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')}")
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 = 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

View File

@ -10,9 +10,9 @@ GUI元素管理模块
from direct.gui.DirectGui import *
from panda3d.core import *
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
QDoubleSpinBox, QPushButton, QDialogButtonBox,
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout)
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
QDoubleSpinBox, QPushButton, QDialogButtonBox,
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout, QGridLayout, QSpinBox)
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt
# 尝试导入 QtWebEngineWidgets如果失败则设置为 None
@ -147,6 +147,112 @@ class GUIManager:
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
return entry
def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2):
"""创建2D GUI图片"""
from direct.gui.DirectGui import DirectFrame
from panda3d.core import TransparencyAttrib,Texture,CardMaker,CardMaker,Vec3
# 将3D坐标转换为2D屏幕坐标
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
#使用CardMaker创建一个更可靠的图片框架
cm = CardMaker('gui-2d-image')
cm.setFrame(-size,size,-size,size)
image_node = self.world.aspect2d.attachNewNode(cm.generate())
image_node.setPos(gui_pos)
image_node.setBin('fixed',0)
image_node.setDepthWrite(False)
image_node.setDepthTest(False)
image_node.setColor(1,1,1,1)
# 设置透明度支持
image_node.setTransparency(TransparencyAttrib.MAlpha)
# 如果提供了图像路径,则加载纹理
if image_path:
try:
texture = self.world.loader.loadTexture(image_path)
if texture:
image_node.setTexture(texture,1)
texture.setWrapU(Texture.WM_clamp)
texture.setWrapV(Texture.WM_clamp)
texture.setMinfilter(Texture.FT_linear)
texture.setMagfilter(Texture.FT_linear)
image_node.setColor(1,1,1,1)
else:
print(f"⚠️ 无法加载2D图片纹理: {image_path}")
except Exception as e:
print(f"❌ 加载2D图片纹理失败: {e}")
# 为GUI元素添加标识
image_node.setTag("gui_type", "2d_image")
image_node.setTag("gui_id", f"2d_image_{len(self.gui_elements)}")
image_node.setTag("gui_text", f"2D图片_{len(self.gui_elements)}")
if image_path:
image_node.setTag("image_path", image_path)
image_node.setTag("is_gui_element", "1")
self.gui_elements.append(image_node)
# 安全地调用updateSceneTree
if hasattr(self.world, 'updateSceneTree'):
self.world.updateSceneTree()
print(f"✓ 创建2D GUI图片 (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
return image_node
def constrain2DPosition(self,gui_element,new_x=None,new_z=None):
"""限制2dGUI元素位置在屏幕范围内"""
try:
from panda3d.core import Vec3
bounds = gui_element.getTightBounds()
element_width=0
element_height=0
if bounds:
min_point,max_point = bounds
element_width = (max_point.getX() - min_point.getX())/2
element_height = (max_point.getZ()-min_point.getZ())/2
#获取当前缩放
scale = gui_element.getScale()
if hasattr(scale,'getX'):
scale_x = scale.getX()
scale_z = scale.getZ() if hasattr(scale,'getZ') else scale_x
else:
scale_x = scale_z = scale if isinstance(scale,(int,float)) else 1.0
actual_width = element_width * scale_x
actual_height = element_height * scale_z
screen_width = 1.9
screen_height = 0.9
min_x = -screen_width + actual_width
max_x = screen_width - actual_width
min_z = -screen_height + actual_height
max_z = screen_height - actual_height
#获取当前位置
current_pos = gui_element.getPos()
x = new_x if new_x is not None else current_pos.getX()
z = new_z if new_z is not None else current_pos.getZ()
#应用边界限制
x = max(min_x,min(max_x,x))
z = max(min_z,min(max_z,z))
return Vec3(x,current_pos.getY(),z)
except Exception as e:
print(f"约束2D位置时出错: {e}")
# 出错时返回原始值
current_pos = gui_element.getPos()
x = new_x if new_x is not None else current_pos.getX()
z = new_z if new_z is not None else current_pos.getZ()
return Vec3(x, current_pos.getY(), z)
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=1):
"""创建3D空间文本"""
@ -436,25 +542,34 @@ class GUIManager:
elif property_name == "color": # 添加颜色处理
if isinstance(value, (list, tuple)) and len(value) >= 3:
# 更新材质颜色
if not gui_element.hasMaterial():
material = Material(f"text-material-{gui_element.getName()}")
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
gui_element.setMaterial(material, 1)
else:
material = gui_element.getMaterial()
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
gui_element.setMaterial(material, 1)
# 对于2D GUI元素button和label使用frameColor属性
if gui_type in ["button", "label"]:
# 设置背景颜色
gui_element['frameColor'] = (value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)
print(f"成功更新2D GUI背景颜色: {gui_type} -> {value}")
# 对于3D元素使用材质颜色
elif gui_type in ["3d_text", "3d_image", "virtual_screen"]:
# 更新材质颜色
if not gui_element.hasMaterial():
material = Material(f"text-material-{gui_element.getName()}")
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
gui_element.setMaterial(material, 1)
else:
material = gui_element.getMaterial()
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
gui_element.setMaterial(material, 1)
# 更新 TextNode 的文本颜色
if isinstance(gui_element.node(), TextNode):
gui_element.node().setTextColor(
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
# if gui_type in ["3d_text", "virtual_screen"]:
# gui_element.setColor(*value)
# elif gui_type in ["button", "label"]:
# gui_element['text_fg'] = value
if isinstance(gui_element.node(), TextNode):
gui_element.node().setTextColor(
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
print(f"成功更新3D GUI颜色: {gui_type} -> {value}")
else:
print(f"警告: 未知的GUI类型 {gui_type},无法设置颜色")
elif property_name == "position":
if isinstance(value, (list, tuple)) and len(value) >= 3:
@ -497,6 +612,9 @@ class GUIManager:
elif gui_type == "3d_image":
image_path = gui_element.getTag("image_path")
self.createGUI3DImage(new_pos,image_path,size=(2,2))
elif gui_type == "2d_image":
image_path = gui_element.getTag("image_path")
self.createGUI2DImage(new_pos, image_path, size=0.2)
elif gui_type == "virtual_screen":
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
@ -745,7 +863,20 @@ class GUIManager:
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
)
y_pos -= spacing
# 2D图片工具
btn_2d_image = DirectButton(
parent=self.guiEditPanel,
text="2D图片",
pos=(0, 0, y_pos),
scale=0.04,
command=self.setGUICreateTool,
extraArgs=["2d_image"],
frameColor=(0.8, 0.6, 0.2, 1),
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
)
y_pos -= spacing
# 虚拟屏幕工具
btn_screen = DirectButton(
parent=self.guiEditPanel,
@ -944,6 +1075,8 @@ class GUIManager:
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
elif gui_type == "3d_image":
element = self.createGUI3DImage(pos)
elif gui_type == "2d_image":
element = self.createGUI2DImage(pos)
elif gui_type == "virtual_screen":
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
else:
@ -1039,54 +1172,198 @@ class GUIManager:
# 位置属性
if hasattr(gui_element, 'getPos'):
# 根据GUI类型设置组名
if gui_type in ["button", "label", "entry", "2d_image"]:
transform_group = QGroupBox("变换 Rect Transform")
else:
transform_group = QGroupBox("变换 Transform")
transform_layout = QGridLayout()
pos = gui_element.getPos()
# 根据GUI类型决定位置编辑方式
if gui_type in ["button", "label", "entry"]:
if gui_type in ["button", "label", "entry","2d_image"]:
# 2D GUI组件使用屏幕坐标
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
logical_z = pos.getZ() / 0.1
transform_layout.addWidget(QLabel("屏幕位置"), 0, 0)
x_label = QLabel("X")
z_label = QLabel("z")
x_label.setAlignment(Qt.Aligncenter)
z_label.setAlignment(Qt.AlignCenter)
transform_layout.addWidget(x_label, 0, 1)
transform_layout.addWidget(z_label, 0, 2)
xPos = QDoubleSpinBox()
xPos.setRange(-50, 50)
xPos.setValue(logical_x)
xPos.valueChanged.connect(lambda v: self.editGUI2DPosition(gui_element, "x", v))
layout.addRow("屏幕位置 X:", xPos)
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
transform_layout.addWidget(xPos, 1, 1)
zPos = QDoubleSpinBox()
zPos.setRange(-50, 50)
zPos.setValue(logical_z)
zPos.valueChanged.connect(lambda v: self.editGUI2DPosition(gui_element, "z", v))
layout.addRow("屏幕位置 Z:", zPos)
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
transform_layout.addWidget(zPos, 1, 2)
# 显示实际屏幕坐标(只读)
transform_layout.addWidget(QLabel("实际坐标"), 2, 0)
actualXLabel = QLabel(f"{pos.getX():.3f}")
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
layout.addRow("实际屏幕 X:", actualXLabel)
actualZLabel = QLabel(f"{pos.getZ():.3f}")
actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
layout.addRow("实际屏幕 Z:", actualZLabel)
transform_layout.addWidget(actualXLabel, 3, 1)
transform_layout.addWidget(actualZLabel, 3, 2)
# 添加宽度和高度控件对于2D图像
if gui_type == "2d_image":
# 添加排序控制组
sort_group = QGroupBox("渲染顺序")
sort_layout = QGridLayout()
# 获取当前的sort值如果没有设置则默认为0
current_sort = int(gui_element.getTag("sort") or "0")
sort_layout.addWidget(QLabel("层级:"), 0, 0)
sort_spin = QSpinBox()
sort_spin.setRange(-1000, 1000) # 设置合理的范围
sort_spin.setValue(current_sort)
# 创建更新排序的函数
def updateSort(value):
try:
# 设置标签
gui_element.setTag("sort", str(value))
# 应用sort到节点 - 使用fixed bin和指定的值
gui_element.setBin("fixed", value)
print(f"✓ 更新2D图像渲染顺序: {value}")
except Exception as e:
print(f"✗ 更新2D图像渲染顺序失败: {e}")
sort_spin.valueChanged.connect(updateSort)
sort_layout.addWidget(sort_spin, 0, 1)
# 添加说明标签
sort_help = QLabel("(数值越大越靠前)")
sort_help.setStyleSheet("font-size: 10px; color: gray;")
sort_layout.addWidget(sort_help, 1, 0, 1, 2)
sort_group.setLayout(sort_layout)
layout.addWidget(sort_group)
scale = gui_element.getScale()
width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,
(tuple, list)) else scale
height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,
(tuple, list)) and len(
scale) > 1 else scale
# 宽度控件
transform_layout.addWidget(QLabel("宽度"), 4, 0)
widthSpinBox = QDoubleSpinBox()
widthSpinBox.setRange(0.1, 10)
widthSpinBox.setSingleStep(0.1)
widthSpinBox.setValue(width)
widthSpinBox.valueChanged.connect(
lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
transform_layout.addWidget(widthSpinBox, 4, 1)
# 高度控件
transform_layout.addWidget(QLabel("高度"), 4, 2)
heightSpinBox = QDoubleSpinBox()
heightSpinBox.setRange(0.1, 10)
heightSpinBox.setSingleStep(0.1)
heightSpinBox.setValue(height)
heightSpinBox.valueChanged.connect(
lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
transform_layout.addWidget(heightSpinBox, 4, 3)
else:
# 3D GUI组件使用世界坐标
transform_layout.addWidget(QLabel("位置"), 0, 0)
# X, Y, Z 标签居中
x_label = QLabel("X")
y_label = QLabel("Y")
z_label = QLabel("Z")
x_label.setAlignment(Qt.AlignCenter)
y_label.setAlignment(Qt.AlignCenter)
z_label.setAlignment(Qt.AlignCenter)
transform_layout.addWidget(x_label, 0, 1)
transform_layout.addWidget(y_label, 0, 2)
transform_layout.addWidget(z_label, 0, 3)
# 位置数值输入框
xPos = QDoubleSpinBox()
xPos.setRange(-1000, 1000)
xPos.setRange(-100, 100)
xPos.setValue(pos.getX())
xPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [v, pos.getY(), pos.getZ()]))
layout.addRow("位置 X:", xPos)
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v))
transform_layout.addWidget(xPos, 1, 1)
yPos = QDoubleSpinBox()
yPos.setRange(-1000, 1000)
yPos.setRange(-100, 100)
yPos.setValue(pos.getY())
yPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [pos.getX(), v, pos.getZ()]))
layout.addRow("位置 Y:", yPos)
yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v))
transform_layout.addWidget(yPos, 1, 2)
zPos = QDoubleSpinBox()
zPos.setRange(-1000, 1000)
zPos.setRange(-100, 100)
zPos.setValue(pos.getZ())
zPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [pos.getX(), pos.getY(), v]))
layout.addRow("位置 Z:", zPos)
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v))
transform_layout.addWidget(zPos, 1, 3)
# 缩放属性
scale = gui_element.getScale()
transform_layout.addWidget(QLabel("缩放"), 2, 0)
# X, Y, Z 缩放标签居中
sx_label = QLabel("X")
sy_label = QLabel("Y")
sz_label = QLabel("Z")
sx_label.setAlignment(Qt.AlignCenter)
sy_label.setAlignment(Qt.AlignCenter)
sz_label.setAlignment(Qt.AlignCenter)
transform_layout.addWidget(sx_label, 2, 1)
transform_layout.addWidget(sy_label, 2, 2)
transform_layout.addWidget(sz_label, 2, 3)
# 缩放数值输入框
scale_x = QDoubleSpinBox()
scale_x.setRange(0.01, 10)
scale_x.setSingleStep(0.1)
scale_x.setValue(
scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale)
scale_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
transform_layout.addWidget(scale_x, 3, 1)
scale_y = QDoubleSpinBox()
scale_y.setRange(0.01, 10)
scale_y.setSingleStep(0.1)
scale_y.setValue(
scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len(
scale) > 1 else scale)
scale_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "y", v))
transform_layout.addWidget(scale_y, 3, 2)
scale_z = QDoubleSpinBox()
scale_z.setRange(0.01, 10)
scale_z.setSingleStep(0.1)
scale_z.setValue(
scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len(
scale) > 2 else scale)
scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
transform_layout.addWidget(scale_z, 3, 3)
transform_group.setLayout(transform_layout)
self._propertyLayout.addWidget(transform_group)
# 缩放属性
if hasattr(gui_element, 'getScale'):
@ -1136,24 +1413,24 @@ class GUIManager:
r, g, b = color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0
self.editGUIElement(gui_element, "color", [r, g, b, 1.0])
def editGUI2DPosition(self, gui_element, axis, value):
"""编辑2D GUI元素位置"""
try:
current_pos = gui_element.getPos()
if axis == "x":
# 将逻辑坐标转换为屏幕坐标
new_screen_x = value * 0.1
gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
elif axis == "z":
# 将逻辑坐标转换为屏幕坐标
new_screen_z = value * 0.1
gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
except Exception as e:
print(f"编辑2D GUI位置失败: {str(e)}")
# def editGUI2DPosition(self, gui_element, axis, value):
# """编辑2D GUI元素位置"""
# try:
# current_pos = gui_element.getPos()
#
# if axis == "x":
# # 将逻辑坐标转换为屏幕坐标
# new_screen_x = value * 0.1
# gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
# elif axis == "z":
# # 将逻辑坐标转换为屏幕坐标
# new_screen_z = value * 0.1
# gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
#
# print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
#
# except Exception as e:
# print(f"编辑2D GUI位置失败: {str(e)}")
def update3DImageTexture(self, model_nodepath, image_path):
from panda3d.core import Texture
@ -1190,7 +1467,164 @@ class GUIManager:
except Exception as e:
print(f"❌ 更新纹理时出错: {e}")
# 替换现有的 createCesiumView 方法
def update2DImageTexture(self, gui_element, image_path):
"""更新2D图片纹理"""
try:
# 加载新纹理
new_texture = self.world.loader.loadTexture(image_path)
if new_texture:
new_texture.setMagfilter(Texture.FT_linear)
new_texture.setMinfilter(Texture.FT_linear_mipmap_linear)
new_texture.setFormat(Texture.F_rgba)
new_texture.setWrapU(Texture.WM_clamp)
new_texture.setWrapV(Texture.WM_clamp)
# 应用纹理到模型
gui_element.setTexture(new_texture, 1)
# 更新标签
gui_element.setTag("texture_path", image_path)
gui_element.setTag("image_path", image_path)
print(f"✅ 2D图像纹理已更新为: {image_path}")
return True
else:
print(f"❌ 无法加载2D图片纹理: {image_path}")
return False
except Exception as e:
print(f"❌ 更新2D图片纹理时出错: {e}")
return False
# 在gui_manager.py或其他相关文件中添加以下方法
def editGUI2DPosition(self, gui_element, axis, value):
"""编辑2D GUI元素位置并应用边界约束"""
try:
gui_type = gui_element.getTag("gui_type")
if gui_type in ["button", "label", "entry", "2d_image"]:
# 2D元素使用屏幕坐标需要转换
current_pos = gui_element.getPos()
if axis == "x":
# 转换逻辑坐标到屏幕坐标
screen_x = value * 0.1
# 应用边界约束
constrained_pos = self.constrain2DPosition(gui_element, new_x=screen_x)
new_pos = (constrained_pos.getX(), constrained_pos.getY(), constrained_pos.getZ())
elif axis == "z":
screen_z = value * 0.1
# 应用边界约束
constrained_pos = self.constrain2DPosition(gui_element, new_z=screen_z)
new_pos = (constrained_pos.getX(), constrained_pos.getY(), constrained_pos.getZ())
else:
return False
gui_element.setPos(*new_pos)
print(f"✓ 更新2D GUI元素位置: {axis}={value} (约束后位置: {new_pos})")
return True
else:
print(f"✗ 不支持的GUI类型进行2D位置编辑: {gui_type}")
return False
except Exception as e:
print(f"✗ 更新2D GUI元素位置失败: {e}")
import traceback
traceback.print_exc()
return False
def editGUI3DPosition(self, gui_element, axis, value):
"""编辑3D GUI元素位置"""
try:
gui_type = gui_element.getTag("gui_type")
if gui_type in ["3d_text", "3d_image"]:
current_pos = gui_element.getPos()
if axis == "x":
new_pos = (value, current_pos.getY(), current_pos.getZ())
elif axis == "y":
new_pos = (current_pos.getX(), value, current_pos.getZ())
elif axis == "z":
new_pos = (current_pos.getX(), current_pos.getY(), value)
else:
return False
gui_element.setPos(*new_pos)
print(f"✓ 更新3D GUI元素位置: {axis}={value}")
return True
else:
print(f"✗ 不支持的GUI类型进行3D位置编辑: {gui_type}")
return False
except Exception as e:
print(f"✗ 更新3D GUI元素位置失败: {e}")
import traceback
traceback.print_exc()
return False
def editGUIScale(self, gui_element, axis, value):
"""编辑GUI元素缩放"""
try:
gui_type = gui_element.getTag("gui_type")
current_scale = gui_element.getScale()
# 确保缩放值不为0
if value == 0:
value = 0.01
if gui_type in ["3d_text", "3d_image"]:
# 3D元素处理
if axis == "x":
new_scale = (value, current_scale.getY(), current_scale.getZ())
elif axis == "y":
new_scale = (current_scale.getX(), value, current_scale.getZ())
elif axis == "z":
new_scale = (current_scale.getX(), current_scale.getY(), value)
else:
return False
gui_element.setScale(*new_scale)
elif gui_type == "2d_image":
# 2D图像特殊处理 - 分别控制宽度和高度
if axis == "x":
# X轴控制宽度
gui_element.setScale(value, current_scale.getZ() if hasattr(current_scale, 'getZ')
else current_scale[1] if isinstance(current_scale, (list, tuple))
else current_scale)
elif axis == "z":
# Z轴控制高度
gui_element.setScale(current_scale.getX() if hasattr(current_scale, 'getX')
else current_scale[0] if isinstance(current_scale, (list, tuple))
else current_scale, value)
else:
# 其他情况使用统一缩放
gui_element.setScale(value)
gui_element.setTransparency(TransparencyAttrib.MAlpha)
else:
# 其他2D元素处理
if axis in ["x", "z"]: # 对于2D图像x和z分别代表宽度和高度
# 保持原有缩放比例,仅调整指定轴
if axis == "x":
gui_element.setScale(value,
current_scale.getZ() if hasattr(current_scale, 'getZ')
else current_scale[1] if isinstance(current_scale, (list, tuple))
else current_scale)
elif axis == "z":
gui_element.setScale(
current_scale.getX() if hasattr(current_scale, 'getX')
else current_scale[0] if isinstance(current_scale, (list, tuple))
else current_scale, value)
else:
# 对于其他2D元素使用统一缩放
gui_element.setScale(value)
print(f"✓ 更新GUI元素缩放: {axis}={value}")
return True
except Exception as e:
print(f"✗ 更新GUI元素缩放失败: {e}")
import traceback
traceback.print_exc()
return False
def createCesiumView(self, main_window=None):
"""创建 Cesium 视图窗口(离线版本)"""

54
main.py
View File

@ -19,6 +19,7 @@ from core.vr_manager import VRManager
from core.vr_input_handler import VRInputHandler
from core.alvr_streamer import ALVRStreamer
from gui.gui_manager import GUIManager
from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager
from project.project_manager import ProjectManager
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
@ -84,6 +85,7 @@ class MyWorld(CoreWorld):
self.script_manager.start_system()
#self.material_editor = None
self.terrain_manager = TerrainManager(self)
print("✓ MyWorld 初始化完成")
@ -164,6 +166,17 @@ class MyWorld(CoreWorld):
"""树形控件的兼容性设置器"""
self.interface_manager.treeWidget = value
#保留terrains属性以兼容现有代码
@property
def terrains(self):
"""地形列表的兼容性属性"""
return self.terrain_manager.terrains
@terrains.setter
def terrains(self,value):
"""地形列表的兼容性设置器"""
self.terrain_manager.terrains = value
# ==================== GUI管理功能代理 ====================
# GUI元素创建方法 - 代理到gui_manager
@ -187,11 +200,15 @@ class MyWorld(CoreWorld):
"""创建3D图片"""
return self.gui_manager.createGUI3DImage(pos,text,size)
def createSpotLight(self,pos=(-20,0,5)):
def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2):
"""创建2D GUI图片"""
return self.gui_manager.createGUI2DImage(pos, image_path, size)
def createSpotLight(self,pos=(0,0,5)):
"""创建聚光灯"""
return self.scene_manager.createSpotLight(pos)
def createPointLight(self,pos=(20,0,5)):
def createPointLight(self,pos=(0,0,5)):
"""创建点光源"""
return self.scene_manager.createPointLight(pos)
@ -686,6 +703,28 @@ class MyWorld(CoreWorld):
else:
return self.scene_manager.load_cesium_tileset(url,position)
# ==================== 地形管理功能代理 ====================
# 地形创建方法 - 代理到terrain_manager
def createTerrainFromHeightMap(self, heightmap_path, scale=(1, 1, 1)):
"""从高度图创建地形"""
return self.terrain_manager.createTerrainFromHeightMap(heightmap_path, scale)
def createFlatTerrain(self, size=(0.3, 0.3), resolution=256):
"""创建平面地形"""
return self.terrain_manager.createFlatTerrain(size, resolution)
def updateTerrain(self):
"""更新所有地形的LOD"""
return self.terrain_manager.updateTerrain()
def modifyTerrainHeight(self, terrain_info, x, y, radius, strength, operation="add"):
"""修改地形高度"""
return self.terrain_manager.modifyTerrainHeight(terrain_info, x, y, radius, strength, operation)
def getTerrainHeight(self, terrain_info, x, y):
"""获取地形上指定点的高度"""
return self.terrain_manager.getTerrainHeight(terrain_info, x, y)
# ==================== 项目管理功能代理 ====================
# 以下函数代理到project_manager模块的对应功能
@ -716,17 +755,6 @@ def buildPackage(appw):
return world.project_manager.buildPackage(appw)
# def open_material_editor(self):
# """打开材质编辑器作为子窗口"""
# if not self.material_editor:
# self.material_editor = MaterialEditor()
# # 设置为子窗口
# self.material_editor.setParent(self)
# self.material_editor.setWindowFlags(Qt.Window)
#
# self.material_editor.show()
#
if __name__ == "__main__":
world = MyWorld()

View File

@ -667,20 +667,20 @@ class SceneManager:
cNode = CollisionNode(f'modelCollision_{model.getName()}')
# 设置碰撞掩码
cNode.setIntoCollideMask(BitMask32.bit(2)) # 使用第2位作为模型的碰撞掩码
# 获取模型的边界
bounds = model.getBounds()
center = bounds.getCenter()
radius = bounds.getRadius()
# 添加碰撞球体
cSphere = CollisionSphere(center, radius)
cNode.addSolid(cSphere)
# 将碰撞节点附加到模型上
cNodePath = model.attachNewNode(cNode)
#cNodePath.hide()
# cNodePath.show() # 取消注释可以显示碰撞体,用于调试
#cNodePath.show() # 取消注释可以显示碰撞体,用于调试
# ==================== 场景树管理 ====================
@ -943,34 +943,27 @@ class SceneManager:
render_pipeline = get_render_pipeline()
# 创建一个挂载节点(你控制的)
light_np = NodePath("SpotlightAttachNode")
# 为每个灯光创建独立的节点
light_np = NodePath(f"Spotlight_{len(self.Spotlight)}")
light_np.reparentTo(self.world.render)
#light_np.setPos(*pos)
self.half_energy = 5000
self.lamp_fov = 70
self.lamp_radius = 1000
light_np.setPos(*pos)
# 创建独立的灯光对象
light = SpotLight()
light.direction = Vec3(0, 0, -1) # 光照方向
light.fov = self.lamp_fov # 光源角度(类似手电筒)
light.fov = self.lamp_fov if hasattr(self, 'lamp_fov') else 70 # 光源角度
light.set_color_from_temperature(5 * 1000.0) # 色温K
light.energy = self.half_energy # 光照强度
light.radius = self.lamp_radius # 影响范围
light.energy = self.half_energy if hasattr(self, 'half_energy') else 5000 # 光照强度
light.radius = self.lamp_radius if hasattr(self, 'lamp_radius') else 1000 # 影响范围
light.casts_shadows = True # 是否投射阴影
light.shadow_map_resolution = 256 # 阴影分辨率
light.setPos(*pos)
#light_np.setPos(*pos)
#light_np = render_pipeline.add_light(light, parent=self.world.render)
render_pipeline.add_light(light) # 添加到渲染管线
light_name = f"Spotlight_{len(self.Spotlight)}"
light_np.setName(light_name) # 设置唯一名称
#light_np.reparentTo(self.world.render) # 挂载到场景根节点
#light_name = f"Spotlight_{len(self.Spotlight)}"
#light_np.setName(light_name) # 设置唯一名称
light_np.setTag("light_type", "spot_light")
light_np.setTag("is_scene_element", "1")
light_np.setTag("light_energy", str(light.energy))
@ -982,7 +975,7 @@ class SceneManager:
if hasattr(self.world, 'updateSceneTree'):
self.world.updateSceneTree()
#print("nikan"+light_np.getHpr())
return light,light_np
def createPointLight(self, pos=(0, 0, 0)):
from RenderPipelineFile.rpcore import PointLight, RenderPipeline
@ -990,14 +983,14 @@ class SceneManager:
render_pipeline = get_render_pipeline()
# 创建一个挂载节点(你控制的)
light_np = NodePath("PointlightAttachNode")
# 为每个灯光创建独立的节点
light_np = NodePath(f"Pointlight_{len(self.Pointlight)}")
light_np.reparentTo(self.world.render)
light_np.setPos(*pos)
# 创建独立的灯光对象
light = PointLight()
light.setPos(*pos)
light_np.setPos(*pos)
light.energy = 5000
light.radius = 1000
light.inner_radius = 0.4
@ -1005,30 +998,27 @@ class SceneManager:
light.casts_shadows = True # 是否投射阴影
light.shadow_map_resolution = 256 # 阴影分辨率
render_pipeline.add_light(light) # 添加到渲染管线
light_name = f"Pointlight{len(self.Pointlight)}"
light_np.setName(light_name) # 设置唯一名称
#light_np = NodePath(f"PointLight_{len(self.Pointlight)}")
#light_np.reparentTo(self.world.render)
#light_np.setPos(*pos)
# 添加到渲染管线
render_pipeline.add_light(light)
# 设置标签和Python标签
light_np.setTag("light_type", "point_light")
light_np.setTag("is_scene_element", "1")
light_np.setTag("light_energy", str(light.energy))
# 保存光源对象引用(重要!用于属性面板
# 保存光源对象引用(重要!用于属性面板和删除操作)
light_np.setPythonTag("rp_light_object", light)
# 添加到灯光列表
self.Pointlight.append(light_np)
# 更新场景树
if hasattr(self.world, 'updateSceneTree'):
self.world.updateSceneTree()
return light,light_np
print(f"创建点光源: {light_np.getName()}")
return light, light_np
# ==================== GLB 转换方法 ====================
def _shouldConvertToGLB(self, filepath):
@ -1486,42 +1476,66 @@ except Exception as e:
normal = GeomVertexWriter(vdata, 'normal')
color = GeomVertexWriter(vdata, 'color')
# 定义立方体顶点(稍微大一些,便于识别)
# 定义立方体顶点
size = 1.0
vertices = [
(-size, -size, -size), (size, -size, -size), (size, size, -size), (-size, size, -size),
(-size, -size, size), (size, -size, size), (size, size, size), (-size, size, size)
# 前面 (Z+)
(-size, -size, size), (size, -size, size), (size, size, size), (-size, size, size),
# 后面 (Z-)
(-size, -size, -size), (-size, size, -size), (size, size, -size), (size, -size, -size),
# 左面 (X-)
(-size, -size, -size), (-size, -size, size), (-size, size, size), (-size, size, -size),
# 右面 (X+)
(size, -size, -size), (size, size, -size), (size, size, size), (size, -size, size),
# 上面 (Y+)
(-size, size, -size), (-size, size, size), (size, size, size), (size, size, -size),
# 下面 (Y-)
(-size, -size, -size), (size, -size, -size), (size, -size, size), (-size, -size, size)
]
# 使用更鲜明的颜色
for vert in vertices:
normals = [
# 前面法线
(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1),
# 后面法线
(0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1),
# 左面法线
(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0),
# 右面法线
(1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0),
# 上面法线
(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0),
# 下面法线
(0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0)
]
# 青色
face_colors = [
(0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), # 前面 - 青色
(0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), # 后面 - 稍暗青色
(0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), # 左面 - 中等青色
(0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), # 右面 - 稍暗青色
(0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), # 上面 - 青色
(0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0) # 下面 - 更暗青色
]
for i, vert in enumerate(vertices):
vertex.addData3f(*vert)
normal.addData3f(0, 0, 1)
color.addData4f(0.0, 1.0, 1.0, 1.0) # 青色
normal.addData3f(*normals[i])
color.addData4f(*face_colors[i])
# 创建几何体
geom = Geom(vdata)
# 创建面
# 创建面(每个面两个三角形)
prim = GeomTriangles(Geom.UHStatic)
# 底面
prim.addVertices(0, 1, 2)
prim.addVertices(0, 2, 3)
# 顶面
prim.addVertices(4, 7, 6)
prim.addVertices(4, 6, 5)
# 前面
prim.addVertices(0, 4, 5)
prim.addVertices(0, 5, 1)
# 后面
prim.addVertices(2, 6, 7)
prim.addVertices(2, 7, 3)
# 左面
prim.addVertices(0, 3, 7)
prim.addVertices(0, 7, 4)
# 右面
prim.addVertices(1, 5, 6)
prim.addVertices(1, 6, 2)
# 每个面4个顶点生成2个三角形
for face in range(6): # 6个面
base_index = face * 4
# 第一个三角形
prim.addVertices(base_index, base_index + 1, base_index + 2)
# 第二个三角形
prim.addVertices(base_index, base_index + 2, base_index + 3)
prim.closePrimitive()
geom.addPrimitive(prim)
@ -1532,7 +1546,10 @@ except Exception as e:
# 添加到场景
cube_node = parent_node.attachNewNode(geom_node)
cube_node.setScale(5) # 适当大小
cube_node.setScale(5) # 设置合适的大小
# 设置双面渲染
cube_node.setTwoSided(True)
# 添加材质
material = Material()

View File

@ -1,4 +1,4 @@
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu, QStyle
from PyQt5.QtCore import Qt
from PyQt5.sip import delete
from panda3d.core import GeomNode, ModelRoot
@ -199,15 +199,6 @@ class InterfaceManager:
cameraItem.setData(0, Qt.UserRole, self.world.cam)
print("添加相机节点")
# # 添加模型节点组
# modelsItem = QTreeWidgetItem(sceneRoot, ['模型'])
# print(f"模型列表中的模型数量: {len(self.world.models)}")
#
# # 添加GUI元素节点组
# guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
#
# lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
BLACK_LIST = {'','**','temp','collision'}
from panda3d.core import CollisionNode
@ -224,35 +215,20 @@ class InterfaceManager:
for child in node.getChildren():
addNodeToTree(child,nodeItem,force = False)
# 递归添加节点及其子节点
# def addNodeToTree(node, parentItem):
# print(f"\n处理节点: {node.getName()}")
# # 创建节点项
# nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
# nodeItem.setData(0, Qt.UserRole, node)
# print(f"添加节点: {node.getName()}")
#
# # 递归处理所有子节点
# for child in node.getChildren():
# # 检查是否是有效的模型节点
# if (isinstance(child.node(), GeomNode) or
# child.hasTag("file") or
# child.getName() == "RootNode" or
# isinstance(child.node(), ModelRoot)):
# print(f"处理子节点: {child.getName()}")
# addNodeToTree(child, nodeItem)
# else:
# print(f"跳过节点: {child.getName()}")
for model in self.world.models:
addNodeToTree(model, sceneRoot,force=True)
# 添加所有GUI元素
for gui in self.world.gui_elements:
gui_type = gui.getTag("gui_type") or "unknown"
gui_text = gui.getTag("gui_text") or "GUI元素"
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
item.setData(0, Qt.UserRole, gui)
# 检查是否是有效的GUI节点具有getTag方法的NodePath
if hasattr(gui, 'getTag') and hasattr(gui, 'getName'):
gui_type = gui.getTag("gui_type") or "unknown"
gui_text = gui.getTag("text") or gui.getTag("gui_text") or "GUI元素"
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
item.setData(0, Qt.UserRole, gui)
else:
# 跳过非GUI节点如QDockWidget等Qt对象
continue
#添加灯光节点
for light in self.world.Spotlight + self.world.Pointlight:
addNodeToTree(light, sceneRoot, force=True)
@ -265,6 +241,16 @@ class InterfaceManager:
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
tileset_item.setData(0,Qt.UserRole,tileset_node)
addNodeToTree(tileset_node,tileset_item,force=True)
#添加地形节点
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
for terrain_info in self.world.terrain_manager.terrains:
terrain_node = terrain_info['node']
terrain_name = terrain_info.get('name','未知地形')
terrain_item = QTreeWidgetItem(sceneRoot,[f"地形:{terrain_name}"])
terrain_item.setData(0,Qt.UserRole,terrain_node)
terrain_item.setData(0,Qt.UserRole+1,"terrain") # 标记为地形节点
addNodeToTree(terrain_node,terrain_item,force=True)
# 添加地板节点
if hasattr(self.world, 'ground') and self.world.ground:

View File

@ -12,7 +12,8 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget,QDialog)
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog,
QSpinBox)
from PyQt5.QtCore import Qt, QDir, QTimer
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
@ -115,6 +116,13 @@ class MainWindow(QMainWindow):
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
self.createPointLightAction = self.createLightaddMenu.addAction('点光源')
#添加地形菜单
self.createTerrainMenu = self.createMenu.addMenu('地形')
self.createFlatTerrainAction = self.createTerrainMenu.addAction('创建平面地形')
self.createHeightmapTerrainAction = self.createTerrainMenu.addAction('从高度图创建地形')
self.createTerrainMenu.addSeparator()
self.terrainEditModeAction = self.createTerrainMenu.addAction('地形编辑模式')
# GUI菜单
self.guiMenu = menubar.addMenu('GUI')
self.guiEditModeAction = self.guiMenu.addAction('进入GUI编辑模式')
@ -275,6 +283,10 @@ class MainWindow(QMainWindow):
self.create3DImageTool.setText("3D图片")
self.toolbar.addWidget(self.create3DImageTool)
self.create2DImageTool = QToolButton()
self.create2DImageTool.setText("2D图片")
self.toolbar.addWidget(self.create2DImageTool)
self.createSpotLight = QToolButton()
self.createSpotLight.setText("聚光灯")
self.toolbar.addWidget(self.createSpotLight)
@ -299,6 +311,21 @@ class MainWindow(QMainWindow):
self.addModelTool.clicked.connect(self.onAddModelClicked)
self.toolbar.addWidget(self.addModelTool)
#地形
# 地形工具
self.createFlatTerrainTool = QToolButton()
self.createFlatTerrainTool.setText("平面地形")
self.toolbar.addWidget(self.createFlatTerrainTool)
self.createHeightmapTerrainTool = QToolButton()
self.createHeightmapTerrainTool.setText("高度图地形")
self.toolbar.addWidget(self.createHeightmapTerrainTool)
self.terrainEditTool = QToolButton()
self.terrainEditTool.setText("地形编辑")
self.terrainEditTool.setCheckable(True)
self.toolbar.addWidget(self.terrainEditTool)
# 默认选择"选择"工具
self.selectTool.setChecked(True)
self.world.setCurrentTool("选择")
@ -458,14 +485,25 @@ class MainWindow(QMainWindow):
self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView)
self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView)
self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView)
#连接地形创建事件
self.createFlatTerrainAction.triggered.connect(self.onCreateFlatTerrain)
self.createHeightmapTerrainAction.triggered.connect(self.onCreateHeightmapTerrain)
self.terrainEditModeAction.triggered.connect(self.onTerrainEditMode)
# 连接工具栏GUI创建按钮事件
self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton())
self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel())
self.create3DTextTool.clicked.connect(lambda: self.world.createGUI3DText())
self.create3DImageTool.clicked.connect(lambda: self.world.createGUI3DImage())
self.create2DImageTool.clicked.connect(lambda: self.world.createGUI2DImage())
self.createSpotLight.clicked.connect(lambda :self.world.createSpotLight())
self.createPointLight.clicked.connect(lambda :self.world.createPointLight())
# 连接工具栏地形创建按钮事件
self.createFlatTerrainTool.clicked.connect(self.onCreateFlatTerrain)
self.createHeightmapTerrainTool.clicked.connect(self.onCreateHeightmapTerrain)
self.terrainEditTool.clicked.connect(self.onTerrainEditMode)
# 连接树节点点击信号
# self.treeWidget.itemClicked.connect(self.world.onTreeItemClicked)
@ -955,7 +993,158 @@ class MainWindow(QMainWindow):
print(f"⚠️ 关闭应用程序时出错: {e}")
event.accept() # 即使出错也要关闭
def onCreateFlatTerrain(self):
"""创建平面地形"""
dialog = QDialog(self)
dialog.setWindowTitle("创建平面地形")
dialog.setModal(True)
dialog.resize(300,200)
layout = QVBoxLayout(dialog)
width_layout = QHBoxLayout()
width_layout.addWidget(QLabel("宽度:"))
width_spin = QDoubleSpinBox()
width_spin.setRange(1,10000)
width_spin.setValue(100)
width_layout.addWidget(width_spin)
layout.addLayout(width_layout)
# 高度
height_layout = QHBoxLayout()
height_layout.addWidget(QLabel("高度:"))
height_spin = QDoubleSpinBox()
height_spin.setRange(1, 10000)
height_spin.setValue(100)
height_layout.addWidget(height_spin)
layout.addLayout(height_layout)
# 分辨率
resolution_layout = QHBoxLayout()
resolution_layout.addWidget(QLabel("分辨率:"))
resolution_spin = QSpinBox()
resolution_spin.setRange(16, 2048)
resolution_spin.setValue(256)
resolution_spin.setSingleStep(16)
resolution_layout.addWidget(resolution_spin)
layout.addLayout(resolution_layout)
# 按钮
button_layout = QHBoxLayout()
ok_button = QPushButton("创建")
cancel_button = QPushButton("取消")
button_layout.addWidget(ok_button)
button_layout.addWidget(cancel_button)
layout.addLayout(button_layout)
# 连接信号
ok_button.clicked.connect(dialog.accept)
cancel_button.clicked.connect(dialog.reject)
# 显示对话框
if dialog.exec_() == QDialog.Accepted:
width = width_spin.value()
height = height_spin.value()
resolution = resolution_spin.value()
# 调用世界对象创建地形
terrain_info = self.world.createFlatTerrain((width, height), resolution)
if terrain_info:
QMessageBox.information(self, "成功", "平面地形创建成功!")
else:
QMessageBox.warning(self, "错误", "平面地形创建失败!")
def onCreateHeightmapTerrain(self):
"""从高度图创建地形"""
file_path,_=QFileDialog.getOpenFileName(
self,
"选择高度图文件",
"",
"图像文件 (*.png *.jpg *.jpeg *.bmp *.tga);;所有文件 (*)"
)
if file_path:
#创建对话框获取地形参数
dialog = QDialog(self)
dialog.setWindowTitle("设置地形参数")
dialog.setModal(True)
dialog.resize(300,250)
layout = QVBoxLayout(dialog)
x_scale_layout = QHBoxLayout()
x_scale_layout.addWidget(QLabel("X缩放"))
x_scale_spin = QDoubleSpinBox()
x_scale_spin.setRange(0.1,1000)
x_scale_spin.setValue(0.3)
x_scale_spin.setSingleStep(10)
x_scale_layout.addWidget(x_scale_spin)
layout.addLayout(x_scale_layout)
# Y缩放
y_scale_layout = QHBoxLayout()
y_scale_layout.addWidget(QLabel("Y缩放:"))
y_scale_spin = QDoubleSpinBox()
y_scale_spin.setRange(0.1, 1000)
y_scale_spin.setValue(0.3)
y_scale_spin.setSingleStep(10)
y_scale_layout.addWidget(y_scale_spin)
layout.addLayout(y_scale_layout)
# Z缩放
z_scale_layout = QHBoxLayout()
z_scale_layout.addWidget(QLabel("Z缩放:"))
z_scale_spin = QDoubleSpinBox()
z_scale_spin.setRange(0.1, 1000)
z_scale_spin.setValue(50)
z_scale_spin.setSingleStep(5)
z_scale_layout.addWidget(z_scale_spin)
layout.addLayout(z_scale_layout)
# 按钮
button_layout = QHBoxLayout()
ok_button = QPushButton("创建")
cancel_button = QPushButton("取消")
button_layout.addWidget(ok_button)
button_layout.addWidget(cancel_button)
layout.addLayout(button_layout)
# 连接信号
ok_button.clicked.connect(dialog.accept)
cancel_button.clicked.connect(dialog.reject)
# 显示对话框
if dialog.exec_() == QDialog.Accepted:
x_scale = x_scale_spin.value()
y_scale = y_scale_spin.value()
z_scale = z_scale_spin.value()
# 调用世界对象创建地形
terrain_info = self.world.createTerrainFromHeightMap(
file_path,
(x_scale, y_scale, z_scale)
)
if terrain_info:
QMessageBox.information(self, "成功", "高度图地形创建成功!")
else:
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
def onTerrainEditMode(self):
"""地形编辑模式"""
# 检查当前是否已经处于地形编辑模式
if self.world.currentTool == "地形编辑":
# 退出地形编辑模式
self.world.setCurrentTool(None)
self.terrainEditTool.setChecked(False)
self.terrainEditTool.setText("地形编辑")
QMessageBox.information(self, "地形编辑", "已退出地形编辑模式")
else:
# 进入地形编辑模式
self.world.setCurrentTool("地形编辑")
self.terrainEditTool.setChecked(True)
self.terrainEditTool.setText("退出地形编辑")
QMessageBox.information(self, "地形编辑",
"已进入地形编辑模式\n\n使用鼠标左键抬高地形\n使用鼠标右键降低地形")
def setup_main_window(world):
"""设置主窗口的便利函数"""
app = QApplication.instance()
@ -965,4 +1154,4 @@ def setup_main_window(world):
main_window = MainWindow(world)
main_window.show()
return app, main_window
return app, main_window

File diff suppressed because it is too large Load Diff