diff --git a/include/isurfacerenderer.h b/include/isurfacerenderer.h index 942726ad75..9a0f194610 100644 --- a/include/isurfacerenderer.h +++ b/include/isurfacerenderer.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -33,6 +34,10 @@ class IRenderableSurface // The surface bounds in local coordinates virtual const AABB& getSurfaceBounds() = 0; + + // Emitted when the surface bounds have changed, + // because it has been either moved or resized. + virtual sigc::signal& signal_boundsChanged() = 0; }; /** diff --git a/libs/render/RenderableSurface.h b/libs/render/RenderableSurface.h index 64fa9aefee..a02c53b32a 100644 --- a/libs/render/RenderableSurface.h +++ b/libs/render/RenderableSurface.h @@ -24,6 +24,8 @@ class RenderableSurface : using ShaderMapping = std::map; ShaderMapping _shaders; + sigc::signal _sigBoundsChanged; + protected: RenderableSurface() {} @@ -52,12 +54,20 @@ class RenderableSurface : } // Notifies all the attached shaders that the surface geometry changed + // Also fires the bounds changed signal void queueUpdate() { for (auto& [shader, slot] : _shaders) { shader->updateSurface(slot); } + + boundsChanged(); + } + + void boundsChanged() + { + _sigBoundsChanged.emit(); } // Removes the surface from all shaders @@ -78,6 +88,11 @@ class RenderableSurface : shader->renderSurface(slot); } + sigc::signal& signal_boundsChanged() override + { + return _sigBoundsChanged; + } + private: void detachFromShader(const ShaderMapping::iterator& iter) { diff --git a/libs/scene/Node.h b/libs/scene/Node.h index 158bdb9e52..0b1fbe0ba7 100644 --- a/libs/scene/Node.h +++ b/libs/scene/Node.h @@ -150,7 +150,7 @@ class Node: public LitObject, public virtual INode, public std::enable_shared_fr const Matrix4& localToWorld() const override; - void transformChangedLocal() override; + virtual void transformChangedLocal() override; void transformChanged() override; diff --git a/radiantcore/entity/RenderableSurfaceCollection.h b/radiantcore/entity/RenderableSurfaceCollection.h index 954bb2f56f..0db16f0310 100644 --- a/radiantcore/entity/RenderableSurfaceCollection.h +++ b/radiantcore/entity/RenderableSurfaceCollection.h @@ -1,18 +1,23 @@ #pragma once -#include +#include +#include +#include +#include #include "irender.h" +#include "itextstream.h" namespace entity { -class RenderableSurfaceCollection +class RenderableSurfaceCollection : + public sigc::trackable { private: AABB _collectionBounds; bool _collectionBoundsNeedUpdate; - std::set _surfaces; + std::map _surfaces; public: RenderableSurfaceCollection() : @@ -21,13 +26,34 @@ class RenderableSurfaceCollection void addSurface(const render::IRenderableSurface::Ptr& surface) { - _surfaces.insert(surface); + sigc::connection subscription = surface->signal_boundsChanged().connect( + sigc::mem_fun(*this, &RenderableSurfaceCollection::onSurfaceBoundsChanged)); + + if (!_surfaces.try_emplace(surface, subscription).second) + { + // We've already been subscribed to this one + subscription.disconnect(); + rWarning() << "Renderable surface has already been attached to entity" << std::endl; + return; + } + _collectionBoundsNeedUpdate = true; } void removeSurface(const render::IRenderableSurface::Ptr& surface) { - _surfaces.erase(surface); + auto mapping = _surfaces.find(surface); + + if (mapping != _surfaces.end()) + { + mapping->second.disconnect(); + _surfaces.erase(mapping); + } + else + { + rWarning() << "Renderable surface has not been attached to entity" << std::endl; + } + _collectionBoundsNeedUpdate = true; } @@ -39,10 +65,9 @@ class RenderableSurfaceCollection ensureBoundsUpToDate(); // If the whole collection doesn't intersect, quit early - // TODO: bounds not updated when surface changes - //if (!_collectionBounds.intersects(bounds)) return; + if (!_collectionBounds.intersects(bounds)) return; - for (const auto& surface : _surfaces) + for (const auto& [surface, _] : _surfaces) { auto orientedBounds = AABB::createFromOrientedAABBSafe( surface->getSurfaceBounds(), surface->getSurfaceTransform()); @@ -55,6 +80,11 @@ class RenderableSurfaceCollection } private: + void onSurfaceBoundsChanged() + { + _collectionBoundsNeedUpdate = true; + } + void ensureBoundsUpToDate() { if (!_collectionBoundsNeedUpdate) return; @@ -63,7 +93,7 @@ class RenderableSurfaceCollection _collectionBounds = AABB(); - for (const auto& surface : _surfaces) + for (const auto& [surface, _] : _surfaces) { _collectionBounds.includeAABB(AABB::createFromOrientedAABBSafe( surface->getSurfaceBounds(), surface->getSurfaceTransform())); diff --git a/radiantcore/model/NullModelNode.cpp b/radiantcore/model/NullModelNode.cpp index 6b1209279d..3fbc559db3 100644 --- a/radiantcore/model/NullModelNode.cpp +++ b/radiantcore/model/NullModelNode.cpp @@ -125,6 +125,13 @@ void NullModelNode::onRemoveFromScene(scene::IMapRootNode& root) _renderableBox.detach(); } +void NullModelNode::transformChangedLocal() +{ + Node::transformChangedLocal(); + + _renderableBox.boundsChanged(); +} + void NullModelNode::onVisibilityChanged(bool isVisibleNow) { Node::onVisibilityChanged(isVisibleNow); diff --git a/radiantcore/model/NullModelNode.h b/radiantcore/model/NullModelNode.h index 7e8a457544..785d1b0bc1 100644 --- a/radiantcore/model/NullModelNode.h +++ b/radiantcore/model/NullModelNode.h @@ -57,6 +57,7 @@ class NullModelNode final : const AABB& localAABB() const override; void onRemoveFromScene(scene::IMapRootNode& root) override; + void transformChangedLocal() override; protected: void onVisibilityChanged(bool isVisibleNow) override; diff --git a/radiantcore/model/StaticModelNode.cpp b/radiantcore/model/StaticModelNode.cpp index 0fcbed6d04..854a8e7dd9 100644 --- a/radiantcore/model/StaticModelNode.cpp +++ b/radiantcore/model/StaticModelNode.cpp @@ -186,6 +186,16 @@ bool StaticModelNode::getIntersection(const Ray& ray, Vector3& intersection) return _model->getIntersection(ray, intersection, localToWorld()); } +void StaticModelNode::transformChangedLocal() +{ + Node::transformChangedLocal(); + + for (auto& surface : _renderableSurfaces) + { + surface->boundsChanged(); + } +} + // Skin changed notify void StaticModelNode::skinChanged(const std::string& newSkinName) { diff --git a/radiantcore/model/StaticModelNode.h b/radiantcore/model/StaticModelNode.h index 1f599798e5..6672cd1dc8 100644 --- a/radiantcore/model/StaticModelNode.h +++ b/radiantcore/model/StaticModelNode.h @@ -95,6 +95,8 @@ class StaticModelNode final : // Traceable implementation bool getIntersection(const Ray& ray, Vector3& intersection) override; + void transformChangedLocal() override; + protected: void _onTransformationChanged() override; void _applyTransformation() override; diff --git a/radiantcore/model/md5/MD5ModelNode.cpp b/radiantcore/model/md5/MD5ModelNode.cpp index 440a3af0c1..137f978549 100644 --- a/radiantcore/model/md5/MD5ModelNode.cpp +++ b/radiantcore/model/md5/MD5ModelNode.cpp @@ -212,4 +212,14 @@ void MD5ModelNode::onModelAnimationUpdated() } } +void MD5ModelNode::transformChangedLocal() +{ + Node::transformChangedLocal(); + + for (auto& surface : _renderableSurfaces) + { + surface->boundsChanged(); + } +} + } // namespace md5 diff --git a/radiantcore/model/md5/MD5ModelNode.h b/radiantcore/model/md5/MD5ModelNode.h index 8fcbc9d8f7..33bfa07ead 100644 --- a/radiantcore/model/md5/MD5ModelNode.h +++ b/radiantcore/model/md5/MD5ModelNode.h @@ -81,6 +81,8 @@ class MD5ModelNode : virtual std::string getSkin() const override; void skinChanged(const std::string& newSkinName) override; + void transformChangedLocal() override; + protected: void onVisibilityChanged(bool isVisibleNow) override;