1 /*
2     Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
3     Copyright 2010 Stefan Majewsky <majewsky@gmx.net>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 #include "ball.h"
21 #include "game.h"
22 #include "overlay.h"
23 #include "shape.h"
24 
25 #include <QApplication>
26 
Ball(QGraphicsItem * parent,b2World * world)27 Ball::Ball(QGraphicsItem* parent, b2World* world)
28 	: EllipticalCanvasItem(true, QStringLiteral("ball"), parent, world)
29 {
30 	const int diameter = 8;
31 	setSize(QSizeF(diameter, diameter));
32 	setZBehavior(CanvasItem::IsRaisedByStrut, 10);
33 
34 	setData(0, Rtti_NoCollision);
35 	m_doDetect = true;
36 	setBeginningOfHole(false);
37 	m_collisionId = 0;
38 	m_addStroke = false;
39 	m_placeOnGround = false;
40 	m_forceStillGoing = false;
41 	frictionMultiplier = 1.0;
42 
43 	QFont font(QApplication::font());
44 	font.setPixelSize(12);
45 	label = new QGraphicsSimpleTextItem(QString(), this);
46 	label->setFont(font);
47 	label->setBrush(Qt::white);
48 	label->setPos(5, 5);
49 	label->setVisible(false);
50 
51 	// this sets z
52 	setState(Stopped);
53 }
54 
setState(BallState newState)55 void Ball::setState(BallState newState)
56 {
57 	state = newState;
58 	if (state == Holed || !EllipticalCanvasItem::isVisible())
59 		setSimulationType(CanvasItem::NoSimulation);
60 	else
61 		setSimulationType(CanvasItem::DynamicSimulation);
62 
63 	if (state != Stopped)
64 		setBeginningOfHole(false);
65 }
66 
friction()67 void Ball::friction()
68 {
69 	if (state == Stopped || state == Holed || !isVisible())
70 	{
71 		setVelocity(QPointF());
72 		return;
73 	}
74 	const double subtractAmount = .027 * frictionMultiplier;
75 	Vector velocity = this->velocity();
76 	if (velocity.magnitude() <= subtractAmount)
77 	{
78 		state = Stopped;
79 		setVelocity(QPointF());
80 		game->timeout();
81 		return;
82 	}
83 	velocity.setMagnitude(velocity.magnitude() - subtractAmount);
84 	setVelocity(velocity);
85 
86 	frictionMultiplier = 1.0;
87 }
88 
moveBy(double dx,double dy)89 void Ball::moveBy(double dx, double dy)
90 {
91 	EllipticalCanvasItem::moveBy(dx, dy);
92 
93 	if (game && !game->isPaused())
94 		collisionDetect();
95 
96 	if ((dx || dy) && game && game->curBall() == this)
97 		game->ballMoved();
98 }
99 
endSimulation()100 void Ball::endSimulation()
101 {
102 	CanvasItem::endSimulation();
103 	if (state == Stopped)
104 		if (!qFuzzyIsNull(Vector(velocity()).magnitude()))
105 			//ball was resting, but received some momentum from collision with other ball
106 			setState(Rolling);
107 }
108 
collisionDetect()109 void Ball::collisionDetect()
110 {
111 	if (!isVisible() || state == Holed || !m_doDetect)
112 		return;
113 
114 	// do friction every other time
115 	m_collisionId = (m_collisionId + 1) % 2;
116 	if (m_collisionId == 1 && !velocity().isNull())
117 		friction();
118 
119 	const double initialVelocity = Vector(velocity()).magnitude();
120 	const double minSpeed = .06;
121 
122 	const QList<QGraphicsItem *> items = collidingItems();
123 
124 	bool doTerrainCollisions = true;
125 	for (QGraphicsItem* item : items) {
126 		if (item->data(0) == Rtti_NoCollision || item->data(0) == Rtti_Putter)
127 		{
128 			if (item->data(0) == Rtti_NoCollision)
129 				game->playSound(Sound::Wall);
130 			continue;
131 		}
132 
133 		if (!isVisible() || state == Holed)
134 			return;
135 
136 		CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
137 		if (citem)
138 		{
139 			if (!citem->terrainCollisions())
140 			{
141 				//do collision
142 				const bool allowTerrainCollisions = citem->collision(this);
143 				doTerrainCollisions = doTerrainCollisions && allowTerrainCollisions;
144 			}
145 			break;
146 		}
147 	}
148 
149 	if (doTerrainCollisions)
150 	{
151 		for (QGraphicsItem* item : items) {
152 			CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
153 			if (citem && citem->terrainCollisions())
154 			{
155 				// slopes return false
156 				// as only one should be processed
157 				// however that might not always be true
158 				if (!citem->collision(this))
159 				{
160 					break;
161 				}
162 			}
163 		}
164 	}
165 
166 	const double currentVelocity = Vector(velocity()).magnitude();
167 	const double velocityChange = qAbs(initialVelocity - currentVelocity);
168 
169 	if(currentVelocity < minSpeed && velocityChange < minSpeed && currentVelocity)
170 	{
171 		//cutoff low velocities
172 		setVelocity(Vector());
173 		setState(Stopped);
174 	}
175 }
176 
currentState()177 BallState Ball::currentState()
178 {
179 	return state;
180 }
181 
infoItems() const182 QList<QGraphicsItem*> Ball::infoItems() const
183 {
184 	return QList<QGraphicsItem*>() << label;
185 }
186 
setName(const QString & name)187 void Ball::setName(const QString &name)
188 {
189 	label->setText(name);
190 }
191 
setVisible(bool yes)192 void Ball::setVisible(bool yes)
193 {
194 	EllipticalCanvasItem::setVisible(yes);
195 	setState(state);
196 }
197 
createOverlay()198 Kolf::Overlay* Ball::createOverlay()
199 {
200 	return new Kolf::Overlay(this, this);
201 }
202