per-mesh 引用:MeshRendererComponent.meshIndex(-1=整模型)贯穿渲染收集/物理 Mesh 碰撞(自动跟随)/场景序列化(缺省键向前兼容)/Inspector mesh 下拉
This commit is contained in:
@@ -739,6 +739,40 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
if (const Engine::Model* model = ctx.assets->GetModel(mr->model);
|
if (const Engine::Model* model = ctx.assets->GetModel(mr->model);
|
||||||
model != nullptr)
|
model != nullptr)
|
||||||
{
|
{
|
||||||
|
// 单 mesh 子引用下拉(-1 = 整模型全部):名字用
|
||||||
|
// GetMeshMaterialInfos(材质名优先,空回退 Slot N)
|
||||||
|
{
|
||||||
|
const auto& infos = model->GetMeshMaterialInfos();
|
||||||
|
const int sel = mr->meshIndex + 1; // 0 = All
|
||||||
|
std::string preview = "All meshes";
|
||||||
|
if (sel > 0 &&
|
||||||
|
static_cast<size_t>(sel - 1) < infos.size()) {
|
||||||
|
preview = infos[static_cast<size_t>(sel) - 1]
|
||||||
|
.name.empty()
|
||||||
|
? "Slot " + std::to_string(sel - 1)
|
||||||
|
: infos[static_cast<size_t>(sel) - 1].name;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginCombo("mesh", preview.c_str()))
|
||||||
|
{
|
||||||
|
if (ImGui::Selectable("All meshes", sel <= 0)) {
|
||||||
|
mr->meshIndex = -1;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < infos.size(); ++i)
|
||||||
|
{
|
||||||
|
const std::string label =
|
||||||
|
(infos[i].name.empty()
|
||||||
|
? "Slot " + std::to_string(i)
|
||||||
|
: infos[i].name) +
|
||||||
|
"##" + std::to_string(i);
|
||||||
|
const bool cur =
|
||||||
|
mr->meshIndex == static_cast<int>(i);
|
||||||
|
if (ImGui::Selectable(label.c_str(), cur)) {
|
||||||
|
mr->meshIndex = static_cast<int>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto& clips = model->GetClips();
|
const auto& clips = model->GetClips();
|
||||||
if (model->GetMeshes().empty())
|
if (model->GetMeshes().empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -203,6 +203,10 @@ namespace Engine::ECS {
|
|||||||
struct MeshRendererComponent {
|
struct MeshRendererComponent {
|
||||||
ModelRef model;
|
ModelRef model;
|
||||||
bool active = true; // 禁用 = 渲染/拾取跳过(Unity enabled / UE SetActive)
|
bool active = true; // 禁用 = 渲染/拾取跳过(Unity enabled / UE SetActive)
|
||||||
|
// 单 mesh 子引用:-1 = 整模型全部;N = 只画第 N 个
|
||||||
|
//(GetMeshes 下标)。物理 Mesh 碰撞体自动跟随(BuildMeshShape
|
||||||
|
// 读本组件)。序列化缺省键 = -1 向前兼容
|
||||||
|
int meshIndex = -1;
|
||||||
std::unordered_map<u32, std::string> materialOverrides;
|
std::unordered_map<u32, std::string> materialOverrides;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,6 +226,11 @@ namespace Engine::ECS {
|
|||||||
bool playing = true;
|
bool playing = true;
|
||||||
bool loop = true;
|
bool loop = true;
|
||||||
f32 speed = 1.0f;
|
f32 speed = 1.0f;
|
||||||
|
// 根运动开关(Unity Animator "Apply Root Motion" 同语义):
|
||||||
|
// true = 保留根节点位移轨道(跑动 clip 自带位移会漂出锚点);
|
||||||
|
// false = 原地播放(丢弃动画子树根节点位移,旋转保留)——
|
||||||
|
// 位移归脚本/物理驱动的角色(CharacterController 等)用 false
|
||||||
|
bool applyRootMotion = true;
|
||||||
|
|
||||||
// ---- 播放头 / crossfade(瞬态,不落盘——重启从 0 重播)----
|
// ---- 播放头 / crossfade(瞬态,不落盘——重启从 0 重播)----
|
||||||
f32 time = 0.0f; // 当前 clip 播放头(秒,wrap 由消费端处理)
|
f32 time = 0.0f; // 当前 clip 播放头(秒,wrap 由消费端处理)
|
||||||
|
|||||||
@@ -767,8 +767,14 @@ namespace Engine {
|
|||||||
// 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动)
|
// 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动)
|
||||||
if (rb.type != ECS::RigidBodyComponent::BodyType::Static) {
|
if (rb.type != ECS::RigidBodyComponent::BodyType::Static) {
|
||||||
JPH::ConvexHullShapeSettings cs;
|
JPH::ConvexHullShapeSettings cs;
|
||||||
for (const auto &mesh: model->GetMeshes()) {
|
const auto &meshes = model->GetMeshes();
|
||||||
for (const auto &p: mesh->GetCpuPositions()) {
|
for (size_t mi = 0; mi < meshes.size(); ++mi) {
|
||||||
|
// 单 mesh 子引用:碰撞只取渲染可见的同一 mesh(语义跟随)
|
||||||
|
if (mc->meshIndex >= 0 &&
|
||||||
|
static_cast<int>(mi) != mc->meshIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const auto &p: meshes[mi]->GetCpuPositions()) {
|
||||||
cs.mPoints.emplace_back(p.x * s.x, p.y * s.y, p.z * s.z);
|
cs.mPoints.emplace_back(p.x * s.x, p.y * s.y, p.z * s.z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,9 +794,15 @@ namespace Engine {
|
|||||||
JPH::VertexList verts;
|
JPH::VertexList verts;
|
||||||
JPH::IndexedTriangleList tris;
|
JPH::IndexedTriangleList tris;
|
||||||
u32 base = 0;
|
u32 base = 0;
|
||||||
for (const auto &mesh: model->GetMeshes()) {
|
const auto &meshes = model->GetMeshes();
|
||||||
const auto &pos = mesh->GetCpuPositions();
|
for (size_t mi = 0; mi < meshes.size(); ++mi) {
|
||||||
const auto &idx = mesh->GetCpuIndices();
|
// 单 mesh 子引用:碰撞只取渲染可见的同一 mesh(语义跟随)
|
||||||
|
if (mc->meshIndex >= 0 &&
|
||||||
|
static_cast<int>(mi) != mc->meshIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &pos = meshes[mi]->GetCpuPositions();
|
||||||
|
const auto &idx = meshes[mi]->GetCpuIndices();
|
||||||
verts.reserve(verts.size() + pos.size());
|
verts.reserve(verts.size() + pos.size());
|
||||||
for (const auto &p: pos) {
|
for (const auto &p: pos) {
|
||||||
verts.push_back(JPH::Float3(p.x * s.x, p.y * s.y, p.z * s.z));
|
verts.push_back(JPH::Float3(p.x * s.x, p.y * s.y, p.z * s.z));
|
||||||
|
|||||||
@@ -757,6 +757,12 @@ namespace Engine
|
|||||||
|
|
||||||
const auto& meshes = model->GetMeshes();
|
const auto& meshes = model->GetMeshes();
|
||||||
for (size_t meshIdx = 0; meshIdx < meshes.size(); ++meshIdx) {
|
for (size_t meshIdx = 0; meshIdx < meshes.size(); ++meshIdx) {
|
||||||
|
// 单 mesh 子引用:只收集指派的那一个(覆盖表/物理
|
||||||
|
// 均按原模型下标,语义不变)
|
||||||
|
if (mr.meshIndex >= 0 &&
|
||||||
|
static_cast<int>(meshIdx) != mr.meshIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
auto& mesh = meshes[meshIdx];
|
auto& mesh = meshes[meshIdx];
|
||||||
const auto& bounds = mesh->GetBounds();
|
const auto& bounds = mesh->GetBounds();
|
||||||
const glm::vec3 worldCenter =
|
const glm::vec3 worldCenter =
|
||||||
|
|||||||
@@ -312,6 +312,11 @@ namespace Engine::Serialization {
|
|||||||
out << YAML::Key << "Path" << YAML::Value << path;
|
out << YAML::Key << "Path" << YAML::Value << path;
|
||||||
out << YAML::EndMap; // Model(flow)
|
out << YAML::EndMap; // Model(flow)
|
||||||
out << YAML::Key << "Active" << YAML::Value << mr->active;
|
out << YAML::Key << "Active" << YAML::Value << mr->active;
|
||||||
|
// 单 mesh 子引用(-1 = 整模型,缺省键向前兼容)
|
||||||
|
if (mr->meshIndex >= 0) {
|
||||||
|
out << YAML::Key << "MeshIndex"
|
||||||
|
<< YAML::Value << mr->meshIndex;
|
||||||
|
}
|
||||||
// 材质覆盖表(UE Material Slot):seq of {Slot,Guid,Path}
|
// 材质覆盖表(UE Material Slot):seq of {Slot,Guid,Path}
|
||||||
if (!mr->materialOverrides.empty()) {
|
if (!mr->materialOverrides.empty()) {
|
||||||
out << YAML::Key << "MaterialOverrides"
|
out << YAML::Key << "MaterialOverrides"
|
||||||
@@ -609,6 +614,7 @@ namespace Engine::Serialization {
|
|||||||
handle.GetComponent<ECS::MeshRendererComponent>();
|
handle.GetComponent<ECS::MeshRendererComponent>();
|
||||||
mrComp.active =
|
mrComp.active =
|
||||||
n["Active"].as<bool>(true); // 缺省 true 向前兼容
|
n["Active"].as<bool>(true); // 缺省 true 向前兼容
|
||||||
|
mrComp.meshIndex = n["MeshIndex"].as<int>(-1);
|
||||||
// 材质覆盖表:逐项解析(guid 反查 → path 兜底)
|
// 材质覆盖表:逐项解析(guid 反查 → path 兜底)
|
||||||
// 并预载;加载失败保留路径(收集期回退内建材质)
|
// 并预载;加载失败保留路径(收集期回退内建材质)
|
||||||
if (const YAML::Node ovs = n["MaterialOverrides"]) {
|
if (const YAML::Node ovs = n["MaterialOverrides"]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user