Undo/Redo system, hierarchy for models in scene, uuid for basic_nodes, menu bar 'windows' options

This commit is contained in:
Daniu
2026-02-23 21:31:31 +01:00
parent 01bcadff8c
commit 23783892f0
12 changed files with 975 additions and 454 deletions

View File

@@ -25,12 +25,12 @@ void
editorkeyboard_input::default_bindings() {
m_bindingsetups = {
{ user_command::moveleft, {GLFW_KEY_LEFT, "Move left"} },
{ user_command::moveright, {GLFW_KEY_RIGHT, "Move right"} },
{ user_command::moveforward, {GLFW_KEY_UP, "Move forwards"} },
{ user_command::moveback, {GLFW_KEY_DOWN, "Move backwards"} },
{ user_command::moveup, {GLFW_KEY_PAGE_UP, "Move up"} },
{ user_command::movedown, {GLFW_KEY_PAGE_DOWN, "Move down"} },
{ user_command::moveleft, {GLFW_KEY_A, "Move left"} },
{ user_command::moveright, {GLFW_KEY_D, "Move right"} },
{ user_command::moveforward, {GLFW_KEY_W, "Move forwards"} },
{ user_command::moveback, {GLFW_KEY_S, "Move backwards"} },
{ user_command::moveup, {GLFW_KEY_E, "Move up"} },
{ user_command::movedown, {GLFW_KEY_Q, "Move down"} },
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,12 @@ class editor_mode : public application_mode
}
void on_event_poll() override;
bool is_command_processor() const override;
void undo_last();
static bool focus_active();
static void set_focus_active(bool isActive);
static TCamera& get_camera() { return Camera; }
static bool change_history() { return m_change_history; }
static void set_change_history(bool enabled) { m_change_history = enabled; }
private:
// types
struct editormode_input
@@ -65,6 +70,25 @@ class editor_mode : public application_mode
bool freefly;
bool picking;
};
struct EditorSnapshot
{
enum class Action { Move, Rotate, Add, Delete, Other };
Action action{Action::Other};
std::string node_name; // node identifier (basic_node::name())
// direct pointer to node when available; used for in-memory undo/redo lookup
scene::basic_node *node_ptr{nullptr};
std::string serialized; // full text for recreate (used for Add/Delete)
glm::dvec3 position{0.0, 0.0, 0.0};
glm::vec3 rotation{0.0f, 0.0f, 0.0f};
UID uuid; // node UUID for reference, used as fallback lookup for deleted/recreated nodes
};
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
std::vector<EditorSnapshot> m_history; // history of changes to nodes, used for undo functionality
std::vector<EditorSnapshot> g_redo;
// methods
void update_camera(double const Deltatime);
bool mode_translation() const;
@@ -73,15 +97,50 @@ class editor_mode : public application_mode
bool mode_rotationX() const;
bool mode_rotationZ() const;
bool mode_snap() const;
editor_ui *ui() const;
void redo_last();
void handle_brush_mouse_hold(int Action, int Button);
void apply_rotation_for_new_node(scene::basic_node *node, int rotation_mode, float fixed_rotation_value);
// members
state_backup m_statebackup; // helper, cached variables to be restored on mode exit
editormode_input m_input;
TCamera Camera;
static TCamera Camera;
// focus (smooth camera fly-to) state
static bool m_focus_active;
glm::dvec3 m_focus_start_pos{0.0,0.0,0.0};
glm::dvec3 m_focus_target_pos{0.0,0.0,0.0};
glm::dvec3 m_focus_start_lookat{0.0,0.0,0.0};
glm::dvec3 m_focus_target_lookat{0.0,0.0,0.0};
double m_focus_time{0.0};
double m_focus_duration{0.6};
double fTime50Hz{0.0}; // bufor czasu dla komunikacji z PoKeys
scene::basic_editor m_editor;
scene::basic_node *m_node; // currently selected scene node
bool m_takesnapshot{true}; // helper, hints whether snapshot of selected node(s) should be taken before modification
bool m_dragging = false;
glm::vec3 oldPos;
glm::dvec3 oldPos;
bool mouseHold{false};
float kMaxPlacementDistance = 200.0f;
static bool m_change_history;
// UI/history settings
int m_max_history_size{200};
int m_selected_history_idx{-1};
glm::dvec3 clamp_mouse_offset_to_max(const glm::dvec3 &offset);
// focus camera smoothly on specified node
void start_focus(scene::basic_node *node, double duration = 0.6);
// hierarchy management
void add_to_hierarchy(scene::basic_node *node);
void remove_from_hierarchy(scene::basic_node *node);
scene::basic_node* find_in_hierarchy(const std::string &uuid_str);
scene::basic_node* find_node_by_any(scene::basic_node *node_ptr, const std::string &uuid_str, const std::string &name);
// clear history/redo pointers that reference the given node (prevent dangling pointers)
void nullify_history_pointers(scene::basic_node *node);
void render_change_history();
};

