#include #include #include #include #include #include #include #include "physics.h" #include #include #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"