From 6854d53f1186f278da2d8a0c1353df233bea3498 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Fri, 9 Oct 2026 12:38:05 +0800 Subject: [PATCH] =?UTF-8?q?per-mesh=20=E5=BC=95=E7=94=A8=EF=BC=9AMeshRende?= =?UTF-8?q?rerComponent.meshIndex=EF=BC=88-1=3D=E6=95=B4=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=EF=BC=89=E8=B4=AF=E7=A9=BF=E6=B8=B2=E6=9F=93=E6=94=B6=E9=9B=86?= =?UTF-8?q?/=E7=89=A9=E7=90=86=20Mesh=20=E7=A2=B0=E6=92=9E=EF=BC=88?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=B7=9F=E9=9A=8F=EF=BC=89/=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E5=BA=8F=E5=88=97=E5=8C=96=EF=BC=88=E7=BC=BA=E7=9C=81?= =?UTF-8?q?=E9=94=AE=E5=90=91=E5=89=8D=E5=85=BC=E5=AE=B9=EF=BC=89/Inspecto?= =?UTF-8?q?r=20mesh=20=E4=B8=8B=E6=8B=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/editor/panels/InspectorPanel.cpp | 34 ++++++++++++++++++++ Engine/include/Engine/ECS/Components.h | 9 ++++++ Engine/src/Physics/PhysicsWorld.cpp | 22 ++++++++++--- Engine/src/Renderer/SceneRenderer.cpp | 6 ++++ Engine/src/Serialization/SceneSerializer.cpp | 6 ++++ 5 files changed, 72 insertions(+), 5 deletions(-) diff --git a/App/editor/panels/InspectorPanel.cpp b/App/editor/panels/InspectorPanel.cpp index 4c7c670..879382d 100644 --- a/App/editor/panels/InspectorPanel.cpp +++ b/App/editor/panels/InspectorPanel.cpp @@ -739,6 +739,40 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) if (const Engine::Model* model = ctx.assets->GetModel(mr->model); 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(sel - 1) < infos.size()) { + preview = infos[static_cast(sel) - 1] + .name.empty() + ? "Slot " + std::to_string(sel - 1) + : infos[static_cast(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(i); + if (ImGui::Selectable(label.c_str(), cur)) { + mr->meshIndex = static_cast(i); + } + } + ImGui::EndCombo(); + } + } const auto& clips = model->GetClips(); if (model->GetMeshes().empty()) { diff --git a/Engine/include/Engine/ECS/Components.h b/Engine/include/Engine/ECS/Components.h index 6620498..382871c 100644 --- a/Engine/include/Engine/ECS/Components.h +++ b/Engine/include/Engine/ECS/Components.h @@ -203,6 +203,10 @@ namespace Engine::ECS { struct MeshRendererComponent { ModelRef model; bool active = true; // 禁用 = 渲染/拾取跳过(Unity enabled / UE SetActive) + // 单 mesh 子引用:-1 = 整模型全部;N = 只画第 N 个 + //(GetMeshes 下标)。物理 Mesh 碰撞体自动跟随(BuildMeshShape + // 读本组件)。序列化缺省键 = -1 向前兼容 + int meshIndex = -1; std::unordered_map materialOverrides; }; @@ -222,6 +226,11 @@ namespace Engine::ECS { bool playing = true; bool loop = true; f32 speed = 1.0f; + // 根运动开关(Unity Animator "Apply Root Motion" 同语义): + // true = 保留根节点位移轨道(跑动 clip 自带位移会漂出锚点); + // false = 原地播放(丢弃动画子树根节点位移,旋转保留)—— + // 位移归脚本/物理驱动的角色(CharacterController 等)用 false + bool applyRootMotion = true; // ---- 播放头 / crossfade(瞬态,不落盘——重启从 0 重播)---- f32 time = 0.0f; // 当前 clip 播放头(秒,wrap 由消费端处理) diff --git a/Engine/src/Physics/PhysicsWorld.cpp b/Engine/src/Physics/PhysicsWorld.cpp index 5f94351..0b44422 100644 --- a/Engine/src/Physics/PhysicsWorld.cpp +++ b/Engine/src/Physics/PhysicsWorld.cpp @@ -767,8 +767,14 @@ namespace Engine { // 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动) if (rb.type != ECS::RigidBodyComponent::BodyType::Static) { JPH::ConvexHullShapeSettings cs; - for (const auto &mesh: model->GetMeshes()) { - for (const auto &p: mesh->GetCpuPositions()) { + const auto &meshes = model->GetMeshes(); + for (size_t mi = 0; mi < meshes.size(); ++mi) { + // 单 mesh 子引用:碰撞只取渲染可见的同一 mesh(语义跟随) + if (mc->meshIndex >= 0 && + static_cast(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); } } @@ -788,9 +794,15 @@ namespace Engine { JPH::VertexList verts; JPH::IndexedTriangleList tris; u32 base = 0; - for (const auto &mesh: model->GetMeshes()) { - const auto &pos = mesh->GetCpuPositions(); - const auto &idx = mesh->GetCpuIndices(); + const auto &meshes = model->GetMeshes(); + for (size_t mi = 0; mi < meshes.size(); ++mi) { + // 单 mesh 子引用:碰撞只取渲染可见的同一 mesh(语义跟随) + if (mc->meshIndex >= 0 && + static_cast(mi) != mc->meshIndex) { + continue; + } + const auto &pos = meshes[mi]->GetCpuPositions(); + const auto &idx = meshes[mi]->GetCpuIndices(); verts.reserve(verts.size() + pos.size()); for (const auto &p: pos) { verts.push_back(JPH::Float3(p.x * s.x, p.y * s.y, p.z * s.z)); diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 6a80940..7673382 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -757,6 +757,12 @@ namespace Engine const auto& meshes = model->GetMeshes(); for (size_t meshIdx = 0; meshIdx < meshes.size(); ++meshIdx) { + // 单 mesh 子引用:只收集指派的那一个(覆盖表/物理 + // 均按原模型下标,语义不变) + if (mr.meshIndex >= 0 && + static_cast(meshIdx) != mr.meshIndex) { + continue; + } auto& mesh = meshes[meshIdx]; const auto& bounds = mesh->GetBounds(); const glm::vec3 worldCenter = diff --git a/Engine/src/Serialization/SceneSerializer.cpp b/Engine/src/Serialization/SceneSerializer.cpp index 309665e..46fa094 100644 --- a/Engine/src/Serialization/SceneSerializer.cpp +++ b/Engine/src/Serialization/SceneSerializer.cpp @@ -312,6 +312,11 @@ namespace Engine::Serialization { out << YAML::Key << "Path" << YAML::Value << path; out << YAML::EndMap; // Model(flow) 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} if (!mr->materialOverrides.empty()) { out << YAML::Key << "MaterialOverrides" @@ -609,6 +614,7 @@ namespace Engine::Serialization { handle.GetComponent(); mrComp.active = n["Active"].as(true); // 缺省 true 向前兼容 + mrComp.meshIndex = n["MeshIndex"].as(-1); // 材质覆盖表:逐项解析(guid 反查 → path 兜底) // 并预载;加载失败保留路径(收集期回退内建材质) if (const YAML::Node ovs = n["MaterialOverrides"]) {