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