1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "stickman.h"
52#include "node.h"
53
54#include <QPainter>
55#include <QtMath>
56
57static constexpr qreal Coords[NodeCount * 2] = {
58 0.0, -150.0, // head, #0
59
60 0.0, -100.0, // body pentagon, top->bottom, left->right, #1 - 5
61 -50.0, -50.0,
62 50.0, -50.0,
63 -25.0, 50.0,
64 25.0, 50.0,
65
66 -100.0, 0.0, // right arm, #6 - 7
67 -125.0, 50.0,
68
69 100.0, 0.0, // left arm, #8 - 9
70 125.0, 50.0,
71
72 -35.0, 75.0, // lower body, #10 - 11
73 35.0, 75.0,
74
75 -25.0, 200.0, // right leg, #12 - 13
76 -30.0, 300.0,
77
78 25.0, 200.0, // left leg, #14 - 15
79 30.0, 300.0
80
81};
82
83static constexpr int Bones[BoneCount * 2] = {
84 0, 1, // neck
85
86 1, 2, // body
87 1, 3,
88 1, 4,
89 1, 5,
90 2, 3,
91 2, 4,
92 2, 5,
93 3, 4,
94 3, 5,
95 4, 5,
96
97 2, 6, // right arm
98 6, 7,
99
100 3, 8, // left arm
101 8, 9,
102
103 4, 10, // lower body
104 4, 11,
105 5, 10,
106 5, 11,
107 10, 11,
108
109 10, 12, // right leg
110 12, 13,
111
112 11, 14, // left leg
113 14, 15
114
115};
116
117StickMan::StickMan()
118{
119 // Set up start position of limbs
120 for (int i = 0; i < NodeCount; ++i) {
121 m_nodes[i] = new Node(QPointF(Coords[i * 2], Coords[i * 2 + 1]), this);
122 connect(sender: m_nodes[i], signal: &Node::positionChanged, receiver: this, slot: &StickMan::childPositionChanged);
123 }
124
125 for (int i = 0; i < BoneCount; ++i) {
126 int n1 = Bones[i * 2];
127 int n2 = Bones[i * 2 + 1];
128
129 Node *node1 = m_nodes[n1];
130 Node *node2 = m_nodes[n2];
131
132 QPointF dist = node1->pos() - node2->pos();
133 m_perfectBoneLengths[i] = sqrt(x: pow(x: dist.x(), y: 2) + pow(x: dist.y(), y: 2));
134 }
135
136 startTimer(interval: 10);
137}
138
139void StickMan::childPositionChanged()
140{
141 prepareGeometryChange();
142}
143
144void StickMan::setDrawSticks(bool on)
145{
146 m_sticks = on;
147 for (int i = 0; i < nodeCount(); ++i) {
148 Node *node = m_nodes[i];
149 node->setVisible(on);
150 }
151}
152
153QRectF StickMan::boundingRect() const
154{
155 // account for head radius=50.0 plus pen which is 5.0
156 return childrenBoundingRect().adjusted(xp1: -55.0, yp1: -55.0, xp2: 55.0, yp2: 55.0);
157}
158
159int StickMan::nodeCount() const
160{
161 return NodeCount;
162}
163
164Node *StickMan::node(int idx) const
165{
166 if (idx >= 0 && idx < NodeCount)
167 return m_nodes[idx];
168 return nullptr;
169}
170
171void StickMan::timerEvent(QTimerEvent *)
172{
173 update();
174}
175
176void StickMan::stabilize()
177{
178 static const qreal threshold = 0.001;
179
180 for (int i = 0; i < BoneCount; ++i) {
181 int n1 = Bones[i * 2];
182 int n2 = Bones[i * 2 + 1];
183
184 Node *node1 = m_nodes[n1];
185 Node *node2 = m_nodes[n2];
186
187 QPointF pos1 = node1->pos();
188 QPointF pos2 = node2->pos();
189
190 QPointF dist = pos1 - pos2;
191 qreal length = sqrt(x: pow(x: dist.x(),y: 2) + pow(x: dist.y(),y: 2));
192 qreal diff = (length - m_perfectBoneLengths[i]) / length;
193
194 QPointF p = dist * (0.5 * diff);
195 if (p.x() > threshold && p.y() > threshold) {
196 pos1 -= p;
197 pos2 += p;
198
199 node1->setPos(pos1);
200 node2->setPos(pos2);
201 }
202 }
203}
204
205QPointF StickMan::posFor(int idx) const
206{
207 return m_nodes[idx]->pos();
208}
209
210//#include <QTime>
211void StickMan::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
212{
213 /* static int frames = 0;
214 static QTime time;
215 if (frames++ % 100 == 0) {
216 frames = 1;
217 time.restart();
218 }
219
220 if (time.elapsed() > 0) {
221 painter->setPen(Qt::white);
222 painter->drawText(0, 0, QString::number(frames / (time.elapsed() / 1000.0)));
223 }*/
224
225 stabilize();
226 if (m_sticks) {
227 painter->setPen(Qt::white);
228 for (int i = 0; i < BoneCount; ++i) {
229 int n1 = Bones[i * 2];
230 int n2 = Bones[i * 2 + 1];
231
232 Node *node1 = m_nodes[n1];
233 Node *node2 = m_nodes[n2];
234
235 painter->drawLine(p1: node1->pos(), p2: node2->pos());
236 }
237 } else {
238 // first bone is neck and will be used for head
239
240 QPainterPath path;
241 path.moveTo(p: posFor(idx: 0));
242 path.lineTo(p: posFor(idx: 1));
243
244 // right arm
245 path.lineTo(p: posFor(idx: 2));
246 path.lineTo(p: posFor(idx: 6));
247 path.lineTo(p: posFor(idx: 7));
248
249 // left arm
250 path.moveTo(p: posFor(idx: 3));
251 path.lineTo(p: posFor(idx: 8));
252 path.lineTo(p: posFor(idx: 9));
253
254 // body
255 path.moveTo(p: posFor(idx: 2));
256 path.lineTo(p: posFor(idx: 4));
257 path.lineTo(p: posFor(idx: 10));
258 path.lineTo(p: posFor(idx: 11));
259 path.lineTo(p: posFor(idx: 5));
260 path.lineTo(p: posFor(idx: 3));
261 path.lineTo(p: posFor(idx: 1));
262
263 // right leg
264 path.moveTo(p: posFor(idx: 10));
265 path.lineTo(p: posFor(idx: 12));
266 path.lineTo(p: posFor(idx: 13));
267
268 // left leg
269 path.moveTo(p: posFor(idx: 11));
270 path.lineTo(p: posFor(idx: 14));
271 path.lineTo(p: posFor(idx: 15));
272
273 painter->setPen(QPen(m_penColor, 5.0, Qt::SolidLine, Qt::RoundCap));
274 painter->drawPath(path);
275
276 {
277 int n1 = Bones[0];
278 int n2 = Bones[1];
279 Node *node1 = m_nodes[n1];
280 Node *node2 = m_nodes[n2];
281
282 QPointF dist = node2->pos() - node1->pos();
283
284 qreal sinAngle = dist.x() / sqrt(x: pow(x: dist.x(), y: 2) + pow(x: dist.y(), y: 2));
285 qreal angle = qRadiansToDegrees(radians: asin(x: sinAngle));
286
287 QPointF headPos = node1->pos();
288 painter->translate(offset: headPos);
289 painter->rotate(a: -angle);
290
291 painter->setBrush(m_fillColor);
292 painter->drawEllipse(center: QPointF(0,0), rx: 50.0, ry: 50.0);
293
294 painter->setBrush(m_penColor);
295 painter->setPen(QPen(m_penColor, 2.5, Qt::SolidLine, Qt::RoundCap));
296
297 // eyes
298 if (m_isDead) {
299 painter->drawLine(x1: -30.0, y1: -30.0, x2: -20.0, y2: -20.0);
300 painter->drawLine(x1: -20.0, y1: -30.0, x2: -30.0, y2: -20.0);
301
302 painter->drawLine(x1: 20.0, y1: -30.0, x2: 30.0, y2: -20.0);
303 painter->drawLine(x1: 30.0, y1: -30.0, x2: 20.0, y2: -20.0);
304 } else {
305 painter->drawChord(rect: QRectF(-30.0, -30.0, 25.0, 70.0), a: 30.0*16, alen: 120.0*16);
306 painter->drawChord(rect: QRectF(5.0, -30.0, 25.0, 70.0), a: 30.0*16, alen: 120.0*16);
307 }
308
309 // mouth
310 if (m_isDead) {
311 painter->drawLine(x1: -28.0, y1: 2.0, x2: 29.0, y2: 2.0);
312 } else {
313 painter->setBrush(QColor(128, 0, 64 ));
314 painter->drawChord(rect: QRectF(-28.0, 2.0-55.0/2.0, 57.0, 55.0), a: 0.0, alen: -180.0*16);
315 }
316
317 // pupils
318 if (!m_isDead) {
319 painter->setPen(QPen(m_fillColor, 1.0, Qt::SolidLine, Qt::RoundCap));
320 painter->setBrush(m_fillColor);
321 painter->drawEllipse(center: QPointF(-12.0, -25.0), rx: 5.0, ry: 5.0);
322 painter->drawEllipse(center: QPointF(22.0, -25.0), rx: 5.0, ry: 5.0);
323 }
324 }
325 }
326}
327
328
329
330

source code of qtbase/examples/widgets/animation/stickman/stickman.cpp