#define __BSD_VISIBLE 1 #include #include #include #include #include "chaos-drop.h" #if defined(AZUR_TOOLKIT_SDL) #include #include #include #include "backend/linux/programs.h" static uint16_t vram[VWIDTH * VHEIGHT]; std::unique_ptr 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(); 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 #include #include #include #include #include #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 < 2; 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)); } #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); } 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); // Load incoming level elements game_advance_in_level(g); if(!g->mirror && !g->plane && !g->text && 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; }