rework drag selection, now checks for occlusion and bounds + optimizations
optimize multi selections pivot updates management experimental rendering update : don't do object frustum/visibiltiy checks if camera/objects did not move
This commit is contained in:
@@ -100,6 +100,7 @@ public:
|
||||
|
||||
std::atomic<bool> changed;
|
||||
|
||||
bool _was_rendered_last_frame = false;
|
||||
|
||||
bool intersect (math::ray const&, selection_result*);
|
||||
|
||||
@@ -218,7 +219,6 @@ private:
|
||||
bool _load_textures;
|
||||
World* _world;
|
||||
|
||||
|
||||
Noggit::Rendering::TileRender _renderer;
|
||||
Noggit::Rendering::FlightBoundsRender _fl_bounds_render;
|
||||
|
||||
|
||||
@@ -3459,7 +3459,8 @@ glm::mat4x4 MapView::model_view(bool use_debug_cam) const
|
||||
|
||||
glm::mat4x4 MapView::projection() const
|
||||
{
|
||||
float far_z = _settings->value("view_distance", 2000.f).toFloat() + 1.f;
|
||||
// float far_z = _settings->value("view_distance", 2000.f).toFloat() + 1.f;
|
||||
float far_z = _world->renderer()->_view_distance + 1.0f;
|
||||
|
||||
if (_display_mode == display_mode::in_2D)
|
||||
{
|
||||
@@ -4011,7 +4012,7 @@ void MapView::mouseReleaseEvent (QMouseEvent* event)
|
||||
glm::vec2(std::max(_drag_start_pos.x(), drag_end_pos.x()), std::max(_drag_start_pos.y(), drag_end_pos.y()))
|
||||
};
|
||||
// _world->select_objects_in_area(selection_box, !_mod_shift_down, model_view(), projection(), width(), height(), objectEditor->drag_selection_depth(), _camera.position);
|
||||
_world->select_objects_in_area(selection_box, !_mod_shift_down, model_view(), projection(), width(), height(), 3000.0f, _camera.position);
|
||||
_world->select_objects_in_area(selection_box, !_mod_shift_down, model_view(), projection(), width(), height(), 50000.0f, _camera.position);
|
||||
}
|
||||
else // Do normal selection when we just clicked
|
||||
{
|
||||
@@ -4063,7 +4064,10 @@ void MapView::save(save_mode mode)
|
||||
first_warning.setIcon(QMessageBox::Critical);
|
||||
first_warning.setWindowIcon(QIcon (":/icon"));
|
||||
first_warning.setWindowTitle("Some models couldn't be loaded");
|
||||
first_warning.setText("Error:\nSome models could not be loaded and saving will cause collision and culling issues, would you still like to save ?");
|
||||
first_warning.setText("Error:\nSome models could not be loaded and saving will cause collision and culling issues,"
|
||||
" this is most likely caused by missing or corrupted models."
|
||||
"\nCheck the log file for the list of model errors and fix them."
|
||||
"\nWould you still like to save ?");
|
||||
// roles are swapped to force the user to pay attention and both are "accept" roles so that escape does nothing
|
||||
no = first_warning.addButton("No", QMessageBox::ButtonRole::AcceptRole);
|
||||
yes = first_warning.addButton("Yes", QMessageBox::ButtonRole::YesRole);
|
||||
@@ -4222,7 +4226,7 @@ void MapView::onSettingsSave()
|
||||
|
||||
_world->renderer()->markTerrainParamsUniformBlockDirty();
|
||||
|
||||
_world->renderer()->setViewDistance(_settings->value("view_distance", 2000.f).toFloat());
|
||||
_world->renderer()->_view_distance = _settings->value("view_distance", 2000.f).toFloat();
|
||||
|
||||
_world.get()->mapIndex.setLoadingRadius(_settings->value("loading_radius", 2).toInt());
|
||||
_world.get()->mapIndex.setUnloadDistance(_settings->value("unload_dist", 5).toInt());
|
||||
|
||||
@@ -55,6 +55,12 @@ namespace misc
|
||||
clipSpacePos.x = (ndcX + 1.0f) * 0.5f * viewport_width;
|
||||
clipSpacePos.y = (1.0f - (ndcY + 1.0f) * 0.5f) * viewport_height;
|
||||
|
||||
// from MapView::normalized_device_coords
|
||||
// x 2.0f * x / viewport_width - 1.0f
|
||||
// y 1.0f - 2.0f * y / viewport_height
|
||||
// z 0.0f
|
||||
// w 1.0f
|
||||
|
||||
valid = true;
|
||||
return clipSpacePos;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
#include <noggit/TextureManager.h>
|
||||
#include <noggit/tool_enums.hpp>
|
||||
#include <noggit/ContextObject.hpp>
|
||||
#include <noggit/rendering/ModelRender.hpp>
|
||||
#include <opengl/scoped.hpp>
|
||||
#include <opengl/shader.fwd.hpp>
|
||||
#include <ClientFile.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <noggit/rendering/ModelRender.hpp>
|
||||
#include <map>
|
||||
|
||||
class Bone;
|
||||
class Model;
|
||||
@@ -188,6 +188,8 @@ public:
|
||||
[[nodiscard]]
|
||||
Noggit::Rendering::ModelRender* renderer() { return &_renderer; }
|
||||
|
||||
uint32_t get_anim_lenght(int16_t anim_id) { return _animation_length[anim_id]; }
|
||||
|
||||
// ===============================
|
||||
// Toggles
|
||||
// ===============================
|
||||
@@ -276,5 +278,6 @@ private:
|
||||
Noggit::Rendering::ModelRender _renderer;
|
||||
|
||||
bool _hidden = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ public:
|
||||
bool isInFrustum(math::frustum const& frustum);
|
||||
bool isInRenderDist(const float& cull_distance, const glm::vec3& camera, display_mode display);
|
||||
|
||||
bool extentsDirty() { return _need_recalc_extents || !model->finishedLoading(); };
|
||||
|
||||
[[nodiscard]]
|
||||
virtual glm::vec3 const& get_pos() const { return pos; }
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ public:
|
||||
unsigned int uid;
|
||||
int frame;
|
||||
|
||||
// Note : First, need to check if the tile that contained it was rendered too
|
||||
bool _rendered_last_frame = false;
|
||||
|
||||
protected:
|
||||
SceneObjectTypes _type;
|
||||
|
||||
|
||||
@@ -317,8 +317,9 @@ void selection_group::select_group()
|
||||
if (_world->is_selected(instance))
|
||||
continue;
|
||||
|
||||
_world->add_to_selection(obj.value(), true);
|
||||
_world->add_to_selection(obj.value(), true, false);
|
||||
}
|
||||
_world->update_selection_pivot();
|
||||
|
||||
_is_selected = true;
|
||||
}
|
||||
@@ -328,9 +329,9 @@ 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, true);
|
||||
_world->remove_from_selection(obj_uid, true, false);
|
||||
}
|
||||
|
||||
_world->update_selection_pivot();
|
||||
_is_selected = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,5 +111,5 @@ private:
|
||||
// bool _need_recalc_extents = false;
|
||||
};
|
||||
|
||||
using selection_entry = std::pair<float, selection_type>;
|
||||
using selection_entry = std::pair<float, selection_type>; // float = hit distance
|
||||
using selection_result = std::vector<selection_entry>;
|
||||
@@ -35,6 +35,7 @@ namespace Noggit
|
||||
{
|
||||
display_mode displayMode = display_mode::in_3D;
|
||||
bool underMap = false;
|
||||
bool camera_moved_since_last_draw = false;
|
||||
|
||||
bool left_mouse = false;
|
||||
bool right_mouse = false;
|
||||
|
||||
@@ -558,7 +558,8 @@ void World::set_current_selection(selection_type entry)
|
||||
add_to_selection(entry);
|
||||
}
|
||||
|
||||
void World::add_to_selection(selection_type entry, bool skip_group)
|
||||
// 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)
|
||||
{
|
||||
ZoneScoped;
|
||||
if (entry.index() == eEntry_Object)
|
||||
@@ -583,10 +584,12 @@ void World::add_to_selection(selection_type entry, bool skip_group)
|
||||
}
|
||||
}
|
||||
_current_selection.push_back(entry);
|
||||
update_selection_pivot();
|
||||
|
||||
if (update_pivot)
|
||||
update_selection_pivot();
|
||||
}
|
||||
|
||||
void World::remove_from_selection(selection_type entry, bool skip_group)
|
||||
void World::remove_from_selection(selection_type entry, bool skip_group, bool update_pivot)
|
||||
{
|
||||
ZoneScoped;
|
||||
std::vector<selection_type>::iterator position = std::find(_current_selection.begin(), _current_selection.end(), entry);
|
||||
@@ -613,11 +616,12 @@ void World::remove_from_selection(selection_type entry, bool skip_group)
|
||||
}
|
||||
|
||||
_current_selection.erase(position);
|
||||
update_selection_pivot();
|
||||
if (update_pivot)
|
||||
update_selection_pivot();
|
||||
}
|
||||
}
|
||||
|
||||
void World::remove_from_selection(std::uint32_t uid, bool skip_group)
|
||||
void World::remove_from_selection(std::uint32_t uid, bool skip_group, bool update_pivot)
|
||||
{
|
||||
ZoneScoped;
|
||||
for (auto it = _current_selection.begin(); it != _current_selection.end(); ++it)
|
||||
@@ -645,8 +649,8 @@ void World::remove_from_selection(std::uint32_t uid, bool skip_group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_selection_pivot();
|
||||
if (update_pivot)
|
||||
update_selection_pivot();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2041,6 +2045,10 @@ void World::remove_models_if_needed(std::vector<uint32_t> const& uids)
|
||||
{
|
||||
reset_selection();
|
||||
}
|
||||
else
|
||||
{
|
||||
update_selection_pivot();
|
||||
}
|
||||
/*
|
||||
if (uids.size())
|
||||
{
|
||||
@@ -3030,14 +3038,15 @@ void World::range_add_to_selection(glm::vec3 const& pos, float radius, bool remo
|
||||
{
|
||||
if (remove)
|
||||
{
|
||||
remove_from_selection(obj);
|
||||
remove_from_selection(obj, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_selected(obj))
|
||||
add_to_selection(obj);
|
||||
add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
update_selection_pivot();
|
||||
}
|
||||
|
||||
float World::getMaxTileHeight(const TileIndex& tile)
|
||||
@@ -3576,14 +3585,14 @@ void World::notifyTileRendererOnSelectedTextureChange()
|
||||
}
|
||||
|
||||
void World::select_objects_in_area(
|
||||
const std::array<glm::vec2, 2> selection_box,
|
||||
const std::array<glm::vec2, 2>& selection_box,
|
||||
bool reset_selection,
|
||||
glm::mat4x4 view,
|
||||
glm::mat4x4 projection,
|
||||
const glm::mat4x4& view,
|
||||
const glm::mat4x4& projection,
|
||||
int viewport_width,
|
||||
int viewport_height,
|
||||
float user_depth,
|
||||
glm::vec3 camera_position)
|
||||
const glm::vec3& camera_position)
|
||||
{
|
||||
ZoneScoped;
|
||||
|
||||
@@ -3594,6 +3603,15 @@ void World::select_objects_in_area(
|
||||
|
||||
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;
|
||||
@@ -3607,17 +3625,19 @@ void World::select_objects_in_area(
|
||||
{
|
||||
// tile not in screen, skip
|
||||
// frustum.intersects(tile_extents[1], tile_extents[0])
|
||||
if (tile->renderer()->isFrustumCulled())
|
||||
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
|
||||
|
||||
// skip if no objects
|
||||
if (tile->getObjectInstances().empty())
|
||||
continue;
|
||||
|
||||
// check if tile combined extents are within selection rectangle
|
||||
bool valid = false;
|
||||
auto screenBounds = misc::getAABBScreenBounds(tile->getCombinedExtents(), VPmatrix
|
||||
, viewport_width, viewport_height, valid);
|
||||
, 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]
|
||||
@@ -3629,81 +3649,235 @@ void World::select_objects_in_area(
|
||||
|
||||
for (auto& pair : tile->getObjectInstances())
|
||||
{
|
||||
auto objectType = pair.second[0]->which();
|
||||
if (objectType == eMODEL || objectType == eWMO)
|
||||
{
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
// Old code to check position point instead of bound box
|
||||
glm::vec4 screenPos = VPmatrix * glm::vec4(instance->pos, 1.0f);
|
||||
[[unlikely]]
|
||||
if (!pair.first->finishedLoading())
|
||||
continue;
|
||||
|
||||
// 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())
|
||||
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;
|
||||
|
||||
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 depth = glm::distance(camera_position, instance->pos);
|
||||
if (depth <= user_depth)
|
||||
bool corner_occluded = is_point_occluded_by_terrain(corner, view, VPmatrix, viewport_width, viewport_height, camera_position);
|
||||
|
||||
if (!corner_occluded)
|
||||
{
|
||||
bool do_selection = false;
|
||||
object_occluded = false;
|
||||
break;
|
||||
}
|
||||
// object_occluded = true;
|
||||
}
|
||||
|
||||
do_selection = !object_occluded;
|
||||
}
|
||||
|
||||
// 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))
|
||||
do_selection = true;
|
||||
if (!do_selection)
|
||||
continue;
|
||||
|
||||
// if it's not, check again if bounding box is within selection
|
||||
if (!do_selection)
|
||||
{
|
||||
bool valid = false;
|
||||
auto screenBounds = misc::getAABBScreenBounds(instance->getExtents(), VPmatrix
|
||||
, viewport_width, viewport_height, valid, 0.5f);
|
||||
|
||||
if (valid && math::boxIntersects(screenBounds[0], screenBounds[1]
|
||||
, selection_box[0], selection_box[1]))
|
||||
do_selection = true;
|
||||
}
|
||||
auto& obj = instance;
|
||||
auto which = obj->which();
|
||||
if (which == eWMO)
|
||||
{
|
||||
auto model_instance = static_cast<WMOInstance*>(obj);
|
||||
|
||||
if (do_selection)
|
||||
{
|
||||
unsigned int uid = instance->uid;
|
||||
auto modelInstance = _model_instance_storage.get_instance(uid);
|
||||
if (modelInstance && modelInstance.value().index() == eEntry_Object) {
|
||||
auto obj = std::get<selected_object_type>(modelInstance.value());
|
||||
auto which = std::get<selected_object_type>(modelInstance.value())->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->wmo->is_hidden())
|
||||
{
|
||||
this->add_to_selection(obj);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is_selected(obj) && !model_instance->model->is_hidden())
|
||||
{
|
||||
this->add_to_selection(obj, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->update_selection_pivot();
|
||||
}
|
||||
|
||||
|
||||
bool World::is_point_occluded_by_terrain(const glm::vec3& point,
|
||||
const glm::mat4x4& view,
|
||||
const glm::mat4& VPmatrix,
|
||||
float viewport_width,
|
||||
float viewport_height,
|
||||
const glm::vec3& camera_position)
|
||||
{
|
||||
/*
|
||||
bool point_valid = false;
|
||||
auto point_screen_pos = misc::projectPointToScreen(point, VPmatrix, viewport_width, viewport_height, point_valid);
|
||||
|
||||
if (!point_valid)
|
||||
{
|
||||
return true;
|
||||
}*/
|
||||
|
||||
math::ray ray(camera_position, point - camera_position); // 3d display mode only.
|
||||
|
||||
// intersect only terrain with a ray to object's position
|
||||
selection_result terrain_intersect_results
|
||||
(intersect
|
||||
(glm::transpose(view)
|
||||
, ray
|
||||
, true
|
||||
, false
|
||||
, true
|
||||
, false
|
||||
, false
|
||||
, false
|
||||
, false
|
||||
)
|
||||
);
|
||||
|
||||
float distance = glm::distance(camera_position, point);
|
||||
|
||||
// bool point_occluded = false;
|
||||
for (const auto& terrain_hit : terrain_intersect_results)
|
||||
{
|
||||
// if terrain hit is further, skip
|
||||
if (terrain_hit.first + 10.0f > distance) // add some leeway, skip hits that are too close, especially for the terrain at object's origin
|
||||
continue;
|
||||
|
||||
return true;
|
||||
|
||||
auto const& hitChunkInfo = std::get<selected_chunk_type>(terrain_hit.second);
|
||||
|
||||
/*
|
||||
// check if terrain hit is higher than the object's corner in 2D screen space
|
||||
bool point_valid = false;
|
||||
auto terrain_hit_screen_pos = misc::projectPointToScreen(hitChunkInfo.position, VPmatrix, viewport_width, viewport_height, point_valid);
|
||||
|
||||
if (!point_valid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if any terrain intersection point is above point, it means point is occluded
|
||||
if (point_screen_pos.y > terrain_hit_screen_pos.y)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
// no terrain intersection point above point
|
||||
return false;
|
||||
}
|
||||
|
||||
void World::add_object_group_from_selection()
|
||||
|
||||
@@ -136,9 +136,9 @@ 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);
|
||||
void remove_from_selection(selection_type entry, bool skip_group = false);
|
||||
void remove_from_selection(std::uint32_t uid, bool skip_group = false);
|
||||
void 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();
|
||||
void delete_selected_models();
|
||||
glm::vec3 get_ground_height(glm::vec3 pos);
|
||||
@@ -396,21 +396,25 @@ public:
|
||||
unsigned getNumRenderedObjects() const { return _n_rendered_objects; };
|
||||
|
||||
void select_objects_in_area(
|
||||
const std::array<glm::vec2, 2> selection_box,
|
||||
const std::array<glm::vec2, 2>& selection_box,
|
||||
bool reset_selection,
|
||||
glm::mat4x4 view,
|
||||
glm::mat4x4 projection,
|
||||
const glm::mat4x4& view,
|
||||
const glm::mat4x4& projection,
|
||||
int viewport_width,
|
||||
int viewport_height,
|
||||
float user_depth,
|
||||
glm::vec3 camera_position
|
||||
const glm::vec3& camera_position
|
||||
);
|
||||
|
||||
|
||||
void add_object_group_from_selection();
|
||||
void remove_selection_group(selection_group* group);
|
||||
|
||||
void clear_selection_groups();
|
||||
|
||||
private:
|
||||
bool is_point_occluded_by_terrain(const glm::vec3& point, const glm::mat4x4& view, const glm::mat4& VPmatrix, float viewport_width, float viewport_height, const glm::vec3& camera_position);
|
||||
|
||||
protected:
|
||||
// void update_models_by_filename();
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
unsigned tile_counter = 0;
|
||||
for (MapTile* tile : _world->mapIndex.loaded_tiles())
|
||||
{
|
||||
tile->recalcCombinedExtents();
|
||||
tile->_was_rendered_last_frame = false;
|
||||
|
||||
if (minimap_render)
|
||||
{
|
||||
@@ -226,6 +226,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
, frustum
|
||||
, _cull_distance
|
||||
, _world->animtime
|
||||
, _world->time
|
||||
, draw_skybox
|
||||
, _outdoor_light_stats
|
||||
);
|
||||
@@ -304,6 +305,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
);
|
||||
|
||||
_world->_n_rendered_tiles++;
|
||||
tile->_was_rendered_last_frame = true;
|
||||
|
||||
}
|
||||
|
||||
@@ -383,10 +385,20 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
// TODO: subject to potential generalization
|
||||
for (auto& pair : tile->getObjectInstances())
|
||||
{
|
||||
if (!pair.first->finishedLoading())
|
||||
continue;
|
||||
|
||||
if (pair.second[0]->which() == eMODEL)
|
||||
{
|
||||
if (!draw_models && !(minimap_render && minimap_render_settings->use_filters))
|
||||
{
|
||||
// can optimize this with a tile.rendered_m2s_lastframe or just check if models are enabled
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
instance->_rendered_last_frame = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& instances = models_to_draw[reinterpret_cast<Model*>(pair.first)];
|
||||
|
||||
@@ -405,9 +417,12 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
instance->_rendered_last_frame = false;
|
||||
|
||||
// do not render twice the cross-referenced objects twice
|
||||
if (instance->frame == frame)
|
||||
{
|
||||
instance->_rendered_last_frame = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -415,9 +430,20 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
|
||||
auto m2_instance = static_cast<ModelInstance*>(instance);
|
||||
|
||||
if ((tile->renderer()->objectsFrustumCullTest() > 1 || m2_instance->isInFrustum(frustum)) && m2_instance->isInRenderDist(_cull_distance, camera_pos, display))
|
||||
// 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*/)
|
||||
{
|
||||
if (m2_instance->_rendered_last_frame)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -426,7 +452,13 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
else if (pair.second[0]->which() == eWMO)
|
||||
{
|
||||
if (!draw_wmo)
|
||||
continue;
|
||||
{
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
instance->_rendered_last_frame = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// memory allocation heuristic. all objects will pass if tile is entirely in frustum.
|
||||
// otherwise we only allocate for a half
|
||||
@@ -442,9 +474,12 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
|
||||
for (auto& instance : pair.second)
|
||||
{
|
||||
instance->_rendered_last_frame = false;
|
||||
|
||||
// do not render twice the cross-referenced objects twice
|
||||
if (instance->frame == frame)
|
||||
{
|
||||
instance->_rendered_last_frame = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -452,9 +487,20 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
|
||||
auto wmo_instance = static_cast<WMOInstance*>(instance);
|
||||
|
||||
// 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*/)
|
||||
{
|
||||
if (wmo_instance->_rendered_last_frame)
|
||||
{
|
||||
wmos_to_draw.push_back(wmo_instance);
|
||||
wmo_instance->_rendered_last_frame = true;
|
||||
continue; // skip visibility checks
|
||||
}
|
||||
}
|
||||
if (tile->renderer()->objectsFrustumCullTest() > 1 || frustum.intersects(wmo_instance->getExtents()[1], wmo_instance->getExtents()[0]))
|
||||
{
|
||||
wmos_to_draw.push_back(wmo_instance);
|
||||
wmo_instance->_rendered_last_frame = true;
|
||||
|
||||
if (draw_wmo_doodads)
|
||||
{
|
||||
@@ -831,7 +877,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
||||
|
||||
|
||||
|
||||
if (model->isInFrustum(frustum) && model->isInRenderDist(_cull_distance, camera_pos, display))
|
||||
if (model->isInRenderDist(_cull_distance, camera_pos, display) && model->isInFrustum(frustum))
|
||||
{
|
||||
bool is_selected = false;
|
||||
/*
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Noggit::Rendering
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Skies>& skies() { return _skies; };
|
||||
|
||||
void setViewDistance(float distance) { _view_distance = distance; };
|
||||
float _view_distance;
|
||||
|
||||
private:
|
||||
|
||||
@@ -109,7 +109,6 @@ namespace Noggit::Rendering
|
||||
|
||||
World* _world;
|
||||
float _cull_distance;
|
||||
float _view_distance;
|
||||
|
||||
// shaders
|
||||
std::unique_ptr<OpenGL::program> _mcnk_program;;
|
||||
@@ -134,6 +133,7 @@ namespace Noggit::Rendering
|
||||
Noggit::Rendering::Primitives::Sphere _sphere_render;
|
||||
Noggit::Rendering::Primitives::Square _square_render;
|
||||
Noggit::Rendering::Primitives::Line _line_render;
|
||||
Noggit::Rendering::Primitives::WireBox _wirebox_render;
|
||||
|
||||
// buffers
|
||||
OpenGL::Scoped::deferred_upload_buffers<8> _buffers;
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace Noggit
|
||||
{
|
||||
if (model_instance.instance_model()->file_key().filepath() == model_name)
|
||||
{
|
||||
world->add_to_selection(&model_instance);
|
||||
world->add_to_selection(&model_instance, false, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -229,16 +229,11 @@ namespace Noggit
|
||||
if (wmo_instance.instance_model()->file_key().filepath() == model_name)
|
||||
{
|
||||
// objects_to_select.push_back(wmo_instance.uid);
|
||||
world->add_to_selection(&wmo_instance);
|
||||
world->add_to_selection(&wmo_instance, false, false);
|
||||
}
|
||||
});
|
||||
|
||||
// for (auto uid_it = objects_to_select.begin(); uid_it != objects_to_select.end(); uid_it++)
|
||||
// {
|
||||
// auto instance = world->getObjectInstance(*uid_it);
|
||||
// // if (!world->is_selected(instance))
|
||||
// world->add_to_selection(instance);
|
||||
// }
|
||||
world->update_selection_pivot();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -782,7 +777,7 @@ namespace Noggit
|
||||
QPoint drag_end_pos = params.mouse_position;
|
||||
int drag_distance_threshold = QApplication::startDragDistance(); // QApplication::startDragDistance() is 10
|
||||
/*_drag_start_pos != drag_end_pos*/
|
||||
if (!ImGuizmo::IsUsing() && (_drag_start_pos - drag_end_pos).manhattanLength() > drag_distance_threshold && _area_selection->isVisible())
|
||||
if (!ImGuizmo::IsUsing() && (_drag_start_pos - drag_end_pos).manhattanLength() > drag_distance_threshold && _area_selection->isVisible() )
|
||||
{
|
||||
const std::array<glm::vec2, 2> selection_box
|
||||
{
|
||||
@@ -790,7 +785,7 @@ namespace Noggit
|
||||
glm::vec2(std::max(_drag_start_pos.x(), drag_end_pos.x()), std::max(_drag_start_pos.y(), drag_end_pos.y()))
|
||||
};
|
||||
// _world->select_objects_in_area(selection_box, !_mod_shift_down, model_view(), projection(), width(), height(), objectEditor->drag_selection_depth(), _camera.position);
|
||||
mapView()->selectObjects(selection_box, 3000.0f);
|
||||
mapView()->selectObjects(selection_box, 10000.0f);
|
||||
}
|
||||
else // Do normal selection when we just clicked, or selection box is too small
|
||||
{
|
||||
@@ -846,7 +841,7 @@ namespace Noggit
|
||||
}
|
||||
}
|
||||
|
||||
if (params.mod_shift_down || params.mod_ctrl_down)
|
||||
if (_area_selection->isHidden() && (params.mod_shift_down || params.mod_ctrl_down) )
|
||||
{
|
||||
mapView->doSelection(false, true);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Noggit
|
||||
|
||||
if (--_instance_count_per_uid.at(uid) == 0)
|
||||
{
|
||||
_world->remove_from_selection(uid);
|
||||
_world->remove_from_selection(uid, false, false);
|
||||
|
||||
_instance_count_per_uid.erase(uid);
|
||||
_m2s.erase(uid);
|
||||
|
||||
Reference in New Issue
Block a user