per-mesh 引用:MeshRendererComponent.meshIndex(-1=整模型)贯穿渲染收集/物理 Mesh 碰撞(自动跟随)/场景序列化(缺省键向前兼容)/Inspector mesh 下拉

This commit is contained in:
2026-10-09 12:38:05 +08:00
parent 505217ff7e
commit 6854d53f11
5 changed files with 72 additions and 5 deletions
+34
View File
@@ -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<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();
if (model->GetMeshes().empty())
{
+9
View File
@@ -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<u32, std::string> 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 由消费端处理)
+17 -5
View File
@@ -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<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);
}
}
@@ -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<int>(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));
+6
View File
@@ -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<int>(meshIdx) != mr.meshIndex) {
continue;
}
auto& mesh = meshes[meshIdx];
const auto& bounds = mesh->GetBounds();
const glm::vec3 worldCenter =
@@ -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<ECS::MeshRendererComponent>();
mrComp.active =
n["Active"].as<bool>(true); // 缺省 true 向前兼容
mrComp.meshIndex = n["MeshIndex"].as<int>(-1);
// 材质覆盖表:逐项解析(guid 反查 → path 兜底)
// 并预载;加载失败保留路径(收集期回退内建材质)
if (const YAML::Node ovs = n["MaterialOverrides"]) {