基于https://www.bilibili.com/video/BV1Li421Y7EH/?spm_id_from=333.999.top_right_bar_window_history.content.click原理的一个qt实现
#pragma once
#include <QWidget>
#include "ui_Worm.h"
#include <QPointF>
#include <QList>
#include <QPainter>
#include <QKeyEvent>
#include <cmath>
#include <QPropertyAnimation>
#include <algorithm>
#include <QPainterPath>
#include <thread>
//多节刚性杆模型
class Worm : public QWidget
{
Q_OBJECT
Q_PROPERTY(int curFrameIndex READ curFrameIndex WRITE setcurFrameIndex NOTIFY curFrameIndexChanged)
public:
Worm(QWidget *parent = nullptr);
~Worm();
enum class Direction
{
Up=0,
Down,
Left,
Right
};
private:
Ui::WormClass ui;
int m_frameRatio = 60; //帧率
int m_curFrameIndex = -1; //当前帧序号
int curFrameIndex();
void setcurFrameIndex(int frameIndex);
const double m_step = 5.0; //一帧前进的步长
const int boneCount = 40; //骨骼点数量
QList<QPointF*> m_bonePoints; //骨骼点坐标
const double angleConstraint = M_PI_4/1; //角度约束
QList<qreal> m_constraintRadius; //骨骼点约束半径
//在骨骼点外绘制圆
QList<qreal> m_bodyRadius
{
20, 25, 20, 15, 15,
15, 15, 15, 15, 15,
15, 15, 15, 15, 15,
15, 15, 15, 15, 15,
15, 15, 15, 15, 15,
15, 15, 15, 15, 15,
15, 15, 15, 15, 15,
15, 15, 15, 10, 10
};
void init();
float InvSqrt(float x);
void calculate(QPointF* anchor, QPointF* target, float r);
double calBonePointsAngle(int pointIndex); //根据骨骼点坐标计算骨骼点角度
static void createNBezierCurve(const QList<QPointF>& src, QList<QPointF>& dest, qreal precision) //计算N次贝塞尔曲线
{
if (src.size() <= 0) return;
QList<QPointF>().swap(dest);
for (qreal t = 0; t < 1.0000; t += precision) {
int size = src.size();
QVector<qreal> coefficient(size, 0);
coefficient[0] = 1.000;
qreal u1 = 1.0 - t;
for (int j = 1; j <= size - 1; j++) {
qreal saved = 0.0;
for (int k = 0; k < j; k++) {
qreal temp = coefficient[k];
coefficient[k] = saved + u1 * temp;
saved = t * temp;
}
coefficient[j] = saved;
}
QPointF resultPoint;
for (int i = 0; i < size; i++) {
QPointF point = src.at(i);
resultPoint = resultPoint + point * coefficient[i];
}
dest.append(resultPoint);
}
}
//两两骨骼点连线绝对角度(骨骼点参数方程,假设angle0=angle1)
QList<double> m_bonePointsAngle;
//身体两侧轮廓点
QList<QPointF> m_bodyPointsL;
QList<QPointF> m_bodyPointsR;
Direction m_direction = Direction::Right;
Direction direction();
void setDirection(Direction dirc);
QPointF firstBonePos();
void setFirstBonePos(QPointF firstPos); //修改第一个骨骼点坐标,引起所有坐标更新
void moveDirection(double distance);
QPropertyAnimation* m_animation;
protected:
void virtual paintEvent(QPaintEvent* e) override;
void virtual keyPressEvent(QKeyEvent* e) override;
signals:
void firstBonePosChanged(QPointF firstPos);
void dirEctionChanged(Direction dirction);
void curFrameIndexChanged(int frameIndex);
};
```cpp
#include "Worm.h"
Worm::Worm(QWidget* parent)
: QWidget(parent)
{
ui.setupUi(this);
init();
m_animation = new QPropertyAnimation(this, "curFrameIndex");
m_animation->setDuration(1000);
m_animation->setLoopCount(-1);
m_animation->setStartValue(0);
m_animation->setEndValue(m_frameRatio-1);
m_animation->start();
}
Worm::~Worm()
{
for (auto bonePoint : m_bonePoints)
{
delete bonePoint;
}
delete m_animation;
}
int Worm::curFrameIndex()
{
return m_curFrameIndex;
}
void Worm::setcurFrameIndex(int frameIndex)
{
if (m_curFrameIndex != frameIndex)
{
m_curFrameIndex = frameIndex;
moveDirection(m_step);
emit curFrameIndexChanged(frameIndex);
}
}
void Worm::init()
{
for (int i = 0; i < boneCount; i++)
{
m_bonePoints.append(new QPointF(200.0 - 20.0 * i, 100.0));
m_constraintRadius.append(20);
}
for (int i = 0; i < boneCount; i++)
{
double angleBonePoint = calBonePointsAngle(i);
m_bonePointsAngle.append(angleBonePoint);
}
}
float Worm::InvSqrt(float x)
{
//1/x^0.5
float xhalf = 0.5f * x;
int i = *(int*)&x;
i = 0x5f3759df - (i >> 1);
x = *(float*)&i;
x = x * (1.5f - xhalf * x * x);
return x;
}
void Worm::calculate(QPointF* anchor, QPointF* target, float r)
{
float deltax = target->x() - anchor->x();
float deltay = target->y() - anchor->y();
float k = r * InvSqrt(deltax * deltax + deltay * deltay);
float x_ = k * target->x() + (1.0 - k) * anchor->x();
float y_ = k * target->y() + (1.0 - k) * anchor->y();
target->setX(x_);
target->setY(y_);
}
double Worm::calBonePointsAngle(int pointIndex)
{
double angleBonePoint = 0.0;
//计算骨骼点角度
if (pointIndex < 2)
{
double deltay = m_bonePoints.at(1)->y() - m_bonePoints.at(0)->y();
double deltax = m_bonePoints.at(1)->x() - m_bonePoints.at(0)->x();
angleBonePoint = atan2(deltay, deltax);
}
else
{
double deltay = m_bonePoints.at(pointIndex)->y() - m_bonePoints.at(pointIndex - 1)->y(); //向量起点是i-1,终点是i
double deltax = m_bonePoints.at(pointIndex)->x() - m_bonePoints.at(pointIndex - 1)->x();
angleBonePoint = atan2(deltay, deltax);
}
return angleBonePoint;
}
Worm::Direction Worm::direction()
{
return m_direction;
}
void Worm::setDirection(Direction dirc)
{
if (m_direction != dirc)
{
m_direction = dirc;
emit dirEctionChanged(dirc);
}
}
QPointF Worm::firstBonePos()
{
return *m_bonePoints.at(0);
}
void Worm::setFirstBonePos(QPointF firstPos)
{
if (*m_bonePoints.at(0) != firstPos)
{
m_bonePoints[0]->setX(firstPos.x());
m_bonePoints[0]->setY(firstPos.y());
emit firstBonePosChanged(firstPos);
//刷新骨骼点坐标前,清空所有坐标记录
m_bonePointsAngle.clear();
m_bodyPointsL.clear();
m_bodyPointsR.clear();
//刷新骨骼点坐标
for (int i = 0; i < m_bonePoints.size() - 1; i++)
{
//半径约束:刷新所有骨骼点坐标
calculate(m_bonePoints[i], m_bonePoints[i + 1], m_constraintRadius[i]);
}
//刷新骨骼点角度
for (int i = 0; i < m_bonePoints.size(); i++)
{
double currentAngle = calBonePointsAngle(i);
double prevAngle = calBonePointsAngle(i - 1);
//角度约束:矫正骨骼点坐标和角度
if (i >= 2 && i < m_bonePoints.size()/1)
{
double deltaAngle = 0.0;
deltaAngle = currentAngle - m_bonePointsAngle.at(i - 1);
deltaAngle = fmod(currentAngle - prevAngle + M_PI, 2 * M_PI) - M_PI; //?
//deltaAngle = currentAngle - prevAngle;
if (fabs(deltaAngle) > angleConstraint)
{
if (deltaAngle > 0)
{
currentAngle = prevAngle + angleConstraint;
}
else
{
currentAngle = prevAngle - angleConstraint;
}
double newX = m_bonePoints.at(i - 1)->x() + m_constraintRadius.at(i - 1) * cos(currentAngle);
double newY = m_bonePoints.at(i - 1)->y() + m_constraintRadius.at(i - 1) * sin(currentAngle);
m_bonePoints[i]->setX(newX);
m_bonePoints[i]->setY(newY);
}
}
m_bonePointsAngle.append(currentAngle);
//记录轮廓点
if (i == 0)
{
//头部前端3个点
double angleHead = m_bonePointsAngle.at(i) + M_PI;
double angleHead1 = m_bonePointsAngle.at(i) + 3 * M_PI_4;
double angleHead2 = m_bonePointsAngle.at(i) - 3 * M_PI_4;
QPointF bodyPointHead(m_bonePoints.at(i)->x() + m_bodyRadius.at(i) * cos(angleHead), m_bonePoints.at(i)->y() + m_bodyRadius.at(i) * sin(angleHead));
QPointF bodyPointHead1(m_bonePoints.at(i)->x() + m_bodyRadius.at(i) * cos(angleHead1), m_bonePoints.at(i)->y() + m_bodyRadius.at(i) * sin(angleHead1));
QPointF bodyPointHead2(m_bonePoints.at(i)->x() + m_bodyRadius.at(i) * cos(angleHead2), m_bonePoints.at(i)->y() + m_bodyRadius.at(i) * sin(angleHead2));
m_bodyPointsL.append(bodyPointHead);
m_bodyPointsL.append(bodyPointHead1);
m_bodyPointsR.append(bodyPointHead2);
}
double angleLeft = m_bonePointsAngle.at(i) + M_PI_2;
double angleRight = m_bonePointsAngle.at(i) - M_PI_2;
QPointF bodyPointLeft(m_bonePoints.at(i)->x() + m_bodyRadius.at(i) * cos(angleLeft), m_bonePoints.at(i)->y() + m_bodyRadius.at(i) * sin(angleLeft));
QPointF bodyPointRight(m_bonePoints.at(i)->x() + m_bodyRadius.at(i) * cos(angleRight), m_bonePoints.at(i)->y() + m_bodyRadius.at(i) * sin(angleRight));
m_bodyPointsL.append(bodyPointLeft);
m_bodyPointsR.append(bodyPointRight);
if (i == m_bonePoints.size() - 1)
{
//尾部前端3个点
double angleTail = m_bonePointsAngle.at(i);
QPointF bodyPointTail(m_bonePoints.at(i)->x() + m_bodyRadius.at(i) * cos(angleTail), m_bonePoints.at(i)->y() + m_bodyRadius.at(i) * sin(angleTail));
m_bodyPointsL.append(bodyPointTail);
}
/* painter.drawEllipse(bodyPointLeft, 2, 2);
painter.drawEllipse(bodyPointRight, 2, 2);*/
}
//重绘
update();
}
}
void Worm::moveDirection(double distance)
{
QPointF posFirstBoneNew;
switch (m_direction)
{
case Worm::Direction::Up:
posFirstBoneNew.setX(m_bonePoints.at(0)->x());
posFirstBoneNew.setY(m_bonePoints.at(0)->y() - distance);
break;
case Worm::Direction::Down:
posFirstBoneNew.setX(m_bonePoints.at(0)->x());
posFirstBoneNew.setY(m_bonePoints.at(0)->y() + distance);
break;
case Worm::Direction::Left:
posFirstBoneNew.setX(m_bonePoints.at(0)->x() - distance);
posFirstBoneNew.setY(m_bonePoints.at(0)->y());
break;
case Worm::Direction::Right:
posFirstBoneNew.setX(m_bonePoints.at(0)->x() + distance);
posFirstBoneNew.setY(m_bonePoints.at(0)->y());
break;
default:
break;
}
setFirstBonePos(posFirstBoneNew);;
}
void Worm::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing);
//绘制骨架
for (int i=0;i< m_bonePoints.size();i++)
{
painter.setBrush(QColor("lightblue"));
QPen pen(QColor("lightblue"));
pen.setWidth(5);
painter.setPen(pen);
//painter.drawEllipse(*m_bonePoints.at(i), m_bodyRadius.at(i), m_bodyRadius.at(i));
painter.drawEllipse(*m_bonePoints.at(i), 2, 2);
}
for (int i = 0; i < m_bonePoints.size() - 1; i++)
{
painter.drawLine(*m_bonePoints.at(i), *m_bonePoints.at(i + 1));
}
//绘制轮廓
/*QPen pen(QColor("red"));
painter.setPen(pen);*/
std::reverse(m_bodyPointsR.begin(), m_bodyPointsR.end());
QList<QPointF> bodyPointsTmp = m_bodyPointsL + m_bodyPointsR;
//直线
painter.drawPolygon(bodyPointsTmp);
//N次贝塞尔曲线
//QList<QPointF> bodyPoints;
//createNBezierCurve(bodyPointsTmp, bodyPoints, 0.01);
//QPainterPath path;
//for (int i = 0;i < bodyPoints.size()-2;i++)
//{
// if(i == 0) path.moveTo(bodyPoints.at(0)); // 设置起始点
// path.cubicTo(bodyPoints.at(i), bodyPoints.at(i+1), bodyPoints.at(i+2)); // 3次贝塞尔曲线
// if(i == bodyPoints.size() - 3) path.cubicTo(bodyPoints.at(i+1), bodyPoints.at(i + 2), bodyPoints.at(0));
//}
//painter.drawPath(path);
//绘制眼睛
painter.setPen(QColor("green"));
painter.setBrush(QColor("green"));
double eye0Angle = m_bonePointsAngle.at(0) + M_PI_2;
double eye1Angle = m_bonePointsAngle.at(0) - M_PI_2;
double eyeRadius = m_bodyRadius.at(0) - 5; //眼睛所在圆半径小一点
QPointF eye0(m_bonePoints.at(0)->x() + eyeRadius * cos(eye0Angle), m_bonePoints.at(0)->y() + eyeRadius * sin(eye0Angle));
QPointF eye1(m_bonePoints.at(0)->x() + eyeRadius * cos(eye1Angle), m_bonePoints.at(0)->y() + eyeRadius * sin(eye1Angle));
painter.drawEllipse(eye0, 2, 2);
painter.drawEllipse(eye1, 2, 2);
}
void Worm::keyPressEvent(QKeyEvent* e)
{
switch (e->key())
{
case Qt::Key_W:
setDirection(Direction::Up);
break;
case Qt::Key_S:
setDirection(Direction::Down);
break;
case Qt::Key_A:
setDirection(Direction::Left);
break;
case Qt::Key_D:
setDirection(Direction::Right);
break;
case Qt::Key_Up:
if (m_animation->state() == QAbstractAnimation::Running) m_animation->stop();
break;
case Qt::Key_Down:
if (m_animation->state() == QAbstractAnimation::Stopped) m_animation->start();
break;
default:
break;
}
}
标签:动画,15,qt,实现,bonePoints,void,Worm,int,double
From: https://www.cnblogs.com/wk2522466153/p/18466162