optimize objects selection and rendering
This commit is contained in:
@@ -69,6 +69,7 @@
|
||||
#include <noggit/tools/ScriptingTool.hpp>
|
||||
#include <noggit/tools/ChunkTool.hpp>
|
||||
#include <noggit/StringHash.hpp>
|
||||
#include <noggit/application/NoggitApplication.hpp>
|
||||
|
||||
#ifdef USE_MYSQL_UID_STORAGE
|
||||
#include <mysql/mysql.h>
|
||||
|
||||
@@ -48,6 +48,7 @@ void ModelInstance::draw_box (glm::mat4x4 const& model_view
|
||||
|
||||
if (is_current_selection)
|
||||
{
|
||||
// draw collision box
|
||||
Noggit::Rendering::Primitives::WireBox::getInstance(_context).draw ( model_view
|
||||
, projection
|
||||
, transformMatrix()
|
||||
@@ -56,6 +57,7 @@ void ModelInstance::draw_box (glm::mat4x4 const& model_view
|
||||
, misc::transform_model_box_coords(model->header.collision_box_max)
|
||||
);
|
||||
|
||||
// draw bounding box
|
||||
Noggit::Rendering::Primitives::WireBox::getInstance(_context).draw ( model_view
|
||||
, projection
|
||||
, transformMatrix()
|
||||
@@ -64,6 +66,7 @@ void ModelInstance::draw_box (glm::mat4x4 const& model_view
|
||||
, misc::transform_model_box_coords(model->header.bounding_box_max)
|
||||
);
|
||||
|
||||
// draw extents
|
||||
Noggit::Rendering::Primitives::WireBox::getInstance(_context).draw ( model_view
|
||||
, projection
|
||||
, glm::mat4x4(1)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <sstream>
|
||||
|
||||
|
||||
selected_chunk_type::selected_chunk_type(MapChunk* _chunk, std::tuple<int, int, int> _triangle, glm::vec3 _position)
|
||||
selected_chunk_type::selected_chunk_type(MapChunk* _chunk, const std::tuple<int, int, int>& _triangle, const glm::vec3& _position)
|
||||
: chunk(_chunk)
|
||||
, triangle(_triangle)
|
||||
, position(_position)
|
||||
@@ -228,10 +228,10 @@ void selected_chunk_type::updateDetails(Noggit::Ui::detail_infos* detail_widget)
|
||||
detail_widget->setText(select_info.str());
|
||||
}
|
||||
|
||||
selection_group::selection_group(std::vector<SceneObject*> selected_objects, World* world)
|
||||
selection_group::selection_group(const std::vector<SceneObject*>& selected_objects, World* world)
|
||||
: _world(world)
|
||||
{
|
||||
if (!selected_objects.size())
|
||||
if (selected_objects.empty())
|
||||
return;
|
||||
|
||||
// _is_selected = true;
|
||||
@@ -246,14 +246,16 @@ selection_group::selection_group(std::vector<SceneObject*> selected_objects, Wor
|
||||
// save_json();
|
||||
}
|
||||
|
||||
selection_group::selection_group(std::vector<unsigned int> objects_uids, World* world)
|
||||
// only called when initializing world before objects are loaded, so can't set selected_obj->_grouped
|
||||
selection_group::selection_group(const std::vector<unsigned int>& objects_uids, World* world)
|
||||
: _world(world)
|
||||
{
|
||||
if (!objects_uids.size())
|
||||
if (objects_uids.empty())
|
||||
return;
|
||||
|
||||
// _is_selected = true;
|
||||
_members_uid = objects_uids;
|
||||
// std::unordered_set<unsigned int> _members_uid(objects_uids.begin(), objects_uids.end());
|
||||
|
||||
recalcExtents();
|
||||
// save_json();
|
||||
@@ -314,9 +316,6 @@ void selection_group::select_group()
|
||||
|
||||
instance->_grouped = true; // ensure grouped attribute, some models could still be unloaded when creating the group
|
||||
|
||||
if (_world->is_selected(instance))
|
||||
continue;
|
||||
|
||||
_world->add_to_selection(obj.value(), true, false);
|
||||
}
|
||||
_world->update_selection_pivot();
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
|
||||
struct selected_chunk_type : Selectable
|
||||
{
|
||||
selected_chunk_type(MapChunk* _chunk, std::tuple<int, int, int> _triangle, glm::vec3 _position);
|
||||
selected_chunk_type(MapChunk* _chunk, const std::tuple<int, int, int>& _triangle, const glm::vec3& _position);
|
||||
// : chunk(_chunk)
|
||||
// , triangle(_triangle)
|
||||
// , position(_position)
|
||||
@@ -61,8 +61,8 @@ enum eSelectionEntryTypes
|
||||
class selection_group
|
||||
{
|
||||
public:
|
||||
selection_group(std::vector<SceneObject*> selected_objects, World* world);
|
||||
selection_group(std::vector<unsigned int> objects_uids, World* world);
|
||||
selection_group(const std::vector<SceneObject*>& selected_objects, World* world);
|
||||
selection_group(const std::vector<unsigned int>& objects_uids, World* world);
|
||||
|
||||
// ~selection_group();
|
||||
|
||||
@@ -71,13 +71,11 @@ public:
|
||||
|
||||
void remove_group(bool save = true);
|
||||
|
||||
void add_member(SceneObject* object);
|
||||
// void add_member(SceneObject* object);
|
||||
void remove_member(unsigned int object_uid);
|
||||
|
||||
bool contains_object(SceneObject* object);
|
||||
|
||||
|
||||
|
||||
void select_group();
|
||||
void unselect_group();
|
||||
|
||||
@@ -99,7 +97,8 @@ public:
|
||||
bool _is_selected = false;
|
||||
|
||||
private:
|
||||
std::vector<unsigned int> _members_uid; // uids
|
||||
std::vector<unsigned int> _members_uid;
|
||||
// std::unordered_set<unsigned int> _members_uid;
|
||||
// std::vector<SceneObject*> _object_members;
|
||||
|
||||
std::array<glm::vec3, 2> _group_extents;
|
||||
|
||||
@@ -587,6 +587,7 @@ bool Skies::draw(glm::mat4x4 const& model_view
|
||||
, math::frustum const& frustum
|
||||
, const float& cull_distance
|
||||
, int animtime
|
||||
, int time
|
||||
/*, bool draw_particles*/
|
||||
, bool draw_skybox
|
||||
, OutdoorLightStats const& light_stats
|
||||
|
||||
@@ -330,6 +330,7 @@ public:
|
||||
, math::frustum const& frustum
|
||||
, const float& cull_distance
|
||||
, int animtime
|
||||
, int time
|
||||
/*, bool draw_particles*/
|
||||
, bool draw_skybox
|
||||
, OutdoorLightStats const& light_stats
|
||||
|
||||
@@ -53,15 +53,15 @@ WMOInstance::WMOInstance(BlizzardArchive::Listfile::FileKey const& file_key, Nog
|
||||
|
||||
|
||||
void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader
|
||||
, glm::mat4x4 const& model_view
|
||||
, glm::mat4x4 const& projection
|
||||
, const glm::mat4x4 const& model_view
|
||||
, const glm::mat4x4 const& projection
|
||||
, math::frustum const& frustum
|
||||
, const float& cull_distance
|
||||
, const glm::vec3& camera
|
||||
, bool force_box
|
||||
, bool draw_doodads
|
||||
, bool draw_fog
|
||||
, std::vector<selection_type> selection
|
||||
, bool is_selected
|
||||
, int animtime
|
||||
, bool world_has_skies
|
||||
, display_mode display
|
||||
@@ -76,16 +76,6 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader
|
||||
|
||||
ensureExtents();
|
||||
|
||||
const uint id = this->uid;
|
||||
bool const is_selected = selection.size() > 0 &&
|
||||
std::find_if(selection.begin(), selection.end(),
|
||||
[id](selection_type type)
|
||||
{
|
||||
return var_type(type) == typeid(selected_object_type)
|
||||
&& std::get<selected_object_type>(type)->which() == SceneObjectTypes::eWMO
|
||||
&& static_cast<WMOInstance*>(std::get<selected_object_type>(type))->uid == id;
|
||||
}) != selection.end();
|
||||
|
||||
{
|
||||
unsigned region_visible = 0;
|
||||
|
||||
|
||||
@@ -87,15 +87,15 @@ public:
|
||||
}
|
||||
|
||||
void draw ( OpenGL::Scoped::use_program& wmo_shader
|
||||
, glm::mat4x4 const& model_view
|
||||
, glm::mat4x4 const& projection
|
||||
, const glm::mat4x4 const& model_view
|
||||
, const glm::mat4x4 const& projection
|
||||
, math::frustum const& frustum
|
||||
, const float& cull_distance
|
||||
, const glm::vec3& camera
|
||||
, bool force_box
|
||||
, bool draw_doodads
|
||||
, bool draw_fog
|
||||
, std::vector<selection_type> selection
|
||||
, bool is_selected
|
||||
, int animtime
|
||||
, bool world_has_skies
|
||||
, display_mode display
|
||||
@@ -106,6 +106,7 @@ public:
|
||||
void intersect (math::ray const&, selection_result*, bool do_exterior = true);
|
||||
|
||||
std::array<glm::vec3, 2> const& getExtents() override;
|
||||
bool extentsDirty() { return _need_recalc_extents || !wmo->finishedLoading(); };
|
||||
void recalcExtents() override;
|
||||
void change_nameset(uint16_t name_set);
|
||||
void ensureExtents() override;
|
||||
|
||||
@@ -178,12 +178,13 @@ void World::update_selection_pivot()
|
||||
}
|
||||
}
|
||||
|
||||
bool World::is_selected(selection_type selection) const
|
||||
bool World::is_selected(selection_type selection)
|
||||
{
|
||||
ZoneScoped;
|
||||
if (selection.index() != eEntry_Object)
|
||||
return false;
|
||||
|
||||
/*
|
||||
auto which = std::get<selected_object_type>(selection)->which();
|
||||
|
||||
if (which == eMODEL)
|
||||
@@ -222,43 +223,42 @@ bool World::is_selected(selection_type selection) const
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
*/
|
||||
|
||||
auto selected_object = std::get<selected_object_type>(selection);
|
||||
unsigned int uid = selected_object->uid;
|
||||
|
||||
bool found = selected_uids.contains(uid);
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
// verify object type
|
||||
// probably should only be done when adding or removing objects.
|
||||
/*
|
||||
auto instance = getObjectInstance(uid);
|
||||
if (instance == nullptr || var_type(instance) != typeid(selected_object_type))
|
||||
return false;
|
||||
|
||||
if (selected_object->which() != instance->which())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
bool World::is_selected(std::uint32_t uid) const
|
||||
{
|
||||
ZoneScoped;
|
||||
for (selection_type const& entry : _current_selection)
|
||||
{
|
||||
if (entry.index() != eEntry_Object)
|
||||
continue;
|
||||
|
||||
auto obj = std::get<selected_object_type>(entry);
|
||||
|
||||
if (obj->which() == eWMO)
|
||||
{
|
||||
if (static_cast<WMOInstance*>(obj)->uid == uid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (obj->which() == eMODEL)
|
||||
{
|
||||
if (static_cast<ModelInstance*>(obj)->uid == uid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return selected_uids.contains(uid);
|
||||
}
|
||||
|
||||
std::optional<selection_type> World::get_last_selected_model() const
|
||||
{
|
||||
ZoneScoped;
|
||||
if (_current_selection.empty())
|
||||
return std::optional<selection_type>();
|
||||
return std::nullopt;
|
||||
|
||||
auto const it
|
||||
( std::find_if ( _current_selection.rbegin()
|
||||
@@ -561,17 +561,27 @@ void World::set_current_selection(selection_type entry)
|
||||
}
|
||||
|
||||
// updating pivot is expensive, in mass selection situation, it should only be updated once after operation is done
|
||||
void World::add_to_selection(selection_type entry, bool skip_group, bool update_pivot)
|
||||
// now checks if model is already selected, don't need to call is_selected anymore !
|
||||
bool World::add_to_selection(selection_type entry, bool skip_group, bool update_pivot)
|
||||
{
|
||||
ZoneScoped;
|
||||
if (entry.index() == eEntry_Object)
|
||||
{
|
||||
_selected_model_count++;
|
||||
|
||||
auto obj = std::get<selected_object_type>(entry);
|
||||
|
||||
auto result = selected_uids.insert(obj->uid);
|
||||
|
||||
if (!result.second)
|
||||
{
|
||||
// Duplicate existed
|
||||
return false;
|
||||
}
|
||||
|
||||
_selected_model_count++;
|
||||
// check if it is in a group
|
||||
if (!skip_group)
|
||||
{
|
||||
auto obj = std::get<selected_object_type>(entry);
|
||||
for (auto& group : _selection_groups)
|
||||
{
|
||||
if (group.contains_object(obj))
|
||||
@@ -580,7 +590,7 @@ void World::add_to_selection(selection_type entry, bool skip_group, bool update_
|
||||
_current_selection.push_back(entry);
|
||||
// this then calls add_to_selection() with skip_group = true to avoid repetition
|
||||
group.select_group();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,24 +599,33 @@ void World::add_to_selection(selection_type entry, bool skip_group, bool update_
|
||||
|
||||
if (update_pivot)
|
||||
update_selection_pivot();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void World::remove_from_selection(selection_type entry, bool skip_group, bool update_pivot)
|
||||
{
|
||||
ZoneScoped;
|
||||
if (entry.index() == eEntry_Object)
|
||||
{
|
||||
auto obj = std::get<selected_object_type>(entry);
|
||||
size_t erased_count = selected_uids.erase(obj->uid);
|
||||
if (erased_count == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<selection_type>::iterator position = std::find(_current_selection.begin(), _current_selection.end(), entry);
|
||||
if (position != _current_selection.end())
|
||||
{
|
||||
if (entry.index() == eEntry_Object)
|
||||
{
|
||||
_selected_model_count--;
|
||||
|
||||
// check if it is in a group
|
||||
if (!skip_group)
|
||||
{
|
||||
auto obj = std::get<selected_object_type>(entry);
|
||||
for (auto& group : _selection_groups)
|
||||
{
|
||||
auto obj = std::get<selected_object_type>(entry);
|
||||
if (group.contains_object(obj))
|
||||
{
|
||||
// this then calls remove_from_selection() with skip_group = true to avoid repetition
|
||||
@@ -626,6 +645,10 @@ void World::remove_from_selection(selection_type entry, bool skip_group, bool up
|
||||
void World::remove_from_selection(std::uint32_t uid, bool skip_group, bool update_pivot)
|
||||
{
|
||||
ZoneScoped;
|
||||
size_t erased_count = selected_uids.erase(uid);
|
||||
if (erased_count == 0)
|
||||
return;
|
||||
|
||||
for (auto it = _current_selection.begin(); it != _current_selection.end(); ++it)
|
||||
{
|
||||
if (it->index() != eEntry_Object)
|
||||
@@ -635,34 +658,34 @@ void World::remove_from_selection(std::uint32_t uid, bool skip_group, bool updat
|
||||
|
||||
if (obj->uid == uid)
|
||||
{
|
||||
_selected_model_count--;
|
||||
_current_selection.erase(it);
|
||||
_selected_model_count--;
|
||||
_current_selection.erase(it);
|
||||
|
||||
// check if it is in a group
|
||||
if (!skip_group)
|
||||
// check if it is in a group
|
||||
if (!skip_group)
|
||||
{
|
||||
for (auto& group : _selection_groups)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (group.contains_object(obj))
|
||||
{
|
||||
// this then calls remove_from_selection() with skip_group = true to avoid repetition
|
||||
group.unselect_group();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (update_pivot)
|
||||
update_selection_pivot();
|
||||
return;
|
||||
}
|
||||
if (update_pivot)
|
||||
update_selection_pivot();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void World::reset_selection()
|
||||
{
|
||||
ZoneScoped;
|
||||
selected_uids.clear();
|
||||
_current_selection.clear();
|
||||
_multi_select_pivot = std::nullopt;
|
||||
_selected_model_count = 0;
|
||||
@@ -3038,15 +3061,14 @@ void World::range_add_to_selection(glm::vec3 const& pos, float radius, bool remo
|
||||
|
||||
for (auto obj : objects_in_range)
|
||||
{
|
||||
if (remove)
|
||||
{
|
||||
remove_from_selection(obj, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_selected(obj))
|
||||
add_to_selection(obj, false, false);
|
||||
}
|
||||
if (remove)
|
||||
{
|
||||
remove_from_selection(obj, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
update_selection_pivot();
|
||||
}
|
||||
@@ -3587,228 +3609,222 @@ void World::notifyTileRendererOnSelectedTextureChange()
|
||||
}
|
||||
|
||||
void World::select_objects_in_area(
|
||||
const std::array<glm::vec2, 2>& selection_box,
|
||||
bool reset_selection,
|
||||
const glm::mat4x4& view,
|
||||
const glm::mat4x4& projection,
|
||||
int viewport_width,
|
||||
int viewport_height,
|
||||
float user_depth,
|
||||
const glm::vec3& camera_position)
|
||||
const std::array<glm::vec2, 2>& selection_box,
|
||||
bool reset_selection,
|
||||
const glm::mat4x4& view,
|
||||
const glm::mat4x4& projection,
|
||||
int viewport_width,
|
||||
int viewport_height,
|
||||
float user_depth,
|
||||
const glm::vec3& camera_position)
|
||||
{
|
||||
ZoneScoped;
|
||||
|
||||
if (reset_selection)
|
||||
ZoneScoped;
|
||||
|
||||
if (reset_selection)
|
||||
{
|
||||
this->reset_selection();
|
||||
}
|
||||
|
||||
glm::mat4 VPmatrix = projection * view;
|
||||
|
||||
constexpr int max_position_raycast_processing = 10000;
|
||||
constexpr int max_bounds_raycast_processing = 5000; // when selecting large amount of objects, avoid doing complex ray calculations to not freeze
|
||||
constexpr float bounds_check_scale = 0.8f; // size of the bounding box to use when interesecting withs election rectangle
|
||||
constexpr float obj_raycast_min_size = 30.0f; // screen size rectangle lenght in pixels
|
||||
|
||||
int processed_obj_count = 0;
|
||||
// int debug_count_obj_min_size = 0;
|
||||
// int debug_count_obj_min_size_not = 0;
|
||||
|
||||
for (auto& map_object : _loaded_tiles_buffer)
|
||||
{
|
||||
MapTile* tile = map_object.second;
|
||||
|
||||
if (!tile)
|
||||
{
|
||||
this->reset_selection();
|
||||
break;
|
||||
}
|
||||
|
||||
glm::mat4 VPmatrix = projection * view;
|
||||
|
||||
constexpr int max_position_raycast_processing = 10000;
|
||||
constexpr int max_bounds_raycast_processing = 5000; // when selecting large amount of objects, avoid doing complex ray calculations to not freeze
|
||||
constexpr float bounds_check_scale = 0.8f; // size of the bounding box to use when interesecting withs election rectangle
|
||||
constexpr float obj_raycast_min_size = 30.0f; // screen size rectangle lenght in pixels
|
||||
|
||||
int processed_obj_count = 0;
|
||||
// int debug_count_obj_min_size = 0;
|
||||
// int debug_count_obj_min_size_not = 0;
|
||||
|
||||
for (auto& map_object : _loaded_tiles_buffer)
|
||||
// some optimizations to see if the tile is in selection before iterating objects in it
|
||||
{
|
||||
MapTile* tile = map_object.second;
|
||||
// tile not in screen, skip
|
||||
// frustum.intersects(tile_extents[1], tile_extents[0])
|
||||
if (!tile->_was_rendered_last_frame)
|
||||
continue;
|
||||
|
||||
if (!tile)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// some optimizations to see if the tile is in selection before iterating objects in it
|
||||
{
|
||||
// tile not in screen, skip
|
||||
// frustum.intersects(tile_extents[1], tile_extents[0])
|
||||
if (!tile->_was_rendered_last_frame)
|
||||
continue;
|
||||
|
||||
// check if tile combined extents are within selection rectangle
|
||||
// note very useful because cases where a tile is fully rendered and not selected are very rare
|
||||
// check if tile combined extents are within selection rectangle
|
||||
// note very useful because cases where a tile is fully rendered and not selected are very rare
|
||||
|
||||
// skip if no objects
|
||||
if (tile->getObjectInstances().empty())
|
||||
continue;
|
||||
// skip if no objects
|
||||
if (tile->getObjectInstances().empty())
|
||||
continue;
|
||||
|
||||
bool valid = false;
|
||||
auto screenBounds = misc::getAABBScreenBounds(tile->getCombinedExtents(), VPmatrix
|
||||
, viewport_width, viewport_height, valid, 1.0f);
|
||||
bool valid = false;
|
||||
auto screenBounds = misc::getAABBScreenBounds(tile->getCombinedExtents(), VPmatrix
|
||||
, viewport_width, viewport_height, valid, 1.0f);
|
||||
|
||||
// this only works if all tile points are in screen space
|
||||
if (valid && !math::boxIntersects(screenBounds[0], screenBounds[1]
|
||||
, selection_box[0], selection_box[1]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& pair : tile->getObjectInstances())
|
||||
{
|
||||
[[unlikely]]
|
||||
if (!pair.first->finishedLoading())
|
||||
continue;
|
||||
|
||||
auto objectType = pair.second[0]->which();
|
||||
|
||||
[[unlikely]]
|
||||
if (!(objectType == eMODEL || objectType == eWMO))
|
||||
continue;
|
||||
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
// problem : M2s have additional sized based culling with >isInRenderDist()
|
||||
// if (!instance->_rendered_last_frame)
|
||||
// continue;
|
||||
|
||||
// unsigned int uid = instance->uid;
|
||||
// auto modelInstance = _model_instance_storage.get_instance(uid);
|
||||
// [[unlikely]]
|
||||
// if (!modelInstance || !modelInstance.value().index() == eEntry_Object)
|
||||
// continue;
|
||||
// auto obj = std::get<selected_object_type>(modelInstance.value());
|
||||
|
||||
bool do_selection = false;
|
||||
|
||||
// Old code to check position point instead of bound box
|
||||
{
|
||||
glm::vec4 screenPos = VPmatrix * glm::vec4(instance->pos, 1.0f);
|
||||
|
||||
// if screenPos.w < 0.0f, object is behind camera
|
||||
// check object bounding radius instead to compare the object's size, if it clips with the camera.
|
||||
if (screenPos.w < -instance->getBoundingRadius())
|
||||
continue;
|
||||
|
||||
screenPos.x /= screenPos.w;
|
||||
screenPos.y /= screenPos.w;
|
||||
|
||||
// Convert normalized device coordinates (NDC) to screen coordinates
|
||||
screenPos.x = (screenPos.x + 1.0f) * 0.5f * viewport_width;
|
||||
screenPos.y = (1.0f - (screenPos.y + 1.0f) * 0.5f) * viewport_height;
|
||||
|
||||
|
||||
float distance = glm::distance(camera_position, instance->pos);
|
||||
if (distance > user_depth)
|
||||
continue;
|
||||
|
||||
// check if position(origin) point is within rectangle first because it is much cheaper
|
||||
{
|
||||
const glm::vec2 screenPos2D = glm::vec2(screenPos);
|
||||
if (misc::pointInside(screenPos2D, selection_box))
|
||||
{
|
||||
processed_obj_count++;
|
||||
|
||||
// check if point is occluded by terrain
|
||||
if (processed_obj_count < max_position_raycast_processing
|
||||
&& !is_point_occluded_by_terrain(instance->pos, view, VPmatrix, viewport_width, viewport_height, camera_position))
|
||||
{
|
||||
do_selection = true;
|
||||
}
|
||||
// else
|
||||
// bool debug_breakpoint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it's not, check again if bounding box is within selection
|
||||
if (!do_selection && instance->_rendered_last_frame && (processed_obj_count < max_bounds_raycast_processing) )
|
||||
{
|
||||
bool valid = false;
|
||||
auto screenBounds = misc::getAABBScreenBounds(instance->getExtents(), VPmatrix
|
||||
, viewport_width, viewport_height, valid, 0.75f);
|
||||
|
||||
if (valid && math::boxIntersects(screenBounds[0], screenBounds[1]
|
||||
, selection_box[0], selection_box[1]))
|
||||
{
|
||||
// do_selection = true;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
// Optimization : Only do raycast bounds checks for object that take enough screen space
|
||||
if (!do_selection)
|
||||
{
|
||||
if (glm::distance(screenBounds[0], screenBounds[1]) < obj_raycast_min_size)
|
||||
{
|
||||
// debug_count_obj_min_size++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// debug_count_obj_min_size_not++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Occlusion test on object's corners (that are in selection box)
|
||||
// uses ray casting, very expensive
|
||||
if (!do_selection)
|
||||
{
|
||||
math::aabb obj_aabb(instance->getExtents()[0], instance->getExtents()[1]);
|
||||
auto obj_aabb_corners = obj_aabb.all_corners();
|
||||
|
||||
// int required_num_unoccluded_corers = 6; // require 6 of 8 corners to not be occluded
|
||||
bool object_occluded = true;
|
||||
|
||||
for (const auto& corner : obj_aabb_corners)
|
||||
{
|
||||
// TODO : only need to do max top left and max top right in 2d instead of all corners?
|
||||
|
||||
// only process points that are within selection rectangle
|
||||
bool point_valid = false;
|
||||
auto point_screen_pos = misc::projectPointToScreen(corner, VPmatrix, viewport_width, viewport_height, point_valid);
|
||||
if (!point_valid)
|
||||
continue;
|
||||
if (!misc::pointInside(point_screen_pos, selection_box))
|
||||
continue;
|
||||
|
||||
|
||||
bool corner_occluded = is_point_occluded_by_terrain(corner, view, VPmatrix, viewport_width, viewport_height, camera_position);
|
||||
|
||||
if (!corner_occluded)
|
||||
{
|
||||
object_occluded = false;
|
||||
break;
|
||||
}
|
||||
// object_occluded = true;
|
||||
}
|
||||
|
||||
do_selection = !object_occluded;
|
||||
}
|
||||
|
||||
if (!do_selection)
|
||||
continue;
|
||||
|
||||
auto& obj = instance;
|
||||
auto which = obj->which();
|
||||
if (which == eWMO)
|
||||
{
|
||||
auto model_instance = static_cast<WMOInstance*>(obj);
|
||||
|
||||
if (!is_selected(obj) && !model_instance->wmo->is_hidden())
|
||||
{
|
||||
this->add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
else if (which == eMODEL)
|
||||
{
|
||||
auto model_instance = static_cast<ModelInstance*>(obj);
|
||||
|
||||
if (!is_selected(obj) && !model_instance->model->is_hidden())
|
||||
{
|
||||
this->add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// this only works if all tile points are in screen space
|
||||
if (valid && !math::boxIntersects(screenBounds[0], screenBounds[1]
|
||||
, selection_box[0], selection_box[1]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& pair : tile->getObjectInstances())
|
||||
{
|
||||
[[unlikely]]
|
||||
if (!pair.first->finishedLoading())
|
||||
continue;
|
||||
|
||||
auto objectType = pair.second[0]->which();
|
||||
|
||||
[[unlikely]]
|
||||
if (!(objectType == eMODEL || objectType == eWMO))
|
||||
continue;
|
||||
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
// problem : M2s have additional sized based culling with >isInRenderDist()
|
||||
// if (!instance->_rendered_last_frame)
|
||||
// continue;
|
||||
|
||||
bool do_selection = false;
|
||||
|
||||
// Old code to check position point instead of bound box
|
||||
{
|
||||
glm::vec4 screenPos = VPmatrix * glm::vec4(instance->pos, 1.0f);
|
||||
|
||||
// if screenPos.w < 0.0f, object is behind camera
|
||||
// check object bounding radius instead to compare the object's size, if it clips with the camera.
|
||||
if (screenPos.w < -instance->getBoundingRadius())
|
||||
continue;
|
||||
|
||||
screenPos.x /= screenPos.w;
|
||||
screenPos.y /= screenPos.w;
|
||||
|
||||
// Convert normalized device coordinates (NDC) to screen coordinates
|
||||
screenPos.x = (screenPos.x + 1.0f) * 0.5f * viewport_width;
|
||||
screenPos.y = (1.0f - (screenPos.y + 1.0f) * 0.5f) * viewport_height;
|
||||
|
||||
|
||||
float distance = glm::distance(camera_position, instance->pos);
|
||||
if (distance > user_depth)
|
||||
continue;
|
||||
|
||||
// check if position(origin) point is within rectangle first because it is much cheaper
|
||||
{
|
||||
const glm::vec2 screenPos2D = glm::vec2(screenPos);
|
||||
if (misc::pointInside(screenPos2D, selection_box))
|
||||
{
|
||||
processed_obj_count++;
|
||||
|
||||
// check if point is occluded by terrain
|
||||
if (processed_obj_count < max_position_raycast_processing
|
||||
&& !is_point_occluded_by_terrain(instance->pos, view, VPmatrix, viewport_width, viewport_height, camera_position))
|
||||
{
|
||||
do_selection = true;
|
||||
}
|
||||
// else
|
||||
// bool debug_breakpoint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it's not, check again if bounding box is within selection
|
||||
// we check _rendered_last_frame because m2s that are too small on screen already don't render
|
||||
if (!do_selection && instance->_rendered_last_frame && (processed_obj_count < max_bounds_raycast_processing) )
|
||||
{
|
||||
bool valid = false;
|
||||
auto screenBounds = misc::getAABBScreenBounds(instance->getExtents(), VPmatrix
|
||||
, viewport_width, viewport_height, valid, 0.75f);
|
||||
|
||||
if (valid && math::boxIntersects(screenBounds[0], screenBounds[1]
|
||||
, selection_box[0], selection_box[1]))
|
||||
{
|
||||
// do_selection = true;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
// Optimization : Only do raycast bounds checks for object that take enough screen space
|
||||
if (!do_selection)
|
||||
{
|
||||
if (glm::distance(screenBounds[0], screenBounds[1]) < obj_raycast_min_size)
|
||||
{
|
||||
// debug_count_obj_min_size++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// debug_count_obj_min_size_not++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Occlusion test on object's corners (that are in selection box)
|
||||
// uses ray casting, very expensive
|
||||
if (!do_selection)
|
||||
{
|
||||
math::aabb obj_aabb(instance->getExtents()[0], instance->getExtents()[1]);
|
||||
auto obj_aabb_corners = obj_aabb.all_corners();
|
||||
|
||||
// int required_num_unoccluded_corers = 6; // require 6 of 8 corners to not be occluded
|
||||
bool object_occluded = true;
|
||||
|
||||
for (const auto& corner : obj_aabb_corners)
|
||||
{
|
||||
// TODO : only need to do max top left and max top right in 2d instead of all corners?
|
||||
|
||||
// only process points that are within selection rectangle
|
||||
bool point_valid = false;
|
||||
auto point_screen_pos = misc::projectPointToScreen(corner, VPmatrix, viewport_width, viewport_height, point_valid);
|
||||
if (!point_valid)
|
||||
continue;
|
||||
if (!misc::pointInside(point_screen_pos, selection_box))
|
||||
continue;
|
||||
|
||||
|
||||
bool corner_occluded = is_point_occluded_by_terrain(corner, view, VPmatrix, viewport_width, viewport_height, camera_position);
|
||||
|
||||
if (!corner_occluded)
|
||||
{
|
||||
object_occluded = false;
|
||||
break;
|
||||
}
|
||||
// object_occluded = true;
|
||||
}
|
||||
|
||||
do_selection = !object_occluded;
|
||||
}
|
||||
|
||||
if (!do_selection)
|
||||
continue;
|
||||
|
||||
auto& obj = instance;
|
||||
auto which = obj->which();
|
||||
if (which == eWMO)
|
||||
{
|
||||
auto model_instance = static_cast<WMOInstance*>(obj);
|
||||
|
||||
if (!model_instance->wmo->is_hidden())
|
||||
{
|
||||
this->add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
else if (which == eMODEL)
|
||||
{
|
||||
auto model_instance = static_cast<ModelInstance*>(obj);
|
||||
|
||||
if (!model_instance->model->is_hidden())
|
||||
{
|
||||
this->add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->update_selection_pivot();
|
||||
}
|
||||
|
||||
@@ -3894,32 +3910,30 @@ void World::add_object_group_from_selection()
|
||||
saveSelectionGroups();
|
||||
}
|
||||
|
||||
/*
|
||||
void World::remove_selection_group(selection_group* group)
|
||||
{
|
||||
// std::vector<selection_type>::iterator position = std::find(_selection_groups.begin(), _selection_groups.end(), group);
|
||||
// if (position != _selection_groups.end())
|
||||
// {
|
||||
// _selection_groups.erase(position);
|
||||
// }
|
||||
std::vector<selection_type>::iterator position = std::find(_selection_groups.begin(), _selection_groups.end(), group);
|
||||
if (position != _selection_groups.end())
|
||||
{
|
||||
_selection_groups.erase(position);
|
||||
}
|
||||
|
||||
// for (auto it = _selection_groups.begin(); it != _selection_groups.end(); ++it)
|
||||
// {
|
||||
// auto it_group = *it;
|
||||
// if (it_group.getMembers().size() == group->getMembers().size() && it_group.getExtents() == group->getExtents())
|
||||
// // if (it_group.isSelected())
|
||||
// {
|
||||
// _selection_groups.erase(it);
|
||||
// saveSelectionGroups();
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
for (auto it = _selection_groups.begin(); it != _selection_groups.end(); ++it)
|
||||
{
|
||||
auto it_group = *it;
|
||||
if (it_group.getMembers().size() == group->getMembers().size() && it_group.getExtents() == group->getExtents())
|
||||
// if (it_group.isSelected())
|
||||
{
|
||||
_selection_groups.erase(it);
|
||||
saveSelectionGroups();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void World::clear_selection_groups()
|
||||
{
|
||||
// _selection_groups.clear();
|
||||
|
||||
// for (auto it = _selection_groups.begin(); it != _selection_groups.end(); ++it)
|
||||
for (auto& group : _selection_groups)
|
||||
{
|
||||
// auto it_group = *it;
|
||||
|
||||
@@ -58,6 +58,7 @@ class World
|
||||
friend class Noggit::Rendering::WorldRender;
|
||||
|
||||
protected:
|
||||
std::unordered_set<unsigned int> selected_uids; // fast lookup
|
||||
std::vector<selection_type> _current_selection;
|
||||
// std::unordered_map<std::string, std::vector<ModelInstance*>> _models_by_filename;
|
||||
Noggit::world_model_instances_storage _model_instance_storage;
|
||||
@@ -125,7 +126,7 @@ public:
|
||||
std::optional<glm::vec3> const& multi_select_pivot() const { return _multi_select_pivot; }
|
||||
|
||||
// Selection related methods.
|
||||
bool is_selected(selection_type selection) const;
|
||||
bool is_selected(selection_type selection);
|
||||
bool is_selected(std::uint32_t uid) const;
|
||||
std::vector<selection_type> const& current_selection() const { return _current_selection; }
|
||||
std::vector<selected_object_type> const get_selected_objects() const;
|
||||
@@ -136,7 +137,7 @@ public:
|
||||
// Unused in Red, models are now iterated by adt because of the occlusion check
|
||||
// std::unordered_map<std::string, std::vector<ModelInstance*>> 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, bool update_pivot = true);
|
||||
bool add_to_selection(selection_type entry, bool skip_group = false, bool update_pivot = true);
|
||||
void remove_from_selection(selection_type entry, bool skip_group = false, bool update_pivot = true);
|
||||
void remove_from_selection(std::uint32_t uid, bool skip_group = false, bool update_pivot = true);
|
||||
void reset_selection();
|
||||
@@ -408,7 +409,7 @@ public:
|
||||
|
||||
|
||||
void add_object_group_from_selection();
|
||||
void remove_selection_group(selection_group* group);
|
||||
// void remove_selection_group(selection_group* group);
|
||||
|
||||
void clear_selection_groups();
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
|
||||
tsl::robin_map<Model*, std::vector<glm::mat4x4>> models_to_draw;
|
||||
std::vector<WMOInstance*> wmos_to_draw;
|
||||
std::unordered_map<Model*, std::size_t> model_boxes_to_draw;
|
||||
|
||||
// frame counter loop. pretty hacky but works
|
||||
// this is used to make sure no object is processed more than once within a frame
|
||||
@@ -426,10 +427,13 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
continue;
|
||||
}
|
||||
|
||||
instance->frame = frame;
|
||||
|
||||
auto m2_instance = static_cast<ModelInstance*>(instance);
|
||||
|
||||
if (!draw_hidden_models && m2_instance->model->is_hidden())
|
||||
continue;
|
||||
|
||||
instance->frame = frame;
|
||||
|
||||
// experimental : if camera and object haven't moved/changed since last frame, we don't need to do frustum culling again
|
||||
if (!camera_moved && !m2_instance->extentsDirty()/* && not_moved*/)
|
||||
{
|
||||
@@ -437,15 +441,22 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
{
|
||||
instances.push_back(m2_instance->transformMatrix());
|
||||
m2_instance->_rendered_last_frame = true;
|
||||
continue; // skip visibility checks
|
||||
}
|
||||
}
|
||||
|
||||
if (m2_instance->isInRenderDist(_cull_distance, camera_pos, display) && (tile->renderer()->objectsFrustumCullTest() > 1 || m2_instance->isInFrustum(frustum)))
|
||||
{
|
||||
instances.push_back(m2_instance->transformMatrix());
|
||||
m2_instance->_rendered_last_frame = true;
|
||||
}
|
||||
|
||||
// if (render && !draw_models_with_box /* && !m2_instance->model->is_hidden()*/)
|
||||
// {
|
||||
// // model box wasn't set in model draw(), add selection boxes
|
||||
// if (_world->selected_uids.contains(m2_instance->uid))
|
||||
// model_boxes_to_draw.emplace(m2_instance->model, instances.size());
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -483,10 +494,13 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
continue;
|
||||
}
|
||||
|
||||
instance->frame = frame;
|
||||
|
||||
auto wmo_instance = static_cast<WMOInstance*>(instance);
|
||||
|
||||
if (!draw_hidden_models && wmo_instance->wmo->is_hidden())
|
||||
continue;
|
||||
|
||||
instance->frame = frame;
|
||||
|
||||
// experimental : if camera and object haven't moved/changed since last frame, we don't need to do frustum culling again
|
||||
if (!camera_moved && !wmo_instance->extentsDirty()/* && not_moved*/)
|
||||
{
|
||||
@@ -618,8 +632,9 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const is_selected = _world->selected_uids.contains(instance->uid);
|
||||
|
||||
if (draw_hidden_models || !is_hidden)
|
||||
/*if (draw_hidden_models || !is_hidden)*/ // now checking when adding instances
|
||||
{
|
||||
instance->draw(wmo_program
|
||||
, model_view
|
||||
@@ -630,7 +645,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
, is_hidden
|
||||
, draw_wmo_doodads
|
||||
, draw_fog
|
||||
, _world->current_selection()
|
||||
, is_selected
|
||||
, _world->animtime
|
||||
, _skies->hasSkies()
|
||||
, display
|
||||
@@ -722,8 +737,6 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
_world->update_models_by_filename();
|
||||
}*/
|
||||
|
||||
std::unordered_map<Model*, std::size_t> model_boxes_to_draw;
|
||||
|
||||
{
|
||||
if (draw_models || draw_doodads_wmo || (minimap_render && minimap_render_settings->use_filters))
|
||||
{
|
||||
@@ -773,7 +786,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
continue;
|
||||
}
|
||||
|
||||
if (draw_hidden_models || !pair.first->is_hidden())
|
||||
/*if (draw_hidden_models || !pair.first->is_hidden())*/ // now done when building models_to_draw
|
||||
{
|
||||
pair.first->renderer()->draw( model_view
|
||||
, pair.second
|
||||
@@ -863,21 +876,22 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
it.first->renderer()->drawBox(m2_box_shader, it.second);
|
||||
}
|
||||
}
|
||||
model_boxes_to_draw.clear();
|
||||
|
||||
// render selection boxes.
|
||||
// TODO can try to move to m2 box shader but it requires some refactor
|
||||
for (auto& selection : _world->current_selection())
|
||||
{
|
||||
if (selection.index() == eEntry_Object)
|
||||
{
|
||||
auto obj = std::get<selected_object_type>(selection);
|
||||
|
||||
|
||||
if (obj->which() != eMODEL)
|
||||
continue;
|
||||
|
||||
|
||||
auto model = static_cast<ModelInstance*>(obj);
|
||||
|
||||
|
||||
|
||||
if (model->isInRenderDist(_cull_distance, camera_pos, display) && model->isInFrustum(frustum))
|
||||
if (model->_rendered_last_frame)
|
||||
{
|
||||
bool is_selected = false;
|
||||
/*
|
||||
@@ -890,7 +904,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
&& std::get<selected_object_type>(type)->which() == SceneObjectTypes::eMODEL
|
||||
&& static_cast<ModelInstance*>(std::get<selected_object_type>(type))->uid == id;
|
||||
}) != _world->current_selection().end();*/
|
||||
|
||||
|
||||
model->draw_box(model_view, projection, is_selected); // make optional!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace Noggit
|
||||
|
||||
addMenuSeperator(menu);
|
||||
|
||||
addMenuItem(menu, "Select all Like Selected", "Warning : Doing actions on models overlapping unloaded tiles can cause crash",
|
||||
addMenuItem(menu, "Select all Like Selected", "",
|
||||
world->get_selected_model_count() == 1, [=] {
|
||||
auto world = mapView()->getWorld();
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ void AddObjectInstanceToSelectionNode::compute()
|
||||
|
||||
SceneObject* obj = defaultPortData<ObjectInstanceData>(PortType::In, 1)->value();
|
||||
|
||||
if (!world->is_selected(obj))
|
||||
world->add_to_selection(obj);
|
||||
world->add_to_selection(obj);
|
||||
|
||||
_out_ports[0].out_value = std::make_shared<LogicData>(true);
|
||||
_node->onDataUpdated(0);
|
||||
|
||||
@@ -191,7 +191,7 @@ void PreviewRenderer::draw()
|
||||
wmo_instance.draw(
|
||||
wmo_program, model_view(), projection(), frustum, culldistance,
|
||||
_camera.position, _draw_boxes.get(), _draw_models.get()
|
||||
, false, std::vector<selection_type>(), 0, false, display_mode::in_3D, true
|
||||
, false, false, 0, false, display_mode::in_3D, true
|
||||
);
|
||||
|
||||
auto doodads = wmo_instance.get_doodads(true);
|
||||
|
||||
Reference in New Issue
Block a user