Compare commits

...

10 Commits

Author SHA1 Message Date
Camden Dixie O'Brien
68e87b807e Write README 2025-10-18 17:42:34 +01:00
Camden Dixie O'Brien
e4bc796b0f Add license (Komorebi 2.0.0) 2025-10-18 17:42:11 +01:00
Camden Dixie O'Brien
311dd4322d Add armour items 2025-10-18 17:42:08 +01:00
Camden Dixie O'Brien
74113c32b4 Handle wrapping in physics_separation() 2025-10-18 10:58:18 +01:00
Camden Dixie O'Brien
536ee74b61 Refactor into more modular architecture 2025-10-17 14:59:41 +01:00
Camden Dixie O'Brien
34e32c6a46 Make aspect global 2025-10-16 14:28:59 +01:00
Camden Dixie O'Brien
03f1bedc61 Add pausing 2025-10-16 14:22:10 +01:00
Camden Dixie O'Brien
07553feef1 Add score 2025-10-16 13:45:31 +01:00
Camden Dixie O'Brien
a1cfb77cac Add levelling 2025-10-15 18:37:21 +01:00
Camden Dixie O'Brien
ac0d3de2fb Only display "clear" message for a short time 2025-10-15 14:58:17 +01:00
24 changed files with 1336 additions and 443 deletions

108
LICENSE.md Normal file
View File

@@ -0,0 +1,108 @@
# Komorebi License
Version 2.0.0
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose. However, you may only distribute the source
code of the software according to the [Distribution License](
#distribution-license), you may only make changes according
to the [Changes License](#changes-license), and you may not
otherwise distribute the software or new works based on the
software.
## Distribution License
The licensor grants you an additional copyright license to
distribute copies of the source code of the software. Your
license to distribute covers distributing the source code of
the software with changes permitted by the [Changes License](
#changes-license).
## Changes License
The licensor grants you an additional copyright license to
make changes for any permitted purpose.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Personal Uses
Personal use for research, experiment, and testing for
the benefit of public knowledge, personal study, private
entertainment, hobby projects, amateur pursuits, or religious
observance, without any anticipated commercial application,
is use for a permitted purpose.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have
violated any of these terms, or done anything with the software
not covered by your licenses, your licenses can nonetheless
continue if you come into full compliance with these terms,
and take practical steps to correct past violations, within
32 days of receiving notice. Otherwise, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.

25
README Normal file
View File

@@ -0,0 +1,25 @@
ASTEROIDS
This is a clone (ish) of the classic game Asteroids, written in C23
for Linux. To build the program, you'll need the Linux headers, a
C23-compatible compiler and libdrm, along with its headers. There is
a build script that uses GCC; simply running this (`./build.sh`) is
likely to work on many systems.
The game directly uses Linux DRM, so it doesn't play nice with X,
display managers etc. You'll likely want to change to a virtual TTY
and run the game from there rather than from inside a graphical
session. You may also need to add your user to the "input" group.
CONTROLS
+--------------------------+
| forward up |
| anti-clockwise left |
| clockwise right |
| fire space |
| |
| pause p |
| quit q |
| restart r |
+--------------------------+

183
asteroids.c Normal file
View File

@@ -0,0 +1,183 @@
#include "asteroids.h"
#include "collisions.h"
#include "entity.h"
#include "renderer.h"
#include "rng.h"
#include "scene.h"
#include <assert.h>
#include <math.h>
#include <string.h>
#define MIN_VERTS 5
#define VERT_RANGE (MAX_VERTS - MIN_VERTS)
#define A_JITTER 0.8
#define V_JITTER 0.001
#define ROT_JITTER 0.005
#define R_JITTER 0.02
#define MIN_DIST 0.8
#define REPLACE_MIN 3
#define REPLACE_MAX 5
#define REPLACE_RANGE (REPLACE_MAX - REPLACE_MIN)
#define REPLACE_R_COEFF 0.8
#define REPLACE_R_JITTER 0.02
#define REPLACE_A_JITTER 0.4
#define REPLACE_V_JITTER 0.0005
#define REPLACE_RADIAL_V_COEFF 0.005
#define REPLACE_ROT_JITTER 0.02
#define COLLIDE_PRIOR 0
typedef struct {
bool valid;
unsigned component_id;
asteroid_size_t size;
} entry_t;
static asteroids_cb_t clear;
static unsigned count;
static entry_t entries[MAX_ENTITIES];
static float radii[] = {
[ASTEROID_SMALL] = 0.05f,
[ASTEROID_MEDIUM] = 0.1f,
[ASTEROID_LARGE] = 0.2f,
[ASTEROID_HUGE] = 0.4f,
};
static void update(unsigned new_entity_id, void *ref)
{
entry_t *old_e = (entry_t *)ref;
entry_t *new_e = entries + new_entity_id;
memcpy(new_e, old_e, sizeof(entry_t));
old_e->valid = false;
entity_update_component(new_entity_id, new_e->component_id, new_e);
}
static void remove(void *ref)
{
entry_t *e = (entry_t *)ref;
e->valid = false;
--count;
if (count == 0)
clear();
}
static void
create_asteroid(vec2_t pos, vec2_t vel, float rot, asteroid_size_t size)
{
const float r_mean = radii[size];
const unsigned n = MIN_VERTS + rng_uint32() % VERT_RANGE;
const float da = 2.0f * PI / n;
vec2_t verts[n];
for (unsigned i = 0; i < n; ++i) {
const float r = r_mean + rng_plusminus() * R_JITTER;
const float a_jitter = A_JITTER * rng_plusminus() * da / 2;
const float a = i * da + a_jitter;
verts[i] = (vec2_t) { r * cosf(a), r * sinf(a) };
}
const unsigned id = entity_add();
scene_add(id, verts, n, true);
physics_add(id, pos, MAT2_ID, vel, rot, r_mean * r_mean);
collisions_add(id, r_mean, COLLIDE_PRIOR, physics_bounce);
entry_t *entry = entries + id;
entry->valid = true;
entry->component_id = entity_add_component(id, update, remove, entry);
entry->size = size;
++count;
}
static asteroid_size_t draw_size(const asteroid_size_dist_entry_t *dist)
{
const float r = rng_canon();
float acc = 0;
for (unsigned i = 0; dist[i].probability != 0.0f; ++i) {
acc += dist[i].probability;
if (r < acc)
return dist[i].size;
}
assert(false);
}
void asteroids_clear()
{
count = 0;
memset(entries, 0, sizeof(entries));
}
void asteroids_on_clear(asteroids_cb_t cb)
{
clear = cb;
}
void asteroids_add(const asteroid_size_dist_entry_t *dist)
{
vec2_t pos;
do {
pos.y = rng_plusminus();
pos.x = aspect * rng_plusminus();
} while (vec2_len(pos) < MIN_DIST);
const vec2_t vel = {
V_JITTER * rng_plusminus(),
V_JITTER * rng_plusminus(),
};
const float rot = ROT_JITTER * rng_plusminus();
const asteroid_size_t size = draw_size(dist);
create_asteroid(pos, vel, rot, size);
}
void asteroids_hit(unsigned shot, unsigned asteroid, physics_sep_t)
{
assert(entries[asteroid].valid);
entity_mark(asteroid);
const asteroid_size_t old_size = entries[asteroid].size;
if (old_size == ASTEROID_SMALL)
return;
const asteroid_size_t size = old_size - 1;
const float mass = radii[size] * radii[size];
const physics_t *phys_shot = physics_get(shot);
const physics_t *phys_ast = physics_get(asteroid);
const vec2_t p_shot = vec2_scale(phys_shot->vel, phys_shot->mass);
const vec2_t p_ast = vec2_scale(phys_ast->vel, phys_ast->mass);
const unsigned n = REPLACE_MIN + rng_uint32() % REPLACE_RANGE;
const float da = 2 * PI / n;
const vec2_t p = vec2_add(p_shot, p_ast);
const vec2_t inherit_v = vec2_scale(p, 1 / (n * mass));
for (unsigned i = 0; i < n; ++i) {
const float r = REPLACE_R_COEFF * radii[old_size]
+ REPLACE_R_JITTER * rng_plusminus();
const float a = i * da + REPLACE_A_JITTER * rng_plusminus();
const vec2_t disp = { r * cosf(a), r * sinf(a) };
const vec2_t radial_v = vec2_scale(
vec2_norm(disp), REPLACE_RADIAL_V_COEFF * rng_canon());
const vec2_t jitter_v = {
REPLACE_V_JITTER * rng_plusminus(),
REPLACE_V_JITTER * rng_plusminus(),
};
const vec2_t pos = vec2_add(phys_ast->pos, disp);
const vec2_t vel = vec2_add(inherit_v, vec2_add(radial_v, jitter_v));
const float rot
= phys_ast->rot / n + REPLACE_ROT_JITTER * rng_plusminus();
create_asteroid(pos, vel, rot, size);
}
}
bool asteroids_check(unsigned id)
{
return entries[id].valid;
}

27
asteroids.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef ASTEROIDS_H
#define ASTEROIDS_H
#include "physics.h"
typedef void (*asteroids_cb_t)();
typedef enum {
ASTEROID_SMALL,
ASTEROID_MEDIUM,
ASTEROID_LARGE,
ASTEROID_HUGE,
} asteroid_size_t;
typedef struct {
float probability;
asteroid_size_t size;
} asteroid_size_dist_entry_t;
void asteroids_clear();
void asteroids_on_clear(asteroids_cb_t cb);
void asteroids_add(const asteroid_size_dist_entry_t *dist);
void asteroids_hit(unsigned shot, unsigned asteroid, physics_sep_t sep);
bool asteroids_check(unsigned id);
#endif

View File

@@ -8,4 +8,5 @@ defs="-D_POSIX_C_SOURCE=200809L"
$cc $warn $flags $libs $defs \
-o asteroids \
fb.c game.c input.c main.c maths.c renderer.c rng.c text.c
asteroids.c collisions.c entity.c fb.c game.c input.c \
main.c maths.c physics.c scene.c renderer.c rng.c text.c

87
collisions.c Normal file
View File

@@ -0,0 +1,87 @@
#include "collisions.h"
#include "entity.h"
#include <assert.h>
#include <string.h>
typedef struct {
bool valid;
unsigned component_id;
float r;
unsigned priority;
collision_cb_t cb;
} entry_t;
static unsigned max;
static entry_t entries[MAX_ENTITIES];
static void update(unsigned new_entity_id, void *ref)
{
entry_t *old_e = (entry_t *)ref;
entry_t *new_e = entries + new_entity_id;
memcpy(new_e, old_e, sizeof(entry_t));
old_e->valid = false;
entity_update_component(new_entity_id, new_e->component_id, new_e);
}
static void remove(void *ref)
{
entry_t *e = (entry_t *)ref;
e->valid = false;
}
void collisions_init()
{
max = 0;
}
void collisions_update()
{
for (unsigned i = 0; i < max; ++i) {
if (!entries[i].valid)
continue;
for (unsigned j = i + 1; j < max; ++j) {
if (!entries[j].valid)
continue;
const physics_sep_t sep = physics_separation(i, j);
if (sep.dist > entries[i].r + entries[j].r)
continue;
if (sep.va - sep.vb <= 0)
continue;
if (entries[i].priority >= entries[j].priority) {
entries[i].cb(i, j, sep);
} else {
const physics_sep_t flip_sep = {
.dist = sep.dist,
.norm = vec2_scale(sep.norm, -1),
.va = -sep.vb,
.vb = -sep.va,
};
entries[j].cb(j, i, flip_sep);
}
}
}
}
void collisions_add(
unsigned entity, float radius, unsigned priority, collision_cb_t cb)
{
assert(entity < MAX_ENTITIES);
if (entity >= max)
max = entity + 1;
else
assert(!entries[entity].valid);
entry_t *entry = entries + entity;
entry->valid = true;
entry->component_id
= entity_add_component(entity, update, remove, entry);
entry->r = radius;
entry->priority = priority;
entry->cb = cb;
}

14
collisions.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef COLLISIONS_H
#define COLLISIONS_H
#include "physics.h"
typedef void (*collision_cb_t)(unsigned a, unsigned b, physics_sep_t sep);
void collisions_init();
void collisions_update();
void collisions_add(
unsigned entity, float radius, unsigned priority, collision_cb_t cb);
#endif

88
entity.c Normal file
View File

@@ -0,0 +1,88 @@
#include "entity.h"
#include <assert.h>
#include <string.h>
#define MAX_COMPONENTS_PER_ENTITY 4U
typedef struct {
update_cb_t update;
remove_cb_t remove;
void *ref;
} component_t;
typedef struct {
bool marked;
unsigned count;
component_t components[MAX_COMPONENTS_PER_ENTITY];
} entity_t;
static unsigned count;
static entity_t entities[MAX_ENTITIES];
void entities_clear()
{
count = 0;
}
void entities_purge()
{
for (unsigned id = count - 1; id < count; --id) {
entity_t *e = entities + id;
if (!e->marked)
continue;
for (unsigned i = 0; i < e->count; ++i)
e->components[i].remove(e->components[i].ref);
const unsigned last = count - 1;
if (id != last) {
memcpy(e, entities + last, sizeof(entity_t));
for (unsigned i = 0; i < e->count; ++i)
e->components[i].update(id, e->components[i].ref);
}
--count;
}
}
unsigned entity_add()
{
assert(count < MAX_ENTITIES);
const unsigned id = count++;
memset(entities + id, 0, sizeof(entity_t));
return id;
}
void entity_mark(unsigned id)
{
assert(id < count);
entities[id].marked = true;
}
unsigned entity_add_component(
unsigned id, update_cb_t update, remove_cb_t remove, void *ref)
{
assert(id < count);
entity_t *e = entities + id;
assert(e->count < MAX_COMPONENTS_PER_ENTITY);
const unsigned component_id = e->count++;
e->components[component_id] = (component_t) {
.update = update,
.remove = remove,
.ref = ref,
};
return component_id;
}
void entity_update_component(
unsigned entity_id, unsigned component_id, void *new_ref)
{
assert(entity_id < count);
entity_t *e = entities + entity_id;
assert(component_id < e->count);
e->components[component_id].ref = new_ref;
}

20
entity.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef ENTITY_H
#define ENTITY_H
#define MAX_ENTITIES 256U
typedef void (*update_cb_t)(unsigned new_entity_id, void *ref);
typedef void (*remove_cb_t)(void *ref);
void entities_clear();
void entities_purge();
unsigned entity_add();
void entity_mark(unsigned id);
unsigned entity_add_component(
unsigned id, update_cb_t update, remove_cb_t remove, void *ref);
void entity_update_component(
unsigned entity_id, unsigned component_id, void *new_ref);
#endif

635
game.c
View File

@@ -1,94 +1,54 @@
#include "game.h"
#include "asteroids.h"
#include "collisions.h"
#include "entity.h"
#include "input.h"
#include "physics.h"
#include "renderer.h"
#include "rng.h"
#include "scene.h"
#include "text.h"
#include <assert.h>
#include <linux/input-event-codes.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#define INIT_LEVEL 1
#define SHIP_COLLIDE_R 0.05
#define SHIP_MASS 0.5
#define SHIP_MASS 0.02
#define FIRE_MEAN -0.15
#define FIRE_JITTER 0.01
#define ASTEROID_MIN_VERTS 5
#define ASTEROID_MAX_VERTS 8
#define ASTEROID_VERT_RANGE (ASTEROID_MAX_VERTS - ASTEROID_MIN_VERTS)
#define ASTEROID_A_JITTER 0.8
#define ASTEROID_VEL_JITTER 0.001
#define ASTEROID_OMG_JITTER 0.005
#define INIT_ASTEROIDS 1
#define ASTEROID_SMALL 0.05f
#define ASTEROID_MEDIUM 0.1f
#define ASTEROID_LARGE 0.2f
#define ASTEROID_HUGE 0.4f
#define ASTEROID_R_JITTER 0.02
#define ASTEROID_MIN_DIST 0.8
#define REPLACE_MIN 3
#define REPLACE_MAX 5
#define REPLACE_RANGE (REPLACE_MAX - REPLACE_MIN)
#define REPLACE_R_COEFF 0.8
#define REPLACE_R_JITTER 0.02
#define REPLACE_A_JITTER 0.4
#define REPLACE_V_JITTER 0.0005
#define REPLACE_RADIAL_V_COEFF 0.005
#define REPLACE_OMG_JITTER 0.02
#define LIN_PWR 0.0001
#define ROT_PWR 0.002
#define SHOT_VEL 0.04
#define SHOT_COLLIDE_R 0.005
#define SHOT_MASS 0.01
#define MAX_SHAPES 256U
#define MAX_ENTITIES 128U
#define MAX_SHAPES_PER_ENTITY 2
#define MAX_COLLISIONS 128U
#define SHOT_VEL 0.03
#define SHOT_COLLIDE_R 0.03
#define SHOT_MASS 0.001
#define ARROW_SCALE 0.025
#define ARROW_WIDTH 4
#define ARROW_HEIGHT 4
#define COUNTER_MASK (1 << 6)
#define ITEM_SPAWN_R 0.9
#define ITEM_COLLIDE_R 0.025
#define ITEM_SPAWN_EXP_S 40
#define ITEM_MIN_SCORE 32
#define ARMOUR_COEFF 5e-6
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
typedef enum {
COLLISION_SHIP,
COLLISION_SHOT,
COLLISION_ASTEROID,
} collision_tag_t;
typedef struct {
vec2_t pos;
vec2_t vel;
mat2_t dir;
float omg;
unsigned shapes[MAX_SHAPES_PER_ENTITY];
unsigned shape_count;
float radius;
collision_tag_t tag;
bool dead;
} entity_t;
typedef struct {
bool visible;
int entity;
unsigned vert_count;
vec2_t verts[MAX_VERTS];
bool connect;
} shape_t;
typedef struct {
unsigned entities[2];
float vs[2];
vec2_t normal;
} collision_t;
enum {
SHOT_COLLIDE_PRIOR = 1,
SHIP_COLLIDE_PRIOR,
ITEM_COLLIDE_PRIOR,
};
static const vec2_t ship_verts[] = {
{ 0.0, 0.11 },
@@ -103,406 +63,291 @@ static const vec2_t fire_verts[] = {
};
static const vec2_t shot_verts[] = { { 0.0, -0.02 }, { 0.0, 0.02 } };
static entity_t entities[MAX_ENTITIES];
static mat3_t transforms[MAX_ENTITIES];
static shape_t shapes[MAX_SHAPES];
static const vec2_t arrow_verts[][MAX_VERTS] = {
{ { 1, 2 }, { 2, 0 }, { 1, -2 } },
{ { -2, 0 }, { 2, 0 } },
};
static const unsigned arrow_counts[NELEMS(arrow_verts)] = { 3, 2 };
static unsigned entity_count;
static unsigned shape_count;
static unsigned asteroid_count;
static const vec2_t item_verts[][MAX_VERTS] = {
{ { -0.025, -0.025 }, { 0.025, 0.025 } },
{ { -0.025, 0.025 }, { 0.025, -0.025 } },
};
static const unsigned item_counts[NELEMS(item_verts)] = { 2, 2 };
static bool dead;
static float aspect;
static uint8_t counter;
static const vec2_t shot_vel = { 0, SHOT_VEL };
static const asteroid_size_dist_entry_t easy_dist[] = {
{ 0.5, ASTEROID_MEDIUM },
{ 0.5, ASTEROID_LARGE },
{ 0, 0 },
};
static const asteroid_size_dist_entry_t hard_dist[] = {
{ 0.4, ASTEROID_MEDIUM },
{ 0.5, ASTEROID_LARGE },
{ 0.1, ASTEROID_HUGE },
{ 0, 0 },
};
static unsigned ship_entity_id;
static unsigned ship_shape_id;
static unsigned fire_shape_id;
static unsigned fire_scene_id;
static void restart()
static unsigned level;
static bool dead;
static bool clear;
static bool paused;
static unsigned asteroid_count;
static uint8_t counter;
static unsigned score;
static char *msg;
static unsigned armour_level;
static bool item;
static void cleared()
{
game_init(aspect);
clear = true;
counter = 0;
renderer_set_wrap(false);
physics_escape(ship_entity_id);
}
static entity_t *add_entity(unsigned *id_out)
static void die()
{
const unsigned id = entity_count++;
memset(entities + id, 0, sizeof(entity_t));
entities[id].dir = (mat2_t) { { 1, 0 }, { 0, 1 } };
if (id_out != nullptr)
*id_out = id;
return entities + id;
dead = true;
clear = false;
counter = 0;
}
static shape_t *add_shape(unsigned entity, unsigned *id_out)
static void upgrade_armour()
{
assert(entities[entity].shape_count < MAX_SHAPES_PER_ENTITY);
const int id = shape_count++;
memset(shapes + id, 0, sizeof(shape_t));
shapes[id].entity = entity;
entities[entity].shapes[entities[entity].shape_count++] = id;
if (id_out != nullptr)
*id_out = id;
return shapes + id;
if (armour_level >= 9)
return;
static char armour_msg[] = "ARMOUR X";
armour_msg[7] = '0' + ++armour_level;
msg = armour_msg;
counter = 0;
}
static void shot_collide(unsigned a, unsigned b, physics_sep_t sep)
{
entity_mark(a);
if (asteroids_check(b)) {
++score;
asteroids_hit(a, b, sep);
} else {
entity_mark(b);
}
}
static void ship_collide(unsigned a, unsigned b, physics_sep_t sep)
{
const float v = sep.va - sep.vb;
const float impact = physics_get(b)->mass * v;
const float armour = armour_level * ARMOUR_COEFF;
if (impact < armour)
physics_bounce(a, b, sep);
else
die();
}
static void item_collide(unsigned a, unsigned b, physics_sep_t)
{
item = false;
entity_mark(a);
if (b == ship_entity_id)
upgrade_armour();
}
static void shoot()
{
if (entity_count >= MAX_ENTITIES || shape_count >= MAX_SHAPES)
if (dead || paused)
return;
entity_t *ship = entities + ship_entity_id;
const shape_t *ship_shape = shapes + ship_shape_id;
physics_t *ship = physics_get(ship_entity_id);
unsigned id;
entity_t *e = add_entity(&id);
e->dir = ship->dir;
e->pos = vec2_add(
ship->pos, mat2_mul_vec2(ship->dir, ship_shape->verts[0]));
e->vel = vec2_add(
ship->vel, mat2_mul_vec2(ship->dir, (vec2_t) { 0, SHOT_VEL }));
e->radius = SHIP_COLLIDE_R;
e->tag = COLLISION_SHOT;
const vec2_t pos
= vec2_add(ship->pos, mat2_mul_vec2(ship->dir, ship_verts[0]));
const vec2_t vel
= vec2_add(ship->vel, mat2_mul_vec2(ship->dir, shot_vel));
shape_t *s = add_shape(id, nullptr);
s->visible = true;
s->vert_count = NELEMS(shot_verts);
memcpy(s->verts, shot_verts, sizeof(shot_verts));
const unsigned id = entity_add();
scene_add(id, shot_verts, NELEMS(shot_verts), false);
physics_add(id, pos, ship->dir, vel, 0, SHOT_MASS);
collisions_add(id, SHOT_COLLIDE_R, SHOT_COLLIDE_PRIOR, shot_collide);
const vec2_t ship_p = vec2_scale(ship->vel, SHIP_MASS);
const vec2_t shot_p = vec2_scale(e->vel, SHOT_MASS);
const vec2_t new_ship_p = vec2_sub(ship_p, shot_p);
ship->vel = vec2_scale(new_ship_p, 1 / SHIP_MASS);
const vec2_t p_ship = vec2_scale(ship->vel, ship->mass);
const vec2_t p_shot = vec2_scale(vel, SHOT_MASS);
ship->vel = vec2_scale(vec2_sub(p_ship, p_shot), 1 / SHIP_MASS);
}
static entity_t *gen_asteroid(float r_mean, unsigned *id_out)
static void draw_arrow()
{
unsigned id;
entity_t *e = add_entity(&id);
e->radius = r_mean;
e->tag = COLLISION_ASTEROID;
shape_t *s = add_shape(id, nullptr);
s->visible = true;
s->connect = true;
const unsigned n
= ASTEROID_MIN_VERTS + rng_uint32() % ASTEROID_VERT_RANGE;
s->vert_count = n;
const float da = 2.0f * PI / n;
for (unsigned i = 0; i < n; ++i) {
const float r = r_mean + rng_plusminus() * ASTEROID_R_JITTER;
const float a
= i * da + ASTEROID_A_JITTER * rng_plusminus() * da / 2;
s->verts[i] = (vec2_t) { r * cosf(a), r * sinf(a) };
}
if (id_out)
*id_out = id;
return e;
}
static bool intersecting(unsigned a, unsigned b)
{
const float sep = vec2_len(vec2_sub(entities[b].pos, entities[a].pos));
return sep <= entities[a].radius + entities[b].radius;
}
static bool intersecting_any(unsigned id)
{
for (unsigned i = 0; i < entity_count; ++i) {
if (i == id)
continue;
if (intersecting(id, i))
return true;
}
return false;
}
static void spawn_asteroid()
{
float r;
const float rnd = rng_canon();
if (rnd < 0.4)
r = ASTEROID_MEDIUM;
else if (rnd < 0.8)
r = ASTEROID_LARGE;
else
r = ASTEROID_HUGE;
unsigned id;
entity_t *e = gen_asteroid(r, &id);
do {
e->pos.y = rng_plusminus();
e->pos.x = aspect * rng_plusminus();
} while (vec2_len(e->pos) < ASTEROID_MIN_DIST || intersecting_any(id));
e->vel = (vec2_t) {
ASTEROID_VEL_JITTER * rng_plusminus(),
ASTEROID_VEL_JITTER * rng_plusminus(),
const float tx = aspect - ARROW_SCALE * (ARROW_WIDTH / 2.0 + 2);
const mat3_t m = {
{ ARROW_SCALE, 0, 0 },
{ 0, ARROW_SCALE, 0 },
{ tx, 0, 1 },
};
e->omg = ASTEROID_OMG_JITTER * rng_plusminus();
++asteroid_count;
const vec3_t c_mod = { 0, 0, 1 };
const vec3_t s_mod = { ARROW_WIDTH + 2, ARROW_HEIGHT + 2, 0 };
const vec3_t c = mat3_mul_vec3(m, c_mod);
const vec3_t s = mat3_mul_vec3(m, s_mod);
renderer_clear_rect(vec3_reduce(c), (vec2_t) { s.x, s.y });
for (unsigned i = 0; i < NELEMS(arrow_verts); ++i)
renderer_draw(arrow_verts[i], arrow_counts[i], m, false);
}
static void remove_shape(unsigned id)
static void anim_fire(vec2_t *verts)
{
if (id < shape_count - 1) {
const shape_t *last = shapes + shape_count - 1;
memcpy(shapes + id, last, sizeof(shape_t));
for (unsigned i = 0; i < entities[last->entity].shape_count; ++i) {
if (entities[last->entity].shapes[i] == shape_count - 1) {
entities[last->entity].shapes[i] = id;
break;
}
}
}
--shape_count;
verts[0].y = FIRE_MEAN + FIRE_JITTER * rng_plusminus();
}
static void remove_entity(unsigned id)
static void create_field()
{
assert(id != ship_entity_id);
item = false;
dead = false;
clear = false;
paused = false;
asteroid_count = 0;
msg = nullptr;
entity_t *e = entities + id;
for (unsigned i = 0; i < e->shape_count; ++i)
remove_shape(e->shapes[i]);
renderer_set_wrap(true);
if (id < entity_count - 1) {
const entity_t *last = entities + entity_count - 1;
memcpy(e, last, sizeof(entity_t));
for (unsigned i = 0; i < e->shape_count; ++i)
shapes[e->shapes[i]].entity = id;
}
entities_clear();
scene_clear();
physics_init();
collisions_init();
--entity_count;
ship_entity_id = entity_add();
scene_add(ship_entity_id, ship_verts, NELEMS(ship_verts), true);
physics_add(
ship_entity_id, (vec2_t) {}, MAT2_ID, (vec2_t) {}, 0, SHIP_MASS);
collisions_add(
ship_entity_id, SHIP_COLLIDE_R, SHIP_COLLIDE_PRIOR, ship_collide);
fire_scene_id
= scene_add(ship_entity_id, fire_verts, NELEMS(fire_verts), true);
scene_animate(fire_scene_id, anim_fire);
for (unsigned i = 0; i < level; ++i)
asteroids_add(level < 4 ? easy_dist : hard_dist);
}
static void win()
{
renderer_set_wrap(true);
++level;
create_field();
}
static void reset()
{
level = INIT_LEVEL;
score = 0;
armour_level = 0;
asteroids_clear();
create_field();
}
static void pause()
{
paused = !paused;
counter = 0;
}
static void ship_update()
{
entity_t *ship = entities + ship_entity_id;
physics_t *ship = physics_get(ship_entity_id);
ship->omg += ROT_PWR * (float)input.spin;
ship->rot += ROT_PWR * (float)input.spin;
const vec2_t thrust = { 0, (float)input.thrust * LIN_PWR };
const vec2_t acc = mat2_mul_vec2(ship->dir, thrust);
ship->vel = vec2_add(ship->vel, acc);
shape_t *fire = shapes + fire_shape_id;
fire->visible = input.thrust != 0;
fire->verts[0].y = FIRE_MEAN + FIRE_JITTER * rng_plusminus();
}
static unsigned check_collisions(collision_t *out)
{
unsigned count = 0;
for (unsigned i = 0; i < entity_count; ++i) {
for (unsigned j = i + 1; j < entity_count; ++j) {
if (intersecting(i, j)) {
const vec2_t n
= vec2_norm(vec2_sub(entities[j].pos, entities[i].pos));
const float vi = vec2_dot(entities[i].vel, n);
const float vj = vec2_dot(entities[j].vel, n);
if (vi < 0 && vj > 0)
continue;
assert(count < MAX_COLLISIONS);
out[count].entities[0] = i;
out[count].entities[1] = j;
out[count].vs[0] = vi;
out[count].vs[1] = vj;
out[count].normal = n;
++count;
}
}
}
return count;
}
static void bounce(collision_t c)
{
entity_t *a = entities + c.entities[0];
entity_t *b = entities + c.entities[1];
const float ma = a->radius * a->radius;
const float mb = b->radius * b->radius;
const float m = ma + mb;
const vec2_t n = c.normal;
const float va1 = c.vs[0];
const float vb1 = c.vs[1];
const float va2 = (va1 * (ma - mb) + 2 * mb * vb1) / m;
const float vb2 = (vb1 * (mb - ma) + 2 * ma * va1) / m;
a->vel = vec2_add(a->vel, vec2_scale(n, va2 - va1));
b->vel = vec2_add(b->vel, vec2_scale(n, vb2 - vb1));
}
static void destroy_asteroid(entity_t *a, vec2_t shot_vel)
{
a->dead = true;
--asteroid_count;
float r;
if (a->radius == ASTEROID_HUGE)
r = ASTEROID_LARGE;
else if (a->radius == ASTEROID_LARGE)
r = ASTEROID_MEDIUM;
else if (a->radius == ASTEROID_MEDIUM)
r = ASTEROID_SMALL;
else
return;
const unsigned n = REPLACE_MIN + rng_uint32() % REPLACE_RANGE;
const float da = 2 * PI / n;
const float m = a->radius * a->radius;
const vec2_t p
= vec2_add(vec2_scale(a->vel, m), vec2_scale(shot_vel, SHOT_MASS));
const vec2_t inherit_v = vec2_scale(p, 1 / n * (m + SHOT_MASS));
for (unsigned i = 0; i < n; ++i) {
const float disp_r = REPLACE_R_COEFF * a->radius
+ REPLACE_R_JITTER * rng_plusminus();
const float disp_a = i * da + REPLACE_A_JITTER * rng_plusminus();
const vec2_t disp = { disp_r * cosf(disp_a), disp_r * sinf(disp_a) };
const vec2_t radial_v = vec2_scale(
vec2_norm(disp), REPLACE_RADIAL_V_COEFF * rng_canon());
const vec2_t jitter_v = {
REPLACE_V_JITTER * rng_plusminus(),
REPLACE_V_JITTER * rng_plusminus(),
};
entity_t *e = gen_asteroid(r, nullptr);
e->pos = vec2_add(a->pos, disp);
e->vel = vec2_add(inherit_v, vec2_add(radial_v, jitter_v));
e->omg = a->omg / n + REPLACE_OMG_JITTER * rng_plusminus();
++asteroid_count;
if (input.thrust != 0) {
const vec2_t thrust = { 0, (float)input.thrust * LIN_PWR };
const vec2_t acc = mat2_mul_vec2(ship->dir, thrust);
ship->vel = vec2_add(ship->vel, acc);
scene_show(fire_scene_id);
} else {
scene_hide(fire_scene_id);
}
printf("asteroid count: %u\n", asteroid_count);
if (clear && ship->pos.x > aspect)
win();
}
static void handle_collisions(const collision_t *collisions, unsigned count)
static void add_item()
{
for (unsigned i = 0; i < count; ++i) {
const collision_t c = collisions[i];
entity_t *a = entities + c.entities[0];
entity_t *b = entities + c.entities[1];
const unsigned id = entity_add();
if (a->tag == COLLISION_SHIP || b->tag == COLLISION_SHIP) {
dead = true;
continue;
}
for (unsigned i = 0; i < NELEMS(item_verts); ++i)
scene_add(id, item_verts[i], item_counts[i], false);
if (a->tag == COLLISION_SHOT && b->tag == COLLISION_SHOT)
continue;
const vec2_t pos = {
.x = ITEM_SPAWN_R * rng_plusminus(),
.y = ITEM_SPAWN_R * rng_plusminus(),
};
physics_add(id, pos, MAT2_ID, (vec2_t) {}, 0, 0);
if (a->tag == COLLISION_ASTEROID && b->tag == COLLISION_ASTEROID) {
bounce(c);
continue;
}
collisions_add(id, ITEM_COLLIDE_R, ITEM_COLLIDE_PRIOR, item_collide);
if (a->tag == COLLISION_SHOT) {
a->dead = true;
destroy_asteroid(b, a->vel);
}
if (b->tag == COLLISION_SHOT) {
b->dead = true;
destroy_asteroid(a, b->vel);
}
}
for (unsigned i = 0; i < entity_count; ++i) {
if (entities[i].dead)
remove_entity(i--);
}
item = true;
}
void game_init(float _aspect)
void game_init()
{
input_on_shoot(shoot);
input_on_restart(restart);
input_on_restart(reset);
input_on_pause(pause);
asteroids_on_clear(cleared);
aspect = _aspect;
dead = false;
counter = 0;
entity_count = shape_count = 0;
asteroid_count = 0;
entity_t *ship = add_entity(&ship_entity_id);
ship->radius = SHIP_COLLIDE_R;
ship->tag = COLLISION_SHIP;
shape_t *ship_shape = add_shape(ship_entity_id, &ship_shape_id);
ship_shape->visible = true;
ship_shape->connect = true;
ship_shape->vert_count = NELEMS(ship_verts);
memcpy(ship_shape->verts, ship_verts, sizeof(ship_verts));
shape_t *fire = add_shape(ship_entity_id, &fire_shape_id);
fire->visible = true;
fire->connect = true;
fire->vert_count = NELEMS(fire_verts);
memcpy(fire->verts, fire_verts, sizeof(fire_verts));
for (unsigned i = 0; i < INIT_ASTEROIDS; ++i)
spawn_asteroid();
reset();
}
void game_update()
{
if (dead) {
if (msg != nullptr || dead || clear || paused)
++counter;
if (counter > COUNTER_MASK)
msg = nullptr;
if (dead || paused)
return;
}
ship_update();
physics_update();
collisions_update();
scene_update();
for (unsigned i = 0; i < entity_count; ++i) {
entity_t *e = entities + i;
entities_purge();
e->dir = mat2_mul_mat2(mat2_rotation(e->omg), e->dir);
e->pos = vec2_add(e->pos, e->vel);
if (e->pos.y > 1)
e->pos.y -= 2;
else if (e->pos.y <= -1)
e->pos.y += 2;
if (e->pos.x >= aspect)
e->pos.x -= 2 * aspect;
else if (e->pos.x < -aspect)
e->pos.x += 2 * aspect;
transforms[i]
= mat3_mul_mat3(mat3_translation(e->pos), mat2_extend(e->dir));
}
static collision_t collisions[MAX_COLLISIONS];
const unsigned ncols = check_collisions(collisions);
handle_collisions(collisions, ncols);
const bool items_enabled = score >= ITEM_MIN_SCORE;
if (items_enabled && !item
&& rng_canon() < 1.0f / (60 * ITEM_SPAWN_EXP_S))
add_item();
}
void game_draw()
{
renderer_clear();
for (unsigned i = 0; i < shape_count; ++i) {
if (!shapes[i].visible)
continue;
scene_draw();
const mat3_t transform = transforms[shapes[i].entity];
renderer_draw(
shapes[i].verts, shapes[i].vert_count, transform,
shapes[i].connect);
text_draw_score(score);
if (clear)
draw_arrow();
const bool display_text = !(counter & COUNTER_MASK);
if (display_text) {
if (dead)
text_draw_centre("GAME OVER");
if (clear) {
draw_arrow();
if (!(counter & COUNTER_MASK))
text_draw_centre("CLEAR");
}
if (paused)
text_draw_centre("PAUSED");
if (msg != nullptr)
text_draw_centre(msg);
}
if (asteroid_count == 0)
text_draw("CLEAR");
if (dead && !(counter & COUNTER_MASK))
text_draw("GAME OVER");
}

2
game.h
View File

@@ -1,7 +1,7 @@
#ifndef GAME_H
#define GAME_H
void game_init(float aspect);
void game_init();
void game_update();
void game_draw();

11
input.c
View File

@@ -19,6 +19,7 @@ static int input_fd;
static input_callback_t restart;
static input_callback_t quit;
static input_callback_t shoot;
static input_callback_t pause_;
static void key_press(int key)
{
@@ -46,6 +47,9 @@ static void key_press(int key)
if (shoot != nullptr)
shoot();
break;
case KEY_P:
if (pause_ != nullptr)
pause_();
}
}
@@ -74,7 +78,7 @@ static void key_repeat(int key)
int input_init()
{
memset(&input, 0, sizeof(input));
shoot = nullptr;
shoot = restart = quit = pause_ = nullptr;
input_fd = open("/dev/input/event0", O_RDONLY);
assert(input_fd != -1);
@@ -105,6 +109,11 @@ void input_on_quit(input_callback_t cb)
quit = cb;
}
void input_on_pause(input_callback_t cb)
{
pause_ = cb;
}
void input_handle()
{
struct input_event ev;

View File

@@ -16,6 +16,7 @@ void input_cleanup();
void input_on_shoot(input_callback_t cb);
void input_on_restart(input_callback_t cb);
void input_on_quit(input_callback_t cb);
void input_on_pause(input_callback_t cb);
void input_handle();

5
main.c
View File

@@ -18,16 +18,15 @@ static void quit()
int main()
{
const int input_fd = input_init();
const renderer_params_t renderer_params = renderer_init();
const int drm_fd = renderer_init();
rng_init();
game_init(renderer_params.aspect);
game_init();
input_on_quit(quit);
renderer_clear();
renderer_swap();
const int drm_fd = renderer_params.drm_fd;
const int max_fd = MAX(input_fd, drm_fd);
fd_set set;

View File

@@ -41,6 +41,11 @@ float vec2_dot(vec2_t v1, vec2_t v2)
return v1.x * v2.x + v1.y * v2.y;
}
vec2_t vec3_reduce(vec3_t v)
{
return (vec2_t) { v.x / v.z, v.y / v.z };
}
mat2_t mat2_rotation(float theta)
{
return (mat2_t) {

View File

@@ -3,6 +3,9 @@
#define PI 3.14159265358979323846264
#define MAT2_ID ((mat2_t) { { 1, 0 }, { 0, 1 } })
#define MAT3_ID ((mat3_t) { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } })
typedef struct {
float x, y;
} vec2_t;
@@ -27,6 +30,8 @@ vec3_t vec2_extend(vec2_t v);
vec2_t vec2_norm(vec2_t v);
float vec2_dot(vec2_t v1, vec2_t v2);
vec2_t vec3_reduce(vec3_t v);
mat2_t mat2_rotation(float theta);
vec2_t mat2_mul_vec2(mat2_t m, vec2_t v);
mat2_t mat2_mul_mat2(mat2_t m1, mat2_t m2);

152
physics.c Normal file
View File

@@ -0,0 +1,152 @@
#include "physics.h"
#include "entity.h"
#include "renderer.h"
#include <assert.h>
#include <math.h>
#include <string.h>
typedef struct {
bool valid;
unsigned component_id;
physics_t state;
} entry_t;
static unsigned max;
static unsigned escape_entity;
static entry_t entries[MAX_ENTITIES];
static void update(unsigned new_entity_id, void *ref)
{
entry_t *old_e = (entry_t *)ref;
entry_t *new_e = entries + new_entity_id;
memcpy(new_e, old_e, sizeof(entry_t));
old_e->valid = false;
entity_update_component(new_entity_id, new_e->component_id, new_e);
}
static void remove(void *ref)
{
entry_t *e = (entry_t *)ref;
e->valid = false;
}
void physics_init()
{
max = 0;
escape_entity = MAX_ENTITIES;
}
void physics_update()
{
for (unsigned i = 0; i < max; ++i) {
if (!entries[i].valid)
continue;
physics_t *st = &entries[i].state;
st->dir = mat2_mul_mat2(mat2_rotation(st->rot), st->dir);
st->pos = vec2_add(st->pos, st->vel);
if (st->pos.y > 1)
st->pos.y -= 2;
else if (st->pos.y <= -1)
st->pos.y += 2;
if (st->pos.x >= aspect && i != escape_entity)
st->pos.x -= 2 * aspect;
else if (st->pos.x < -aspect)
st->pos.x += 2 * aspect;
}
}
void physics_add(
unsigned entity, vec2_t pos, mat2_t dir, vec2_t vel, float rot,
float mass)
{
assert(entity < MAX_ENTITIES);
if (entity >= max)
max = entity + 1;
else
assert(!entries[entity].valid);
entry_t *entry = entries + entity;
entry->valid = true;
entry->component_id
= entity_add_component(entity, update, remove, entry);
entry->state = (physics_t) {
.pos = pos,
.dir = dir,
.vel = vel,
.rot = rot,
.mass = mass,
};
}
physics_t *physics_get(unsigned entity)
{
assert(entity < max);
assert(entries[entity].valid);
return &entries[entity].state;
}
void physics_escape(unsigned entity)
{
assert(entity < max);
assert(entries[entity].valid);
escape_entity = entity;
}
static void pos_views(vec2_t a, vec2_t b, vec2_t out[4])
{
const float dx = 2 * aspect;
const float dy = 2;
out[0] = b;
out[1] = (vec2_t) { b.x + copysign(dx, a.x), b.y };
out[2] = (vec2_t) { b.x, b.y + copysign(dy, a.y) };
out[3] = (vec2_t) { b.x + copysign(dx, a.x), b.y + copysign(dy, a.y) };
}
physics_sep_t physics_separation(unsigned a, unsigned b)
{
const physics_t *pa = physics_get(a);
const physics_t *pb = physics_get(b);
vec2_t pos[4];
pos_views(pa->pos, pb->pos, pos);
float min_dist = INFINITY;
vec2_t min_disp;
for (unsigned i = 0; i < 4; ++i) {
const vec2_t disp = vec2_sub(pos[i], pa->pos);
const float dist = vec2_len(disp);
if (dist < min_dist) {
min_disp = disp;
min_dist = dist;
}
}
const vec2_t norm = vec2_norm(min_disp);
return (physics_sep_t) {
.dist = min_dist,
.norm = norm,
.va = vec2_dot(pa->vel, norm),
.vb = vec2_dot(pb->vel, norm),
};
}
void physics_bounce(unsigned a, unsigned b, physics_sep_t sep)
{
physics_t *pa = physics_get(a);
physics_t *pb = physics_get(b);
const float ma = pa->mass;
const float mb = pb->mass;
const float ms = ma + mb;
const float va = (sep.va * (ma - mb) + 2 * mb * sep.vb) / ms;
const float vb = (sep.vb * (mb - ma) + 2 * ma * sep.va) / ms;
pa->vel = vec2_add(pa->vel, vec2_scale(sep.norm, va - sep.va));
pb->vel = vec2_add(pb->vel, vec2_scale(sep.norm, vb - sep.vb));
}

31
physics.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef PHYSICS_H
#define PHYSICS_H
#include "maths.h"
typedef struct {
vec2_t pos;
mat2_t dir;
vec2_t vel;
float rot;
float mass;
} physics_t;
typedef struct {
float dist, va, vb;
vec2_t norm;
} physics_sep_t;
void physics_init();
void physics_update();
void physics_add(
unsigned entity, vec2_t pos, mat2_t dir, vec2_t vel, float rot,
float mass);
physics_t *physics_get(unsigned entity);
void physics_escape(unsigned entity);
physics_sep_t physics_separation(unsigned a, unsigned b);
void physics_bounce(unsigned a, unsigned b, physics_sep_t sep);
#endif

View File

@@ -9,6 +9,8 @@
#include <unistd.h>
#include <xf86drm.h>
float aspect;
static int drm_fd;
static drmModeConnector *conn;
static drmModeCrtc *crtc;
@@ -20,6 +22,8 @@ static int front, back;
static uint32_t width, height;
static mat3_t view;
static bool wrap;
static vec3_t vert_buf[MAX_VERTS];
static void page_flip_handler(int, unsigned, unsigned, unsigned, void *)
@@ -34,6 +38,13 @@ static void set_pixel(uint32_t x, uint32_t y)
static void draw_line(vec3_t v1, vec3_t v2)
{
v1.x /= v1.z;
v1.y /= v1.z;
v1.z = 1;
v2.x /= v2.z;
v2.y /= v2.z;
v2.z = 1;
const float delta_x = v2.x - v1.x;
const float delta_y = v2.y - v1.y;
const float step = fmaxf(fabsf(delta_x), fabsf(delta_y));
@@ -43,16 +54,20 @@ static void draw_line(vec3_t v1, vec3_t v2)
for (unsigned i = 0; i <= step; ++i) {
float x = roundf(v1.x + i * step_x);
if (x >= width)
x -= width;
else if (x < 0)
x += width;
float y = roundf(v1.y + i * step_y);
if (y >= height)
y -= height;
else if (y < 0)
y += height;
if (wrap) {
if (x >= width)
x -= width;
else if (x < 0)
x += width;
if (y >= height)
y -= height;
else if (y < 0)
y += height;
} else if (x >= width || x < 0 || y >= height || y < 0) {
continue;
}
set_pixel(x, y);
}
@@ -61,7 +76,7 @@ static void draw_line(vec3_t v1, vec3_t v2)
}
}
renderer_params_t renderer_init()
int renderer_init()
{
drm_fd = open("/dev/dri/card1", O_RDWR | O_CLOEXEC);
assert(drm_fd != -1);
@@ -89,7 +104,9 @@ renderer_params_t renderer_init()
width = mode.hdisplay;
height = mode.vdisplay;
const float aspect = (float)width / (float)height;
wrap = true;
aspect = (float)width / (float)height;
const float scale = (float)height / 2.0f;
view = (mat3_t) {
{ scale, 0, 0 },
@@ -97,10 +114,7 @@ renderer_params_t renderer_init()
{ aspect * scale, scale, 1 },
};
return (renderer_params_t) {
.drm_fd = drm_fd,
.aspect = aspect,
};
return drm_fd;
}
void renderer_cleanup()
@@ -136,6 +150,11 @@ void renderer_swap()
back = (back + 1) & 1;
}
void renderer_set_wrap(bool enable)
{
wrap = enable;
}
void renderer_clear()
{
memset(fbs[back].buf, 0, fbs[back].size);

View File

@@ -7,20 +7,17 @@
#define MAX_VERTS 8U
typedef struct {
int drm_fd;
float aspect;
} renderer_params_t;
extern float aspect;
renderer_params_t renderer_init();
int renderer_init();
void renderer_cleanup();
void renderer_handle();
void renderer_swap();
void renderer_set_wrap(bool enable);
void renderer_clear();
void renderer_clear_rect(vec2_t centre, vec2_t size);
void renderer_draw(
const vec2_t *vs, unsigned count, mat3_t model, bool connect);

105
scene.c Normal file
View File

@@ -0,0 +1,105 @@
#include "scene.h"
#include "entity.h"
#include "physics.h"
#include "renderer.h"
#include <assert.h>
#include <string.h>
#define MAX_SHAPES 256U
typedef struct {
unsigned entity_id;
unsigned component_id;
unsigned vert_count;
vec2_t verts[MAX_VERTS];
scene_anim_cb_t anim_cb;
bool hidden;
bool connect;
} shape_t;
static unsigned count;
static shape_t shapes[MAX_SHAPES];
static void update(unsigned new_entity_id, void *ref)
{
shape_t *s = (shape_t *)ref;
s->entity_id = new_entity_id;
}
static void remove(void *ref)
{
shape_t *s = (shape_t *)ref;
const shape_t *last = shapes + (count - 1);
if (s < last) {
memcpy(s, last, sizeof(shape_t));
entity_update_component(s->entity_id, s->component_id, s);
}
--count;
}
static mat3_t get_transform(unsigned entity_id)
{
const physics_t *phys = physics_get(entity_id);
return mat3_mul_mat3(
mat3_translation(phys->pos), mat2_extend(phys->dir));
}
void scene_clear()
{
count = 0;
}
void scene_update()
{
for (unsigned i = 0; i < count; ++i) {
if (shapes[i].anim_cb != nullptr)
shapes[i].anim_cb(shapes[i].verts);
}
}
void scene_draw()
{
for (unsigned i = 0; i < count; ++i) {
const shape_t *s = shapes + i;
if (!s->hidden) {
const mat3_t m = get_transform(s->entity_id);
renderer_draw(s->verts, s->vert_count, m, s->connect);
}
}
}
unsigned scene_add(
unsigned entity, const vec2_t *verts, unsigned vert_count, bool connect)
{
assert(count < MAX_SHAPES);
const unsigned id = count++;
shape_t *s = shapes + id;
*s = (shape_t) {
.entity_id = entity,
.vert_count = vert_count,
.connect = connect,
};
memcpy(s->verts, verts, vert_count * sizeof(vec2_t));
s->component_id = entity_add_component(entity, update, remove, s);
return id;
}
void scene_hide(unsigned id)
{
assert(id < count);
shapes[id].hidden = true;
}
void scene_show(unsigned id)
{
assert(id < count);
shapes[id].hidden = false;
}
void scene_animate(unsigned id, scene_anim_cb_t cb)
{
assert(id < count);
shapes[id].anim_cb = cb;
}

18
scene.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef SCENE_H
#define SCENE_H
#include "maths.h"
typedef void (*scene_anim_cb_t)(vec2_t *verts);
void scene_clear();
void scene_update();
void scene_draw();
unsigned scene_add(
unsigned entity, const vec2_t *verts, unsigned vert_count, bool connect);
void scene_hide(unsigned id);
void scene_show(unsigned id);
void scene_animate(unsigned id, scene_anim_cb_t cb);
#endif

193
text.c
View File

@@ -13,6 +13,8 @@
#define LETTER_WIDTH 3
#define SPACE_WIDTH 1
#define MAX_SCORE_CHARS 4
typedef struct {
unsigned line_count;
unsigned line_lens[MAX_LINES];
@@ -26,6 +28,93 @@ static const mat3_t text_transform = {
};
static const glyph_t font[] = {
['0'] = {
.line_count = 1,
.line_lens = { 5 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } },
},
},
['1'] = {
.line_count = 2,
.line_lens = { 3, 2 },
.lines = {
{ { -1, 4 }, { 0, 4 }, { 0, -4 } },
{ { -1, -4 }, { 1, -4 } },
},
},
['2'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ -1, 4 }, { 1, 4 }, { 1, -2 },
{ -1, -2 }, { -1, -4 }, { 1, -4 },
},
},
},
['3'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ -1, 4 }, { 1, 4 }, { 0, -2 },
{ 1, -2 }, { 1, -4 }, { -1, -4 },
},
},
},
['4'] = {
.line_count = 1,
.line_lens = { 4 },
.lines = {
{ { 1, -2 }, { -1, -2 }, { 1, 4 }, { 1, -4 } }
},
},
['5'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, 4 }, { -1, 4 }, { -1, -2 },
{ 1, -2 }, { 1, -4 }, { -1, -4 },
},
},
},
['6'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, 4 }, { -1, 4 }, { -1, -4 },
{ 1, -4 }, { 1, -2 }, { -1, -2 },
},
},
},
['7'] = {
.line_count = 1,
.line_lens = { 3 },
.lines = {
{ { -1, 4 }, { 1, 4 }, { 0, -4 } },
},
},
['8'] = {
.line_count = 2,
.line_lens = { 5, 2 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } },
{ { -1, -2 }, { 1, -2 } },
},
},
['9'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, -2 }, { -1, -2 }, { -1, 4 },
{ 1, 4 }, { 1, -4 }, { -1, -4 },
},
},
},
['A'] = {
.line_count = 2,
.line_lens = { 3, 2 },
@@ -41,6 +130,13 @@ static const glyph_t font[] = {
{ { 1, 4 }, { -1, 4 }, { -1, -4 }, { 1, -4 } },
},
},
['D'] = {
.line_count = 1,
.line_lens = { 5 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 0, 4 }, { -1, 4 } },
},
},
['E'] = {
.line_count = 2,
.line_lens = { 4, 2 },
@@ -77,6 +173,13 @@ static const glyph_t font[] = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } },
},
},
['P'] = {
.line_count = 1,
.line_lens = { 5 },
.lines = {
{ { -1, -4 }, { -1, 4 }, { 1, 4 }, { 1, -2 }, { -1, -2 } },
},
},
['R'] = {
.line_count = 1,
.line_lens = { 6 },
@@ -87,6 +190,23 @@ static const glyph_t font[] = {
},
},
},
['S'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, 4 }, { -1, 4 }, { -1, -2 },
{ 1, -2 }, { 1, -4 }, { -1, -4 },
},
},
},
['U'] = {
.line_count = 1,
.line_lens = { 4 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 } },
},
},
['V'] = {
.line_count = 1,
.line_lens = { 3 },
@@ -96,7 +216,29 @@ static const glyph_t font[] = {
},
};
void text_draw(const char *s)
static void draw_glyph(unsigned c, vec2_t pos)
{
assert(c < NELEMS(font) && font[c].line_count != 0);
const glyph_t *g = font + c;
const mat3_t m = mat3_mul_mat3(mat3_translation(pos), text_transform);
for (unsigned i = 0; i < g->line_count; ++i)
renderer_draw(g->lines[i], g->line_lens[i], m, false);
}
static void draw_text(const char *s, vec2_t pos)
{
for (const char *p = s; *p != '\0'; ++p) {
if (*p == ' ') {
pos.x += TEXT_SCALE * SPACE_WIDTH;
} else {
draw_glyph((unsigned)*p, pos);
pos.x += TEXT_SCALE * LETTER_WIDTH;
}
}
}
void text_draw_centre(const char *s)
{
int width = 0;
for (const char *p = s; *p != '\0'; ++p)
@@ -109,23 +251,34 @@ void text_draw(const char *s)
};
renderer_clear_rect((vec2_t) { 0, 0 }, bg_size);
int x = -width / 2 + 1;
for (const char *p = s; *p != '\0'; ++p) {
if (*p == ' ') {
x += SPACE_WIDTH;
} else {
const unsigned c = (unsigned)*p;
assert(c < NELEMS(font) && font[c].line_count != 0);
const glyph_t *g = font + c;
const vec2_t t = { .x = x };
const mat3_t m
= mat3_mul_mat3(text_transform, mat3_translation(t));
for (unsigned i = 0; i < g->line_count; ++i)
renderer_draw(g->lines[i], g->line_lens[i], m, false);
x += LETTER_WIDTH;
}
}
const vec2_t pos = { TEXT_SCALE * (-width / 2.0f + 1), 0 };
draw_text(s, pos);
}
void text_draw_score(unsigned score)
{
char buf[MAX_SCORE_CHARS + 1];
buf[MAX_SCORE_CHARS] = '\0';
for (unsigned i = 0; i < MAX_SCORE_CHARS; ++i) {
const unsigned v = score % 10;
score /= 10;
buf[MAX_SCORE_CHARS - 1 - i] = '0' + v;
}
const int width = LETTER_WIDTH * MAX_SCORE_CHARS - 1;
const vec2_t bg_centre = {
.x = TEXT_SCALE * (width / 2.0f + 2) - aspect,
.y = 1.0f - TEXT_SCALE * (TEXT_HEIGHT / 2.0f + 2),
};
const vec2_t bg_size = {
.x = TEXT_SCALE * (width + 2),
.y = TEXT_SCALE * (TEXT_HEIGHT + 2),
};
renderer_clear_rect(bg_centre, bg_size);
const vec2_t text_pos = {
.x = TEXT_SCALE * 3 - aspect,
.y = bg_centre.y,
};
draw_text(buf, text_pos);
}

3
text.h
View File

@@ -1,6 +1,7 @@
#ifndef TEXT_H
#define TEXT_H
void text_draw(const char *s);
void text_draw_centre(const char *s);
void text_draw_score(unsigned score);
#endif