From c76697d51158ed5e52bd697c71fbdfed1ec25a3f Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Thu, 8 Feb 2024 19:53:09 -0800
Subject: Add Pong demo.

---
 game/src/plugins/CMakeLists.txt |  18 ++-
 game/src/plugins/pong.c         | 237 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 252 insertions(+), 3 deletions(-)
 create mode 100644 game/src/plugins/pong.c

(limited to 'game/src/plugins')

diff --git a/game/src/plugins/CMakeLists.txt b/game/src/plugins/CMakeLists.txt
index ecb2a45..e5abbb8 100644
--- a/game/src/plugins/CMakeLists.txt
+++ b/game/src/plugins/CMakeLists.txt
@@ -2,16 +2,28 @@ cmake_minimum_required(VERSION 3.0)
 
 project(plugins)
 
-set(LINK_LIBRARIES cstring math gfx)
+set(LINK_LIBRARIES cstring math gfx gfx-app)
+
+# GLTF viewer
 
 add_library(gltf_view SHARED
   gltf_view.c)
 
+target_link_libraries(gltf_view PUBLIC
+  ${LINK_LIBRARIES})
+
+# Texture viewer
+
 add_library(texture_view SHARED
   texture_view.c)
 
-target_link_libraries(gltf_view PUBLIC
+target_link_libraries(texture_view PUBLIC
   ${LINK_LIBRARIES})
 
-target_link_libraries(texture_view PUBLIC
+# Pong
+
+add_library(pong SHARED
+  pong.c)
+
+target_link_libraries(pong PUBLIC
   ${LINK_LIBRARIES})
diff --git a/game/src/plugins/pong.c b/game/src/plugins/pong.c
new file mode 100644
index 0000000..3a0b825
--- /dev/null
+++ b/game/src/plugins/pong.c
@@ -0,0 +1,237 @@
+#include "plugin.h"
+
+#include <gfx/gfx.h>
+#include <gfx/gfx_app.h>
+#include <gfx/renderer.h>
+
+#include <math/mat4.h>
+#include <math/vec2.h>
+#include <math/vec4.h>
+
+#include <stdlib.h>
+
+static const vec2 PAD_SIZE        = (vec2){120, 20};
+static const R    PLAYER_Y_OFFSET = 50;
+static const R    PLAYER_SPEED    = 800;
+
+static const R ENEMY_SPEED = 2;
+
+static const R BALL_SIZE  = 18;
+static const R BALL_SPEED = 360; // In each dimension.
+
+static const R EPS = (R)1e-3;
+
+typedef struct Player {
+  vec2 position;
+} Player;
+
+typedef struct Ball {
+  vec2 position;
+  vec2 velocity;
+} Ball;
+
+typedef struct State {
+  bool   game_started;
+  Player human;
+  Player enemy;
+  Ball   ball;
+  mat4   viewProjection;
+} State;
+
+bool init(Game* game, State** pp_state) {
+  assert(game);
+
+  State* state = calloc(1, sizeof(State));
+  if (!state) {
+    return false;
+  }
+
+  *pp_state = state;
+  return true;
+
+cleanup:
+  free(state);
+  return false;
+}
+
+void shutdown(Game* game, State* state) {
+  assert(game);
+  assert(state);
+}
+
+static void move_ball(Ball* ball, R dt, int width, int height) {
+  assert(ball);
+
+  const R offset = BALL_SIZE / 2;
+
+  ball->position = vec2_add(ball->position, vec2_scale(ball->velocity, dt));
+
+  // Right wall.
+  if (ball->position.x + offset > (R)width) {
+    ball->position.x = (R)width - offset - EPS;
+    ball->velocity.x = -ball->velocity.x;
+  }
+  // Left wall.
+  else if (ball->position.x - offset < 0) {
+    ball->position.x = offset + EPS;
+    ball->velocity.x = -ball->velocity.x;
+  }
+  // Top wall.
+  if (ball->position.y + offset > (R)height) {
+    ball->position.y = (R)height - offset - EPS;
+    ball->velocity.y = -ball->velocity.y;
+  }
+  // Bottom wall.
+  else if (ball->position.y - offset < 0) {
+    ball->position.y = offset + EPS;
+    ball->velocity.y = -ball->velocity.y;
+  }
+}
+
+void move_enemy_player(int width, Player* player, R t) {
+  const R half_width = (R)width / 2;
+  const R amplitude  = half_width - (PAD_SIZE.x / 2);
+  player->position.x = half_width + amplitude * sinf(t * ENEMY_SPEED);
+}
+
+void move_human_player(Player* player, R dt) {
+  assert(player);
+
+  R speed = 0;
+  if (gfx_is_key_pressed('a')) {
+    speed -= PLAYER_SPEED;
+  }
+  if (gfx_is_key_pressed('d')) {
+    speed += PLAYER_SPEED;
+  }
+
+  player->position.x += speed * dt;
+}
+
+void clamp_player(Player* player, int width) {
+  assert(player);
+
+  const R offset = PAD_SIZE.x / 2;
+
+  // Left wall.
+  if (player->position.x + offset > (R)width) {
+    player->position.x = (R)width - offset;
+  }
+  // Right wall.
+  else if (player->position.x - offset < 0) {
+    player->position.x = offset;
+  }
+}
+
+void collide_ball(vec2 old_ball_position, const Player* player, Ball* ball) {
+  assert(player);
+  assert(ball);
+
+  // Discrete but simple collision. Checks for intersection and moves the ball
+  // back by a small epsilon.
+
+  // Player bounding box.
+  const vec2 player_pmin = vec2_make(
+      player->position.x - PAD_SIZE.x / 2, player->position.y - PAD_SIZE.y / 2);
+  const vec2 player_pmax = vec2_make(
+      player->position.x + PAD_SIZE.x / 2, player->position.y + PAD_SIZE.y / 2);
+
+  // Ball bounding box.
+  const vec2 ball_pmin = vec2_make(
+      ball->position.x - BALL_SIZE / 2, ball->position.y - BALL_SIZE / 2);
+  const vec2 ball_pmax = vec2_make(
+      ball->position.x + BALL_SIZE / 2, ball->position.y + BALL_SIZE / 2);
+
+  // Check for intersection and update ball.
+  if (!((ball_pmax.x < player_pmin.x) || (ball_pmin.x > player_pmax.x) ||
+        (ball_pmax.y < player_pmin.y) || (ball_pmin.y > player_pmax.y))) {
+    ball->position =
+        vec2_add(old_ball_position, vec2_scale(ball->velocity, -EPS));
+    ball->velocity.y = -ball->velocity.y;
+  }
+}
+
+void update(Game* game, State* state, double t, double dt) {
+  assert(game);
+  assert(state);
+
+  // TODO: Move game width/height to GfxApp query functions?
+  const vec2 old_ball_position = state->ball.position;
+  move_ball(&state->ball, (R)dt, game->width, game->height);
+  move_human_player(&state->human, (R)dt);
+  move_enemy_player(game->width, &state->enemy, (R)t);
+  clamp_player(&state->human, game->width);
+  collide_ball(old_ball_position, &state->human, &state->ball);
+  collide_ball(old_ball_position, &state->enemy, &state->ball);
+}
+
+static void draw_player(ImmRenderer* imm, const Player* player) {
+  assert(imm);
+  assert(player);
+
+  const vec2 half_box = vec2_div(PAD_SIZE, vec2_make(2, 2));
+
+  const vec2  pmin = vec2_sub(player->position, half_box);
+  const vec2  pmax = vec2_add(player->position, half_box);
+  const aabb2 box  = aabb2_make(pmin, pmax);
+
+  gfx_imm_draw_aabb2(imm, box);
+}
+
+static void draw_ball(ImmRenderer* imm, const Ball* ball) {
+  assert(imm);
+  assert(ball);
+
+  const vec2  half_box = vec2_make(BALL_SIZE / 2, BALL_SIZE / 2);
+  const vec2  pmin     = vec2_sub(ball->position, half_box);
+  const vec2  pmax     = vec2_add(ball->position, half_box);
+  const aabb2 box      = aabb2_make(pmin, pmax);
+
+  gfx_imm_draw_aabb2(imm, box);
+}
+
+void render(const Game* game, const State* state) {
+  assert(game);
+  assert(state);
+
+  ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
+  gfx_imm_start(imm);
+  gfx_imm_set_view_projection_matrix(imm, &state->viewProjection);
+  gfx_imm_load_identity(imm);
+  gfx_imm_set_colour(imm, vec4_make(1, 1, 1, 1));
+  draw_player(imm, &state->human);
+  draw_player(imm, &state->enemy);
+  draw_ball(imm, &state->ball);
+  gfx_imm_end(imm);
+}
+
+static R clamp_to_width(int width, R x, R extent) {
+  return min(x, (R)width - extent);
+}
+
+void resize(Game* game, State* state, int width, int height) {
+  assert(game);
+  assert(state);
+
+  state->viewProjection = mat4_ortho(0, (R)width, 0, (R)height, -1, 1);
+
+  state->human.position.y = PLAYER_Y_OFFSET;
+  state->enemy.position.y = (R)height - PLAYER_Y_OFFSET;
+
+  if (!state->game_started) {
+    state->human.position.x = (R)width / 2;
+    state->enemy.position.x = (R)width / 2;
+
+    state->ball.position =
+        vec2_div(vec2_make((R)width, (R)height), vec2_make(2, 2));
+
+    state->ball.velocity = vec2_make(BALL_SPEED, BALL_SPEED);
+
+    state->game_started = true;
+  } else {
+    state->human.position.x =
+        clamp_to_width(width, state->human.position.x, PAD_SIZE.x / 2);
+    state->enemy.position.x =
+        clamp_to_width(width, state->enemy.position.x, PAD_SIZE.x / 2);
+  }
+}
-- 
cgit v1.2.3