File indexing completed on 2025-02-02 07:28:25
0001 /* 0002 Copyright 2008-2010 Stefan Majewsky <majewsky@gmx.net> 0003 0004 This program is free software; you can redistribute it and/or modify 0005 it under the terms of the GNU General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or 0007 (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 GNU General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License 0015 along with this program; if not, write to the Free Software 0016 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0017 */ 0018 0019 #include "shape.h" 0020 #include "canvasitem.h" 0021 #include "overlay.h" 0022 #include <QGraphicsScene> 0023 #include <QVarLengthArray> 0024 #include <Box2D/Collision/Shapes/b2CircleShape.h> 0025 #include <Box2D/Collision/Shapes/b2EdgeShape.h> 0026 #include <Box2D/Collision/Shapes/b2PolygonShape.h> 0027 #include <Box2D/Dynamics/b2Fixture.h> 0028 0029 static inline b2Vec2 toB2Vec2(const QPointF& p) 0030 { 0031 return b2Vec2(p.x(), p.y()); 0032 } 0033 0034 //BEGIN Kolf::Shape 0035 0036 const qreal Kolf::Shape::ActivationOutlinePadding = 5; 0037 0038 Kolf::Shape::Shape() 0039 : m_traits(Kolf::Shape::ParticipatesInPhysicalSimulation) 0040 , m_citem(nullptr) 0041 , m_body(nullptr) 0042 , m_fixtureDef(new b2FixtureDef) 0043 , m_fixture(nullptr) 0044 , m_shape(nullptr) 0045 { 0046 m_fixtureDef->density = 1; 0047 m_fixtureDef->restitution = 1; 0048 m_fixtureDef->friction = 0; 0049 m_fixtureDef->userData = this; 0050 } 0051 0052 Kolf::Shape::~Shape() 0053 { 0054 delete m_fixtureDef; 0055 updateFixture(nullptr); //clear fixture and shape 0056 } 0057 0058 QPainterPath Kolf::Shape::activationOutline() const 0059 { 0060 return m_activationOutline; 0061 } 0062 0063 QPainterPath Kolf::Shape::interactionOutline() const 0064 { 0065 return m_interactionOutline; 0066 } 0067 0068 bool Kolf::Shape::attach(CanvasItem* item) 0069 { 0070 if (m_citem) 0071 return false; 0072 m_citem = item; 0073 m_body = item->m_body; 0074 updateFixture(createShape()); 0075 return true; 0076 } 0077 0078 Kolf::Shape::Traits Kolf::Shape::traits() const 0079 { 0080 return m_traits; 0081 } 0082 0083 void Kolf::Shape::setTraits(Kolf::Shape::Traits traits) 0084 { 0085 if (m_traits == traits) 0086 return; 0087 m_traits = traits; 0088 updateFixture(createShape()); 0089 } 0090 0091 void Kolf::Shape::updateFixture(b2Shape* newShape) 0092 { 0093 if (!m_body) 0094 return; 0095 //destroy old fixture 0096 if (m_fixture) 0097 m_body->DestroyFixture(m_fixture); 0098 delete m_shape; 0099 m_shape = nullptr; 0100 //create new fixture 0101 if (m_traits & Kolf::Shape::CollisionDetectionFlag) 0102 { 0103 m_shape = newShape; 0104 if (m_shape) 0105 { 0106 b2FixtureDef fixtureDef = *m_fixtureDef; 0107 fixtureDef.shape = m_shape; 0108 fixtureDef.isSensor = !(m_traits & Kolf::Shape::PhysicalSimulationFlag); 0109 m_fixture = m_body->CreateFixture(&fixtureDef); 0110 } 0111 } 0112 else 0113 delete newShape; //TODO: inefficient 0114 } 0115 0116 void Kolf::Shape::update() 0117 { 0118 updateFixture(createShape()); 0119 m_interactionOutline = m_activationOutline = QPainterPath(); 0120 createOutlines(m_activationOutline, m_interactionOutline); 0121 //propagate update to overlays 0122 if (m_citem) 0123 { 0124 Kolf::Overlay* overlay = m_citem->overlay(false); 0125 if (overlay) //may be 0 if the overlay has not yet been instantiated 0126 overlay->update(); 0127 } 0128 } 0129 0130 //END Kolf::Shape 0131 //BEGIN Kolf::EllipseShape 0132 0133 Kolf::EllipseShape::EllipseShape(const QRectF& rect) 0134 : m_rect(rect) 0135 { 0136 update(); 0137 } 0138 0139 QRectF Kolf::EllipseShape::rect() const 0140 { 0141 return m_rect; 0142 } 0143 0144 void Kolf::EllipseShape::setRect(const QRectF& rect) 0145 { 0146 if (m_rect != rect) 0147 { 0148 m_rect = rect; 0149 update(); 0150 } 0151 } 0152 0153 b2Shape* Kolf::EllipseShape::createShape() 0154 { 0155 const b2Vec2 c = toB2Vec2(m_rect.center() * Kolf::Box2DScaleFactor); 0156 //ensure some minimum size because b2PolygonShape gets confused when its 0157 //area is smaller than FLT_EPSILON (TODO: handle rx*ry == 0 differently?) 0158 const qreal rx = qMax(qreal(m_rect.width() * Kolf::Box2DScaleFactor / 2), qreal(1e-5)); 0159 const qreal ry = qMax(qreal(m_rect.height() * Kolf::Box2DScaleFactor / 2), qreal(1e-5)); 0160 if (rx == ry) 0161 { 0162 //use circle shape when possible because it's cheaper and exact 0163 b2CircleShape* shape = new b2CircleShape; 0164 shape->m_p = c; 0165 shape->m_radius = rx; 0166 return shape; 0167 } 0168 else 0169 { 0170 //elliptical shape is not pre-made in Box2D, so create a polygon instead 0171 b2PolygonShape* shape = new b2PolygonShape; 0172 static const int N = qMin(20, b2_maxPolygonVertices); 0173 //increase N if the approximation turns out to be too bad 0174 //TODO: calculate the (cos, sin) pairs only once 0175 QVarLengthArray<b2Vec2, 20> vertices(N); 0176 static const qreal angleStep = 2 * M_PI / N; 0177 for (int i = 0; i < N; ++i) 0178 { 0179 const qreal angle = -i * angleStep; //CCW order as required by Box2D 0180 vertices[i].x = c.x + rx * cos(angle); 0181 vertices[i].y = c.y + ry * sin(angle); 0182 } 0183 shape->Set(vertices.data(), N); 0184 return shape; 0185 } 0186 } 0187 0188 void Kolf::EllipseShape::createOutlines(QPainterPath& activationOutline, QPainterPath& interactionOutline) 0189 { 0190 interactionOutline.addEllipse(m_rect); 0191 const qreal& p = Kolf::Shape::ActivationOutlinePadding; 0192 activationOutline.addEllipse(m_rect.adjusted(-p, -p, p, p)); 0193 } 0194 0195 //END Kolf::EllipseShape 0196 //BEGIN Kolf::RectShape 0197 0198 Kolf::RectShape::RectShape(const QRectF& rect) 0199 : m_rect(rect) 0200 { 0201 update(); 0202 } 0203 0204 QRectF Kolf::RectShape::rect() const 0205 { 0206 return m_rect; 0207 } 0208 0209 void Kolf::RectShape::setRect(const QRectF& rect) 0210 { 0211 if (m_rect != rect) 0212 { 0213 m_rect = rect; 0214 update(); 0215 } 0216 } 0217 0218 b2Shape* Kolf::RectShape::createShape() 0219 { 0220 b2PolygonShape* shape = new b2PolygonShape; 0221 shape->SetAsBox( 0222 m_rect.width() * Kolf::Box2DScaleFactor / 2, 0223 m_rect.height() * Kolf::Box2DScaleFactor / 2, 0224 toB2Vec2(m_rect.center() * Kolf::Box2DScaleFactor), 0225 0 //intrinsic rotation angle 0226 ); 0227 return shape; 0228 } 0229 0230 void Kolf::RectShape::createOutlines(QPainterPath& activationOutline, QPainterPath& interactionOutline) 0231 { 0232 interactionOutline.addRect(m_rect); 0233 const qreal& p = Kolf::Shape::ActivationOutlinePadding; 0234 activationOutline.addRect(m_rect.adjusted(-p, -p, p, p)); 0235 } 0236 0237 //END Kolf::RectShape 0238 //BEGIN Kolf::LineShape 0239 0240 Kolf::LineShape::LineShape(const QLineF& line) 0241 : m_line(line) 0242 { 0243 update(); 0244 } 0245 0246 QLineF Kolf::LineShape::line() const 0247 { 0248 return m_line; 0249 } 0250 0251 void Kolf::LineShape::setLine(const QLineF& line) 0252 { 0253 if (m_line != line) 0254 { 0255 m_line = line; 0256 update(); 0257 } 0258 } 0259 0260 b2Shape* Kolf::LineShape::createShape() 0261 { 0262 b2EdgeShape* shape = new b2EdgeShape; 0263 shape->Set( 0264 toB2Vec2(m_line.p1() * Kolf::Box2DScaleFactor), 0265 toB2Vec2(m_line.p2() * Kolf::Box2DScaleFactor) 0266 ); 0267 return shape; 0268 } 0269 0270 void Kolf::LineShape::createOutlines(QPainterPath& activationOutline, QPainterPath& interactionOutline) 0271 { 0272 const QPointF extent = m_line.p2() - m_line.p1(); 0273 const qreal angle = atan2(extent.y(), extent.x()); 0274 const qreal paddingAngle = angle + M_PI / 2; 0275 const qreal padding = Kolf::Shape::ActivationOutlinePadding; 0276 const QPointF paddingVector(padding * cos(paddingAngle), padding * sin(paddingAngle)); 0277 //interaction outline: a rectangle that is aligned with the wall 0278 interactionOutline.moveTo(m_line.p1() + paddingVector); 0279 interactionOutline.lineTo(m_line.p1() - paddingVector); 0280 interactionOutline.lineTo(m_line.p2() - paddingVector); 0281 interactionOutline.lineTo(m_line.p2() + paddingVector); 0282 interactionOutline.closeSubpath(); 0283 //activation outline: the same rectangle with additional half-circles at the ends 0284 activationOutline = interactionOutline; 0285 activationOutline.addEllipse(m_line.p1(), padding, padding); 0286 activationOutline.addEllipse(m_line.p2(), padding, padding); 0287 } 0288 0289 //END Kolf::LineShape