1
0
forked from Rowland/EG

addRender #25

Merged
Hector merged 8 commits from addRender into main 2025-08-28 09:13:32 +00:00
18 changed files with 4777 additions and 768 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
__pycache__/
*.pyc
.idea/
*.pyc

View File

@ -53,7 +53,7 @@ class Panda3DWorld(ShowBase):
Panda3DWorld : A class to handle all panda3D world manipulation
"""
def __init__(self, width=1254, height=729, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
def __init__(self, width=1380, height=729, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
name="qpanda3D"):
global _global_world_instance

View File

@ -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())

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

945
core/collision_manager.py Normal file
View 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

View File

@ -49,72 +49,228 @@ 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
@ -321,136 +477,345 @@ class GUIManager:
print(f"✓ 创建3D文本: {text} (世界位置: {pos})")
return textNodePath
def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0):
from panda3d.core import CardMaker, Material, LColor,TransparencyAttrib
# 参数类型检查和转换
if isinstance(size, (list, tuple)):
if len(size) >= 2:
x_size, y_size = float(size[0]), float(size[1])
else:
x_size = y_size = float(size[0]) if size else 1.0
else:
x_size = y_size = float(size)
# 创建卡片
cm = CardMaker('gui_3d_image')
cm.setFrame(-x_size/2, x_size/2, -y_size/2, y_size/2)
# 创建3D图像节点
image_node = self.world.render.attachNewNode(cm.generate())
image_node.setPos(*pos)
# 设置面向摄像机
#image_node.setBillboardAxis() # 让图像总是面向相机
# 创建支持贴图的材质
# mat = Material()
# mat.setName("GUI3DImageMaterial")
# color = LColor(1, 1, 1, 1)
# mat.set_base_color(color)
# mat.set_roughness(0.5)
# mat.set_metallic(0.0)
# image_node.set_material(mat)
# 为3D图像创建独立的材质
material = Material(f"image-material-{len(self.gui_elements)}")
material.setBaseColor(LColor(1, 1, 1, 1))
material.setDiffuse(LColor(1, 1, 1, 1))
material.setAmbient(LColor(0.5, 0.5, 0.5, 1))
material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0))
material.setShininess(10.0)
material.setEmission(LColor(0, 0, 0, 1)) # 无自发光
image_node.setMaterial(material, 1)
image_node.setTransparency(TransparencyAttrib.MAlpha)
# 如果提供了图像路径,则加载纹理
if image_path:
self.update3DImageTexture(image_node, image_path)
# 应用PBR效果如果可用
"""创建2D GUI文本输入框 - 支持多选创建和GUI父子关系优化版本"""
try:
if hasattr(self, 'render_pipeline') and self.render_pipeline:
self.render_pipeline.set_effect(
image_node,
"effects/default.yaml",
{
"normal_mapping": True,
"render_gbuffer": True,
"alpha_testing": False,
"parallax_mapping": False,
"render_shadow": False,
"render_envmap": True,
"disable_children_effects": True
},
50
)
print("✓ GUI 3D图像PBR效果已应用")
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 3D图像PBR效果应用失败: {e}")
print(f"❌ 创建GUI输入框过程失败: {str(e)}")
import traceback
traceback.print_exc()
return None
# 为GUI元素添加标识效仿3D文本方法
image_node.setTag("gui_type", "3d_image")
image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}")
if image_path:
image_node.setTag("gui_image_path", image_path)
image_node.setTag("is_gui_element", "1")
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
"""创建3D空间文本 - 支持多选创建,优化版本"""
try:
from panda3d.core import TextNode
from PyQt5.QtCore import Qt
self.gui_elements.append(image_node)
print(f"📄 开始创建3D文本位置: {pos}, 文本: {text}, 尺寸: {size}")
# 更新场景树
if hasattr(self.world, 'updateSceneTree'):
self.world.updateSceneTree()
# 获取树形控件
tree_widget = self._get_tree_widget()
if not tree_widget:
print("❌ 无法访问树形控件")
return None
print(f"✓ 3D图像创建完成: {image_path or '无纹理'} (世界位置: {pos})")
return image_node
# 获取目标父节点列表
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,
@ -459,20 +824,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):
@ -926,7 +1292,7 @@ class GUIManager:
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
)
y_pos -= spacing
# 分隔线
y_pos -= 0.1

BIN
icons/move_tool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
icons/rotate_tool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
icons/scale_tool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/select_tool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

261
main.py
View File

@ -1,4 +1,7 @@
import warnings
from demo.video_integration import VideoManager
warnings.filterwarnings("ignore", category=DeprecationWarning)
import sys
@ -63,7 +66,10 @@ class MyWorld(CoreWorld):
# 初始化GUI管理系统
self.gui_manager = GUIManager(self)
# 初始化视频管理
self.video_manager = VideoManager(self)
# 初始化场景管理系统
self.scene_manager = SceneManager(self)
@ -91,27 +97,47 @@ class MyWorld(CoreWorld):
self.terrain_edit_strength=0.3
self.terrain_edit_operation = "add"
# 初始化碰撞管理器
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)
try:
self.property_panel = PropertyPanelManager(self)
print("✓ 属性面板管理器初始化完成")
except Exception as e:
print(f"⚠ 属性面板管理器初始化失败: {e}")
import traceback
traceback.print_exc()
self.property_panel = None
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元素列表的兼容性设置器"""
@ -136,35 +162,35 @@ 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):
"""树形控件的兼容性设置器"""
@ -182,20 +208,20 @@ class MyWorld(CoreWorld):
self.terrain_manager.terrains = 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)
@ -215,110 +241,110 @@ class MyWorld(CoreWorld):
def createPointLight(self,pos=(0,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)
@ -346,7 +372,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)
@ -354,35 +380,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)
@ -394,7 +420,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)
@ -412,7 +438,7 @@ class MyWorld(CoreWorld):
return self.tool_manager.setCurrentTool(tool)
# ==================== 场景管理功能代理 ====================
# 模型导入和处理方法 - 代理到scene_manager
def importModel(self, filepath):
"""导入模型到场景"""
@ -421,8 +447,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
@ -435,17 +461,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):
"""处理模型材质"""
@ -454,17 +480,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文件"""
@ -473,39 +499,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
@ -513,153 +539,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串流已启用")
@ -667,26 +693,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 {
@ -747,6 +773,23 @@ class MyWorld(CoreWorld):
return self.terrain_manager.modifyTerrainHeight(terrain_info, x, y, radius, strength, operation)
return False
# 添加碰撞管理相关的代理方法
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模块的对应功能

File diff suppressed because it is too large Load Diff

View File

@ -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()
@ -209,10 +209,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("添加相机节点")
BLACK_LIST = {'','**','temp','collision'}
@ -254,6 +256,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")
#添加灯光节点
for light in self.world.Spotlight + self.world.Pointlight:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff