Files
billiards_gl/billiards.c
Florian Stecker 04a707b4ed initial version
2025-12-07 19:04:34 -05:00

499 lines
14 KiB
C

#include <sys/time.h>
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL 1
#include <glm/gtx/transform.hpp>
#include <glm/gtx/rotate_vector.hpp>
#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(&current_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(&current_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;
}