// // InspectorPanel.cpp:组件编辑实现。 // // Transform 编辑必须走 Set* 入口(标脏 → 帧循环统一 Compose 重算 // world),直接改私有成员是编译错误——面板天然遵守引擎约束。 // #include "InspectorPanel.h" #include "Engine/ECS/Components.h" #include "Engine/Renderer/Model.h" #include #include #include namespace { // InputText 需要 char 缓冲;提交时写回 std::string bool InputTextString(const char* label, std::string& str) { char buf[256]; std::snprintf(buf, sizeof(buf), "%s", str.c_str()); if (ImGui::InputText(label, buf, sizeof(buf))) { str = buf; return true; } return false; } } void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) { ImGui::Begin("Inspector"); auto& reg = ctx.scene->GetRegistry(); if (selected == entt::null || !reg.valid(selected)) { ImGui::TextDisabled("(no entity selected)"); ImGui::End(); return; } // ---- Name ---- if (auto* name = reg.try_get(selected)) { InputTextString("Name", name->name); } ImGui::Separator(); // ---- Add Component ---- if (ImGui::Button("Add Component")) { ImGui::OpenPopup("add_component"); } if (ImGui::BeginPopup("add_component")) { if (!reg.all_of(selected) && ImGui::MenuItem("Light")) { reg.emplace(selected); } if (!reg.all_of(selected) && ImGui::MenuItem("Camera")) { reg.emplace(selected); } if (!reg.all_of(selected) && ImGui::MenuItem("Animation")) { reg.emplace(selected); } if (!reg.all_of(selected) && ImGui::MenuItem("Name")) { reg.emplace(selected); } if (!reg.all_of(selected) && ImGui::MenuItem("RigidBody")) { reg.emplace(selected); } ImGui::EndPopup(); } ImGui::Separator(); // ---- Transform ---- if (auto* t = reg.try_get(selected)) { if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { glm::vec3 pos = t->GetPosition(); if (ImGui::DragFloat3("position", &pos.x, 0.05f)) { t->SetPosition(pos); } glm::vec3 rot = t->GetRotation(); // 欧拉角(度) if (ImGui::DragFloat3("rotation", &rot.x, 0.5f)) { t->SetRotation(rot); } glm::vec3 scl = t->GetScale(); if (ImGui::DragFloat3("scale", &scl.x, 0.02f, 0.001f, 100.0f)) { t->SetScale(scl); } } } // ---- MeshRenderer ---- if (auto* mr = reg.try_get(selected)) { if (ImGui::CollapsingHeader("MeshRenderer", ImGuiTreeNodeFlags_DefaultOpen)) { if (const Engine::Model* model = ctx.assets->GetModel(mr->model); model != nullptr) { const auto& clips = model->GetClips(); ImGui::Text("model: valid (%u clips, %u bones)", static_cast(clips.size()), static_cast(model->GetBoneCount())); ImGui::TextDisabled("(mesh asset is read-only here)"); } else { ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "model: INVALID (dangling ref)"); } } } // ---- Light ---- if (auto* light = reg.try_get(selected)) { if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) { int type = static_cast(light->type); if (ImGui::Combo("type", &type, "Directional\0Point\0")) { light->type = static_cast(type); } ImGui::ColorEdit3("color", &light->color.x); ImGui::DragFloat("intensity", &light->color.a, 0.05f, 0.0f, 100.0f); if (light->type == Engine::ECS::LightComponent::Type::Directional) { ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f); ImGui::Checkbox("castShadow", &light->castShadow); } else { ImGui::DragFloat("radius", &light->radius, 0.1f, 0.0f, 100.0f); ImGui::TextDisabled("(position = entity Transform)"); } } } // ---- Camera ---- if (auto* cam = reg.try_get(selected)) { if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) { if (ImGui::Checkbox("primary", &cam->primary) && cam->primary) { // primary 互斥:启用本相机时清掉其余实体的 primary //(引擎取第一个 primary 作为渲染视角) reg.view().each( [&](entt::entity e, Engine::ECS::CameraComponent& c) { if (e != selected) { c.primary = false; } }); } } } // ---- RigidBody(Jolt 刚体:动态体 Transform 由物理每帧写回)---- // type/collider/mass 变更 → PhysicsWorld 重建 body;其余参数即时生效 if (auto* rb = reg.try_get(selected)) { if (ImGui::CollapsingHeader("RigidBody", ImGuiTreeNodeFlags_DefaultOpen)) { int type = static_cast(rb->type); if (ImGui::Combo("type", &type, "Static\0Dynamic\0Kinematic\0")) { rb->type = static_cast(type); } int collider = static_cast(rb->collider); if (ImGui::Combo("collider", &collider, "Box\0Sphere\0Mesh\0")) { rb->collider = static_cast(collider); } if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Box) { ImGui::DragFloat3("halfExtents", &rb->boxHalfExtents.x, 0.05f, 0.01f, 500.0f); } else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Sphere) { ImGui::DragFloat("radius", &rb->sphereRadius, 0.05f, 0.01f, 500.0f); } else { // Mesh:跟随 MeshRenderer 视觉网格(Static=精确网格, // Dynamic=凸包近似),无尺寸可调 ImGui::TextDisabled("follows MeshRenderer geometry"); } ImGui::DragFloat("mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f"); ImGui::SliderFloat("gravityScale", &rb->gravityScale, -1.0f, 5.0f); ImGui::SliderFloat("friction", &rb->friction, 0.0f, 2.0f); ImGui::SliderFloat("restitution", &rb->restitution, 0.0f, 1.0f); ImGui::Checkbox("isSensor", &rb->isSensor); if (rb->bodyValid && rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic) { ImGui::Text("vel: %.2f, %.2f, %.2f m/s", rb->velocity.x, rb->velocity.y, rb->velocity.z); } } } // ---- Animation ---- if (auto* anim = reg.try_get(selected)) { if (ImGui::CollapsingHeader("Animation", ImGuiTreeNodeFlags_DefaultOpen)) { const auto* mr = reg.try_get(selected); const Engine::Model* model = mr ? ctx.assets->GetModel(mr->model) : nullptr; if (model != nullptr && model->HasAnimations()) { const auto& clips = model->GetClips(); std::vector clipNames; clipNames.reserve(clips.size()); for (const auto& c : clips) { clipNames.push_back(c.name.c_str()); } int cur = anim->clipIndex; if (ImGui::Combo("clip", &cur, clipNames.data(), static_cast(clipNames.size()))) { anim->Play(cur); // 触发 crossfade(fade 秒) } ImGui::Checkbox("playing", &anim->playing); ImGui::SameLine(); ImGui::Checkbox("loop", &anim->loop); ImGui::SliderFloat("speed", &anim->speed, -2.0f, 2.0f); ImGui::SliderFloat("fade", &anim->fadeDuration, 0.0f, 1.0f); if (anim->clipIndex >= 0 && anim->clipIndex < static_cast(clips.size())) { ImGui::Text("t: %.2f / %.2f s", anim->time, clips[anim->clipIndex].duration); } } else { ImGui::TextDisabled("(no animation clips on model)"); } } } ImGui::End(); }