diff options
Diffstat (limited to 'kue/interface.cpp')
-rw-r--r-- | kue/interface.cpp | 319 |
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" + |