Paddle Hit Detection
We add collision detection for the paddle to be able to hit the ball
Published
Liked the article? Share it!
We have a ball. We have a paddle. Time to add hit detection to bounce the paddle off the ball.
main.c
#include <epoxy/gl.h>
#include <epoxy/glx.h>
#include <gtk/gtk.h>
#include <math.h>
#include "DashGL/dashgl.h"
#define WIDTH 640.0f
#define HEIGHT 480.0f
static void on_realize(GtkGLArea *area);
static void on_render(GtkGLArea *area, GdkGLContext *context);
static gboolean on_idle(gpointer data);
static gboolean on_keydown(GtkWidget *widget, GdkEventKey *event);
static gboolean on_keyup(GtkWidget *widget, GdkEventKey *event);
struct {
float dx, dy;
float r;
vec3 pos;
vec3 color;
mat4 mvp;
GLuint vbo;
gboolean left, right, top, bottom;
} ball;
struct {
float width;
float height;
float dx;
vec3 pos;
vec3 color;
mat4 mvp;
GLuint vbo;
gboolean key_left;
gboolean key_right;
} paddle;
GLuint program;
GLuint vao;
GLint attribute_coord2d;
GLint uniform_mvp, uniform_color;
int main(int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *glArea;
gtk_init(&argc, &argv);
// Initialize Window
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Brickout Tutorial");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_UTILITY);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(window, "key-press-event", G_CALLBACK(on_keydown), NULL);
g_signal_connect(window, "key-release-event", G_CALLBACK(on_keyup), NULL);
// Initialize GTK GL Area
glArea = gtk_gl_area_new();
gtk_widget_set_vexpand(glArea, TRUE);
gtk_widget_set_hexpand(glArea, TRUE);
g_signal_connect(glArea, "realize", G_CALLBACK(on_realize), NULL);
g_signal_connect(glArea, "render", G_CALLBACK(on_render), NULL);
gtk_container_add(GTK_CONTAINER(window), glArea);
// Show widgets
gtk_widget_show_all(window);
gtk_main();
return 0;
}
static void on_realize(GtkGLArea *area) {
// Debug Message
g_print("on realize\n");
gtk_gl_area_make_current(area);
if(gtk_gl_area_get_error(area) != NULL) {
fprintf(stderr, "Unknown error\n");
return;
}
const GLubyte *renderer = glGetString(GL_RENDER);
const GLubyte *version = glGetString(GL_VERSION);
const GLubyte *shader = glGetString(GL_SHADING_LANGUAGE_VERSION);
printf("Shader %s\n", shader);
printf("Renderer: %s\n", renderer);
printf("OpenGL version supported %s\n", version);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
ball.r = 18.0f;
int i;
float angle, nextAngle;
int num_segments = 99;
GLfloat circle_vertices[6 * 100];
for(i = 0; i ball.r;
circle_vertices[i*6 + 1] = sin(angle) * ball.r;
circle_vertices[i*6 + 2] = cos(nextAngle) * ball.r;
circle_vertices[i*6 + 3] = sin(nextAngle) * ball.r;
circle_vertices[i*6 + 4] = 0.0f;
circle_vertices[i*6 + 5] = 0.0f;
}
glGenBuffers(1, &ball.vbo);
glBindBuffer(GL_ARRAY_BUFFER, ball.vbo);
glBufferData(
GL_ARRAY_BUFFER,
sizeof(circle_vertices),
circle_vertices,
GL_STATIC_DRAW
);
paddle.width = 50.0f;
paddle.height = 7.0f;
GLfloat paddle_vertices[] = {
-paddle.width, -paddle.height,
-paddle.width, paddle.height,
paddle.width, paddle.height,
paddle.width, paddle.height,
paddle.width, -paddle.height,
-paddle.width, -paddle.height
};
glGenBuffers(1, &paddle.vbo);
glBindBuffer(GL_ARRAY_BUFFER, paddle.vbo);
glBufferData(
GL_ARRAY_BUFFER,
sizeof(paddle_vertices),
paddle_vertices,
GL_STATIC_DRAW
);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glDisableVertexAttribArray(0);
const char *vs = "shader/vertex.glsl";
const char *fs = "shader/fragment.glsl";
program = shader_load_program(vs, fs);
const char *attribute_name = "coord2d";
attribute_coord2d = glGetAttribLocation(program, attribute_name);
if(attribute_coord2d == -1) {
fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
return;
}
const char *uniform_name = "orthograph";
GLint uniform_ortho = glGetUniformLocation(program, uniform_name);
if(uniform_ortho == -1) {
fprintf(stderr, "Could not bind uniform %s\n", uniform_name);
return;
}
uniform_name = "mvp";
uniform_mvp = glGetUniformLocation(program, uniform_name);
if(uniform_mvp == -1) {
fprintf(stderr, "Could not bind uniform %s\n", uniform_name);
return;
}
uniform_name = "color";
uniform_color = glGetUniformLocation(program, uniform_name);
if(uniform_color == -1) {
fprintf(stderr, "Could not bind uniform %s\n", uniform_name);
return;
}
glUseProgram(program);
mat4 orthograph;
mat4_orthagonal(WIDTH, HEIGHT, orthograph);
glUniformMatrix4fv(uniform_ortho, 1, GL_FALSE, orthograph);
g_timeout_add(20, on_idle, (void*)area);
ball.dx = 2.0f;
ball.dy = 3.0f;
ball.pos[0] = 50.0f;
ball.pos[1] = 50.0f;
ball.pos[2] = 0.0f;
ball.color[0] = 0.0f;
ball.color[1] = 0.0f;
ball.color[2] = 1.0f;
paddle.dx = 2.0f;
paddle.pos[0] = WIDTH / 2.0f;
paddle.pos[1] = 20.0f;
paddle.pos[2] = 0.0f;
paddle.color[0] = 0.0f;
paddle.color[1] = 0.0f;
paddle.color[2] = 0.0f;
}
static void on_render(GtkGLArea *area, GdkGLContext *context) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mat4_translate(ball.pos, ball.mvp);
glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, ball.mvp);
glUniform3fv(uniform_color, 1, ball.color);
glBindVertexArray(vao);
glEnableVertexAttribArray(attribute_coord2d);
glBindBuffer(GL_ARRAY_BUFFER, ball.vbo);
glVertexAttribPointer(
attribute_coord2d,
2,
GL_FLOAT,
GL_FALSE,
0,
0
);
glDrawArrays(GL_TRIANGLES, 0, 3 * 100);
mat4_translate(paddle.pos, paddle.mvp);
glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, paddle.mvp);
glUniform3fv(uniform_color, 1, paddle.color);
glBindBuffer(GL_ARRAY_BUFFER, paddle.vbo);
glVertexAttribPointer(
attribute_coord2d,
2,
GL_FLOAT,
GL_FALSE,
0,
0
);
glDrawArrays(GL_TRIANGLES, 0, 3 * 2);
glDisableVertexAttribArray(attribute_coord2d);
}
static gboolean on_idle(gpointer data) {
ball.pos[0] += ball.dx;
ball.pos[1] += ball.dy;
if(ball.pos[0] > WIDTH) {
ball.pos[0] = WIDTH;
ball.dx *= -1;
} else if(ball.pos[0] <0) {
ball.pos[0] = 0;
ball.dx *= -1;
}
if(ball.pos[1] > HEIGHT) {
ball.pos[1] = HEIGHT;
ball.dy *= -1;
} else if(ball.pos[1] <0) {
ball.pos[1] = 0;
ball.dy *= -1;
}
if(paddle.key_left) {
paddle.pos[0] -= paddle.dx;
}
if(paddle.key_right) {
paddle.pos[0] += paddle.dx;
}
if(paddle.pos[0] <0) {
paddle.pos[0] = 0.0f;
} else if(paddle.pos[0] > WIDTH) {
paddle.pos[0] = WIDTH;
}
ball.left = ball.pos[0] > paddle.pos[0] - paddle.width;
ball.right = ball.pos[0] paddle.pos[1] - paddle.height;
if(ball.dy <0 && ball.left && ball.right && ball.top && ball.bottom) {
ball.dy *= -1.05;
}
gtk_widget_queue_draw(GTK_WIDGET(data));
return TRUE;
}
static gboolean on_keydown(GtkWidget *widget, GdkEventKey *event) {
switch(event->keyval) {
case GDK_KEY_Left:
paddle.key_left = TRUE;
break;
case GDK_KEY_Right:
paddle.key_right = TRUE;
break;
}
}
static gboolean on_keyup(GtkWidget *widget, GdkEventKey *event) {
switch(event->keyval) {
case GDK_KEY_Left:
paddle.key_left = FALSE;
break;
case GDK_KEY_Right:
paddle.key_right = FALSE;
break;
}
}
Compile
$ gcc -c -o DashGL/dashgl.o DashGL/dashgl.c -lepoxy -lpng -lm
$ gcc `pkg-config --cflags gtk+-3.0` main.c DashGL/dashgl.o `pkg-config --libs gtk+-3.0` \
-lepoxy -lm -lpng
Run
$ ./a.out