diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2020-04-19 16:07:21 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2020-04-19 16:07:21 -0700 |
commit | 308d0ee789659778210662ca9156981944452ccc (patch) | |
tree | 1e2021b57e96c25f0f69326e9f1cfac73dc1a347 | |
parent | c068006e340c83fe7099b0e63daf33dbd94b9432 (diff) |
src: implement a game for Blender 2020
The theme of this Blender was:
Monkeys / Rescuing / Between Realities
With all the COVID-19 stuff going on, it seemed like a fun way
to lighten things up a bit to make something where a monkey runs
around trying to rescue child monkeys from coronaviruses moving
across the playfield. In keeping with the theme, to rescue the
helpless monkeys you take them to a different reality by carrying
them off the window/screen. As infections increase the field
becomes crowded with viruses until your player becomes infected
through contact, ending your game.
This was written quickly kamikaze style overnight. Some
scaffolding bits came from past projects of mine like the vector
headers, shader and texture node building blocks, and the plasma
effect has been used a few times now and originally was derived
from some gpu programming tutorial if memory serves. I just
wanted to put something in the background for this strange
reality.
This is the first time I've used libplay, in fact, it was
basically slapped together last night at the start of this to
avoid having to do all that SDL initialization all over again.
The unique meat of this work is in game.c, there isn't really all
that much to this game though. It's not pretty code, but it
works well enough and this task served as a useful exercise of
trying to get some quick game dev done using this collection of
facilities.
Most the heavy lifting comes from my reused libraries which are
slowly evolving into something somewhat effective for simple game
development.
Enjoy, and happy hacking!
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/Makefile.am | 65 | ||||
-rw-r--r-- | src/adult-node.c | 32 | ||||
-rw-r--r-- | src/adult-node.h | 25 | ||||
-rw-r--r-- | src/baby-node.c | 32 | ||||
-rw-r--r-- | src/baby-node.h | 25 | ||||
-rw-r--r-- | src/bb2f.h | 38 | ||||
-rw-r--r-- | src/bb3f.h | 38 | ||||
-rw-r--r-- | src/digit-node.c | 60 | ||||
-rw-r--r-- | src/digit-node.h | 25 | ||||
-rw-r--r-- | src/game.c | 660 | ||||
-rw-r--r-- | src/hungrycat-node.c | 27 | ||||
-rw-r--r-- | src/hungrycat-node.h | 25 | ||||
-rw-r--r-- | src/hungrycat.c | 157 | ||||
-rw-r--r-- | src/m4f-3dx.h | 122 | ||||
-rw-r--r-- | src/m4f-bbx.h | 89 | ||||
-rw-r--r-- | src/plasma-node.c | 93 | ||||
-rw-r--r-- | src/plasma-node.h | 25 | ||||
-rw-r--r-- | src/sfx.c | 37 | ||||
-rw-r--r-- | src/sfx.h | 34 | ||||
-rw-r--r-- | src/shader-node.c | 174 | ||||
-rw-r--r-- | src/shader-node.h | 30 | ||||
-rw-r--r-- | src/shader.c | 144 | ||||
-rw-r--r-- | src/shader.h | 28 | ||||
-rw-r--r-- | src/tex-node.c | 100 | ||||
-rw-r--r-- | src/tex-node.h | 27 | ||||
-rw-r--r-- | src/tex.c | 189 | ||||
-rw-r--r-- | src/tex.h | 30 | ||||
-rw-r--r-- | src/tv-node.c | 32 | ||||
-rw-r--r-- | src/tv-node.h | 25 | ||||
-rw-r--r-- | src/virus-node.c | 32 | ||||
-rw-r--r-- | src/virus-node.h | 25 |
32 files changed, 2443 insertions, 4 deletions
diff --git a/Makefile.am b/Makefile.am index cf98fba..39053b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1 +1 @@ -SUBDIRS = libstage libplay src +SUBDIRS = libix2 libplay libstage src diff --git a/src/Makefile.am b/src/Makefile.am index 9adbab6..7dce89e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,63 @@ bin_PROGRAMS = sars -sars_SOURCES = KRH/khrplatform.h clear-node.h clear-node.c macros.h main.c sars.c sars.h glad.c glad.h -sars_CPPFLAGS = -I@top_srcdir@/libstage/src -I@top_srcdir@/libplay/src -ffast-math -sars_LDADD = @top_builddir@/libstage/src/libstage.a @top_builddir@/libplay/src/libplay.a -lm -ldl +sars_SOURCES = \ + adult-node.c \ + adult-node.h \ + baby-node.c \ + baby-node.h \ + bb2f.h \ + bb3f.h \ + clear-node.c \ + clear-node.h \ + digit-node.c \ + digit-node.h \ + game.c \ + gfx/gfx-adult.h \ + gfx/gfx-baby.h \ + gfx/gfx-eight.h \ + gfx/gfx-five.h \ + gfx/gfx-four.h \ + gfx/gfx-hungrycat.h \ + gfx/gfx-nine.h \ + gfx/gfx-one.h \ + gfx/gfx-seven.h \ + gfx/gfx-six.h \ + gfx/gfx-three.h \ + gfx/gfx-tv.h \ + gfx/gfx-two.h \ + gfx/gfx-virus.h \ + gfx/gfx-zero.h \ + glad.c \ + glad.h \ + hungrycat.c \ + hungrycat-node.c \ + hungrycat-node.h \ + KHR/khrplatform.h \ + m4f-3dx.h \ + m4f-bbx.h \ + m4f.h \ + macros.h \ + main.c \ + plasma-node.c \ + plasma-node.h \ + sars.c \ + sars.h \ + sfx.c \ + sfx.h \ + shader.c \ + shader.h \ + shader-node.c \ + shader-node.h \ + tex.c \ + tex.h \ + tex-node.c \ + tex-node.h \ + tv-node.c \ + tv-node.h \ + v2f.h \ + v3f.h \ + v4f.h \ + virus-node.c \ + virus-node.h + +sars_CPPFLAGS = -I@top_srcdir@/libix2/src -I@top_srcdir@/libix2/libpad/src -I@top_srcdir@/libstage/src -I@top_srcdir@/libplay/src -ffast-math +sars_LDADD = @top_builddir@/libix2/src/libix2.a @top_builddir@/libix2/libpad/src/libpad.a @top_builddir@/libstage/src/libstage.a @top_builddir@/libplay/src/libplay.a -lm -ldl diff --git a/src/adult-node.c b/src/adult-node.c new file mode 100644 index 0000000..f314715 --- /dev/null +++ b/src/adult-node.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stage.h> + +#include "adult-node.h" +#include "gfx/gfx-adult.h" +#include "tex.h" +#include "tex-node.h" + +static tex_t *adult_tex; + +stage_t * adult_node_new(stage_conf_t *conf, m4f_t *model_x) +{ + if (!adult_tex) + adult_tex = tex_new(gfx_adult.width, gfx_adult.height, gfx_adult.pixel_data); + + return tex_node_new_tex(conf, adult_tex, model_x); +} diff --git a/src/adult-node.h b/src/adult-node.h new file mode 100644 index 0000000..151014f --- /dev/null +++ b/src/adult-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _ADULT_NODE_H +#define _ADULT_NODE_H + +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +stage_t * adult_node_new(stage_conf_t *conf, m4f_t *model_x); + +#endif diff --git a/src/baby-node.c b/src/baby-node.c new file mode 100644 index 0000000..8f49670 --- /dev/null +++ b/src/baby-node.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stage.h> + +#include "baby-node.h" +#include "gfx/gfx-baby.h" +#include "tex.h" +#include "tex-node.h" + +static tex_t *baby_tex; + +stage_t * baby_node_new(stage_conf_t *conf, m4f_t *model_x) +{ + if (!baby_tex) + baby_tex = tex_new(gfx_baby.width, gfx_baby.height, gfx_baby.pixel_data); + + return tex_node_new_tex(conf, baby_tex, model_x); +} diff --git a/src/baby-node.h b/src/baby-node.h new file mode 100644 index 0000000..f6cc02e --- /dev/null +++ b/src/baby-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _BABY_NODE_H +#define _BABY_NODE_H + +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +stage_t * baby_node_new(stage_conf_t *conf, m4f_t *model_x); + +#endif diff --git a/src/bb2f.h b/src/bb2f.h new file mode 100644 index 0000000..dc0a765 --- /dev/null +++ b/src/bb2f.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _BB2F_H +#define _BB2F_H + +#include "v2f.h" + +typedef struct bb2f_t { + v2f_t min, max; +} bb2f_t; + + +/* linearly interpolate between a and b by t */ +static inline bb2f_t bb2f_lerp(const bb2f_t *a, const bb2f_t *b, float t) +{ + bb2f_t bb2f; + + bb2f.min = v2f_lerp(&a->min, &b->min, t); + bb2f.max = v2f_lerp(&a->max, &b->max, t); + + return bb2f; +} + +#endif diff --git a/src/bb3f.h b/src/bb3f.h new file mode 100644 index 0000000..8bd4009 --- /dev/null +++ b/src/bb3f.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _BB3F_H +#define _BB3F_H + +#include "v3f.h" + +typedef struct bb3f_t { + v3f_t min, max; +} bb3f_t; + + +/* linearly interpolate between a and b by t */ +static inline bb3f_t bb3f_lerp(const bb3f_t *a, const bb3f_t *b, float t) +{ + bb3f_t bb3f; + + bb3f.min = v3f_lerp(&a->min, &b->min, t); + bb3f.max = v3f_lerp(&a->max, &b->max, t); + + return bb3f; +} + +#endif diff --git a/src/digit-node.c b/src/digit-node.c new file mode 100644 index 0000000..a5e2795 --- /dev/null +++ b/src/digit-node.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stage.h> + +#include "tex.h" +#include "tex-node.h" + +#include "gfx/gfx-zero.h" +#include "gfx/gfx-one.h" +#include "gfx/gfx-two.h" +#include "gfx/gfx-three.h" +#include "gfx/gfx-four.h" +#include "gfx/gfx-five.h" +#include "gfx/gfx-six.h" +#include "gfx/gfx-seven.h" +#include "gfx/gfx-eight.h" +#include "gfx/gfx-nine.h" + +static const unsigned char *digits_pixels[10] = { + gfx_zero.pixel_data, + gfx_one.pixel_data, + gfx_two.pixel_data, + gfx_three.pixel_data, + gfx_four.pixel_data, + gfx_five.pixel_data, + gfx_six.pixel_data, + gfx_seven.pixel_data, + gfx_eight.pixel_data, + gfx_nine.pixel_data, +}; + +static tex_t *digits_tex[10]; + +#define DIGIT_WIDTH 184 +#define DIGIT_HEIGHT 288 + +stage_t * digit_node_new(stage_conf_t *conf, unsigned digit, m4f_t *model_x) +{ + assert(digit < 10); + + if (!digits_tex[digit]) + digits_tex[digit] = tex_new(DIGIT_WIDTH, DIGIT_HEIGHT, digits_pixels[digit]); + + return tex_node_new_tex(conf, digits_tex[digit], model_x); +} diff --git a/src/digit-node.h b/src/digit-node.h new file mode 100644 index 0000000..9e686de --- /dev/null +++ b/src/digit-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _DIGIT_NODE_H +#define _DIGIT_NODE_H + +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +stage_t * digit_node_new(stage_conf_t *conf, unsigned digit, m4f_t *model_x); + +#endif diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..552471b --- /dev/null +++ b/src/game.c @@ -0,0 +1,660 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <SDL.h> +#include <assert.h> + +#include <ix2.h> +#include <pad.h> +#include <play.h> +#include <stage.h> + +#include "adult-node.h" +#include "baby-node.h" +#include "bb2f.h" +#include "bb3f.h" +#include "digit-node.h" +#include "glad.h" +#include "m4f.h" +#include "m4f-3dx.h" +#include "m4f-bbx.h" +#include "macros.h" +#include "plasma-node.h" +#include "sars.h" +#include "sfx.h" +#include "tv-node.h" +#include "v2f.h" +#include "virus-node.h" + +#define GAME_NUM_VIRUSES 8 +#define GAME_NUM_BABIES 10 + +#define GAME_VIRUS_SPEED .01f +#define GAME_ADULT_SPEED .04f + +#define GAME_VIRUS_DELAY_MS 20 +#define GAME_VIRUS_TIMER PLAY_TICKS_TIMER1 + +#define GAME_TV_DELAY_MS 3000 +#define GAME_TV_TIMER PLAY_TICKS_TIMER3 + +#define GAME_KBD_DELAY_MS 20 +#define GAME_KBD_TIMER PLAY_TICKS_TIMER4 + +#define GAME_OVER_DELAY_MS 1000 +#define GAME_OVER_TIMER PLAY_TICKS_TIMER2 + +#define GAME_BABY_SCALE (v3f_t){.05f, .05f, .05f} +#define GAME_ADULT_SCALE (v3f_t){.07f, .07f, .07f} +#define GAME_TV_SCALE (v3f_t){.15f, .15f, .15f} +#define GAME_VIRUS_SCALE (v3f_t){.05f, .05f, .05f} +#define GAME_DIGITS_SCALE (v3f_t){.05f, .05f, .05f} + +/* every entity just starts with a unit cube AABB and is transformed with a matrix into its position, + * so here's a convenient aabb to feed into those transformations as needed. + */ +static const bb3f_t any_aabb = { .min = { -1.f -1.f, -1.f}, .max = { 1.f, 1.f, 1.f } }; + +typedef enum game_state_t { + GAME_STATE_PLAYING, + GAME_STATE_OVER, + GAME_STATE_OVER_DELAY, + GAME_STATE_OVER_WAITING +} game_state_t; + +typedef enum entity_type_t { + ENTITY_TYPE_BABY, + ENTITY_TYPE_ADULT, + ENTITY_TYPE_VIRUS, + ENTITY_TYPE_TV, +} entity_type_t; + +typedef union entity_t entity_t; + +typedef struct entity_any_t { + entity_type_t type; + stage_t *node; + ix2_object_t *ix2_object; + v2f_t position; + v3f_t scale; + m4f_t model_x; + bb2f_t aabb_x; +} entity_any_t; + +typedef struct baby_t { + entity_any_t entity; +} baby_t; + +typedef struct virus_t { + entity_any_t entity; +} virus_t; + +typedef struct adult_t { + entity_any_t entity; + unsigned rescues; + unsigned frozen:1; + entity_t *holding; +} adult_t; + +typedef struct tv_t { + entity_any_t entity; +} tv_t; + +union entity_t { + entity_any_t any; + baby_t baby; + virus_t virus; + adult_t adult; + tv_t tv; +}; + + +typedef struct game_t { + game_state_t state; + + stage_t *stage; + stage_t *game_node; + stage_t *babies_node; + stage_t *viruses_node; + stage_t *plasma_node; + stage_t *score_node; + ix2_t *ix2; + pad_t *pad; + + adult_t *adult; + tv_t *tv; + virus_t *viruses[GAME_NUM_VIRUSES]; + m4f_t score_digits_x[10]; +} game_t; + + +static inline float randf(void) +{ + return 2.f / RAND_MAX * rand() - 1.f; +} + + +/* update the entity's transformation and position in the index */ +static void entity_update_x(game_t *game, entity_any_t *entity) +{ + + entity->model_x = m4f_identity(); + entity->model_x = m4f_translate(&entity->model_x, &(v3f_t){entity->position.x, entity->position.y, 0.f}); + entity->model_x = m4f_scale(&entity->model_x, &entity->scale); + + /* apply the entities transform to any_aabb to get the current transformed aabb, cache it in the + * entity in case a search needs to be done... */ + m4f_mult_bb3f_bb2f(&entity->model_x, &any_aabb, &entity->aabb_x); + + if (!(entity->ix2_object)) { + entity->ix2_object = ix2_object_new(game->ix2, NULL, NULL, &entity->aabb_x, entity); + } else { + entity->ix2_object = ix2_object_move(game->ix2, entity->ix2_object, NULL, NULL, &entity->aabb_x); + } + + fatal_if(!entity->ix2_object, "Unable to update ix2 object"); +} + + +/* this is unnecessary copy and paste junk, but I'm really falling asleep here + * and need to get shit working before I pass out. + */ + +static adult_t * adult_new(game_t *game, stage_t *parent) +{ + adult_t *adult; + + adult = pad_get(game->pad, sizeof(entity_t)); + fatal_if(!adult, "unale to allocate adult_t"); + + adult->entity.type = ENTITY_TYPE_ADULT; + adult->entity.node = adult_node_new(&(stage_conf_t){ .parent = parent, .name = "adult", .layer = 3, .alpha = 1.f }, &adult->entity.model_x); + adult->entity.scale = GAME_ADULT_SCALE; + entity_update_x(game, &adult->entity); + + return adult; +} + + + +static baby_t * baby_new(game_t *game, stage_t *parent) +{ + baby_t *baby; + + baby = pad_get(game->pad, sizeof(entity_t)); + fatal_if(!baby, "unale to allocate baby_t"); + + baby->entity.type = ENTITY_TYPE_BABY; + baby->entity.node = baby_node_new(&(stage_conf_t){ .parent = parent, .name = "baby", .active = 1, .alpha = 1.f }, &baby->entity.model_x); + baby->entity.scale = GAME_BABY_SCALE; + baby->entity.position.x = randf(); + baby->entity.position.y = randf(); + entity_update_x(game, &baby->entity); + + return baby; +} + + +static tv_t * tv_new(game_t *game, stage_t *parent) +{ + tv_t *tv; + + tv = pad_get(game->pad, sizeof(entity_t)); + fatal_if(!tv, "unale to allocate tv_t"); + + tv->entity.type = ENTITY_TYPE_TV; + tv->entity.node = tv_node_new(&(stage_conf_t){ .parent = parent, .name = "tv", .layer = 1, .alpha = 1.f }, &tv->entity.model_x); + tv->entity.scale = GAME_TV_SCALE; + entity_update_x(game, &tv->entity); + + return tv; +} + + +static void randomize_virus(virus_t *virus) +{ + virus->entity.position.y = randf(); + virus->entity.position.x = randf(); +} + + +static virus_t * virus_new(game_t *game, stage_t *parent) +{ + virus_t *virus; + + virus = pad_get(game->pad, sizeof(entity_t)); + fatal_if(!virus, "unale to allocate virus_t"); + + virus->entity.type = ENTITY_TYPE_VIRUS; + virus->entity.node = virus_node_new(&(stage_conf_t){ .parent = parent, .name = "virus", .alpha = 1.f }, &virus->entity.model_x); + virus->entity.scale = GAME_VIRUS_SCALE; + randomize_virus(virus); + entity_update_x(game, &virus->entity); + + return virus; +} + + +static void reset_virus(virus_t *virus) +{ + stage_set_active(virus->entity.node, 0); + randomize_virus(virus); +} + + +typedef struct virus_search_t { + game_t *game; + virus_t *virus; +} virus_search_t; + +static ix2_search_status_t virus_search(void *cb_context, ix2_object_t *ix2_object, v2f_t *ix2_object_position, bb2f_t *ix2_object_aabb, void *object) +{ + virus_search_t *search = cb_context; + entity_t *entity = object; + + switch (entity->any.type) { + case ENTITY_TYPE_BABY: + /* convert baby into inanimate virus (off the viruses array) */ + stage_free(entity->any.node); + entity->any.node = virus_node_new(&(stage_conf_t){ .parent = search->game->viruses_node, .name = "baby-virus", .active = 1, .alpha = 1.f }, &entity->any.model_x); + sfx_play(sfx.baby_infected); + entity->any.type = ENTITY_TYPE_VIRUS; + + /* add new baby */ + (void) baby_new(search->game, search->game->babies_node); + + /* reset virus */ + reset_virus(search->virus); + + /* stop searching */ + return IX2_SEARCH_STOP_HIT; + + case ENTITY_TYPE_ADULT: + /* convert adult into inanimate virus (of the viruses array) */ + stage_free(entity->any.node); + entity->any.node = virus_node_new(&(stage_conf_t){ .parent = search->game->viruses_node, .name = "adult-virus", .active = 1, .alpha = 1.f }, &entity->any.model_x); + sfx_play(sfx.adult_infected); + search->game->state = GAME_STATE_OVER; + return IX2_SEARCH_STOP_HIT; + + case ENTITY_TYPE_VIRUS: + case ENTITY_TYPE_TV: + return IX2_SEARCH_MORE_MISS; + + default: + assert(0); + } + + /* XXX: I'm not really caring about the return value for now */ +} + + +/* animate the viruses: + * - anything newly infected becomes an inanimate virus (change their node) + * and the virus respawns somewhere + * - if the newly infected thing is the adult, the game ends + */ +static void update_viruses(play_t *play, game_t *game) +{ + virus_search_t search = { .game = game }; + + assert(play); + assert(game); + + if (randf() > .95f && !stage_get_active(game->tv->entity.node)) { + /* sometimes turn on the TV at a random location, we + * get stuck to it */ + play_ticks_reset(play, GAME_TV_TIMER); + game->tv->entity.position.x = randf(); + game->tv->entity.position.y = randf(); + entity_update_x(game, &game->tv->entity); + stage_set_active(game->tv->entity.node, 1); + } + + for (int i = 0; i < NELEMS(game->viruses); i++) { + virus_t *virus = game->viruses[i]; + + /* are they off-screen? */ + if (virus->entity.position.y > 1.2f) { + + if (stage_get_active(virus->entity.node)) { + /* active and off-screen gets randomize and inactivated */ + reset_virus(virus); + } else { + /* inactive and off-screen gets activated and moved to the + * top */ + stage_set_active(virus->entity.node, 1); + virus->entity.position.y = -1.2f; + } + } + + virus->entity.position.y += GAME_VIRUS_SPEED; + entity_update_x(game, &virus->entity); + + if (stage_get_active(virus->entity.node)) { + search.virus = virus; + + /* search ix2 for collisions */ + ix2_search_by_aabb(game->ix2, NULL, NULL, &virus->entity.aabb_x, virus_search, &search); + } + } +} + + +static ix2_search_status_t adult_search(void *cb_context, ix2_object_t *ix2_object, v2f_t *ix2_object_position, bb2f_t *ix2_object_aabb, void *object) +{ + game_t *game = cb_context; + entity_t *entity = object; + + switch (entity->any.type) { + case ENTITY_TYPE_BABY: + if (!game->adult->holding) { + sfx_play(sfx.baby_held); + game->adult->holding = entity; + } + + /* we should probably keep looking because there could be a virus too, + * but fuck it, these types of bugs are fun in silly games. + */ + return IX2_SEARCH_STOP_HIT; + + case ENTITY_TYPE_ADULT: + /* ignore ourselves */ + return IX2_SEARCH_MORE_MISS; + + case ENTITY_TYPE_VIRUS: + if (!stage_get_active(entity->any.node)) + return IX2_SEARCH_MORE_MISS; + + /* convert adult into inanimate adult (of the adultes array) */ + stage_free(game->adult->entity.node); + game->adult->entity.node = virus_node_new(&(stage_conf_t){ .parent = game->viruses_node, .name = "adult-virus", .active = 1, .alpha = 1.f }, &game->adult->entity.model_x); + sfx_play(sfx.adult_infected); + + if (game->adult->holding) { + stage_free(game->adult->holding->any.node); + game->adult->holding->any.node = virus_node_new(&(stage_conf_t){ .parent = game->viruses_node, .name = "baby-virus", .active = 1, .alpha = 1.f }, &game->adult->holding->any.model_x); + sfx_play(sfx.baby_infected); + } + game->state = GAME_STATE_OVER; + return IX2_SEARCH_STOP_HIT; + + case ENTITY_TYPE_TV: + if (!stage_get_active(entity->any.node)) + return IX2_SEARCH_MORE_MISS; + + game->adult->frozen = 1; + return IX2_SEARCH_STOP_HIT; + + default: + assert(0); + } + + /* XXX: I'm not really caring about the return value for now */ +} + + +static void game_move_adult(game_t *game, v2f_t *dir) +{ + assert(game); + assert(dir); + + if (game->adult->frozen) + return; + + game->adult->entity.position.x += dir->x; + game->adult->entity.position.y += dir->y; + + /* prevent the player from going too far off the reservation */ + if (game->adult->entity.position.x > 1.1f) + game->adult->entity.position.x = 1.1f; + + if (game->adult->entity.position.x < -1.1f) + game->adult->entity.position.x = -1.1f; + + if (game->adult->entity.position.y > 1.1f) + game->adult->entity.position.y = 1.1f; + + if (game->adult->entity.position.y < -1.1f) + game->adult->entity.position.y = -1.1f; + + entity_update_x(game, &game->adult->entity); + + if (game->adult->holding) { + game->adult->holding->any.position = game->adult->entity.position; + entity_update_x(game, &game->adult->holding->any); + + if (game->adult->entity.position.x > 1.05f || + game->adult->entity.position.x < -1.05f || + game->adult->entity.position.y > 1.05f || + game->adult->entity.position.y < -1.05f) { + + /* rescued baby */ + sfx_play(sfx.baby_rescued); + + game->adult->holding->any.position.x = randf(); + game->adult->holding->any.position.y = randf(); + entity_update_x(game, &game->adult->holding->any); + + game->adult->holding = NULL; + game->adult->rescues++; + } + } + + /* search ix2 for collisions */ + ix2_search_by_aabb(game->ix2, NULL, NULL, &game->adult->entity.aabb_x, adult_search, game); +} + + +static void reset_game(game_t *game) +{ + ix2_reset(game->ix2); + stage_free(game->game_node); + + if (game->pad) /* XXX FIXME: this is a stupidty in libpad */ + pad_free(game->pad); + + game->game_node = stage_new(&(stage_conf_t){ .parent = game->stage, .name = "game", .active = 1, .alpha = 1.f }, NULL, NULL); + + game->babies_node = stage_new(&(stage_conf_t){ .parent = game->game_node, .name = "babies", .layer = 4, .alpha = 1.f }, NULL, NULL); + game->viruses_node = stage_new(&(stage_conf_t){ .parent = game->game_node, .name = "viruses", .layer = 5, .alpha = 1.f }, NULL, NULL); + game->score_node = stage_new(&(stage_conf_t){ .parent = game->game_node, .name = "score", .layer = 7, .alpha = 1 }, NULL, NULL); + + game->pad = pad_new(sizeof(entity_t) * 32, PAD_FLAGS_ZERO); + + game->tv = tv_new(game, game->game_node); + game->adult = adult_new(game, game->game_node); + for (int i = 0; i < NELEMS(game->viruses); i++) + game->viruses[i] = virus_new(game, game->viruses_node); + + for (int i = 0; i < GAME_NUM_BABIES; i++) + (void) baby_new(game, game->babies_node); + + stage_set_active(game->adult->entity.node, 1); + stage_set_active(game->babies_node, 1); + stage_set_active(game->viruses_node, 1); + + game->state = GAME_STATE_PLAYING; +} + + +static void show_score(game_t *game) +{ + unsigned score = game->adult->rescues * 420; + + for (unsigned i = 1000000000, pos = 0; i > 0; score %= i, i /= 10, pos++) { + unsigned v = score / i; + + digit_node_new(&(stage_conf_t){ .parent = game->score_node, .name = "score-digit", .active = 1, .alpha = 1.f }, v, &game->score_digits_x[pos]); + } + + stage_set_active(game->score_node, 1); +} + + +static void * game_init(play_t *play, int argc, char *argv[]) +{ + sars_t *sars = play_context(play, SARS_CONTEXT_SARS); + game_t *game; + + assert(sars); + + game = calloc(1, sizeof(game_t)); + fatal_if(!game, "Unable to allocate game_t"); + + game->stage = sars->stage; + game->plasma_node = plasma_node_new(&(stage_conf_t){ .parent = sars->stage, .name = "plasma", .alpha = 1 }); + + game->ix2 = ix2_new(NULL, 4, 4, 1 /* increase for nested searches */); + + /* setup transformation matrices for the score digits, this is really fast and nasty hack because + * I am completely delerious and ready to fall asleep. + */ + for (int i = 0; i < NELEMS(game->score_digits_x); i++) { + game->score_digits_x[i] = m4f_identity(); + game->score_digits_x[i] = m4f_translate(&game->score_digits_x[i], &(v3f_t){ 1.f / NELEMS(game->score_digits_x) * i - .5f, 0.f, 0.f }); + game->score_digits_x[i] = m4f_scale(&game->score_digits_x[i], &GAME_DIGITS_SCALE); + } + + sfx_init(); + + return game; +} + + +static void game_enter(play_t *play, void *context) +{ + game_t *game = context; + + assert(game); + + play_music_set(play, PLAY_MUSIC_FLAG_LOOP|PLAY_MUSIC_FLAG_IDEMPOTENT, "assets/game.ogg"); + play_ticks_reset(play, GAME_VIRUS_TIMER); + stage_set_active(game->plasma_node, 1); + reset_game(game); +} + + +static void game_update(play_t *play, void *context) +{ + sars_t *sars = play_context(play, SARS_CONTEXT_SARS); + game_t *game = context; + + assert(game); + assert(sars); + + switch (game->state) { + case GAME_STATE_PLAYING: { + if (play_ticks_elapsed(play, GAME_VIRUS_TIMER, GAME_VIRUS_DELAY_MS)) + update_viruses(play, game); + + if (play_ticks_elapsed(play, GAME_KBD_TIMER, GAME_KBD_DELAY_MS)) { + const Uint8 *key_state = SDL_GetKeyboardState(NULL); + v2f_t dir = {}, *move = NULL; + + if (key_state[SDL_SCANCODE_LEFT] || key_state[SDL_SCANCODE_A]) { + dir.x += -GAME_ADULT_SPEED; + move = &dir; + } + + if (key_state[SDL_SCANCODE_RIGHT] || key_state[SDL_SCANCODE_D]) { + dir.x += GAME_ADULT_SPEED; + move = &dir; + } + + if (key_state[SDL_SCANCODE_UP] || key_state[SDL_SCANCODE_W]) { + dir.y += GAME_ADULT_SPEED; + move = &dir; + } + + if (key_state[SDL_SCANCODE_DOWN] || key_state[SDL_SCANCODE_S]) { + dir.y += -GAME_ADULT_SPEED; + move = &dir; + } + + if (move) + game_move_adult(game, move); + } + + if (play_ticks_elapsed(play, GAME_TV_TIMER, GAME_TV_DELAY_MS)) { + stage_set_active(game->tv->entity.node, 0); + game->adult->frozen = 0; + } + + break; + } + + case GAME_STATE_OVER: + show_score(game); + play_ticks_reset(play, GAME_OVER_TIMER); + game->state = GAME_STATE_OVER_DELAY; + break; + + case GAME_STATE_OVER_DELAY: + if (!play_ticks_elapsed(play, GAME_OVER_TIMER, GAME_OVER_DELAY_MS)) + break; + + /* maybe throw something on-screen? */ + game->state = GAME_STATE_OVER_WAITING; + break; + + case GAME_STATE_OVER_WAITING: + /* just do nothing and wait for a keypress of some kind */ + break; + + default: + assert(0); + } + + /* just always dirty the stage in the game context */ + stage_dirty(sars->stage); +} + + +static void game_dispatch(play_t *play, void *context, SDL_Event *event) +{ + game_t *game = context; + + /* global handlers */ + sars_dispatch(play, context, event); + + /* anything more to do here? */ + switch (event->type) { + case SDL_KEYDOWN: + if (event->key.keysym.sym == SDLK_ESCAPE) + exit(0); + + if (game->state == GAME_STATE_OVER_WAITING) { + reset_game(game); + break; + } + + break; + + default: + break; + } +} + + +const play_ops_t game_ops = { + .init = game_init, +// .shutdown = game_shutdown, + .update = game_update, + .render = sars_render, + .dispatch = game_dispatch, + .enter = game_enter, +}; diff --git a/src/hungrycat-node.c b/src/hungrycat-node.c new file mode 100644 index 0000000..da78ed9 --- /dev/null +++ b/src/hungrycat-node.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stage.h> + +#include "gfx/gfx-hungrycat.h" +#include "hungrycat-node.h" +#include "tex-node.h" + + +stage_t * hungrycat_node_new(stage_conf_t *conf, m4f_t *model_x) +{ + return tex_node_new_mem(conf, gfx_hungrycat.width, gfx_hungrycat.height, gfx_hungrycat.pixel_data, model_x); +} diff --git a/src/hungrycat-node.h b/src/hungrycat-node.h new file mode 100644 index 0000000..8c5839f --- /dev/null +++ b/src/hungrycat-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _HUNGRYCAT_NODE_H +#define _HUNGRYCAT_NODE_H + +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +stage_t * hungrycat_node_new(stage_conf_t *conf, m4f_t *model_x); + +#endif diff --git a/src/hungrycat.c b/src/hungrycat.c new file mode 100644 index 0000000..bae9c10 --- /dev/null +++ b/src/hungrycat.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <SDL.h> +#include <assert.h> + +#include <play.h> +#include <stage.h> + +#include "glad.h" +#include "hungrycat-node.h" +#include "m4f.h" +#include "m4f-3dx.h" +#include "macros.h" +#include "sars.h" + +#define HUNGRYCAT_FADE_MS 3333 + +#define HUNGRYCAT_FADEIN_MS HUNGRYCAT_FADE_MS +#define HUNGRYCAT_SHOW_MS HUNGRYCAT_FADE_MS +#define HUNGRYCAT_FADEOUT_MS HUNGRYCAT_FADE_MS + +#define HUNGRYCAT_FADE_TIMER PLAY_TICKS_TIMER0 + +typedef enum hungrycat_state_t { + HUNGRYCAT_STATE_FADEIN, + HUNGRYCAT_STATE_SHOW, + HUNGRYCAT_STATE_FADEOUT, + HUNGRYCAT_STATE_LEAVE, +} hungrycat_state_t; + +typedef struct hungrycat_t { + hungrycat_state_t state; + stage_t *node; + m4f_t model_x; +} hungrycat_t; + + +static void * hungrycat_init(play_t *play, int argc, char *argv[]) +{ + sars_t *sars = play_context(play, SARS_CONTEXT_SARS); + hungrycat_t *hungrycat; + + hungrycat = calloc(1, sizeof(hungrycat_t)); + fatal_if(!hungrycat, "Unable to allocate hungrycat_t"); + + hungrycat->node = hungrycat_node_new(&(stage_conf_t){ .parent = sars->stage, .name = "hungrycat", .active = 1 }, &hungrycat->model_x); + fatal_if(!hungrycat->node, "Unable to create hungrycat->node"); + + hungrycat->model_x = m4f_identity(); + hungrycat->model_x = m4f_scale(&hungrycat->model_x, &(v3f_t){1.f, .25f, .5f}); + + return hungrycat; +} + + +static void hungrycat_enter(play_t *play, void *context) +{ + hungrycat_t *hungrycat = context; + + assert(hungrycat); + + play_music_set(play, 0, "assets/hungrycat.ogg"); + play_ticks_reset(play, HUNGRYCAT_FADE_TIMER); +} + + +static float fade_t(play_t *play) +{ + return (1.f / (float)HUNGRYCAT_FADE_MS) * (float)play_ticks(play, HUNGRYCAT_FADE_TIMER); +} + + +static void hungrycat_update(play_t *play, void *context) +{ + hungrycat_t *hungrycat = context; + + assert(hungrycat); + + switch (hungrycat->state) { + case HUNGRYCAT_STATE_FADEIN: + stage_dirty(hungrycat->node); + if (!play_ticks_elapsed(play, HUNGRYCAT_FADE_TIMER, HUNGRYCAT_FADE_MS)) { + stage_set_alpha(hungrycat->node, fade_t(play)); + break; + } + + stage_set_alpha(hungrycat->node, 1.f); + hungrycat->state = HUNGRYCAT_STATE_SHOW; + break; + + case HUNGRYCAT_STATE_SHOW: + if (!play_ticks_elapsed(play, HUNGRYCAT_FADE_TIMER, HUNGRYCAT_FADE_MS)) + break; + + hungrycat->state = HUNGRYCAT_STATE_FADEOUT; + break; + + case HUNGRYCAT_STATE_FADEOUT: + stage_dirty(hungrycat->node); + if (!play_ticks_elapsed(play, HUNGRYCAT_FADE_TIMER, HUNGRYCAT_FADE_MS)) { + stage_set_alpha(hungrycat->node, 1.f - fade_t(play)); + break; + } + + stage_set_alpha(hungrycat->node, 0.f); + hungrycat->state = HUNGRYCAT_STATE_LEAVE; + break; + + case HUNGRYCAT_STATE_LEAVE: + play_context_enter(play, SARS_CONTEXT_GAME); + break; + + default: + assert(0); + } +} + + +static void hungrycat_leave(play_t *play, void *context) +{ + hungrycat_t *hungrycat = context; + + assert(hungrycat); + + /* we never reenter this context since it's just a splash, so + * the context leave is effectively the shutdown, cleanup. + */ + stage_free(hungrycat->node); + free(hungrycat); + /* XXX: this is icky though, play_context(SARS_CONTEXT_HUNGRYCAT) will + * return a dangling pointer! Not that it occurs anywhere though. + */ +} + + +const play_ops_t hungrycat_ops = { + .init = hungrycat_init, + .update = hungrycat_update, + .render = sars_render, + .dispatch = sars_dispatch, + .enter = hungrycat_enter, + .leave = hungrycat_leave, +}; diff --git a/src/m4f-3dx.h b/src/m4f-3dx.h new file mode 100644 index 0000000..384641b --- /dev/null +++ b/src/m4f-3dx.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _M4F_3DX_H +#define _M4F_3DX_H + +#include <math.h> + +#include "m4f.h" +#include "v3f.h" + +/* helpers for manipulating 3D transformation matrices */ + +/* XXX: note this is column-major */ + +/* adjust the matrix m to translate by v, returning the resulting matrix */ +/* if m is NULL the identity vector is assumed */ +static inline m4f_t m4f_translate(const m4f_t *m, const v3f_t *v) +{ + m4f_t identity = m4f_identity(); + m4f_t translate = m4f_identity(); + + if (!m) + m = &identity; + + translate.m[3][0] = v->x; + translate.m[3][1] = v->y; + translate.m[3][2] = v->z; + + return m4f_mult(m, &translate); +} + + +/* adjust the matrix m to scale by v, returning the resulting matrix */ +/* if m is NULL the identity vector is assumed */ +static inline m4f_t m4f_scale(const m4f_t *m, const v3f_t *v) +{ + m4f_t identity = m4f_identity(); + m4f_t scale = {}; + + if (!m) + m = &identity; + + scale.m[0][0] = v->x; + scale.m[1][1] = v->y; + scale.m[2][2] = v->z; + scale.m[3][3] = 1.f; + + return m4f_mult(m, &scale); +} + + +/* adjust the matrix m to rotate around the specified axis by radians, returning the resulting matrix */ +/* axis is expected to be a unit vector */ +/* if m is NULL the identity vector is assumed */ +static inline m4f_t m4f_rotate(const m4f_t *m, const v3f_t *axis, float radians) +{ + m4f_t identity = m4f_identity(); + float cos_r = cosf(radians); + float sin_r = sinf(radians); + m4f_t rotate; + + if (!m) + m = &identity; + + rotate.m[0][0] = cos_r + axis->x * axis->x * (1.f - cos_r); + rotate.m[0][1] = axis->y * axis->x * (1.f - cos_r) + axis->z * sin_r; + rotate.m[0][2] = axis->z * axis->x * (1.f - cos_r) - axis->y * sin_r; + rotate.m[0][3] = 0.f; + + rotate.m[1][0] = axis->x * axis->y * (1.f - cos_r) - axis->z * sin_r; + rotate.m[1][1] = cos_r + axis->y * axis->y * (1.f - cos_r); + rotate.m[1][2] = axis->z * axis->y * (1.f - cos_r) + axis->x * sin_r; + rotate.m[1][3] = 0.f; + + rotate.m[2][0] = axis->x * axis->z * (1.f - cos_r) + axis->y * sin_r; + rotate.m[2][1] = axis->y * axis->z * (1.f - cos_r) - axis->x * sin_r; + rotate.m[2][2] = cos_r + axis->z * axis->z * (1.f - cos_r); + rotate.m[2][3] = 0.f; + + rotate.m[3][0] = 0.f; + rotate.m[3][1] = 0.f; + rotate.m[3][2] = 0.f; + rotate.m[3][3] = 1.f; + + return m4f_mult(m, &rotate); +} + + +/* this is a simple perpsective projection matrix taken from an opengl tutorial */ +static inline m4f_t m4f_frustum(float bot, float top, float left, float right, float nnear, float ffar) +{ + m4f_t m = {}; + + m.m[0][0] = 2 * nnear / (right - left); + + m.m[1][1] = 2 * nnear / (top - bot); + + m.m[2][0] = (right + left) / (right - left);; + m.m[2][1] = (top + bot) / (top - bot); + m.m[2][2] = -(ffar + nnear) / (ffar - nnear); + m.m[2][3] = -1; + + m.m[3][2] = -2 * ffar * nnear / (ffar - nnear); + + return m; +} + +#endif diff --git a/src/m4f-bbx.h b/src/m4f-bbx.h new file mode 100644 index 0000000..bcae848 --- /dev/null +++ b/src/m4f-bbx.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _M4F_BBX_H +#define _M4F_BBX_H + +#include <float.h> + +#include "bb2f.h" +#include "bb3f.h" +#include "m4f.h" +#include "macros.h" + + +/* multiply a matrix and bb3f */ +static bb3f_t * m4f_mult_bb3f(const m4f_t *transform, const bb3f_t *aabb, bb3f_t *res_aabb) +{ + v3f_t box[8] = { + { aabb->min.x, aabb->min.y, aabb->min.z }, + { aabb->min.x, aabb->min.y, aabb->max.z }, + { aabb->min.x, aabb->max.y, aabb->max.z }, + { aabb->min.x, aabb->max.y, aabb->min.z }, + { aabb->max.x, aabb->min.y, aabb->min.z }, + { aabb->max.x, aabb->min.y, aabb->max.z }, + { aabb->max.x, aabb->max.y, aabb->max.z }, + { aabb->max.x, aabb->max.y, aabb->min.z }, + }; + + bb3f_t _aabb = { + .min = { FLT_MAX, FLT_MAX, FLT_MAX }, + .max = { -FLT_MAX, -FLT_MAX, -FLT_MAX}, + }; + + for (int i = 0; i < NELEMS(box); i++) { + v3f_t X = m4f_mult_v3f(transform, &box[i]); + + if (_aabb.min.x > X.x) + _aabb.min.x = X.x; + if (_aabb.max.x < X.x) + _aabb.max.x = X.x; + + if (_aabb.min.y > X.y) + _aabb.min.y = X.y; + if (_aabb.max.y < X.y) + _aabb.max.y = X.y; + + if (_aabb.min.z > X.z) + _aabb.min.z = X.z; + if (_aabb.max.z < X.z) + _aabb.max.z = X.z; + } + + *res_aabb = _aabb; + + return res_aabb; +} + + +/* multiply a matrix and a bb3f but return only the x,y components in a bb2f */ +/* Caller must supply the storage in res_aabb, for convenience this is also returned. */ +static bb2f_t * m4f_mult_bb3f_bb2f(const m4f_t *transform, const bb3f_t *aabb, bb2f_t *res_aabb) +{ + bb3f_t _aabb; + + m4f_mult_bb3f(transform, aabb, &_aabb); + + res_aabb->min.x = _aabb.min.x; + res_aabb->min.y = _aabb.min.y; + res_aabb->max.x = _aabb.max.x; + res_aabb->max.y = _aabb.max.y; + + return res_aabb; +} + + +#endif diff --git a/src/plasma-node.c b/src/plasma-node.c new file mode 100644 index 0000000..1bc0e40 --- /dev/null +++ b/src/plasma-node.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <SDL.h> + +#include <play.h> +#include <stage.h> + +#include "glad.h" +#include "plasma-node.h" +#include "shader-node.h" + + +static const char *plasma_vs = "" + "#version 120\n" + + "attribute vec3 vertex;" + "attribute vec2 texcoord;" + + "void main()" + "{" + " gl_TexCoord[0].xy = texcoord;" + //" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;" + " gl_Position = vec4(vertex, 1.f);" + "}" +""; + + +// derived from https://www.bidouille.org/prog/plasma +static const char *plasma_fs = "" + "#version 120\n" + + "#define PI 3.1415926535897932384626433832795\n" + + "uniform float alpha;" + "uniform float time;" + + "void main() {" + " float v;" + " float stime = sin(time * .01f) * 100.f;" + + " vec2 c = gl_TexCoord[0].st;" + + // this zooms the texture coords in and out a bit with time + " c *= (sin(stime * .01f) *.5f + .5f) * 3.f + 1.f;" + + // plasma calculations, stime instead of time directly to vary directions and speed + " v = sin((c.x + stime));" + " v += sin((c.y + stime) * .5f);" + " v += sin((c.x + c.y +stime) * .5f);" + + " c += vec2(sin(stime * .33f), cos(stime * .5f)) * 3.f;" + + " v += sin(sqrt(c.x * c.x + c.y * c.y + 1.f) + stime);" + + " vec3 col = vec3(cos(PI * v + sin(time)), sin(PI * v + cos(time * .33f)), cos(PI * v + sin(time * .66f)));" + " gl_FragColor = vec4(col * .5f + .5f, alpha);" + "}" +""; + + +static void plasma_uniforms(void *uniforms_ctxt, void *render_ctxt, unsigned n_uniforms, const int *uniforms, const m4f_t *model_x, float alpha) +{ + play_t *play = render_ctxt; + + glUniform1f(uniforms[0], alpha); + glUniform1f(uniforms[1], play_ticks(play, PLAY_TICKS_TIMER0) * .001f); // FIXME KLUDGE ALERT +} + + +/* create plasma rendering stage */ +stage_t * plasma_node_new(const stage_conf_t *conf) +{ + return shader_node_new_src(conf, plasma_vs, plasma_fs, NULL, plasma_uniforms, NULL, 2, + (const char *[]){ + "alpha", + "time", + } + ); +} diff --git a/src/plasma-node.h b/src/plasma-node.h new file mode 100644 index 0000000..1607ece --- /dev/null +++ b/src/plasma-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _PLASMA_NODE_H +#define _PLASMA_NODE_H + +typedef struct stage_t stage_t; +typedef struct stage_conf_t stage_conf_t; + +stage_t * plasma_node_new(const stage_conf_t *conf); + +#endif diff --git a/src/sfx.c b/src/sfx.c new file mode 100644 index 0000000..54a8bce --- /dev/null +++ b/src/sfx.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <SDL_mixer.h> + +#include "sfx.h" + +sfx_t sfx; + + +void sfx_init(void) +{ + sfx.baby_infected = Mix_LoadWAV("assets/baby-infected.wav"); + sfx.baby_held = Mix_LoadWAV("assets/baby-held.wav"); + sfx.baby_rescued = Mix_LoadWAV("assets/baby-rescued.wav"); + sfx.adult_infected = Mix_LoadWAV("assets/adult-infected.wav"); +} + + +void sfx_play(Mix_Chunk *chunk) +{ + if (chunk) + Mix_PlayChannel(-1, chunk, 0); +} diff --git a/src/sfx.h b/src/sfx.h new file mode 100644 index 0000000..a2f3214 --- /dev/null +++ b/src/sfx.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _SFX_H +#define _SFX_H + +#include <SDL_mixer.h> + +typedef struct sfx_t { + Mix_Chunk *baby_infected; + Mix_Chunk *baby_held; + Mix_Chunk *baby_rescued; + Mix_Chunk *adult_infected; +} sfx_t; + +extern sfx_t sfx; + +void sfx_init(void); +void sfx_play(Mix_Chunk *chunk); + +#endif diff --git a/src/shader-node.c b/src/shader-node.c new file mode 100644 index 0000000..5a21c89 --- /dev/null +++ b/src/shader-node.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> + +#include <stage.h> + +#include "glad.h" +#include "m4f.h" +#include "macros.h" +#include "shader.h" +#include "shader-node.h" +#include "v2f.h" + +typedef struct shader_node_t { + shader_t *shader; + shader_node_uniforms_func_t *uniforms_func; + void *uniforms_ctxt; + const m4f_t *transform; +} shader_node_t; + +static unsigned vbo, tcbo; + +static const float vertices[] = { + +1.f, +1.f, 0.f, + +1.f, -1.f, 0.f, + -1.f, +1.f, 0.f, + +1.f, -1.f, 0.f, + -1.f, -1.f, 0.f, + -1.f, +1.f, 0.f, +}; + + +/* TODO: verify that this is OK, I recall tutorials stating these are always + * in the range 0-1, but it seems perfectly OK to use -1..+1 which is more + * convenient here where these shader-textured quads appreciate being fed + * unit square coordinates with 0,0 @ the center. + */ +static const float texcoords[] = { + 1.f, 1.f, + 1.f, -1.f, + -1.f, 1.f, + 1.f, -1.f, + -1.f, -1.f, + -1.f, 1.f, +}; + + +static void shader_node_render(const stage_t *stage, void *object, float alpha, void *render_ctxt) +{ + shader_node_t *shader_node = object; + unsigned n_uniforms; + int *uniforms, *attributes; + + assert(stage); + assert(shader_node); + + shader_use(shader_node->shader, &n_uniforms, &uniforms, NULL, &attributes); + + if (shader_node->uniforms_func) + shader_node->uniforms_func(shader_node->uniforms_ctxt, render_ctxt, n_uniforms, uniforms, shader_node->transform, alpha); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glVertexAttribPointer(attributes[0], 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(attributes[0]); + + glBindBuffer(GL_ARRAY_BUFFER, tcbo); + glVertexAttribPointer(attributes[1], 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + glEnableVertexAttribArray(attributes[1]); + + /* XXX: this could be made optional, but since alpha is a constant throughout the stage + * integration I'm just always turning it on so stage_set_alpha() always works. There + * are definitely full-screen full-opaque shader node situations where the pointless + * performance hit sucks though, especially on older hardware. + */ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDrawArrays(GL_TRIANGLES, 0, 6); + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + + +static void shader_node_free(const stage_t *stage, void *object) +{ + shader_node_t *shader_node = object; + + assert(stage); + assert(shader_node); + + /* XXX FIXME: hmm, maybe the caller should supply a shader_t ** instead */ + (void) shader_free(shader_node->shader); + free(shader_node); +} + + +static const stage_ops_t shader_node_ops = { + .render_func = shader_node_render, + .free_func = shader_node_free, +}; + + +/* return a new shader stage node from an already compiled and linked shader program */ +stage_t * shader_node_new_shader(const stage_conf_t *conf, shader_t *shader, const m4f_t *transform, shader_node_uniforms_func_t *uniforms_func, void *uniforms_ctxt) +{ + shader_node_t *shader_node; + stage_t *stage; + + assert(conf); + assert(shader); + + if (!vbo) { + /* common to all shader nodes */ + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenBuffers(1, &tcbo); + glBindBuffer(GL_ARRAY_BUFFER, tcbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(texcoords), texcoords, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + shader_node = calloc(1, sizeof(shader_node_t)); + fatal_if(!shader_node, "Unable to allocate shader_node"); + + shader_ref(shader); + shader_node->shader = shader; + shader_node->uniforms_func = uniforms_func; + shader_node->uniforms_ctxt = uniforms_ctxt; + shader_node->transform = transform; + + stage = stage_new(conf, &shader_node_ops, shader_node); + fatal_if(!stage, "Unable to create stage \"%s\"", conf->name); + + return stage; +} + + +/* return a new shader stage node from source */ +stage_t * shader_node_new_src(const stage_conf_t *conf, const char *vs_src, const char *fs_src, const m4f_t *transform, shader_node_uniforms_func_t *uniforms_func, void *uniforms_ctxt, unsigned n_uniforms, const char **uniforms) +{ + stage_t *stage; + shader_t *shader; + + assert(vs_src); + assert(fs_src); + + shader = shader_pair_new(vs_src, fs_src, n_uniforms, uniforms, + 2, + (const char *[]) { + "vertex", + "texcoord", + }); + stage = shader_node_new_shader(conf, shader, transform, uniforms_func, uniforms_ctxt); + shader_free(shader); + + return stage; +} diff --git a/src/shader-node.h b/src/shader-node.h new file mode 100644 index 0000000..34fccc2 --- /dev/null +++ b/src/shader-node.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _SHADER_NODE_H +#define _SHADER_NODE_H + +typedef struct shader_t shader_t; +typedef struct stage_t stage_t; +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +typedef void (shader_node_uniforms_func_t)(void *uniforms_ctxt, void *render_ctxt, unsigned n_uniforms, const int *uniforms, const m4f_t *transform, float alpha); + +stage_t * shader_node_new_shader(const stage_conf_t *conf, shader_t *shader, const m4f_t *transform, shader_node_uniforms_func_t *uniforms_func, void *uniforms_ctxt); +stage_t * shader_node_new_src(const stage_conf_t *conf, const char *vs_src, const char *fs_src, const m4f_t *transform, shader_node_uniforms_func_t *uniforms_func, void *uniforms_ctxt, unsigned n_uniforms, const char **uniforms); + +#endif diff --git a/src/shader.c b/src/shader.c new file mode 100644 index 0000000..e54f9d5 --- /dev/null +++ b/src/shader.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> + +#include "glad.h" +#include "macros.h" +#include "shader.h" + + +typedef struct shader_t { + unsigned program, refcnt; + unsigned n_uniforms, n_attributes; + int *uniforms, *attributes; + + int locations[]; +} shader_t; + + +unsigned int shader_pair_new_bare(const char *vs_src, const char *fs_src) +{ + unsigned int vertex_shader, fragment_shader, shader; + int shader_success; + char shader_info[4096]; + + vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &vs_src, NULL); + glCompileShader(vertex_shader); + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &shader_success); + if (!shader_success) { + glGetShaderInfoLog(vertex_shader, sizeof(shader_info), NULL, shader_info); + fatal_if(1, "Error compiling vertex shader: \"%s\"", shader_info); + } + + fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fs_src, NULL); + glCompileShader(fragment_shader); + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &shader_success); + if (!shader_success) { + glGetShaderInfoLog(fragment_shader, sizeof(shader_info), NULL, shader_info); + fatal_if(1, "Error compiling fragment shader: \"%s\"", shader_info); + } + + shader = glCreateProgram(); + glAttachShader(shader, vertex_shader); + glAttachShader(shader, fragment_shader); + glLinkProgram(shader); + glGetProgramiv(shader, GL_LINK_STATUS, &shader_success); + if (!shader_success) { + glGetProgramInfoLog(shader, sizeof(shader_info), NULL, shader_info); + fatal_if(1, "Error linking shader program: \"%s\"", shader_info); + } + + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return shader; +} + + +shader_t * shader_pair_new(const char *vs_src, const char *fs_src, unsigned n_uniforms, const char **uniforms, unsigned n_attributes, const char **attributes) +{ + shader_t *shader; + + assert(vs_src); + assert(fs_src); + assert(uniforms || !n_uniforms); + assert(attributes || !n_attributes); + + shader = calloc(1, sizeof(shader_t) + (n_uniforms + n_attributes) * sizeof(int)); + fatal_if(!shader, "Unable to allocate shader"); + + shader->program = shader_pair_new_bare(vs_src, fs_src); + shader->refcnt++; + shader->n_uniforms = n_uniforms; + shader->n_attributes = n_attributes; + shader->uniforms = shader->locations; + shader->attributes = &shader->locations[n_uniforms]; + + for (unsigned i = 0; i < n_uniforms; i++) + shader->uniforms[i] = glGetUniformLocation(shader->program, uniforms[i]); + + for (unsigned i = 0; i < n_attributes; i++) + shader->attributes[i] = glGetAttribLocation(shader->program, attributes[i]); + + return shader; +} + + +void shader_ref(shader_t *shader) +{ + assert(shader); + + shader->refcnt++; +} + + +shader_t * shader_free(shader_t *shader) +{ + assert(shader); + + shader->refcnt--; + if (shader->refcnt > 0) + return shader; + + glDeleteProgram(shader->program); + free(shader); + + return NULL; +} + + +void shader_use(shader_t *shader, unsigned *res_n_uniforms, int **res_uniforms, unsigned *res_n_attributes, int **res_attributes) +{ + assert(shader); + + if (res_n_uniforms) + *res_n_uniforms = shader->n_uniforms; + + if (res_uniforms) + *res_uniforms = shader->uniforms; + + if (res_n_attributes) + *res_n_attributes = shader->n_attributes; + + if (res_attributes) + *res_attributes = shader->attributes; + + glUseProgram(shader->program); +} diff --git a/src/shader.h b/src/shader.h new file mode 100644 index 0000000..c0c5be5 --- /dev/null +++ b/src/shader.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _SHADER_H +#define _SHADER_H + +typedef struct shader_t shader_t; + +unsigned int shader_pair_new_bare(const char *vs_src, const char *fs_src); +shader_t * shader_pair_new(const char *vs_src, const char *fs_src, unsigned n_uniforms, const char **uniforms, unsigned n_attributes, const char **attributes); +void shader_ref(shader_t *shader); +shader_t * shader_free(shader_t *shader); +void shader_use(shader_t *shader, unsigned *res_n_uniforms, int **res_uniforms, unsigned *res_n_attributes, int **res_attributes); + +#endif diff --git a/src/tex-node.c b/src/tex-node.c new file mode 100644 index 0000000..6d049ca --- /dev/null +++ b/src/tex-node.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018-2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* TODO: this could totally be built upon shader-node.[ch], it would just supply + * shaders that do texturing and a uniform func that binds in the texture... + */ + +#include <assert.h> +#include <stdlib.h> + +#include <stage.h> + +#include "glad.h" +#include "m4f.h" +#include "macros.h" +#include "shader.h" +#include "tex-node.h" +#include "tex.h" + +typedef struct tex_node_t { + tex_t *tex; + m4f_t *model_x; +} tex_node_t; + + +/* Render simply renders a texd texture onto the screen */ +static void tex_node_render(const stage_t *stage, void *object, float alpha, void *render_ctxt) +{ + tex_node_t *tex_node = object; + + assert(stage); + assert(tex_node); + + tex_render(tex_node->tex, alpha, tex_node->model_x); +} + + +static void tex_node_free(const stage_t *stage, void *object) +{ + tex_node_t *tex_node = object; + + assert(stage); + assert(tex_node); + + tex_free(tex_node->tex); +} + +static const stage_ops_t tex_node_ops = { + .render_func = tex_node_render, + .free_func = tex_node_free, +}; + + +/* retun a tex node from a reusable refcounted tex instance */ +stage_t * tex_node_new_tex(stage_conf_t *conf, tex_t *tex, m4f_t *model_x) +{ + tex_node_t *tex_node; + stage_t *s; + + assert(conf); + + tex_node = calloc(1, sizeof(tex_node_t)); + fatal_if(!tex_node, "Unable to allocate tex_node \"%s\"", conf->name); + + s = stage_new(conf, &tex_node_ops, tex_node); + fatal_if(!s, "Unable to create stage \"%s\"", conf->name); + + tex_node->tex = tex_ref(tex); + tex_node->model_x = model_x; + + return s; + +} + + +/* return a tex node from a pix array + * the pixels are used in-place and no duplicate is made. + */ +stage_t * tex_node_new_mem(stage_conf_t *conf, int width, int height, const unsigned char *buf, m4f_t *model_x) +{ + tex_t *tex = tex_new(width, height, buf); + stage_t *stage = tex_node_new_tex(conf, tex_new(width, height, buf), model_x); + + tex_free(tex); + + return stage; +} diff --git a/src/tex-node.h b/src/tex-node.h new file mode 100644 index 0000000..59c731b --- /dev/null +++ b/src/tex-node.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _TEX_NODE_H +#define _TEX_NODE_H + +typedef struct m4f_t m4f_t; +typedef struct stage_t stage_t; +typedef struct tex_t tex_t; + +stage_t * tex_node_new_tex(stage_conf_t *conf, tex_t *tex, m4f_t *model_x); +stage_t * tex_node_new_mem(stage_conf_t *conf, int width, int height, const unsigned char *buf, m4f_t *model_x); + +#endif diff --git a/src/tex.c b/src/tex.c new file mode 100644 index 0000000..b8254e1 --- /dev/null +++ b/src/tex.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> + +#include "glad.h" +#include "m4f.h" +#include "macros.h" +#include "shader.h" +#include "tex.h" + +typedef struct tex_t { + unsigned tex; + unsigned refcnt; +} tex_t; + +static unsigned vbo, tcbo; +static shader_t *tex_shader; + +static const float vertices[] = { + +1.f, +1.f, 0.f, + +1.f, -1.f, 0.f, + -1.f, +1.f, 0.f, + +1.f, -1.f, 0.f, + -1.f, -1.f, 0.f, + -1.f, +1.f, 0.f, +}; + +static const float texcoords[] = { + 1.f, 0.f, + 1.f, 1.f, + 0.f, 0.f, + 1.f, 1.f, + 0.f, 1.f, + 0.f, 0.f, +}; + + +static const char *tex_vs = "" + "#version 120\n" + + "uniform mat4 model_x;" + + "attribute vec3 vertex;" + "attribute vec2 texcoord;" + + "void main()" + "{" + " gl_TexCoord[0].xy = texcoord;" + " gl_Position = model_x * vec4(vertex, 1.f);" + "}" +""; + + +static const char *tex_fs = "" + "#version 120\n" + + "uniform sampler2D tex0;" + "uniform float alpha;" + + "void main()" + "{" + " gl_FragColor = texture2D(tex0, gl_TexCoord[0].st);" + " gl_FragColor.a *= alpha;" + "}" +""; + + +/* Render simply renders a texd texture onto the screen */ +void tex_render(tex_t *tex, float alpha, m4f_t *model_x) +{ + int *uniforms, *attributes; + + assert(tex); + assert(model_x); + + shader_use(tex_shader, NULL, &uniforms, NULL, &attributes); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glVertexAttribPointer(attributes[0], 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(attributes[0]); + + glBindBuffer(GL_ARRAY_BUFFER, tcbo); + glVertexAttribPointer(attributes[1], 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + glEnableVertexAttribArray(attributes[1]); + + glBindTexture(GL_TEXTURE_2D, tex->tex); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUniform1f(uniforms[0], alpha); + glUniformMatrix4fv(uniforms[1], 1, GL_FALSE, &model_x->m[0][0]); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + + +tex_t * tex_new(int width, int height, const unsigned char *buf) +{ + tex_t *tex; + + assert(buf); + + if (!vbo) { + /* common to all tex instances */ + tex_shader = shader_pair_new(tex_vs, tex_fs, + 2, + (const char *[]) { + "alpha", + "model_x", + }, + 2, + (const char *[]) { + "vertex", + "texcoord", + }); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenBuffers(1, &tcbo); + glBindBuffer(GL_ARRAY_BUFFER, tcbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(texcoords), texcoords, GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + tex = calloc(1, sizeof(tex_t)); + fatal_if(!tex, "Unable to allocate tex_t"); + + glGenTextures(1, &tex->tex); + glBindTexture(GL_TEXTURE_2D, tex->tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf); + glBindTexture(GL_TEXTURE_2D, 0); + + tex->refcnt = 1; + + return tex; +} + + +tex_t * tex_ref(tex_t *tex) +{ + assert(tex); + + tex->refcnt++; + + return tex; +} + + +tex_t * tex_free(tex_t *tex) +{ + if (!tex) + return NULL; + + assert(tex->refcnt > 0); + + tex->refcnt--; + if (!tex->refcnt) { + glDeleteTextures(1, &tex->tex); + free(tex); + } + + return NULL; +} diff --git a/src/tex.h b/src/tex.h new file mode 100644 index 0000000..00e5759 --- /dev/null +++ b/src/tex.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _TEX_H +#define _TEX_H + +#include <stdint.h> + +typedef struct tex_t tex_t; +typedef struct m4f_t m4f_t; + +void tex_render(tex_t *tex, float alpha, m4f_t *model_x); +tex_t * tex_new(int width, int height, const unsigned char *buf); +tex_t * tex_ref(tex_t *tex); +tex_t * tex_free(tex_t *tex); + +#endif diff --git a/src/tv-node.c b/src/tv-node.c new file mode 100644 index 0000000..267e2e2 --- /dev/null +++ b/src/tv-node.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stage.h> + +#include "tv-node.h" +#include "gfx/gfx-tv.h" +#include "tex.h" +#include "tex-node.h" + +static tex_t *tv_tex; + +stage_t * tv_node_new(stage_conf_t *conf, m4f_t *model_x) +{ + if (!tv_tex) + tv_tex = tex_new(gfx_tv.width, gfx_tv.height, gfx_tv.pixel_data); + + return tex_node_new_tex(conf, tv_tex, model_x); +} diff --git a/src/tv-node.h b/src/tv-node.h new file mode 100644 index 0000000..3974994 --- /dev/null +++ b/src/tv-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _TV_NODE_H +#define _TV_NODE_H + +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +stage_t * tv_node_new(stage_conf_t *conf, m4f_t *model_x); + +#endif diff --git a/src/virus-node.c b/src/virus-node.c new file mode 100644 index 0000000..bf52385 --- /dev/null +++ b/src/virus-node.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stage.h> + +#include "virus-node.h" +#include "gfx/gfx-virus.h" +#include "tex.h" +#include "tex-node.h" + +static tex_t *virus_tex; + +stage_t * virus_node_new(stage_conf_t *conf, m4f_t *model_x) +{ + if (!virus_tex) + virus_tex = tex_new(gfx_virus.width, gfx_virus.height, gfx_virus.pixel_data); + + return tex_node_new_tex(conf, virus_tex, model_x); +} diff --git a/src/virus-node.h b/src/virus-node.h new file mode 100644 index 0000000..3c437ce --- /dev/null +++ b/src/virus-node.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _VIRUS_NODE_H +#define _VIRUS_NODE_H + +typedef struct stage_conf_t stage_conf_t; +typedef struct m4f_t m4f_t; + +stage_t * virus_node_new(stage_conf_t *conf, m4f_t *model_x); + +#endif |