View File

@@ -61,9 +61,9 @@ void itemproperties_panel::update(scene::basic_node const *Node)
}
}
*/
textline = "name: " + (node->name().empty() ? "(none)" : Bezogonkow(node->name())) + "\nlocation: [" + to_string(node->location().x, 2) + ", " + to_string(node->location().y, 2) + ", " +
textline = "name: " + (node->name().empty() ? "(none)" : Bezogonkow(node->name())) + "\ntype: " + node->node_type + "\nlocation: [" + to_string(node->location().x, 2) + ", " + to_string(node->location().y, 2) + ", " +
to_string(node->location().z, 2) + "]" +
" (distance: " + to_string(glm::length(glm::dvec3{node->location().x, 0.0, node->location().z} - glm::dvec3{camera.Pos.x, 0.0, camera.Pos.z}), 1) + " m)";
" (distance: " + to_string(glm::length(glm::dvec3{node->location().x, 0.0, node->location().z} - glm::dvec3{camera.Pos.x, 0.0, camera.Pos.z}), 1) + " m)" + "\nUUID: " + node->uuid.to_string();
text_lines.emplace_back(textline, Global.UITextColor);
// subclass-specific data

View File

@@ -27,7 +27,8 @@ namespace scene {
std::string const EU07_FILEEXTENSION_REGION { ".sbt" };
std::uint32_t const EU07_FILEHEADER { MAKE_ID4( 'E','U','0','7' ) };
std::uint32_t const EU07_FILEVERSION_REGION { MAKE_ID4( 'S', 'B', 'T', '2' ) };
std::map<std::string, basic_node *> Hierarchy;
// potentially activates event handler with the same name as provided node, and within handler activation range
void
basic_cell::on_click( TAnimModel const *Instance ) {
@@ -678,9 +679,6 @@ basic_cell::enclose_area( scene::basic_node *Node ) {
m_area.radius,
static_cast<float>( glm::length( m_area.center - Node->location() ) + Node->radius() ) );
}
// potentially activates event handler with the same name as provided node, and within handler activation range
void
basic_section::on_click( TAnimModel const *Instance ) {

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include <array>
#include <stack>
#include <unordered_set>
#include <map>
#include "parser.h"
#include "geometrybank.h"
@@ -455,6 +456,8 @@ public:
};
// global hierarchy map for scene nodes
extern std::map<std::string, basic_node *> Hierarchy;
} // scene
//---------------------------------------------------------------------------

View File

