File indexing completed on 2024-12-22 03:51:26

0001 /*
0002     Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
0003     Copyright 2008, 2009, 2010 Stefan Majewsky <majewsky@gmx.net>
0004 
0005     This program is free software; you can redistribute it and/or modify
0006     it under the terms of the GNU General Public License as published by
0007     the Free Software Foundation; either version 2 of the License, or
0008     (at your option) any later version.
0009 
0010     This program is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013     GNU General Public License for more details.
0014 
0015     You should have received a copy of the GNU General Public License
0016     along with this program; if not, write to the Free Software
0017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 */
0019 
0020 #include "canvasitem.h"
0021 #include "game.h"
0022 #include "landscape.h"
0023 #include "overlay.h"
0024 #include "shape.h"
0025 
0026 #include <Box2D/Dynamics/b2Body.h>
0027 #include <Box2D/Dynamics/b2World.h>
0028 
0029 //this is how much a strut and the items on it are raised
0030 static const int ZValueStep = 100;
0031 
0032 CanvasItem::CanvasItem(b2World* world)
0033     : game(nullptr)
0034     , m_zBehavior(CanvasItem::FixedZValue)
0035     , m_zValue(0)
0036     , m_strut(nullptr)
0037     , m_staticStrut(nullptr)
0038     , m_body(nullptr)
0039     , m_overlay(nullptr)
0040     , m_simulationType((CanvasItem::SimulationType) -1)
0041 {
0042     b2BodyDef bodyDef;
0043     bodyDef.userData = this;
0044     m_body = world->CreateBody(&bodyDef);
0045     setSimulationType(CanvasItem::CollisionSimulation);
0046 }
0047 
0048 CanvasItem::~CanvasItem()
0049 {
0050     //disconnect struts
0051     if (m_strut)
0052         m_strut->m_struttedItems.removeAll(this);
0053     for (CanvasItem* item : std::as_const(m_struttedItems))
0054         item->m_strut = nullptr;
0055     //The overlay is deleted first, because it might interact with all other parts of the object.
0056     delete m_overlay;
0057     //NOTE: Box2D objects will need to be destroyed in the following order:
0058     //subobjects, shapes, own b2Body
0059     qDeleteAll(m_shapes);
0060     m_body->GetWorld()->DestroyBody(m_body);
0061 }
0062 
0063 void CanvasItem::setZBehavior(CanvasItem::ZBehavior behavior, qreal zValue)
0064 {
0065     m_zBehavior = behavior;
0066     m_zValue = zValue;
0067     QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(this);
0068     if (qitem)
0069     {
0070         if (m_zBehavior == CanvasItem::FixedZValue)
0071             qitem->setZValue(m_zValue);
0072         else
0073             updateZ(qitem);
0074     }
0075 }
0076 
0077 void CanvasItem::setStaticStrut(CanvasItem* citem)
0078 {
0079     m_staticStrut = citem;
0080 }
0081 
0082 void CanvasItem::updateZ(QGraphicsItem* self)
0083 {
0084     //disconnect from old strut (if any)
0085     //TODO: not if old strut is new strut (or did I forget some cornercases?)
0086     if (m_strut)
0087     {
0088         m_strut->m_struttedItems.removeAll(this);
0089         m_strut = nullptr;
0090     }
0091     //simple behavior
0092     if (m_zBehavior == CanvasItem::FixedZValue)
0093     {
0094         self->setZValue(m_zValue);
0095         return;
0096     }
0097     if (m_zBehavior == CanvasItem::IsStrut)
0098     {
0099         self->setZValue(ZValueStep);
0100         return;
0101     }
0102     //determine new strut
0103     if (m_staticStrut)
0104         m_strut = m_staticStrut;
0105     else
0106     {
0107         const auto collidingItems = self->collidingItems();
0108         for (QGraphicsItem* qitem :collidingItems) {
0109             CanvasItem* citem = dynamic_cast<CanvasItem*>(qitem);
0110             if (citem && citem->m_zBehavior == CanvasItem::IsStrut)
0111             {
0112                 //special condition for slopes: they must lie inside the strut's area, not only touch it
0113                 Kolf::Slope* slope = dynamic_cast<Kolf::Slope*>(this);
0114                 if (slope)
0115                     if (!slope->collidesWithItem(qitem, Qt::ContainsItemBoundingRect))
0116                         continue;
0117                 //strut found
0118                 m_strut = citem;
0119                 break;
0120             }
0121         }
0122     }
0123     //strut found?
0124     if (m_strut)
0125     {
0126         m_strut->m_struttedItems << this;
0127         self->setZValue(m_zValue + ZValueStep);
0128     }
0129     //no strut found -> set default zValue
0130     else
0131         self->setZValue(m_zValue);
0132 }
0133 
0134 void CanvasItem::moveItemsOnStrut(const QPointF& posDiff)
0135 {
0136     for (CanvasItem* citem : std::as_const(m_struttedItems)) {
0137         QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
0138         if (!qitem || qitem->data(0) == Rtti_Putter)
0139             continue;
0140         citem->moveBy(posDiff.x(), posDiff.y());
0141         Ball* ball = dynamic_cast<Ball*>(citem);
0142         if (ball && game && !game->isEditing() && game->curBall() == ball)
0143             game->ballMoved();
0144     }
0145 }
0146 
0147 /*static*/ bool CanvasItem::mayCollide(CanvasItem* citem1, CanvasItem* citem2)
0148 {
0149     //which one is the ball?
0150     Ball* ball = dynamic_cast<Ball*>(citem1);
0151     CanvasItem* citem = citem2;
0152     if (!ball)
0153     {
0154         ball = dynamic_cast<Ball*>(citem2);
0155         citem = citem1;
0156     }
0157     if (!ball)
0158         //huh, no ball involved? then don't restrict anything, because
0159         //that likely introduces weird bugs later
0160         return true;
0161     //if both items are graphicsitems, restrict collisions of ball to those
0162     //objects on same strut level or above (i.e. don't collide with
0163     //stuff below the current strut)
0164     const QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
0165     if (!qitem)
0166         return true;
0167     const int ballStrutLevel = int(ball->zValue()) / ZValueStep;
0168     const int itemStrutLevel = int(qitem->zValue()) / ZValueStep;
0169     return ballStrutLevel <= itemStrutLevel;
0170 }
0171 
0172 void CanvasItem::moveBy(double dx, double dy)
0173 {
0174     Q_UNUSED(dx) Q_UNUSED(dy)
0175     QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(this);
0176     if (qitem)
0177         updateZ(qitem);
0178 }
0179 
0180 void CanvasItem::save(KConfigGroup *cfgGroup)
0181 {
0182     cfgGroup->writeEntry("dummykey", true);
0183 }
0184 
0185 void CanvasItem::editModeChanged(bool editing)
0186 {
0187     Kolf::Overlay* overlay = this->overlay();
0188     if (overlay)
0189         overlay->setVisible(editing);
0190 }
0191 
0192 b2World* CanvasItem::world() const
0193 {
0194     return m_body->GetWorld();
0195 }
0196 
0197 void CanvasItem::addShape(Kolf::Shape* shape)
0198 {
0199     if (shape->attach(this)) //this will fail if the shape is already attached to some object
0200         m_shapes << shape;
0201 }
0202 
0203 void CanvasItem::setSimulationType(CanvasItem::SimulationType type)
0204 {
0205     if (m_simulationType != type)
0206     {
0207         m_simulationType = type;
0208         //write type into b2Body
0209         b2BodyType b2type; bool b2active;
0210         switch (type)
0211         {
0212             case CanvasItem::NoSimulation:
0213                 b2type = b2_staticBody;
0214                 b2active = false;
0215                 break;
0216             case CanvasItem::CollisionSimulation:
0217                 b2type = b2_staticBody;
0218                 b2active = true;
0219                 break;
0220             case CanvasItem::KinematicSimulation:
0221                 b2type = b2_kinematicBody;
0222                 b2active = true;
0223                 break;
0224             case CanvasItem::DynamicSimulation: default:
0225                 b2type = b2_dynamicBody;
0226                 b2active = true;
0227                 break;
0228         }
0229         m_body->SetType(b2type);
0230         m_body->SetActive(b2active);
0231     }
0232 }
0233 
0234 QPointF CanvasItem::velocity() const
0235 {
0236     b2Vec2 v = m_body->GetLinearVelocity();
0237     return QPointF(v.x, v.y);
0238 }
0239 
0240 void CanvasItem::setVelocity(const QPointF& newVelocity)
0241 {
0242     const QPointF currentVelocity = this->velocity();
0243     if (newVelocity != currentVelocity)
0244     {
0245         const qreal mass = m_body->GetMass();
0246         //WARNING: Velocities are NOT scaled. The timestep is scaled, instead.
0247         //See where b2World::Step() gets called for more info.
0248         if (mass == 0 || m_simulationType != CanvasItem::DynamicSimulation)
0249         {
0250             m_body->SetLinearVelocity(b2Vec2(newVelocity.x(), newVelocity.y()));
0251         }
0252         else
0253         {
0254             const QPointF impulse = (newVelocity - currentVelocity) * mass;
0255             m_body->ApplyLinearImpulse(b2Vec2(impulse.x(), impulse.y()), m_body->GetPosition());
0256         }
0257     }
0258 }
0259 
0260 void CanvasItem::startSimulation()
0261 {
0262     const QPointF position = getPosition() * Kolf::Box2DScaleFactor;
0263     m_body->SetTransform(b2Vec2(position.x(), position.y()), 0);
0264 }
0265 
0266 void CanvasItem::endSimulation()
0267 {
0268     //read position
0269     b2Vec2 v = m_body->GetPosition();
0270     QPointF position = QPointF(v.x, v.y) / Kolf::Box2DScaleFactor;
0271     if (position != getPosition())
0272         //HACK: The above condition can be removed later, but for now we need to
0273         //prevent moveBy() from being called with (0, 0) arguments because such
0274         //have a non-standard behavior with some classes (e.g. Ball), i.e. these
0275         //arguments trigger some black magic
0276         setPosition(position);
0277 }
0278 
0279 Kolf::Overlay* CanvasItem::overlay(bool createIfNecessary)
0280 {
0281     //the overlay is created once it is requested
0282     if (!m_overlay && createIfNecessary)
0283     {
0284         m_overlay = createOverlay();
0285         if (m_overlay)
0286         {
0287             //should be above object representation
0288             m_overlay->setZValue(m_overlay->qitem()->zValue() + 100);
0289             //initialize the overlay's parameters
0290             m_overlay->update();
0291         }
0292     }
0293     return m_overlay;
0294 }
0295 
0296 void CanvasItem::propagateUpdate()
0297 {
0298     if (m_overlay)
0299         m_overlay->update();
0300 }
0301 
0302 //BEGIN EllipticalCanvasItem
0303 
0304 EllipticalCanvasItem::EllipticalCanvasItem(bool withEllipse, const QString& spriteKey, QGraphicsItem* parent, b2World* world)
0305     : Tagaro::SpriteObjectItem(Kolf::renderer(), spriteKey, parent)
0306     , CanvasItem(world)
0307     , m_ellipseItem(nullptr)
0308     , m_shape(nullptr)
0309 {
0310     if (withEllipse)
0311     {
0312         m_ellipseItem = new QGraphicsEllipseItem(this);
0313         m_ellipseItem->setFlag(QGraphicsItem::ItemStacksBehindParent);
0314         //won't appear unless pen/brush is configured
0315         m_ellipseItem->setPen(Qt::NoPen);
0316         m_ellipseItem->setBrush(Qt::NoBrush);
0317     }
0318     m_shape = new Kolf::EllipseShape(QRectF());
0319     addShape(m_shape);
0320 }
0321 
0322 bool EllipticalCanvasItem::contains(const QPointF& point) const
0323 {
0324     const QSizeF halfSize = size() / 2;
0325     const qreal xScaled = point.x() / halfSize.width();
0326     const qreal yScaled = point.y() / halfSize.height();
0327     return xScaled * xScaled + yScaled * yScaled < 1;
0328 }
0329 
0330 QPainterPath EllipticalCanvasItem::shape() const
0331 {
0332     QPainterPath path;
0333     path.addEllipse(rect());
0334     return path;
0335 }
0336 
0337 QRectF EllipticalCanvasItem::rect() const
0338 {
0339     return Tagaro::SpriteObjectItem::boundingRect();
0340 }
0341 
0342 void EllipticalCanvasItem::setSize(const QSizeF& size)
0343 {
0344     setOffset(QPointF(-0.5 * size.width(), -0.5 * size.height()));
0345     Tagaro::SpriteObjectItem::setSize(size);
0346     if (m_ellipseItem)
0347         m_ellipseItem->setRect(this->rect());
0348     m_shape->setRect(this->rect());
0349 }
0350 
0351 void EllipticalCanvasItem::moveBy(double dx, double dy)
0352 {
0353     Tagaro::SpriteObjectItem::moveBy(dx, dy);
0354     CanvasItem::moveBy(dx, dy);
0355 }
0356 
0357 void EllipticalCanvasItem::saveSize(KConfigGroup* group)
0358 {
0359     const QSizeF size = this->size();
0360     group->writeEntry("width", size.width());
0361     group->writeEntry("height", size.height());
0362 }
0363 
0364 void EllipticalCanvasItem::loadSize(KConfigGroup* group)
0365 {
0366     QSizeF size = this->size();
0367     size.rwidth() = group->readEntry("width", size.width());
0368     size.rheight() = group->readEntry("height", size.height());
0369     setSize(size);
0370 }
0371 
0372 //END EllipticalCanvasItem
0373 //BEGIN ArrowItem
0374 
0375 ArrowItem::ArrowItem(QGraphicsItem* parent)
0376     : QGraphicsPathItem(parent)
0377     , m_angle(0), m_length(20)
0378     , m_reversed(false)
0379 {
0380     updatePath();
0381     setPen(QPen(Qt::black));
0382     setBrush(Qt::NoBrush);
0383 }
0384 
0385 qreal ArrowItem::angle() const
0386 {
0387     return m_angle;
0388 }
0389 
0390 void ArrowItem::setAngle(qreal angle)
0391 {
0392     if (m_angle != angle)
0393     {
0394         m_angle = angle;
0395         updatePath();
0396     }
0397 }
0398 
0399 qreal ArrowItem::length() const
0400 {
0401     return m_length;
0402 }
0403 
0404 void ArrowItem::setLength(qreal length)
0405 {
0406     if (m_length != length)
0407     {
0408         m_length = qMax<qreal>(length, 0.0);
0409         updatePath();
0410     }
0411 }
0412 
0413 bool ArrowItem::isReversed() const
0414 {
0415     return m_reversed;
0416 }
0417 
0418 void ArrowItem::setReversed(bool reversed)
0419 {
0420     if (m_reversed != reversed)
0421     {
0422         m_reversed = reversed;
0423         updatePath();
0424     }
0425 }
0426 
0427 Vector ArrowItem::vector() const
0428 {
0429     return Vector::fromMagnitudeDirection(m_length, m_angle);
0430 }
0431 
0432 void ArrowItem::updatePath()
0433 {
0434     if (m_length == 0)
0435     {
0436         setPath(QPainterPath());
0437         return;
0438     }
0439     //the following three points define the arrow tip
0440     const QPointF extent = Vector::fromMagnitudeDirection(m_length, m_angle);
0441     const QPointF startPoint = m_reversed ? extent : QPointF();
0442     const QPointF endPoint = m_reversed ? QPointF() : extent;
0443     const QPointF point1 = endPoint - Vector::fromMagnitudeDirection(m_length / 2, m_angle + M_PI / 12);
0444     const QPointF point2 = endPoint - Vector::fromMagnitudeDirection(m_length / 2, m_angle - M_PI / 12);
0445     QPainterPath path;
0446     path.addPolygon(QPolygonF() << startPoint << endPoint);
0447     path.addPolygon(QPolygonF() << point1 << endPoint << point2);
0448     setPath(path);
0449 }
0450 
0451 //END ArrowItem