finish drag selection

This commit is contained in:
T1ti
2024-09-30 01:31:42 +02:00
parent 5150a52b92
commit 4a14ab2fde
11 changed files with 218 additions and 163 deletions

View File

@@ -58,14 +58,14 @@ namespace math
std::array<glm::vec3, 8> box_points(glm::vec3 const& box_min, glm::vec3 const& box_max)
{
return std::array<glm::vec3, 8> {
glm::vec3(box_max.x, box_max.y, box_max.z),
glm::vec3(box_max.x, box_max.y, box_min.z),
glm::vec3(box_max.x, box_min.y, box_max.z),
glm::vec3(box_max.x, box_min.y, box_min.z),
glm::vec3(box_min.x, box_max.y, box_max.z),
glm::vec3(box_min.x, box_max.y, box_min.z),
glm::vec3(box_min.x, box_min.y, box_max.z),
glm::vec3(box_min.x, box_min.y, box_min.z)
glm::vec3(box_max.x, box_max.y, box_max.z), // 0: Top-right-front
glm::vec3(box_max.x, box_max.y, box_min.z), // 1: Top-right-back
glm::vec3(box_max.x, box_min.y, box_max.z), // 2: Bottom-right-front
glm::vec3(box_max.x, box_min.y, box_min.z), // 3: Bottom-right-back
glm::vec3(box_min.x, box_max.y, box_max.z), // 4: Top-left-front
glm::vec3(box_min.x, box_max.y, box_min.z), // 5: Top-left-back
glm::vec3(box_min.x, box_min.y, box_max.z), // 6: Bottom-left-front
glm::vec3(box_min.x, box_min.y, box_min.z) // 7: Bottom-left-back
};
}

View File

@@ -15,6 +15,7 @@ namespace math
aabb(std::vector<glm::vec3> const& points);
std::array<glm::vec3, 8> all_corners() const;
inline glm::vec3 center() const { return (min + max) * 0.5f; };
glm::vec3 min;
glm::vec3 max;

View File

