feat: add corner fill toggle for blobs

This commit is contained in:
2 * r + 2 * t 2026-05-08 03:13:53 +10:00
parent ba2d4f03d6
commit 71386f008d
6 changed files with 95 additions and 2 deletions

View file

@ -28,6 +28,14 @@ void BlobGroup::setColor(const QColor& c) {
markDirty();
}
void BlobGroup::setCornerFill(bool e) {
if (m_cornerFill == e)
return;
m_cornerFill = e;
emit cornerFillChanged();
markDirty();
}
void BlobGroup::addShape(BlobShape* shape) {
if (!shape || m_shapes.contains(shape))
return;

View file

@ -13,6 +13,7 @@ class BlobGroup : public QObject {
QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged)
public:
explicit BlobGroup(QObject* parent = nullptr);
@ -26,6 +27,10 @@ public:
void setColor(const QColor& c);
bool cornerFill() const { return m_cornerFill; }
void setCornerFill(bool e);
void addShape(BlobShape* shape);
void removeShape(BlobShape* shape);
@ -43,10 +48,12 @@ public:
signals:
void smoothingChanged();
void colorChanged();
void cornerFillChanged();
private:
qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff };
bool m_cornerFill = true;
QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false;

View file

@ -173,11 +173,24 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
return false;
}
bool BlobRect::isCornerExcluded(const BlobShape* other) const {
for (const auto& ptr : m_excludeCorners) {
if (ptr == other)
return true;
}
return false;
}
QQmlListProperty<BlobRect> BlobRect::exclude() {
return QQmlListProperty<BlobRect>(
this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast);
}
QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt,
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast);
}
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_exclude.append(rect);
@ -224,6 +237,52 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
emit self->excludeChanged();
}
void BlobRect::excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners.append(rect);
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.size();
}
BlobRect* BlobRect::excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index) {
auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.at(index);
}
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty())
return;
self->m_excludeCorners.clear();
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners[index] = rect;
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty())
return;
self->m_excludeCorners.removeLast();
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::checkAtRest(float speed) {
constexpr float kEpsilon = 0.002f;
const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&

View file

@ -14,6 +14,7 @@ class BlobRect : public BlobShape {
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY excludeCornersChanged)
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged)
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged)
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged)
@ -52,8 +53,10 @@ public:
}
QQmlListProperty<BlobRect> exclude();
QQmlListProperty<BlobRect> excludeCorners();
bool isExcluded(const BlobShape* other) const override;
bool isCornerExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override;
qreal topLeftRadius() const { return m_topLeftRadius; }
@ -77,6 +80,7 @@ signals:
void dampingChanged();
void deformScaleChanged();
void excludeChanged();
void excludeCornersChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomLeftRadiusChanged();
@ -116,6 +120,7 @@ private:
qreal m_bottomRightRadius = -1;
QList<QPointer<BlobRect>> m_exclude;
QList<QPointer<BlobRect>> m_excludeCorners;
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
@ -123,4 +128,11 @@ private:
static void excludeClear(QQmlListProperty<BlobRect>* prop);
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
static void excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
};

View file

@ -288,10 +288,12 @@ 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 bool cornerFill = m_group->cornerFill();
const auto rectCount = m_cachedRects.size();
for (qsizetype i = 0; i < rectCount; ++i) {
auto& ri = m_cachedRects[i];
const int riExcludeMask = ri.excludeMask;
BlobShape* const si = rectShapes[i];
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
@ -299,11 +301,14 @@ void BlobShape::updatePolish() {
const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh;
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
for (qsizetype j = 0; j < rectCount; ++j) {
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
if (j == i)
continue;
if (riExcludeMask & (1 << j))
continue;
BlobShape* const sj = rectShapes[j];
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
continue;
const auto& rj = m_cachedRects[j];
// Skip when the corner is inside rj: it's buried, not on a visible junction,
// so squaring it would only perturb interior SDF gradients.
@ -321,7 +326,7 @@ void BlobShape::updatePolish() {
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, sdTl));
}
if (m_cachedHasInverted) {
if (cornerFill && m_cachedHasInverted) {
const float icx = m_cachedInvertedInner[0];
const float icy = m_cachedInvertedInner[1];
const float ihw = m_cachedInvertedInner[2];

View file

@ -49,6 +49,8 @@ protected:
virtual bool isExcluded(const BlobShape* /*other*/) const { return false; }
virtual bool isCornerExcluded(const BlobShape* /*other*/) const { return false; }
virtual void cornerRadii(float out[4]) const;
virtual void updatePhysics() {}