summaryrefslogtreecommitdiffstats
path: root/kue/interface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kue/interface.cpp')
-rw-r--r--kue/interface.cpp319
1 files changed, 319 insertions, 0 deletions
diff --git a/kue/interface.cpp b/kue/interface.cpp
new file mode 100644
index 00000000..5e99a8ff
--- /dev/null
+++ b/kue/interface.cpp
@@ -0,0 +1,319 @@
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <GL/gl.h>
+#include <tdemainwindow.h>
+#include <tdeapplication.h>
+#include <kstatusbar.h>
+#include "physics.h"
+#include <kdebug.h>
+#include <tqptrvector.h>
+
+#include "interface.h"
+#include "graphics.h"
+#include "vector.h"
+#include "global.h"
+
+// Radius of the billiards
+const double RADIUS = 0.00286;
+
+// How long the user can hold down the mouse button to increase shot power, it levels off if this value is exceeded
+const int MAX_WINDUP_TIME = 2500;
+// What's the value in meters/second of that maximum shot
+const double MAX_SHOT_SPEED = 0.45;
+
+const int UPDATE_TIME = 15;
+
+GLUserInterface::GLUserInterface(TQWidget *parent) :
+ TQGLWidget(parent),
+ _cue_location(0.0, 0.0, 0.0),
+ _mouse_location(0.5, 0.5),
+ _cue_texture("cue-player1")
+{
+ // Set all our variables to known safe values
+ _placing_cue = false;
+ _shooting = false;
+ _forward_only = false;
+
+ setMouseTracking(true);
+ _table = new table;
+}
+
+
+GLUserInterface::~GLUserInterface()
+{
+ delete _table;
+}
+
+double GLUserInterface::windowToInternalX(int x)
+{
+ // Make sure the value isn't outside the window (yes, we
+ // used to get invalid values from GLUT sometimes)
+ if (x < 0)
+ return 0.0;
+
+ if (x > width())
+ return 1.0;
+
+ // Now divide the x value by the windows width, so the left edge
+ // has a value of 0.0, the middle has 0.5, and the right edge 1.0
+ return (x / (double)width());
+}
+
+double GLUserInterface::windowToInternalY(int y)
+{
+ if (y < 0)
+ return 0.0;
+
+ if (y > height())
+ return 1.0;
+
+ // Now divide the y value by the window's height, so the left edge
+ // has a value of 0.0, the middle has 0.5, and the right edge 1.0
+ return (y / (double)height());
+}
+
+void GLUserInterface::initializeGL()
+{
+ // Initialize our graphics subsystem
+ if (!graphics::init())
+ kdWarning() << "Unable to initialize graphics" << endl;
+}
+
+void GLUserInterface::resizeGL(int width, int height)
+{
+ graphics::resize(width, height);
+}
+
+void GLUserInterface::paintGL()
+{
+ // Tell the graphics code to enter drawing mode
+ graphics::startDraw();
+
+ // Draw the table
+ _table->draw(KueGlobal::physics()->fieldWidth(), KueGlobal::physics()->fieldHeight());
+
+
+ // Draw the basic physics scene
+ graphics::drawScene();
+
+ // Are we shooting?
+ if (_shooting) {
+ double angle_rad;
+ double angle_deg;
+
+ // Calculate the current view angle
+ angle_rad = positionToAngle(_mouse_location.positionX());
+ // Convert it to degrees for OpenGL's benefit
+ angle_deg = angle_rad / M_PI * 180;
+
+ // Calculate the 'focus' (where the cue is pointing)
+ double focusx = _cue_location.positionX() + (cos(angle_rad) / 150.0 * ((shotPower() * 2.0) + 0.7));
+ double focusy = _cue_location.positionY() + (sin(angle_rad) / 150.0 * ((shotPower() * 2.0) + 0.7));
+
+ // Draw
+ cue::draw(focusx, focusy, angle_deg, _cue_texture, _player_color);
+ }
+
+ // Now we're done with drawing
+ graphics::endDraw();
+}
+
+void GLUserInterface::mouseMoveEvent(TQMouseEvent *e)
+{
+ double x = windowToInternalX(e->x());
+ double y = windowToInternalY(e->y());
+
+ // Invert the mouse along the X-axis
+ x = 1.0 - x;
+
+ // Update the mouse location variable
+ _mouse_location = point(x, y);
+ // Update our 3D view
+ updateView();
+
+ if (_placing_cue)
+ {
+ // If we're placing a cue ball, the mouse location affects its position on the table
+ point cue_ball_point(_cue_line, positionToCuePlacement(x));
+ emit(previewBilliardPlace(cue_ball_point));
+ }
+}
+
+void GLUserInterface::mousePressEvent(TQMouseEvent *e)
+{
+ mouseClicked(windowToInternalX(e->x()), windowToInternalY(e->y()), e->button(), true);
+}
+
+void GLUserInterface::mouseReleaseEvent(TQMouseEvent *e)
+{
+ mouseClicked(windowToInternalX(e->x()), windowToInternalY(e->y()), e->button(), false);
+}
+
+void GLUserInterface::mouseClicked(double x, double y, int button, int state) {
+ // Invert the mouse along the X-axis
+ x = 1.0 - x;
+
+ // Regardless of the button press event, we'll take a free mouse position update
+ _mouse_location = point(x, y);
+ updateView();
+
+ // But no non-left buttons past this point;
+ if (button != TQt::LeftButton)
+ return;
+
+ // Are we placing the cue ball?
+ // The "mouse down only" check is so we don't catch a mouse button
+ // coming up that was pressed down before we started placing
+ // the cue ball. It can be very confusing otherwise, and makes
+ // the game seem "glitchy"
+ if (_placing_cue && (state))
+ {
+ // Calculate the cues new position
+ point cue_ball_point(_cue_line, positionToCuePlacement(x));
+ // The the rules engine what we've decided
+ emit(billiardPlaced(cue_ball_point));
+
+ // We're done placing the cue
+ _placing_cue = false;
+ // We calculate the view differently depending on if we're
+ // placing the cue or taking a shot, so update the view
+ // with both the new cue position and with the "taking a shot"
+ // view mode.
+ updateView();
+
+ return;
+ }
+
+ if (_shooting)
+ {
+ if (state)
+ {
+ if (!_shot_started)
+ {
+ _shot_started = true;
+ _shot_time.start();
+ startTimer(UPDATE_TIME);
+ }
+ }
+ else if (_shot_started)
+ {
+ // Calculate the angle
+ double angle = positionToAngle(_mouse_location.positionX()) + M_PI;
+
+ // Take the shot
+ vector velocity(shotPower() * MAX_SHOT_SPEED, angle);
+ emit(shotTaken(velocity));
+
+ // We're no longer shooting
+ _shooting = false;
+ _shot_started = false;
+ killTimers();
+ }
+ }
+}
+
+void GLUserInterface::placeBilliard(double cue_line)
+{
+ // We've been asked to place the cue ball
+
+ // Enter 'cue placing' mode
+ _placing_cue = true;
+ _cue_line = cue_line;
+
+ // Show it in the position that is associated with our current mouse position
+ point cue_ball_point(_cue_line, positionToCuePlacement(_mouse_location.positionX()));
+ emit(previewBilliardPlace(cue_ball_point));
+
+ // Set up our stupid placing-cue-specific view
+ updateView();
+}
+
+void GLUserInterface::startShot(circle cue_location, TQColor player_color, bool forward_only) {
+ // Enter 'shooting' mode
+ _shot_started = false;
+ _shooting = true;
+
+ // Copy over our parameters
+ _forward_only = forward_only;
+ _cue_location = cue_location;
+ _player_color = player_color;
+
+ // Set up our new view
+ updateView();
+}
+
+void GLUserInterface::updateView() {
+ if (_placing_cue)
+ {
+ // Our eye is slightly behind the cue line
+ double eyex = _cue_line - (1 / 200.0);
+ // And right in the middle of the table horizontally
+ double eyey = KueGlobal::physics()->fieldHeight() / 2.0;
+
+ // Look at the cue line from our eye position
+ graphics::lookAt(eyex, eyey, (_cue_location.radius() * 4.0 * _mouse_location.positionY()) + _cue_location.radius(), _cue_line, KueGlobal::physics()->fieldHeight() / 2.0, RADIUS);
+ }
+ else
+ {
+ // Figure out our view angle
+ double angle = positionToAngle(_mouse_location.positionX());
+ // Use that to calculate the position of our eye
+ double eyex = _cue_location.positionX() + (cos(angle) / 200.0);
+ double eyey = _cue_location.positionY() + (sin(angle) / 200.0);
+
+ // Look at the cue ball
+ graphics::lookAt(eyex, eyey, (RADIUS * 4.0 * _mouse_location.positionY()) + RADIUS, _cue_location.positionX(), _cue_location.positionY(), RADIUS);
+ }
+
+ // We most certainly need to redraw, unless the physics engine is runnung
+ if (!KueGlobal::physics()->running())
+ KueGlobal::glWidget()->updateGL();
+}
+
+double GLUserInterface::shotPower()
+{
+ if (!_shot_started)
+ return 0.0;
+
+ int difference = _shot_time.elapsed();
+
+ if (difference > MAX_WINDUP_TIME)
+ return 1.0;
+
+ return (double(difference) / double(MAX_WINDUP_TIME));
+}
+
+double GLUserInterface::positionToAngle(double position)
+{
+ // Convert the mouse x-position to a view angle, depending if we're only allow to shoot forward or not
+ if (_forward_only)
+ return (position * M_PI) + (M_PI / 2.0);
+ else
+ return (((position - 0.5) * 1.1) + 0.5) * M_PI * 2.0;
+}
+
+double GLUserInterface::positionToCuePlacement(double position)
+{
+ // Convert the mouse x-position to a cue x-location
+
+ // Direct linear mapping to the table
+ double y_pos = position * KueGlobal::physics()->fieldHeight();
+
+ // Except we must be careful not to go off the table
+ if (y_pos < RADIUS)
+ y_pos = RADIUS;
+
+ if ((y_pos + RADIUS) > KueGlobal::physics()->fieldHeight())
+ y_pos = KueGlobal::physics()->fieldHeight() - RADIUS;
+
+ return y_pos;
+}
+
+void GLUserInterface::timerEvent( TQTimerEvent * )
+{
+ KueGlobal::glWidget()->updateGL();
+}
+
+#include "interface.moc"
+