chaos-drop/src/main.cc

545 lines
14 KiB
C++

#define __BSD_VISIBLE 1
#include <azur/azur.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "chaos-drop.h"
#if defined(AZUR_TOOLKIT_SDL)
#include <SDL2/SDL.h>
#include <azur/gl/gl.h>
#include <memory>
#include "backend/linux/programs.h"
static uint16_t vram[VWIDTH * VHEIGHT];
std::unique_ptr<ProgramTexture> shader_texture = nullptr;
static GLuint tex_vram;
static void view_update(void)
{
SDL_Window *window = azur_sdl_window();
int width, height;
SDL_GetWindowSize(window, &width, &height);
/* Transform from pixel coordinates within the winfow to GL coordinates */
glm::mat3 tr_pixel2gl(
2.0f / width, 0.0f, 0.0f,
0.0f, -2.0f / height, 0.0f,
-1.0f, 1.0f, 1.0f);
glUseProgram(shader_texture->prog);
shader_texture->set_uniform("u_transform", tr_pixel2gl);
}
static void init(void)
{
shader_texture = std::make_unique<ProgramTexture>();
view_update();
memset(vram, 0x55, sizeof vram);
glGenTextures(1, &tex_vram);
glBindTexture(GL_TEXTURE_2D, tex_vram);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VWIDTH, VHEIGHT, 0, GL_RGB,
GL_UNSIGNED_SHORT_5_6_5, vram);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
static void quit(void)
{
}
static int platform_update(struct input *input)
{
SDL_Event e;
*input = (struct input){};
while(SDL_PollEvent(&e)) {
// ImGui_ImplSDL2_ProcessEvent(&e);
// render_needed = std::max(render_needed, 1);
if(e.type == SDL_QUIT)
return 1;
if(e.type == SDL_WINDOWEVENT &&
e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
view_update();
}
}
Uint8 const *state = SDL_GetKeyboardState(NULL);
input->up = state[SDL_SCANCODE_UP];
input->down = state[SDL_SCANCODE_DOWN];
input->left = state[SDL_SCANCODE_LEFT];
input->right = state[SDL_SCANCODE_RIGHT];
input->roll_left = state[SDL_SCANCODE_F1];
input->roll_right = state[SDL_SCANCODE_F2];
input->enter = state[SDL_SCANCODE_RETURN];
return 0;
}
static void platform_render(void)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VWIDTH, VHEIGHT, 0, GL_RGB,
GL_UNSIGNED_SHORT_5_6_5, vram);
SDL_Window *window = azur_sdl_window();
shader_texture->vertices.clear();
shader_texture->add_texture(0, 0, 3*VWIDTH, 3*VHEIGHT);
shader_texture->draw();
SDL_GL_SwapWindow(window);
}
static void draw_text(int x, int y, char const *str, int size)
{
// TODO: SDL: Text rendering
}
static int text_size(char const *str, int length)
{
// TODO: SDL: Text rendering
return 0;
}
#elif defined(AZUR_TOOLKIT_GINT)
#include <azur/gint/render.h>
#include <gint/gint.h>
#include <gint/keyboard.h>
#include <gint/display.h>
#include <gint/drivers/r61524.h>
#include <libprof.h>
#define vram gint_vram
static int platform_update(struct input *input)
{
key_event_t e;
while((e = pollevent()).type != KEYEV_NONE) {
if(e.type == KEYEV_UP)
continue;
if(e.key == KEY_EXIT)
return 1;
if(e.key == KEY_MENU)
gint_osmenu();
if(e.key == KEY_OPTN)
input->OPTN = true;
if(e.key == KEY_F1)
input->roll_left = true;
if(e.key == KEY_F2)
input->roll_right = true;
if(e.key == KEY_EXE && keydown(KEY_VARS))
input->RESET_LEVEL = true;
if(e.key == KEY_MINUS && keydown(KEY_VARS))
input->PREV_LEVEL = true;
if(e.key == KEY_PLUS && keydown(KEY_VARS))
input->NEXT_LEVEL = true;
if(e.key == KEY_SHIFT)
input->enter = true;
if(e.key == KEY_LEFT)
input->left_trigger = true;
if(e.key == KEY_RIGHT)
input->right_trigger = true;
if(e.key == KEY_UP)
input->up_trigger = true;
if(e.key == KEY_DOWN)
input->down_trigger = true;
}
input->left = keydown(KEY_LEFT);
input->right = keydown(KEY_RIGHT);
input->up = keydown(KEY_UP);
input->down = keydown(KEY_DOWN);
return 0;
}
static void platform_render(void)
{
azrp_update();
}
static void init(void)
{
gint_setrestart(true);
prof_init();
azrp_config_scale(2);
cd_raytrace_configure();
cd_vfx_configure();
azrp_shader_clear_configure();
azrp_shader_image_p8_configure();
}
static void quit(void)
{
prof_quit();
}
static uint8_t const font_glyph_width[96] = {
3,1,3,5,5,4,5,2,2,2,3,5,2,5,2,3,
4,4,4,4,4,4,4,4,4,4,2,2,3,4,3,4,
5,4,4,4,4,4,4,4,4,1,4,4,4,5,4,4,
4,4,4,4,5,4,5,5,5,5,4,2,3,2,5,4,
2,4,4,4,4,4,4,4,4,1,2,4,3,5,4,4,
4,4,4,4,4,4,5,5,5,4,4,3,1,3,5,4,
};
/* Very bad text renderer */
static void draw_text(int x, int y, char const *str, int size)
{
extern bopti_image_t img_font;
for(int i = 0; str[i] && i < size; i++) {
if(str[i] < 32 || str[i] >= 0x7f)
continue;
int row = (str[i] - 32) >> 4;
int col = (str[i] - 32) & 15;
int gw = font_glyph_width[str[i] - 32];
azrp_subimage(x, y, &img_font, 7 * col + 1, 9 * row + 1, gw, 8,
DIMAGE_NONE);
x += gw + 1;
}
}
static int text_size(char const *str, int length)
{
int total = 0;
for(int i = 0; str[i] && i < length; i++) {
if(str[i] < 32 || str[i] >= 0x7f)
continue;
total += font_glyph_width[str[i] - 32] + 1;
}
return total - (total > 0);
}
#endif
mat3 operator *(mat3 const &A, mat3 const &B)
{
mat3 C;
C.x11 = A.x11 * B.x11 + A.x12 * B.x21 + A.x13 * B.x31;
C.x12 = A.x11 * B.x12 + A.x12 * B.x22 + A.x13 * B.x32;
C.x13 = A.x11 * B.x13 + A.x12 * B.x23 + A.x13 * B.x33;
C.x21 = A.x21 * B.x11 + A.x22 * B.x21 + A.x23 * B.x31;
C.x22 = A.x21 * B.x12 + A.x22 * B.x22 + A.x23 * B.x32;
C.x23 = A.x21 * B.x13 + A.x22 * B.x23 + A.x23 * B.x33;
C.x31 = A.x31 * B.x11 + A.x32 * B.x21 + A.x33 * B.x31;
C.x32 = A.x31 * B.x12 + A.x32 * B.x22 + A.x33 * B.x32;
C.x33 = A.x31 * B.x13 + A.x32 * B.x23 + A.x33 * B.x33;
return C;
}
vec3 operator * (mat3 const &M, vec3 const &u)
{
vec3 v;
v.x = M.x11 * u.x + M.x12 * u.y + M.x13 * u.z;
v.y = M.x21 * u.x + M.x22 * u.y + M.x23 * u.z;
v.z = M.x31 * u.x + M.x32 * u.y + M.x33 * u.z;
return v;
}
__attribute__((unused))
static void print_text(int x, int y, char const *fmt, ...)
{
static char str[128];
va_list args;
va_start(args, fmt);
vsnprintf(str, sizeof str, fmt, args);
va_end(args);
draw_text(x, y, str, strlen(str));
}
void render_centered_text(char const *str)
{
/* Count lines */
int lines = 1;
for(int i = 0; str[i]; i++)
lines += str[i] == '\n';
static int const line_height = 10;
int y = (VHEIGHT - line_height * lines) / 2;
/* Draw each line centered */
char const *line = str;
char const *endline;
while(*line) {
endline = strchrnul(line, '\n');
int w = text_size(line, endline - line);
draw_text((VWIDTH - w) / 2, y, line, endline - line);
line = endline + (*endline != 0);
y += line_height;
}
}
void game_render(struct game *g)
{
struct level const *level = g->level;
#ifdef AZUR_TOOLKIT_GINT
extern image_t img_title, img_cursor, img_cursor0;
azrp_perf_clear();
if(g->blank_frames > 0) {
g->blank_frames--;
azrp_clear(0xffff);
}
else if(game_in_menu(g)) {
cd_raytrace(g);
azrp_image((azrp_width - img_title.width) / 2, 4, &img_title);
for(int row = 0; row < 3; row++)
for(int col = 0; col < 3; col++) {
int i = 3 * row + col;
if(i >= level_count())
continue;
int x = azrp_width / 2 + 40 * (col - 1);
int y = 55 + 20 * row;
char const *name = level_get(i)->name;
int l = strlen(name);
int width = text_size(name, l);
if(g->menu_cursor == i)
azrp_image(x - width/2 - 8, y-5, &img_cursor);
else
azrp_image(x - width/2 - 6, y-3, &img_cursor0);
draw_text(x - width/2, y, name, l);
}
}
else {
cd_raytrace(g);
if(g->text)
render_centered_text(g->text->str);
draw_text(1, 1, level->name, strlen(level->name));
if(g->vfx)
cd_vfx(g->vfx->effect);
}
#else
if(g->blank_frames > 0) {
g->blank_frames--;
memset(vram, 0xff, sizeof vram);
}
else {
render_fragment(g, vram, 0, VHEIGHT);
if(g->text)
render_centered_text(g->text->str);
draw_text(1, 1, level->name, strlen(level->name));
}
#endif
}
int game_update(struct game *g, bool *debug)
{
struct input input = {};
if(platform_update(&input))
return 1;
struct camera *camera = g->camera;
bool menu = game_in_menu(g);
/* Debug controls */
if(input.OPTN)
*debug = !*debug;
if(!menu && input.RESET_LEVEL) {
game_restart_level(g, 2);
return 0;
}
if(!menu && input.NEXT_LEVEL) {
game_next_level_or_menu(g);
g->blank_frames = 2;
return 0;
}
if(!menu && input.PREV_LEVEL) {
game_prev_level_or_menu(g);
g->blank_frames = 2;
return 0;
}
if(g->blank_frames > 0)
return 0;
if(menu) {
if(input.left_trigger && g->menu_cursor > 0) {
g->menu_cursor--;
}
if(input.right_trigger && g->menu_cursor + 1 < level_count()) {
g->menu_cursor++;
}
if(input.up_trigger && g->menu_cursor >= 3) {
g->menu_cursor -= 3;
}
if(input.down_trigger && g->menu_cursor + 3 < level_count()) {
g->menu_cursor += 3;
}
if(input.enter) {
game_load_level(g, g->menu_cursor, 0);
menu = game_in_menu(g);
}
}
static num const mv_speed = 0.45;
static num const wall = WORLD_SIZE * 0.9; /* margin */
static float const rot_speed = 0.01;
static float const rot_snap = 0.6;
static float const rot_max = 0.2;
static num const fall_speed = 2;
static float const barrel_snap = 0.5;
if(!menu) {
if(input.left) {
camera->pos -= mv_speed * camera->right;
camera->yaw = fmaxf(camera->yaw - rot_speed, -rot_max);
}
else if(input.right) {
camera->pos += mv_speed * camera->right;
camera->yaw = fminf(camera->yaw + rot_speed, rot_max);
}
else {
camera->yaw *= rot_snap;
}
if(input.up) {
camera->pos += mv_speed * camera->up;
camera->pitch = fmaxf(camera->pitch - rot_speed, -rot_max);
}
else if(input.down) {
camera->pos -= mv_speed * camera->up;
camera->pitch = fminf(camera->pitch + rot_speed, rot_max);
}
else {
camera->pitch *= rot_snap;
}
camera->pos.x = clamp(camera->pos.x, -wall, wall);
camera->pos.z = clamp(camera->pos.z, -wall, wall);
if(input.roll_left && !camera_rolling(camera)) {
camera->roll_quadrant = (camera->roll_quadrant + 4 - 1) % 4;
}
else if(input.roll_right && !camera_rolling(camera)) {
camera->roll_quadrant = (camera->roll_quadrant + 1) % 4;
}
camera_roll(camera, barrel_snap);
camera_update_angles(camera);
}
camera->pos.y = 0;
g->depth += fall_speed;
g->neon_position += g->neon_period - num16(fall_speed);
g->neon_position %= g->neon_period;
if(menu)
return 0;
// Apply collision with plane
struct element_plane const *p = game_plane_collision(g);
if((p->type == ELEMENT_PLANE_DAMAGE || p->type == ELEMENT_PLANE_INVIS)
&& level_plane_collides(num16(camera->pos.x), num16(camera->pos.z),
p->shape))
{
game_restart_level(g, 2);
}
else if(p->type == ELEMENT_PLANE_ARROW && camera->roll_quadrant != p->data)
{
game_restart_level(g, 2);
}
else if(p->type == ELEMENT_PLANE_DEATH)
{
game_next_level_or_menu(g);
g->blank_frames = 2;
return 0;
}
// Load incoming level elements
game_advance_in_level(g);
if(!g->mirror && !g->plane && !g->text && !g->vfx
&& g->depth >= g->level->finish && !g->blank_frames) {
game_next_level_or_menu(g);
}
// Pre-compute plane collision info
num pc = g->plane ? g->plane->y - g->depth : num(0x7fff);
if(g->plane->type == ELEMENT_PLANE_INVIS) {
g->plane_collision[0] = num(0x7fff);
g->plane_collision[1] = pc;
}
else {
g->plane_collision[0] = pc;
g->plane_collision[1] = pc;
}
return 0;
}
//---
static struct game *game = NULL;
static bool debug = false;
void render(void)
{
game_render(game);
platform_render();
#ifdef AZUR_TOOLKIT_GINT
if(debug) {
drect(0, DHEIGHT-20, DWIDTH-1, DHEIGHT-1, C_WHITE);
dprint(4, 209, C_BLACK, "render:%4d+%4dus",
prof_time(azrp_perf_render) - prof_time(azrp_perf_r61524),
prof_time(azrp_perf_r61524));
r61524_display(gint_vram, DHEIGHT-20, 20, R61524_DMA_WAIT);
}
#endif
}
int update(void)
{
return game_update(game, &debug);
}
int main(void)
{
if(azur_init("Chaos Drop!", 3*VWIDTH, 3*VHEIGHT))
return 1;
struct camera camera = {};
/* TODO: Why do I need such a low FOV?! */
camera_set_fov(&camera, 80.0);
camera_update_angles(&camera);
struct game g = {};
g.camera = &camera;
g.menu_cursor = 0;
g.blank_frames = 0;
g.neon_position = 0;
g.neon_period = 64;
g.plane_collision[0] = num(0x7fff);
g.plane_collision[1] = num(0x7fff);
game_load_menu(&g);
game = &g;
init();
int rc = azur_main_loop(render, 30, update, -1, AZUR_MAIN_LOOP_TIED);
quit();
return rc;
}