fix: sidebar bulge at connection with utilities
Basically make sidebar and utilities exclude each other from merging and manually animate corner radius Also format c++
This commit is contained in:
parent
0f5d2ddc16
commit
e8555061ab
12 changed files with 286 additions and 158 deletions
|
|
@ -166,6 +166,15 @@ Variants {
|
|||
panel: panels.sidebar
|
||||
bar: bar
|
||||
deformAmount: 0
|
||||
height: panel.height + 1
|
||||
exclude: [utilsBg]
|
||||
bottomLeftRadius: panels.sidebar.visible ? 0 : radius
|
||||
|
||||
Behavior on bottomLeftRadius {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelBg {
|
||||
|
|
@ -193,6 +202,14 @@ Variants {
|
|||
blobGroup: blobGroup
|
||||
panel: panels.utilities
|
||||
bar: bar
|
||||
exclude: [sidebarBg]
|
||||
topLeftRadius: panels.sidebar.visible ? 0 : radius
|
||||
|
||||
Behavior on topLeftRadius {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelBg {
|
||||
|
|
|
|||
|
|
@ -74,20 +74,15 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
|
|||
|
||||
// Use cached padded rects to find spatial neighbors
|
||||
const float pad = static_cast<float>(m_smoothing) * 2.0f;
|
||||
const QRectF srcRect(
|
||||
static_cast<double>(source->m_cachedPaddedX - pad),
|
||||
static_cast<double>(source->m_cachedPaddedY - pad),
|
||||
static_cast<double>(source->m_cachedPaddedW + pad * 2.0f),
|
||||
const QRectF srcRect(static_cast<double>(source->m_cachedPaddedX - pad),
|
||||
static_cast<double>(source->m_cachedPaddedY - pad), static_cast<double>(source->m_cachedPaddedW + pad * 2.0f),
|
||||
static_cast<double>(source->m_cachedPaddedH + pad * 2.0f));
|
||||
|
||||
for (auto* shape : std::as_const(m_shapes)) {
|
||||
if (shape == source)
|
||||
continue;
|
||||
const QRectF otherRect(
|
||||
static_cast<double>(shape->m_cachedPaddedX),
|
||||
static_cast<double>(shape->m_cachedPaddedY),
|
||||
static_cast<double>(shape->m_cachedPaddedW),
|
||||
static_cast<double>(shape->m_cachedPaddedH));
|
||||
const QRectF otherRect(static_cast<double>(shape->m_cachedPaddedX), static_cast<double>(shape->m_cachedPaddedY),
|
||||
static_cast<double>(shape->m_cachedPaddedW), static_cast<double>(shape->m_cachedPaddedH));
|
||||
if (srcRect.intersects(otherRect)) {
|
||||
shape->polish();
|
||||
shape->update();
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ class BlobInvertedRect;
|
|||
class BlobGroup : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
||||
smoothingChanged)
|
||||
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -13,21 +13,36 @@ BlobInvertedRect::BlobInvertedRect(QQuickItem* parent)
|
|||
|
||||
static void setFrameIndices(quint16* idx) {
|
||||
// Top strip: 0-1-4, 1-5-4
|
||||
idx[0] = 0; idx[1] = 1; idx[2] = 4;
|
||||
idx[3] = 1; idx[4] = 5; idx[5] = 4;
|
||||
idx[0] = 0;
|
||||
idx[1] = 1;
|
||||
idx[2] = 4;
|
||||
idx[3] = 1;
|
||||
idx[4] = 5;
|
||||
idx[5] = 4;
|
||||
// Right strip: 1-2-5, 2-6-5
|
||||
idx[6] = 1; idx[7] = 2; idx[8] = 5;
|
||||
idx[9] = 2; idx[10] = 6; idx[11] = 5;
|
||||
idx[6] = 1;
|
||||
idx[7] = 2;
|
||||
idx[8] = 5;
|
||||
idx[9] = 2;
|
||||
idx[10] = 6;
|
||||
idx[11] = 5;
|
||||
// Bottom strip: 2-3-6, 3-7-6
|
||||
idx[12] = 2; idx[13] = 3; idx[14] = 6;
|
||||
idx[15] = 3; idx[16] = 7; idx[17] = 6;
|
||||
idx[12] = 2;
|
||||
idx[13] = 3;
|
||||
idx[14] = 6;
|
||||
idx[15] = 3;
|
||||
idx[16] = 7;
|
||||
idx[17] = 6;
|
||||
// Left strip: 3-0-7, 0-4-7
|
||||
idx[18] = 3; idx[19] = 0; idx[20] = 7;
|
||||
idx[21] = 0; idx[22] = 4; idx[23] = 7;
|
||||
idx[18] = 3;
|
||||
idx[19] = 0;
|
||||
idx[20] = 7;
|
||||
idx[21] = 0;
|
||||
idx[22] = 4;
|
||||
idx[23] = 7;
|
||||
}
|
||||
|
||||
QSGNode* BlobInvertedRect::updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||
QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||
if (!m_group) {
|
||||
delete oldNode;
|
||||
return nullptr;
|
||||
|
|
@ -55,9 +70,8 @@ QSGNode* BlobInvertedRect::updatePaintNode(
|
|||
delete oldNode;
|
||||
node = new QSGGeometryNode;
|
||||
|
||||
auto* geometry = new QSGGeometry(
|
||||
QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24,
|
||||
QSGGeometry::UnsignedShortType);
|
||||
auto* geometry =
|
||||
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, QSGGeometry::UnsignedShortType);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
|
||||
node->setGeometry(geometry);
|
||||
node->setFlag(QSGNode::OwnsGeometry);
|
||||
|
|
@ -105,13 +119,10 @@ QSGNode* BlobInvertedRect::updatePaintNode(
|
|||
material->m_color = m_group->color();
|
||||
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
|
||||
material->m_invertedRadius = m_cachedInvertedRadius;
|
||||
memcpy(material->m_invertedOuter, m_cachedInvertedOuter,
|
||||
sizeof(m_cachedInvertedOuter));
|
||||
memcpy(material->m_invertedInner, m_cachedInvertedInner,
|
||||
sizeof(m_cachedInvertedInner));
|
||||
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter));
|
||||
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner));
|
||||
|
||||
const int count =
|
||||
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
||||
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
||||
material->m_rectCount = count;
|
||||
for (int i = 0; i < count; ++i)
|
||||
material->m_rects[i] = m_cachedRects[i];
|
||||
|
|
|
|||
|
|
@ -7,14 +7,10 @@
|
|||
class BlobInvertedRect : public BlobShape {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY
|
||||
borderLeftChanged)
|
||||
Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY
|
||||
borderRightChanged)
|
||||
Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY
|
||||
borderTopChanged)
|
||||
Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY
|
||||
borderBottomChanged)
|
||||
Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY borderLeftChanged)
|
||||
Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY borderRightChanged)
|
||||
Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY borderTopChanged)
|
||||
Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY borderBottomChanged)
|
||||
|
||||
public:
|
||||
explicit BlobInvertedRect(QQuickItem* parent = nullptr);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ QSGMaterialType* BlobMaterial::type() const {
|
|||
return &s_type;
|
||||
}
|
||||
|
||||
QSGMaterialShader* BlobMaterial::createShader(
|
||||
QSGRendererInterface::RenderMode) const {
|
||||
QSGMaterialShader* BlobMaterial::createShader(QSGRendererInterface::RenderMode) const {
|
||||
return new BlobMaterialShader;
|
||||
}
|
||||
|
||||
|
|
@ -25,8 +24,7 @@ BlobMaterialShader::BlobMaterialShader() {
|
|||
setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb"));
|
||||
}
|
||||
|
||||
bool BlobMaterialShader::updateUniformData(
|
||||
RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
|
||||
bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
|
||||
Q_UNUSED(oldMaterial);
|
||||
auto* mat = static_cast<BlobMaterial*>(newMaterial);
|
||||
QByteArray* buf = state.uniformData();
|
||||
|
|
@ -85,13 +83,13 @@ bool BlobMaterialShader::updateUniformData(
|
|||
const auto& r = mat->m_rects[i];
|
||||
const int base = 160 + i * 80;
|
||||
const float d0[4] = { r.cx, r.cy, r.hw, r.hh };
|
||||
const float d1[4] = { r.radius, r.offsetX, r.offsetY, r.minEig };
|
||||
const float d1[4] = { 0.0f, r.offsetX, r.offsetY, r.minEig };
|
||||
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
|
||||
memcpy(buf->data() + base, d0, 16);
|
||||
memcpy(buf->data() + base + 16, d1, 16);
|
||||
memcpy(buf->data() + base + 32, r.invDeform, 16);
|
||||
memcpy(buf->data() + base + 48, d3, 16);
|
||||
memcpy(buf->data() + base + 64, r.cornerFill, 16);
|
||||
memcpy(buf->data() + base + 64, r.radius, 16);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -6,22 +6,20 @@
|
|||
|
||||
struct BlobRectData {
|
||||
float cx = 0, cy = 0, hw = 0, hh = 0;
|
||||
float radius = 0;
|
||||
float offsetX = 0, offsetY = 0;
|
||||
float minEig = 1.0f;
|
||||
// Inverse of 2x2 deformation matrix, column-major for GLSL
|
||||
float invDeform[4] = { 1, 0, 0, 1 };
|
||||
// Screen-space AABB half-extents of the deformed rect
|
||||
float screenHalfX = 0, screenHalfY = 0;
|
||||
// Pre-computed corner fill factors (tr, br, bl, tl)
|
||||
float cornerFill[4] = { 1, 1, 1, 1 };
|
||||
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
|
||||
float radius[4] = { 0, 0, 0, 0 };
|
||||
};
|
||||
|
||||
class BlobMaterial : public QSGMaterial {
|
||||
public:
|
||||
QSGMaterialType* type() const override;
|
||||
QSGMaterialShader* createShader(
|
||||
QSGRendererInterface::RenderMode) const override;
|
||||
QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override;
|
||||
int compare(const QSGMaterial* other) const override;
|
||||
|
||||
float m_paddedX = 0;
|
||||
|
|
@ -42,6 +40,5 @@ public:
|
|||
class BlobMaterialShader : public QSGMaterialShader {
|
||||
public:
|
||||
BlobMaterialShader();
|
||||
bool updateUniformData(RenderState& state, QSGMaterial* newMaterial,
|
||||
QSGMaterial* oldMaterial) override;
|
||||
bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,10 +17,8 @@ void BlobRect::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);
|
||||
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
|
||||
|
|
@ -62,10 +60,8 @@ void BlobRect::updatePhysics() {
|
|||
return;
|
||||
}
|
||||
|
||||
const float velX =
|
||||
static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
|
||||
const float velY =
|
||||
static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
|
||||
const float velX = static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
|
||||
const float velY = static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
|
||||
m_prevScenePos = scenePos;
|
||||
|
||||
const float speed = std::sqrt(velX * velX + velY * velY);
|
||||
|
|
@ -86,8 +82,7 @@ void BlobRect::updatePhysics() {
|
|||
float target11 = 1.0f;
|
||||
|
||||
if (speed > 5.0f) {
|
||||
const float targetStretch =
|
||||
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
||||
const float targetStretch = 1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
||||
const float targetCompress = 1.0f / targetStretch;
|
||||
|
||||
const float cosA = velX / speed;
|
||||
|
|
@ -105,35 +100,132 @@ void BlobRect::updatePhysics() {
|
|||
const float kStiffness = static_cast<float>(m_stiffness);
|
||||
const float kDamping = static_cast<float>(m_damping);
|
||||
|
||||
const float accel00 =
|
||||
-kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
|
||||
const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
|
||||
m_dmVel00 += accel00 * dt;
|
||||
m_dm00 += m_dmVel00 * dt;
|
||||
|
||||
const float accel01 =
|
||||
-kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
|
||||
const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
|
||||
m_dmVel01 += accel01 * dt;
|
||||
m_dm01 += m_dmVel01 * dt;
|
||||
|
||||
const float accel11 =
|
||||
-kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
|
||||
const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
|
||||
m_dmVel11 += accel11 * dt;
|
||||
m_dm11 += m_dmVel11 * dt;
|
||||
|
||||
m_deformMatrix = QMatrix4x4(
|
||||
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
m_deformMatrix = QMatrix4x4(m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
updateCenteredDeformMatrix();
|
||||
|
||||
checkAtRest(speed);
|
||||
}
|
||||
|
||||
void BlobRect::setTopLeftRadius(qreal r) {
|
||||
if (!qFuzzyCompare(m_topLeftRadius, r)) {
|
||||
m_topLeftRadius = r;
|
||||
emit topLeftRadiusChanged();
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void BlobRect::setTopRightRadius(qreal r) {
|
||||
if (!qFuzzyCompare(m_topRightRadius, r)) {
|
||||
m_topRightRadius = r;
|
||||
emit topRightRadiusChanged();
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void BlobRect::setBottomLeftRadius(qreal r) {
|
||||
if (!qFuzzyCompare(m_bottomLeftRadius, r)) {
|
||||
m_bottomLeftRadius = r;
|
||||
emit bottomLeftRadiusChanged();
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void BlobRect::setBottomRightRadius(qreal r) {
|
||||
if (!qFuzzyCompare(m_bottomRightRadius, r)) {
|
||||
m_bottomRightRadius = r;
|
||||
emit bottomRightRadiusChanged();
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void BlobRect::cornerRadii(float out[4]) const {
|
||||
const auto base = static_cast<float>(m_radius);
|
||||
out[0] = m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base;
|
||||
out[1] = m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius) : base;
|
||||
out[2] = m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base;
|
||||
out[3] = m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base;
|
||||
}
|
||||
|
||||
bool BlobRect::isExcluded(const BlobShape* other) const {
|
||||
for (const auto& ptr : m_exclude) {
|
||||
if (ptr == other)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> BlobRect::exclude() {
|
||||
return QQmlListProperty<BlobRect>(
|
||||
this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast);
|
||||
}
|
||||
|
||||
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
self->m_exclude.append(rect);
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeChanged();
|
||||
}
|
||||
|
||||
qsizetype BlobRect::excludeCount(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
return self->m_exclude.size();
|
||||
}
|
||||
|
||||
BlobRect* BlobRect::excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
return self->m_exclude.at(index);
|
||||
}
|
||||
|
||||
void BlobRect::excludeClear(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
if (self->m_exclude.isEmpty())
|
||||
return;
|
||||
self->m_exclude.clear();
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeChanged();
|
||||
}
|
||||
|
||||
void BlobRect::excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
self->m_exclude[index] = rect;
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeChanged();
|
||||
}
|
||||
|
||||
void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
if (self->m_exclude.isEmpty())
|
||||
return;
|
||||
self->m_exclude.removeLast();
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeChanged();
|
||||
}
|
||||
|
||||
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 &&
|
||||
const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
|
||||
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
|
||||
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon &&
|
||||
speed < 5.0f;
|
||||
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && speed < 5.0f;
|
||||
|
||||
if (atRest) {
|
||||
m_dm00 = 1.0f;
|
||||
|
|
|
|||
|
|
@ -3,17 +3,22 @@
|
|||
#include "blobshape.hpp"
|
||||
|
||||
#include <qelapsedtimer.h>
|
||||
#include <qpointer.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmllist.h>
|
||||
|
||||
class BlobRect : public BlobShape {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY
|
||||
stiffnessChanged)
|
||||
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged)
|
||||
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(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)
|
||||
Q_PROPERTY(
|
||||
qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
||||
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
|
||||
deformScaleChanged)
|
||||
qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged)
|
||||
|
||||
public:
|
||||
explicit BlobRect(QQuickItem* parent = nullptr);
|
||||
|
|
@ -46,10 +51,36 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> exclude();
|
||||
|
||||
bool isExcluded(const BlobShape* other) const override;
|
||||
void cornerRadii(float out[4]) const override;
|
||||
|
||||
qreal topLeftRadius() const { return m_topLeftRadius; }
|
||||
|
||||
void setTopLeftRadius(qreal r);
|
||||
|
||||
qreal topRightRadius() const { return m_topRightRadius; }
|
||||
|
||||
void setTopRightRadius(qreal r);
|
||||
|
||||
qreal bottomLeftRadius() const { return m_bottomLeftRadius; }
|
||||
|
||||
void setBottomLeftRadius(qreal r);
|
||||
|
||||
qreal bottomRightRadius() const { return m_bottomRightRadius; }
|
||||
|
||||
void setBottomRightRadius(qreal r);
|
||||
|
||||
signals:
|
||||
void stiffnessChanged();
|
||||
void dampingChanged();
|
||||
void deformScaleChanged();
|
||||
void excludeChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
|
||||
protected:
|
||||
void updatePolish() override;
|
||||
|
|
@ -78,4 +109,18 @@ private:
|
|||
qreal m_stiffness = 200.0;
|
||||
qreal m_damping = 16.0;
|
||||
qreal m_deformScale = 0.0005;
|
||||
|
||||
qreal m_topLeftRadius = -1;
|
||||
qreal m_topRightRadius = -1;
|
||||
qreal m_bottomLeftRadius = -1;
|
||||
qreal m_bottomRightRadius = -1;
|
||||
|
||||
QList<QPointer<BlobRect>> m_exclude;
|
||||
|
||||
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
|
|||
return std::max(extraX, extraY);
|
||||
}
|
||||
|
||||
static float cpuSdBox(float px, float py, float cx, float cy, float hw,
|
||||
float hh) {
|
||||
static float cpuSdBox(float px, float py, float cx, float cy, float hw, float hh) {
|
||||
const float dx = std::abs(px - cx) - hw;
|
||||
const float dy = std::abs(py - cy) - hh;
|
||||
const float mdx = std::max(dx, 0.0f);
|
||||
|
|
@ -66,8 +65,7 @@ void BlobShape::componentComplete() {
|
|||
registerWithGroup();
|
||||
}
|
||||
|
||||
void BlobShape::geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||
void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
updateCenteredDeformMatrix();
|
||||
if (m_group) {
|
||||
|
|
@ -92,6 +90,14 @@ void BlobShape::updateCenteredDeformMatrix() {
|
|||
emit deformMatrixChanged();
|
||||
}
|
||||
|
||||
void BlobShape::cornerRadii(float out[4]) const {
|
||||
const auto r = static_cast<float>(m_radius);
|
||||
out[0] = r;
|
||||
out[1] = r;
|
||||
out[2] = r;
|
||||
out[3] = r;
|
||||
}
|
||||
|
||||
void BlobShape::registerWithGroup() {
|
||||
if (m_group)
|
||||
m_group->addShape(this);
|
||||
|
|
@ -127,19 +133,15 @@ void BlobShape::updatePolish() {
|
|||
m_cachedPaddedY = static_cast<float>(scenePos.y()) - totalPad;
|
||||
m_cachedPaddedW = static_cast<float>(width()) + 2.0f * totalPad;
|
||||
m_cachedPaddedH = static_cast<float>(height()) + 2.0f * totalPad;
|
||||
m_localPaddedRect = QRectF(static_cast<double>(-totalPad),
|
||||
static_cast<double>(-totalPad),
|
||||
width() + 2.0 * static_cast<double>(totalPad),
|
||||
height() + 2.0 * static_cast<double>(totalPad));
|
||||
m_localPaddedRect = QRectF(static_cast<double>(-totalPad), static_cast<double>(-totalPad),
|
||||
width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad));
|
||||
}
|
||||
|
||||
// Filter nearby normal rects
|
||||
m_cachedRects.clear();
|
||||
m_cachedMyIndex = -2;
|
||||
const QRectF myPadded(static_cast<double>(m_cachedPaddedX),
|
||||
static_cast<double>(m_cachedPaddedY),
|
||||
static_cast<double>(m_cachedPaddedW),
|
||||
static_cast<double>(m_cachedPaddedH));
|
||||
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
|
||||
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH));
|
||||
|
||||
for (BlobShape* other : m_group->shapes()) {
|
||||
if (other->isInvertedRect())
|
||||
|
|
@ -149,6 +151,9 @@ void BlobShape::updatePolish() {
|
|||
if (other->width() <= 0 || other->height() <= 0)
|
||||
continue;
|
||||
|
||||
if (isExcluded(other))
|
||||
continue;
|
||||
|
||||
const QPointF otherScene = other->mapToScene(QPointF(0, 0));
|
||||
|
||||
bool include = false;
|
||||
|
|
@ -157,12 +162,9 @@ void BlobShape::updatePolish() {
|
|||
} else {
|
||||
const float otherHW = static_cast<float>(other->width()) * 0.5f;
|
||||
const float otherHH = static_cast<float>(other->height()) * 0.5f;
|
||||
const float otherPad =
|
||||
pad + deformPadding(other->m_deformMatrix, otherHW, otherHH);
|
||||
const QRectF otherPadded(
|
||||
otherScene.x() - static_cast<double>(otherPad),
|
||||
otherScene.y() - static_cast<double>(otherPad),
|
||||
other->width() + 2.0 * static_cast<double>(otherPad),
|
||||
const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH);
|
||||
const QRectF otherPadded(otherScene.x() - static_cast<double>(otherPad),
|
||||
otherScene.y() - static_cast<double>(otherPad), other->width() + 2.0 * static_cast<double>(otherPad),
|
||||
other->height() + 2.0 * static_cast<double>(otherPad));
|
||||
include = myPadded.intersects(otherPadded);
|
||||
}
|
||||
|
|
@ -180,14 +182,13 @@ void BlobShape::updatePolish() {
|
|||
r.cy = static_cast<float>(otherScene.y() + other->height() / 2.0);
|
||||
r.hw = static_cast<float>(other->width() / 2.0);
|
||||
r.hh = static_cast<float>(other->height() / 2.0);
|
||||
r.radius = static_cast<float>(other->radius());
|
||||
other->cornerRadii(r.radius);
|
||||
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;
|
||||
const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f;
|
||||
r.invDeform[0] = d * invDet;
|
||||
r.invDeform[1] = -b * invDet;
|
||||
r.invDeform[2] = -c * invDet;
|
||||
|
|
@ -218,25 +219,15 @@ void BlobShape::updatePolish() {
|
|||
auto* inv = m_group->invertedRect();
|
||||
if (inv) {
|
||||
const QPointF invScene = inv->mapToScene(QPointF(0, 0));
|
||||
const float outerCX =
|
||||
static_cast<float>(invScene.x() + inv->width() / 2.0);
|
||||
const float outerCY =
|
||||
static_cast<float>(invScene.y() + inv->height() / 2.0);
|
||||
const float outerCX = static_cast<float>(invScene.x() + inv->width() / 2.0);
|
||||
const float outerCY = static_cast<float>(invScene.y() + inv->height() / 2.0);
|
||||
const float outerHW = static_cast<float>(inv->width() / 2.0);
|
||||
const float outerHH = static_cast<float>(inv->height() / 2.0);
|
||||
|
||||
const float innerCX =
|
||||
outerCX +
|
||||
static_cast<float>((inv->borderLeft() - inv->borderRight()) / 2.0);
|
||||
const float innerCY =
|
||||
outerCY +
|
||||
static_cast<float>((inv->borderTop() - inv->borderBottom()) / 2.0);
|
||||
const float innerHW =
|
||||
outerHW -
|
||||
static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
|
||||
const float innerHH =
|
||||
outerHH -
|
||||
static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
|
||||
const float innerCX = outerCX + static_cast<float>((inv->borderLeft() - inv->borderRight()) / 2.0);
|
||||
const float innerCY = outerCY + static_cast<float>((inv->borderTop() - inv->borderBottom()) / 2.0);
|
||||
const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
|
||||
const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
|
||||
|
||||
// Check if this rect is near the border (within 2x smoothing of inner edge)
|
||||
bool nearBorder = isInvertedRect();
|
||||
|
|
@ -247,10 +238,8 @@ void BlobShape::updatePolish() {
|
|||
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);
|
||||
nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) ||
|
||||
(myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin);
|
||||
}
|
||||
|
||||
if (nearBorder) {
|
||||
|
|
@ -269,8 +258,9 @@ void BlobShape::updatePolish() {
|
|||
}
|
||||
}
|
||||
|
||||
// Pre-compute corner fill factors (moves O(N²) work from GPU to CPU)
|
||||
// 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();
|
||||
for (qsizetype i = 0; i < rectCount; ++i) {
|
||||
auto& ri = m_cachedRects[i];
|
||||
|
|
@ -285,14 +275,10 @@ void BlobShape::updatePolish() {
|
|||
if (j == i)
|
||||
continue;
|
||||
const auto& rj = m_cachedRects[j];
|
||||
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor,
|
||||
cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor,
|
||||
cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor,
|
||||
cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor,
|
||||
cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
}
|
||||
|
||||
if (m_cachedHasInverted) {
|
||||
|
|
@ -300,20 +286,17 @@ void BlobShape::updatePolish() {
|
|||
const float icy = m_cachedInvertedInner[1];
|
||||
const float ihw = m_cachedInvertedInner[2];
|
||||
const float ihh = m_cachedInvertedInner[3];
|
||||
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor,
|
||||
-cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh)));
|
||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor,
|
||||
-cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh)));
|
||||
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor,
|
||||
-cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh)));
|
||||
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor,
|
||||
-cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
|
||||
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh)));
|
||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh)));
|
||||
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh)));
|
||||
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
|
||||
}
|
||||
|
||||
ri.cornerFill[0] = fTr;
|
||||
ri.cornerFill[1] = fBr;
|
||||
ri.cornerFill[2] = fBl;
|
||||
ri.cornerFill[3] = fTl;
|
||||
// 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);
|
||||
ri.radius[3] = std::max(ri.radius[3] * fTl, minR);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -327,8 +310,7 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
|||
if (!node) {
|
||||
node = new QSGGeometryNode;
|
||||
|
||||
auto* geometry = new QSGGeometry(
|
||||
QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
|
||||
auto* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||
node->setGeometry(geometry);
|
||||
node->setFlag(QSGNode::OwnsGeometry);
|
||||
|
|
@ -366,13 +348,10 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
|||
material->m_color = m_group->color();
|
||||
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
|
||||
material->m_invertedRadius = m_cachedInvertedRadius;
|
||||
memcpy(material->m_invertedOuter, m_cachedInvertedOuter,
|
||||
sizeof(m_cachedInvertedOuter));
|
||||
memcpy(material->m_invertedInner, m_cachedInvertedInner,
|
||||
sizeof(m_cachedInvertedInner));
|
||||
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter));
|
||||
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner));
|
||||
|
||||
const int count =
|
||||
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
||||
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
||||
material->m_rectCount = count;
|
||||
for (int i = 0; i < count; ++i)
|
||||
material->m_rects[i] = m_cachedRects[i];
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ class BlobShape : public QQuickItem {
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged)
|
||||
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
|
||||
Q_PROPERTY(
|
||||
QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged)
|
||||
Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged)
|
||||
|
||||
friend class BlobGroup;
|
||||
|
||||
|
|
@ -38,13 +37,16 @@ signals:
|
|||
|
||||
protected:
|
||||
void componentComplete() override;
|
||||
void geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
void updatePolish() override;
|
||||
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
|
||||
|
||||
virtual bool isInvertedRect() const { return false; }
|
||||
|
||||
virtual bool isExcluded(const BlobShape* /*other*/) const { return false; }
|
||||
|
||||
virtual void cornerRadii(float out[4]) const;
|
||||
|
||||
virtual void updatePhysics() {}
|
||||
|
||||
virtual void registerWithGroup();
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ void main() {
|
|||
vec4 props = rectData[i * 5 + 1]; // radius, offsetX, offsetY, minEig
|
||||
vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
|
||||
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0
|
||||
vec4 fills = rectData[i * 5 + 4]; // f_tr, f_br, f_bl, f_tl
|
||||
vec4 radii = rectData[i * 5 + 4]; // effective per-corner radii (tr, br, bl, tl)
|
||||
|
||||
// Offset center for asymmetric deformation
|
||||
vec2 center = rect.xy + props.yz;
|
||||
|
|
@ -94,10 +94,7 @@ void main() {
|
|||
mat2 invDeform = mat2(invDm.xy, invDm.zw);
|
||||
vec2 transformedPixel = center + invDeform * (pixel - center);
|
||||
|
||||
// Use pre-computed corner fill factors
|
||||
float br = props.x;
|
||||
float minR = 2.0;
|
||||
vec4 radii = max(br * fills, vec4(minR));
|
||||
// Use pre-computed effective per-corner radii
|
||||
float d = sdRoundedBox4(transformedPixel, center, rect.zw, radii);
|
||||
|
||||
// Use pre-computed minimum eigenvalue for SDF correction
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue