summaryrefslogtreecommitdiffstats
path: root/kolf/ball.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kolf/ball.cpp')
-rw-r--r--kolf/ball.cpp466
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());
+}
+