#include #include #define GLM_ENABLE_EXPERIMENTAL 1 #include #include #include "gl3.h" #include "sphere.h" #define EPSILON 1e-6 #define PATH_VERTICES 10000 #define ARROW_VERTICES 6 // functions static void start_timer(); static void wait_update_timer(); static bool checkEvents(); static int findCollision(double x1, double x2, double v1, double v2, float *line_strip, int nVertices, double *time, double *n1, double *n2); static int calculatePath(GLfloat *outline, int nOutlineVertices, GLfloat *path, GLfloat *times, int nPathVertices, double x1, double x2, double v1, double v2); static bool processEvent(XEvent *ev); static void select_outline(int i); static void init(); static void cleanup(); static void draw(); // globals struct timeval start_time; unsigned long frames; double elapsed, frametime; GLInfo gl; GLuint program, program3d; unsigned int width = 100, height = 100; float speed = 1, flowtime = 0; int stopped = 0; int flowtime_index = 0; FILE *dumpfile; GLuint outlineVA, outlineVB; GLuint pathVA, pathVB[2]; GLuint arrowVA, arrowVB; GLuint ballVA; int outline_index = 0; int outline_count = 12; int outline_lengths[] = {5, 5, 5, 5, 4, 4, 4, 4, 7, 7, 7, 13}; GLfloat outlines[12][30] = { {-2, -1, 2, -1, 2, 1, -2, 1, -2, -1}, {-2, -1, 2, -1, 2, 1, -2, 1, -2, -1}, {-2, -1, 2, -1, 2, 1, -2, 1, -2, -1}, {-2, -1, 2, -1, 2, 1, -2, 1, -2, -1}, {-2, -1, 2, -1, 2, 4/sqrt(3) - 1, -2, -1}, {-2, -1, 2, -1, 2, 4/sqrt(3) - 1, -2, -1}, {-2, -1, 2, -1, 2, 4/sqrt(3) - 1, -2, -1}, {-2, -1, 2, -1, 2, 4*tan(M_PI/8) - 1, -2, -1}, {1, 1, 1, 0, 2.7614348, 0, 2.7614348, -1, -1, -1, -1, 1, 1, 1}, {1, 1, 1, 0, 2.7614348, 0, 2.7614348, -1, -1, -1, -1, 1, 1, 1}, {-1.2, 1, 1.2, 1, 0.2, 0, 1.2, -1, -1.2, -1, -0.2, 0, -1.2, 1}, {-2, -1, -1, -1, -1, -0.05, 1, -0.05, 1, -1, 2, -1, 2, 1, 1, 1, 1, 0.05, -1, 0.05, -1, 1, -2, 1, -2, -1}, }; GLfloat starting_vectors[] = { -1, 0, 2, 0, -1, 0, 1.6, 2.4, -1, 0, 7.04, 8, -1, 0, 10, 7.34819206, 0, -0.5, 1, -sqrt(3), 0, -0.5, 1, 2.5*sqrt(3), 0, -0.5, 5, 5, 0.4, -0.7, -0.5, 1 + sqrt(2), -0.2, -0.5, 0.5, -0.5, 0.5, 0.7, 1, -1, -0.2, -0.1, 2, 1, -1.5, -0.1, 1, 2*sqrt(2), }; char filename[1000]; int outline_length; GLfloat outline[1000]; GLfloat starting_vector[4]; int nPathVertices; GLfloat path[PATH_VERTICES*2]; GLfloat times[PATH_VERTICES]; GLfloat arrow[ARROW_VERTICES*2] = {0, 0, 1, 0, 0.9, 0.1, 1, 0, 0.9, -0.1, 1, 0}; static void start_timer() { gettimeofday(&start_time, NULL); frames = 0; } static void wait_update_timer() { struct timeval current_time; double new_elapsed; gettimeofday(¤t_time, NULL); new_elapsed = current_time.tv_sec - start_time.tv_sec; new_elapsed += (current_time.tv_usec - start_time.tv_usec) * 1e-6; frametime = new_elapsed - elapsed; if(frametime < 0.01) { // frames < 10ms are considered too short; sleep a while and then measure again usleep(10000 - frametime*1e6); gettimeofday(¤t_time, NULL); new_elapsed = current_time.tv_sec - start_time.tv_sec; new_elapsed += (current_time.tv_usec - start_time.tv_usec) * 1e-6; frametime = new_elapsed - elapsed; } elapsed = new_elapsed; frames++; } static bool checkEvents() // get any events from the queue and the server, process them if neccessary, quit if wanted { XEvent ev; while(XCheckIfEvent(gl.display, &ev, alwaysTruePredicate, NULL)) { // we essentially want XCheckWindowEvent, but we want to avoid that events for other windows fill up the queue if(ev.xany.window != gl.win) continue; if(ev.type == KeyRelease) { // deal with autorepeat XEvent nev; if(XCheckIfEvent(gl.display, &nev, alwaysTruePredicate, NULL)) { // is there another event? if (nev.type == KeyPress && nev.xkey.time == ev.xkey.time && nev.xkey.keycode == ev.xkey.keycode) // which is equal, but KeyPress? Then it's just auto-repeat continue; // so we ignore both XPutBackEvent(gl.display, &nev); // otherwise put the event back, we will consider it in the next round } } if(processEvent(&ev)) return true; // quit event queue and application } return false; } /********************************** INTERESTING PART STARTS HERE ***************************************************/ static int findCollision(double x1, double x2, double v1, double v2, float *line_strip, int nVertices, double *time, double *n1, double *n2) { double a1, a2, w1, w2, t, s; int near_wall_count = 0; int index = -1; *time = INFINITY; for(int i = 0; i < nVertices - 1; i++) { a1 = line_strip[2*i]; a2 = line_strip[2*i+1]; w1 = line_strip[2*i+2] - a1; w2 = line_strip[2*i+3] - a2; // x + tv = a + sw s = ((x1-a1)*v2 - (x2-a2)*v1) / (w1*v2 - w2*v1); t = ((x1-a1)*w2 - (x2-a2)*w1) / (w1*v2 - w2*v1); if(s <= 1 && s >= 0) { if(t < EPSILON && t > -EPSILON) near_wall_count ++; else if(t > 0 && t < *time) { *time = t; if((x1-a1)*w2 - (x2-a2)*w1 >= 0) { *n1 = w2; *n2 = -w1; } else { *n1 = -w2; *n2 = w1; } index = i; } } } if(index == -1 || near_wall_count > 1) { return 0; // failed } else return 1; } static int calculatePath(GLfloat *outline, int nOutlineVertices, GLfloat *path, GLfloat *times, int nPathVertices, double x1, double x2, double v1, double v2) { double t, n1, n2, v1new, v2new, ttotal; memset(path, 0, nPathVertices*2*sizeof(GLfloat)); path[0] = x1; path[1] = x2; times[0] = ttotal = 0; for(int i = 1; i < nPathVertices; i++) { if(findCollision(x1, x2, v1, v2, outline, nOutlineVertices, &t, &n1, &n2) == 0) { // we hit a singularity, so just stay here and set the next time to infinity path[2*i] = x1; path[2*i+1] = x2; times[i] = INFINITY; return i + 1; } x1 += t * v1; x2 += t * v2; v1new = v1 - 2 * (v1*n1 + v2*n2) * n1 / (n1*n1 + n2*n2); // reflect v along the normal to n v2new = v2 - 2 * (v1*n1 + v2*n2) * n2 / (n1*n1 + n2*n2); v1 = v1new; v2 = v2new; ttotal += t; path[2*i] = x1; path[2*i+1] = x2; times[i] = ttotal; } return nPathVertices; } static bool processEvent(XEvent *ev) { int state; switch(ev->type) { case ConfigureNotify: // printf("ConfigureNotify Event, new dimensions: %d %d %d %d\n", ev->xconfigure.x, ev->xconfigure.y, ev->xconfigure.width, ev->xconfigure.height); width = ev->xconfigure.width; height = ev->xconfigure.height; glViewport(0, 0, width, height); break; case KeyPress: state = ev->xkey.state & (ShiftMask | LockMask | ControlMask); // printf("KeyPress Event, keycode: %d, state: %d, masked state: %d\n", ev->xkey.keycode, ev->xkey.state, state); if(state == 0 && ev->xkey.keycode == 26) { printf("Quit\n"); return true; } else if(state == 0 && ev->xkey.keycode == 27) { select_outline(0); } else if (state == 0 && ev->xkey.keycode == 114) { speed *= 2; } else if (state == 0 && ev->xkey.keycode == 113) { speed *= 0.5; } else if (state == 0 && ev->xkey.keycode == 65) { stopped = !stopped; } else if (state == 0 && ev->xkey.keycode >= 10 && ev->xkey.keycode <= 19) { if(ev->xkey.keycode - 10 < outline_count) select_outline(ev->xkey.keycode - 10); } break; case KeyRelease: state = ev->xkey.state & (ShiftMask | LockMask | ControlMask); // printf("KeyRelease Event, keycode: %d, state: %d, masked state: %d\n", ev->xkey.keycode, ev->xkey.state, state); break; case ClientMessage: if((Atom)ev->xclient.message_type == gl.wm_protocols && (Atom)ev->xclient.data.l[0] == gl.wm_delete_window) { printf("Window closed\n"); return true; } break; default: // printf("Event of type %d\n", ev->type); break; } return false; } static int load_outline(const char *filename) { int c; FILE *f = fopen(filename, "r"); if(!f) return 0; double x, y, vx, vy, x0, y0; printf("Loading file %s:\n", filename); if(fscanf(f, "%lf %lf %lf %lf", &x, &y, &vx, &vy) != 4) return 0; printf("Line: %lf %lf %lf %lf\n", x, y, vx, vy); starting_vector[0] = x; starting_vector[1] = y; starting_vector[2] = vx; starting_vector[3] = vy; do { c = fgetc(f); } while(c != EOF && c != '\n'); fscanf(f, "%lf %lf", &x0, &y0); printf("Line: %lf %lf\n", x0, y0); outline[0] = x0; outline[1] = y0; outline_length = 1; while(!feof(f)) { if(fscanf(f, "%lf %lf", &x, &y) != 2) continue; do { c = fgetc(f); } while(c != EOF && c != '\n'); printf("Line: %lf %lf\n", x, y); outline[outline_length*2] = x; outline[outline_length*2+1] = y; outline_length++; } outline[outline_length*2] = x0; outline[outline_length*2+1] = y0; outline_length++; fclose(f); return 1; } static void select_outline(int i) { load_outline(filename); nPathVertices = calculatePath(outline, outline_length, path, times, PATH_VERTICES, starting_vector[0], starting_vector[1], starting_vector[2], starting_vector[3]); //nPathVertices = calculatePath(outlines[i], outline_lengths[i], path, times, PATH_VERTICES, starting_vectors[4*i], starting_vectors[4*i+1], starting_vectors[4*i+2], starting_vectors[4*i+3]); printf("Calculated path until time: %.2f\n", times[nPathVertices - 1]); glBindBuffer(GL_ARRAY_BUFFER, outlineVB); // glBufferData(GL_ARRAY_BUFFER, outline_lengths[i]*2*sizeof(GLfloat), outlines[i], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, outline_length*2*sizeof(GLfloat), outline, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, pathVB[0]); glBufferData(GL_ARRAY_BUFFER, nPathVertices*2*sizeof(GLfloat), path, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, pathVB[1]); glBufferData(GL_ARRAY_BUFFER, nPathVertices*sizeof(GLfloat), times, GL_STATIC_DRAW); // outline_index = i; flowtime = 0; flowtime_index = 0; stopped = 1; speed = 1; } static void init() { glGenVertexArrays(1, &outlineVA); glBindVertexArray(outlineVA); glGenBuffers(1, &outlineVB); glBindBuffer(GL_ARRAY_BUFFER, outlineVB); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glGenVertexArrays(1, &arrowVA); glBindVertexArray(arrowVA); glGenBuffers(1, &arrowVB); glBindBuffer(GL_ARRAY_BUFFER, arrowVB); glBufferData(GL_ARRAY_BUFFER, ARROW_VERTICES*2*sizeof(GLfloat), arrow, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glGenVertexArrays(1, &pathVA); glBindVertexArray(pathVA); glGenBuffers(2, pathVB); glBindBuffer(GL_ARRAY_BUFFER, pathVB[0]); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, pathVB[1]); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0); select_outline(outline_index); // ball generateSphere(20, 20, &ballVA); initShaders("vertex.glsl", "fragment.glsl", &program); initShaders("vertex3d.glsl", "fragment3d.glsl", &program3d); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_CULL_FACE); glFrontFace(GL_CW); } static void cleanup() { // fclose(dumpfile); } static void draw() { glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(!stopped) flowtime += frametime*speed; glm::mat4 proj = glm::perspective((float)M_PI/4, (float)width / (float)height, 0.1f, 100.0f); glm::mat4 view = glm::lookAt(glm::vec3(0,0,7), glm::vec3(0,0,0), glm::vec3(0,1,0)); glUseProgram(program); // draw arrow glm::mat4 rotation = glm::mat4(1.0); // GLfloat x = starting_vectors[4*outline_index+2]; // GLfloat y = starting_vectors[4*outline_index+3]; GLfloat x = starting_vector[2]; GLfloat y = starting_vector[3]; GLfloat norm = sqrt(x*x + y*y); x *= 0.5/norm; y *= 0.5/norm; rotation[0][0] = x; rotation[0][1] = y; rotation[1][0] = -y; rotation[1][1] = x; // glm::mat4 model = glm::translate(glm::vec3(starting_vectors[4*outline_index], starting_vectors[4*outline_index+1], 0)) * rotation; glm::mat4 model = glm::translate(glm::vec3(starting_vector[0], starting_vector[1], 0)) * rotation; glm::mat4 mvp = proj * view * model; glUniformMatrix4fv(glGetUniformLocation(program, "mvp"), 1, GL_FALSE, &mvp[0][0]); glUniform1i(glGetUniformLocation(program, "type"), 2); glBindVertexArray(arrowVA); glDrawArrays(GL_LINES, 0, ARROW_VERTICES); // draw outline and path mvp = proj * view; glUniformMatrix4fv(glGetUniformLocation(program, "mvp"), 1, GL_FALSE, &mvp[0][0]); glUniform1i(glGetUniformLocation(program, "type"), 1); glBindVertexArray(outlineVA); // glDrawArrays(GL_LINE_STRIP, 0, outline_lengths[outline_index]); glDrawArrays(GL_LINE_STRIP, 0, outline_length); glUniform1i(glGetUniformLocation(program, "type"), 0); glUniform1f(glGetUniformLocation(program, "time"), flowtime); glBindVertexArray(pathVA); glDrawArrays(GL_LINE_STRIP, 0, nPathVertices); // calculate ball position from flowtime (and flowtime_index), times and path GLfloat pos[3]; while(flowtime_index < nPathVertices - 1 && times[flowtime_index+1] < flowtime) flowtime_index++; if(flowtime_index < nPathVertices - 1) { pos[0] = path[2*flowtime_index + 0] + (path[2*flowtime_index + 2] - path[2*flowtime_index + 0])*(flowtime - times[flowtime_index])/(times[flowtime_index+1] - times[flowtime_index]); pos[1] = path[2*flowtime_index + 1] + (path[2*flowtime_index + 3] - path[2*flowtime_index + 1])*(flowtime - times[flowtime_index])/(times[flowtime_index+1] - times[flowtime_index]); pos[2] = 0; } else { pos[0] = path[2*flowtime_index + 0]; pos[1] = path[2*flowtime_index + 1]; pos[2] = 0; } // draw ball glm::mat4 mv = view * glm::translate(glm::vec3(pos[0], pos[1], pos[2])) * glm::scale(glm::vec3(0.05f, 0.05f, 0.05f)); mvp = proj * mv; glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(mv))); glUseProgram(program3d); glUniformMatrix4fv(glGetUniformLocation(program3d, "mvp"), 1, GL_FALSE, &mvp[0][0]); glUniformMatrix3fv(glGetUniformLocation(program3d, "normal_transform"), 1, GL_FALSE, &normal_transform[0][0]); glBindVertexArray(ballVA); glDrawElements(GL_TRIANGLES, 6*20*20, GL_UNSIGNED_INT, 0); // GLuint pixels[width*height]; // glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); // fwrite(pixels, sizeof(GLuint), width*height, dumpfile); glXSwapBuffers(gl.display, gl.win); } int main(int argc, char * const *argv) { int screen = 0; int opt; while ((opt = getopt(argc, argv, "s:n:f:")) != -1) { switch (opt) { case 's': screen = atoi(optarg); break; case 'n': outline_index = atoi(optarg) - 1; break; case 'f': strncpy(filename, optarg, 1000); break; default: fprintf(stderr, "Usage: %s [-s screen] [-f file]\n", argv[0]); return 1; } } if(!initGL(screen, StructureNotifyMask | KeyPressMask | KeyReleaseMask, &gl)) return 1; init(); start_timer(); while(!checkEvents()) { draw(); wait_update_timer(); } cleanup(); destroyGL(&gl); return 0; }