17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 00:29:19 +02:00
Files
maszyna/application/editormodeselection.cpp

363 lines
12 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// more models selected at once, the clipboard, groups and the menu at the cursor of the scenery editor
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editoruilayer.h"
#include "application/editorprojection.h"
#include "editor/editorFormat.hpp"
#include "model/AnimModel.h"
#include "rendering/renderer.h"
#include "scene/scenelayers.h"
#include "scene/scenenodegroups.h"
#include "simulation/simulation.h"
#include "utilities/Globals.h"
#include "utilities/translation.h"
#include "imgui/imgui.h"
#include <glm/gtx/rotate_vector.hpp>
#include <algorithm>
#include <map>
namespace
{
// the models of the selection can be grouped, copied and dropped onto the ground; tracks and other nodes are left out
TAnimModel *as_model(scene::basic_node *Node)
{
return Node != nullptr ? dynamic_cast<TAnimModel *>(Node) : nullptr;
}
} // namespace
std::vector<scene::basic_node *> editor_mode::selected_nodes() const
{
std::vector<scene::basic_node *> nodes;
if (m_node == nullptr)
return nodes;
nodes.push_back(m_node);
// the selection of more models is a thing of the surroundings
if (ui()->mode() != nodebank_panel::MODIFY)
return nodes;
for (auto *node : m_selection)
if (node != nullptr && std::find(nodes.begin(), nodes.end(), node) == nodes.end())
nodes.push_back(node);
return nodes;
}
std::vector<scene::basic_node *> editor_mode::selection_with_groups() const
{
auto nodes{selected_nodes()};
auto const count{nodes.size()};
for (std::size_t index = 0; index < count; ++index)
{
// NOTE: the groups up to the first one stand for no group, see scene::basic_editor
if (nodes[index]->group() <= 1)
continue;
for (auto *member : scene::Groups.group(nodes[index]->group()).nodes)
if (std::find(nodes.begin(), nodes.end(), member) == nodes.end())
nodes.push_back(member);
}
return nodes;
}
void editor_mode::select_node(scene::basic_node *Node, bool const Additive)
{
if (false == Additive)
{
m_selection.clear();
m_node = Node;
ui()->set_node(m_node);
return;
}
if (Node == nullptr)
return;
if (m_node == nullptr)
{
m_node = Node;
}
else if (Node == m_node)
{
// taken out; the one selected before it takes the gizmo
if (m_selection.empty())
{
m_node = nullptr;
}
else
{
m_node = m_selection.back();
m_selection.pop_back();
}
}
else if (auto const found{std::find(m_selection.begin(), m_selection.end(), Node)}; found != m_selection.end())
{
m_selection.erase(found);
}
else
{
// the model selected last gets the gizmo
m_selection.push_back(m_node);
m_node = Node;
}
ui()->set_node(m_node);
auto const count{selected_nodes().size()};
if (count > 1)
ui()->set_status(format(STR_C("%zu models selected; Shift+LMB adds or takes one out"), count));
}
void editor_mode::draw_selection_overlay()
{
if (ui()->mode() != nodebank_panel::MODIFY || m_node == nullptr)
return;
auto const nodes{selection_with_groups()};
if (nodes.size() < 2)
return;
// a ring around each model of the selection, the one with the gizmo brighter, the models of their groups dimmer
screen_projection const projection;
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
auto const direct{selected_nodes()};
for (auto *node : nodes)
{
ImVec2 point;
if (false == projection.project(node->location(), point))
continue;
auto const chosen{std::find(direct.begin(), direct.end(), node) != direct.end()};
auto const color{node == m_node ? overlay_color::highlight : chosen ? overlay_color::selected : overlay_color::marked};
drawlist->AddCircle(point, node == m_node ? 9.0f : 7.0f, color, 16, 2.0f);
}
}
void editor_mode::selection_snapshots(EditorSnapshot::Action const Action)
{
// one step of the history for all of them
auto joined{false};
for (auto *node : selection_with_groups())
{
push_snapshot(node, Action);
if (false == m_history.empty())
m_history.back().joined = joined;
joined = true;
}
}
void editor_mode::selection_follow(glm::dvec3 const &Moved, glm::vec3 const &Turned, glm::dvec3 const &Pivot)
{
// a group is moved by any of its models, once; the one of m_node is moved with it already
std::vector<scene::group_handle> moved;
if (m_node != nullptr && m_node->group() > 1)
moved.push_back(m_node->group());
for (auto *node : selected_nodes())
{
if (node == m_node)
continue;
if (node->group() > 1)
{
if (std::find(moved.begin(), moved.end(), node->group()) != moved.end())
continue;
moved.push_back(node->group());
}
if (Turned != glm::vec3(0.0f))
{
// turned in place, then carried around the turning point
m_editor.rotate(node, Turned, 0.0f);
auto const location{Pivot + glm::rotateY(node->location() - Pivot, glm::radians<double>(Turned.y))};
m_editor.translate(node, location, true);
}
if (Moved != glm::dvec3(0.0))
m_editor.translate(node, node->location() + Moved, true);
}
}
glm::dvec3 editor_mode::cursor_ground()
{
return placement_on_ground(Camera.Pos + clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position()));
}
void editor_mode::copy_selection()
{
auto const nodes{selection_with_groups()};
auto const *anchor{as_model(m_node)};
if (anchor == nullptr)
{
ui()->set_status(STR("Select a model to copy"), true);
return;
}
m_clipboard.clear();
std::map<scene::group_handle, int> groups;
for (auto *node : nodes)
{
auto *model{as_model(node)};
if (model == nullptr)
continue;
clipboard_entry entry;
model->export_as_text(entry.definition);
entry.offset = model->location() - anchor->location();
entry.angles = model->Angles();
entry.scale = model->Scale();
if (model->group() > 1)
entry.group = groups.emplace(model->group(), static_cast<int>(groups.size())).first->second;
m_clipboard.push_back(std::move(entry));
}
ui()->set_status(format(STR_C("%zu models copied; Ctrl+V puts them at the cursor"), m_clipboard.size()));
}
void editor_mode::paste_clipboard(glm::dvec3 const &Location)
{
if (m_clipboard.empty())
{
ui()->set_status(STR("Nothing copied yet; Ctrl+C copies the selected models"), true);
return;
}
// one step of the history, the way an array is: the copies are taken out together
EditorSnapshot snap;
snap.action = EditorSnapshot::Action::Array;
std::vector<TAnimModel *> made;
std::map<int, std::vector<scene::basic_node *>> groups;
for (auto const &entry : m_clipboard)
{
// NOTE: no names for the copies, same as with the brush and the array
auto *model{simulation::State.create_model(entry.definition, std::string{}, Location + entry.offset)};
if (model == nullptr)
continue;
model->location(Location + entry.offset);
model->Angles(entry.angles);
model->Scale(entry.scale);
add_to_hierarchy(model);
EditorSnapshot::array_copy copy;
copy.model = model;
copy.uuid = model->uuid;
snap.copies.push_back(std::move(copy));
snap.layer = model->layer();
made.push_back(model);
if (entry.group >= 0)
groups[entry.group].push_back(model);
}
if (made.empty())
return;
m_history.push_back(std::move(snap));
g_redo.clear();
for (auto const &group : groups)
if (group.second.size() > 1)
scene::Groups.make(group.second);
// the copies are the selection now
m_selection.assign(made.begin() + 1, made.end());
m_node = made.front();
ui()->set_node(m_node);
ui()->set_status(format(STR_C("%zu models pasted; Ctrl+Z takes them back"), made.size()));
}
void editor_mode::group_selection()
{
auto nodes{selection_with_groups()};
nodes.erase(std::remove_if(nodes.begin(), nodes.end(), [](scene::basic_node *Node) { return as_model(Node) == nullptr; }), nodes.end());
if (nodes.size() < 2)
{
ui()->set_status(STR("Select two models or more to group them: Shift+LMB adds one to the selection"), true);
return;
}
for (auto *node : nodes)
{
std::string reason;
if (node->from_template() || false == scene::Layers.editable(node, &reason))
{
ui()->set_status("\"" + (node->name().empty() ? std::string{"(unnamed node)"} : node->name()) + "\" can't be grouped: " + reason, true);
return;
}
}
// a group is written to the scenery as one block, in the layer of the model with the gizmo
for (auto *node : nodes)
if (node->layer() != m_node->layer())
scene::Layers.move(node, m_node->layer());
scene::Groups.make(nodes);
m_selection.clear();
ui()->set_status(format(STR_C("%zu models grouped: a click on one selects them all; Ctrl+Shift+G takes the group apart"), nodes.size()));
}
void editor_mode::ungroup_selection()
{
std::vector<scene::group_handle> groups;
for (auto *node : selected_nodes())
{
if (node->group() <= 1 || std::find(groups.begin(), groups.end(), node->group()) != groups.end())
continue;
// the groups of the scenery templates are their includes, they can't be taken apart here
auto const &members{scene::Groups.group(node->group()).nodes};
if (std::any_of(members.begin(), members.end(), [](scene::basic_node const *Member) { return Member->from_template(); }))
continue;
groups.push_back(node->group());
}
if (groups.empty())
{
ui()->set_status(STR("The selected model isn't in a group"), true);
return;
}
for (auto const group : groups)
scene::Groups.dissolve(group);
ui()->set_status(format(STR_C("%zu groups taken apart"), groups.size()));
}
void editor_mode::drop_selection_to_ground()
{
// each model by itself, a group follows the ground under its models
auto joined{false};
for (auto *node : selection_with_groups())
{
auto const size{m_history.size()};
snap_to_ground(node, true, joined);
joined = joined || m_history.size() != size;
}
}
void editor_mode::render_context_menu()
{
if (m_contextopen)
{
ImGui::OpenPopup("##editorcontext");
m_contextopen = false;
}
if (false == ImGui::BeginPopup("##editorcontext"))
return;
auto const selected{m_node != nullptr};
auto const model{as_model(m_node) != nullptr};
auto const count{selected_nodes().size()};
if (count > 1)
ImGui::TextDisabled(STR_C("%zu models selected"), count);
else if (selected)
ImGui::TextDisabled("%s", m_node->name().empty() ? "(unnamed node)" : m_node->name().c_str());
else
ImGui::TextDisabled("%s", STR_C("Nothing selected"));
ImGui::Separator();
auto const item = [](char const *Label, char const *Keys, bool const Enabled, char const *Tooltip) {
auto const chosen{ImGui::MenuItem(Label, Keys, false, Enabled)};
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
ImGui::SetTooltip("%s", Tooltip);
return chosen;
};
if (item(STR_C("Copy"), "Ctrl+C", model, STR_C("Copies the selected models with their groups")))
copy_selection();
if (item(STR_C("Paste here"), "Ctrl+V", false == m_clipboard.empty(), STR_C("Puts the copied models here, around this point as they were around the selected one")))
paste_clipboard(m_contextpoint);
ImGui::Separator();
if (item(STR_C("Group"), "Ctrl+G", model && count > 1, STR_C("The selected models move together from now on; a click on one selects them all")))
group_selection();
if (item(STR_C("Ungroup"), "Ctrl+Shift+G", selected && m_node->group() > 1, STR_C("The models of the group move each by itself again")))
ungroup_selection();
if (item(STR_C("Drop to the ground"), STR_C("End"), selected, STR_C("Puts each selected model, also each model of their groups, down onto the terrain or the object under it")))
drop_selection_to_ground();
ImGui::Separator();
if (item(STR_C("Fly to the selected"), "F", selected, STR_C("The camera flies to the selected model and looks at it")))
start_focus(m_node, 0.6);
if (item(STR_C("Delete"), STR_C("Del"), selected, STR_C("Deletes the selected models")))
delete_selected();
ImGui::EndPopup();
}