diff options
Diffstat (limited to 'kolf/ball.cpp')
-rw-r--r-- | kolf/ball.cpp | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/kolf/ball.cpp b/kolf/ball.cpp new file mode 100644 index 00000000..8adaa8cb --- /dev/null +++ b/kolf/ball.cpp @@ -0,0 +1,466 @@ +#include <qcanvas.h> +#include <qcolor.h> +#include <qpen.h> + +#include <kapplication.h> +#include <kdebug.h> + +#include <math.h> +#include <stdlib.h> +#include <unistd.h> + +#include "rtti.h" +#include "vector.h" +#include "canvasitem.h" +#include "game.h" +#include "ball.h" + +Ball::Ball(QCanvas *canvas) + : QCanvasEllipse(canvas) +{ + m_doDetect = true; + m_collisionLock = false; + setBeginningOfHole(false); + setBlowUp(false); + setPen(black); + resetSize(); + collisionId = 0; + m_addStroke = false; + m_placeOnGround = false; + m_forceStillGoing = false; + frictionMultiplier = 1.0; + QFont font(kapp->font()); + //font.setPixelSize(10); + label = new QCanvasText("", font, canvas); + label->setColor(white); + label->setVisible(false); + + // this sets z + setState(Stopped); + label->setZ(z() - .1); +} + +void Ball::aboutToDie() +{ + delete label; +} + +void Ball::setState(BallState newState) +{ + state = newState; + if (state == Stopped) + setZ(1000); + else + setBeginningOfHole(false); +} + +void Ball::advance(int phase) +{ + // not used anymore + // can be used to make ball wobble + if (phase == 1 && m_blowUp) + { + if (blowUpCount >= 50) + { + // i should make this a config option + //setAddStroke(addStroke() + 1); + setBlowUp(false); + resetSize(); + return; + } + + const double diff = 8; + double randnum = kapp->random(); + const double width = 6 + randnum * (diff / RAND_MAX); + randnum = kapp->random(); + const double height = 6 + randnum * (diff / RAND_MAX); + setSize(width, height); + blowUpCount++; + } +} + +void Ball::friction() +{ + if (state == Stopped || state == Holed || !isVisible()) { setVelocity(0, 0); return; } + const double subtractAmount = .027 * frictionMultiplier; + if (m_vector.magnitude() <= subtractAmount) + { + state = Stopped; + setVelocity(0, 0); + game->timeout(); + return; + } + m_vector.setMagnitude(m_vector.magnitude() - subtractAmount); + setVector(m_vector); + + frictionMultiplier = 1.0; +} + +void Ball::setVelocity(double vx, double vy) +{ + QCanvasEllipse::setVelocity(vx, vy); + + if (vx == 0 && vy == 0) + { + m_vector.setDirection(0); + m_vector.setMagnitude(0); + return; + } + + double ballAngle = atan2(-vy, vx); + + m_vector.setDirection(ballAngle); + m_vector.setMagnitude(sqrt(pow(vx, 2) + pow(vy, 2))); +} + +void Ball::setVector(const Vector &newVector) +{ + m_vector = newVector; + + if (newVector.magnitude() == 0) + { + setVelocity(0, 0); + return; + } + + QCanvasEllipse::setVelocity(cos(newVector.direction()) * newVector.magnitude(), -sin(newVector.direction()) * newVector.magnitude()); +} + +void Ball::moveBy(double dx, double dy) +{ + double oldx; + double oldy; + oldx = x(); + oldy = y(); + QCanvasEllipse::moveBy(dx, dy); + + if (game && !game->isPaused()) + collisionDetect(oldx, oldy); + + if ((dx || dy) && game && game->curBall() == this) + game->ballMoved(); + + label->move(x() + width(), y() + height()); +} + +void Ball::doAdvance() +{ + QCanvasEllipse::advance(1); +} + +namespace Lines +{ + // provides a point made of doubles + + struct Line + { + Point p1, p2; + }; + + int ccw(const Point &p0, const Point &p1, const Point &p2) + { + double dx1, dx2, dy1, dy2; + dx1 = p1.x - p0.x; dy1 = p1.y - p0.y; + dx2 = p2.x - p0.x; dy2 = p2.y - p0.y; + if (dx1*dy2 > dy1*dx2) return +1; + if (dx1*dy2 < dy1*dx2) return -1; + if ((dx1*dx2 < 0) || (dy1*dy2 < 0)) return -1; + if ((dx1*dx1+dy1*dy1) < (dx2*dx2+dy2*dy2)) + return +1; + return 0; + } + + int intersects(const Line &l1, const Line &l2) + { + // Charles says, TODO: Account for vertical lines + // Jason says, in my testing vertical lines work + return ((ccw(l1.p1, l1.p2, l2.p1) + *ccw(l1.p1, l1.p2, l2.p2)) <= 0) + && ((ccw(l2.p1, l2.p2, l1.p1) + *ccw(l2.p1, l2.p2, l1.p2)) <= 0); + } + + + bool intersects( + double xa1, double ya1, double xb1, double yb1, + double xa2, double ya2, double xb2, double yb2 + ) + { + Line l1, l2; + l1.p1.x = xa1; + l1.p1.y = ya1; + l1.p2.x = xb1; + l1.p2.y = yb1; + + l2.p1.x = xa2; + l2.p1.y = ya2; + l2.p2.x = xb2; + l2.p2.y = yb2; + + return intersects(l1, l2); + } +} + +void Ball::collisionDetect(double oldx, double oldy) +{ + if (!isVisible() || state == Holed || !m_doDetect) + return; + + if (collisionId >= INT_MAX - 1) + collisionId = 0; + else + collisionId++; + + //kdDebug(12007) << "------" << endl; + //kdDebug(12007) << "Ball::collisionDetect id " << collisionId << endl; + + // every other time... + // do friction + if (collisionId % 2 && !(xVelocity() == 0 && yVelocity() == 0)) + friction(); + + const double minSpeed = .06; + + QCanvasItemList m_list = collisions(true); + + // please don't ask why QCanvas doesn't actually sort its list; + // it just doesn't. + m_list.sort(); + + this->m_list = m_list; + + for (QCanvasItemList::Iterator it = m_list.begin(); it != m_list.end(); ++it) + { + QCanvasItem *item = *it; + + if (item->rtti() == Rtti_NoCollision || item->rtti() == Rtti_Putter) + continue; + + if (item->rtti() == rtti() && !m_collisionLock) + { + // it's one of our own kind, a ball + Ball *oball = dynamic_cast<Ball *>(item); + if (!oball || oball->collisionLock()) + continue; + oball->setCollisionLock(true); + + if ((oball->x() - x() != 0 && oball->y() - y() != 0) && state == Rolling && oball->curState() != Holed) + { + m_collisionLock = true; + // move this ball to where it was barely touching + double ballAngle = m_vector.direction(); + while (collisions(true).contains(item) > 0) + move(x() - cos(ballAngle) / 2.0, y() + sin(ballAngle) / 2.0); + + // make a 2 pixel separation + move(x() - 2 * cos(ballAngle), y() + 2 * sin(ballAngle)); + + Vector bvector = oball->curVector(); + m_vector -= bvector; + + Vector unit1 = Vector(QPoint(x(), y()), QPoint(oball->x(), oball->y())); + unit1 = unit1.unit(); + + Vector unit2 = m_vector.unit(); + + double cos = unit1 * unit2; + + unit1 *= m_vector.magnitude() * cos; + m_vector -= unit1; + m_vector += bvector; + + bvector += unit1; + + oball->setVector(bvector); + setVector(m_vector); + + oball->setState(Rolling); + setState(Rolling); + + oball->doAdvance(); + } + + continue; + } + else if (item->rtti() == Rtti_WallPoint) + { + //kdDebug(12007) << "collided with WallPoint\n"; + // iterate through the rst + QPtrList<WallPoint> points; + for (QCanvasItemList::Iterator pit = it; pit != m_list.end(); ++pit) + { + if ((*pit)->rtti() == Rtti_WallPoint) + { + WallPoint *point = (WallPoint *)(*pit); + if (point) + points.prepend(point); + } + } + + // ok now we have a list of wall points we are on + + WallPoint *iterpoint = 0; + WallPoint *finalPoint = 0; + + // this wont be least when we're done hopefully + double leastAngleDifference = 9999; + + for (iterpoint = points.first(); iterpoint; iterpoint = points.next()) + { + //kdDebug(12007) << "-----\n"; + const Wall *parentWall = iterpoint->parentWall(); + const QPoint qp(iterpoint->x() + parentWall->x(), iterpoint->y() + parentWall->y()); + const Point p(qp.x(), qp.y()); + const QPoint qother = QPoint(parentWall->startPoint() == qp? parentWall->endPoint() : parentWall->startPoint()) + QPoint(parentWall->x(), parentWall->y()); + const Point other(qother.x(), qother.y()); + + // vector of wall + Vector v = Vector(p, other); + + // difference between our path and the wall path + double ourDir = m_vector.direction(); + + double wallDir = M_PI - v.direction(); + + //kdDebug(12007) << "ourDir: " << rad2deg(ourDir) << endl; + //kdDebug(12007) << "wallDir: " << rad2deg(wallDir) << endl; + + const double angleDifference = fabs(M_PI - fabs(ourDir - wallDir)); + //kdDebug(12007) << "computed angleDifference: " << rad2deg(angleDifference) << endl; + + // only if this one is the least of all + if (angleDifference < leastAngleDifference) + { + leastAngleDifference = angleDifference; + finalPoint = iterpoint; + //kdDebug(12007) << "it's the one\n"; + } + } + + // this'll never happen + if (!finalPoint) + continue; + + // collide with our chosen point + finalPoint->collision(this, collisionId); + + // don't worry about colliding with walls + // wall points are ok alone + goto end; + } + + if (!isVisible() || state == Holed) + return; + + CanvasItem *citem = dynamic_cast<CanvasItem *>(item); + if (citem) + { + if (!citem->terrainCollisions()) + { + // read: if (not do terrain collisions) + if (!citem->collision(this, collisionId)) + { + // if (skip smart wall test) + if (citem->vStrut() || item->rtti() == Rtti_Wall) + goto end; + else + goto wallCheck; + } + } + break; + } + } + + for (QCanvasItemList::Iterator it = m_list.begin(); it != m_list.end(); ++it) + { + CanvasItem *citem = dynamic_cast<CanvasItem *>(*it); + if (citem && citem->terrainCollisions()) + { + // slopes return false + // as only one should be processed + // however that might not always be true + + // read: if (not do terrain collisions) + if (!citem->collision(this, collisionId)) + { + break; + } + } + } + +// Charles's smart wall check: + + wallCheck: + + { // check if I went through a wall + QCanvasItemList items; + if (game) + items = game->canvas()->allItems(); + for (QCanvasItemList::Iterator i = items.begin(); i != items.end(); ++i) + { + if ((*i)->rtti() != Rtti_Wall) + continue; + + QCanvasItem *item = (*i); + Wall *wall = dynamic_cast<Wall*>(item); + if (!wall || !wall->isVisible()) + continue; + + if (Lines::intersects( + wall->startPoint().x() + wall->x(), wall->startPoint().y() + wall->y(), + wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y(), + + oldx, oldy, x(), y() + )) + { + //kdDebug(12007) << "smart wall collision\n"; + wall->collision(this, collisionId); + break; + } + + + } + } + + end: + + if (m_vector.magnitude() < minSpeed && m_vector.magnitude()) + { + setVelocity(0, 0); + setState(Stopped); + } +} + +BallState Ball::currentState() +{ + return state; +} + +void Ball::showInfo() +{ + label->setVisible(isVisible()); +} + +void Ball::hideInfo() +{ + label->setVisible(false); +} + +void Ball::setName(const QString &name) +{ + label->setText(name); +} + +void Ball::setCanvas(QCanvas *c) +{ + QCanvasEllipse::setCanvas(c); + label->setCanvas(c); +} + +void Ball::setVisible(bool yes) +{ + QCanvasEllipse::setVisible(yes); + + label->setVisible(yes && game && game->isInfoShowing()); +} + |