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);