@@ -8,37 +8,23 @@
namespace math
{
std::optional<float> ray::intersect_bounds
(glm::vec3 const& min, glm::vec3 const& max) const
(glm::vec3 const& min, glm::vec3 const& max) const noexcept
{
float tmin (std::numeric_limits<float>::lowest());
float tmax (std::numeric_limits<float>::max());
if (_direction.x != 0.0f)
{
float const tx1 ((min.x - _origin.x) / _direction.x);
float const tx2 ((max.x - _origin.x) / _direction.x);
auto calculate_tmin_tmax = [](float origin, float direction, float min, float max, float& tmin, float& tmax) {
if (direction != 0.0f) {
float t1 = (min - origin) / direction;
float t2 = (max - origin) / direction;
tmin = std::max(tmin, std::min(t1, t2));
tmax = std::min(tmax, std::max(t1, t2));
}
};
tmin = std::max (tmin, std::min (tx1, tx2));
tmax = std::min (tmax, std::max (tx1, tx2));
}
if (_direction.y != 0.0f)
{
float const ty1 ((min.y - _origin.y) / _direction.y);
float const ty2 ((max.y - _origin.y) / _direction.y);
tmin = std::max (tmin, std::min (ty1, ty2));
tmax = std::min (tmax, std::max (ty1, ty2));
}
if (_direction.z != 0.0f)
{
float const tz1 ((min.z - _origin.z) / _direction.z);
float const tz2 ((max.z - _origin.z) / _direction.z);
tmin = std::max (tmin, std::min (tz1, tz2));
tmax = std::min (tmax, std::max (tz1, tz2));
}
calculate_tmin_tmax(_origin.x, _direction.x, min.x, max.x, tmin, tmax);
calculate_tmin_tmax(_origin.y, _direction.y, min.y, max.y, tmin, tmax);
calculate_tmin_tmax(_origin.z, _direction.z, min.z, max.z, tmin, tmax);
if (tmax >= tmin)
{
@@ -49,7 +35,7 @@ namespace math
}
std::optional<float> ray::intersect_triangle
(glm::vec3 const& v0, glm::vec3 const& v1, glm::vec3 const& v2) const
(glm::vec3 const& v0, glm::vec3 const& v1, glm::vec3 const& v2) const noexcept
{
glm::vec3 e1 (v1 - v0);
glm::vec3 e2 (v2 - v0);
@@ -58,7 +44,8 @@ namespace math
float const det = glm::dot(e1, P);
if (det == 0.0f)
constexpr float epsilon = std::numeric_limits<float>::epsilon();
if (std::fabs(det) < epsilon) // if (det == 0.0f)
{
return std::nullopt;
}
@@ -81,7 +68,7 @@ namespace math
float const dott = glm::dot(e2 , Q) / det;
if (dott > std::numeric_limits<float>::min())
if (dott > epsilon) // if (dott > std::numeric_limits<float>::min())
{
return dott;
}

View File

@@ -2,25 +2,32 @@
#pragma once
#include <glm/mat4x4.hpp>
#include <optional>
#include <cmath>
namespace math
{
struct ray
{
ray (glm::vec3 origin, glm::vec3 const& direction): _origin (std::move (origin)), _direction (glm::normalize(direction))
{}
ray (glm::vec3 origin, glm::vec3 const& direction)
: _origin (std::move (origin)), _direction (glm::normalize(direction))
{
if (std::isnan(_direction.x) || std::isnan(_direction.y) || std::isnan(_direction.z))
{
std::cout << "Vector contains NaN values!" << std::endl;
}
}
ray (glm::mat4x4 const& transform, ray const& other): ray (
glm::vec3(
(transform * glm::vec4(other._origin.x, other._origin.y, other._origin.z, 1.0))),
glm::vec3((transform * glm::vec4(other._direction.x, other._direction.y, other._direction.z, 0.0)))
ray (glm::mat4x4 const& transform, ray const& other)
: ray (
glm::vec3(transform * glm::vec4(other._origin, 1.0f)),
glm::vec3((glm::mat3(transform) * other._direction))
)
{}
std::optional<float> intersect_bounds
(glm::vec3 const& _min, glm::vec3 const& _max) const;
(glm::vec3 const& _min, glm::vec3 const& _max) const noexcept;
std::optional<float> intersect_triangle
(glm::vec3 const& _v0, glm::vec3 const& _v1, glm::vec3 const& _v2) const;
(glm::vec3 const& _v0, glm::vec3 const& _v1, glm::vec3 const& _v2) const noexcept;
glm::vec3 position (float distance) const
{

View File

@@ -43,7 +43,7 @@ namespace misc
// Perspective division to move to normalized device coordinates (NDC)
float ndcX = clipSpacePos.x / clipSpacePos.w;
float ndcY = clipSpacePos.y / clipSpacePos.w;
float ndcZ = clipSpacePos.z / clipSpacePos.w;
// float ndcZ = clipSpacePos.z / clipSpacePos.w; // unnecessary but could be used for extra checks
// If the point is out of the normalized device coordinates range, it's off-screen
if (ndcX < -1.0f || ndcX > 1.0f || ndcY < -1.0f || ndcY > 1.0f)
@@ -55,6 +55,8 @@ namespace misc
// Convert NDC to screen space coordinates
clipSpacePos.x = (ndcX + 1.0f) * 0.5f * viewport_width;
clipSpacePos.y = (1.0f - (ndcY + 1.0f) * 0.5f) * viewport_height;
// test inverting the MapView::normalized_device_coords formula.
// clipSpacePos.y = (1.0f - ndcY) * 0.5f * viewport_height;
// from MapView::normalized_device_coords
// x 2.0f * x / viewport_width - 1.0f

View File

@@ -68,6 +68,12 @@ namespace misc
bool pointInside(glm::vec3 point, std::array<glm::vec3, 2> const& extents);
bool pointInside(glm::vec2 point, std::array<glm::vec2, 2> const& extents);
inline glm::vec4 normalized_device_coords(int x, int y, int screen_width, int screen_height)
{
return { 2.0f * x / screen_width - 1.0f, 1.0f - 2.0f * y / screen_height, 0.0f, 1.0f };
}
void minmax(glm::vec3* a, glm::vec3* b);
inline int rounded_int_div(int value, int div)

View File

@@ -1,19 +1,21 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/ModelInstance.h>
#include <noggit/Log.h>
#include <noggit/Misc.h> // checkinside
#include <noggit/Model.h> // Model, etc.
#include <noggit/WMOInstance.h>
#include <noggit/ContextObject.hpp>
#include <noggit/rendering/Primitives.hpp>
#include <opengl/scoped.hpp>
#include <opengl/shader.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <math/bounding_box.hpp>
#include <math/frustum.hpp>
#include <glm/glm.hpp>
#include <noggit/Log.h>
#include <noggit/Misc.h> // checkinside
#include <noggit/Model.h> // Model, etc.
#include <noggit/ModelInstance.h>
#include <noggit/WMOInstance.h>
#include <noggit/ContextObject.hpp>
#include <noggit/rendering/Primitives.hpp>
#include <opengl/scoped.hpp>
#include <opengl/shader.hpp>
#include <glm/vec3.hpp>
#include <sstream>
@@ -88,12 +90,17 @@ void ModelInstance::draw_box (glm::mat4x4 const& model_view
}
}
std::vector<std::tuple<int, int, int>> ModelInstance::intersect (glm::mat4x4 const& model_view
void ModelInstance::intersect (glm::mat4x4 const& model_view
, math::ray const& ray
, selection_result* results
, int animtime
)
{
if (!finishedLoading() || model->loading_failed())
return;
ensureExtents();
std::vector<std::tuple<int, int, int>> triangle_indices;
math::ray subray (_transform_mat_inverted, ray);
@@ -102,7 +109,7 @@ std::vector<std::tuple<int, int, int>> ModelInstance::intersect (glm::mat4x4 con
)
)
{
return triangle_indices;
return;
}
for (auto&& result : model->intersect (model_view, subray, animtime))
@@ -112,7 +119,7 @@ std::vector<std::tuple<int, int, int>> ModelInstance::intersect (glm::mat4x4 con
results->emplace_back (result.first * scale, this);
triangle_indices.emplace_back(result.second);
}
return triangle_indices;
return;
}
@@ -189,23 +196,26 @@ void ModelInstance::recalcExtents()
//! the model is bad itself. We *could* detect that case and explicitly
//! assume {-1, 1} then, to be nice to fuckported models.
auto corners_in_world = std::vector<glm::vec3>();
std::array<glm::vec3, 8> corners_in_world;
auto transform = misc::transform_model_box_coords;
auto points = relative_to_model.all_corners();
for (auto& point : points)
std::array<glm::vec3, 8> points = relative_to_model.all_corners();
for (int i = 0; i < 8; ++i)
// for (auto& point : points)
{
point = transform(point);
corners_in_world.push_back(point);
// points[i] = transform(points[i]);
corners_in_world[i] = transform(points[i]);
}
auto rotated_corners_in_world = std::vector<glm::vec3>();
auto transposedMat = _transform_mat;
for (auto const& point : corners_in_world)
std::array<glm::vec3, 8> rotated_corners_in_world;
// for (auto const& point : corners_in_world)
for (int i = 0; i < 8; ++i)
{
rotated_corners_in_world.emplace_back(transposedMat * glm::vec4(point, 1.f));
rotated_corners_in_world[i] = _transform_mat * glm::vec4(corners_in_world[i], 1.f);
}
math::aabb const bounding_of_rotated_points (rotated_corners_in_world);
math::aabb const bounding_of_rotated_points (std::vector<glm::vec3>(rotated_corners_in_world.begin()
, rotated_corners_in_world.end()));
extents[0] = bounding_of_rotated_points.min;
extents[1] = bounding_of_rotated_points.max;

View File

@@ -81,7 +81,7 @@ public:
);
std::vector<std::tuple<int, int, int>> intersect(glm::mat4x4 const& model_view
void intersect(glm::mat4x4 const& model_view
, math::ray const&
, selection_result*
, int animtime

View File

@@ -136,6 +136,9 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader
void WMOInstance::intersect (math::ray const& ray, selection_result* results, bool do_exterior)
{
if (!finishedLoading() || wmo->loading_failed())
return;
ensureExtents();
if (!ray.intersect_bounds (extents[0], extents[1]))
@@ -160,7 +163,7 @@ std::array<glm::vec3, 2> const& WMOInstance::getExtents()
void WMOInstance::ensureExtents()
{
if (_need_recalc_extents && wmo->finishedLoading())
if ( (_need_recalc_extents || _update_group_extents) && wmo->finishedLoading())
{
recalcExtents();
}
@@ -234,11 +237,11 @@ void WMOInstance::recalcExtents()
glm::vec3 wmo_max(misc::transform_model_box_coords(wmo->extents[1]));
auto&& root_points = math::aabb(wmo_min, wmo_max).all_corners();
auto adjustedPoints = std::vector<glm::vec3>();
std::array<glm::vec3, 8> adjustedPoints;
for (auto const& point : root_points)
for (int i = 0; i < 8; ++i)
{
adjustedPoints.push_back(_transform_mat * glm::vec4(point, 1.f));
adjustedPoints[i] = _transform_mat * glm::vec4(root_points[i], 1.f);
}
points.insert(points.end(), adjustedPoints.begin(), adjustedPoints.end());
@@ -248,24 +251,24 @@ void WMOInstance::recalcExtents()
auto const& group = wmo->groups[i];
auto&& group_points = math::aabb(group.BoundingBoxMin, group.BoundingBoxMax).all_corners();
auto adjustedGroupPoints = std::vector<glm::vec3>();
std::array<glm::vec3, 8> adjustedGroupPoints;
for (auto const& point : group_points)
for (int i = 0; i < 8; ++i)
{
adjustedGroupPoints.push_back(_transform_mat * glm::vec4(point, 1.f));
adjustedGroupPoints[i] = _transform_mat * glm::vec4(group_points[i], 1.f);
}
points.insert(points.end(), adjustedGroupPoints.begin(), adjustedGroupPoints.end());
if (group.has_skybox() /* || _update_group_extents*/)
if (group.has_skybox() || _update_group_extents)
{
math::aabb const group_aabb(adjustedGroupPoints);
math::aabb const group_aabb(std::vector<glm::vec3>(adjustedGroupPoints.begin()
, adjustedGroupPoints.end()));
group_extents[i] = {group_aabb.min, group_aabb.max};
_update_group_extents = false;
}
}
_update_group_extents = false;
math::aabb const wmo_aabb(points);

View File

@@ -1767,7 +1767,7 @@ void World::loadAllTiles()
if (mTile)
{
mTile->wait_until_loaded();
// mTile->wait_until_loaded();
}
}
}
@@ -3626,13 +3626,14 @@ void World::select_objects_in_area(
}
glm::mat4 VPmatrix = projection * view;
glm::mat4x4 const invertedViewMatrix = glm::inverse(VPmatrix);
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 processed_obj_count = 0; // num objects that had a raycast test at least once
// int debug_count_obj_min_size = 0;
// int debug_count_obj_min_size_not = 0;
@@ -3674,105 +3675,161 @@ void World::select_objects_in_area(
for (auto& pair : tile->getObjectInstances())
{
[[unlikely]]
if (!pair.first->finishedLoading())
if (!pair.first->finishedLoading() || pair.first->loading_failed())
continue;
auto objectType = pair.second[0]->which();
[[unlikely]]
if (!(objectType == eMODEL || objectType == eWMO))
if (pair.second.empty())
continue;
SceneObjectTypes objectType = pair.second[0]->which();
// check if object is hidden
if (objectType == eWMO)
{
WMOInstance* model_instance = static_cast<WMOInstance*>(pair.second[0]);
if (model_instance->wmo->is_hidden())
continue;
}
else if (objectType == eMODEL)
{
ModelInstance* model_instance = static_cast<ModelInstance*>(pair.second[0]);
if (model_instance->model->is_hidden())
continue;
}
else
[[unlikely]]
{
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;
const float distance = glm::distance(camera_position, instance->pos);
if (distance > user_depth || distance > renderer()->cullDistance())
continue;
// Old code to check position point instead of bound box
math::aabb obj_aabb(instance->getExtents()[0], instance->getExtents()[1]);
auto aabb_center = obj_aabb.center();
bool point_valid = false;
auto origin_screen_pos = misc::projectPointToScreen(aabb_center, VPmatrix, viewport_width, viewport_height, point_valid);
// 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 (!origin_screen_pos.w < -instance->getBoundingRadius())
{
glm::vec4 screenPos = VPmatrix * glm::vec4(instance->pos, 1.0f);
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())
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
bool do_selection = false;
// check if position point is within rectangle first because it is much cheaper
{
const glm::vec2 screenPos2D = glm::vec2(origin_screen_pos);
if (misc::pointInside(screenPos2D, selection_box))
{
const glm::vec2 screenPos2D = glm::vec2(screenPos);
if (misc::pointInside(screenPos2D, selection_box))
// processed_obj_count++;
// check if center point is occluded by terrain
if (processed_obj_count < max_position_raycast_processing &&
!is_point_occluded_by_terrain(aabb_center, view, VPmatrix, viewport_width, viewport_height, camera_position))
{
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 not occluded, select it and skip other checks
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) )
// if center point raycast didn't succeed, check again if bounding box is within selection in 2D screen space to test other points
std::array<glm::vec2, 2> aabb_screnbounds;
if (!do_selection)
{
bool valid = false;
auto screenBounds = misc::getAABBScreenBounds(instance->getExtents(), VPmatrix
aabb_screnbounds = misc::getAABBScreenBounds(instance->getExtents(), VPmatrix
, viewport_width, viewport_height, valid, 0.75f);
if (valid && math::boxIntersects(screenBounds[0], screenBounds[1]
if (valid && math::boxIntersects(aabb_screnbounds[0], aabb_screnbounds[1]
, selection_box[0], selection_box[1]))
{
// do_selection = true;
}
else
{
// if rectangles don't intersect, just skip
continue;
}
// Optimization : Only do raycast bounds checks for object that take enough screen space
// if object is too small checking other points is useless
if (!do_selection)
{
if (glm::distance(screenBounds[0], screenBounds[1]) < obj_raycast_min_size)
{
// debug_count_obj_min_size++;
continue;
}
else
float bounds_size = glm::distance(aabb_screnbounds[0], aabb_screnbounds[1]);
if ( bounds_size < obj_raycast_min_size || !instance->_rendered_last_frame)
{
// debug_count_obj_min_size_not++;
continue;
}
else if (processed_obj_count > max_bounds_raycast_processing)
{
// select it anyways
do_selection = true;
// debug_count_obj_min_size++;
}
}
}
// Occlusion test on object's corners (that are in selection box)
// uses ray casting, very expensive
if (!do_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)*/)
{
math::aabb obj_aabb(instance->getExtents()[0], instance->getExtents()[1]);
processed_obj_count++;
// get the center of the intersectino rectangle
// 1 : get the intersection rectangle of screen space and bounding box
glm::vec2 intersectionMin = glm::max(aabb_screnbounds[0], selection_box[0]);
glm::vec2 intersectionMax = glm::min(aabb_screnbounds[1], selection_box[1]);
// Check for Valid Intersection:
if (intersectionMin.x < intersectionMax.x && intersectionMin.y < intersectionMax.y) {
// Valid intersection
}
else {
// No intersection, shouldn't happen
continue;
}
// 2 : get center
glm::vec2 intersectionCenter = (intersectionMin + intersectionMax) * 0.5f;
// 3 : convert 2D screenspace point back to 3d
glm::vec4 normalisedView = invertedViewMatrix * misc::normalized_device_coords(intersectionCenter.x, intersectionCenter.y,
viewport_width, viewport_height);
glm::vec3 intersectionCenter_pos = glm::vec3(normalisedView.x / normalisedView.w, normalisedView.y / normalisedView.w, normalisedView.z / normalisedView.w);
auto obj_aabb_corners = obj_aabb.all_corners();
// int required_num_unoccluded_corers = 6; // require 6 of 8 corners to not be occluded
// Iterate key points instead of all 8 corners
std::vector<glm::vec3> key_points = {
// intersectionCenter_pos,
// (obj_aabb_corners[0] + obj_aabb_corners[6]) * 0.5f, // Center between top corners
obj_aabb_corners[0], // Top-right-front
obj_aabb_corners[5], // Top-left-back
obj_aabb_corners[4], // Top-left-front
obj_aabb_corners[1] // Top-right-back
};
// int required_num_unoccluded_corners = 2;
bool object_occluded = true;
for (const auto& corner : obj_aabb_corners)
// check if points are occluded by terrain
for (const auto& corner : key_points /*obj_aabb_corners*/)
{
// TODO : only need to do max top left and max top right in 2d instead of all corners?
@@ -3789,6 +3846,7 @@ void World::select_objects_in_area(
if (!corner_occluded)
{
// if just one point isn't occluded is enough, select object
object_occluded = false;
break;
}
@@ -3801,31 +3859,12 @@ void World::select_objects_in_area(
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);
}
}
add_to_selection(instance, false, false);
}
}
}
this->update_selection_pivot();
this->update_selection_pivot();
}
@@ -3868,14 +3907,13 @@ bool World::is_point_occluded_by_terrain(const glm::vec3& point,
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
if (terrain_hit.first + 5.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);

View File

@@ -87,6 +87,7 @@ namespace Noggit::Rendering
[[nodiscard]] std::unique_ptr<Skies>& skies() { return _skies; };
float _view_distance;
inline float cullDistance() const { return _cull_distance; }
private: