329 lines
8.8 KiB
C++
329 lines
8.8 KiB
C++
#include "blobrect.hpp"
|
|
#include "blobgroup.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <qnamespace.h>
|
|
|
|
BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {}
|
|
|
|
BlobRect::~BlobRect() {
|
|
if (m_group) m_group->removeShape(this);
|
|
}
|
|
|
|
void BlobRect::updatePolish() {
|
|
if (m_physicsActive) {
|
|
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) {
|
|
m_dm00 = 1.0f;
|
|
m_dm01 = 0.0f;
|
|
m_dm11 = 1.0f;
|
|
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
|
m_deformMatrix = QMatrix4x4();
|
|
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,
|
|
[this]() {
|
|
if (m_physicsActive && m_group) m_group->markDirty();
|
|
},
|
|
Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
BlobShape::updatePolish();
|
|
}
|
|
|
|
void BlobRect::updatePhysics() {
|
|
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
|
|
|
|
if (!m_hasPrevPos) {
|
|
m_prevScenePos = scenePos;
|
|
m_elapsed.start();
|
|
m_hasPrevPos = true;
|
|
return;
|
|
}
|
|
|
|
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
|
|
if (dt > 0.1f || dt < 0.001f) {
|
|
m_prevScenePos = scenePos;
|
|
if (m_physicsActive) checkAtRest(0.0f);
|
|
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;
|
|
m_prevScenePos = scenePos;
|
|
|
|
const float speed = std::sqrt(velX * velX + velY * velY);
|
|
|
|
if (!m_physicsActive) {
|
|
if (speed < 5.0f) return;
|
|
m_physicsActive = true;
|
|
}
|
|
|
|
const float kStretchFactor = static_cast<float>(m_deformScale);
|
|
constexpr float kMaxStretch = 0.35f;
|
|
|
|
float target00 = 1.0f;
|
|
float target01 = 0.0f;
|
|
float target11 = 1.0f;
|
|
|
|
if (speed > 5.0f) {
|
|
const float targetStretch =
|
|
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
|
const float targetCompress = 1.0f / targetStretch;
|
|
|
|
const float cosA = velX / speed;
|
|
const float sinA = velY / speed;
|
|
const float cos2 = cosA * cosA;
|
|
const float sin2 = sinA * sinA;
|
|
const float cs = cosA * sinA;
|
|
|
|
target00 = targetStretch * cos2 + targetCompress * sin2;
|
|
target01 = (targetStretch - targetCompress) * cs;
|
|
target11 = targetStretch * sin2 + targetCompress * cos2;
|
|
}
|
|
|
|
const float kStiffness = static_cast<float>(m_stiffness);
|
|
const float kDamping = static_cast<float>(m_damping);
|
|
const float invDamp = 1.0f / (1.0f + kDamping * dt);
|
|
|
|
m_dmVel00 = (m_dmVel00 - kStiffness * (m_dm00 - target00) * dt) * invDamp;
|
|
m_dm00 += m_dmVel00 * dt;
|
|
|
|
m_dmVel01 = (m_dmVel01 - kStiffness * (m_dm01 - target01) * dt) * invDamp;
|
|
m_dm01 += m_dmVel01 * dt;
|
|
|
|
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
|
|
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);
|
|
emit rawDeformMatrixChanged();
|
|
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 maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
|
const auto base = std::min(static_cast<float>(m_radius), maxR);
|
|
out[0] = std::min(
|
|
m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base,
|
|
maxR);
|
|
out[1] = std::min(
|
|
m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius)
|
|
: base,
|
|
maxR);
|
|
out[2] = std::min(
|
|
m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base,
|
|
maxR);
|
|
out[3] = std::min(
|
|
m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base,
|
|
maxR);
|
|
}
|
|
|
|
bool BlobRect::isExcluded(const BlobShape* other) const {
|
|
for (const auto& ptr : m_exclude) {
|
|
if (ptr == other) return true;
|
|
}
|
|
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);
|
|
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::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 &&
|
|
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;
|
|
|
|
if (atRest) {
|
|
m_dm00 = 1.0f;
|
|
m_dm01 = 0.0f;
|
|
m_dm11 = 1.0f;
|
|
m_dmVel00 = 0.0f;
|
|
m_dmVel01 = 0.0f;
|
|
m_dmVel11 = 0.0f;
|
|
m_deformMatrix = QMatrix4x4();
|
|
emit rawDeformMatrixChanged();
|
|
updateCenteredDeformMatrix();
|
|
m_physicsActive = false;
|
|
|
|
if (m_group) {
|
|
QMetaObject::invokeMethod(
|
|
this,
|
|
[this]() {
|
|
if (m_group) m_group->markDirty();
|
|
},
|
|
Qt::QueuedConnection);
|
|
}
|
|
}
|
|
}
|