1.修改整体布局(有BUG未修改完成)
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.idea/
|
||||
*.pyc
|
||||
|
||||
@ -121,25 +121,43 @@ class InternalLightManager(object):
|
||||
print("ERROR: Could not detach light, light was not attached!")
|
||||
return
|
||||
|
||||
self._lights.free_slot(light.get_slot())
|
||||
# 保存必要信息,避免过早清理
|
||||
light_slot = light.get_slot()
|
||||
has_shadows = light.get_casts_shadows()
|
||||
|
||||
# 处理shadow sources(如果有的话)
|
||||
if has_shadows:
|
||||
num_sources = light.get_num_shadow_sources()
|
||||
if num_sources > 0:
|
||||
# 关键修复:先保存第一个source的slot,再清理
|
||||
first_source = light.get_shadow_source(0)
|
||||
first_source_slot = first_source.get_slot() # 保存slot值
|
||||
|
||||
# 先发送GPU移除命令(在清理之前)
|
||||
cmd_remove = GPUCommand(GPUCommand.CMD_remove_sources)
|
||||
cmd_remove.push_int(first_source_slot) # 使用保存的slot值
|
||||
cmd_remove.push_int(num_sources)
|
||||
self._cmd_list.add_command(cmd_remove)
|
||||
|
||||
# 然后清理所有shadow sources
|
||||
for i in range(num_sources):
|
||||
source = light.get_shadow_source(i)
|
||||
if source.has_slot():
|
||||
self._shadow_sources.free_slot(source.get_slot())
|
||||
if source.has_region():
|
||||
self._shadow_manager.get_atlas().free_region(source.get_region())
|
||||
source.clear_region()
|
||||
|
||||
# 清理light的shadow sources
|
||||
light.clear_shadow_sources()
|
||||
|
||||
# 发送GPU移除light命令
|
||||
self.gpu_remove_light(light)
|
||||
|
||||
# 最后释放light slot和清理light信息
|
||||
self._lights.free_slot(light_slot)
|
||||
light.remove_slot()
|
||||
|
||||
if light.get_casts_shadows():
|
||||
|
||||
for i in range(light.get_num_shadow_sources()):
|
||||
source = light.get_shadow_source(i)
|
||||
if source.has_slot():
|
||||
self._shadow_sources.free_slot(source.get_slot())
|
||||
if source.has_region():
|
||||
self._shadow_manager.get_atlas().free_region(source.get_region())
|
||||
source.clear_region()
|
||||
|
||||
self.gpu_remove_consecutive_sources(
|
||||
light.get_shadow_source(0), light.get_num_shadow_sources())
|
||||
|
||||
light.clear_shadow_sources()
|
||||
|
||||
def gpu_remove_consecutive_sources(self, first_source, num_sources):
|
||||
cmd_remove = GPUCommand(GPUCommand.CMD_remove_sources)
|
||||
cmd_remove.push_int(first_source.get_slot())
|
||||
|
||||
BIN
Resources/icons/test_metallic_stripes.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/icons/test_roughness_checkerboard.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/icons/test_roughness_circle.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/icons/test_roughness_gradient.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
945
core/collision_manager.py
Normal file
@ -0,0 +1,945 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
from panda3d.core import (
|
||||
CollisionTraverser, CollisionHandlerQueue, CollisionNode,
|
||||
CollisionRay, CollisionSphere, BitMask32, Point3, Vec3
|
||||
)
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
|
||||
class CollisionManager:
|
||||
"""统一的碰撞检测管理器"""
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.traverser = CollisionTraverser("main_traverser")
|
||||
self.queue = CollisionHandlerQueue()
|
||||
|
||||
# 碰撞掩码定义
|
||||
self.MASKS = {
|
||||
# === 基础碰撞掩码定义 ===
|
||||
# 每个掩码使用不同的位(bit)来标识,可以进行位运算组合
|
||||
|
||||
'TERRAIN': BitMask32.bit(0), # 地形/地面 - 通常用于地面碰撞检测
|
||||
'UI_ELEMENT': BitMask32.bit(1), # UI元素 - 界面组件的碰撞检测
|
||||
'CAMERA': BitMask32.bit(2), # 摄像机 - 相机的碰撞检测
|
||||
'MODEL_COLLISION': BitMask32.bit(3), # 模型碰撞 - 通用模型间碰撞检测
|
||||
}
|
||||
|
||||
# 碰撞体形状类型
|
||||
self.COLLISION_SHAPES = {
|
||||
'SPHERE': 'sphere',
|
||||
'BOX': 'box',
|
||||
'CAPSULE': 'capsule',
|
||||
'PLANE': 'plane',
|
||||
'POLYGON': 'polygon',
|
||||
'MESH': 'mesh'
|
||||
}
|
||||
|
||||
# 掩码组合定义
|
||||
self.MASK_COMBINATIONS = {
|
||||
}
|
||||
|
||||
# 碰撞分组管理
|
||||
self.collision_groups = {}
|
||||
self.group_enabled = {}
|
||||
|
||||
# 碰撞回调系统
|
||||
self.collision_callbacks = {}
|
||||
self.collision_filters = {}
|
||||
|
||||
# 空间分割系统(八叉树)
|
||||
self.spatial_partitioning_enabled = False
|
||||
self.octree = None
|
||||
self.octree_max_depth = 6
|
||||
self.octree_max_objects = 10
|
||||
|
||||
# 连续碰撞检测
|
||||
self.ccd_enabled = False
|
||||
self.ccd_threshold = 10.0 # 速度阈值
|
||||
|
||||
# 性能监控
|
||||
self.performance_monitor = CollisionPerformanceMonitor()
|
||||
|
||||
# 模型间碰撞检测配置
|
||||
self.model_collision_enabled = False
|
||||
self.collision_detection_frequency = 0.1
|
||||
self.collision_distance_threshold = 0
|
||||
self.last_collision_check = 0
|
||||
self.collision_history = []
|
||||
self.max_collision_history = 100
|
||||
|
||||
# 碰撞状态跟踪 - 避免重复打印
|
||||
self.active_collisions = set() # 当前活跃的碰撞对
|
||||
|
||||
# 模型间碰撞检测任务
|
||||
self.collision_task = None
|
||||
|
||||
print("✅ 碰撞检测管理器初始化完成")
|
||||
|
||||
def enableModelCollisionDetection(self, enable=True, frequency=0.1, threshold=0.5):
|
||||
"""启用/禁用模型间碰撞检测
|
||||
|
||||
Args:
|
||||
enable: 是否启用碰撞检测
|
||||
frequency: 检测频率(秒)
|
||||
threshold: 碰撞距离阈值
|
||||
"""
|
||||
self.model_collision_enabled = enable
|
||||
self.collision_detection_frequency = frequency
|
||||
self.collision_distance_threshold = threshold
|
||||
|
||||
if enable:
|
||||
print(f"✅ 启用模型间碰撞检测 - 频率: {frequency}s, 阈值: {threshold}")
|
||||
self._startCollisionDetectionTask()
|
||||
else:
|
||||
print("❌ 禁用模型间碰撞检测")
|
||||
self._stopCollisionDetectionTask()
|
||||
|
||||
def _startCollisionDetectionTask(self):
|
||||
"""启动碰撞检测任务"""
|
||||
if self.collision_task:
|
||||
taskMgr.remove(self.collision_task)
|
||||
|
||||
def collisionDetectionTask(task):
|
||||
try:
|
||||
self.detectModelCollisions()
|
||||
return task.again
|
||||
except Exception as e:
|
||||
print(f"❌ 碰撞检测任务异常: {str(e)}")
|
||||
return task.again
|
||||
|
||||
self.collision_task = taskMgr.doMethodLater(
|
||||
self.collision_detection_frequency,
|
||||
collisionDetectionTask,
|
||||
"modelCollisionDetection"
|
||||
)
|
||||
|
||||
def _stopCollisionDetectionTask(self):
|
||||
"""停止碰撞检测任务"""
|
||||
if self.collision_task:
|
||||
taskMgr.remove(self.collision_task)
|
||||
self.collision_task = None
|
||||
|
||||
def detectModelCollisions(self, specific_models=None, log_results=True):
|
||||
"""检测模型间碰撞"""
|
||||
if not self.model_collision_enabled and specific_models is None:
|
||||
return []
|
||||
|
||||
start_time = time.perf_counter()
|
||||
current_time = datetime.now()
|
||||
|
||||
models_to_check = specific_models if specific_models else self.world.models
|
||||
if len(models_to_check) < 2:
|
||||
return []
|
||||
|
||||
collision_results = []
|
||||
checked_pairs = set()
|
||||
current_collision_pairs = set()
|
||||
|
||||
# 遍历所有模型对
|
||||
for i, model_a in enumerate(models_to_check):
|
||||
for j, model_b in enumerate(models_to_check[i+1:], i+1):
|
||||
pair_key = tuple(sorted([id(model_a), id(model_b)]))
|
||||
if pair_key in checked_pairs:
|
||||
continue
|
||||
checked_pairs.add(pair_key)
|
||||
|
||||
collision_info = self._checkModelPairCollision(model_a, model_b, current_time)
|
||||
if collision_info:
|
||||
collision_results.append(collision_info)
|
||||
current_collision_pairs.add(pair_key)
|
||||
|
||||
# 只有新碰撞才打印日志
|
||||
if log_results and pair_key not in self.active_collisions:
|
||||
self._logCollisionInfo(collision_info)
|
||||
print(f"🆕 新碰撞检测到!")
|
||||
|
||||
# 检测碰撞结束的情况
|
||||
ended_collisions = self.active_collisions - current_collision_pairs
|
||||
for pair_key in ended_collisions:
|
||||
print(f"✅ 碰撞结束: 模型对 {pair_key}")
|
||||
|
||||
# 更新活跃碰撞状态
|
||||
self.active_collisions = current_collision_pairs
|
||||
|
||||
# 记录性能和历史
|
||||
detection_time = time.perf_counter() - start_time
|
||||
self.performance_monitor.recordModelCollisionDetection(
|
||||
detection_time, len(models_to_check), len(collision_results))
|
||||
|
||||
if collision_results:
|
||||
self.collision_history.extend(collision_results)
|
||||
if len(self.collision_history) > self.max_collision_history:
|
||||
self.collision_history = self.collision_history[-self.max_collision_history:]
|
||||
|
||||
return collision_results
|
||||
|
||||
def _checkModelPairCollision(self, model_a, model_b, timestamp):
|
||||
"""使用Panda3D内置碰撞系统检查两个模型是否碰撞"""
|
||||
try:
|
||||
# 检查模型是否有碰撞节点
|
||||
collision_node_a = self._findCollisionNode(model_a)
|
||||
collision_node_b = self._findCollisionNode(model_b)
|
||||
|
||||
if not collision_node_a or not collision_node_b:
|
||||
return None
|
||||
|
||||
# 使用Panda3D的碰撞遍历器
|
||||
temp_traverser = CollisionTraverser()
|
||||
temp_queue = CollisionHandlerQueue()
|
||||
|
||||
# 设置碰撞检测
|
||||
temp_traverser.addCollider(collision_node_a, temp_queue)
|
||||
|
||||
# 执行碰撞检测
|
||||
temp_traverser.traverse(model_b)
|
||||
|
||||
if temp_queue.getNumEntries() > 0:
|
||||
# 有碰撞,获取碰撞信息
|
||||
entry = temp_queue.getEntry(0)
|
||||
|
||||
center_a = model_a.getPos(self.world.render)
|
||||
center_b = model_b.getPos(self.world.render)
|
||||
distance = (center_b - center_a).length()
|
||||
|
||||
return {
|
||||
'timestamp': timestamp,
|
||||
'model_a': {
|
||||
'name': model_a.getName(),
|
||||
'center': center_a,
|
||||
'radius': 0, # 不再需要手动计算半径
|
||||
'node': model_a
|
||||
},
|
||||
'model_b': {
|
||||
'name': model_b.getName(),
|
||||
'center': center_b,
|
||||
'radius': 0,
|
||||
'node': model_b
|
||||
},
|
||||
'collision_point': entry.getSurfacePoint(self.world.render),
|
||||
'distance': distance,
|
||||
'penetration_depth': 0, # Panda3D会自动处理
|
||||
'collision_normal': entry.getSurfaceNormal(self.world.render)
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检测模型对碰撞失败 ({model_a.getName()} vs {model_b.getName()}): {str(e)}")
|
||||
return None
|
||||
|
||||
def _findCollisionNode(self, model):
|
||||
"""查找模型的碰撞节点"""
|
||||
for child in model.getChildren():
|
||||
if isinstance(child.node(), CollisionNode):
|
||||
return child
|
||||
return None
|
||||
|
||||
def _logCollisionInfo(self, collision_info):
|
||||
"""输出碰撞信息日志"""
|
||||
timestamp = collision_info['timestamp'].strftime('%H:%M:%S.%f')[:-3]
|
||||
model_a = collision_info['model_a']
|
||||
model_b = collision_info['model_b']
|
||||
|
||||
print(f"🔥 [{timestamp}] 检测到碰撞:")
|
||||
print(f" 模型A: {model_a['name']} (中心: {model_a['center']}, 半径: {model_a['radius']:.2f})")
|
||||
print(f" 模型B: {model_b['name']} (中心: {model_b['center']}, 半径: {model_b['radius']:.2f})")
|
||||
print(f" 碰撞点: {collision_info['collision_point']}")
|
||||
print(f" 中心距离: {collision_info['distance']:.3f}")
|
||||
print(f" 穿透深度: {collision_info['penetration_depth']:.3f}")
|
||||
print(f" 碰撞法向: {collision_info['collision_normal']}")
|
||||
|
||||
def getCollisionHistory(self, limit=None):
|
||||
"""获取碰撞历史记录
|
||||
|
||||
Args:
|
||||
limit: 返回记录数量限制
|
||||
|
||||
Returns:
|
||||
list: 碰撞历史记录
|
||||
"""
|
||||
if limit:
|
||||
return self.collision_history[-limit:]
|
||||
return self.collision_history.copy()
|
||||
|
||||
def clearCollisionHistory(self):
|
||||
"""清除碰撞历史记录"""
|
||||
self.collision_history.clear()
|
||||
print("✅ 碰撞历史记录已清除")
|
||||
|
||||
def getCollisionStatistics(self):
|
||||
"""获取碰撞统计信息"""
|
||||
if not self.collision_history:
|
||||
return {"total_collisions": 0, "unique_pairs": 0, "most_frequent_pair": None}
|
||||
|
||||
# 统计碰撞对
|
||||
collision_pairs = {}
|
||||
for collision in self.collision_history:
|
||||
pair_key = tuple(sorted([
|
||||
collision['model_a']['name'],
|
||||
collision['model_b']['name']
|
||||
]))
|
||||
collision_pairs[pair_key] = collision_pairs.get(pair_key, 0) + 1
|
||||
|
||||
most_frequent_pair = max(collision_pairs.items(), key=lambda x: x[1])
|
||||
|
||||
return {
|
||||
"total_collisions": len(self.collision_history),
|
||||
"unique_pairs": len(collision_pairs),
|
||||
"most_frequent_pair": {
|
||||
"models": most_frequent_pair[0],
|
||||
"count": most_frequent_pair[1]
|
||||
},
|
||||
"collision_pairs": collision_pairs
|
||||
}
|
||||
|
||||
def createCollisionShape(self, model, shape_type='auto', **kwargs):
|
||||
"""为模型创建指定类型的碰撞体
|
||||
|
||||
Args:
|
||||
model: 模型节点
|
||||
shape_type: 碰撞体类型 ('auto', 'sphere', 'box', 'capsule', 'plane', 'polygon')
|
||||
**kwargs: 形状特定参数
|
||||
|
||||
Returns:
|
||||
CollisionSolid: 创建的碰撞体
|
||||
"""
|
||||
from panda3d.core import (
|
||||
CollisionSphere, CollisionBox, CollisionCapsule,
|
||||
CollisionPlane, CollisionPolygon, Plane, Vec3
|
||||
)
|
||||
|
||||
bounds = model.getBounds()
|
||||
if bounds.isEmpty():
|
||||
# 默认小球体
|
||||
return CollisionSphere(Point3(0, 0, 0), 1.0)
|
||||
|
||||
center = bounds.getCenter()
|
||||
radius = bounds.getRadius()
|
||||
|
||||
# 自动选择最适合的形状
|
||||
if shape_type == 'auto':
|
||||
shape_type = self._determineOptimalShape(model, bounds)
|
||||
|
||||
if shape_type == 'sphere':
|
||||
return CollisionSphere(center, kwargs.get('radius', radius))
|
||||
|
||||
elif shape_type == 'box':
|
||||
# 创建包围盒
|
||||
min_point = bounds.getMin()
|
||||
max_point = bounds.getMax()
|
||||
return CollisionBox(min_point, max_point)
|
||||
|
||||
elif shape_type == 'capsule':
|
||||
# 创建胶囊体(适合角色)
|
||||
height = kwargs.get('height', (bounds.getMax().z - bounds.getMin().z))
|
||||
radius = kwargs.get('radius', min(bounds.getRadius() * 0.5, height * 0.3))
|
||||
point_a = Point3(center.x, center.y, bounds.getMin().z + radius)
|
||||
point_b = Point3(center.x, center.y, bounds.getMax().z - radius)
|
||||
return CollisionCapsule(point_a, point_b, radius)
|
||||
|
||||
elif shape_type == 'plane':
|
||||
# 创建平面(适合地面、墙面)
|
||||
normal = kwargs.get('normal', Vec3(0, 0, 1))
|
||||
point = kwargs.get('point', center)
|
||||
plane = Plane(normal, point)
|
||||
return CollisionPlane(plane)
|
||||
|
||||
elif shape_type == 'polygon':
|
||||
# === 创建多边形碰撞体 ===
|
||||
# CollisionPolygon特点:
|
||||
# - 单个平面多边形,所有顶点必须共面
|
||||
# - 适用于:地板、墙面、简单平台等平面区域
|
||||
# - 性能较好,但只能表示平面形状
|
||||
#
|
||||
# Mesh碰撞体特点:
|
||||
# - 由多个三角形组成的复杂3D形状
|
||||
# - 适用于:复杂地形、建筑物、不规则物体
|
||||
# - 性能较差,但可以表示任意复杂形状
|
||||
|
||||
vertices = kwargs.get('vertices', [])
|
||||
if len(vertices) >= 3:
|
||||
# 将顶点转换为Point3对象
|
||||
# 要求:所有顶点必须在同一平面上
|
||||
collision_poly = CollisionPolygon(*[Point3(*v) for v in vertices])
|
||||
return collision_poly
|
||||
else:
|
||||
print("⚠️ 多边形至少需要3个顶点,回退到球体")
|
||||
return CollisionSphere(center, radius)
|
||||
|
||||
def _determineOptimalShape(self, model, bounds):
|
||||
"""根据模型特征自动确定最适合的碰撞体形状"""
|
||||
# 获取模型尺寸比例
|
||||
size = bounds.getMax() - bounds.getMin()
|
||||
max_dim = max(size.x, size.y, size.z)
|
||||
min_dim = min(size.x, size.y, size.z)
|
||||
|
||||
# 根据模型名称和比例判断
|
||||
model_name = model.getName().lower()
|
||||
|
||||
# 角色类模型使用胶囊体
|
||||
if any(keyword in model_name for keyword in ['character', 'human', 'player', 'npc']):
|
||||
return 'capsule'
|
||||
|
||||
# 建筑、箱子类使用包围盒
|
||||
if any(keyword in model_name for keyword in ['building', 'box', 'cube', 'wall', 'door']):
|
||||
return 'box'
|
||||
|
||||
# 地面、平台使用平面
|
||||
if any(keyword in model_name for keyword in ['ground', 'floor', 'platform', 'terrain']):
|
||||
return 'plane'
|
||||
|
||||
# 根据尺寸比例判断
|
||||
aspect_ratio = max_dim / min_dim if min_dim > 0 else 1
|
||||
|
||||
if aspect_ratio > 3: # 细长物体用胶囊体
|
||||
return 'capsule'
|
||||
elif aspect_ratio < 1.5: # 接近球形用球体
|
||||
return 'sphere'
|
||||
else: # 其他用包围盒
|
||||
return 'box'
|
||||
|
||||
def createMouseRay(self, screen_x, screen_y, mask_types=['SELECTABLE']):
|
||||
"""创建鼠标射线"""
|
||||
# 组合掩码
|
||||
combined_mask = BitMask32.allOff()
|
||||
for mask_type in mask_types:
|
||||
if mask_type in self.MASKS:
|
||||
combined_mask |= self.MASKS[mask_type]
|
||||
|
||||
# 坐标转换
|
||||
near_point, far_point = self.world.event_handler.robustCoordinateConversion(
|
||||
screen_x, screen_y)
|
||||
|
||||
if not near_point:
|
||||
return None
|
||||
|
||||
# 创建射线节点
|
||||
ray_node = CollisionNode('mouse_ray')
|
||||
ray_node.setFromCollideMask(combined_mask)
|
||||
ray_node.setIntoCollideMask(BitMask32.allOff())
|
||||
|
||||
# 创建射线
|
||||
direction = far_point - near_point
|
||||
direction.normalize()
|
||||
ray = CollisionRay(near_point, direction)
|
||||
ray_node.addSolid(ray)
|
||||
|
||||
return ray_node, near_point, far_point
|
||||
|
||||
def performRaycast(self, ray_node, scene_root=None):
|
||||
"""执行射线检测"""
|
||||
if scene_root is None:
|
||||
scene_root = self.world.render
|
||||
|
||||
# 创建临时节点路径
|
||||
ray_np = self.world.cam.attachNewNode(ray_node)
|
||||
|
||||
try:
|
||||
# 清除之前的结果
|
||||
self.queue.clearEntries()
|
||||
|
||||
# 添加碰撞器
|
||||
self.traverser.addCollider(ray_np, self.queue)
|
||||
|
||||
# 执行遍历
|
||||
start_time = time.perf_counter()
|
||||
self.traverser.traverse(scene_root)
|
||||
detection_time = time.perf_counter() - start_time
|
||||
|
||||
# 记录性能
|
||||
self.performance_monitor.recordDetection(
|
||||
detection_time, self.queue.getNumEntries())
|
||||
|
||||
# 处理结果
|
||||
results = []
|
||||
if self.queue.getNumEntries() > 0:
|
||||
self.queue.sortEntries()
|
||||
|
||||
for i in range(self.queue.getNumEntries()):
|
||||
entry = self.queue.getEntry(i)
|
||||
results.append({
|
||||
'hit_pos': entry.getSurfacePoint(scene_root),
|
||||
'hit_normal': entry.getSurfaceNormal(scene_root),
|
||||
'hit_node': entry.getIntoNodePath(),
|
||||
'distance': entry.getT()
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
finally:
|
||||
# 清理资源
|
||||
self.traverser.removeCollider(ray_np)
|
||||
ray_np.removeNode()
|
||||
|
||||
def getCollisionMask(self, mask_type):
|
||||
"""获取碰撞掩码"""
|
||||
return self.MASKS.get(mask_type, BitMask32.allOff())
|
||||
|
||||
def createCollisionGroup(self, group_name, mask_types, enabled=True):
|
||||
"""创建碰撞分组
|
||||
|
||||
Args:
|
||||
group_name: 分组名称
|
||||
mask_types: 掩码类型列表
|
||||
enabled: 是否启用
|
||||
"""
|
||||
combined_mask = BitMask32.allOff()
|
||||
for mask_type in mask_types:
|
||||
if mask_type in self.MASKS:
|
||||
combined_mask |= self.MASKS[mask_type]
|
||||
|
||||
self.collision_groups[group_name] = {
|
||||
'mask': combined_mask,
|
||||
'types': mask_types,
|
||||
'objects': []
|
||||
}
|
||||
self.group_enabled[group_name] = enabled
|
||||
|
||||
print(f"✅ 创建碰撞分组: {group_name}, 掩码: {mask_types}")
|
||||
|
||||
def addToCollisionGroup(self, group_name, model, collision_node):
|
||||
"""将模型添加到碰撞分组"""
|
||||
if group_name in self.collision_groups:
|
||||
self.collision_groups[group_name]['objects'].append({
|
||||
'model': model,
|
||||
'collision_node': collision_node
|
||||
})
|
||||
|
||||
def enableCollisionGroup(self, group_name, enabled=True):
|
||||
"""启用/禁用碰撞分组"""
|
||||
if group_name in self.group_enabled:
|
||||
self.group_enabled[group_name] = enabled
|
||||
|
||||
# 更新分组中所有对象的碰撞状态
|
||||
if group_name in self.collision_groups:
|
||||
for obj in self.collision_groups[group_name]['objects']:
|
||||
collision_node = obj['collision_node']
|
||||
if enabled:
|
||||
collision_node.show()
|
||||
else:
|
||||
collision_node.hide()
|
||||
|
||||
print(f"{'✅ 启用' if enabled else '❌ 禁用'}碰撞分组: {group_name}")
|
||||
|
||||
def registerCollisionCallback(self, mask_a, mask_b, callback_func, filter_func=None):
|
||||
"""注册碰撞回调函数
|
||||
|
||||
Args:
|
||||
mask_a: 第一个掩码类型
|
||||
mask_b: 第二个掩码类型
|
||||
callback_func: 碰撞回调函数 callback(collision_info)
|
||||
filter_func: 过滤函数 filter(model_a, model_b) -> bool
|
||||
"""
|
||||
key = tuple(sorted([mask_a, mask_b]))
|
||||
if key not in self.collision_callbacks:
|
||||
self.collision_callbacks[key] = []
|
||||
|
||||
self.collision_callbacks[key].append(callback_func)
|
||||
|
||||
if filter_func:
|
||||
if key not in self.collision_filters:
|
||||
self.collision_filters[key] = []
|
||||
self.collision_filters[key].append(filter_func)
|
||||
|
||||
print(f"✅ 注册碰撞回调: {mask_a} <-> {mask_b}")
|
||||
|
||||
def _executeCollisionCallbacks(self, collision_info):
|
||||
"""执行碰撞回调"""
|
||||
model_a = collision_info['model_a']['node']
|
||||
model_b = collision_info['model_b']['node']
|
||||
|
||||
# 获取模型的掩码类型
|
||||
mask_a = self._getModelMaskType(model_a)
|
||||
mask_b = self._getModelMaskType(model_b)
|
||||
|
||||
if mask_a and mask_b:
|
||||
key = tuple(sorted([mask_a, mask_b]))
|
||||
|
||||
# 检查过滤条件
|
||||
if key in self.collision_filters:
|
||||
for filter_func in self.collision_filters[key]:
|
||||
if not filter_func(model_a, model_b):
|
||||
return
|
||||
|
||||
# 执行回调
|
||||
if key in self.collision_callbacks:
|
||||
for callback_func in self.collision_callbacks[key]:
|
||||
try:
|
||||
callback_func(collision_info)
|
||||
except Exception as e:
|
||||
print(f"❌ 碰撞回调执行失败: {e}")
|
||||
|
||||
def _getModelMaskType(self, model):
|
||||
"""获取模型的掩码类型"""
|
||||
# 从模型标签或属性中获取掩码类型
|
||||
if model.hasTag('collision_mask_type'):
|
||||
return model.getTag('collision_mask_type')
|
||||
return None
|
||||
|
||||
def enableSpatialPartitioning(self, enabled=True, max_depth=6, max_objects=10):
|
||||
"""启用空间分割优化
|
||||
|
||||
Args:
|
||||
enabled: 是否启用
|
||||
max_depth: 八叉树最大深度
|
||||
max_objects: 每个节点最大对象数
|
||||
"""
|
||||
self.spatial_partitioning_enabled = enabled
|
||||
self.octree_max_depth = max_depth
|
||||
self.octree_max_objects = max_objects
|
||||
|
||||
if enabled:
|
||||
self._buildOctree()
|
||||
print(f"✅ 启用空间分割优化 - 深度: {max_depth}, 对象数: {max_objects}")
|
||||
else:
|
||||
self.octree = None
|
||||
print("❌ 禁用空间分割优化")
|
||||
|
||||
def _buildOctree(self):
|
||||
"""构建八叉树"""
|
||||
if not hasattr(self.world, 'models') or not self.world.models:
|
||||
return
|
||||
|
||||
# 计算场景边界
|
||||
min_bound = Point3(float('inf'), float('inf'), float('inf'))
|
||||
max_bound = Point3(float('-inf'), float('-inf'), float('-inf'))
|
||||
|
||||
for model in self.world.models:
|
||||
bounds = model.getBounds()
|
||||
if not bounds.isEmpty():
|
||||
model_min = bounds.getMin()
|
||||
model_max = bounds.getMax()
|
||||
|
||||
min_bound.x = min(min_bound.x, model_min.x)
|
||||
min_bound.y = min(min_bound.y, model_min.y)
|
||||
min_bound.z = min(min_bound.z, model_min.z)
|
||||
|
||||
max_bound.x = max(max_bound.x, model_max.x)
|
||||
max_bound.y = max(max_bound.y, model_max.y)
|
||||
max_bound.z = max(max_bound.z, model_max.z)
|
||||
|
||||
# 创建八叉树根节点
|
||||
self.octree = OctreeNode(min_bound, max_bound, 0, self.octree_max_depth, self.octree_max_objects)
|
||||
|
||||
# 将模型插入八叉树
|
||||
for model in self.world.models:
|
||||
self.octree.insert(model)
|
||||
|
||||
def detectModelCollisionsOptimized(self, specific_models=None, log_results=True):
|
||||
"""使用空间分割优化的碰撞检测"""
|
||||
if not self.spatial_partitioning_enabled or not self.octree:
|
||||
return self.detectModelCollisions(specific_models, log_results)
|
||||
|
||||
start_time = time.perf_counter()
|
||||
current_time = datetime.now()
|
||||
|
||||
models_to_check = specific_models if specific_models else self.world.models
|
||||
if len(models_to_check) < 2:
|
||||
return []
|
||||
|
||||
collision_results = []
|
||||
current_collision_pairs = set()
|
||||
|
||||
# 使用八叉树进行优化检测
|
||||
for model in models_to_check:
|
||||
# 获取可能碰撞的模型列表
|
||||
potential_collisions = self.octree.query(model)
|
||||
|
||||
for other_model in potential_collisions:
|
||||
if model == other_model:
|
||||
continue
|
||||
|
||||
pair_key = tuple(sorted([id(model), id(other_model)]))
|
||||
if pair_key in current_collision_pairs:
|
||||
continue
|
||||
|
||||
collision_info = self._checkModelPairCollision(model, other_model, current_time)
|
||||
if collision_info:
|
||||
collision_results.append(collision_info)
|
||||
current_collision_pairs.add(pair_key)
|
||||
|
||||
# 执行回调
|
||||
self._executeCollisionCallbacks(collision_info)
|
||||
|
||||
# 只有新碰撞才打印日志
|
||||
if log_results and pair_key not in self.active_collisions:
|
||||
self._logCollisionInfo(collision_info)
|
||||
print(f"🆕 新碰撞检测到!")
|
||||
|
||||
# 更新活跃碰撞状态
|
||||
ended_collisions = self.active_collisions - current_collision_pairs
|
||||
for pair_key in ended_collisions:
|
||||
print(f"✅ 碰撞结束: 模型对 {pair_key}")
|
||||
|
||||
self.active_collisions = current_collision_pairs
|
||||
|
||||
# 记录性能
|
||||
detection_time = time.perf_counter() - start_time
|
||||
self.performance_monitor.recordModelCollisionDetection(
|
||||
detection_time, len(models_to_check), len(collision_results))
|
||||
|
||||
return collision_results
|
||||
|
||||
def cleanup(self):
|
||||
"""清理碰撞管理器资源"""
|
||||
self._stopCollisionDetectionTask()
|
||||
self.collision_history.clear()
|
||||
self.active_collisions.clear()
|
||||
print("✅ 碰撞管理器已清理")
|
||||
|
||||
def setupAdvancedCollision(self, model, shape_type='auto', mask_type='SELECTABLE',
|
||||
group_name=None, **shape_kwargs):
|
||||
"""高级碰撞设置方法
|
||||
|
||||
Args:
|
||||
model: 模型节点
|
||||
shape_type: 碰撞体形状类型
|
||||
mask_type: 掩码类型
|
||||
group_name: 碰撞分组名称
|
||||
**shape_kwargs: 形状特定参数
|
||||
|
||||
Returns:
|
||||
collision_node_path: 碰撞节点路径
|
||||
"""
|
||||
# 创建碰撞节点
|
||||
cNode = CollisionNode(f'collision_{model.getName()}')
|
||||
|
||||
# 设置掩码
|
||||
if mask_type in self.MASKS:
|
||||
cNode.setIntoCollideMask(self.MASKS[mask_type])
|
||||
# 设置模型标签用于回调识别
|
||||
model.setTag('collision_mask_type', mask_type)
|
||||
|
||||
# 创建碰撞体
|
||||
collision_solid = self.createCollisionShape(model, shape_type, **shape_kwargs)
|
||||
cNode.addSolid(collision_solid)
|
||||
|
||||
# 附加到模型
|
||||
cNodePath = model.attachNewNode(cNode)
|
||||
|
||||
# 添加到分组
|
||||
if group_name:
|
||||
if group_name not in self.collision_groups:
|
||||
self.createCollisionGroup(group_name, [mask_type])
|
||||
self.addToCollisionGroup(group_name, model, cNodePath)
|
||||
|
||||
# 根据调试设置决定是否显示
|
||||
if hasattr(self.world, 'debug_collision') and self.world.debug_collision:
|
||||
cNodePath.show()
|
||||
else:
|
||||
cNodePath.hide()
|
||||
|
||||
print(f"✅ 为 {model.getName()} 设置高级碰撞 - 形状: {shape_type}, 掩码: {mask_type}")
|
||||
return cNodePath
|
||||
|
||||
def example_usage(self):
|
||||
"""碰撞系统使用示例"""
|
||||
print("\n=== 碰撞系统使用示例 ===")
|
||||
|
||||
# 1. 创建碰撞分组
|
||||
self.createCollisionGroup('characters', ['CHARACTER', 'DYNAMIC_OBJECT'])
|
||||
self.createCollisionGroup('environment', ['STATIC_GEOMETRY', 'TERRAIN'])
|
||||
self.createCollisionGroup('pickups', ['PICKUP_ITEM', 'INTERACTIVE'])
|
||||
|
||||
# 2. 注册碰撞回调
|
||||
def character_pickup_callback(collision_info):
|
||||
print(f"角色拾取物品: {collision_info['model_a']['name']} -> {collision_info['model_b']['name']}")
|
||||
|
||||
def character_environment_callback(collision_info):
|
||||
print(f"角色碰撞环境: {collision_info['model_a']['name']} 碰到 {collision_info['model_b']['name']}")
|
||||
|
||||
self.registerCollisionCallback('CHARACTER', 'PICKUP_ITEM', character_pickup_callback)
|
||||
self.registerCollisionCallback('CHARACTER', 'STATIC_GEOMETRY', character_environment_callback)
|
||||
|
||||
# 3. 启用空间分割优化
|
||||
self.enableSpatialPartitioning(enabled=True, max_depth=6, max_objects=8)
|
||||
|
||||
# 4. 为模型设置不同类型的碰撞体
|
||||
# model1 = ... # 假设已有模型
|
||||
# self.setupAdvancedCollision(model1, 'capsule', 'CHARACTER', 'characters')
|
||||
# self.setupAdvancedCollision(model2, 'box', 'PICKUP_ITEM', 'pickups')
|
||||
# self.setupAdvancedCollision(model3, 'plane', 'TERRAIN', 'environment')
|
||||
|
||||
print("✅ 碰撞系统示例配置完成")
|
||||
|
||||
class CollisionPerformanceMonitor:
|
||||
"""碰撞检测性能监控器"""
|
||||
|
||||
def __init__(self):
|
||||
self.detection_times = []
|
||||
self.collision_counts = []
|
||||
self.model_collision_times = [] # 新增:模型间碰撞检测时间
|
||||
self.model_collision_data = [] # 新增:模型间碰撞数据
|
||||
self.max_records = 100
|
||||
|
||||
def recordDetection(self, detection_time, collision_count):
|
||||
"""记录射线检测性能"""
|
||||
self.detection_times.append(detection_time)
|
||||
self.collision_counts.append(collision_count)
|
||||
|
||||
# 限制记录数量
|
||||
if len(self.detection_times) > self.max_records:
|
||||
self.detection_times.pop(0)
|
||||
self.collision_counts.pop(0)
|
||||
|
||||
def recordModelCollisionDetection(self, detection_time, model_count, collision_count):
|
||||
"""记录模型间碰撞检测性能"""
|
||||
self.model_collision_times.append(detection_time)
|
||||
self.model_collision_data.append({
|
||||
'model_count': model_count,
|
||||
'collision_count': collision_count,
|
||||
'timestamp': time.time()
|
||||
})
|
||||
|
||||
# 限制记录数量
|
||||
if len(self.model_collision_times) > self.max_records:
|
||||
self.model_collision_times.pop(0)
|
||||
self.model_collision_data.pop(0)
|
||||
|
||||
def getAverageTime(self):
|
||||
"""获取平均射线检测时间"""
|
||||
if not self.detection_times:
|
||||
return 0
|
||||
return sum(self.detection_times) / len(self.detection_times)
|
||||
|
||||
def getAverageModelCollisionTime(self):
|
||||
"""获取平均模型间碰撞检测时间"""
|
||||
if not self.model_collision_times:
|
||||
return 0
|
||||
return sum(self.model_collision_times) / len(self.model_collision_times)
|
||||
|
||||
def getPerformanceReport(self):
|
||||
"""获取完整性能报告"""
|
||||
report = []
|
||||
|
||||
# 射线检测性能
|
||||
if self.detection_times:
|
||||
avg_time = self.getAverageTime()
|
||||
max_time = max(self.detection_times)
|
||||
avg_collisions = sum(self.collision_counts) / len(self.collision_counts)
|
||||
|
||||
report.append("=== 射线检测性能 ===")
|
||||
report.append(f"平均检测时间: {avg_time*1000:.2f}ms")
|
||||
report.append(f"最大检测时间: {max_time*1000:.2f}ms")
|
||||
report.append(f"平均碰撞数量: {avg_collisions:.1f}")
|
||||
report.append(f"检测次数: {len(self.detection_times)}")
|
||||
|
||||
# 模型间碰撞检测性能
|
||||
if self.model_collision_times:
|
||||
avg_model_time = self.getAverageModelCollisionTime()
|
||||
max_model_time = max(self.model_collision_times)
|
||||
avg_model_count = sum(d['model_count'] for d in self.model_collision_data) / len(self.model_collision_data)
|
||||
total_collisions = sum(d['collision_count'] for d in self.model_collision_data)
|
||||
|
||||
report.append("\n=== 模型间碰撞检测性能 ===")
|
||||
report.append(f"平均检测时间: {avg_model_time*1000:.2f}ms")
|
||||
report.append(f"最大检测时间: {max_model_time*1000:.2f}ms")
|
||||
report.append(f"平均模型数量: {avg_model_count:.1f}")
|
||||
report.append(f"总碰撞次数: {total_collisions}")
|
||||
report.append(f"检测次数: {len(self.model_collision_times)}")
|
||||
|
||||
return "\n".join(report) if report else "没有性能数据"
|
||||
|
||||
class OctreeNode:
|
||||
"""八叉树节点"""
|
||||
|
||||
def __init__(self, min_bound, max_bound, depth, max_depth, max_objects):
|
||||
self.min_bound = min_bound
|
||||
self.max_bound = max_bound
|
||||
self.depth = depth
|
||||
self.max_depth = max_depth
|
||||
self.max_objects = max_objects
|
||||
|
||||
self.objects = []
|
||||
self.children = []
|
||||
self.is_leaf = True
|
||||
|
||||
def insert(self, model):
|
||||
"""插入模型到八叉树"""
|
||||
if not self._contains(model):
|
||||
return False
|
||||
|
||||
if self.is_leaf:
|
||||
self.objects.append(model)
|
||||
|
||||
# 如果超过最大对象数且未达到最大深度,则分割
|
||||
if len(self.objects) > self.max_objects and self.depth < self.max_depth:
|
||||
self._subdivide()
|
||||
|
||||
# 重新分配对象到子节点
|
||||
remaining_objects = []
|
||||
for obj in self.objects:
|
||||
inserted = False
|
||||
for child in self.children:
|
||||
if child.insert(obj):
|
||||
inserted = True
|
||||
break
|
||||
if not inserted:
|
||||
remaining_objects.append(obj)
|
||||
|
||||
self.objects = remaining_objects
|
||||
else:
|
||||
# 尝试插入到子节点
|
||||
inserted = False
|
||||
for child in self.children:
|
||||
if child.insert(model):
|
||||
inserted = True
|
||||
break
|
||||
|
||||
if not inserted:
|
||||
self.objects.append(model)
|
||||
|
||||
return True
|
||||
|
||||
def query(self, model):
|
||||
"""查询可能与指定模型碰撞的模型列表"""
|
||||
if not self._contains(model):
|
||||
return []
|
||||
|
||||
result = list(self.objects)
|
||||
|
||||
if not self.is_leaf:
|
||||
for child in self.children:
|
||||
result.extend(child.query(model))
|
||||
|
||||
return result
|
||||
|
||||
def _contains(self, model):
|
||||
"""检查模型是否在此节点范围内"""
|
||||
bounds = model.getBounds()
|
||||
if bounds.isEmpty():
|
||||
return False
|
||||
|
||||
model_min = bounds.getMin()
|
||||
model_max = bounds.getMax()
|
||||
|
||||
return (model_max.x >= self.min_bound.x and model_min.x <= self.max_bound.x and
|
||||
model_max.y >= self.min_bound.y and model_min.y <= self.max_bound.y and
|
||||
model_max.z >= self.min_bound.z and model_min.z <= self.max_bound.z)
|
||||
|
||||
def _subdivide(self):
|
||||
"""分割节点为8个子节点"""
|
||||
center = Point3(
|
||||
(self.min_bound.x + self.max_bound.x) * 0.5,
|
||||
(self.min_bound.y + self.max_bound.y) * 0.5,
|
||||
(self.min_bound.z + self.max_bound.z) * 0.5
|
||||
)
|
||||
|
||||
# 创建8个子节点
|
||||
subdivisions = [
|
||||
(self.min_bound, center),
|
||||
(Point3(center.x, self.min_bound.y, self.min_bound.z), Point3(self.max_bound.x, center.y, center.z)),
|
||||
(Point3(self.min_bound.x, center.y, self.min_bound.z), Point3(center.x, self.max_bound.y, center.z)),
|
||||
(Point3(center.x, center.y, self.min_bound.z), Point3(self.max_bound.x, self.max_bound.y, center.z)),
|
||||
(Point3(self.min_bound.x, self.min_bound.y, center.z), Point3(center.x, center.y, self.max_bound.z)),
|
||||
(Point3(center.x, self.min_bound.y, center.z), Point3(self.max_bound.x, center.y, self.max_bound.z)),
|
||||
(Point3(self.min_bound.x, center.y, center.z), Point3(center.x, self.max_bound.y, self.max_bound.z)),
|
||||
(center, self.max_bound)
|
||||
]
|
||||
|
||||
for min_b, max_b in subdivisions:
|
||||
child = OctreeNode(min_b, max_b, self.depth + 1, self.max_depth, self.max_objects)
|
||||
self.children.append(child)
|
||||
|
||||
self.is_leaf = False
|
||||
@ -41,180 +41,568 @@ class GUIManager:
|
||||
self.currentGUITool = None
|
||||
|
||||
print("✓ GUI管理系统初始化完成")
|
||||
|
||||
|
||||
# ==================== GUI元素创建方法 ====================
|
||||
|
||||
|
||||
def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1):
|
||||
"""创建2D GUI按钮"""
|
||||
from direct.gui.DirectGui import DirectButton
|
||||
|
||||
# 将3D坐标转换为2D屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
|
||||
button = DirectButton(
|
||||
text=text,
|
||||
pos=gui_pos,
|
||||
scale=size,
|
||||
command=self.onGUIButtonClick,
|
||||
extraArgs=[f"button_{len(self.gui_elements)}"],
|
||||
frameColor=(0.2, 0.6, 0.8, 1),
|
||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None,
|
||||
rolloverSound=None,
|
||||
clickSound=None
|
||||
)
|
||||
|
||||
# 为GUI元素添加标识
|
||||
button.setTag("gui_type", "button")
|
||||
button.setTag("gui_id", f"button_{len(self.gui_elements)}")
|
||||
button.setTag("gui_text", text)
|
||||
button.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(button)
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 创建GUI按钮: {text} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||
return button
|
||||
|
||||
"""创建2D GUI按钮 - 支持多选创建和GUI父子关系,优化版本"""
|
||||
try:
|
||||
from direct.gui.DirectGui import DirectButton
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
print(f"🔘 开始创建GUI按钮,位置: {pos}, 文本: {text}, 尺寸: {size}")
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
created_buttons = []
|
||||
|
||||
# 为每个有效的父节点创建GUI按钮
|
||||
for parent_item, parent_node in target_parents:
|
||||
try:
|
||||
# 生成唯一名称
|
||||
button_name = f"GUIButton_{len(self.gui_elements)}"
|
||||
|
||||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||||
if tree_widget.is_gui_element(parent_node):
|
||||
# 父节点是GUI元素 - 作为子GUI挂载
|
||||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||||
parent_gui_node = parent_node # 直接挂载到GUI元素
|
||||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||||
else:
|
||||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
parent_gui_node = None # 使用默认的aspect2d
|
||||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||||
|
||||
button = DirectButton(
|
||||
text=text,
|
||||
pos=gui_pos,
|
||||
scale=size,
|
||||
command=self.onGUIButtonClick,
|
||||
extraArgs=[f"button_{len(self.gui_elements)}"],
|
||||
frameColor=(0.2, 0.6, 0.8, 1),
|
||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None,
|
||||
rolloverSound=None,
|
||||
clickSound=None,
|
||||
parent=parent_gui_node # 设置GUI父节点
|
||||
)
|
||||
|
||||
# 设置节点标签
|
||||
button.setTag("gui_type", "button")
|
||||
button.setTag("gui_id", f"button_{len(self.gui_elements)}")
|
||||
button.setTag("gui_text", text)
|
||||
button.setTag("is_gui_element", "1")
|
||||
button.setTag("is_scene_element", "1")
|
||||
button.setTag("created_by_user", "1")
|
||||
button.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||||
button.setName(button_name)
|
||||
|
||||
# 如果有GUI父节点,建立引用关系
|
||||
if parent_gui_node:
|
||||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||||
button.setTag("gui_parent_id", parent_id)
|
||||
|
||||
# 添加到GUI元素列表
|
||||
self.gui_elements.append(button)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建GUI按钮成功: {button_name}")
|
||||
|
||||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(button, parent_item, "GUI_BUTTON")
|
||||
if qt_item:
|
||||
created_buttons.append((button, qt_item))
|
||||
else:
|
||||
created_buttons.append((button, None))
|
||||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建GUI按钮失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 处理创建结果
|
||||
if not created_buttons:
|
||||
print("❌ 没有成功创建任何GUI按钮")
|
||||
return None
|
||||
|
||||
# 选中最后创建的按钮并更新场景树
|
||||
if created_buttons:
|
||||
last_button, last_qt_item = created_buttons[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
tree_widget.update_selection_and_properties(last_button, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_buttons)} 个GUI按钮")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_buttons) == 1:
|
||||
return created_buttons[0][0]
|
||||
else:
|
||||
return [button for button, _ in created_buttons]
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建GUI按钮过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08):
|
||||
"""创建2D GUI标签"""
|
||||
from direct.gui.DirectGui import DirectLabel
|
||||
|
||||
# 将3D坐标转换为2D屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
|
||||
label = DirectLabel(
|
||||
text=text,
|
||||
pos=gui_pos,
|
||||
scale=size,
|
||||
frameColor=(0, 0, 0, 0), # 透明背景
|
||||
text_fg=(1, 1, 1, 1),
|
||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
)
|
||||
|
||||
# 为GUI元素添加标识
|
||||
label.setTag("gui_type", "label")
|
||||
label.setTag("gui_id", f"label_{len(self.gui_elements)}")
|
||||
label.setTag("gui_text", text)
|
||||
label.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(label)
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 创建GUI标签: {text} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||
return label
|
||||
|
||||
"""创建2D GUI标签 - 支持多选创建和GUI父子关系,优化版本"""
|
||||
try:
|
||||
from direct.gui.DirectGui import DirectLabel
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
print(f"🏷️ 开始创建GUI标签,位置: {pos}, 文本: {text}, 尺寸: {size}")
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
created_labels = []
|
||||
|
||||
# 为每个有效的父节点创建GUI标签
|
||||
for parent_item, parent_node in target_parents:
|
||||
try:
|
||||
# 生成唯一名称
|
||||
label_name = f"GUILabel_{len(self.gui_elements)}"
|
||||
|
||||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||||
if tree_widget.is_gui_element(parent_node):
|
||||
# 父节点是GUI元素 - 作为子GUI挂载
|
||||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||||
parent_gui_node = parent_node
|
||||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||||
else:
|
||||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
parent_gui_node = None
|
||||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||||
|
||||
label = DirectLabel(
|
||||
text=text,
|
||||
pos=gui_pos,
|
||||
scale=size,
|
||||
frameColor=(0, 0, 0, 0), # 透明背景
|
||||
text_fg=(1, 1, 1, 1),
|
||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None,
|
||||
parent=parent_gui_node # 设置GUI父节点
|
||||
)
|
||||
|
||||
# 设置节点标签
|
||||
label.setTag("gui_type", "label")
|
||||
label.setTag("gui_id", f"label_{len(self.gui_elements)}")
|
||||
label.setTag("gui_text", text)
|
||||
label.setTag("is_gui_element", "1")
|
||||
label.setTag("is_scene_element", "1")
|
||||
label.setTag("created_by_user", "1")
|
||||
label.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||||
label.setName(label_name)
|
||||
|
||||
# 如果有GUI父节点,建立引用关系
|
||||
if parent_gui_node:
|
||||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||||
label.setTag("gui_parent_id", parent_id)
|
||||
|
||||
# 添加到GUI元素列表
|
||||
self.gui_elements.append(label)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建GUI标签成功: {label_name}")
|
||||
|
||||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(label, parent_item, "GUI_LABEL")
|
||||
if qt_item:
|
||||
created_labels.append((label, qt_item))
|
||||
else:
|
||||
created_labels.append((label, None))
|
||||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建GUI标签失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 处理创建结果
|
||||
if not created_labels:
|
||||
print("❌ 没有成功创建任何GUI标签")
|
||||
return None
|
||||
|
||||
# 选中最后创建的标签并更新场景树
|
||||
if created_labels:
|
||||
last_label, last_qt_item = created_labels[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
tree_widget.update_selection_and_properties(last_label, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_labels)} 个GUI标签")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_labels) == 1:
|
||||
return created_labels[0][0]
|
||||
else:
|
||||
return [label for label, _ in created_labels]
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建GUI标签过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
|
||||
"""创建2D GUI文本输入框"""
|
||||
from direct.gui.DirectGui import DirectEntry
|
||||
|
||||
# 将3D坐标转换为2D屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
|
||||
entry = DirectEntry(
|
||||
text="",
|
||||
pos=gui_pos,
|
||||
scale=size,
|
||||
command=self.onGUIEntrySubmit,
|
||||
extraArgs=[f"entry_{len(self.gui_elements)}"],
|
||||
initialText=placeholder,
|
||||
numLines=1,
|
||||
width=12,
|
||||
focus=0
|
||||
)
|
||||
|
||||
# 为GUI元素添加标识
|
||||
entry.setTag("gui_type", "entry")
|
||||
entry.setTag("gui_id", f"entry_{len(self.gui_elements)}")
|
||||
entry.setTag("gui_placeholder", placeholder)
|
||||
entry.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(entry)
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||
return entry
|
||||
|
||||
"""创建2D GUI文本输入框 - 支持多选创建和GUI父子关系,优化版本"""
|
||||
try:
|
||||
from direct.gui.DirectGui import DirectEntry
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
print(f"📝 开始创建GUI输入框,位置: {pos}, 占位符: {placeholder}, 尺寸: {size}")
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
created_entries = []
|
||||
|
||||
# 为每个有效的父节点创建GUI输入框
|
||||
for parent_item, parent_node in target_parents:
|
||||
try:
|
||||
# 生成唯一名称
|
||||
entry_name = f"GUIEntry_{len(self.gui_elements)}"
|
||||
|
||||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||||
if tree_widget.is_gui_element(parent_node):
|
||||
# 父节点是GUI元素 - 作为子GUI挂载
|
||||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||||
parent_gui_node = parent_node
|
||||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||||
else:
|
||||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
parent_gui_node = None
|
||||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||||
|
||||
entry = DirectEntry(
|
||||
text="",
|
||||
pos=gui_pos,
|
||||
scale=size,
|
||||
command=self.onGUIEntrySubmit,
|
||||
extraArgs=[f"entry_{len(self.gui_elements)}"],
|
||||
initialText=placeholder,
|
||||
numLines=1,
|
||||
width=12,
|
||||
focus=0,
|
||||
parent=parent_gui_node # 设置GUI父节点
|
||||
)
|
||||
|
||||
# 设置节点标签
|
||||
entry.setTag("gui_type", "entry")
|
||||
entry.setTag("gui_id", f"entry_{len(self.gui_elements)}")
|
||||
entry.setTag("gui_placeholder", placeholder)
|
||||
entry.setTag("is_gui_element", "1")
|
||||
entry.setTag("is_scene_element", "1")
|
||||
entry.setTag("created_by_user", "1")
|
||||
entry.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||||
entry.setName(entry_name)
|
||||
|
||||
# 如果有GUI父节点,建立引用关系
|
||||
if parent_gui_node:
|
||||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||||
entry.setTag("gui_parent_id", parent_id)
|
||||
|
||||
# 添加到GUI元素列表
|
||||
self.gui_elements.append(entry)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建GUI输入框成功: {entry_name}")
|
||||
|
||||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(entry, parent_item, "GUI_ENTRY")
|
||||
if qt_item:
|
||||
created_entries.append((entry, qt_item))
|
||||
else:
|
||||
created_entries.append((entry, None))
|
||||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建GUI输入框失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 处理创建结果
|
||||
if not created_entries:
|
||||
print("❌ 没有成功创建任何GUI输入框")
|
||||
return None
|
||||
|
||||
# 选中最后创建的输入框并更新场景树
|
||||
if created_entries:
|
||||
last_entry, last_qt_item = created_entries[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
tree_widget.update_selection_and_properties(last_entry, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_entries)} 个GUI输入框")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_entries) == 1:
|
||||
return created_entries[0][0]
|
||||
else:
|
||||
return [entry for entry, _ in created_entries]
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建GUI输入框过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
|
||||
"""创建3D空间文本"""
|
||||
from panda3d.core import TextNode
|
||||
|
||||
textNode = TextNode(f'3d-text-{len(self.gui_elements)}')
|
||||
textNode.setText(text)
|
||||
textNode.setAlign(TextNode.ACenter)
|
||||
if self.world.getChineseFont():
|
||||
textNode.setFont(self.world.getChineseFont())
|
||||
|
||||
textNodePath = self.world.render.attachNewNode(textNode)
|
||||
textNodePath.setPos(*pos)
|
||||
textNodePath.setScale(size)
|
||||
textNodePath.setColor(1, 1, 0, 1)
|
||||
textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||||
|
||||
# 为GUI元素添加标识
|
||||
textNodePath.setTag("gui_type", "3d_text")
|
||||
textNodePath.setTag("gui_id", f"3d_text_{len(self.gui_elements)}")
|
||||
textNodePath.setTag("gui_text", text)
|
||||
textNodePath.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(textNodePath)
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 创建3D文本: {text} (世界位置: {pos})")
|
||||
return textNodePath
|
||||
"""创建3D空间文本 - 支持多选创建,优化版本"""
|
||||
try:
|
||||
from panda3d.core import TextNode
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
print(f"📄 开始创建3D文本,位置: {pos}, 文本: {text}, 尺寸: {size}")
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
created_texts = []
|
||||
|
||||
# 为每个有效的父节点创建3D文本
|
||||
for parent_item, parent_node in target_parents:
|
||||
try:
|
||||
# 生成唯一名称
|
||||
text_name = f"GUI3DText_{len(self.gui_elements)}"
|
||||
|
||||
textNode = TextNode(f'3d-text-{len(self.gui_elements)}')
|
||||
textNode.setText(text)
|
||||
textNode.setAlign(TextNode.ACenter)
|
||||
if self.world.getChineseFont():
|
||||
textNode.setFont(self.world.getChineseFont())
|
||||
|
||||
# 挂载到选中的父节点
|
||||
textNodePath = parent_node.attachNewNode(textNode)
|
||||
textNodePath.setPos(*pos)
|
||||
textNodePath.setScale(size)
|
||||
textNodePath.setColor(1, 1, 0, 1)
|
||||
textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||||
textNodePath.setName(text_name)
|
||||
|
||||
# 设置节点标签
|
||||
textNodePath.setTag("gui_type", "3d_text")
|
||||
textNodePath.setTag("gui_id", f"3d_text_{len(self.gui_elements)}")
|
||||
textNodePath.setTag("gui_text", text)
|
||||
textNodePath.setTag("is_gui_element", "1")
|
||||
textNodePath.setTag("is_scene_element", "1")
|
||||
textNodePath.setTag("created_by_user", "1")
|
||||
|
||||
# 添加到GUI元素列表
|
||||
self.gui_elements.append(textNodePath)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建3D文本成功: {text_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(textNodePath, parent_item, "GUI_3DTEXT")
|
||||
if qt_item:
|
||||
created_texts.append((textNodePath, qt_item))
|
||||
else:
|
||||
created_texts.append((textNodePath, None))
|
||||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建3D文本失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 处理创建结果
|
||||
if not created_texts:
|
||||
print("❌ 没有成功创建任何3D文本")
|
||||
return None
|
||||
|
||||
# 选中最后创建的文本并更新场景树
|
||||
if created_texts:
|
||||
last_text, last_qt_item = created_texts[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
tree_widget.update_selection_and_properties(last_text, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_texts)} 个3D文本")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_texts) == 1:
|
||||
return created_texts[0][0]
|
||||
else:
|
||||
return [text_np for text_np, _ in created_texts]
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建3D文本过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
|
||||
"""创建3D虚拟屏幕"""
|
||||
from panda3d.core import CardMaker, TransparencyAttrib, TextNode
|
||||
|
||||
# 创建虚拟屏幕
|
||||
cm = CardMaker(f"virtual-screen-{len(self.gui_elements)}")
|
||||
cm.setFrame(-size[0]/2, size[0]/2, -size[1]/2, size[1]/2)
|
||||
virtualScreen = self.world.render.attachNewNode(cm.generate())
|
||||
virtualScreen.setPos(*pos)
|
||||
virtualScreen.setColor(0.2, 0.2, 0.2, 0.8)
|
||||
virtualScreen.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
# 在虚拟屏幕上添加文本
|
||||
screenText = TextNode(f'screen-text-{len(self.gui_elements)}')
|
||||
screenText.setText(text)
|
||||
screenText.setAlign(TextNode.ACenter)
|
||||
if self.world.getChineseFont():
|
||||
screenText.setFont(self.world.getChineseFont())
|
||||
screenTextNP = virtualScreen.attachNewNode(screenText)
|
||||
screenTextNP.setPos(0, 0.01, 0)
|
||||
screenTextNP.setScale(0.3)
|
||||
screenTextNP.setColor(0, 1, 0, 1)
|
||||
|
||||
# 为GUI元素添加标识
|
||||
virtualScreen.setTag("gui_type", "virtual_screen")
|
||||
virtualScreen.setTag("gui_id", f"virtual_screen_{len(self.gui_elements)}")
|
||||
virtualScreen.setTag("gui_text", text)
|
||||
virtualScreen.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(virtualScreen)
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 创建虚拟屏幕: {text} (世界位置: {pos})")
|
||||
return virtualScreen
|
||||
|
||||
"""创建3D虚拟屏幕 - 支持多选创建,优化版本"""
|
||||
try:
|
||||
from panda3d.core import CardMaker, TransparencyAttrib, TextNode
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
print(f"🖥️ 开始创建虚拟屏幕,位置: {pos}, 尺寸: {size}, 文本: {text}")
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
created_screens = []
|
||||
|
||||
# 为每个有效的父节点创建虚拟屏幕
|
||||
for parent_item, parent_node in target_parents:
|
||||
try:
|
||||
# 生成唯一名称
|
||||
screen_name = f"VirtualScreen_{len(self.gui_elements)}"
|
||||
|
||||
# 创建虚拟屏幕几何体
|
||||
cm = CardMaker(f"virtual-screen-{len(self.gui_elements)}")
|
||||
cm.setFrame(-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2)
|
||||
|
||||
# 创建挂载节点 - 挂载到选中的父节点
|
||||
virtual_screen = parent_node.attachNewNode(cm.generate())
|
||||
virtual_screen.setPos(*pos)
|
||||
virtual_screen.setName(screen_name)
|
||||
virtual_screen.setColor(0.2, 0.2, 0.2, 0.8)
|
||||
virtual_screen.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
# 在虚拟屏幕上添加文本
|
||||
screen_text_node = self._create_screen_text(virtual_screen, text, len(self.gui_elements))
|
||||
|
||||
# 设置节点标签
|
||||
virtual_screen.setTag("gui_type", "virtual_screen")
|
||||
virtual_screen.setTag("gui_id", f"virtual_screen_{len(self.gui_elements)}")
|
||||
virtual_screen.setTag("gui_text", text)
|
||||
virtual_screen.setTag("is_gui_element", "1")
|
||||
virtual_screen.setTag("is_scene_element", "1")
|
||||
virtual_screen.setTag("created_by_user", "1")
|
||||
|
||||
# 添加到GUI元素列表
|
||||
self.gui_elements.append(virtual_screen)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建虚拟屏幕成功: {screen_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(virtual_screen, parent_item, "GUI_VirtualScreen")
|
||||
if qt_item:
|
||||
created_screens.append((virtual_screen, qt_item))
|
||||
else:
|
||||
created_screens.append((virtual_screen, None))
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建虚拟屏幕失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 处理创建结果
|
||||
if not created_screens:
|
||||
print("❌ 没有成功创建任何虚拟屏幕")
|
||||
return None
|
||||
|
||||
# 选中最后创建的虚拟屏幕
|
||||
if created_screens:
|
||||
last_screen_np, last_qt_item = created_screens[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
# 更新选择和属性面板
|
||||
tree_widget.update_selection_and_properties(last_screen_np, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_screens)} 个虚拟屏幕")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_screens) == 1:
|
||||
return created_screens[0][0] # 单个屏幕返回NodePath
|
||||
else:
|
||||
return [screen_np for screen_np, _ in created_screens] # 多个屏幕返回列表
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建虚拟屏幕过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _create_screen_text(self, virtual_screen, text, screen_index):
|
||||
"""为虚拟屏幕创建文本节点"""
|
||||
try:
|
||||
from panda3d.core import TextNode
|
||||
|
||||
screen_text = TextNode(f'screen-text-{screen_index}')
|
||||
screen_text.setText(text)
|
||||
screen_text.setAlign(TextNode.ACenter)
|
||||
|
||||
# 设置中文字体
|
||||
if hasattr(self.world, 'getChineseFont') and self.world.getChineseFont():
|
||||
screen_text.setFont(self.world.getChineseFont())
|
||||
|
||||
# 创建文本节点路径并设置属性
|
||||
screen_text_np = virtual_screen.attachNewNode(screen_text)
|
||||
screen_text_np.setPos(0, 0.01, 0) # 稍微向前偏移避免Z-fighting
|
||||
screen_text_np.setScale(0.3)
|
||||
screen_text_np.setColor(0, 1, 0, 1) # 绿色文本
|
||||
|
||||
print(f"✅ 虚拟屏幕文本创建成功: {text}")
|
||||
return screen_text_np
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建虚拟屏幕文本失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _get_tree_widget(self):
|
||||
"""安全获取树形控件"""
|
||||
try:
|
||||
if (hasattr(self.world, 'interface_manager') and
|
||||
hasattr(self.world.interface_manager, 'treeWidget')):
|
||||
return self.world.interface_manager.treeWidget
|
||||
except AttributeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def createGUISlider(self, pos=(0, 0, 0), text="滑块", scale=0.3):
|
||||
"""创建2D GUI滑块"""
|
||||
from direct.gui.DirectGui import DirectSlider
|
||||
|
||||
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
|
||||
|
||||
slider = DirectSlider(
|
||||
pos=gui_pos,
|
||||
scale=scale,
|
||||
@ -223,20 +611,21 @@ class GUIManager:
|
||||
frameColor=(0.6, 0.6, 0.6, 1),
|
||||
thumbColor=(0.2, 0.8, 0.2, 1)
|
||||
)
|
||||
|
||||
|
||||
slider.setTag("gui_type", "slider")
|
||||
slider.setTag("gui_id", f"slider_{len(self.gui_elements)}")
|
||||
slider.setTag("gui_text", text)
|
||||
slider.setTag("is_gui_element", "1")
|
||||
|
||||
|
||||
self.gui_elements.append(slider)
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
pass # CH
|
||||
self.world.updateSceneTree()
|
||||
|
||||
|
||||
print(f"✓ 创建GUI滑块: {text} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||
return slider
|
||||
|
||||
|
||||
# ==================== GUI元素管理方法 ====================
|
||||
|
||||
def deleteGUIElement(self, gui_element):
|
||||
@ -937,7 +1326,7 @@ class GUIManager:
|
||||
"""编辑2D GUI元素位置"""
|
||||
try:
|
||||
current_pos = gui_element.getPos()
|
||||
|
||||
|
||||
if axis == "x":
|
||||
# 将逻辑坐标转换为屏幕坐标
|
||||
new_screen_x = value * 0.1
|
||||
@ -946,8 +1335,8 @@ class GUIManager:
|
||||
# 将逻辑坐标转换为屏幕坐标
|
||||
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)}")
|
||||
BIN
icons/move_tool.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
icons/rotate_tool.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
icons/scale_tool.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
icons/select_tool.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
250
main.py
@ -1,4 +1,7 @@
|
||||
import warnings
|
||||
|
||||
from demo.video_integration import VideoManager
|
||||
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
import sys
|
||||
@ -62,6 +65,9 @@ class MyWorld(CoreWorld):
|
||||
|
||||
# 初始化GUI管理系统
|
||||
self.gui_manager = GUIManager(self)
|
||||
|
||||
# 初始化视频管理
|
||||
self.video_manager = VideoManager(self)
|
||||
|
||||
# 初始化场景管理系统
|
||||
self.scene_manager = SceneManager(self)
|
||||
@ -85,27 +91,38 @@ class MyWorld(CoreWorld):
|
||||
|
||||
#self.material_editor = None
|
||||
|
||||
# 初始化碰撞管理器
|
||||
from core.collision_manager import CollisionManager
|
||||
self.collision_manager = CollisionManager(self)
|
||||
|
||||
# 调试选项
|
||||
self.debug_collision = True # 是否显示碰撞体
|
||||
|
||||
# 默认启用模型间碰撞检测(可选)
|
||||
self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5)
|
||||
|
||||
print("✓ MyWorld 初始化完成")
|
||||
|
||||
print("✅ 碰撞管理器已初始化")
|
||||
|
||||
# ==================== 兼容性属性 ====================
|
||||
|
||||
|
||||
# 保留models属性以兼容现有代码
|
||||
@property
|
||||
def models(self):
|
||||
"""模型列表的兼容性属性"""
|
||||
return self.scene_manager.models
|
||||
|
||||
|
||||
@models.setter
|
||||
def models(self, value):
|
||||
"""模型列表的兼容性设置器"""
|
||||
self.scene_manager.models = value
|
||||
|
||||
|
||||
# 保留gui_elements属性以兼容现有代码
|
||||
@property
|
||||
def gui_elements(self):
|
||||
"""GUI元素列表的兼容性属性"""
|
||||
return self.gui_manager.gui_elements
|
||||
|
||||
|
||||
@gui_elements.setter
|
||||
def gui_elements(self, value):
|
||||
"""GUI元素列表的兼容性设置器"""
|
||||
@ -130,55 +147,55 @@ class MyWorld(CoreWorld):
|
||||
def Pointlight(self,value):
|
||||
self.scene_manager.Pointlight = value
|
||||
|
||||
|
||||
|
||||
# 保留guiEditMode属性以兼容现有代码
|
||||
@property
|
||||
def guiEditMode(self):
|
||||
"""GUI编辑模式的兼容性属性"""
|
||||
return self.gui_manager.guiEditMode
|
||||
|
||||
|
||||
@guiEditMode.setter
|
||||
def guiEditMode(self, value):
|
||||
"""GUI编辑模式的兼容性设置器"""
|
||||
self.gui_manager.guiEditMode = value
|
||||
|
||||
|
||||
# 保留currentTool属性以兼容现有代码
|
||||
@property
|
||||
def currentTool(self):
|
||||
"""当前工具的兼容性属性"""
|
||||
return self.tool_manager.currentTool
|
||||
|
||||
|
||||
@currentTool.setter
|
||||
def currentTool(self, value):
|
||||
"""当前工具的兼容性设置器"""
|
||||
self.tool_manager.currentTool = value
|
||||
|
||||
|
||||
# 保留treeWidget属性以兼容现有代码
|
||||
@property
|
||||
def treeWidget(self):
|
||||
"""树形控件的兼容性属性"""
|
||||
return self.interface_manager.treeWidget
|
||||
|
||||
|
||||
@treeWidget.setter
|
||||
def treeWidget(self, value):
|
||||
"""树形控件的兼容性设置器"""
|
||||
self.interface_manager.treeWidget = value
|
||||
|
||||
# ==================== GUI管理功能代理 ====================
|
||||
|
||||
|
||||
# GUI元素创建方法 - 代理到gui_manager
|
||||
def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1):
|
||||
"""创建2D GUI按钮"""
|
||||
return self.gui_manager.createGUIButton(pos, text, size)
|
||||
|
||||
|
||||
def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08):
|
||||
"""创建2D GUI标签"""
|
||||
return self.gui_manager.createGUILabel(pos, text, size)
|
||||
|
||||
|
||||
def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
|
||||
"""创建2D GUI文本输入框"""
|
||||
return self.gui_manager.createGUIEntry(pos, placeholder, size)
|
||||
|
||||
|
||||
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
|
||||
"""创建3D空间文本"""
|
||||
return self.gui_manager.createGUI3DText(pos, text, size)
|
||||
@ -190,110 +207,110 @@ class MyWorld(CoreWorld):
|
||||
def createPointLight(self,pos=(20,0,5)):
|
||||
"""创建点光源"""
|
||||
return self.scene_manager.createPointLight(pos)
|
||||
|
||||
|
||||
def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
|
||||
"""创建3D虚拟屏幕"""
|
||||
return self.gui_manager.createGUIVirtualScreen(pos, size, text)
|
||||
|
||||
|
||||
def createGUISlider(self, pos=(0, 0, 0), text="滑块", scale=0.3):
|
||||
"""创建2D GUI滑块"""
|
||||
return self.gui_manager.createGUISlider(pos, text, scale)
|
||||
|
||||
|
||||
# GUI元素管理方法 - 代理到gui_manager
|
||||
def deleteGUIElement(self, gui_element):
|
||||
"""删除GUI元素"""
|
||||
return self.gui_manager.deleteGUIElement(gui_element)
|
||||
|
||||
|
||||
def editGUIElement(self, gui_element, property_name, value):
|
||||
"""编辑GUI元素属性"""
|
||||
return self.gui_manager.editGUIElement(gui_element, property_name, value)
|
||||
|
||||
|
||||
def duplicateGUIElement(self, gui_element):
|
||||
"""复制GUI元素"""
|
||||
return self.gui_manager.duplicateGUIElement(gui_element)
|
||||
|
||||
|
||||
def editGUIElementDialog(self, gui_element):
|
||||
"""显示GUI元素编辑对话框"""
|
||||
return self.gui_manager.editGUIElementDialog(gui_element)
|
||||
|
||||
|
||||
# GUI事件处理方法 - 代理到gui_manager
|
||||
def onGUIButtonClick(self, button_id):
|
||||
"""GUI按钮点击事件处理"""
|
||||
return self.gui_manager.onGUIButtonClick(button_id)
|
||||
|
||||
|
||||
def onGUIEntrySubmit(self, text, entry_id):
|
||||
"""GUI输入框提交事件处理"""
|
||||
return self.gui_manager.onGUIEntrySubmit(text, entry_id)
|
||||
|
||||
|
||||
# GUI编辑模式方法 - 代理到gui_manager
|
||||
def toggleGUIEditMode(self):
|
||||
"""切换GUI编辑模式"""
|
||||
return self.gui_manager.toggleGUIEditMode()
|
||||
|
||||
|
||||
def enterGUIEditMode(self):
|
||||
"""进入GUI编辑模式"""
|
||||
return self.gui_manager.enterGUIEditMode()
|
||||
|
||||
|
||||
def exitGUIEditMode(self):
|
||||
"""退出GUI编辑模式"""
|
||||
return self.gui_manager.exitGUIEditMode()
|
||||
|
||||
|
||||
def createGUIEditPanel(self):
|
||||
"""创建GUI编辑面板"""
|
||||
return self.gui_manager.createGUIEditPanel()
|
||||
|
||||
|
||||
def openGUIPreviewWindow(self):
|
||||
"""打开独立的GUI预览窗口"""
|
||||
return self.gui_manager.openGUIPreviewWindow()
|
||||
|
||||
|
||||
def closeGUIPreviewWindow(self):
|
||||
"""关闭GUI预览窗口"""
|
||||
return self.gui_manager.closeGUIPreviewWindow()
|
||||
|
||||
|
||||
# GUI工具和选择方法 - 代理到gui_manager
|
||||
def setGUICreateTool(self, tool_type):
|
||||
"""设置GUI创建工具"""
|
||||
return self.gui_manager.setGUICreateTool(tool_type)
|
||||
|
||||
|
||||
def deleteSelectedGUI(self):
|
||||
"""删除选中的GUI元素"""
|
||||
return self.gui_manager.deleteSelectedGUI()
|
||||
|
||||
|
||||
def copySelectedGUI(self):
|
||||
"""复制选中的GUI元素"""
|
||||
return self.gui_manager.copySelectedGUI()
|
||||
|
||||
|
||||
def handleGUIEditClick(self, hitPos):
|
||||
"""处理GUI编辑模式下的点击"""
|
||||
return self.gui_manager.handleGUIEditClick(hitPos)
|
||||
|
||||
|
||||
def createGUIAtPosition(self, world_pos, gui_type):
|
||||
"""在指定位置创建GUI元素"""
|
||||
return self.gui_manager.createGUIAtPosition(world_pos, gui_type)
|
||||
|
||||
|
||||
def findClickedGUI(self, hitNode):
|
||||
"""查找被点击的GUI元素"""
|
||||
return self.gui_manager.findClickedGUI(hitNode)
|
||||
|
||||
|
||||
def selectGUIInTree(self, gui_element):
|
||||
"""在树形控件中选中GUI元素"""
|
||||
return self.gui_manager.selectGUIInTree(gui_element)
|
||||
|
||||
|
||||
def updateGUISelection(self, gui_element):
|
||||
"""更新GUI元素选择状态"""
|
||||
return self.gui_manager.updateGUISelection(gui_element)
|
||||
|
||||
|
||||
# GUI属性面板方法 - 代理到gui_manager
|
||||
def selectGUIColor(self, gui_element):
|
||||
"""选择GUI元素颜色"""
|
||||
return self.gui_manager.selectGUIColor(gui_element)
|
||||
|
||||
|
||||
def editGUI2DPosition(self, gui_element, axis, value):
|
||||
"""编辑2D GUI元素位置"""
|
||||
return self.gui_manager.editGUI2DPosition(gui_element, axis, value)
|
||||
|
||||
# ==================== 事件处理代理 ====================
|
||||
|
||||
|
||||
def onTreeItemClicked(self, item, column):
|
||||
"""处理树形控件项目点击事件 - 代理到interface_manager"""
|
||||
return self.interface_manager.onTreeItemClicked(item, column)
|
||||
@ -321,7 +338,7 @@ class MyWorld(CoreWorld):
|
||||
def mousePressEventMiddle(self, evt):
|
||||
"""处理鼠标中键按下事件 - 代理到event_handler"""
|
||||
return self.event_handler.mousePressEventMiddle(evt)
|
||||
|
||||
|
||||
def mouseReleaseEventMiddle(self, evt):
|
||||
"""处理鼠标中键释放事件 - 代理到event_handler"""
|
||||
return self.event_handler.mouseReleaseEventMiddle(evt)
|
||||
@ -329,35 +346,35 @@ class MyWorld(CoreWorld):
|
||||
def mouseMoveEvent(self, evt):
|
||||
"""处理鼠标移动事件 - 代理到event_handler"""
|
||||
return self.event_handler.mouseMoveEvent(evt)
|
||||
|
||||
|
||||
# ==================== 射线显示控制 ====================
|
||||
|
||||
|
||||
def toggleRayDisplay(self):
|
||||
"""切换射线显示状态 - 代理到event_handler"""
|
||||
return self.event_handler.toggleRayDisplay()
|
||||
|
||||
|
||||
def setRayDisplay(self, show=True):
|
||||
"""设置射线显示状态 - 代理到event_handler"""
|
||||
self.event_handler.showRay = show
|
||||
if not show:
|
||||
self.event_handler.clearRay()
|
||||
return show
|
||||
|
||||
|
||||
def getRayDisplay(self):
|
||||
"""获取射线显示状态 - 代理到event_handler"""
|
||||
return self.event_handler.showRay
|
||||
|
||||
|
||||
def setRayLifetime(self, seconds):
|
||||
"""设置射线显示时长(秒) - 代理到event_handler"""
|
||||
self.event_handler.rayLifetime = seconds
|
||||
print(f"射线显示时长设置为: {seconds}秒")
|
||||
|
||||
|
||||
def getRayLifetime(self):
|
||||
"""获取射线显示时长 - 代理到event_handler"""
|
||||
return self.event_handler.rayLifetime
|
||||
|
||||
# ==================== 属性面板代理 ====================
|
||||
|
||||
|
||||
def setPropertyLayout(self, layout):
|
||||
"""设置属性面板布局引用 - 代理到property_panel"""
|
||||
return self.property_panel.setPropertyLayout(layout)
|
||||
@ -369,7 +386,7 @@ class MyWorld(CoreWorld):
|
||||
def updatePropertyPanel(self, item):
|
||||
"""更新属性面板显示 - 代理到property_panel"""
|
||||
return self.property_panel.updatePropertyPanel(item)
|
||||
|
||||
|
||||
def updateGUIPropertyPanel(self, gui_element):
|
||||
"""更新GUI元素属性面板 - 代理到property_panel"""
|
||||
return self.property_panel.updateGUIPropertyPanel(gui_element)
|
||||
@ -387,7 +404,7 @@ class MyWorld(CoreWorld):
|
||||
return self.tool_manager.setCurrentTool(tool)
|
||||
|
||||
# ==================== 场景管理功能代理 ====================
|
||||
|
||||
|
||||
# 模型导入和处理方法 - 代理到scene_manager
|
||||
def importModel(self, filepath):
|
||||
"""导入模型到场景"""
|
||||
@ -396,8 +413,8 @@ class MyWorld(CoreWorld):
|
||||
try:
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
reply = QMessageBox.question(
|
||||
None,
|
||||
'格式转换选择',
|
||||
None,
|
||||
'格式转换选择',
|
||||
'FBX文件检测到!\n\n是否要尝试转换为GLB格式以获得更好的动画支持?\n\n点击"是":尝试转换为GLB格式(需要安装转换工具)\n点击"否":直接使用原始FBX格式导入',
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes
|
||||
@ -410,17 +427,17 @@ class MyWorld(CoreWorld):
|
||||
else:
|
||||
# 非FBX文件,保持原有逻辑
|
||||
auto_convert = True
|
||||
|
||||
|
||||
return self.scene_manager.importModel(filepath, auto_convert_to_glb=auto_convert)
|
||||
|
||||
def importModelAsync(self, filepath):
|
||||
"""异步导入模型"""
|
||||
return self.scene_manager.importModelAsync(filepath)
|
||||
|
||||
|
||||
def loadAnimatedModel(self, model_path, anims=None):
|
||||
"""加载带动画的模型"""
|
||||
return self.scene_manager.loadAnimatedModel(model_path, anims)
|
||||
|
||||
|
||||
# 材质和几何体处理方法 - 代理到scene_manager
|
||||
def processMaterials(self, model):
|
||||
"""处理模型材质"""
|
||||
@ -429,17 +446,17 @@ class MyWorld(CoreWorld):
|
||||
def processModelGeometry(self, model):
|
||||
"""处理模型几何体"""
|
||||
return self.scene_manager.processModelGeometry(model)
|
||||
|
||||
|
||||
# 碰撞系统方法 - 代理到scene_manager
|
||||
def setupCollision(self, model):
|
||||
"""为模型设置碰撞检测"""
|
||||
return self.scene_manager.setupCollision(model)
|
||||
|
||||
|
||||
# 场景树管理方法 - 代理到scene_manager
|
||||
def updateSceneTree(self):
|
||||
"""更新场景树显示"""
|
||||
return self.scene_manager.updateSceneTree()
|
||||
|
||||
|
||||
# 场景保存和加载方法 - 代理到scene_manager
|
||||
def saveScene(self, filename):
|
||||
"""保存场景到BAM文件"""
|
||||
@ -448,39 +465,39 @@ class MyWorld(CoreWorld):
|
||||
def loadScene(self, filename):
|
||||
"""从BAM文件加载场景"""
|
||||
return self.scene_manager.loadScene(filename)
|
||||
|
||||
|
||||
# 模型管理方法 - 代理到scene_manager
|
||||
def deleteModel(self, model):
|
||||
"""删除模型"""
|
||||
return self.scene_manager.deleteModel(model)
|
||||
|
||||
|
||||
def clearAllModels(self):
|
||||
"""清除所有模型"""
|
||||
return self.scene_manager.clearAllModels()
|
||||
|
||||
|
||||
def getModels(self):
|
||||
"""获取模型列表"""
|
||||
return self.scene_manager.getModels()
|
||||
|
||||
|
||||
def getModelCount(self):
|
||||
"""获取模型数量"""
|
||||
return self.scene_manager.getModelCount()
|
||||
|
||||
|
||||
def findModelByName(self, name):
|
||||
"""根据名称查找模型"""
|
||||
return self.scene_manager.findModelByName(name)
|
||||
|
||||
# ==================== 脚本系统功能代理 ====================
|
||||
|
||||
|
||||
# 脚本系统控制方法 - 代理到script_manager
|
||||
def startScriptSystem(self):
|
||||
"""启动脚本系统"""
|
||||
return self.script_manager.start_system()
|
||||
|
||||
|
||||
def stopScriptSystem(self):
|
||||
"""停止脚本系统"""
|
||||
return self.script_manager.stop_system()
|
||||
|
||||
|
||||
def enableHotReload(self, enabled=True):
|
||||
"""启用/禁用热重载"""
|
||||
self.script_manager.hot_reload_enabled = enabled
|
||||
@ -488,153 +505,153 @@ class MyWorld(CoreWorld):
|
||||
self.script_manager.start_hot_reload()
|
||||
else:
|
||||
self.script_manager.stop_hot_reload()
|
||||
|
||||
|
||||
# 脚本创建和加载方法 - 代理到script_manager
|
||||
def createScript(self, script_name, template="basic"):
|
||||
"""创建新脚本文件"""
|
||||
return self.script_manager.create_script_file(script_name, template)
|
||||
|
||||
|
||||
def loadScript(self, script_path):
|
||||
"""从文件加载脚本"""
|
||||
return self.script_manager.load_script_from_file(script_path)
|
||||
|
||||
|
||||
def loadAllScripts(self, directory=None):
|
||||
"""从目录加载所有脚本"""
|
||||
return self.script_manager.load_all_scripts_from_directory(directory)
|
||||
|
||||
|
||||
def reloadScript(self, script_name):
|
||||
"""重新加载脚本"""
|
||||
return self.script_manager.reload_script(script_name)
|
||||
|
||||
|
||||
# 脚本挂载和管理方法 - 代理到script_manager
|
||||
def addScript(self, game_object, script_name):
|
||||
"""为游戏对象添加脚本"""
|
||||
return self.script_manager.add_script_to_object(game_object, script_name)
|
||||
|
||||
|
||||
def removeScript(self, game_object, script_name):
|
||||
"""从游戏对象移除脚本"""
|
||||
return self.script_manager.remove_script_from_object(game_object, script_name)
|
||||
|
||||
|
||||
def getScripts(self, game_object):
|
||||
"""获取对象上的所有脚本"""
|
||||
return self.script_manager.get_scripts_on_object(game_object)
|
||||
|
||||
|
||||
def getScript(self, game_object, script_name):
|
||||
"""获取对象上的特定脚本"""
|
||||
return self.script_manager.get_script_on_object(game_object, script_name)
|
||||
|
||||
|
||||
# 脚本信息查询方法 - 代理到script_manager
|
||||
def getAvailableScripts(self):
|
||||
"""获取所有可用的脚本名称"""
|
||||
return self.script_manager.get_available_scripts()
|
||||
|
||||
|
||||
def getScriptInfo(self, script_name):
|
||||
"""获取脚本信息"""
|
||||
return self.script_manager.get_script_info(script_name)
|
||||
|
||||
|
||||
def listAllScripts(self):
|
||||
"""列出所有脚本信息"""
|
||||
return self.script_manager.list_all_scripts()
|
||||
# ==================== VR系统功能代理 ====================
|
||||
|
||||
|
||||
# VR系统控制方法 - 代理到vr_manager
|
||||
def initializeVR(self):
|
||||
"""初始化VR系统"""
|
||||
return self.vr_manager.initialize_vr()
|
||||
|
||||
|
||||
def shutdownVR(self):
|
||||
"""关闭VR系统"""
|
||||
return self.vr_manager.shutdown_vr()
|
||||
|
||||
|
||||
def isVREnabled(self):
|
||||
"""检查VR是否启用"""
|
||||
return self.vr_manager.is_vr_enabled()
|
||||
|
||||
|
||||
def getVRInfo(self):
|
||||
"""获取VR系统信息"""
|
||||
return self.vr_manager.get_vr_info()
|
||||
|
||||
|
||||
# VR输入处理方法 - 代理到vr_input_handler
|
||||
def startVRInput(self):
|
||||
"""启动VR输入处理"""
|
||||
return self.vr_input_handler.start_input_handling()
|
||||
|
||||
|
||||
def stopVRInput(self):
|
||||
"""停止VR输入处理"""
|
||||
return self.vr_input_handler.stop_input_handling()
|
||||
|
||||
|
||||
def showControllerRays(self, show=True):
|
||||
"""显示/隐藏控制器射线"""
|
||||
return self.vr_input_handler.show_controller_rays(show)
|
||||
|
||||
|
||||
def getControllerState(self, controller_id):
|
||||
"""获取控制器状态"""
|
||||
return self.vr_input_handler.get_controller_state(controller_id)
|
||||
|
||||
|
||||
def getAllControllers(self):
|
||||
"""获取所有控制器"""
|
||||
return self.vr_input_handler.get_all_controllers()
|
||||
|
||||
|
||||
def setVRGestureEnabled(self, enabled):
|
||||
"""设置VR手势识别启用状态"""
|
||||
return self.vr_input_handler.set_gesture_enabled(enabled)
|
||||
|
||||
|
||||
def setVRInteractionEnabled(self, enabled):
|
||||
"""设置VR交互启用状态"""
|
||||
return self.vr_input_handler.set_interaction_enabled(enabled)
|
||||
|
||||
|
||||
# ALVR串流方法 - 代理到alvr_streamer
|
||||
def initializeALVR(self):
|
||||
"""初始化ALVR串流"""
|
||||
return self.alvr_streamer.initialize()
|
||||
|
||||
|
||||
def startALVRStreaming(self):
|
||||
"""开始ALVR串流"""
|
||||
return self.alvr_streamer.start_streaming()
|
||||
|
||||
|
||||
def stopALVRStreaming(self):
|
||||
"""停止ALVR串流"""
|
||||
return self.alvr_streamer.stop_streaming()
|
||||
|
||||
|
||||
def getALVRStreamingStatus(self):
|
||||
"""获取ALVR串流状态"""
|
||||
return self.alvr_streamer.get_streaming_status()
|
||||
|
||||
|
||||
def setALVRStreamQuality(self, width, height, fps, bitrate):
|
||||
"""设置ALVR串流质量"""
|
||||
return self.alvr_streamer.set_stream_quality(width, height, fps, bitrate)
|
||||
|
||||
|
||||
def sendHapticFeedback(self, controller_id, duration, intensity):
|
||||
"""发送触觉反馈"""
|
||||
return self.alvr_streamer.send_haptic_feedback(controller_id, duration, intensity)
|
||||
|
||||
|
||||
def shutdownALVR(self):
|
||||
"""关闭ALVR串流"""
|
||||
return self.alvr_streamer.shutdown()
|
||||
|
||||
|
||||
def isALVRConnected(self):
|
||||
"""检查ALVR是否连接"""
|
||||
return self.alvr_streamer.is_connected()
|
||||
|
||||
|
||||
def isALVRStreaming(self):
|
||||
"""检查ALVR是否在串流"""
|
||||
return self.alvr_streamer.is_streaming()
|
||||
|
||||
|
||||
# 便捷方法
|
||||
def enableVRMode(self):
|
||||
"""启用VR模式(一键启动)"""
|
||||
print("启用VR模式...")
|
||||
|
||||
|
||||
# 1. 初始化VR系统
|
||||
if not self.initializeVR():
|
||||
print("VR系统初始化失败")
|
||||
return False
|
||||
|
||||
|
||||
# 2. 启动VR输入处理
|
||||
if not self.startVRInput():
|
||||
print("VR输入处理启动失败")
|
||||
return False
|
||||
|
||||
|
||||
# 3. 初始化ALVR串流
|
||||
if self.initializeALVR():
|
||||
print("✓ ALVR串流已启用")
|
||||
@ -642,26 +659,26 @@ class MyWorld(CoreWorld):
|
||||
self.startALVRStreaming()
|
||||
else:
|
||||
print("⚠ ALVR串流启用失败,但VR系统仍可用")
|
||||
|
||||
|
||||
print("✓ VR模式已启用")
|
||||
return True
|
||||
|
||||
|
||||
def disableVRMode(self):
|
||||
"""禁用VR模式(一键关闭)"""
|
||||
print("禁用VR模式...")
|
||||
|
||||
|
||||
# 1. 停止ALVR串流
|
||||
self.stopALVRStreaming()
|
||||
self.shutdownALVR()
|
||||
|
||||
|
||||
# 2. 停止VR输入处理
|
||||
self.stopVRInput()
|
||||
|
||||
|
||||
# 3. 关闭VR系统
|
||||
self.shutdownVR()
|
||||
|
||||
|
||||
print("✓ VR模式已禁用")
|
||||
|
||||
|
||||
def getVRStatus(self):
|
||||
"""获取VR系统总体状态"""
|
||||
return {
|
||||
@ -673,6 +690,23 @@ class MyWorld(CoreWorld):
|
||||
"streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None
|
||||
}
|
||||
|
||||
# 添加碰撞管理相关的代理方法
|
||||
def enableModelCollisionDetection(self, enable=True, frequency=0.1, threshold=0.5):
|
||||
"""启用模型间碰撞检测"""
|
||||
return self.collision_manager.enableModelCollisionDetection(enable, frequency, threshold)
|
||||
|
||||
def detectModelCollisions(self, specific_models=None, log_results=True):
|
||||
"""检测模型间碰撞"""
|
||||
return self.collision_manager.detectModelCollisions(specific_models, log_results)
|
||||
|
||||
def getCollisionHistory(self, limit=None):
|
||||
"""获取碰撞历史"""
|
||||
return self.collision_manager.getCollisionHistory(limit)
|
||||
|
||||
def getCollisionStatistics(self):
|
||||
"""获取碰撞统计"""
|
||||
return self.collision_manager.getCollisionStatistics()
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
# 以下函数代理到project_manager模块的对应功能
|
||||
|
||||
@ -18,8 +18,8 @@ class InterfaceManager:
|
||||
self.treeWidget = treeWidget
|
||||
|
||||
# 添加右键菜单
|
||||
self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.treeWidget.customContextMenuRequested.connect(self.showTreeContextMenu)
|
||||
# self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
# self.treeWidget.customContextMenuRequested.connect(self.showTreeContextMenu)
|
||||
|
||||
# 更新场景树
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
@ -155,10 +155,12 @@ class InterfaceManager:
|
||||
|
||||
# 创建场景根节点
|
||||
sceneRoot = QTreeWidgetItem(self.treeWidget, ['场景'])
|
||||
|
||||
sceneRoot.setData(0, Qt.UserRole, self.world.render)
|
||||
sceneRoot.setData(0, Qt.UserRole + 1, "SCENE_ROOT")
|
||||
# 添加相机节点
|
||||
cameraItem = QTreeWidgetItem(sceneRoot, ['相机'])
|
||||
cameraItem.setData(0, Qt.UserRole, self.world.cam)
|
||||
cameraItem.setData(0, Qt.UserRole + 1, "CAMERA_NODE")
|
||||
print("添加相机节点")
|
||||
|
||||
# # 添加模型节点组
|
||||
@ -223,6 +225,7 @@ class InterfaceManager:
|
||||
if hasattr(self.world, 'ground') and self.world.ground:
|
||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||
groundItem.setData(0, Qt.UserRole, self.world.ground)
|
||||
groundItem.setData(0,Qt.UserRole + 1, "SCENE_NODE")
|
||||
|
||||
# 展开所有节点
|
||||
#self.treeWidget.expandAll()
|
||||
|
||||
@ -504,6 +504,7 @@ class PropertyPanelManager:
|
||||
success = self.world.gui_manager.editGUIElement(gui_element, "text", text)
|
||||
if success:
|
||||
# 更新场景树显示的名称
|
||||
pass #CH
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
textEdit.textChanged.connect(updateText)
|
||||
|
||||