summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--src/Makefile.am65
-rw-r--r--src/adult-node.c32
-rw-r--r--src/adult-node.h25
-rw-r--r--src/baby-node.c32
-rw-r--r--src/baby-node.h25
-rw-r--r--src/bb2f.h38
-rw-r--r--src/bb3f.h38
-rw-r--r--src/digit-node.c60
-rw-r--r--src/digit-node.h25
-rw-r--r--src/game.c660
-rw-r--r--src/hungrycat-node.c27
-rw-r--r--src/hungrycat-node.h25
-rw-r--r--src/hungrycat.c157
-rw-r--r--src/m4f-3dx.h122
-rw-r--r--src/m4f-bbx.h89
-rw-r--r--src/plasma-node.c93
-rw-r--r--src/plasma-node.h25
-rw-r--r--src/sfx.c37
-rw-r--r--src/sfx.h34
-rw-r--r--src/shader-node.c174
-rw-r--r--src/shader-node.h30
-rw-r--r--src/shader.c144
-rw-r--r--src/shader.h28
-rw-r--r--src/tex-node.c100
-rw-r--r--src/tex-node.h27
-rw-r--r--src/tex.c189
-rw-r--r--src/tex.h30
-rw-r--r--src/tv-node.c32
-rw-r--r--src/tv-node.h25
-rw-r--r--src/virus-node.c32
-rw-r--r--src/virus-node.h25
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
© All Rights Reserved