Prefab placement at cursor

This commit is contained in:
2026-04-26 20:51:20 +03:00
parent 425bb8411d
commit 7563937ab8
8 changed files with 655 additions and 19 deletions

View File

@@ -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
)

View File

@@ -0,0 +1,252 @@
#include "Cursor3D.hpp"
#include "../components/Transform.hpp"
#include <cmath>
// 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<Ogre::SceneNode*>(
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();
}

View File

@@ -0,0 +1,88 @@
#ifndef EDITSCENE_CURSOR3D_HPP
#define EDITSCENE_CURSOR3D_HPP
#pragma once
#include <Ogre.h>
#include <OgreManualObject.h>
// 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

View File

@@ -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() :
"<root>")
<< " radius=" << cc.radius << " height=" << cc.height
<< " dirty=" << cc.dirty << " hasFloor=" << cc.hasFloor
<< std::endl;
CharacterState state;

View File

@@ -63,6 +63,7 @@ EditorUISystem::EditorUISystem(flecs::world &world,
{
registerComponentEditors();
m_gizmo = std::make_unique<Gizmo>(m_sceneMgr);
m_cursor3D = std::make_unique<Cursor3D>(m_sceneMgr);
m_serializer = std::make_unique<SceneSerializer>(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<TransformComponent>()) {
pos = m_selectedEntity
.get<TransformComponent>()
.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<TransformComponent>()) {
m_cursor3D->snapToTransform(
m_selectedEntity.get<TransformComponent>());
}
}
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<TransformComponent>()) {
auto &transform =
m_selectedEntity.get_mut<TransformComponent>();
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();
}

View File

@@ -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<flecs::entity> m_allEntities;
std::unique_ptr<Gizmo> m_gizmo;
std::unique_ptr<Cursor3D> m_cursor3D;
std::unique_ptr<SceneSerializer> m_serializer;
// Settings
@@ -255,6 +266,13 @@ private:
bool m_showPrefabBrowser = false;
std::vector<std::string> 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<EntityNameComponent> m_nameQuery;

View File

@@ -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<EditorMarkerComponent>();
instance.set<EntityNameComponent>(EntityNameComponent(name));
instance.set<PrefabInstanceComponent>(
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<TransformComponent>(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()) :
"<root>") +
" nodeParent=" +
(parentNode != m_sceneMgr->getRootSceneNode() ?
parentNode->getName() :
"<root>"));
if (uiSystem)
uiSystem->addEntity(instance);

View File

@@ -795,7 +795,7 @@ void SceneSerializer::deserializeEntityComponents(
flecs::entity child = m_world.entity();
if (addEditorMarker)
child.add<EditorMarkerComponent>();
if (json.contains("id")) {
if (childJson.contains("id")) {
uint64_t cid = childJson.value("id", 0ULL);
if (cid)
m_entityMap[cid] = child;