From ebccf801f465f391691e37524acb224d00c6cca1 Mon Sep 17 00:00:00 2001 From: Robin Seger Date: Wed, 25 Mar 2026 20:27:56 +0100 Subject: [PATCH] physics throttling, targeted dirty marking, proximity-based border - Snap deformation to rest when imperceptible instead of pumping frames - Only trigger markDirty on geometry changes exceeding 0.5px - Only include inverted rect data when rect is near the border --- plugin/src/Caelestia/Blobs/blobgroup.cpp | 34 ++++++++++++++++ plugin/src/Caelestia/Blobs/blobgroup.hpp | 1 + plugin/src/Caelestia/Blobs/blobrect.cpp | 31 +++++++++++---- plugin/src/Caelestia/Blobs/blobshape.cpp | 50 ++++++++++++++++++------ 4 files changed, 96 insertions(+), 20 deletions(-) diff --git a/plugin/src/Caelestia/Blobs/blobgroup.cpp b/plugin/src/Caelestia/Blobs/blobgroup.cpp index f06c4d51..ab4a31e6 100644 --- a/plugin/src/Caelestia/Blobs/blobgroup.cpp +++ b/plugin/src/Caelestia/Blobs/blobgroup.cpp @@ -66,6 +66,40 @@ void BlobGroup::markDirty() { } } +void BlobGroup::markShapeDirty(BlobShape* source) { + m_physicsUpdated = false; + + source->polish(); + source->update(); + + // Use cached padded rects to find spatial neighbors + const float pad = static_cast(m_smoothing) * 2.0f; + const QRectF srcRect( + static_cast(source->m_cachedPaddedX - pad), + static_cast(source->m_cachedPaddedY - pad), + static_cast(source->m_cachedPaddedW + pad * 2.0f), + static_cast(source->m_cachedPaddedH + pad * 2.0f)); + + for (auto* shape : std::as_const(m_shapes)) { + if (shape == source) + continue; + const QRectF otherRect( + static_cast(shape->m_cachedPaddedX), + static_cast(shape->m_cachedPaddedY), + static_cast(shape->m_cachedPaddedW), + static_cast(shape->m_cachedPaddedH)); + if (srcRect.intersects(otherRect)) { + shape->polish(); + shape->update(); + } + } + + if (m_invertedRect && static_cast(m_invertedRect) != source) { + static_cast(m_invertedRect)->polish(); + static_cast(m_invertedRect)->update(); + } +} + void BlobGroup::ensurePhysicsUpdated() { if (m_physicsUpdated) return; diff --git a/plugin/src/Caelestia/Blobs/blobgroup.hpp b/plugin/src/Caelestia/Blobs/blobgroup.hpp index 4c9ed691..b2e83edb 100644 --- a/plugin/src/Caelestia/Blobs/blobgroup.hpp +++ b/plugin/src/Caelestia/Blobs/blobgroup.hpp @@ -38,6 +38,7 @@ public: BlobInvertedRect* invertedRect() const { return m_invertedRect; } void markDirty(); + void markShapeDirty(BlobShape* source); void ensurePhysicsUpdated(); signals: diff --git a/plugin/src/Caelestia/Blobs/blobrect.cpp b/plugin/src/Caelestia/Blobs/blobrect.cpp index e03baa22..ff021203 100644 --- a/plugin/src/Caelestia/Blobs/blobrect.cpp +++ b/plugin/src/Caelestia/Blobs/blobrect.cpp @@ -16,13 +16,30 @@ void BlobRect::updatePolish() { BlobShape::updatePolish(); if (m_physicsActive) { - QMetaObject::invokeMethod( - this, - [this]() { - if (m_physicsActive && m_group) - m_group->markDirty(); - }, - Qt::QueuedConnection); + // Check if deformation is visually imperceptible + float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + + std::abs(m_dm11 - 1.0f); + float totalVel = + std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); + + if (totalDelta < 0.004f && totalVel < 0.05f) { + // Snap to rest, no visible deformation + m_dm00 = 1.0f; + m_dm01 = 0.0f; + m_dm11 = 1.0f; + m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f; + m_deformMatrix = QMatrix4x4(); + updateCenteredDeformMatrix(); + m_physicsActive = false; + } else { + QMetaObject::invokeMethod( + this, + [this]() { + if (m_physicsActive && m_group) + m_group->markDirty(); + }, + Qt::QueuedConnection); + } } } diff --git a/plugin/src/Caelestia/Blobs/blobshape.cpp b/plugin/src/Caelestia/Blobs/blobshape.cpp index b99bd8e1..a7938a50 100644 --- a/plugin/src/Caelestia/Blobs/blobshape.cpp +++ b/plugin/src/Caelestia/Blobs/blobshape.cpp @@ -70,8 +70,15 @@ void BlobShape::geometryChange( const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); updateCenteredDeformMatrix(); - if (m_group) - m_group->markDirty(); + if (m_group) { + // Only trigger redraw if the change is visually meaningful + const auto dx = std::abs(newGeometry.x() - oldGeometry.x()); + const auto dy = std::abs(newGeometry.y() - oldGeometry.y()); + const auto dw = std::abs(newGeometry.width() - oldGeometry.width()); + const auto dh = std::abs(newGeometry.height() - oldGeometry.height()); + if (dx > 0.5 || dy > 0.5 || dw > 0.5 || dh > 0.5) + m_group->markShapeDirty(this); + } } void BlobShape::updateCenteredDeformMatrix() { @@ -210,9 +217,6 @@ void BlobShape::updatePolish() { auto* inv = m_group->invertedRect(); if (inv) { - m_cachedHasInverted = true; - m_cachedInvertedRadius = static_cast(inv->radius()); - const QPointF invScene = inv->mapToScene(QPointF(0, 0)); const float outerCX = static_cast(invScene.x() + inv->width() / 2.0); @@ -234,15 +238,35 @@ void BlobShape::updatePolish() { outerHH - static_cast((inv->borderTop() + inv->borderBottom()) / 2.0); - m_cachedInvertedOuter[0] = outerCX; - m_cachedInvertedOuter[1] = outerCY; - m_cachedInvertedOuter[2] = outerHW; - m_cachedInvertedOuter[3] = outerHH; + // Check if this rect is near the border (within 2x smoothing of inner edge) + bool nearBorder = isInvertedRect(); + if (!nearBorder) { + const float margin = pad * 2.0f; + const float myCX = m_cachedPaddedX + m_cachedPaddedW * 0.5f; + const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f; + const float myHW = m_cachedPaddedW * 0.5f; + const float myHH = m_cachedPaddedH * 0.5f; + // Near border if any edge of padded rect is within margin of inner edge + nearBorder = (myCX - myHW < innerCX - innerHW + margin) || + (myCX + myHW > innerCX + innerHW - margin) || + (myCY - myHH < innerCY - innerHH + margin) || + (myCY + myHH > innerCY + innerHH - margin); + } - m_cachedInvertedInner[0] = innerCX; - m_cachedInvertedInner[1] = innerCY; - m_cachedInvertedInner[2] = innerHW; - m_cachedInvertedInner[3] = innerHH; + if (nearBorder) { + m_cachedHasInverted = true; + m_cachedInvertedRadius = static_cast(inv->radius()); + + m_cachedInvertedOuter[0] = outerCX; + m_cachedInvertedOuter[1] = outerCY; + m_cachedInvertedOuter[2] = outerHW; + m_cachedInvertedOuter[3] = outerHH; + + m_cachedInvertedInner[0] = innerCX; + m_cachedInvertedInner[1] = innerCY; + m_cachedInvertedInner[2] = innerHW; + m_cachedInvertedInner[3] = innerHH; + } } // Pre-compute corner fill factors (moves O(N²) work from GPU to CPU)