#include #include #include #include #include #include #include // Constants defined in this program #define PI 3.14159 #define DECELERATION 0.10 // Rate of deceleration #define RADIUS 40 // Radius of the ball #define FPS 100 // frames per second refresh rate // Window attributes int WINDOW_WIDTH = 600; int WINDOW_HEIGHT = 600; // Ball attributes int x = 300, y = 300; // location of the ball on the window char grabbed = False; // Indicates if ball is being currently held float direction; // Direction of ball movement (radians) float speed = 10; // Current ball speed // Clean up and close the window void quit(Display *display, Window win, GC gc) { XFreeGC(display, gc); XUnmapWindow(display, win); XDestroyWindow(display, win); XCloseDisplay(display); exit(0); } // Redraw everything void redraw(Display *display, Window win, GC gc, int x, int y) { XSetForeground(display, gc, 0xFFFFFF); // white background XFillRectangle(display, win, gc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); XSetForeground(display, gc, 0x000000); // black ball XFillArc(display, win, gc, x-RADIUS, y-RADIUS, 2*RADIUS, 2*RADIUS, 0, 360*64); XFlush(display); } // Get the current time in microseconds long getTimeInMicroseconds(){ struct timeval currentTime; gettimeofday(¤tTime, NULL); return currentTime.tv_sec * (int)1e6 + currentTime.tv_usec; } int main() { Display *display; Window win; GC gc; XEvent event; int button; Window window_returned; int sx, sy, width, height, borderWidth, depth; float d; unsigned int mask_return; XConfigureEvent cEvent; int mouseX, mouseY; int prevX[5], prevY[5], prevCount=0; // 5 previous mouse locations // Opens connection to X server display = XOpenDisplay(NULL); // Create a simple window, set the title and get the graphics context then // make is visible and get ready to draw win = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0x000000, 0xFFFFFF); // Indicate which events we want to handle XSelectInput(display, win, ButtonPressMask | // When Mouse Button is Pressed ButtonReleaseMask | // When Mouse Button is Released PointerMotionMask | // When mouse is moved within window ExposureMask | // When the window is exposed StructureNotifyMask // When there is a change in window structure ); XStoreName(display, win, "Throwable Ball"); gc = XCreateGC(display, win, 0, NULL); XMapWindow(display, win); XFlush(display); // Indicate that we'd like to be able to gracefully handle window closing Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, win, &WM_DELETE_WINDOW, 1); // Set the direction to be random srand(time(NULL)); direction = (rand()/(double)(RAND_MAX))*2*PI; // Go into infinite loop, updating the animation at FPS rate unsigned long lastRepaint = getTimeInMicroseconds(); // time in microseconds while(1) { // Keep updatingthe mouse location. We remember the last 5 locations so that we // can get a sense of what direction the ball has been thrown as well as how fast // it was thrown. We will be comparing the current mouse reading with one 5 moves ago. prevX[prevCount] = mouseX; prevY[prevCount] = mouseY; prevCount = (prevCount + 1) % 5; XQueryPointer(display, win, &window_returned,&window_returned, &sx, &sy, &mouseX, &mouseY, &mask_return); // Handle any pending events, and then we'll deal with redrawing if (XPending(display) > 0) { XNextEvent(display, &event); switch(event.type) { case Expose: redraw(display, win, gc, x, y); break; case ButtonPress: // Check if the mouse was clicked within the ball's radius d = sqrt((mouseX - x)*(mouseX - x) + (mouseY - y)*(mouseY - y)); if (d < RADIUS) grabbed = True; break; case ButtonRelease: if (grabbed == True) { // Compare the difference between current mouse location and the one 5 // mouse motions ago. Use this to compute the new direction and speed. int px = prevX[(prevCount+1)%5]; int py = prevY[(prevCount+1)%5]; direction = atan2(mouseY - py, mouseX - px); speed = (int)(sqrt((mouseX - px)*(mouseX - px) + (mouseY - py)*(mouseY - py)))/2; if (speed > 50) // Limit to something reasonable speed = 50; } grabbed = False; // Let go of the ball if we were holding it break; case MotionNotify: if (grabbed == True) // Refresh the screen if we are carrying the ball around redraw(display, win, gc, x, y); break; case ConfigureNotify: cEvent = event.xconfigure; // Need to check for window resizing, since this type of event can be // generated for other reasons too if ((cEvent.width != WINDOW_WIDTH) || (cEvent.height != WINDOW_HEIGHT)) { WINDOW_WIDTH = cEvent.width; WINDOW_HEIGHT = cEvent.height; redraw(display, win, gc, x, y); } break; case ClientMessage: quit(display, win, gc); } } // Get the time in microseconds and store it unsigned long end = getTimeInMicroseconds(); // If it has been long enough, animate and redraw everything if (end - lastRepaint > 1000000/FPS) { // Draw the ball redraw(display, win, gc, x, y); // Move the ball forward if (grabbed == False) { x = x + (int)(speed*cos(direction)); y = y + (int)(speed*sin(direction)); } else { x = mouseX; y = mouseY; } // Slow the ball down speed = speed - DECELERATION; if (speed < 0) speed = 0; // Check if the ball collides with borders and adjust accordingly if (x >= (WINDOW_WIDTH-RADIUS)) { direction = PI - direction; x = WINDOW_WIDTH-RADIUS; } else if (x <= RADIUS) { direction = PI - direction; x = RADIUS; } if (y >= (WINDOW_HEIGHT-RADIUS)) { y = WINDOW_HEIGHT-RADIUS; direction *= -1; } else if (y <= RADIUS) { y = RADIUS; direction *= -1; } lastRepaint = getTimeInMicroseconds(); // Remember when the last repaint happened } // IMPORTANT: sleep for a bit to let other processes work if (XPending(display) == 0) { usleep (1000000 / FPS - (end - lastRepaint)); } } }