550 lines
14 KiB
C++
550 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];
|
|
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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
prof_init();
|
|
azrp_config_scale(2);
|
|
cd_raytrace_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;
|
|
}
|
|
}
|
|
|
|
static struct camera *camera = NULL;
|
|
static struct world *world = NULL;
|
|
static int reset_frames = 0;
|
|
static bool debug = false;
|
|
|
|
#ifdef AZUR_TOOLKIT_GINT
|
|
extern struct level level_1_1, level_1_2, level_1_3;
|
|
extern struct level level_2_1, level_2_2, level_2_3;
|
|
extern struct level level_3_1, level_3_2, level_3_3;
|
|
static struct level const *levels[] = {
|
|
&level_1_1, &level_1_2, &level_1_3,
|
|
&level_2_1, &level_2_2, &level_2_3,
|
|
};
|
|
#else
|
|
/* Basic basic level */
|
|
static struct level const lv_test = {
|
|
.name = "<Test>",
|
|
.finish = num(1024),
|
|
.mirror_count = 1,
|
|
.mirrors = (struct element_mirror[]){
|
|
{ .begin = 256, .end = 768 },
|
|
},
|
|
.plane_count = 2,
|
|
.planes = (struct element_plane[]){
|
|
{ .y = 384, .shape = 0b01010'10101'01010'10101'01010,
|
|
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
|
|
{ .y = 384+64, .shape = 0b11111'10001'10001'10001'11111,
|
|
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
|
|
},
|
|
.text_count = 1,
|
|
.texts = (struct element_text[]){
|
|
{ .begin = 32, .end = 256,
|
|
.str = "Red stuff bad!\nDon't touch the red stuff!", },
|
|
},
|
|
};
|
|
static struct level const *levels[] = { &lv_test };
|
|
#endif
|
|
|
|
static int current_level = 0;
|
|
|
|
void load_level_number(int number)
|
|
{
|
|
int total_number = sizeof levels / sizeof levels[0];
|
|
|
|
if(number < 0)
|
|
number = 0;
|
|
if(number > total_number)
|
|
number = total_number - 1;
|
|
|
|
current_level = number;
|
|
}
|
|
|
|
bool is_final_level(int number)
|
|
{
|
|
int total_number = sizeof levels / sizeof levels[0];
|
|
return number >= total_number - 1;
|
|
}
|
|
|
|
void world_reset(struct world *world)
|
|
{
|
|
world->neon_position = 0;
|
|
world->depth = 0;
|
|
world->neon_period = 64;
|
|
world->mirror = NULL;
|
|
world->plane = NULL;
|
|
world->text = NULL;
|
|
world->mirror_index = 0;
|
|
world->plane_index = 0;
|
|
world->text_index = 0;
|
|
}
|
|
|
|
void world_level_transition(struct world *world)
|
|
{
|
|
world->depth = world->depth % num(world->neon_period);
|
|
world->mirror = NULL;
|
|
world->plane = NULL;
|
|
world->text = NULL;
|
|
world->mirror_index = 0;
|
|
world->plane_index = 0;
|
|
world->text_index = 0;
|
|
}
|
|
|
|
void render(void)
|
|
{
|
|
struct level const *level = levels[current_level];
|
|
|
|
#ifdef AZUR_TOOLKIT_GINT
|
|
azrp_perf_clear();
|
|
if(reset_frames > 0) {
|
|
reset_frames--;
|
|
azrp_clear(0xffff);
|
|
}
|
|
else {
|
|
cd_raytrace(camera, world);
|
|
if(world->text)
|
|
render_centered_text(world->text->str);
|
|
draw_text(1, 1, level->name, strlen(level->name));
|
|
}
|
|
platform_render();
|
|
|
|
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);
|
|
}
|
|
#else
|
|
if(reset_frames > 0) {
|
|
reset_frames--;
|
|
memset(vram, 0xff, sizeof vram);
|
|
}
|
|
else {
|
|
render_fragment(camera, world, vram, 0, VHEIGHT);
|
|
if(world->text)
|
|
render_centered_text(world->text->str);
|
|
draw_text(1, 1, level->name, strlen(level->name));
|
|
}
|
|
platform_render();
|
|
#endif
|
|
}
|
|
|
|
int update(void)
|
|
{
|
|
struct input input = {};
|
|
if(platform_update(&input))
|
|
return 1;
|
|
|
|
/* Debug contros */
|
|
if(input.OPTN)
|
|
debug = !debug;
|
|
if(input.RESET_LEVEL) {
|
|
world_reset(world);
|
|
reset_frames = 2;
|
|
}
|
|
if(input.NEXT_LEVEL && !is_final_level(current_level)) {
|
|
world_level_transition(world);
|
|
load_level_number(current_level+1);
|
|
reset_frames = 2;
|
|
}
|
|
if(input.PREV_LEVEL && current_level > 0) {
|
|
world_level_transition(world);
|
|
load_level_number(current_level-1);
|
|
reset_frames = 2;
|
|
}
|
|
if(reset_frames > 0)
|
|
return 0;
|
|
|
|
static num const mv_speed = 0.45;
|
|
static num const wall = WORLD_SIZE * 0.45; /* 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(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);
|
|
|
|
world->depth += fall_speed;
|
|
world->neon_position += world->neon_period - num16(fall_speed);
|
|
world->neon_position %= world->neon_period;
|
|
|
|
// Remove old world elements
|
|
|
|
struct world *w = world;
|
|
|
|
if(w->mirror && w->depth > w->mirror->end)
|
|
w->mirror = NULL;
|
|
if(w->plane && w->depth > w->plane->y - num(4)) {
|
|
/* Apply plane effect */
|
|
if((w->plane->type == ELEMENT_PLANE_DAMAGE ||
|
|
w->plane->type == ELEMENT_PLANE_INVIS)
|
|
&& plane_collides(num16(camera->pos.x), num16(camera->pos.z),
|
|
w->plane->shape)) {
|
|
world_reset(w);
|
|
reset_frames = 2;
|
|
}
|
|
else if(w->plane->type == ELEMENT_PLANE_ARROW &&
|
|
camera->roll_quadrant != w->plane->data) {
|
|
world_reset(w);
|
|
reset_frames = 2;
|
|
}
|
|
w->plane = NULL;
|
|
}
|
|
if(w->text && w->depth > w->text->end)
|
|
w->text = NULL;
|
|
|
|
// Load incoming world elements
|
|
|
|
struct level const *level = levels[current_level];
|
|
|
|
if(!w->mirror && w->mirror_index < level->mirror_count
|
|
&& level->mirrors[w->mirror_index].begin < w->depth + num(64)) {
|
|
w->mirror = &level->mirrors[w->mirror_index++];
|
|
}
|
|
if(!w->plane && w->plane_index < level->plane_count
|
|
&& level->planes[w->plane_index].y < w->depth + num(64)) {
|
|
w->plane = &level->planes[w->plane_index++];
|
|
}
|
|
if(!w->text && w->text_index < level->text_count
|
|
&& level->texts[w->text_index].begin < w->depth + num(64)) {
|
|
w->text = &level->texts[w->text_index++];
|
|
}
|
|
|
|
// End of level
|
|
|
|
if(!w->mirror && !w->plane && !w->text && w->depth >= level->finish
|
|
&& !reset_frames) {
|
|
if(is_final_level(current_level)) {
|
|
// TODO: Main menu
|
|
return 1;
|
|
}
|
|
else {
|
|
world_level_transition(w);
|
|
load_level_number(current_level + 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
if(azur_init("Chaos Drop!", 3*VWIDTH, 3*VHEIGHT))
|
|
return 1;
|
|
|
|
struct camera c = {};
|
|
camera = &c;
|
|
/* TODO: Why do I need such a low FOV?! */
|
|
camera_set_fov(camera, 80.0);
|
|
camera_update_angles(camera);
|
|
|
|
struct world w = {};
|
|
load_level_number(0);
|
|
world_reset(&w);
|
|
world = &w;
|
|
|
|
init();
|
|
int rc = azur_main_loop(render, 30, update, -1, AZUR_MAIN_LOOP_TIED);
|
|
quit();
|
|
return rc;
|
|
}
|