@@ -725,6 +725,8 @@ memory_node::deserialize( cParser &Input, node_data const &Nodedata ) {
basic_node::basic_node( scene::node_data const &Nodedata ) :
m_name( Nodedata.name )
{
uuid = UID::random();
node_type = Nodedata.type;
m_rangesquaredmin = Nodedata.range_min * Nodedata.range_min;
m_rangesquaredmax = (
Nodedata.range_max >= 0.0 ?

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "material.h"
#include "vertex.h"
#include "geometrybank.h"
#include "utils/uuid.hpp"
struct lighting_data {
@@ -352,6 +353,8 @@ public:
bool
dirty() const { return m_dirty; }
std::string node_type;
public:
// members
scene::group_handle m_group { null_handle }; // group this node belongs to, if any
@@ -361,6 +364,7 @@ public:
bool m_visible { true }; // visibility flag
std::string m_name;
bool m_dirty { false };
UID uuid;
private:
// methods
@@ -386,7 +390,6 @@ std::string basic_node::tooltip() const
{
return m_name;
}
inline
void
basic_node::location( glm::dvec3 const Location ) {

View File

@@ -550,6 +550,10 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
}
scene::Groups.insert( scene::Groups.handle(), instance );
simulation::Region->insert( instance );
scene::basic_node *hierarchy_node = instance;
if (hierarchy_node)
{ scene::Hierarchy[hierarchy_node->uuid.to_string()] = hierarchy_node;
}
}
}
else if( ( nodedata.type == "triangles" )

View File

@@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulation.h"
#include "translation.h"
#include "application.h"
#include "editormode.h"
#include "imgui/imgui_impl_glfw.h"
@@ -381,7 +382,7 @@ void ui_layer::render()
render_tooltip();
render_menu();
render_quit_widget();
render_hierarchy();
// template method implementation
render_();
@@ -423,6 +424,53 @@ void ui_layer::render_quit_widget()
ImGui::End();
}
void ui_layer::render_hierarchy(){
if(!m_editor_hierarchy)
return;
ImGui::SetNextWindowSize(ImVec2(0, 0));
ImGui::Begin(STR_C("Scene Hierarchy"), &m_editor_hierarchy, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("Registered nodes: %zu", scene::Hierarchy.size());
ImGui::BeginChild("hierarchy_list", ImVec2(500, 300), true);
for (auto &entry : scene::Hierarchy)
{
const std::string &uuid = entry.first;
scene::basic_node *node = entry.second;
if (node)
{
char buf[512];
std::snprintf(buf, sizeof(buf), "%s | %s (%.1f, %.1f, %.1f)",
node->name().c_str(),
uuid.c_str(),
node->location().x,
node->location().y,
node->location().z);
if (ImGui::Selectable(buf, false))
{
// Focus camera on selected node
auto const node_pos = node->location();
auto const camera_offset = glm::dvec3(0.0, 10.0, -20.0);
editor_mode::set_focus_active(false);
TCamera &camera = editor_mode::get_camera();
camera.Pos = node_pos + camera_offset;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("UUID: %s\nNode type: %s", uuid.c_str(), typeid(*node).name());
}
}
}
ImGui::EndChild();
ImGui::End();
}
void ui_layer::set_cursor(int const Mode)
{
glfwSetInputMode(m_window, GLFW_CURSOR, Mode);
@@ -522,6 +570,14 @@ void ui_layer::render_menu_contents()
GfxRenderer->Debug_Ui_State(ret);
}
if(EditorModeFlag){
ImGui::MenuItem("Hierarchy", nullptr, &m_editor_hierarchy);
bool change_history_enabled = editor_mode::change_history();
if (ImGui::MenuItem("Change History", nullptr, &change_history_enabled))
{
editor_mode::set_change_history(change_history_enabled);
}
}
ImGui::EndMenu();
}
}

View File

@@ -147,6 +147,7 @@ protected:
void render_panels();
void render_menu();
void render_quit_widget();
void render_hierarchy();
// draws a quad between coordinates x,y and z,w with uv-coordinates spanning 0-1
void quad( glm::vec4 const &Coordinates, glm::vec4 const &Color );
// members
@@ -158,4 +159,6 @@ protected:
std::string m_tooltip;
bool m_quit_active = false;
bool m_imgui_demo = false;
bool m_editor_hierarchy = false;
bool m_editor_change_history = false;
};

69
utils/uuid.hpp Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include <array>
#include <random>
#include <sstream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <cstdint>
class UID {
public:
std::array<uint8_t,16> bytes;
static UID random() {
static thread_local std::mt19937_64 gen(std::random_device{}());
UID u;
uint64_t a = gen();
uint64_t b = gen();
for (int i = 0; i < 8; ++i) u.bytes[i] = uint8_t((a >> (i*8)) & 0xFF);
for (int i = 0; i < 8; ++i) u.bytes[8 + i] = uint8_t((b >> (i*8)) & 0xFF);
u.bytes[6] = (u.bytes[6] & 0x0F) | 0x40;
u.bytes[8] = (u.bytes[8] & 0x3F) | 0x80;
return u;
}
std::string to_string() const {
std::ostringstream os;
os << std::hex << std::setfill('0');
auto put = [&](int i){
os << std::setw(2) << static_cast<int>(bytes[i]);
};
// format 8-4-4-4-12
for (int i = 0; i < 4; ++i) put(i);
for (int i = 4; i < 6; ++i) put(i);
os << '-';
for (int i = 6; i < 8; ++i) put(i);
os << '-';
for (int i = 8; i < 10; ++i) put(i);
os << '-';
for (int i = 10; i < 12; ++i) put(i);
os << '-';
for (int i = 12; i < 16; ++i) put(i);
return os.str();
}
static UID from_string(const std::string& str) {
std::istringstream is(str);
is >> std::hex;
UID u;
for (int i = 0; i < 16; ++i) {
int byte;
is >> byte;
u.bytes[i] = static_cast<uint8_t>(byte);
if (is.peek() == '-') is.get();
}
return u;
}
bool operator==(const UID &other) const noexcept {
return bytes == other.bytes;
}
bool operator!=(const UID &other) const noexcept {
return !(*this == other);
}
};