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 | |
30 | static inline b2Vec2 toB2Vec2(const QPointF& p) |
31 | { |
32 | return b2Vec2(p.x(), p.y()); |
33 | } |
34 | |
35 | //BEGIN Kolf::Shape |
36 | |
37 | const qreal Kolf::Shape::ActivationOutlinePadding = 5; |
38 | |
39 | Kolf::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 | |
53 | Kolf::Shape::~Shape() |
54 | { |
55 | delete m_fixtureDef; |
56 | updateFixture(0); //clear fixture and shape |
57 | } |
58 | |
59 | QPainterPath Kolf::Shape::activationOutline() const |
60 | { |
61 | return m_activationOutline; |
62 | } |
63 | |
64 | QPainterPath Kolf::Shape::interactionOutline() const |
65 | { |
66 | return m_interactionOutline; |
67 | } |
68 | |
69 | bool 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 | |
79 | Kolf::Shape::Traits Kolf::Shape::traits() const |
80 | { |
81 | return m_traits; |
82 | } |
83 | |
84 | void Kolf::Shape::setTraits(Kolf::Shape::Traits traits) |
85 | { |
86 | if (m_traits == traits) |
87 | return; |
88 | m_traits = traits; |
89 | updateFixture(createShape()); |
90 | } |
91 | |
92 | void 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 | |
117 | void 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 | |
134 | Kolf::EllipseShape::EllipseShape(const QRectF& rect) |
135 | : m_rect(rect) |
136 | { |
137 | update(); |
138 | } |
139 | |
140 | QRectF Kolf::EllipseShape::rect() const |
141 | { |
142 | return m_rect; |
143 | } |
144 | |
145 | void Kolf::EllipseShape::setRect(const QRectF& rect) |
146 | { |
147 | if (m_rect != rect) |
148 | { |
149 | m_rect = rect; |
150 | update(); |
151 | } |
152 | } |
153 | |
154 | b2Shape* 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 | |
189 | void 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 | |
199 | Kolf::RectShape::RectShape(const QRectF& rect) |
200 | : m_rect(rect) |
201 | { |
202 | update(); |
203 | } |
204 | |
205 | QRectF Kolf::RectShape::rect() const |
206 | { |
207 | return m_rect; |
208 | } |
209 | |
210 | void Kolf::RectShape::setRect(const QRectF& rect) |
211 | { |
212 | if (m_rect != rect) |
213 | { |
214 | m_rect = rect; |
215 | update(); |
216 | } |
217 | } |
218 | |
219 | b2Shape* 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 | |
231 | void 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 | |
241 | Kolf::LineShape::LineShape(const QLineF& line) |
242 | : m_line(line) |
243 | { |
244 | update(); |
245 | } |
246 | |
247 | QLineF Kolf::LineShape::line() const |
248 | { |
249 | return m_line; |
250 | } |
251 | |
252 | void Kolf::LineShape::setLine(const QLineF& line) |
253 | { |
254 | if (m_line != line) |
255 | { |
256 | m_line = line; |
257 | update(); |
258 | } |
259 | } |
260 | |
261 | b2Shape* 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 | |
271 | void 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 | |