forked from Rowland/EG
317 lines
11 KiB
Python
317 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
PANDA3D 3D空间GUI演示
|
||
展示如何在3D场景中使用GUI元素
|
||
"""
|
||
|
||
from direct.showbase.ShowBase import ShowBase
|
||
from direct.gui.DirectGui import *
|
||
from direct.gui.OnscreenText import OnscreenText
|
||
from direct.gui.OnscreenImage import OnscreenImage
|
||
from panda3d.core import *
|
||
from direct.task import Task
|
||
|
||
class GUI3DDemo(ShowBase):
|
||
def __init__(self):
|
||
ShowBase.__init__(self)
|
||
|
||
# 设置背景色
|
||
self.setBackgroundColor(0.1, 0.1, 0.3)
|
||
|
||
# 设置相机位置
|
||
self.cam.setPos(0, -10, 0)
|
||
self.cam.lookAt(0, 0, 0)
|
||
|
||
# 尝试加载中文字体
|
||
self.chinese_font = self.loadChineseFont()
|
||
|
||
# 创建标题(2D界面)
|
||
self.title = OnscreenText(
|
||
text="PANDA3D 3D空间GUI演示",
|
||
pos=(0, 0.9),
|
||
scale=0.08,
|
||
fg=(1, 1, 1, 1),
|
||
align=TextNode.ACenter,
|
||
font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 创建基本的2D GUI
|
||
self.create2DGUI()
|
||
|
||
# 创建3D场景中的GUI元素
|
||
self.create3DGUI()
|
||
|
||
# 加载一个简单的3D模型作为背景
|
||
self.createScene()
|
||
|
||
print("3D GUI演示启动成功!")
|
||
print(f"中文字体加载状态: {'成功' if self.chinese_font else '失败,使用默认字体'}")
|
||
print("你可以看到2D界面元素和3D空间中的GUI")
|
||
|
||
def loadChineseFont(self):
|
||
"""尝试加载中文字体"""
|
||
import os
|
||
font_paths = [
|
||
'/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
|
||
'/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
|
||
'/usr/share/fonts/truetype/arphic/ukai.ttc',
|
||
'/usr/share/fonts/truetype/arphic/uming.ttc',
|
||
]
|
||
|
||
for font_path in font_paths:
|
||
if os.path.exists(font_path):
|
||
try:
|
||
font = self.loader.loadFont(font_path)
|
||
if font:
|
||
return font
|
||
except:
|
||
continue
|
||
return None
|
||
|
||
def create2DGUI(self):
|
||
"""创建传统的2D GUI元素"""
|
||
# 控制面板标题
|
||
self.controlTitle = OnscreenText(
|
||
text="控制面板",
|
||
pos=(-1.2, 0.8),
|
||
scale=0.06,
|
||
fg=(1, 1, 0, 1),
|
||
align=TextNode.ALeft,
|
||
font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 旋转控制按钮
|
||
self.rotateButton = DirectButton(
|
||
text="旋转场景",
|
||
pos=(-1.2, 0, 0.6),
|
||
scale=0.05,
|
||
command=self.toggleRotation,
|
||
frameColor=(0.2, 0.6, 0.8, 1),
|
||
text_font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 颜色变化按钮
|
||
self.colorButton = DirectButton(
|
||
text="变换颜色",
|
||
pos=(-1.2, 0, 0.4),
|
||
scale=0.05,
|
||
command=self.changeColors,
|
||
frameColor=(0.8, 0.2, 0.6, 1),
|
||
text_font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 重置按钮
|
||
self.resetButton = DirectButton(
|
||
text="重置",
|
||
pos=(-1.2, 0, 0.2),
|
||
scale=0.05,
|
||
command=self.resetScene,
|
||
frameColor=(0.6, 0.8, 0.2, 1),
|
||
text_font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 状态显示
|
||
self.statusText = OnscreenText(
|
||
text="状态: 就绪",
|
||
pos=(-1.2, -0.8),
|
||
scale=0.04,
|
||
fg=(0, 1, 0, 1),
|
||
align=TextNode.ALeft,
|
||
font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 退出按钮
|
||
self.exitButton = DirectButton(
|
||
text="退出",
|
||
pos=(1.2, 0, -0.8),
|
||
scale=0.05,
|
||
command=self.exitDemo,
|
||
frameColor=(0.8, 0.2, 0.2, 1),
|
||
text_font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
def create3DGUI(self):
|
||
"""创建3D空间中的GUI元素"""
|
||
# 创建一个在3D空间中的按钮
|
||
# 这实际上是一个2D GUI元素,但我们可以将其父级设置为3D节点
|
||
|
||
# 创建3D空间中的文本
|
||
self.text3D = TextNode('3d-text')
|
||
self.text3D.setText("3D空间中的文本")
|
||
self.text3D.setAlign(TextNode.ACenter)
|
||
if self.chinese_font:
|
||
self.text3D.setFont(self.chinese_font)
|
||
self.textNodePath = self.render.attachNewNode(self.text3D)
|
||
self.textNodePath.setPos(0, 5, 2)
|
||
self.textNodePath.setScale(0.5)
|
||
self.textNodePath.setColor(1, 1, 0, 1)
|
||
self.textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||
|
||
# 创建另一个3D文本,显示说明
|
||
self.info3D = TextNode('info-text')
|
||
self.info3D.setText("这是3D空间中的GUI元素\n它们可以与3D场景交互")
|
||
self.info3D.setAlign(TextNode.ACenter)
|
||
if self.chinese_font:
|
||
self.info3D.setFont(self.chinese_font)
|
||
self.infoNodePath = self.render.attachNewNode(self.info3D)
|
||
self.infoNodePath.setPos(0, 3, -1)
|
||
self.infoNodePath.setScale(0.3)
|
||
self.infoNodePath.setColor(0, 1, 1, 1)
|
||
self.infoNodePath.setBillboardAxis()
|
||
|
||
# 创建一个3D平面作为"虚拟屏幕"
|
||
self.createVirtualScreen()
|
||
|
||
def createVirtualScreen(self):
|
||
"""创建一个3D空间中的虚拟屏幕"""
|
||
# 创建一个卡片(平面)
|
||
cm = CardMaker("virtual-screen")
|
||
cm.setFrame(-2, 2, -1, 1)
|
||
self.virtualScreen = self.render.attachNewNode(cm.generate())
|
||
self.virtualScreen.setPos(3, 5, 0)
|
||
self.virtualScreen.setColor(0.2, 0.2, 0.2, 0.8)
|
||
self.virtualScreen.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 在虚拟屏幕上添加文本
|
||
screenText = TextNode('screen-text')
|
||
screenText.setText("虚拟3D屏幕\n\n这里可以显示\n任何信息")
|
||
screenText.setAlign(TextNode.ACenter)
|
||
if self.chinese_font:
|
||
screenText.setFont(self.chinese_font)
|
||
screenTextNP = self.virtualScreen.attachNewNode(screenText)
|
||
screenTextNP.setPos(0, 0.01, 0) # 稍微偏移,避免z-fighting
|
||
screenTextNP.setScale(0.3)
|
||
screenTextNP.setColor(0, 1, 0, 1)
|
||
|
||
def createScene(self):
|
||
"""创建3D场景"""
|
||
# 创建一个简单的立方体
|
||
from panda3d.core import CardMaker
|
||
|
||
# 创建地面
|
||
cm = CardMaker("ground")
|
||
cm.setFrame(-10, 10, -10, 10)
|
||
ground = self.render.attachNewNode(cm.generate())
|
||
ground.setP(-90) # 旋转成水平面
|
||
ground.setColor(0.5, 0.5, 0.5, 1)
|
||
|
||
# 创建一些立方体
|
||
self.cubes = []
|
||
for i in range(3):
|
||
for j in range(3):
|
||
# 使用内置的立方体几何
|
||
cube = self.loader.loadModel("models/environment")
|
||
if not cube:
|
||
# 如果找不到模型,创建一个简单的几何体
|
||
cube = self.render.attachNewNode("cube")
|
||
|
||
cube.reparentTo(self.render)
|
||
cube.setPos(-2 + i*2, 2 + j*2, 0.5)
|
||
cube.setScale(0.5)
|
||
cube.setColor(
|
||
0.2 + i*0.3,
|
||
0.2 + j*0.3,
|
||
0.5,
|
||
1
|
||
)
|
||
self.cubes.append(cube)
|
||
|
||
# 添加光照
|
||
dlight = DirectionalLight('dlight')
|
||
dlight.setColor((1, 1, 1, 1))
|
||
dlnp = self.render.attachNewNode(dlight)
|
||
dlnp.setHpr(45, -45, 0)
|
||
self.render.setLight(dlnp)
|
||
|
||
alight = AmbientLight('alight')
|
||
alight.setColor((0.3, 0.3, 0.3, 1))
|
||
alnp = self.render.attachNewNode(alight)
|
||
self.render.setLight(alnp)
|
||
|
||
# 初始化旋转状态
|
||
self.rotating = False
|
||
self.colorIndex = 0
|
||
|
||
def toggleRotation(self):
|
||
"""切换场景旋转"""
|
||
if self.rotating:
|
||
self.taskMgr.remove("rotate-scene")
|
||
self.rotating = False
|
||
self.rotateButton['text'] = "旋转场景"
|
||
self.statusText.setText("状态: 停止旋转")
|
||
else:
|
||
self.taskMgr.add(self.rotateSceneTask, "rotate-scene")
|
||
self.rotating = True
|
||
self.rotateButton['text'] = "停止旋转"
|
||
self.statusText.setText("状态: 正在旋转")
|
||
|
||
print(f"旋转状态: {'开启' if self.rotating else '关闭'}")
|
||
|
||
def rotateSceneTask(self, task):
|
||
"""旋转场景的任务"""
|
||
for cube in self.cubes:
|
||
cube.setH(cube.getH() + 50 * globalClock.getDt())
|
||
|
||
# 旋转3D文本
|
||
self.textNodePath.setH(self.textNodePath.getH() + 30 * globalClock.getDt())
|
||
self.virtualScreen.setH(self.virtualScreen.getH() + 20 * globalClock.getDt())
|
||
|
||
return task.cont
|
||
|
||
def changeColors(self):
|
||
"""改变场景颜色"""
|
||
self.colorIndex = (self.colorIndex + 1) % 3
|
||
|
||
colors = [
|
||
[(1, 0.5, 0.5), (0.5, 1, 0.5), (0.5, 0.5, 1)], # 红绿蓝
|
||
[(1, 1, 0.5), (1, 0.5, 1), (0.5, 1, 1)], # 黄洋红青
|
||
[(0.8, 0.2, 0.8), (0.2, 0.8, 0.8), (0.8, 0.8, 0.2)] # 紫青黄
|
||
]
|
||
|
||
colorSet = colors[self.colorIndex]
|
||
|
||
for i, cube in enumerate(self.cubes):
|
||
color = colorSet[i % 3]
|
||
cube.setColor(*color, 1)
|
||
|
||
# 改变3D文本颜色
|
||
textColors = [(1, 1, 0), (0, 1, 1), (1, 0, 1)]
|
||
self.textNodePath.setColor(*textColors[self.colorIndex], 1)
|
||
|
||
self.statusText.setText(f"状态: 颜色方案 {self.colorIndex + 1}")
|
||
print(f"切换到颜色方案 {self.colorIndex + 1}")
|
||
|
||
def resetScene(self):
|
||
"""重置场景"""
|
||
# 停止旋转
|
||
if self.rotating:
|
||
self.toggleRotation()
|
||
|
||
# 重置立方体位置和颜色
|
||
for i, cube in enumerate(self.cubes):
|
||
row = i // 3
|
||
col = i % 3
|
||
cube.setPos(-2 + col*2, 2 + row*2, 0.5)
|
||
cube.setHpr(0, 0, 0)
|
||
cube.setColor(0.2 + col*0.3, 0.2 + row*0.3, 0.5, 1)
|
||
|
||
# 重置3D文本
|
||
self.textNodePath.setHpr(0, 0, 0)
|
||
self.textNodePath.setColor(1, 1, 0, 1)
|
||
self.virtualScreen.setHpr(0, 0, 0)
|
||
|
||
self.colorIndex = 0
|
||
self.statusText.setText("状态: 场景已重置")
|
||
print("场景已重置")
|
||
|
||
def exitDemo(self):
|
||
"""退出演示"""
|
||
print("退出3D GUI演示")
|
||
self.userExit()
|
||
|
||
if __name__ == "__main__":
|
||
print("启动PANDA3D 3D GUI演示...")
|
||
demo = GUI3DDemo()
|
||
demo.run() |