diff --git a/Plugins/ZShell/Blobs/blobrect.cpp b/Plugins/ZShell/Blobs/blobrect.cpp index ea027a9..5847fa4 100644 --- a/Plugins/ZShell/Blobs/blobrect.cpp +++ b/Plugins/ZShell/Blobs/blobrect.cpp @@ -14,15 +14,11 @@ BlobRect::~BlobRect() { } void BlobRect::updatePolish() { - BlobShape::updatePolish(); - if (m_physicsActive) { - // 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; @@ -31,6 +27,16 @@ void BlobRect::updatePolish() { emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); m_physicsActive = false; + + if (m_group) { + QMetaObject::invokeMethod( + this, + [this]() { + if (m_group) + m_group->markDirty(); + }, + Qt::QueuedConnection); + } } else { QMetaObject::invokeMethod( this, @@ -41,6 +47,8 @@ void BlobRect::updatePolish() { Qt::QueuedConnection); } } + + BlobShape::updatePolish(); } void BlobRect::updatePhysics() { @@ -56,7 +64,6 @@ void BlobRect::updatePhysics() { const float dt = static_cast(m_elapsed.restart()) / 1000.0f; if (dt > 0.1f || dt < 0.001f) { m_prevScenePos = scenePos; - // Still check atRest on skipped frames to avoid getting stuck if (m_physicsActive) checkAtRest(0.0f); return; @@ -74,8 +81,6 @@ void BlobRect::updatePhysics() { m_physicsActive = true; } - // Compute target deformation matrix from velocity - // R(θ) * diag(stretch, compress) * R(θ)^T const float kStretchFactor = static_cast(m_deformScale); constexpr float kMaxStretch = 0.35f; @@ -98,7 +103,6 @@ void BlobRect::updatePhysics() { target11 = targetStretch * sin2 + targetCompress * cos2; } - // Underdamped spring on each matrix component const float kStiffness = static_cast(m_stiffness); const float kDamping = static_cast(m_damping); @@ -238,9 +242,19 @@ void BlobRect::checkAtRest(float speed) { m_dmVel00 = 0.0f; m_dmVel01 = 0.0f; m_dmVel11 = 0.0f; - m_deformMatrix = QMatrix4x4(); // identity + m_deformMatrix = QMatrix4x4(); emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); m_physicsActive = false; + + if (m_group) { + QMetaObject::invokeMethod( + this, + [this]() { + if (m_group) + m_group->markDirty(); + }, + Qt::QueuedConnection); + } } } diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 7bcef78..68ff191 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -9,7 +9,6 @@ #include static float deformPadding(const QMatrix4x4& dm, float hw, float hh) { - // Bounding box of the deformed shape: |M * corners| const float dm00 = dm(0, 0), dm01 = dm(0, 1); const float dm10 = dm(1, 0), dm11 = dm(1, 1); const float boundX = std::abs(dm00) * hw + std::abs(dm01) * hh; @@ -69,16 +68,17 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome QQuickItem::geometryChange(newGeometry, oldGeometry); updateCenteredDeformMatrix(); if (m_group) { - // Accumulate sub-pixel drift so slow movements don't desync the shader m_pendingDx += static_cast(newGeometry.x() - oldGeometry.x()); m_pendingDy += static_cast(newGeometry.y() - oldGeometry.y()); - // Accumulate size delta across multiple frames so incremental size - // changes that are each below the threshold still trigger a dirty - // mark once their accumulated delta exceeds it. m_pendingDw += static_cast(newGeometry.width() - oldGeometry.width()); m_pendingDh += static_cast(newGeometry.height() - oldGeometry.height()); - if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || - std::abs(m_pendingDw) > 0.5f || std::abs(m_pendingDh) > 0.5f) { + + const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) + + std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f); + const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f; + + if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold || + std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) { m_pendingDx = 0; m_pendingDy = 0; m_pendingDw = 0; @@ -124,7 +124,6 @@ void BlobShape::updatePolish() { if (!m_group) return; - // Ensure all shapes have up-to-date physics (only once per frame) m_group->ensurePhysicsUpdated(); const QPointF scenePos = mapToScene(QPointF(0, 0)); @@ -149,13 +148,11 @@ void BlobShape::updatePolish() { width() + 2.0 * static_cast(totalPad), height() + 2.0 * static_cast(totalPad)); } - // Filter nearby normal rects m_cachedRects.clear(); m_cachedMyIndex = -2; const QRectF myPadded(static_cast(m_cachedPaddedX), static_cast(m_cachedPaddedY), static_cast(m_cachedPaddedW), static_cast(m_cachedPaddedH)); - // Track shape pointers parallel to m_cachedRects for pairwise exclusion lookups QVector rectShapes; rectShapes.reserve(m_group->shapes().size()); @@ -163,7 +160,6 @@ void BlobShape::updatePolish() { if (other->isInvertedRect()) continue; - // Skip zero-size rects if (other->width() <= 0 || other->height() <= 0) continue; @@ -202,7 +198,6 @@ void BlobShape::updatePolish() { r.offsetX = dm(0, 3); r.offsetY = dm(1, 3); - // Pre-compute inverse deformation matrix const float det = a * d - c * b; const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f; r.invDeform[0] = d * invDet; @@ -210,12 +205,10 @@ void BlobShape::updatePolish() { r.invDeform[2] = -c * invDet; r.invDeform[3] = a * invDet; - // Pre-compute minimum eigenvalue (avoids per-pixel sqrt) const float halfTr = 0.5f * (a + d); const float halfDiff = 0.5f * (a - d); r.minEig = halfTr - std::sqrt(halfDiff * halfDiff + c * c); - // Pre-compute screen-space AABB half-extents r.screenHalfX = std::abs(a) * r.hw + std::abs(c) * r.hh; r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh; @@ -227,8 +220,6 @@ void BlobShape::updatePolish() { if (isInvertedRect()) m_cachedMyIndex = -1; - // Compute pairwise exclude masks. Bit j in entry i is set iff rect i excludes rect j - // or rect j excludes rect i. The shader uses this to avoid smin between excluded pairs. const auto cachedCount = m_cachedRects.size(); for (qsizetype i = 0; i < cachedCount; ++i) { int mask = 0; @@ -243,7 +234,6 @@ void BlobShape::updatePolish() { m_cachedRects[i].excludeMask = mask; } - // Cache inverted rect data m_cachedHasInverted = false; m_cachedInvertedRadius = 0; memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter)); @@ -262,7 +252,6 @@ void BlobShape::updatePolish() { const float innerHW = outerHW - static_cast((inv->borderLeft() + inv->borderRight()) / 2.0); const float innerHH = outerHH - static_cast((inv->borderTop() + inv->borderBottom()) / 2.0); - // 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; @@ -270,7 +259,6 @@ void BlobShape::updatePolish() { 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); } @@ -291,7 +279,6 @@ void BlobShape::updatePolish() { } } - // Pre-compute effective per-corner radii (moves O(N²) work from GPU to CPU) const float smoothFactor = pad; constexpr float minR = 2.0f; const auto rectCount = m_cachedRects.size(); @@ -328,7 +315,6 @@ void BlobShape::updatePolish() { fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); } - // Combine base radii with fill factors into effective per-corner radii ri.radius[0] = std::max(ri.radius[0] * fTr, minR); ri.radius[1] = std::max(ri.radius[1] * fBr, minR); ri.radius[2] = std::max(ri.radius[2] * fBl, minR); @@ -357,7 +343,6 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { node->setFlag(QSGNode::OwnsMaterial); } - // Update geometry auto* geometry = node->geometry(); auto* v = geometry->vertexDataAsTexturedPoint2D(); @@ -373,7 +358,6 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { node->markDirty(QSGNode::DirtyGeometry); - // Update material auto* material = static_cast(node->material()); material->m_paddedX = m_cachedPaddedX; material->m_paddedY = m_cachedPaddedY;