658 lines
24 KiB
Python
658 lines
24 KiB
Python
"""
|
||
VR可视化模块
|
||
|
||
提供VR手柄和交互元素的高级可视化功能:
|
||
- 手柄3D模型渲染
|
||
- 交互射线显示
|
||
- 按钮状态可视化
|
||
- 触摸板和扳机反馈
|
||
"""
|
||
|
||
from panda3d.core import (
|
||
NodePath, GeomNode, LineSegs, CardMaker, Geom, GeomVertexData,
|
||
GeomVertexFormat, GeomVertexWriter, GeomTriangles, GeomPoints,
|
||
Vec3, Vec4, Mat4, TransparencyAttrib, RenderState, ColorAttrib,
|
||
InternalName, loadPrcFileData
|
||
)
|
||
from panda3d.core import Texture, Material, TextureStage
|
||
|
||
# 启用Assimp支持OBJ文件加载
|
||
loadPrcFileData("", "load-file-type p3assimp")
|
||
|
||
|
||
class VRControllerVisualizer:
|
||
"""VR手柄可视化器"""
|
||
|
||
def __init__(self, controller, render_node):
|
||
"""初始化手柄可视化器
|
||
|
||
Args:
|
||
controller: VRController实例
|
||
render_node: 渲染节点
|
||
"""
|
||
self.controller = controller
|
||
self.render = render_node
|
||
|
||
# 可视化节点
|
||
self.visual_node = None
|
||
self.model_node = None
|
||
self.ray_node = None
|
||
self.button_indicator_node = None
|
||
|
||
# 射线参数
|
||
self.ray_length = 10.0
|
||
self.ray_color = Vec4(0.9, 0.9, 0.2, 0.8)
|
||
self.ray_hit_color = Vec4(0.2, 0.9, 0.2, 0.8)
|
||
|
||
# 按钮指示器参数
|
||
self.button_colors = {
|
||
'normal': Vec4(0.3, 0.3, 0.8, 1.0),
|
||
'left': Vec4(0.2, 0.6, 0.9, 1.0),
|
||
'right': Vec4(0.9, 0.3, 0.3, 1.0),
|
||
'trigger': Vec4(0.9, 0.6, 0.2, 1.0),
|
||
'grip': Vec4(0.6, 0.9, 0.3, 1.0),
|
||
'menu': Vec4(0.9, 0.2, 0.9, 1.0),
|
||
'trackpad': Vec4(0.2, 0.9, 0.9, 1.0)
|
||
}
|
||
|
||
self._create_visual_components()
|
||
|
||
def _create_visual_components(self):
|
||
"""创建可视化组件"""
|
||
if not self.controller.anchor_node:
|
||
return
|
||
|
||
# 创建主可视化节点
|
||
self.visual_node = self.controller.anchor_node.attachNewNode(f'{self.controller.name}_visual')
|
||
|
||
# 创建手柄模型
|
||
self._create_controller_model()
|
||
|
||
# 创建交互射线
|
||
self._create_interaction_ray()
|
||
|
||
# 暂时注释按钮指示器功能,避免额外几何体造成悬空零件
|
||
# self._create_button_indicators()
|
||
|
||
def _create_controller_model(self):
|
||
"""创建手柄3D模型"""
|
||
if not self.visual_node:
|
||
return
|
||
|
||
# 创建模型节点
|
||
self.model_node = self.visual_node.attachNewNode(f'{self.controller.name}_model')
|
||
|
||
# 尝试加载SteamVR官方模型
|
||
steamvr_model = self._load_steamvr_model()
|
||
|
||
if steamvr_model:
|
||
# 使用SteamVR官方模型
|
||
steamvr_model.reparentTo(self.model_node)
|
||
|
||
# 应用SteamVR配置中的正确旋转值,绕Y轴(俯仰轴)旋转90度
|
||
# body组件的rotate_xyz: [5.037,0.0,0.0],再加上绕Y轴旋转90度
|
||
# 右手正确,左手需要反向
|
||
if self.controller.name == 'left':
|
||
# 左手控制器:绕Y轴俯仰+90度(修正反向)
|
||
steamvr_model.setHpr(0, 5.037 + 90, 0)
|
||
else:
|
||
# 右手控制器:绕Y轴俯仰+90度(保持不变)
|
||
steamvr_model.setHpr(0, 5.037 + 90, 0)
|
||
|
||
# 设置合适的缩放值
|
||
steamvr_model.setScale(1.0)
|
||
|
||
# 打印实际应用的旋转值
|
||
if self.controller.name == 'left':
|
||
print(f"🔧 {self.controller.name}手柄:缩放: 1.0,旋转: (0, {5.037 + 90}, 0) [Y轴俯仰+90度]")
|
||
else:
|
||
print(f"🔧 {self.controller.name}手柄:缩放: 1.0,旋转: (0, {5.037 + 90}, 0) [Y轴俯仰+90度]")
|
||
|
||
# 修复纯黑色问题:重新设置材质属性
|
||
self._fix_model_material(steamvr_model)
|
||
|
||
# 暂时注释身份标记功能,避免额外几何体造成悬空零件
|
||
# self._apply_controller_identity_marker(steamvr_model)
|
||
|
||
# 设置手柄始终显示在上层
|
||
self._set_always_on_top(steamvr_model)
|
||
|
||
print(f"✅ {self.controller.name}手柄已加载SteamVR官方模型(缩放: 1.0,实体渲染模式)")
|
||
else:
|
||
# 降级到改进的程序化模型
|
||
self._create_fallback_model()
|
||
print(f"⚠️ {self.controller.name}手柄使用程序化模型(未找到SteamVR模型)")
|
||
|
||
def _load_steamvr_model(self):
|
||
"""加载SteamVR官方手柄模型"""
|
||
import os
|
||
from panda3d.core import Filename, Texture, TextureStage
|
||
|
||
# SteamVR模型基础路径
|
||
steamvr_base_paths = [
|
||
"/home/hello/.local/share/Steam/steamapps/common/SteamVR/resources/rendermodels/vr_controller_vive_1_5",
|
||
"/home/hello/.steam/steam/steamapps/common/SteamVR/resources/rendermodels/vr_controller_vive_1_5",
|
||
"~/.local/share/Steam/steamapps/common/SteamVR/resources/rendermodels/vr_controller_vive_1_5"
|
||
]
|
||
|
||
for base_path in steamvr_base_paths:
|
||
expanded_base_path = os.path.expanduser(base_path)
|
||
if os.path.exists(expanded_base_path):
|
||
print(f"🔍 找到SteamVR模型目录: {expanded_base_path}")
|
||
|
||
# 不再添加目录到搜索路径,避免自动加载多余组件
|
||
# from panda3d.core import getModelPath
|
||
# getModelPath().appendDirectory(expanded_base_path)
|
||
|
||
# 尝试加载不同的模型文件,按优先级排序
|
||
model_files = [
|
||
("body.obj", "手柄主体"), # 最重要的部分
|
||
("vr_controller_vive_1_5.obj", "完整手柄模型"), # 组合模型
|
||
]
|
||
|
||
for model_file, description in model_files:
|
||
model_path = os.path.join(expanded_base_path, model_file)
|
||
if os.path.exists(model_path):
|
||
try:
|
||
print(f"🎮 尝试加载{description}: {model_file}")
|
||
|
||
# 加载主模型
|
||
model = loader.loadModel(Filename.fromOsSpecific(model_path))
|
||
if model:
|
||
# 先应用纹理,再修复材质(保持纹理效果)
|
||
self._apply_steamvr_textures(model, expanded_base_path)
|
||
print(f"✅ 成功加载{description}")
|
||
return model
|
||
else:
|
||
print(f"⚠️ 模型文件存在但加载失败: {model_file}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 加载{description}失败: {e}")
|
||
continue
|
||
|
||
# 不再尝试组合加载多个部件,避免悬空零件问题
|
||
print("⚠️ 单个模型文件加载失败,跳过组合加载以避免悬空零件")
|
||
break
|
||
|
||
print("❌ 未找到任何SteamVR模型目录")
|
||
return None
|
||
|
||
def _fix_model_material(self, model):
|
||
"""修复模型材质,解决纯黑色问题同时保持纹理"""
|
||
from panda3d.core import Material, MaterialAttrib
|
||
|
||
# 检查模型是否有纹理
|
||
has_texture = model.hasTexture()
|
||
|
||
# 创建新的材质
|
||
material = Material()
|
||
|
||
if has_texture:
|
||
# 有纹理时,设置材质以增强纹理效果
|
||
material.setDiffuse((1.0, 1.0, 1.0, 1.0)) # 白色漫反射让纹理完全显示
|
||
material.setAmbient((0.4, 0.4, 0.4, 1.0)) # 适度环境光
|
||
material.setSpecular((0.3, 0.3, 0.3, 1.0)) # 轻度高光
|
||
material.setShininess(16.0) # 中等光泽度
|
||
print(f"🎨 {self.controller.name}手柄:已修复材质(保持纹理效果)")
|
||
else:
|
||
# 无纹理时,设置合适的基础颜色
|
||
material.setDiffuse((0.7, 0.7, 0.8, 1.0)) # 略偏蓝的灰色
|
||
material.setAmbient((0.3, 0.3, 0.3, 1.0)) # 环境光
|
||
material.setSpecular((0.5, 0.5, 0.5, 1.0)) # 高光
|
||
material.setShininess(32.0) # 光泽度
|
||
print(f"🎨 {self.controller.name}手柄:已修复材质(使用颜色)")
|
||
|
||
# 应用材质到模型
|
||
model.setMaterial(material)
|
||
|
||
# 确保模型能正确渲染
|
||
model.setTwoSided(False)
|
||
|
||
def _apply_controller_identity_marker(self, model):
|
||
"""为控制器添加身份标记,区分左右手"""
|
||
from panda3d.core import RenderModeAttrib
|
||
|
||
# 创建一个小的标识几何体
|
||
marker_geom = self._create_box_geometry(0.005, 0.005, 0.02)
|
||
marker_node = model.attachNewNode(marker_geom)
|
||
|
||
# 根据左右手设置不同位置和颜色(现在都是Y轴+90度俯仰)
|
||
if self.controller.name == 'left':
|
||
# 左手控制器:左侧标记
|
||
marker_node.setPos(-0.03, 0.05, 0.02)
|
||
marker_node.setColor(0.2, 0.4, 1.0, 1.0) # 蓝色
|
||
print(f"🔵 {self.controller.name}手柄已添加蓝色身份标记")
|
||
else:
|
||
# 右手控制器:右侧标记
|
||
marker_node.setPos(0.03, 0.05, 0.02)
|
||
marker_node.setColor(1.0, 0.2, 0.2, 1.0) # 红色
|
||
print(f"🔴 {self.controller.name}手柄已添加红色身份标记")
|
||
|
||
# 让标记发光以便更容易看到
|
||
marker_node.setLightOff() # 不受光照影响,保持明亮
|
||
|
||
# 添加轻微的色彩调整(非常微弱,不影响主要纹理)
|
||
if self.controller.name == 'left':
|
||
model.setColorScale(0.98, 0.98, 1.02, 1.0) # 极轻微的蓝色调
|
||
else:
|
||
model.setColorScale(1.02, 0.98, 0.98, 1.0) # 极轻微的红色调
|
||
|
||
|
||
def _apply_steamvr_textures(self, model, base_path):
|
||
"""为SteamVR模型应用纹理"""
|
||
import os
|
||
from panda3d.core import Texture, TextureStage
|
||
|
||
# SteamVR纹理文件
|
||
texture_files = {
|
||
'diffuse': 'onepointfive_texture.png',
|
||
'specular': 'onepointfive_spec.png'
|
||
}
|
||
|
||
textures_applied = 0
|
||
|
||
for texture_type, texture_file in texture_files.items():
|
||
texture_path = os.path.join(base_path, texture_file)
|
||
if os.path.exists(texture_path):
|
||
try:
|
||
texture = loader.loadTexture(texture_path)
|
||
if texture:
|
||
# 确保纹理能正确加载
|
||
texture.setWrapU(Texture.WMClamp)
|
||
texture.setWrapV(Texture.WMClamp)
|
||
texture.setMinfilter(Texture.FTLinearMipmapLinear)
|
||
texture.setMagfilter(Texture.FTLinear)
|
||
|
||
if texture_type == 'diffuse':
|
||
# 应用主要漫反射纹理
|
||
model.setTexture(texture)
|
||
print(f"✅ 应用了主纹理: {texture_file}")
|
||
textures_applied += 1
|
||
elif texture_type == 'specular':
|
||
# 应用高光纹理
|
||
ts = TextureStage('specular')
|
||
ts.setMode(TextureStage.MModulate)
|
||
model.setTexture(ts, texture)
|
||
print(f"✅ 应用了高光纹理: {texture_file}")
|
||
textures_applied += 1
|
||
except Exception as e:
|
||
print(f"⚠️ 应用纹理失败 {texture_file}: {e}")
|
||
|
||
if textures_applied == 0:
|
||
print(f"⚠️ {self.controller.name}手柄未能加载任何纹理,将使用材质颜色")
|
||
else:
|
||
print(f"🎨 {self.controller.name}手柄成功应用了 {textures_applied} 个纹理")
|
||
|
||
def _load_combined_steamvr_model(self, base_path):
|
||
"""尝试组合加载多个SteamVR模型部件"""
|
||
import os
|
||
from panda3d.core import NodePath
|
||
|
||
# 重要的模型部件
|
||
important_components = [
|
||
"body.obj",
|
||
"trigger.obj",
|
||
"trackpad.obj",
|
||
"l_grip.obj" if self.controller.name == 'left' else "r_grip.obj"
|
||
]
|
||
|
||
combined_model = NodePath("combined_controller")
|
||
has_components = False
|
||
|
||
for component in important_components:
|
||
component_path = os.path.join(base_path, component)
|
||
if os.path.exists(component_path):
|
||
try:
|
||
part = loader.loadModel(component_path)
|
||
if part:
|
||
part.reparentTo(combined_model)
|
||
has_components = True
|
||
print(f"✅ 加载了部件: {component}")
|
||
except Exception as e:
|
||
print(f"⚠️ 加载部件失败 {component}: {e}")
|
||
|
||
if has_components:
|
||
self._apply_steamvr_textures(combined_model, base_path)
|
||
return combined_model
|
||
|
||
return None
|
||
|
||
def _create_fallback_model(self):
|
||
"""创建改进的程序化手柄模型作为后备方案"""
|
||
# 主体(长条形状)
|
||
main_body = self._create_box_geometry(0.025, 0.15, 0.04)
|
||
main_node = self.model_node.attachNewNode(main_body)
|
||
|
||
# 根据左右手设置不同颜色
|
||
if self.controller.name == 'left':
|
||
color = self.button_colors['left']
|
||
else:
|
||
color = self.button_colors['right']
|
||
|
||
main_node.setColor(color)
|
||
|
||
# 启用光照响应
|
||
from panda3d.core import RenderState, MaterialAttrib, Material
|
||
material = Material()
|
||
material.setShininess(32)
|
||
material.setAmbient((0.2, 0.2, 0.2, 1))
|
||
material.setDiffuse(color)
|
||
material.setSpecular((0.5, 0.5, 0.5, 1))
|
||
main_node.setMaterial(material)
|
||
|
||
# 扳机区域(小突起)
|
||
trigger = self._create_box_geometry(0.015, 0.03, 0.02)
|
||
trigger_node = self.model_node.attachNewNode(trigger)
|
||
trigger_node.setPos(0, -0.08, 0.03)
|
||
trigger_node.setColor(self.button_colors['trigger'])
|
||
trigger_node.setMaterial(material)
|
||
|
||
# 握把区域
|
||
grip = self._create_box_geometry(0.02, 0.06, 0.03)
|
||
grip_node = self.model_node.attachNewNode(grip)
|
||
grip_node.setPos(0, 0.05, -0.03)
|
||
grip_node.setColor(self.button_colors['grip'])
|
||
grip_node.setMaterial(material)
|
||
|
||
# 触摸板区域(圆盘)
|
||
trackpad = self._create_disc_geometry(0.015, 0.005)
|
||
trackpad_node = self.model_node.attachNewNode(trackpad)
|
||
trackpad_node.setPos(0, -0.02, 0.04)
|
||
trackpad_node.setColor(self.button_colors['trackpad'])
|
||
trackpad_node.setMaterial(material)
|
||
|
||
def _create_box_geometry(self, width, length, height):
|
||
"""创建立方体几何体"""
|
||
# 创建顶点格式
|
||
format = GeomVertexFormat.getV3n3()
|
||
vdata = GeomVertexData('box', format, Geom.UHStatic)
|
||
|
||
vertex = GeomVertexWriter(vdata, 'vertex')
|
||
normal = GeomVertexWriter(vdata, 'normal')
|
||
|
||
# 定义立方体的8个顶点
|
||
vertices = [
|
||
Vec3(-width/2, -length/2, -height/2),
|
||
Vec3( width/2, -length/2, -height/2),
|
||
Vec3( width/2, length/2, -height/2),
|
||
Vec3(-width/2, length/2, -height/2),
|
||
Vec3(-width/2, -length/2, height/2),
|
||
Vec3( width/2, -length/2, height/2),
|
||
Vec3( width/2, length/2, height/2),
|
||
Vec3(-width/2, length/2, height/2)
|
||
]
|
||
|
||
# 立方体的6个面,每个面4个顶点
|
||
faces = [
|
||
# 底面 (z = -height/2)
|
||
[0, 1, 2, 3, Vec3(0, 0, -1)],
|
||
# 顶面 (z = height/2)
|
||
[7, 6, 5, 4, Vec3(0, 0, 1)],
|
||
# 前面 (y = -length/2)
|
||
[4, 5, 1, 0, Vec3(0, -1, 0)],
|
||
# 后面 (y = length/2)
|
||
[3, 2, 6, 7, Vec3(0, 1, 0)],
|
||
# 左面 (x = -width/2)
|
||
[0, 3, 7, 4, Vec3(-1, 0, 0)],
|
||
# 右面 (x = width/2)
|
||
[5, 6, 2, 1, Vec3(1, 0, 0)]
|
||
]
|
||
|
||
# 添加顶点和法线
|
||
for face in faces:
|
||
for i in range(4):
|
||
vertex.addData3(vertices[face[i]])
|
||
normal.addData3(face[4]) # 法线向量
|
||
|
||
# 创建几何体
|
||
geom = Geom(vdata)
|
||
|
||
# 为每个面创建三角形
|
||
for face_idx in range(6):
|
||
base_idx = face_idx * 4
|
||
prim = GeomTriangles(Geom.UHStatic)
|
||
# 第一个三角形
|
||
prim.addVertices(base_idx, base_idx + 1, base_idx + 2)
|
||
# 第二个三角形
|
||
prim.addVertices(base_idx, base_idx + 2, base_idx + 3)
|
||
geom.addPrimitive(prim)
|
||
|
||
# 创建几何体节点
|
||
geom_node = GeomNode('box')
|
||
geom_node.addGeom(geom)
|
||
|
||
return geom_node
|
||
|
||
def _create_disc_geometry(self, radius, thickness):
|
||
"""创建圆盘几何体(用于触摸板)"""
|
||
format = GeomVertexFormat.getV3n3()
|
||
vdata = GeomVertexData('disc', format, Geom.UHStatic)
|
||
|
||
vertex = GeomVertexWriter(vdata, 'vertex')
|
||
normal = GeomVertexWriter(vdata, 'normal')
|
||
|
||
# 创建圆盘顶点
|
||
segments = 16
|
||
import math
|
||
|
||
# 中心点
|
||
vertex.addData3(0, 0, thickness/2)
|
||
normal.addData3(0, 0, 1)
|
||
|
||
# 圆周点
|
||
for i in range(segments):
|
||
angle = 2 * math.pi * i / segments
|
||
x = radius * math.cos(angle)
|
||
y = radius * math.sin(angle)
|
||
|
||
vertex.addData3(x, y, thickness/2)
|
||
normal.addData3(0, 0, 1)
|
||
|
||
# 创建几何体
|
||
geom = Geom(vdata)
|
||
prim = GeomTriangles(Geom.UHStatic)
|
||
|
||
# 创建扇形三角形
|
||
for i in range(segments):
|
||
next_i = (i + 1) % segments
|
||
prim.addVertices(0, i + 1, next_i + 1)
|
||
|
||
geom.addPrimitive(prim)
|
||
|
||
# 创建几何体节点
|
||
geom_node = GeomNode('disc')
|
||
geom_node.addGeom(geom)
|
||
|
||
return geom_node
|
||
|
||
def _create_interaction_ray(self):
|
||
"""创建交互射线"""
|
||
if not self.visual_node:
|
||
return
|
||
|
||
# 创建射线几何
|
||
line_segs = LineSegs()
|
||
line_segs.setThickness(3)
|
||
line_segs.setColor(self.ray_color)
|
||
|
||
# 射线主体
|
||
line_segs.moveTo(0, 0, 0)
|
||
line_segs.drawTo(0, self.ray_length, 0)
|
||
|
||
# 射线端点(小球)
|
||
end_point = self._create_sphere_geometry(0.02)
|
||
|
||
# 创建射线节点
|
||
geom_node = line_segs.create()
|
||
self.ray_node = self.visual_node.attachNewNode(geom_node)
|
||
|
||
# 添加端点球
|
||
end_node = self.ray_node.attachNewNode(end_point)
|
||
end_node.setPos(0, self.ray_length, 0)
|
||
end_node.setColor(self.ray_color)
|
||
|
||
# 设置透明度
|
||
self.ray_node.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 默认隐藏射线
|
||
self.ray_node.hide()
|
||
|
||
print(f"✓ {self.controller.name}手柄交互射线已创建")
|
||
|
||
def _create_sphere_geometry(self, radius):
|
||
"""创建球体几何体"""
|
||
# 简单的立方体作为球体替代
|
||
return self._create_box_geometry(radius, radius, radius)
|
||
|
||
def _create_button_indicators(self):
|
||
"""创建按钮状态指示器"""
|
||
if not self.visual_node:
|
||
return
|
||
|
||
# 创建按钮指示器容器
|
||
self.button_indicator_node = self.visual_node.attachNewNode(f'{self.controller.name}_indicators')
|
||
|
||
# 扳机指示器
|
||
trigger_indicator = self._create_box_geometry(0.005, 0.01, 0.005)
|
||
self.trigger_indicator = self.button_indicator_node.attachNewNode(trigger_indicator)
|
||
self.trigger_indicator.setPos(0.02, -0.08, 0.03)
|
||
self.trigger_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||
|
||
# 握把指示器
|
||
grip_indicator = self._create_box_geometry(0.005, 0.02, 0.005)
|
||
self.grip_indicator = self.button_indicator_node.attachNewNode(grip_indicator)
|
||
self.grip_indicator.setPos(-0.02, 0.05, -0.03)
|
||
self.grip_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||
|
||
# 触摸板指示器
|
||
trackpad_indicator = self._create_disc_geometry(0.003, 0.002)
|
||
self.trackpad_indicator = self.button_indicator_node.attachNewNode(trackpad_indicator)
|
||
self.trackpad_indicator.setPos(0, -0.02, 0.045)
|
||
self.trackpad_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||
|
||
print(f"✓ {self.controller.name}手柄按钮指示器已创建")
|
||
|
||
def update(self):
|
||
"""更新可视化状态"""
|
||
if not self.controller.is_connected:
|
||
self.hide()
|
||
return
|
||
|
||
self.show()
|
||
|
||
# 更新按钮指示器状态
|
||
self._update_button_indicators()
|
||
|
||
# 更新射线显示状态
|
||
self._update_ray_display()
|
||
|
||
def _update_button_indicators(self):
|
||
"""更新按钮指示器状态"""
|
||
if not hasattr(self, 'trigger_indicator'):
|
||
return
|
||
|
||
# 扳机指示器
|
||
if self.controller.is_trigger_pressed():
|
||
self.trigger_indicator.setColor(self.button_colors['trigger'])
|
||
# 根据扳机值调整位置
|
||
trigger_offset = self.controller.trigger_value * 0.01
|
||
self.trigger_indicator.setPos(0.02, -0.08 + trigger_offset, 0.03)
|
||
else:
|
||
self.trigger_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||
self.trigger_indicator.setPos(0.02, -0.08, 0.03)
|
||
|
||
# 握把指示器
|
||
if self.controller.is_grip_pressed():
|
||
self.grip_indicator.setColor(self.button_colors['grip'])
|
||
else:
|
||
self.grip_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||
|
||
# 触摸板指示器
|
||
if self.controller.touchpad_touched:
|
||
self.trackpad_indicator.setColor(self.button_colors['trackpad'])
|
||
# 根据触摸位置调整指示器位置
|
||
if hasattr(self.controller, 'touchpad_pos'):
|
||
offset_x = self.controller.touchpad_pos.x * 0.01
|
||
offset_y = self.controller.touchpad_pos.y * 0.01
|
||
self.trackpad_indicator.setPos(offset_x, -0.02 + offset_y, 0.045)
|
||
else:
|
||
self.trackpad_indicator.setColor(0.2, 0.2, 0.2, 1.0)
|
||
self.trackpad_indicator.setPos(0, -0.02, 0.045)
|
||
|
||
def _update_ray_display(self):
|
||
"""更新射线显示"""
|
||
if not self.ray_node:
|
||
return
|
||
|
||
# 根据交互状态显示/隐藏射线
|
||
# 这里可以添加更复杂的逻辑,比如只在指向对象时显示
|
||
show_ray = (self.controller.is_trigger_pressed(threshold=0.1) or
|
||
self.controller.touchpad_touched)
|
||
|
||
if show_ray:
|
||
self.show_ray()
|
||
else:
|
||
self.hide_ray()
|
||
|
||
def show(self):
|
||
"""显示手柄可视化"""
|
||
if self.visual_node:
|
||
self.visual_node.show()
|
||
|
||
def hide(self):
|
||
"""隐藏手柄可视化"""
|
||
if self.visual_node:
|
||
self.visual_node.hide()
|
||
|
||
def show_ray(self):
|
||
"""显示交互射线"""
|
||
if self.ray_node:
|
||
self.ray_node.show()
|
||
|
||
def hide_ray(self):
|
||
"""隐藏交互射线"""
|
||
if self.ray_node:
|
||
self.ray_node.hide()
|
||
|
||
def set_ray_color(self, color):
|
||
"""设置射线颜色"""
|
||
if self.ray_node:
|
||
self.ray_node.setColor(color)
|
||
|
||
def set_ray_length(self, length):
|
||
"""设置射线长度"""
|
||
self.ray_length = length
|
||
# 重新创建射线(简单的实现)
|
||
if self.ray_node:
|
||
self.ray_node.removeNode()
|
||
self._create_interaction_ray()
|
||
|
||
def _set_always_on_top(self, model_node):
|
||
"""设置手柄模型始终显示在上层,不被其他物体遮挡"""
|
||
if not model_node:
|
||
return
|
||
|
||
from panda3d.core import RenderState
|
||
|
||
# 设置为固定渲染bin,优先级设为较高值(1000)
|
||
# fixed bin中的对象按sort值从小到大渲染,越大越后渲染(越在上层)
|
||
model_node.setBin("fixed", 1000)
|
||
|
||
# 禁用深度测试和深度写入,确保始终可见
|
||
model_node.setDepthTest(False)
|
||
model_node.setDepthWrite(False)
|
||
|
||
# 递归设置所有子节点的渲染属性
|
||
for child in model_node.findAllMatches("**"):
|
||
child.setBin("fixed", 1000)
|
||
child.setDepthTest(False)
|
||
child.setDepthWrite(False)
|
||
|
||
print(f"🔝 {self.controller.name}手柄已设置为始终显示在上层")
|
||
|
||
def cleanup(self):
|
||
"""清理资源"""
|
||
if self.visual_node:
|
||
self.visual_node.removeNode()
|
||
|
||
print(f"🧹 {self.controller.name}手柄可视化已清理") |