You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kolf/ball.cpp

467 lines
10 KiB

#include <tqcanvas.h>
#include <tqcolor.h>
#include <tqpen.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(TQCanvas *canvas)
: TQCanvasEllipse(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;
TQFont font(kapp->font());
//font.setPixelSize(10);
label = new TQCanvasText("", 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)
{
TQCanvasEllipse::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;
}
TQCanvasEllipse::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();
TQCanvasEllipse::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()
{
TQCanvasEllipse::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;
TQCanvasItemList m_list = collisions(true);
// please don't ask why TQCanvas doesn't actually sort its list;
// it just doesn't.
m_list.sort();
this->m_list = m_list;
for (TQCanvasItemList::Iterator it = m_list.begin(); it != m_list.end(); ++it)
{
TQCanvasItem *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(TQPoint(x(), y()), TQPoint(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
TQPtrList<WallPoint> points;
for (TQCanvasItemList::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 TQPoint qp(iterpoint->x() + parentWall->x(), iterpoint->y() + parentWall->y());
const Point p(qp.x(), qp.y());
const TQPoint qother = TQPoint(parentWall->startPoint() == qp? parentWall->endPoint() : parentWall->startPoint()) + TQPoint(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 (TQCanvasItemList::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
TQCanvasItemList items;
if (game)
items = game->canvas()->allItems();
for (TQCanvasItemList::Iterator i = items.begin(); i != items.end(); ++i)
{
if ((*i)->rtti() != Rtti_Wall)
continue;
TQCanvasItem *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 TQString &name)
{
label->setText(name);
}
void Ball::setCanvas(TQCanvas *c)
{
TQCanvasEllipse::setCanvas(c);
label->setCanvas(c);
}
void Ball::setVisible(bool yes)
{
TQCanvasEllipse::setVisible(yes);
label->setVisible(yes && game && game->isInfoShowing());
}