diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index 29b258c..5be6948 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -120,6 +120,7 @@ set(EDITSCENE_SOURCES components/SkyboxModule.cpp camera/EditorCamera.cpp gizmo/Gizmo.cpp + gizmo/Cursor3D.cpp physics/physics.cpp ) @@ -239,6 +240,7 @@ set(EDITSCENE_HEADERS components/ActionDebug.hpp camera/EditorCamera.hpp gizmo/Gizmo.hpp + gizmo/Cursor3D.hpp physics/physics.h ) diff --git a/src/features/editScene/gizmo/Cursor3D.cpp b/src/features/editScene/gizmo/Cursor3D.cpp new file mode 100644 index 0000000..0c01c5c --- /dev/null +++ b/src/features/editScene/gizmo/Cursor3D.cpp @@ -0,0 +1,252 @@ +#include "Cursor3D.hpp" +#include "../components/Transform.hpp" +#include + +// Colors for cursor - bright cyan/white for visibility +static const float COLOR_CYAN[3] = { 0.0f, 1.0f, 1.0f }; +static const float COLOR_WHITE[3] = { 1.0f, 1.0f, 1.0f }; +static const float COLOR_RED[3] = { 1.0f, 0.2f, 0.2f }; +static const float COLOR_GREEN[3] = { 0.2f, 1.0f, 0.2f }; +static const float COLOR_BLUE[3] = { 0.2f, 0.4f, 1.0f }; + +Cursor3D::Cursor3D(Ogre::SceneManager *sceneMgr) + : m_sceneMgr(sceneMgr) + , m_cursorNode(nullptr) + , m_axesObj(nullptr) + , m_markerObj(nullptr) + , m_position(Ogre::Vector3::ZERO) + , m_orientation(Ogre::Quaternion::IDENTITY) + , m_size(1.0f) + , m_visible(false) +{ + m_cursorNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode( + "Cursor3DNode"); + + m_axesObj = m_sceneMgr->createManualObject("Cursor3DAxes"); + m_markerObj = m_sceneMgr->createManualObject("Cursor3DMarker"); + + m_cursorNode->attachObject(m_axesObj); + m_cursorNode->attachObject(m_markerObj); + + // Draw on top of everything + m_axesObj->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY); + m_markerObj->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY); + + m_cursorNode->setVisible(false); + + createGeometry(); +} + +void Cursor3D::shutdown() +{ + if (!m_sceneMgr) + return; + + if (m_cursorNode && m_axesObj) { + try { + m_cursorNode->detachObject(m_axesObj); + } catch (...) { + } + } + if (m_cursorNode && m_markerObj) { + try { + m_cursorNode->detachObject(m_markerObj); + } catch (...) { + } + } + + if (m_axesObj) { + try { + m_sceneMgr->destroyManualObject(m_axesObj); + } catch (...) { + } + m_axesObj = nullptr; + } + if (m_markerObj) { + try { + m_sceneMgr->destroyManualObject(m_markerObj); + } catch (...) { + } + m_markerObj = nullptr; + } + + if (m_cursorNode) { + try { + m_sceneMgr->destroySceneNode(m_cursorNode); + } catch (...) { + } + m_cursorNode = nullptr; + } + + m_sceneMgr = nullptr; +} + +Cursor3D::~Cursor3D() +{ + if (m_sceneMgr) + shutdown(); +} + +void Cursor3D::setPosition(const Ogre::Vector3 &pos) +{ + m_position = pos; + updateNodeTransform(); +} + +void Cursor3D::setOrientation(const Ogre::Quaternion &rot) +{ + m_orientation = rot; + updateNodeTransform(); +} + +void Cursor3D::setVisible(bool visible) +{ + m_visible = visible; + if (m_cursorNode) + m_cursorNode->setVisible(visible); +} + +bool Cursor3D::isVisible() const +{ + return m_visible; +} + +void Cursor3D::setSize(float size) +{ + m_size = size; + createGeometry(); +} + +void Cursor3D::snapToTransform(const TransformComponent &transform) +{ + if (transform.node) { + m_position = transform.node->_getDerivedPosition(); + m_orientation = transform.node->_getDerivedOrientation(); + } else { + m_position = transform.position; + m_orientation = transform.rotation; + } + updateNodeTransform(); +} + +void Cursor3D::applyToTransform(TransformComponent &transform) const +{ + if (!transform.node) + return; + + Ogre::SceneNode *parent = static_cast( + transform.node->getParent()); + if (parent) { + transform.position = parent->convertWorldToLocalPosition( + m_position); + transform.rotation = parent->convertWorldToLocalOrientation( + m_orientation); + } else { + transform.position = m_position; + transform.rotation = m_orientation; + } + transform.applyToNode(); + transform.markChanged(); +} + +bool Cursor3D::hitTest(const Ogre::Ray &mouseRay) const +{ + if (!m_cursorNode || !m_visible) + return false; + + // Check if ray passes near cursor center + Ogre::Vector3 toCursor = m_position - mouseRay.getOrigin(); + float tca = toCursor.dotProduct(mouseRay.getDirection()); + if (tca < 0.01f) + return false; + + float d2 = toCursor.dotProduct(toCursor) - tca * tca; + float threshold = 0.3f * m_size; + return d2 <= threshold * threshold; +} + +void Cursor3D::updateNodeTransform() +{ + if (m_cursorNode) { + m_cursorNode->setPosition(m_position); + m_cursorNode->setOrientation(m_orientation); + } +} + +void Cursor3D::createGeometry() +{ + float len = 0.5f * m_size; + float half = 0.05f * m_size; + + // Axes - short lines in RGB + m_axesObj->clear(); + m_axesObj->begin("Ogre/AxisGizmo", + Ogre::RenderOperation::OT_LINE_LIST); + + // X axis - red + m_axesObj->colour(COLOR_RED[0], COLOR_RED[1], COLOR_RED[2]); + m_axesObj->position(0, 0, 0); + m_axesObj->position(len, 0, 0); + + // Y axis - green + m_axesObj->colour(COLOR_GREEN[0], COLOR_GREEN[1], COLOR_GREEN[2]); + m_axesObj->position(0, 0, 0); + m_axesObj->position(0, len, 0); + + // Z axis - blue + m_axesObj->colour(COLOR_BLUE[0], COLOR_BLUE[1], COLOR_BLUE[2]); + m_axesObj->position(0, 0, 0); + m_axesObj->position(0, 0, len); + + m_axesObj->end(); + + // Center marker - small wireframe cube in cyan + m_markerObj->clear(); + m_markerObj->begin("Ogre/AxisGizmo", + Ogre::RenderOperation::OT_LINE_LIST); + m_markerObj->colour(COLOR_CYAN[0], COLOR_CYAN[1], COLOR_CYAN[2]); + + // Cube corners + Ogre::Vector3 corners[8] = { + Ogre::Vector3(-half, -half, -half), + Ogre::Vector3(half, -half, -half), + Ogre::Vector3(half, half, -half), + Ogre::Vector3(-half, half, -half), + Ogre::Vector3(-half, -half, half), + Ogre::Vector3(half, -half, half), + Ogre::Vector3(half, half, half), + Ogre::Vector3(-half, half, half), + }; + + // Bottom face + m_markerObj->position(corners[0]); + m_markerObj->position(corners[1]); + m_markerObj->position(corners[1]); + m_markerObj->position(corners[2]); + m_markerObj->position(corners[2]); + m_markerObj->position(corners[3]); + m_markerObj->position(corners[3]); + m_markerObj->position(corners[0]); + + // Top face + m_markerObj->position(corners[4]); + m_markerObj->position(corners[5]); + m_markerObj->position(corners[5]); + m_markerObj->position(corners[6]); + m_markerObj->position(corners[6]); + m_markerObj->position(corners[7]); + m_markerObj->position(corners[7]); + m_markerObj->position(corners[4]); + + // Vertical edges + m_markerObj->position(corners[0]); + m_markerObj->position(corners[4]); + m_markerObj->position(corners[1]); + m_markerObj->position(corners[5]); + m_markerObj->position(corners[2]); + m_markerObj->position(corners[6]); + m_markerObj->position(corners[3]); + m_markerObj->position(corners[7]); + + m_markerObj->end(); +} diff --git a/src/features/editScene/gizmo/Cursor3D.hpp b/src/features/editScene/gizmo/Cursor3D.hpp new file mode 100644 index 0000000..677c4e8 --- /dev/null +++ b/src/features/editScene/gizmo/Cursor3D.hpp @@ -0,0 +1,88 @@ +#ifndef EDITSCENE_CURSOR3D_HPP +#define EDITSCENE_CURSOR3D_HPP +#pragma once + +#include +#include + +// Forward declarations +struct TransformComponent; + +/** + * 3D Cursor - a visual marker for prefab placement and transform reference + * Shows a small crosshair with axis indicators in world space + */ +class Cursor3D { +public: + Cursor3D(Ogre::SceneManager *sceneMgr); + ~Cursor3D(); + + /** + * Shutdown and cleanup - must be called before SceneManager is destroyed + */ + void shutdown(); + + /** + * Set world position + */ + void setPosition(const Ogre::Vector3 &pos); + Ogre::Vector3 getPosition() const + { + return m_position; + } + + /** + * Set world orientation + */ + void setOrientation(const Ogre::Quaternion &rot); + Ogre::Quaternion getOrientation() const + { + return m_orientation; + } + + /** + * Show/hide cursor + */ + void setVisible(bool visible); + bool isVisible() const; + + /** + * Set cursor size/scale + */ + void setSize(float size); + float getSize() const + { + return m_size; + } + + /** + * Copy position and orientation from a TransformComponent (world space) + */ + void snapToTransform(const TransformComponent &transform); + + /** + * Apply cursor position and orientation to a TransformComponent + */ + void applyToTransform(TransformComponent &transform) const; + + /** + * Simple hit test - returns true if mouse ray passes near cursor center + */ + bool hitTest(const Ogre::Ray &mouseRay) const; + +private: + void createGeometry(); + void updateNodeTransform(); + + Ogre::SceneManager *m_sceneMgr; + Ogre::SceneNode *m_cursorNode; + Ogre::ManualObject *m_axesObj; + Ogre::ManualObject *m_markerObj; + + Ogre::Vector3 m_position; + Ogre::Quaternion m_orientation; + float m_size; + bool m_visible; +}; + +#endif // EDITSCENE_CURSOR3D_HPP diff --git a/src/features/editScene/systems/CharacterSystem.cpp b/src/features/editScene/systems/CharacterSystem.cpp index 461006b..fbb1804 100644 --- a/src/features/editScene/systems/CharacterSystem.cpp +++ b/src/features/editScene/systems/CharacterSystem.cpp @@ -165,6 +165,11 @@ void CharacterSystem::setupEntity(flecs::entity e, CharacterComponent &cc) cc.hasFloor = false; std::cout << "CharacterSystem::setupEntity: entity=" << e.id() << " nodePos=" << transform.node->_getDerivedPosition() + << " parent=" << (transform.node->getParent() ? + transform.node->getParent()->getName() : + "") + << " radius=" << cc.radius << " height=" << cc.height + << " dirty=" << cc.dirty << " hasFloor=" << cc.hasFloor << std::endl; CharacterState state; diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index 3ffceff..089f21f 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -63,6 +63,7 @@ EditorUISystem::EditorUISystem(flecs::world &world, { registerComponentEditors(); m_gizmo = std::make_unique(m_sceneMgr); + m_cursor3D = std::make_unique(m_sceneMgr); m_serializer = std::make_unique(m_world, m_sceneMgr); } @@ -70,17 +71,44 @@ EditorUISystem::~EditorUISystem() = default; void EditorUISystem::shutdown() { - // Shutdown gizmo before SceneManager is destroyed + // Shutdown gizmo and cursor before SceneManager is destroyed if (m_gizmo) { m_gizmo->shutdown(); } + if (m_cursor3D) { + m_cursor3D->shutdown(); + } } bool EditorUISystem::onMousePressed(const Ogre::Ray &mouseRay) { - if (m_gizmo) { - return m_gizmo->onMousePressed(mouseRay); + // Try gizmo first + if (m_gizmo && m_gizmo->onMousePressed(mouseRay)) { + return true; } + + // If cursor placement mode is active, raycast and place cursor + // (skip if ImGui wants the mouse for UI interaction) + if (m_cursor3D && m_cursorPlaceMode && m_physicsSystem && + m_physicsSystem->isInitialized() && + !ImGui::GetIO().WantCaptureMouse) { + JoltPhysicsWrapper *physics = + m_physicsSystem->getPhysicsWrapper(); + if (physics) { + Ogre::Vector3 start = mouseRay.getOrigin(); + Ogre::Vector3 end = + start + mouseRay.getDirection() * 1000.0f; + Ogre::Vector3 hitPos; + JPH::BodyID hitBody; + if (physics->raycastQuery(start, end, hitPos, + hitBody)) { + m_cursor3D->setPosition(hitPos); + m_cursorPlaceMode = false; // disable after placement + return true; + } + } + } + return false; } @@ -250,6 +278,7 @@ void EditorUISystem::update(float deltaTime) } renderPrefabBrowser(); + renderCursorPanel(); // Render FPS overlay renderFPSOverlay(deltaTime); @@ -308,6 +337,14 @@ void EditorUISystem::renderHierarchyWindow() ImGui::EndMenu(); } + // Tools menu + if (ImGui::BeginMenu("Tools")) { + if (ImGui::MenuItem("3D Cursor")) { + m_showCursorPanel = true; + } + ImGui::EndMenu(); + } + // Entity menu if (ImGui::BeginMenu("Entity")) { if (ImGui::MenuItem("New Entity", "Ctrl+N")) { @@ -1582,6 +1619,11 @@ void EditorUISystem::renderPrefabBrowser() if (ImGui::Button("Create Instance")) { // Will show file picker or use selected prefab } + ImGui::SameLine(); + ImGui::Checkbox("Root", &m_prefabInstAtRoot); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip( + "Instantiate at root level (no parent)"); ImGui::Separator(); @@ -1607,20 +1649,72 @@ void EditorUISystem::renderPrefabBrowser() std::string instId = "Inst##" + file; if (ImGui::Button(instId.c_str())) { - Ogre::Vector3 pos(0, 0, 0); - if (m_selectedEntity.is_alive() && - m_selectedEntity.has()) { - pos = m_selectedEntity - .get() - .position + - Ogre::Vector3(2, 0, 0); - } PrefabSystem prefabSys(m_world, m_sceneMgr); flecs::entity parent = - m_selectedEntity.is_alive() - ? m_selectedEntity - : flecs::entity::null(); + flecs::entity::null(); + Ogre::Vector3 pos(0, 0, 0); + + if (m_prefabUseCursor && m_cursor3D && + m_cursor3D->isVisible()) { + // Use cursor position as base + pos = m_cursor3D->getPosition(); + + // Optional downward raycast with margin + if (m_prefabRaycastMargin > 0.0f && + m_physicsSystem && + m_physicsSystem->isInitialized()) { + JoltPhysicsWrapper *physics = + m_physicsSystem + ->getPhysicsWrapper(); + if (physics) { + Ogre::Vector3 start = + pos + Ogre::Vector3( + 0, + m_prefabRaycastMargin, + 0); + Ogre::Vector3 end = + pos + Ogre::Vector3( + 0, + -1000.0f, + 0); + Ogre::Vector3 hitPos; + JPH::BodyID hitBody; + if (physics->raycastQuery( + start, end, + hitPos, + hitBody)) { + pos = hitPos + Ogre::Vector3( + 0, + m_prefabRaycastMargin, + 0); + } + } + } + + // If not at root, parent under selected entity + if (!m_prefabInstAtRoot && + m_selectedEntity.is_alive() && + m_selectedEntity.has< + TransformComponent>()) { + parent = m_selectedEntity; + // Convert world pos to local + auto &pt = m_selectedEntity.get< + TransformComponent>(); + if (pt.node) { + pos = pt.node->convertWorldToLocalPosition( + pos); + } + } + } else if (!m_prefabInstAtRoot && + m_selectedEntity.is_alive() && + m_selectedEntity.has< + TransformComponent>()) { + parent = m_selectedEntity; + // Local offset from parent + pos = Ogre::Vector3(2, 0, 0); + } + auto instance = prefabSys.createInstance( path, parent, pos, file.substr(0, file.find_last_of('.')) @@ -1634,3 +1728,124 @@ void EditorUISystem::renderPrefabBrowser() } ImGui::End(); } + +void EditorUISystem::renderCursorPanel() +{ + if (!m_showCursorPanel) + return; + + if (!m_cursor3D) + return; + + ImGui::SetNextWindowPos( + ImVec2(LEFT_PANEL_WIDTH, 100), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(280, 350), + ImGuiCond_FirstUseEver); + + ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse; + if (ImGui::Begin("3D Cursor", &m_showCursorPanel, flags)) { + // Visibility toggle + bool visible = m_cursor3D->isVisible(); + if (ImGui::Checkbox("Visible", &visible)) { + m_cursor3D->setVisible(visible); + } + + // Placement mode + ImGui::SameLine(); + if (ImGui::Checkbox("Place on Click", &m_cursorPlaceMode)) { + if (m_cursorPlaceMode && !m_cursor3D->isVisible()) + m_cursor3D->setVisible(true); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Click in the 3D viewport to place cursor on surface"); + } + + ImGui::Separator(); + + // Position editor + Ogre::Vector3 pos = m_cursor3D->getPosition(); + float posArr[3] = { pos.x, pos.y, pos.z }; + if (ImGui::DragFloat3("Position", posArr, 0.01f)) { + m_cursor3D->setPosition(Ogre::Vector3( + posArr[0], posArr[1], posArr[2])); + } + + // Rotation editor (Euler angles) + Ogre::Quaternion rot = m_cursor3D->getOrientation(); + float yaw = Ogre::Radian(rot.getYaw()).valueDegrees(); + float pitch = Ogre::Radian(rot.getPitch()).valueDegrees(); + float roll = Ogre::Radian(rot.getRoll()).valueDegrees(); + float rotArr[3] = { yaw, pitch, roll }; + if (ImGui::DragFloat3("Rotation (Y/P/R)", rotArr, 0.5f)) { + Ogre::Quaternion q1(Ogre::Degree(rotArr[0]), + Ogre::Vector3::UNIT_Y); + Ogre::Quaternion q2(Ogre::Degree(rotArr[1]), + Ogre::Vector3::UNIT_X); + Ogre::Quaternion q3(Ogre::Degree(rotArr[2]), + Ogre::Vector3::UNIT_Z); + m_cursor3D->setOrientation(q1 * q2 * q3); + } + + // Size + float size = m_cursor3D->getSize(); + if (ImGui::DragFloat("Size", &size, 0.01f, 0.1f, 10.0f)) { + m_cursor3D->setSize(size); + } + + ImGui::Separator(); + + // Snap to selected entity + if (ImGui::Button("Snap to Selected", ImVec2(120, 0))) { + if (m_selectedEntity.is_alive() && + m_selectedEntity.has()) { + m_cursor3D->snapToTransform( + m_selectedEntity.get()); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Copy selected entity's world position/rotation to cursor"); + } + + ImGui::SameLine(); + + // Apply to selected entity + if (ImGui::Button("Apply to Selected", ImVec2(120, 0))) { + if (m_selectedEntity.is_alive() && + m_selectedEntity.has()) { + auto &transform = + m_selectedEntity.get_mut(); + m_cursor3D->applyToTransform(transform); + if (m_selectedEntity.has< + StaticGeometryMemberComponent>()) { + m_selectedEntity.get_mut< + StaticGeometryMemberComponent>() + .markDirty(); + } + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Copy cursor position/rotation to selected entity"); + } + + ImGui::Separator(); + + // Prefab placement settings + ImGui::Text("Prefab Placement"); + ImGui::Checkbox("Use Cursor Position", &m_prefabUseCursor); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Place prefabs at cursor position instead of selected entity"); + } + ImGui::DragFloat("Raycast Margin", &m_prefabRaycastMargin, + 0.01f, 0.0f, 10.0f); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Vertical offset: raycast from cursor+margin downward, " + "place prefab at hit point + margin"); + } + } + ImGui::End(); +} diff --git a/src/features/editScene/systems/EditorUISystem.hpp b/src/features/editScene/systems/EditorUISystem.hpp index 76cae55..d811d8b 100644 --- a/src/features/editScene/systems/EditorUISystem.hpp +++ b/src/features/editScene/systems/EditorUISystem.hpp @@ -9,6 +9,7 @@ #include "../ui/ComponentRegistry.hpp" #include "../components/EntityName.hpp" #include "../gizmo/Gizmo.hpp" +#include "../gizmo/Cursor3D.hpp" #include "SceneSerializer.hpp" // Forward declarations @@ -92,6 +93,14 @@ public: return m_gizmo.get(); } + /** + * Get the 3D cursor for external interaction + */ + Cursor3D *getCursor3D() const + { + return m_cursor3D.get(); + } + /** * Shutdown UI system - must be called before SceneManager destruction */ @@ -171,6 +180,7 @@ public: */ void showCreatePrefabDialog(flecs::entity entity); void renderPrefabBrowser(); + void renderCursorPanel(); private: // File menu @@ -209,6 +219,7 @@ private: ComponentRegistry m_componentRegistry; std::vector m_allEntities; std::unique_ptr m_gizmo; + std::unique_ptr m_cursor3D; std::unique_ptr m_serializer; // Settings @@ -255,6 +266,13 @@ private: bool m_showPrefabBrowser = false; std::vector m_prefabFiles; bool m_refreshPrefabList = true; + bool m_prefabInstAtRoot = false; + bool m_prefabUseCursor = true; // Use 3D cursor for prefab placement + float m_prefabRaycastMargin = 0.0f; // Vertical margin for raycast placement + + // 3D Cursor state + bool m_showCursorPanel = false; + bool m_cursorPlaceMode = false; // Click in viewport to place cursor // Queries flecs::query m_nameQuery; diff --git a/src/features/editScene/systems/PrefabSystem.cpp b/src/features/editScene/systems/PrefabSystem.cpp index bc93c1a..df2980c 100644 --- a/src/features/editScene/systems/PrefabSystem.cpp +++ b/src/features/editScene/systems/PrefabSystem.cpp @@ -46,13 +46,53 @@ flecs::entity PrefabSystem::createInstance(const std::string &prefabPath, const std::string &name, EditorUISystem *uiSystem) { + // Read prefab root transform so we can preserve it as the base + Ogre::Vector3 prefabPos(0, 0, 0); + Ogre::Quaternion prefabRot(Ogre::Quaternion::IDENTITY); + Ogre::Vector3 prefabScale(1, 1, 1); + try { + std::ifstream file(prefabPath); + if (file.is_open()) { + nlohmann::json prefabJson; + file >> prefabJson; + file.close(); + if (prefabJson.contains("transform")) { + auto &t = prefabJson["transform"]; + if (t.contains("position")) { + auto &p = t["position"]; + prefabPos = Ogre::Vector3( + p.value("x", 0.0f), + p.value("y", 0.0f), + p.value("z", 0.0f)); + } + if (t.contains("rotation")) { + auto &r = t["rotation"]; + prefabRot = Ogre::Quaternion( + r.value("w", 1.0f), + r.value("x", 0.0f), + r.value("y", 0.0f), + r.value("z", 0.0f)); + } + if (t.contains("scale")) { + auto &s = t["scale"]; + prefabScale = Ogre::Vector3( + s.value("x", 1.0f), + s.value("y", 1.0f), + s.value("z", 1.0f)); + } + } + } + } catch (...) { + // Ignore read errors — defaults are safe + } + flecs::entity instance = m_world.entity(); instance.add(); instance.set(EntityNameComponent(name)); instance.set( PrefabInstanceComponent{ prefabPath, false }); - // Create transform + // Create transform: caller offset + prefab root transform TransformComponent transform; Ogre::SceneNode *parentNode = m_sceneMgr->getRootSceneNode(); if (parent.is_valid() && parent != 0 && @@ -61,12 +101,28 @@ flecs::entity PrefabSystem::createInstance(const std::string &prefabPath, instance.child_of(parent); } transform.node = parentNode->createChildSceneNode(); - transform.position = position; - transform.rotation = Ogre::Quaternion::IDENTITY; - transform.scale = Ogre::Vector3::UNIT_SCALE; + transform.position = position + prefabPos; + transform.rotation = prefabRot; + transform.scale = prefabScale; transform.applyToNode(); instance.set(transform); + Ogre::LogManager::getSingleton().logMessage( + "PrefabSystem::createInstance: name=" + name + + " offset=" + Ogre::StringConverter::toString(position) + + " prefabPos=" + Ogre::StringConverter::toString(prefabPos) + + " finalPos=" + + Ogre::StringConverter::toString( + transform.node->_getDerivedPosition()) + + " parent=" + + (parent.is_valid() && parent != 0 ? + std::to_string(parent.id()) : + "") + + " nodeParent=" + + (parentNode != m_sceneMgr->getRootSceneNode() ? + parentNode->getName() : + "")); + if (uiSystem) uiSystem->addEntity(instance); diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index 6693bb9..f217189 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -795,7 +795,7 @@ void SceneSerializer::deserializeEntityComponents( flecs::entity child = m_world.entity(); if (addEditorMarker) child.add(); - if (json.contains("id")) { + if (childJson.contains("id")) { uint64_t cid = childJson.value("id", 0ULL); if (cid) m_entityMap[cid] = child;