diff --git a/doc/classes/VisibilityNotifier.xml b/doc/classes/VisibilityNotifier.xml index b702dee9290..bb1022c7c09 100644 --- a/doc/classes/VisibilityNotifier.xml +++ b/doc/classes/VisibilityNotifier.xml @@ -23,6 +23,10 @@ The VisibilityNotifier's bounding box. + + In addition to checking whether a node is on screen or within a [Camera]'s view, VisibilityNotifier can also optionally check whether a node is within a specified maximum distance when using a [Camera] with perspective projection. This is useful for throttling the performance requirements of nodes that are far away. + [b]Note:[/b] This feature will be disabled if set to 0.0. + diff --git a/scene/3d/visibility_notifier.cpp b/scene/3d/visibility_notifier.cpp index acc1ec3e686..7cb7c746795 100644 --- a/scene/3d/visibility_notifier.cpp +++ b/scene/3d/visibility_notifier.cpp @@ -72,6 +72,24 @@ void VisibilityNotifier::_exit_camera(Camera *p_camera) { } } +void VisibilityNotifier::set_max_distance(real_t p_max_distance) { + if (p_max_distance > CMP_EPSILON) { + _max_distance = p_max_distance; + _max_distance_squared = _max_distance * _max_distance; + _max_distance_active = true; + + // make sure world aabb centre is up to date + if (is_inside_world()) { + AABB world_aabb = get_global_transform().xform(aabb); + _world_aabb_center = world_aabb.get_center(); + } + } else { + _max_distance = 0.0; + _max_distance_squared = 0.0; + _max_distance_active = false; + } +} + void VisibilityNotifier::set_aabb(const AABB &p_aabb) { if (aabb == p_aabb) { return; @@ -79,7 +97,9 @@ void VisibilityNotifier::set_aabb(const AABB &p_aabb) { aabb = p_aabb; if (is_inside_world()) { - get_world()->_update_notifier(this, get_global_transform().xform(aabb)); + AABB world_aabb = get_global_transform().xform(aabb); + get_world()->_update_notifier(this, world_aabb); + _world_aabb_center = world_aabb.get_center(); } _change_notify("aabb"); @@ -128,11 +148,15 @@ void VisibilityNotifier::_notification(int p_what) { AABB world_aabb = get_global_transform().xform(aabb); world->_register_notifier(this, world_aabb); + _world_aabb_center = world_aabb.get_center(); _refresh_portal_mode(); } break; case NOTIFICATION_TRANSFORM_CHANGED: { AABB world_aabb = get_global_transform().xform(aabb); world->_update_notifier(this, world_aabb); + if (_max_distance_active) { + _world_aabb_center = world_aabb.get_center(); + } if (_cull_instance_rid != RID()) { VisualServer::get_singleton()->ghost_update(_cull_instance_rid, world_aabb); @@ -176,7 +200,11 @@ void VisibilityNotifier::_bind_methods() { ClassDB::bind_method(D_METHOD("get_aabb"), &VisibilityNotifier::get_aabb); ClassDB::bind_method(D_METHOD("is_on_screen"), &VisibilityNotifier::is_on_screen); + ClassDB::bind_method(D_METHOD("set_max_distance", "distance"), &VisibilityNotifier::set_max_distance); + ClassDB::bind_method(D_METHOD("get_max_distance"), &VisibilityNotifier::get_max_distance); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "aabb"), "set_aabb", "get_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_RANGE, "0,32768,0.01"), "set_max_distance", "get_max_distance"); ADD_SIGNAL(MethodInfo("camera_entered", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera"))); ADD_SIGNAL(MethodInfo("camera_exited", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera"))); @@ -188,6 +216,10 @@ VisibilityNotifier::VisibilityNotifier() { aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2)); set_notify_transform(true); _in_gameplay = false; + _max_distance_active = false; + _max_distance = 0.0; + _max_distance_squared = 0.0; + _max_distance_leadin_counter = 1; // this could later be exposed as a property if necessary } VisibilityNotifier::~VisibilityNotifier() { diff --git a/scene/3d/visibility_notifier.h b/scene/3d/visibility_notifier.h index cc1cdf64985..f23d0ea2b65 100644 --- a/scene/3d/visibility_notifier.h +++ b/scene/3d/visibility_notifier.h @@ -42,11 +42,21 @@ class VisibilityNotifier : public CullInstance { Set cameras; AABB aabb; + Vector3 _world_aabb_center; // if using rooms and portals RID _cull_instance_rid; bool _in_gameplay; + bool _max_distance_active; + real_t _max_distance; + real_t _max_distance_squared; + + // This is a first number of frames where distance objects + // are forced seen as visible, to make sure their animations + // and physics positions etc are something reasonable. + uint32_t _max_distance_leadin_counter; + protected: virtual void _screen_enter() {} virtual void _screen_exit() {} @@ -64,6 +74,21 @@ public: AABB get_aabb() const; bool is_on_screen() const; + // This is only currently kept up to date if max_distance is active + const Vector3 &get_world_aabb_center() const { return _world_aabb_center; } + + void set_max_distance(real_t p_max_distance); + real_t get_max_distance() const { return _max_distance; } + real_t get_max_distance_squared() const { return _max_distance_squared; } + bool is_max_distance_active() const { return _max_distance_active; } + bool inside_max_distance_leadin() { + if (!_max_distance_leadin_counter) { + return false; + } + _max_distance_leadin_counter--; + return true; + } + VisibilityNotifier(); ~VisibilityNotifier(); }; diff --git a/scene/resources/world.cpp b/scene/resources/world.cpp index f5d33eb5f8d..dc419fbd5e4 100644 --- a/scene/resources/world.cpp +++ b/scene/resources/world.cpp @@ -146,9 +146,11 @@ struct SpatialIndexer { for (Map::Element *E = cameras.front(); E; E = E->next()) { pass++; + // prepare camera info Camera *c = E->key(); - + Vector3 cam_pos = c->get_global_transform().origin; Vector planes = c->get_frustum(); + bool cam_is_ortho = c->get_projection() == Camera::PROJECTION_ORTHOGONAL; int culled = octree.cull_convex(planes, cull.ptrw(), cull.size()); @@ -160,6 +162,19 @@ struct SpatialIndexer { for (int i = 0; i < culled; i++) { //notifiers in frustum + // check and remove notifiers that have a max range + VisibilityNotifier &nt = *ptr[i]; + if (nt.is_max_distance_active() && !cam_is_ortho) { + Vector3 offset = nt.get_world_aabb_center() - cam_pos; + if ((offset.length_squared() >= nt.get_max_distance_squared()) && !nt.inside_max_distance_leadin()) { + // unordered remove + cull.set(i, cull[culled - 1]); + culled--; + i--; + continue; + } + } + Map::Element *H = E->get().notifiers.find(ptr[i]); if (!H) { E->get().notifiers.insert(ptr[i], pass);