1/*
2 Copyright 2008-2010 Stefan Majewsky <majewsky@gmx.net>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17*/
18
19#include "shape.h"
20#include "canvasitem.h"
21#include "overlay.h"
22
23#include <QtCore/qmath.h>
24#include <QtCore/QVarLengthArray>
25#include <Box2D/Collision/Shapes/b2CircleShape.h>
26#include <Box2D/Collision/Shapes/b2EdgeShape.h>
27#include <Box2D/Collision/Shapes/b2PolygonShape.h>
28#include <Box2D/Dynamics/b2Fixture.h>
29
30static inline b2Vec2 toB2Vec2(const QPointF& p)
31{
32 return b2Vec2(p.x(), p.y());
33}
34
35//BEGIN Kolf::Shape
36
37const qreal Kolf::Shape::ActivationOutlinePadding = 5;
38
39Kolf::Shape::Shape()
40 : m_traits(Kolf::Shape::ParticipatesInPhysicalSimulation)
41 , m_citem(0)
42 , m_body(0)
43 , m_fixtureDef(new b2FixtureDef)
44 , m_fixture(0)
45 , m_shape(0)
46{
47 m_fixtureDef->density = 1;
48 m_fixtureDef->restitution = 1;
49 m_fixtureDef->friction = 0;
50 m_fixtureDef->userData = this;
51}
52
53Kolf::Shape::~Shape()
54{
55 delete m_fixtureDef;
56 updateFixture(0); //clear fixture and shape
57}
58
59QPainterPath Kolf::Shape::activationOutline() const
60{
61 return m_activationOutline;
62}
63
64QPainterPath Kolf::Shape::interactionOutline() const
65{
66 return m_interactionOutline;
67}
68
69bool Kolf::Shape::attach(CanvasItem* item)
70{
71 if (m_citem)
72 return false;
73 m_citem = item;
74 m_body = item->m_body;
75 updateFixture(createShape());
76 return true;
77}
78
79Kolf::Shape::Traits Kolf::Shape::traits() const
80{
81 return m_traits;
82}
83
84void Kolf::Shape::setTraits(Kolf::Shape::Traits traits)
85{
86 if (m_traits == traits)
87 return;
88 m_traits = traits;
89 updateFixture(createShape());
90}
91
92void Kolf::Shape::updateFixture(b2Shape* newShape)
93{
94 if (!m_body)
95 return;
96 //destroy old fixture
97 if (m_fixture)
98 m_body->DestroyFixture(m_fixture);
99 delete m_shape;
100 m_shape = 0;
101 //create new fixture
102 if (m_traits & Kolf::Shape::CollisionDetectionFlag)
103 {
104 m_shape = newShape;
105 if (m_shape)
106 {
107 b2FixtureDef fixtureDef = *m_fixtureDef;
108 fixtureDef.shape = m_shape;
109 fixtureDef.isSensor = !(m_traits & Kolf::Shape::PhysicalSimulationFlag);
110 m_fixture = m_body->CreateFixture(&fixtureDef);
111 }
112 }
113 else
114 delete newShape; //TODO: inefficient
115}
116
117void Kolf::Shape::update()
118{
119 updateFixture(createShape());
120 m_interactionOutline = m_activationOutline = QPainterPath();
121 createOutlines(m_activationOutline, m_interactionOutline);
122 //propagate update to overlays
123 if (m_citem)
124 {
125 Kolf::Overlay* overlay = m_citem->overlay(false);
126 if (overlay) //may be 0 if the overlay has not yet been instantiated
127 overlay->update();
128 }
129}
130
131//END Kolf::Shape
132//BEGIN Kolf::EllipseShape
133
134Kolf::EllipseShape::EllipseShape(const QRectF& rect)
135 : m_rect(rect)
136{
137 update();
138}
139
140QRectF Kolf::EllipseShape::rect() const
141{
142 return m_rect;
143}
144
145void Kolf::EllipseShape::setRect(const QRectF& rect)
146{
147 if (m_rect != rect)
148 {
149 m_rect = rect;
150 update();
151 }
152}
153
154b2Shape* Kolf::EllipseShape::createShape()
155{
156 const b2Vec2 c = toB2Vec2(m_rect.center() * Kolf::Box2DScaleFactor);
157 //ensure some minimum size because b2PolygonShape gets confused when its
158 //area is smaller than FLT_EPSILON (TODO: handle rx*ry == 0 differently?)
159 const qreal rx = qMax(qreal(m_rect.width() * Kolf::Box2DScaleFactor / 2), qreal(1e-5));
160 const qreal ry = qMax(qreal(m_rect.height() * Kolf::Box2DScaleFactor / 2), qreal(1e-5));
161 if (rx == ry)
162 {
163 //use circle shape when possible because it's cheaper and exact
164 b2CircleShape* shape = new b2CircleShape;
165 shape->m_p = c;
166 shape->m_radius = rx;
167 return shape;
168 }
169 else
170 {
171 //elliptical shape is not pre-made in Box2D, so create a polygon instead
172 b2PolygonShape* shape = new b2PolygonShape;
173 static const int N = qMin(20, b2_maxPolygonVertices);
174 //increase N if the approximation turns out to be too bad
175 //TODO: calculate the (cos, sin) pairs only once
176 QVarLengthArray<b2Vec2, 20> vertices(N);
177 static const qreal angleStep = 2 * M_PI / N;
178 for (int i = 0; i < N; ++i)
179 {
180 const qreal angle = -i * angleStep; //CCW order as required by Box2D
181 vertices[i].x = c.x + rx * cos(angle);
182 vertices[i].y = c.y + ry * sin(angle);
183 }
184 shape->Set(vertices.data(), N);
185 return shape;
186 }
187}
188
189void Kolf::EllipseShape::createOutlines(QPainterPath& activationOutline, QPainterPath& interactionOutline)
190{
191 interactionOutline.addEllipse(m_rect);
192 const qreal& p = Kolf::Shape::ActivationOutlinePadding;
193 activationOutline.addEllipse(m_rect.adjusted(-p, -p, p, p));
194}
195
196//END Kolf::EllipseShape
197//BEGIN Kolf::RectShape
198
199Kolf::RectShape::RectShape(const QRectF& rect)
200 : m_rect(rect)
201{
202 update();
203}
204
205QRectF Kolf::RectShape::rect() const
206{
207 return m_rect;
208}
209
210void Kolf::RectShape::setRect(const QRectF& rect)
211{
212 if (m_rect != rect)
213 {
214 m_rect = rect;
215 update();
216 }
217}
218
219b2Shape* Kolf::RectShape::createShape()
220{
221 b2PolygonShape* shape = new b2PolygonShape;
222 shape->SetAsBox(
223 m_rect.width() * Kolf::Box2DScaleFactor / 2,
224 m_rect.height() * Kolf::Box2DScaleFactor / 2,
225 toB2Vec2(m_rect.center() * Kolf::Box2DScaleFactor),
226 0 //intrinsic rotation angle
227 );
228 return shape;
229}
230
231void Kolf::RectShape::createOutlines(QPainterPath& activationOutline, QPainterPath& interactionOutline)
232{
233 interactionOutline.addRect(m_rect);
234 const qreal& p = Kolf::Shape::ActivationOutlinePadding;
235 activationOutline.addRect(m_rect.adjusted(-p, -p, p, p));
236}
237
238//END Kolf::RectShape
239//BEGIN Kolf::LineShape
240
241Kolf::LineShape::LineShape(const QLineF& line)
242 : m_line(line)
243{
244 update();
245}
246
247QLineF Kolf::LineShape::line() const
248{
249 return m_line;
250}
251
252void Kolf::LineShape::setLine(const QLineF& line)
253{
254 if (m_line != line)
255 {
256 m_line = line;
257 update();
258 }
259}
260
261b2Shape* Kolf::LineShape::createShape()
262{
263 b2EdgeShape* shape = new b2EdgeShape;
264 shape->Set(
265 toB2Vec2(m_line.p1() * Kolf::Box2DScaleFactor),
266 toB2Vec2(m_line.p2() * Kolf::Box2DScaleFactor)
267 );
268 return shape;
269}
270
271void Kolf::LineShape::createOutlines(QPainterPath& activationOutline, QPainterPath& interactionOutline)
272{
273 const QPointF extent = m_line.p2() - m_line.p1();
274 const qreal angle = atan2(extent.y(), extent.x());
275 const qreal paddingAngle = angle + M_PI / 2;
276 const qreal padding = Kolf::Shape::ActivationOutlinePadding;
277 const QPointF paddingVector(padding * cos(paddingAngle), padding * sin(paddingAngle));
278 //interaction outline: a rectangle that is aligned with the wall
279 interactionOutline.moveTo(m_line.p1() + paddingVector);
280 interactionOutline.lineTo(m_line.p1() - paddingVector);
281 interactionOutline.lineTo(m_line.p2() - paddingVector);
282 interactionOutline.lineTo(m_line.p2() + paddingVector);
283 interactionOutline.closeSubpath();
284 //activation outline: the same rectangle with additional half-circles at the ends
285 activationOutline = interactionOutline;
286 activationOutline.addEllipse(m_line.p1(), padding, padding);
287 activationOutline.addEllipse(m_line.p2(), padding, padding);
288}
289
290//END Kolf::LineShape
291