File indexing completed on 2024-05-12 04:04:35

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