diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index 502013c4..f8206b3b 100755 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -5931,29 +5931,85 @@ void MapView::ShowContextMenu(QPoint pos) // TODO QAction action_group("Group Selected Objects TODO", this); menu->addAction(&action_group); - action_group.setEnabled(_world->has_multiple_model_selected() && true); // TODO, some "notgrouped" condition - QObject::connect(&action_snap, &QAction::triggered, [=]() + // check if all selected objects are already grouped + bool groupable = false; + if ( _world->has_multiple_model_selected()) + { + // if there's no existing groups, that means it's always groupable + if (!_world->_selection_groups.size()) + groupable = true; + + if (!groupable) { - _world->add_object_group(); - - //auto selected_objects = _world->get_selected_objects(); - //for (auto selected_obj : selected_objects) - //{ - // - //} + // check if there's any ungrouped object + for (auto obj : _world->get_selected_objects()) + { + bool obj_ungrouped = true; + for (auto& group : _world->_selection_groups) + { + if (group.contains_object(obj)) + obj_ungrouped = false; + } + if (obj_ungrouped) + { + groupable = true; + break; + } + } + } + } + action_group.setEnabled(groupable); + QObject::connect(&action_group, &QAction::triggered, [=]() + { + // remove all groups the objects are already in and create a new one + // for (auto obj : _world->get_selected_objects()) + // { + // for (auto& group : _world->_selection_groups) + // { + // if (group.contains_object(obj)) + // { + // group.remove_group(); + // } + // } + // } + for (auto& group : _world->_selection_groups) + { + if (group.isSelected()) + { + group.remove_group(); + } + } + _world->add_object_group_from_selection(); }); QAction action_ungroup("Ungroup Selected Objects TODO", this); - - + menu->addAction(&action_ungroup); + bool group_selected = false; + for (auto& group : _world->_selection_groups) + { + if (group.isSelected()) + { + group_selected = true; + break; + } + } + action_ungroup.setEnabled(group_selected); + QObject::connect(&action_ungroup, &QAction::triggered, [=]() + { + for (auto& group : _world->_selection_groups) + { + if (group.isSelected()) + { + group.remove_group(); + } + } + }); menu->exec(mapToGlobal(pos)); // synch - // menu->popup(mapToGlobal(pos)); // asynch + // menu->popup(mapToGlobal(pos)); // asynch, needs to be preloaded to work }; - - } \ No newline at end of file diff --git a/src/noggit/ModelInstance.cpp b/src/noggit/ModelInstance.cpp index 3a075212..06f98787 100755 --- a/src/noggit/ModelInstance.cpp +++ b/src/noggit/ModelInstance.cpp @@ -73,10 +73,11 @@ void ModelInstance::draw_box (glm::mat4x4 const& model_view } else { + const glm::vec4 color = _grouped ? glm::vec4(0.5f, 0.5f, 1.0f, 0.5f) : glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); Noggit::Rendering::Primitives::WireBox::getInstance(_context).draw ( model_view , projection , transformMatrix() - , {0.5f, 0.5f, 0.5f, 1.0f} + , color , misc::transform_model_box_coords(model->header.bounding_box_min) , misc::transform_model_box_coords(model->header.bounding_box_max) ); diff --git a/src/noggit/SceneObject.hpp b/src/noggit/SceneObject.hpp index 4c7dd052..617aaa6a 100755 --- a/src/noggit/SceneObject.hpp +++ b/src/noggit/SceneObject.hpp @@ -72,6 +72,8 @@ public: glm::vec3 const getServerPos() { return glm::vec3(ZEROPOINT - pos.z, ZEROPOINT - pos.x, pos.y); } + bool _grouped = false; + public: glm::vec3 pos; std::array extents; diff --git a/src/noggit/Selection.cpp b/src/noggit/Selection.cpp index 06008ce9..453591aa 100755 --- a/src/noggit/Selection.cpp +++ b/src/noggit/Selection.cpp @@ -77,20 +77,22 @@ void selected_chunk_type::updateDetails(Noggit::Ui::detail_infos* detail_widget) detail_widget->setText(select_info.str()); } -selection_group::selection_group(std::vector selected_objects, World* world) +selection_group::selection_group(std::vector selected_objects, World* world) : _world(world) { - _object_count = selected_objects.size(); + // _object_count = selected_objects.size(); - if (!_object_count) + if (!selected_objects.size()) return; + _is_selected = true; // default group extents to first obj _group_extents = selected_objects.front()->getExtents(); _members_uid.reserve(selected_objects.size()); for (auto& selected_obj : selected_objects) { + selected_obj->_grouped = true; _members_uid.push_back(selected_obj->uid); if (selected_obj->getExtents()[0].x < _group_extents[0].x) @@ -109,7 +111,32 @@ selection_group::selection_group(std::vector selected_obje } } -bool selection_group::group_contains_object(selected_object_type object) +void selection_group::remove_member(unsigned int object_uid) +{ + if (_members_uid.size() == 1) + { + remove_group(); + return; + } + + for (auto it = _members_uid.begin(); it != _members_uid.end(); ++it) + { + auto member_uid = *it; + std::optional obj = _world->get_model(member_uid); + if (!obj) + continue; + SceneObject* instance = std::get(obj.value()); + + if (instance->uid == object_uid) + { + _members_uid.erase(it); + instance->_grouped = false; + return; + } + } +} + +bool selection_group::contains_object(SceneObject* object) { for (unsigned int member_uid : _members_uid) { @@ -134,8 +161,9 @@ void selection_group::select_group() continue; _world->add_to_selection(obj.value(), true); - } + + _is_selected = true; } void selection_group::unselect_group() @@ -143,29 +171,35 @@ void selection_group::unselect_group() for (unsigned int obj_uid : _members_uid) { // don't need to check if it's not selected - _world->remove_from_selection(obj_uid); - - /* - std::optional obj = _world->get_model(obj_uid); - if (!obj) - continue; - - SceneObject* instance = std::get(obj.value()); - - if (_world->is_selected(instance)) - _world->remove_from_selection(obj.value()); - */ + _world->remove_from_selection(obj_uid, true); } + + _is_selected = false; } -void selection_group::move_group() +// only remove the group, not used to delete objects in it +void selection_group::remove_group() { + // TODO remove group from storage and json - // _world->select_objects_in_area + _world->remove_selection_group(this); + + // remvoe grouped attribute + for (auto it = _members_uid.begin(); it != _members_uid.end(); ++it) + { + auto member_uid = *it; + std::optional obj = _world->get_model(member_uid); + if (!obj) + continue; + SceneObject* instance = std::get(obj.value()); + + instance->_grouped = false; + } } void selection_group::recalcExtents() { + bool first_obj = true; for (unsigned int obj_uid : _members_uid) { std::optional obj = _world->get_model(obj_uid); @@ -174,6 +208,13 @@ void selection_group::recalcExtents() SceneObject* instance = std::get(obj.value()); + if (first_obj) + { + _group_extents = instance->getExtents(); + first_obj = false; + continue; + } + // min = glm::min(min, point); if (instance->getExtents()[0].x < _group_extents[0].x) _group_extents[0].x = instance->extents[0].x; diff --git a/src/noggit/Selection.h b/src/noggit/Selection.h index 4f44eacc..a5d58eb9 100755 --- a/src/noggit/Selection.h +++ b/src/noggit/Selection.h @@ -57,40 +57,51 @@ enum eSelectionEntryTypes class selection_group { public: - selection_group(std::vector selected_objects, World* world); + selection_group(std::vector selected_objects, World* world); + // selection_group(std::vector objects_uids, World* world); + // void set_selected_as_group(std::vector selection); + + void remove_group(); + + void add_member(SceneObject* object); + void remove_member(unsigned int object_uid); + + bool contains_object(SceneObject* object); - void add_member(selected_object_type object); - bool group_contains_object(selected_object_type object); void select_group(); void unselect_group(); - // void set_selected_as_group(std::vector selection); + void recalcExtents(); + // void copy_group(); // create and save a new selection group from copied objects - void copy_group(); // create and save a new selection group from copied objects - - void move_group(); - void scale_group(); - void rotate_group(); + // void move_group(); + // void scale_group(); + // void rotate_group(); std::vector const& getObjects() const { return _members_uid; } [[nodiscard]] std::array const& getExtents() { return _group_extents; } // ensureExtents(); -private: - void recalcExtents(); + bool isSelected() const { return _is_selected; } + void setUnselected() { _is_selected = false; } +private: std::vector _members_uid; // uids + bool _is_selected = false; + // std::vector _object_members; std::array _group_extents; - unsigned int _object_count = 0; + // unsigned int _object_count = 0; World* _world; + + // bool _need_recalc_extents = false; }; using selection_entry = std::pair; diff --git a/src/noggit/WMOInstance.cpp b/src/noggit/WMOInstance.cpp index fdd34796..7abcea74 100755 --- a/src/noggit/WMOInstance.cpp +++ b/src/noggit/WMOInstance.cpp @@ -110,7 +110,7 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader , model_view , projection , _transform_mat - , is_selected + , is_selected && !_grouped , frustum , cull_distance , camera @@ -125,10 +125,10 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader if (force_box || is_selected) { - //gl.enable(GL_BLEND); - //gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.enable(GL_BLEND); + gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glm::vec4 color = force_box ? glm::vec4(0.0f, 0.0f, 1.0f, 1.0f) + glm::vec4 color = force_box || _grouped ? glm::vec4(0.5f, 0.5f, 1.0f, 0.5f) : glm::vec4(0.0f, 1.0f, 0.0f, 1.0f); Noggit::Rendering::Primitives::WireBox::getInstance(_context).draw(model_view diff --git a/src/noggit/World.cpp b/src/noggit/World.cpp index a992932e..78654286 100755 --- a/src/noggit/World.cpp +++ b/src/noggit/World.cpp @@ -363,6 +363,8 @@ void World::rotate_selected_models_randomly(float minX, float maxX, float minY, void World::rotate_selected_models_to_ground_normal(bool smoothNormals) { ZoneScoped; + if (!_selected_model_count) + return; for (auto& entry : _current_selection) { auto type = entry.index(); @@ -500,16 +502,14 @@ void World::rotate_selected_models_to_ground_normal(bool smoothNormals) double cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z); updateTilesEntry(entry, model_update::add); } + update_selected_model_groups(); } void World::set_current_selection(selection_type entry) { ZoneScoped; - _current_selection.clear(); - _current_selection.push_back(entry); - _multi_select_pivot = std::nullopt; - - _selected_model_count = entry.index() != eEntry_Object ? 0 : 1; + reset_selection(); + add_to_selection(entry); } void World::add_to_selection(selection_type entry, bool skip_group) @@ -525,7 +525,7 @@ void World::add_to_selection(selection_type entry, bool skip_group) auto obj = std::get(entry); for (auto& group : _selection_groups) { - if (group.group_contains_object(obj)) + if (group.contains_object(obj)) { // this then calls add_to_selection() with skip_group = true to avoid repetition group.select_group(); @@ -538,7 +538,16 @@ void World::add_to_selection(selection_type entry, bool skip_group) update_selection_pivot(); } -void World::remove_from_selection(selection_type entry) +void World::remove_selection_group(selection_group* group) +{ + for (auto it = _selection_groups.begin(); it != _selection_groups.end(); ++it) + { + _selection_groups.erase(it); + return; + } +} + +void World::remove_from_selection(selection_type entry, bool skip_group) { ZoneScoped; std::vector::iterator position = std::find(_current_selection.begin(), _current_selection.end(), entry); @@ -547,6 +556,21 @@ void World::remove_from_selection(selection_type entry) if (entry.index() == eEntry_Object) { _selected_model_count--; + + // check if it is in a group + if (!skip_group) + { + auto obj = std::get(entry); + for (auto& group : _selection_groups) + { + if (group.contains_object(obj)) + { + // this then calls remove_from_selection() with skip_group = true to avoid repetition + group.unselect_group(); + break; + } + } + } } _current_selection.erase(position); @@ -554,7 +578,7 @@ void World::remove_from_selection(selection_type entry) } } -void World::remove_from_selection(std::uint32_t uid) +void World::remove_from_selection(std::uint32_t uid, bool skip_group) { ZoneScoped; for (auto it = _current_selection.begin(); it != _current_selection.end(); ++it) @@ -564,18 +588,30 @@ void World::remove_from_selection(std::uint32_t uid) auto obj = std::get(*it); - if (obj->which() == eMODEL && static_cast(obj)->uid == uid) + if (obj->uid == uid) { - _current_selection.erase(it); - update_selection_pivot(); - return; - } - else if (obj->which() == eWMO && static_cast(obj)->uid == uid) - { - _current_selection.erase(it); - update_selection_pivot(); - return; + _selected_model_count--; + _current_selection.erase(it); + + // check if it is in a group + if (!skip_group) + { + for (auto& group : _selection_groups) + { + if (group.contains_object(obj)) + { + // this then calls remove_from_selection() with skip_group = true to avoid repetition + group.unselect_group(); + break; + } + } + } + + update_selection_pivot(); + return; } + + } } @@ -585,11 +621,28 @@ void World::reset_selection() _current_selection.clear(); _multi_select_pivot = std::nullopt; _selected_model_count = 0; + + for (auto& selection_group : _selection_groups) + { + selection_group.setUnselected(); + } } void World::delete_selected_models() { ZoneScoped; + if (!_selected_model_count) + return; + + // erase selected groups as well + for (auto& group : _selection_groups) + { + if (group.isSelected()) + { + group.remove_group(); + } + } + _model_instance_storage.delete_instances(_current_selection); need_model_updates = true; reset_selection(); @@ -626,6 +679,8 @@ glm::vec3 World::get_ground_height(glm::vec3 pos) void World::snap_selected_models_to_the_ground() { ZoneScoped; + if (!_selected_model_count) + return; for (auto& entry : _current_selection) { auto type = entry.index(); @@ -647,11 +702,14 @@ void World::snap_selected_models_to_the_ground() } update_selection_pivot(); + update_selected_model_groups(); } void World::scale_selected_models(float v, m2_scaling_type type) { ZoneScoped; + if (!_selected_model_count) + return; for (auto& entry : _current_selection) { if (entry.index() == eEntry_Object) @@ -692,11 +750,14 @@ void World::scale_selected_models(float v, m2_scaling_type type) updateTilesModel(mi, model_update::add); } } + update_selected_model_groups(); } void World::move_selected_models(float dx, float dy, float dz) { ZoneScoped; + if (!_selected_model_count) + return; for (auto& entry : _current_selection) { auto type = entry.index(); @@ -721,6 +782,7 @@ void World::move_selected_models(float dx, float dy, float dz) } update_selection_pivot(); + update_selected_model_groups(); } void World::move_model(selection_type entry, float dx, float dy, float dz) @@ -751,6 +813,8 @@ void World::move_model(selection_type entry, float dx, float dy, float dz) void World::set_selected_models_pos(glm::vec3 const& pos, bool change_height) { ZoneScoped; + if (!_selected_model_count) + return; // move models relative to the pivot when several are selected if (has_multiple_model_selected()) { @@ -787,6 +851,7 @@ void World::set_selected_models_pos(glm::vec3 const& pos, bool change_height) } update_selection_pivot(); + update_selected_model_groups(); } void World::set_model_pos(selection_type entry, glm::vec3 const& pos, bool change_height) @@ -811,6 +876,9 @@ void World::set_model_pos(selection_type entry, glm::vec3 const& pos, bool chang void World::rotate_selected_models(math::degrees rx, math::degrees ry, math::degrees rz, bool use_pivot) { ZoneScoped; + if (!_selected_model_count) + return; + math::degrees::vec3 dir_change(rx._, ry._, rz._); bool has_multi_select = has_multiple_model_selected(); @@ -848,17 +916,21 @@ void World::rotate_selected_models(math::degrees rx, math::degrees ry, math::deg updateTilesEntry(entry, model_update::add); } + update_selected_model_groups(); } void World::set_selected_models_rotation(math::degrees rx, math::degrees ry, math::degrees rz) { ZoneScoped; + if (!_selected_model_count) + return; + math::degrees::vec3 new_dir(rx._, ry._, rz._); for (auto& entry : _current_selection) { auto type = entry.index(); - if (type == eEntry_MapChunk) + if (type != eEntry_Object) { continue; } @@ -876,8 +948,17 @@ void World::set_selected_models_rotation(math::degrees rx, math::degrees ry, mat updateTilesEntry(entry, model_update::add); } + update_selected_model_groups(); } +void World::update_selected_model_groups() +{ + for (auto& selection_group : _selection_groups) + { + if (selection_group.isSelected()) + selection_group.recalcExtents(); + } +} MapChunk* World::getChunkAt(glm::vec3 const& pos) { @@ -3390,9 +3471,9 @@ void World::select_objects_in_area( } } -void World::add_object_group() +void World::add_object_group_from_selection() { - // auto selected_objects = get_selected_objects(); + // create group from selecetd objects selection_group selection_group(get_selected_objects(), this); _selection_groups.push_back(selection_group); diff --git a/src/noggit/World.h b/src/noggit/World.h index b8127dce..1331847e 100755 --- a/src/noggit/World.h +++ b/src/noggit/World.h @@ -61,9 +61,9 @@ protected: // std::unordered_map> _models_by_filename; Noggit::world_model_instances_storage _model_instance_storage; Noggit::world_tile_update_queue _tile_update_queue; +public: std::vector _selection_groups; -public: MapIndex mapIndex; Noggit::map_horizon horizon; @@ -132,8 +132,8 @@ public: // std::unordered_map> get_models_by_filename() const& { return _models_by_filename; } void set_current_selection(selection_type entry); void add_to_selection(selection_type entry, bool skip_group = false); - void remove_from_selection(selection_type entry); - void remove_from_selection(std::uint32_t uid); + void remove_from_selection(selection_type entry, bool skip_group = false); + void remove_from_selection(std::uint32_t uid, bool skip_group = false); void reset_selection(); void delete_selected_models(); glm::vec3 get_ground_height(glm::vec3 pos); @@ -165,6 +165,8 @@ public: void rotate_selected_models_randomly(float minX, float maxX, float minY, float maxY, float minZ, float maxZ); void set_selected_models_rotation(math::degrees rx, math::degrees ry, math::degrees rz); + void update_selected_model_groups(); + // Checks the normal of the terrain on model origin and rotates to that spot. void rotate_selected_models_to_ground_normal(bool smoothNormals); @@ -390,8 +392,8 @@ public: glm::vec3 camera_position ); - void add_object_group(); - void delete_object_group(); + void add_object_group_from_selection(); + void remove_selection_group(selection_group* group); protected: // void update_models_by_filename(); diff --git a/src/noggit/rendering/WorldRender.cpp b/src/noggit/rendering/WorldRender.cpp index 97716c32..b502a40b 100755 --- a/src/noggit/rendering/WorldRender.cpp +++ b/src/noggit/rendering/WorldRender.cpp @@ -766,14 +766,46 @@ void WorldRender::draw (glm::mat4x4 const& model_view continue; auto model = static_cast(obj); + + + if (model->isInFrustum(frustum) && model->isInRenderDist(_cull_distance, camera_pos, display)) { - model->draw_box(model_view, projection, false); // make optional! + bool is_selected = false; + /* + auto id = model->uid; + bool const is_selected = _world->current_selection().size() > 0 && + std::find_if(_world->current_selection().begin(), _world->current_selection().end(), + [id](selection_type type) + { + return var_type(type) == typeid(selected_object_type) + && std::get(type)->which() == SceneObjectTypes::eMODEL + && static_cast(std::get(type))->uid == id; + }) != _world->current_selection().end();*/ + + model->draw_box(model_view, projection, is_selected); // make optional! } } } } + // render selection group boxes + for (auto& selection_group : _world->_selection_groups) + { + if (!selection_group.isSelected()) + continue; + + glm::mat4x4 identity_mtx = glm::mat4x4{ 1 }; + auto& extents = selection_group.getExtents(); + Noggit::Rendering::Primitives::WireBox::getInstance(_world->_context).draw(model_view + , projection + , identity_mtx + , { 0.0f, 0.0f, 1.0f, 1.0f } // blue + , extents[0] + , extents[1] + ); + } + // set anim time only once per frame { OpenGL::Scoped::use_program water_shader {*_liquid_program.get()}; diff --git a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp index 66cddd10..8328d769 100755 --- a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp +++ b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp @@ -315,5 +315,7 @@ void ViewportGizmo::handleTransformGizmo(MapView* map_view _world->updateTilesEntry(selected, model_update::add); } } + + _world->update_selected_model_groups(); } diff --git a/src/noggit/world_model_instances_storage.cpp b/src/noggit/world_model_instances_storage.cpp index 3b25a0c8..4a17b29e 100755 --- a/src/noggit/world_model_instances_storage.cpp +++ b/src/noggit/world_model_instances_storage.cpp @@ -189,6 +189,14 @@ namespace Noggit _world->updateTilesEntry(instance.value(), model_update::remove); auto obj = std::get(instance.value()); + for (auto& selection_group : _world->_selection_groups) + { + if (selection_group.contains_object(obj)) + { + selection_group.remove_member(obj->uid); + } + } + if (NOGGIT_CUR_ACTION) { NOGGIT_CUR_ACTION->registerObjectRemoved(obj);