858 lines
34 KiB
Python
858 lines
34 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
场景管理器 - 负责场景和模型管理的核心功能
|
||
处理模型导入、场景树构建、材质系统、碰撞设置等
|
||
"""
|
||
|
||
import os
|
||
from panda3d.core import (
|
||
ModelPool, ModelRoot, Filename, NodePath, GeomNode, Material, Vec4, Vec3,
|
||
MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere,
|
||
BitMask32, TransparencyAttrib,LColor
|
||
)
|
||
from panda3d.egg import EggData, EggVertexPool
|
||
from direct.actor.Actor import Actor
|
||
from QPanda3D.Panda3DWorld import get_render_pipeline
|
||
|
||
class SceneManager:
|
||
"""场景管理器 - 统一管理场景中的所有元素"""
|
||
|
||
def __init__(self, world):
|
||
"""初始化场景管理器
|
||
|
||
Args:
|
||
world: 主程序world对象引用
|
||
"""
|
||
self.world = world
|
||
self.models = [] # 模型列表
|
||
|
||
self.Spotlight = []
|
||
self.Pointlight = []
|
||
|
||
print("✓ 场景管理系统初始化完成")
|
||
|
||
# ==================== 模型导入和处理 ====================
|
||
|
||
def importModel(self, filepath, apply_unit_conversion=False, normalize_scales=True):
|
||
"""导入模型到场景
|
||
|
||
Args:
|
||
filepath: 模型文件路径
|
||
apply_unit_conversion: 是否应用单位转换(主要针对FBX文件)
|
||
normalize_scales: 是否标准化子节点缩放(推荐开启)
|
||
"""
|
||
try:
|
||
print(f"\n=== 开始导入模型: {filepath} ===")
|
||
print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}")
|
||
|
||
# 总是重新加载模型以确保材质信息完整
|
||
# 不使用ModelPool缓存,避免材质信息丢失问题
|
||
print("直接从文件加载模型...")
|
||
model = self.world.loader.loadModel(filepath)
|
||
if not model:
|
||
print("加载模型失败")
|
||
return None
|
||
|
||
# 设置模型名称
|
||
model_name = os.path.basename(filepath)
|
||
model.setName(model_name)
|
||
|
||
# 将模型添加到场景
|
||
model.reparentTo(self.world.render)
|
||
|
||
# 可选的单位转换(主要针对FBX)
|
||
if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||
print("应用FBX单位转换(厘米到米)...")
|
||
self._applyUnitConversion(model, 0.01)
|
||
|
||
# 智能缩放标准化(处理FBX子节点的大缩放值)
|
||
if normalize_scales and filepath.lower().endswith('.fbx'):
|
||
print("标准化FBX模型缩放层级...")
|
||
self._normalizeModelScales(model)
|
||
|
||
# 调整模型位置到地面
|
||
self._adjustModelToGround(model)
|
||
|
||
# 创建并设置基础材质
|
||
print("\n=== 开始设置材质 ===")
|
||
self._applyMaterialsToModel(model)
|
||
|
||
# 设置碰撞检测(重要!用于选择功能)
|
||
print("\n=== 设置碰撞检测 ===")
|
||
self.setupCollision(model)
|
||
|
||
# 添加文件标签用于保存/加载
|
||
model.setTag("file", model_name)
|
||
model.setTag("is_model_root", "1")
|
||
|
||
# 记录应用的处理选项
|
||
if apply_unit_conversion:
|
||
model.setTag("unit_conversion_applied", "true")
|
||
if normalize_scales:
|
||
model.setTag("scale_normalization_applied", "true")
|
||
|
||
# 添加到模型列表
|
||
self.models.append(model)
|
||
|
||
# 更新场景树
|
||
self.updateSceneTree()
|
||
|
||
print(f"=== 模型导入成功: {model_name} ===\n")
|
||
return model
|
||
|
||
except Exception as e:
|
||
print(f"导入模型失败: {str(e)}")
|
||
return None
|
||
|
||
def _applyMaterialsToModel(self, model):
|
||
"""递归应用材质到模型的所有GeomNode"""
|
||
def apply_material(node_path, depth=0):
|
||
indent = " " * depth
|
||
print(f"{indent}处理节点: {node_path.getName()}")
|
||
print(f"{indent}节点类型: {node_path.node().__class__.__name__}")
|
||
|
||
if isinstance(node_path.node(), GeomNode):
|
||
print(f"{indent}发现GeomNode,处理材质")
|
||
geom_node = node_path.node()
|
||
|
||
# 检查所有几何体的状态
|
||
has_color = False
|
||
color = None
|
||
|
||
# 首先检查节点自身的状态
|
||
node_state = node_path.getState()
|
||
if node_state.hasAttrib(MaterialAttrib.getClassType()):
|
||
mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
|
||
node_material = mat_attrib.getMaterial()
|
||
if node_material and node_material.hasDiffuse():
|
||
color = node_material.getDiffuse()
|
||
has_color = True
|
||
print(f"{indent}从节点材质获取颜色: {color}")
|
||
|
||
# 检查FBX特有的属性
|
||
for tag_key in node_path.getTagKeys():
|
||
print(f"{indent}发现标签: {tag_key}")
|
||
if "color" in tag_key.lower() or "diffuse" in tag_key.lower():
|
||
tag_value = node_path.getTag(tag_key)
|
||
print(f"{indent}颜色相关标签: {tag_key} = {tag_value}")
|
||
|
||
# 如果还没找到颜色,检查几何体
|
||
if not has_color:
|
||
for i in range(geom_node.getNumGeoms()):
|
||
geom = geom_node.getGeom(i)
|
||
state = geom_node.getGeomState(i)
|
||
|
||
# 检查顶点颜色
|
||
vdata = geom.getVertexData()
|
||
format = vdata.getFormat()
|
||
for j in range(format.getNumColumns()):
|
||
column = format.getColumn(j)
|
||
# InternalName对象需要使用getName()转换为字符串
|
||
column_name = column.getName().getName()
|
||
if "color" in column_name.lower():
|
||
print(f"{indent}发现顶点颜色数据: {column_name}")
|
||
# 这里可以读取顶点颜色,但先记录发现
|
||
|
||
# 检查材质属性
|
||
if state.hasAttrib(MaterialAttrib.getClassType()):
|
||
mat_attrib = state.getAttrib(MaterialAttrib.getClassType())
|
||
orig_material = mat_attrib.getMaterial()
|
||
if orig_material:
|
||
if orig_material.hasBaseColor():
|
||
color = orig_material.getBaseColor()
|
||
has_color = True
|
||
print(f"{indent}从基础颜色获取: {color}")
|
||
break
|
||
elif orig_material.hasDiffuse():
|
||
color = orig_material.getDiffuse()
|
||
has_color = True
|
||
print(f"{indent}从漫反射颜色获取: {color}")
|
||
break
|
||
|
||
# 检查颜色属性
|
||
if not has_color and state.hasAttrib(ColorAttrib.getClassType()):
|
||
color_attrib = state.getAttrib(ColorAttrib.getClassType())
|
||
if not color_attrib.isOff():
|
||
color = color_attrib.getColor()
|
||
has_color = True
|
||
print(f"{indent}从颜色属性获取: {color}")
|
||
break
|
||
|
||
# 创建新材质
|
||
material = Material()
|
||
if has_color:
|
||
print(f"{indent}应用找到的颜色: {color}")
|
||
material.setDiffuse(color)
|
||
material.setBaseColor(color) # 同时设置基础颜色
|
||
node_path.setColor(color)
|
||
else:
|
||
print(f"{indent}使用默认颜色")
|
||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||
|
||
# 设置其他材质属性
|
||
material.setAmbient((0.2, 0.2, 0.2, 1.0))
|
||
material.setSpecular((0.5, 0.5, 0.5, 1.0))
|
||
material.setShininess(32.0)
|
||
#material.set_metallic(1)
|
||
#material.set_roughness(0)
|
||
|
||
# 应用材质
|
||
node_path.setMaterial(material)
|
||
print(f"{indent}几何体数量: {geom_node.getNumGeoms()}")
|
||
|
||
# 递归处理子节点
|
||
child_count = node_path.getNumChildren()
|
||
print(f"{indent}子节点数量: {child_count}")
|
||
for i in range(child_count):
|
||
child = node_path.getChild(i)
|
||
apply_material(child, depth + 1)
|
||
|
||
# 应用材质
|
||
print("\n开始递归应用材质...")
|
||
apply_material(model)
|
||
print("=== 材质设置完成 ===\n")
|
||
|
||
def _adjustModelToGround(self, model):
|
||
"""智能调整模型到地面,但保持原有缩放结构"""
|
||
try:
|
||
print("调整模型位置到地面...")
|
||
|
||
# 获取模型的边界框
|
||
bounds = model.getBounds()
|
||
if not bounds or bounds.isEmpty():
|
||
print("无法获取模型边界,使用默认位置")
|
||
model.setPos(0, 0, 0)
|
||
return
|
||
|
||
# 获取边界框的最低点
|
||
min_point = bounds.getMin()
|
||
center = bounds.getCenter()
|
||
|
||
# 计算需要移动的距离,使模型底部贴合地面(Z=0)
|
||
# 这里不涉及缩放,只是简单的位置调整
|
||
ground_offset = -min_point.getZ()
|
||
|
||
# 设置模型位置:X,Y居中,Z调整到地面
|
||
model.setPos(0, 0, ground_offset)
|
||
|
||
print(f"模型边界: 最小点{min_point}, 中心{center}")
|
||
print(f"地面偏移: {ground_offset}")
|
||
print(f"最终位置: {model.getPos()}")
|
||
|
||
except Exception as e:
|
||
print(f"调整模型位置失败: {str(e)}")
|
||
# 失败时使用默认位置
|
||
model.setPos(0, 0, 0)
|
||
|
||
def _applyUnitConversion(self, model, scale_factor):
|
||
"""应用单位转换缩放
|
||
|
||
Args:
|
||
model: 要转换的模型
|
||
scale_factor: 缩放因子(如0.01表示从厘米转换到米)
|
||
"""
|
||
try:
|
||
print(f"应用单位转换缩放: {scale_factor}")
|
||
|
||
# 检查模型是否已经应用过单位转换
|
||
if model.hasTag("unit_conversion_applied"):
|
||
print("模型已应用过单位转换,跳过")
|
||
return
|
||
|
||
# 获取当前边界用于后续位置调整
|
||
original_bounds = model.getBounds()
|
||
|
||
# 应用缩放
|
||
model.setScale(scale_factor)
|
||
|
||
# 重新调整位置(因为缩放会影响边界)
|
||
if original_bounds and not original_bounds.isEmpty():
|
||
new_bounds = model.getBounds()
|
||
min_point = new_bounds.getMin()
|
||
ground_offset = -min_point.getZ()
|
||
model.setZ(ground_offset)
|
||
print(f"缩放后重新调整位置: Z偏移 = {ground_offset}")
|
||
|
||
print(f"单位转换完成,缩放因子: {scale_factor}")
|
||
|
||
except Exception as e:
|
||
print(f"应用单位转换失败: {str(e)}")
|
||
|
||
def _normalizeModelScales(self, model):
|
||
"""智能标准化模型缩放层级
|
||
|
||
检测并修复FBX模型中子节点的大缩放值问题
|
||
"""
|
||
try:
|
||
print("开始分析模型缩放结构...")
|
||
|
||
# 收集所有节点的缩放信息
|
||
scale_info = []
|
||
self._collectScaleInfo(model, scale_info)
|
||
|
||
if not scale_info:
|
||
print("没有找到需要处理的缩放信息")
|
||
return
|
||
|
||
# 分析缩放模式
|
||
large_scales = [info for info in scale_info if max(abs(info['scale'].x), abs(info['scale'].y), abs(info['scale'].z)) > 10]
|
||
|
||
if not large_scales:
|
||
print("没有发现大缩放值,无需标准化")
|
||
return
|
||
|
||
print(f"发现 {len(large_scales)} 个节点有大缩放值")
|
||
|
||
# 计算标准化因子(基于最常见的大缩放值)
|
||
common_large_scale = self._findCommonLargeScale(large_scales)
|
||
if common_large_scale:
|
||
normalize_factor = 1.0 / common_large_scale
|
||
print(f"检测到常见大缩放值: {common_large_scale}, 标准化因子: {normalize_factor}")
|
||
|
||
# 应用标准化
|
||
self._applyScaleNormalization(model, normalize_factor)
|
||
print("✓ 缩放标准化完成")
|
||
else:
|
||
print("无法确定合适的标准化因子,跳过标准化")
|
||
|
||
except Exception as e:
|
||
print(f"缩放标准化失败: {str(e)}")
|
||
|
||
def _collectScaleInfo(self, node, scale_info, depth=0):
|
||
"""递归收集节点缩放信息"""
|
||
try:
|
||
scale = node.getScale()
|
||
scale_info.append({
|
||
'node': node,
|
||
'name': node.getName(),
|
||
'scale': scale,
|
||
'depth': depth
|
||
})
|
||
|
||
# 递归处理子节点
|
||
for i in range(node.getNumChildren()):
|
||
child = node.getChild(i)
|
||
self._collectScaleInfo(child, scale_info, depth + 1)
|
||
|
||
except Exception as e:
|
||
print(f"收集缩放信息失败 ({node.getName()}): {str(e)}")
|
||
|
||
def _findCommonLargeScale(self, large_scales):
|
||
"""找到最常见的大缩放值"""
|
||
try:
|
||
# 提取缩放值(取绝对值的最大分量)
|
||
scale_values = []
|
||
for info in large_scales:
|
||
scale = info['scale']
|
||
max_scale = max(abs(scale.x), abs(scale.y), abs(scale.z))
|
||
scale_values.append(round(max_scale)) # 四舍五入到整数
|
||
|
||
if not scale_values:
|
||
return None
|
||
|
||
# 找到最常见的值
|
||
from collections import Counter
|
||
counter = Counter(scale_values)
|
||
most_common = counter.most_common(1)[0]
|
||
|
||
print(f"缩放值统计: {dict(counter)}")
|
||
print(f"最常见的大缩放值: {most_common[0]} (出现{most_common[1]}次)")
|
||
|
||
# 只有当最常见的值确实很大时才返回
|
||
if most_common[0] >= 10:
|
||
return float(most_common[0])
|
||
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"分析常见缩放值失败: {str(e)}")
|
||
return None
|
||
|
||
def _applyScaleNormalization(self, node, normalize_factor, depth=0):
|
||
"""递归应用缩放标准化,同时调整位置以保持视觉一致性"""
|
||
try:
|
||
indent = " " * depth
|
||
current_scale = node.getScale()
|
||
current_pos = node.getPos()
|
||
|
||
# 检查是否需要标准化(只处理明显的大缩放)
|
||
max_scale_component = max(abs(current_scale.x), abs(current_scale.y), abs(current_scale.z))
|
||
|
||
if max_scale_component > 10: # 只标准化明显的大缩放
|
||
# 应用新的缩放
|
||
new_scale = current_scale * normalize_factor
|
||
node.setScale(new_scale)
|
||
|
||
# 同时调整位置:当缩放变小时,位置也应该相应变小以保持视觉相对位置
|
||
# 这确保了子节点之间的相对距离在视觉上保持一致
|
||
new_pos = current_pos * normalize_factor
|
||
node.setPos(new_pos)
|
||
|
||
print(f"{indent}标准化 {node.getName()}:")
|
||
print(f"{indent} 缩放: {current_scale} -> {new_scale}")
|
||
print(f"{indent} 位置: {current_pos} -> {new_pos}")
|
||
|
||
# 递归处理子节点
|
||
for i in range(node.getNumChildren()):
|
||
child = node.getChild(i)
|
||
self._applyScaleNormalization(child, normalize_factor, depth + 1)
|
||
|
||
except Exception as e:
|
||
print(f"应用缩放标准化失败 ({node.getName()}): {str(e)}")
|
||
|
||
def importModelAsync(self, filepath):
|
||
"""异步导入模型"""
|
||
try:
|
||
# 创建异步加载请求
|
||
request = self.world.loader.makeAsyncRequest(filepath)
|
||
|
||
# 添加完成回调
|
||
def modelLoaded(task):
|
||
if task.isReady():
|
||
model = task.result()
|
||
if model:
|
||
# 处理加载完成的模型
|
||
self.processLoadedModel(model)
|
||
return task.done()
|
||
|
||
request.done_event = modelLoaded
|
||
|
||
# 开始异步加载
|
||
self.world.loader.loadAsync(request)
|
||
|
||
except Exception as e:
|
||
print(f"异步加载模型失败: {str(e)}")
|
||
|
||
def loadAnimatedModel(self, model_path, anims=None):
|
||
"""加载带动画的模型"""
|
||
try:
|
||
# 创建Actor对象
|
||
actor = Actor(model_path, anims)
|
||
if actor:
|
||
actor.reparentTo(self.world.render)
|
||
|
||
# 设置碰撞检测
|
||
self.setupCollision(actor)
|
||
|
||
self.models.append(actor)
|
||
# 更新场景树
|
||
self.updateSceneTree()
|
||
return actor
|
||
except Exception as e:
|
||
print(f"加载动画模型失败: {str(e)}")
|
||
return None
|
||
|
||
# ==================== 材质和几何体处理 ====================
|
||
|
||
def processMaterials(self, model):
|
||
"""处理模型材质"""
|
||
if isinstance(model.node(), GeomNode):
|
||
# 创建基础材质
|
||
material = Material()
|
||
material.setAmbient((0.2, 0.2, 0.2, 1.0))
|
||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||
material.setSpecular((0.5, 0.5, 0.5, 1.0))
|
||
material.setShininess(32.0)
|
||
|
||
# 检查FBX材质
|
||
state = model.node().getGeomState(0)
|
||
if state.hasAttrib(MaterialAttrib.getClassType()):
|
||
fbx_material = state.getAttrib(MaterialAttrib.getClassType()).getMaterial()
|
||
if fbx_material:
|
||
# 复制FBX材质属性
|
||
material.setAmbient(fbx_material.getAmbient())
|
||
material.setDiffuse(fbx_material.getDiffuse())
|
||
material.setSpecular(fbx_material.getSpecular())
|
||
material.setShininess(fbx_material.getShininess())
|
||
|
||
# 应用材质
|
||
model.setMaterial(material)
|
||
|
||
def processModelGeometry(self, model):
|
||
"""处理模型几何体"""
|
||
# 创建EggData对象
|
||
egg_data = EggData()
|
||
|
||
# 处理顶点数据
|
||
vertex_pool = EggVertexPool("vpool")
|
||
egg_data.addChild(vertex_pool)
|
||
|
||
# 处理几何体
|
||
if isinstance(model.node(), GeomNode):
|
||
for i in range(model.node().getNumGeoms()):
|
||
geom = model.node().getGeom(i)
|
||
# 处理几何体数据
|
||
# ...
|
||
|
||
# ==================== 碰撞系统 ====================
|
||
|
||
def setupCollision(self, model):
|
||
"""为模型设置碰撞检测"""
|
||
# 创建碰撞节点
|
||
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
||
# 设置碰撞掩码
|
||
cNode.setIntoCollideMask(BitMask32.bit(2)) # 使用第2位作为模型的碰撞掩码
|
||
|
||
# 获取模型的边界
|
||
bounds = model.getBounds()
|
||
center = bounds.getCenter()
|
||
radius = bounds.getRadius()
|
||
|
||
# 添加碰撞球体
|
||
cSphere = CollisionSphere(center, radius)
|
||
cNode.addSolid(cSphere)
|
||
|
||
# 将碰撞节点附加到模型上
|
||
cNodePath = model.attachNewNode(cNode)
|
||
# cNodePath.show() # 取消注释可以显示碰撞体,用于调试
|
||
|
||
# ==================== 场景树管理 ====================
|
||
|
||
def updateSceneTree(self):
|
||
"""更新场景树显示 - 代理到interface_manager"""
|
||
if hasattr(self.world, 'interface_manager'):
|
||
return self.world.interface_manager.updateSceneTree()
|
||
else:
|
||
print("界面管理器未初始化,无法更新场景树")
|
||
|
||
# ==================== 场景保存和加载 ====================
|
||
|
||
def saveScene(self, filename):
|
||
"""保存场景到BAM文件"""
|
||
try:
|
||
print(f"\n=== 开始保存场景到: {filename} ===")
|
||
|
||
# 遍历所有模型,保存材质状态和变换信息
|
||
for model in self.models:
|
||
# 保存变换信息(关键!)
|
||
model.setTag("transform_pos", str(model.getPos()))
|
||
model.setTag("transform_hpr", str(model.getHpr()))
|
||
model.setTag("transform_scale", str(model.getScale()))
|
||
print(f"保存模型 {model.getName()} 的变换信息:")
|
||
print(f" 位置: {model.getPos()}")
|
||
print(f" 旋转: {model.getHpr()}")
|
||
print(f" 缩放: {model.getScale()}")
|
||
|
||
# 获取当前状态
|
||
state = model.getState()
|
||
|
||
# 如果有材质属性,保存为标签
|
||
if state.hasAttrib(MaterialAttrib.getClassType()):
|
||
mat_attrib = state.getAttrib(MaterialAttrib.getClassType())
|
||
material = mat_attrib.getMaterial()
|
||
if material:
|
||
# 保存材质属性到标签
|
||
model.setTag("material_ambient", str(material.getAmbient()))
|
||
model.setTag("material_diffuse", str(material.getDiffuse()))
|
||
model.setTag("material_specular", str(material.getSpecular()))
|
||
model.setTag("material_emission", str(material.getEmission()))
|
||
model.setTag("material_shininess", str(material.getShininess()))
|
||
if material.hasBaseColor():
|
||
model.setTag("material_basecolor", str(material.getBaseColor()))
|
||
|
||
# 如果有颜色属性,保存为标签
|
||
if state.hasAttrib(ColorAttrib.getClassType()):
|
||
color_attrib = state.getAttrib(ColorAttrib.getClassType())
|
||
if not color_attrib.isOff():
|
||
model.setTag("color", str(color_attrib.getColor()))
|
||
|
||
# 保存场景
|
||
success = self.world.render.writeBamFile(filename)
|
||
return success
|
||
|
||
except Exception as e:
|
||
print(f"保存场景时发生错误: {str(e)}")
|
||
return False
|
||
|
||
def loadScene(self, filename):
|
||
"""从BAM文件加载场景"""
|
||
try:
|
||
print(f"\n=== 开始加载场景: {filename} ===")
|
||
|
||
# 清除当前场景
|
||
print("\n清除当前场景...")
|
||
for model in self.models:
|
||
model.removeNode()
|
||
self.models.clear()
|
||
|
||
# 加载场景
|
||
scene = self.world.loader.loadModel(filename)
|
||
if not scene:
|
||
return False
|
||
|
||
# 遍历场景中的所有模型节点
|
||
def processNode(nodePath, depth=0):
|
||
indent = " " * depth
|
||
print(f"{indent}处理节点: {nodePath.getName()}")
|
||
|
||
# 跳过render节点的递归
|
||
if nodePath.getName() == "render" and depth > 0:
|
||
print(f"{indent}跳过重复的render节点")
|
||
return
|
||
|
||
# 跳过光源节点
|
||
if nodePath.getName() in ["alight", "dlight"]:
|
||
print(f"{indent}跳过光源节点: {nodePath.getName()}")
|
||
return
|
||
|
||
# 跳过相机节点
|
||
if nodePath.getName() in ["camera", "cam"]:
|
||
print(f"{indent}跳过相机节点: {nodePath.getName()}")
|
||
return
|
||
|
||
if isinstance(nodePath.node(), ModelRoot):
|
||
print(f"{indent}找到模型根节点!")
|
||
|
||
# 清除现有材质状态
|
||
nodePath.clearMaterial()
|
||
nodePath.clearColor()
|
||
|
||
# 创建新材质
|
||
material = Material()
|
||
|
||
# 从标签恢复材质属性
|
||
def parseColor(color_str):
|
||
"""解析颜色字符串为Vec4"""
|
||
try:
|
||
# 移除LVecBase4f标记,只保留数值
|
||
color_str = color_str.replace('LVecBase4f', '').strip('()')
|
||
r, g, b, a = map(float, color_str.split(','))
|
||
return Vec4(r, g, b, a)
|
||
except:
|
||
return Vec4(1, 1, 1, 1) # 默认白色
|
||
|
||
if nodePath.hasTag("material_ambient"):
|
||
material.setAmbient(parseColor(nodePath.getTag("material_ambient")))
|
||
if nodePath.hasTag("material_diffuse"):
|
||
material.setDiffuse(parseColor(nodePath.getTag("material_diffuse")))
|
||
if nodePath.hasTag("material_specular"):
|
||
material.setSpecular(parseColor(nodePath.getTag("material_specular")))
|
||
if nodePath.hasTag("material_emission"):
|
||
material.setEmission(parseColor(nodePath.getTag("material_emission")))
|
||
if nodePath.hasTag("material_shininess"):
|
||
material.setShininess(float(nodePath.getTag("material_shininess")))
|
||
if nodePath.hasTag("material_basecolor"):
|
||
material.setBaseColor(parseColor(nodePath.getTag("material_basecolor")))
|
||
|
||
# 应用材质
|
||
nodePath.setMaterial(material)
|
||
|
||
# 恢复颜色属性
|
||
if nodePath.hasTag("color"):
|
||
nodePath.setColor(parseColor(nodePath.getTag("color")))
|
||
|
||
# 恢复变换信息(关键!)
|
||
def parseVec3(vec_str):
|
||
"""解析向量字符串为Vec3"""
|
||
try:
|
||
# 移除LVecBase3f标记,只保留数值
|
||
vec_str = vec_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()')
|
||
x, y, z = map(float, vec_str.split(','))
|
||
return Vec3(x, y, z)
|
||
except Exception as e:
|
||
print(f"解析向量失败: {vec_str}, 错误: {e}")
|
||
return Vec3(0, 0, 0) # 默认值
|
||
|
||
if nodePath.hasTag("transform_pos"):
|
||
pos = parseVec3(nodePath.getTag("transform_pos"))
|
||
nodePath.setPos(pos)
|
||
print(f"{indent}恢复位置: {pos}")
|
||
|
||
if nodePath.hasTag("transform_hpr"):
|
||
hpr = parseVec3(nodePath.getTag("transform_hpr"))
|
||
nodePath.setHpr(hpr)
|
||
print(f"{indent}恢复旋转: {hpr}")
|
||
|
||
if nodePath.hasTag("transform_scale"):
|
||
scale = parseVec3(nodePath.getTag("transform_scale"))
|
||
nodePath.setScale(scale)
|
||
print(f"{indent}恢复缩放: {scale}")
|
||
|
||
# 将模型重新挂载到render下
|
||
nodePath.wrtReparentTo(self.world.render)
|
||
|
||
# 为加载的模型设置碰撞检测
|
||
self.setupCollision(nodePath)
|
||
|
||
self.models.append(nodePath)
|
||
|
||
# 递归处理子节点
|
||
for child in nodePath.getChildren():
|
||
processNode(child, depth + 1)
|
||
|
||
print("\n开始处理场景节点...")
|
||
processNode(scene)
|
||
|
||
# 移除临时场景节点
|
||
scene.removeNode()
|
||
|
||
# 更新场景树
|
||
self.updateSceneTree()
|
||
|
||
print("=== 场景加载完成 ===\n")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"加载场景时发生错误: {str(e)}")
|
||
return False
|
||
|
||
# ==================== 模型管理 ====================
|
||
|
||
def deleteModel(self, model):
|
||
"""删除模型"""
|
||
try:
|
||
if model in self.models:
|
||
model.removeNode()
|
||
self.models.remove(model)
|
||
self.updateSceneTree()
|
||
print(f"删除模型: {model.getName()}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"删除模型失败: {str(e)}")
|
||
return False
|
||
|
||
def clearAllModels(self):
|
||
"""清除所有模型"""
|
||
try:
|
||
for model in self.models:
|
||
model.removeNode()
|
||
self.models.clear()
|
||
self.updateSceneTree()
|
||
print("清除所有模型完成")
|
||
except Exception as e:
|
||
print(f"清除所有模型失败: {str(e)}")
|
||
|
||
def getModels(self):
|
||
"""获取模型列表"""
|
||
return self.models.copy()
|
||
|
||
def getModelCount(self):
|
||
"""获取模型数量"""
|
||
return len(self.models)
|
||
|
||
def findModelByName(self, name):
|
||
"""根据名称查找模型"""
|
||
for model in self.models:
|
||
if model.getName() == name:
|
||
return model
|
||
return None
|
||
|
||
# ==================== 帮助方法 ====================
|
||
|
||
def processLoadedModel(self, model):
|
||
"""处理加载完成的模型(用于异步加载回调)"""
|
||
if model:
|
||
# 添加到模型列表
|
||
self.models.append(model)
|
||
|
||
# 设置基础变换
|
||
model.setPos(0, 0, 0)
|
||
model.setHpr(0, 0, 0)
|
||
model.setScale(1, 1, 1)
|
||
|
||
# 应用材质
|
||
self.processMaterials(model)
|
||
|
||
# 设置碰撞检测
|
||
self.setupCollision(model)
|
||
|
||
# 更新场景树
|
||
self.updateSceneTree()
|
||
|
||
print(f"异步加载模型完成: {model.getName()}")
|
||
|
||
def createSpotLight(self, pos=(0, 0, 0)):
|
||
from RenderPipelineFile.rpcore import SpotLight, RenderPipeline
|
||
from panda3d.core import Vec3,NodePath
|
||
|
||
render_pipeline = get_render_pipeline()
|
||
|
||
# 创建一个挂载节点(你控制的)
|
||
light_np = NodePath("SpotlightAttachNode")
|
||
light_np.reparentTo(self.world.render)
|
||
#light_np.setPos(*pos)
|
||
|
||
self.half_energy = 5000
|
||
self.lamp_fov = 70
|
||
self.lamp_radius = 1000
|
||
|
||
light = SpotLight()
|
||
light.direction = Vec3(0, 0, -1) # 光照方向
|
||
light.fov = self.lamp_fov # 光源角度(类似手电筒)
|
||
light.set_color_from_temperature(5 * 1000.0) # 色温(K)
|
||
light.energy = self.half_energy # 光照强度
|
||
light.radius = self.lamp_radius # 影响范围
|
||
light.casts_shadows = True # 是否投射阴影
|
||
light.shadow_map_resolution = 256 # 阴影分辨率
|
||
light.setPos(*pos)
|
||
#light_np.setPos(*pos)
|
||
|
||
#light_np = render_pipeline.add_light(light, parent=self.world.render)
|
||
render_pipeline.add_light(light) # 添加到渲染管线
|
||
|
||
light_name = f"Spotlight_{len(self.Spotlight)}"
|
||
|
||
light_np.setName(light_name) # 设置唯一名称
|
||
#light_np.reparentTo(self.world.render) # 挂载到场景根节点
|
||
|
||
light_np.setTag("light_type", "spot_light")
|
||
light_np.setTag("is_scene_element", "1")
|
||
light_np.setTag("light_energy", str(light.energy))
|
||
|
||
light_np.setPythonTag("rp_light_object", light)
|
||
|
||
self.Spotlight.append(light_np)
|
||
|
||
if hasattr(self.world, 'updateSceneTree'):
|
||
self.world.updateSceneTree()
|
||
|
||
#print("nikan"+light_np.getHpr())
|
||
|
||
def createPointLight(self, pos=(0, 0, 0)):
|
||
from RenderPipelineFile.rpcore import PointLight, RenderPipeline
|
||
from panda3d.core import Vec3, NodePath
|
||
|
||
render_pipeline = get_render_pipeline()
|
||
|
||
# 创建一个挂载节点(你控制的)
|
||
light_np = NodePath("PointlightAttachNode")
|
||
light_np.reparentTo(self.world.render)
|
||
|
||
|
||
light = PointLight()
|
||
light.setPos(*pos)
|
||
light_np.setPos(*pos)
|
||
light.energy = 5000
|
||
light.radius = 1000
|
||
light.inner_radius = 0.4
|
||
light.set_color_from_temperature(5 * 1000.0) # 色温(K)
|
||
light.casts_shadows = True # 是否投射阴影
|
||
light.shadow_map_resolution = 256 # 阴影分辨率
|
||
|
||
render_pipeline.add_light(light) # 添加到渲染管线
|
||
|
||
light_name = f"Pointlight{len(self.Pointlight)}"
|
||
|
||
light_np.setName(light_name) # 设置唯一名称
|
||
|
||
#light_np = NodePath(f"PointLight_{len(self.Pointlight)}")
|
||
#light_np.reparentTo(self.world.render)
|
||
#light_np.setPos(*pos)
|
||
|
||
light_np.setTag("light_type", "point_light")
|
||
light_np.setTag("is_scene_element", "1")
|
||
light_np.setTag("light_energy", str(light.energy))
|
||
|
||
# 保存光源对象引用(重要!用于属性面板)
|
||
light_np.setPythonTag("rp_light_object", light)
|
||
|
||
self.Pointlight.append(light_np)
|
||
|
||
if hasattr(self.world, 'updateSceneTree'):
|
||
self.world.updateSceneTree()
|
||
|
||
return light,light_np
|
||
|