Преглед на файлове

Merge pull request #61544 from lawnjelly/vis_notifier_max_dist

VisibilityNotifier - add max_distance feature
Rémi Verschelde преди 3 години
родител
ревизия
a9e966c319
променени са 4 файла, в които са добавени 78 реда и са изтрити 2 реда
  1. 4 0
      doc/classes/VisibilityNotifier.xml
  2. 33 1
      scene/3d/visibility_notifier.cpp
  3. 25 0
      scene/3d/visibility_notifier.h
  4. 16 1
      scene/resources/world.cpp

+ 4 - 0
doc/classes/VisibilityNotifier.xml

@@ -23,6 +23,10 @@
 		<member name="aabb" type="AABB" setter="set_aabb" getter="get_aabb" default="AABB( -1, -1, -1, 2, 2, 2 )">
 			The VisibilityNotifier's bounding box.
 		</member>
+		<member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="0.0">
+			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.
+		</member>
 	</members>
 	<signals>
 		<signal name="camera_entered">

+ 33 - 1
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() {

+ 25 - 0
scene/3d/visibility_notifier.h

@@ -42,11 +42,21 @@ class VisibilityNotifier : public CullInstance {
 	Set<Camera *> 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();
 };

+ 16 - 1
scene/resources/world.cpp

@@ -147,9 +147,11 @@ struct SpatialIndexer {
 		for (Map<Camera *, CameraData>::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<Plane> planes = c->get_frustum();
+			bool cam_is_ortho = c->get_projection() == Camera::PROJECTION_ORTHOGONAL;
 
 			int culled = octree.cull_convex(planes, cull.ptrw(), cull.size());
 
@@ -161,6 +163,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<VisibilityNotifier *, uint64_t>::Element *H = E->get().notifiers.find(ptr[i]);
 				if (!H) {
 					E->get().notifiers.insert(ptr[i], pass);