From d9af0735ae8ec71275a71d3cd65a81bd87328034 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sat, 27 Aug 2022 14:03:44 +0200 Subject: [PATCH] refactor physics/rendering, game structure, basic jump --- CMakeLists.txt | 10 +- assets-cg/dot.png | Bin 0 -> 589 bytes assets-cg/fxconv-metadata.txt | 4 + src/game.cpp | 95 +++++++++++++ src/game.h | 98 +++++++++++++ src/generator/gen1.cpp | 8 +- src/generator/gen2.cpp | 9 +- src/generator/gen3.cpp | 9 +- src/level.h | 11 +- src/log.cpp | 71 ++++++++++ src/log.h | 17 +++ src/main.cpp | 249 ++++++++++++++++++++++------------ src/render.cpp | 177 ++++++++---------------- src/render.h | 91 ++++++------- src/settings.h | 37 +++++ src/util.cpp | 75 ++++++++++ src/util.h | 36 +++++ 17 files changed, 713 insertions(+), 284 deletions(-) create mode 100644 assets-cg/dot.png create mode 100644 assets-cg/fxconv-metadata.txt create mode 100644 src/game.cpp create mode 100644 src/game.h create mode 100644 src/log.cpp create mode 100644 src/log.h create mode 100644 src/settings.h create mode 100644 src/util.cpp create mode 100644 src/util.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 74a3823..ebbdf2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,13 +9,17 @@ find_package(Gint 2.8 REQUIRED) find_package(LibProf 2.1 REQUIRED) set(SOURCES - src/main.cpp + src/game.cpp src/generator/gen1.cpp src/generator/gen2.cpp src/generator/gen3.cpp src/level.cpp - src/render.cpp) -set(ASSETS) + src/log.cpp + src/main.cpp + src/render.cpp + src/util.cpp) +set(ASSETS + assets-cg/dot.png) fxconv_declare_assets(${ASSETS} WITH_METADATA) diff --git a/assets-cg/dot.png b/assets-cg/dot.png new file mode 100644 index 0000000000000000000000000000000000000000..2ff8dda4ab17b69c3b44d1691673d11bd8818c8a GIT binary patch literal 589 zcmV-T0EX>4Tx04R}tkv&MmKpe$iTct%R4(%Z7kfAzR5WjHLDionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0scmXsb<$0plX(p zP9}tGZdC}rB7{MN5y6ziOnpuiQ}7&L_we!cF3z*O&;2=imAuISpFljzbi*RvAfDc| zbk6(4QC5-^;&b9rgDyz?$aUG}H_kjWYY7*QDULk!Ey()lA#h$62}x(qkMnX zWrgz=XSGset$XqphV$CWGS_JiA&EsSL4*JqHIz|-g*dGmDJIgipYZSxIDU~_GP%lN z5bWxZDBypLEHP94SE4Unl_YXY@@uAaV=zthv3l_Hp_EWT>mu4RCM> zj1(z*-Q(S%&ffk#)9UXBn1FJ&nqebu00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<_H@M6dw^C>ZAYw02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001RPL_t&t*JEU0U|{&q!0?{|3K$s}7#WcTV1S7M3&1MK bh|Np@hDinn=pS}e00000NkvXXu0mjfO7r#B literal 0 HcmV?d00001 diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt new file mode 100644 index 0000000..20769ed --- /dev/null +++ b/assets-cg/fxconv-metadata.txt @@ -0,0 +1,4 @@ +dot.png: + type: bopti-image + name: img_dot + profile: p8_rgb565a diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..bc4befe --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,95 @@ +#include "game.h" +#include "level.h" + +//===== Player implementation =======// + +num player::world_angle() const +{ + num arc = space_platform_arc(); + num angle = this->platform * arc; + + if(this->airborne()) { + /* Time spent rotating; caps at the full rotation time */ + num t = num_clamp(this->jump_t, 0, TIME_ROTATION); + angle += this->jump_dir * arc * t / TIME_ROTATION; + } + + return angle; +} + +vec2 player::world_angle_vector() const +{ + num angle = this->world_angle(); + return vec2(num_cos(angle), num_sin(angle)); +} + +vec3 player::pos() const +{ + /* Local position without accounting for current platform's angle */ + vec3 pos(0, -space_platform_distance() + this->height, this->z); + + return vec_rotate_around_z(pos, this->world_angle_vector()); +} + +vec3 player::forward() const +{ + return vec3(0, 0, 1); +} + +vec3 player::backward() const +{ + return -this->forward(); +} + +vec3 player::up() const +{ + return vec_rotate_around_z(vec3(0, 1, 0), this->world_angle_vector()); +} + +vec3 player::down() const +{ + return -this->up(); +} + +//======= Game implementation =======// + +//======= General utilities =======// + + +static vec2 platform_rotations[PLATFORM_COUNT]; +static num platform_distance; + +num space_platform_distance(void) +{ + return platform_distance; +} + +/* We can't use a constructor function because g++ already generates a call to + the default constructor, which would run after us and override the data. */ +void game_init(void) +{ + num arc = space_platform_arc(); + + for(int i = 0; i < PLATFORM_COUNT; i++) { + num angle = arc * i - (arc / 2); + platform_rotations[i] = vec2(num_cos(angle), num_sin(angle)); + } + + platform_distance = LEVEL_RADIUS * num_cos(arc / 2); +} + +struct prect space_platform_position(int platform_id, num z) +{ + struct prect r; + vec3 radius(0, -LEVEL_RADIUS, z); + + int angle_l = platform_id; + int angle_r = (platform_id + 1) % PLATFORM_COUNT; + + r.nl = r.fl = vec_rotate_around_z(radius, platform_rotations[angle_l]); + r.nr = r.fr = vec_rotate_around_z(radius, platform_rotations[angle_r]); + + r.fl.z += SECTION_LENGTH; + r.fr.z += SECTION_LENGTH; + return r; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..bac304e --- /dev/null +++ b/src/game.h @@ -0,0 +1,98 @@ +#ifndef __GAME_H__ +# define __GAME_H__ + +#include "util.h" +#include "render.h" +#include "level.h" + +/* A player's movement and data information. */ +struct player +{ + //======= Movement =======// + + /* Position in the level (world units). */ + num z; + /* Movement speed along the z axis (world units/s, ≥ 0). */ + num vz; + + /* Current platform; when rotating, platform the player jumped from + (0..PLATFORM_COUNT-1) */ + int platform; + + /* Player's current movement stance. */ + enum { + Running, /* Running on the ground */ + Jumping, /* Jumping, with jump key still pressed */ + Falling, /* Falling, typically after releasing jump key */ + } stance; + + /* Whether the player is airborne. */ + bool airborne() const { return this->stance != Running; } + + /* Jump direction. + * when not jumping: 0 + * when jumping, while rotating: -1, 0 or +1 + * when jumping, after rotating: 0 */ + int jump_dir; + /* Time spent jumping (s). */ + num jump_t; + /* Key to watch to end the jump. */ + int jump_key; + /* Current height relative to current platform (world units). */ + num height; + + /* Player angle relative to -y around +z (depends on the current platform, + plus some extra during rotations). */ + num world_angle() const; + vec2 world_angle_vector() const; + + /* Full player position in 3D space. */ + vec3 pos() const; + + /* Unit vectors in the directions of movement, accounting for player's + rotation around the world cylinder. */ + vec3 forward() const; + vec3 backward() const; + vec3 up() const; + vec3 down() const; + + //======= Data =======// + + /* TODO: Energy -> speed multiplier, skin, others? */ +}; + +/* Dynamic information about one or multiple attempts at a single level. */ +struct game +{ + level_t level; + struct player player; + struct camera camera; + + /* Absolute time spent in the level. */ + num t; + /* Number of sections passed. */ + int sections_passed; + + struct { + bool footer; /* Show performance footer */ + } debug; +}; + + +/* Initialize constants for the game. */ +void game_init(void); + +/* Angle, in radians, of a platform arc (2π / PLATFORM_COUNT). */ +consteval num space_platform_arc() +{ + return num(2 * 3.14159 / PLATFORM_COUNT); +} + +/* Distance between the center of the world and a platform's plane. */ +num space_platform_distance(void); + +/* Position, in world units, of a flat platform in the specified platform slot + (0..PLATFORM_COUNT-1) that has its near side at the specified depth z. */ +struct prect space_platform_position(int platform_slot, num z); + +#endif /* __GAME_H__ */ diff --git a/src/generator/gen1.cpp b/src/generator/gen1.cpp index dde2cea..2e54512 100644 --- a/src/generator/gen1.cpp +++ b/src/generator/gen1.cpp @@ -1,6 +1,8 @@ #include "../generator.h" #include "../level.h" +#define N PLATFORM_COUNT + gen1::gen1() : last_pos{0} { srand(0xc0ffee); @@ -12,17 +14,17 @@ void gen1::generate(level_t *level) int r = rand() % 3; if(r == 0) { - for(int i = 0; i < PLATFORM_COUNT; i++) + for(int i = 0; i < N; i++) section.platforms[i].type = PLATFORM_WHITE; } else if(r == 1) { - for(int i = 0; i < PLATFORM_COUNT; i++) { + for(int i = 0; i < N; i++) { auto t = (i % 2) ? PLATFORM_WHITE : PLATFORM_EMPTY; section.platforms[i].type = t; } } else if(r == 2) { - for(int i = 0; i < PLATFORM_COUNT; i++) { + for(int i = 0; i < N; i++) { auto t = (i % 2) ? PLATFORM_EMPTY : PLATFORM_WHITE; section.platforms[i].type = t; } diff --git a/src/generator/gen2.cpp b/src/generator/gen2.cpp index 2043ad2..4a0a577 100644 --- a/src/generator/gen2.cpp +++ b/src/generator/gen2.cpp @@ -1,8 +1,9 @@ #include "../generator.h" #include "../level.h" - #include +#define N PLATFORM_COUNT + gen2::gen2() : last_pos{0} { srand(123456); } void gen2::generate(level_t *level) @@ -11,11 +12,11 @@ void gen2::generate(level_t *level) this->last_pos += (rand() % 3) - 1; if (this->last_pos < 0) - this->last_pos = PLATFORM_COUNT - 1; - if (this->last_pos >= PLATFORM_COUNT) + this->last_pos = N - 1; + if (this->last_pos >= N) this->last_pos = 0; - for (int i = 0; i < PLATFORM_COUNT; ++i) { + for (int i = 0; i < N; ++i) { if (i == this->last_pos) section.platforms[i].type = PLATFORM_WHITE; else diff --git a/src/generator/gen3.cpp b/src/generator/gen3.cpp index 4a83232..b48828d 100644 --- a/src/generator/gen3.cpp +++ b/src/generator/gen3.cpp @@ -1,8 +1,9 @@ #include "../generator.h" #include "../level.h" - #include +#define N PLATFORM_COUNT + gen3::gen3() : last_pos{0, 1, 2} { srand(123456); } void gen3::generate(level_t *level) @@ -12,12 +13,12 @@ void gen3::generate(level_t *level) for (int i = 0; i < 3; ++i) { this->last_pos[i] += (rand() % 3) - 1; if (this->last_pos[i] < 0) - this->last_pos[i] = PLATFORM_COUNT - 1; - if (this->last_pos[i] >= PLATFORM_COUNT) + this->last_pos[i] = N - 1; + if (this->last_pos[i] >= N) this->last_pos[i] = 0; } - for (int i = 0; i < PLATFORM_COUNT; ++i) { + for (int i = 0; i < N; ++i) { section.platforms[i].type = PLATFORM_EMPTY; if (i == this->last_pos[0] || i == this->last_pos[1] diff --git a/src/level.h b/src/level.h index bd88afe..36c865e 100644 --- a/src/level.h +++ b/src/level.h @@ -1,13 +1,10 @@ -#ifndef LEVEL -# define LEVEL +#ifndef __LEVEL_H__ +# define __LEVEL_H__ +#include "settings.h" #include #include -#define PLATFORM_COUNT 10 - -#define LEVEL_BUFFER_LENGTH 12 - typedef enum { PLATFORM_EMPTY, PLATFORM_WHITE, @@ -40,4 +37,4 @@ extern level_t level_create(int level); extern void level_display(level_t *level); extern void level_advance(level_t *level); -#endif /* LEVEL */ +#endif /* __LEVEL_H__ */ diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..cadec76 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,71 @@ +#include "log.h" +#include +#include +#include +#include + +#if LOG_USB_ENABLE + +void log_init(bool wait) +{ + usb_interface_t const *intf[] = { &usb_ff_bulk, NULL }; + usb_open(intf, GINT_CALL_NULL); + + while(wait && !usb_is_open()) + sleep(); +} + +bool log_write(char const *str) +{ + if(!usb_is_open()) + return false; + + usb_fxlink_text(str, 0); + return true; +} + +bool log_printf(char const *fmt, ...) +{ + if(!usb_is_open()) + return false; + + va_list args; + va_start(args, fmt); + bool b = log_vprintf(fmt, args); + va_end(args); + return b; +} + +bool log_vprintf(char const *fmt, va_list args) +{ + if(!usb_is_open()) + return false; + + static char str[256]; + vsnprintf(str, sizeof str, fmt, args); + log_write(str); + return true; +} + +#else + +void log_init(bool) +{ +} + +bool log_write(char const *) +{ + return false; +} + +bool log_printf(char const *, ...) +{ + return false; +} + +bool log_vprintf(char const *, va_list) +{ + return false; +} + +#endif /* LOG_USB_ENABLE */ diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..4fb95a3 --- /dev/null +++ b/src/log.h @@ -0,0 +1,17 @@ +#ifndef __LOG_H__ +# define __LOG_H__ + +#include +#include "settings.h" + +/* Initialize the log by opening the USB connection. */ +void log_init(bool wait); + +/* Log a string through USB, if the connection is open. true if successful. */ +bool log_write(char const *str); + +/* Like log_write(), but with variadic arguments. */ +bool log_printf(char const *fmt, ...); +bool log_vprintf(char const *fmt, va_list args); + +#endif /* __LOG_H__ */ diff --git a/src/main.cpp b/src/main.cpp index 4a2a5f2..ff33a85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,82 +1,82 @@ #include "level.h" #include "generator.h" #include "render.h" +#include "game.h" +#include "util.h" +#include "log.h" + #include #include #include +#include #include #include #include #include -#define RGB24(hex) \ - (((hex & 0xf80000) >> 8) | \ - ((hex & 0x00fc00) >> 5) | \ - ((hex & 0x0000f8) >> 3)) - -constexpr int bg = RGB24(0x49759f); - -int main(void) +int play_level(int level_id) { - __printf_enable_fp(); - prof_init(); - azrp_config_scale(1); - azrp_shader_clear_configure(); -// azrp_shader_image_rgb16_configure(); -// azrp_shader_image_p8_configure(); -// azrp_shader_image_p4_configure(); - azrp_shader_triangle_configure(); + struct game game; + struct camera *camera = &game.camera; + struct player *player = &game.player; - render_init(); + game.level = level_create(level_id); + game.t = 0.0; + game.sections_passed = 0; + game.debug.footer = false; - level_t level = level_create(3); + camera->set_fov(120.0); + camera->screen_size = vec2(DWIDTH, DHEIGHT); + player->z = 0.0; + player->vz = 4.0; + player->platform = 0; + player->stance = player::Running; + player->jump_dir = 0; + player->jump_t = 0.0; + + /* FPS regulation setup */ int volatile need_frame = 1; int last_frame_us = 0; int timer = timer_configure(TIMER_ANY, 33000, GINT_CALL_SET(&need_frame)); timer_start(timer); - num t = 0.0; - num player_z = 0.0; - num level_z = 0.0; - num player_speed = 5.0; - int sections_passed = 0; - bool show_footer = false; - - struct camera camera; - camera.pos = vec3(0, RENDER_CAMERA_DEPTH, 0); - camera.platform = 0; - camera.rot_direction = 0; - camera.rot_t = 0; + camera_track(camera, player); + log_printf("platform distance: %s\n", str(space_platform_distance())); + log_printf("player: at %s\n", str(player->pos())); + log_printf("camera: at %s, screen distance %s, platform %d, " + "angle vector %s\n", + str(camera->pos), + str(camera->screen_distance()), + camera->platform, + str(camera->angle_vector)); bool game_run = true; while(game_run) { while(!need_frame) sleep(); need_frame = 0; + //======= Initial data updates =======// + num dt = 1.0 / 30; - t += dt; + game.t += dt; prof_t perf_frame = prof_make(); prof_enter_norec(perf_frame); - level_update(&level); - camera_set_angle(&camera, camera_compute_platform_angle(&camera)); + level_update(&game.level); + camera_track(camera, player); - //--- - // Rendering - //--- + //======= Rendering =======// azrp_perf_clear(); - azrp_clear(bg); - - vec2 screen_size(DWIDTH, DHEIGHT); + azrp_clear(RGB24(0x49759f)); prof_t perf_comp = prof_make(); prof_enter_norec(perf_comp); - for(int depth = RENDER_SECTION_DISTANCE - 1; depth >= 0; depth--) { - num z = depth * RENDER_SECTION_LENGTH - level_z; - struct section *s = &level.section_buffer[depth]; + for(int depth = RENDER_DISTANCE - 1; depth >= 0; depth--) { + num z = (game.sections_passed + depth) * SECTION_LENGTH; + struct section *s = &game.level.section_buffer[depth]; for(int i = 0; i < PLATFORM_COUNT; i++) { /* Set this to get the full tunnel test */ @@ -89,29 +89,37 @@ int main(void) if(!full_tunnel && s->platforms[i].type != PLATFORM_WHITE) continue; if(og_coloring) { - int gray = ((i + depth + sections_passed) % PLATFORM_COUNT) - * 31 / PLATFORM_COUNT; + int j = i + depth + game.sections_passed; + int gray = (j % PLATFORM_COUNT) * 31 / PLATFORM_COUNT; color = C_RGB(gray, gray, gray); } else { - color = camera_platform_color(&camera, i); + color = camera_platform_color(&game.camera, i); } - struct prect p = render_platform_position(i, z); + struct prect p = space_platform_position(i, z); /* Near plane clipping */ - if(p.fl.z <= num(0.2)) continue; - if(p.nl.z <= num(0.2)) p.nl.z = num(0.2); - if(p.nr.z <= num(0.2)) p.nr.z = num(0.2); - camera_project_prect(&camera, &p, screen_size); + num near = camera->pos.z + camera->near_plane(); + if(p.fl.z <= near) continue; + if(p.nl.z <= near) p.nl.z = near; + if(p.nr.z <= near) p.nr.z = near; + camera_project_prect(&game.camera, &p); render_triangle(&p.nl, &p.fr, &p.fl, color); render_triangle(&p.fr, &p.nl, &p.nr, color); + } } + + /* Render player */ + vec3 player_dot = camera_project_point(&game.camera, + player->pos()); + render_dots({ player_dot }); + prof_leave_norec(perf_comp); azrp_update(); - if(show_footer) { + if(game.debug.footer) { drect(0, DHEIGHT-20, DWIDTH-1, DHEIGHT-1, C_WHITE); dprint(4, 209, C_BLACK, "render:%4d+%4dus comp:%4dus FPS:%02d", prof_time(azrp_perf_render) - prof_time(azrp_perf_r61524), @@ -121,67 +129,104 @@ int main(void) r61524_display(gint_vram, DHEIGHT-20, 20, R61524_DMA_WAIT); } - //--- - // Input - //--- + //======= Input =======// key_event_t ev; while((ev = pollevent()).type != KEYEV_NONE) { - if(ev.type == KEYEV_UP) + if(ev.type == KEYEV_UP || ev.type == KEYEV_HOLD) continue; + int key = ev.key; - if(ev.key == KEY_EXIT || ev.key == KEY_MENU) + if(key == KEY_EXIT || key == KEY_MENU) game_run = false; - if(ev.key == KEY_OPTN) - show_footer = !show_footer; - if(ev.key == KEY_F1) { - level = level_create(1); - level_update(&level); + if(key == KEY_OPTN) + game.debug.footer = !game.debug.footer; + if(key == KEY_F1) { + game.level = level_create(1); + level_update(&game.level); } - if(ev.key == KEY_F2) { - level = level_create(2); - level_update(&level); + if(key == KEY_F2) { + game.level = level_create(2); + level_update(&game.level); } - if(ev.key == KEY_F3) { - level = level_create(3); - level_update(&level); + if(key == KEY_F3) { + game.level = level_create(3); + level_update(&game.level); } - if(ev.key == KEY_LEFT && !camera.rot_direction) { - camera.rot_direction = -1; - camera.rot_t = 0; + if(key == KEY_LEFT && !player->airborne()) { + player->stance = player::Jumping; + player->jump_dir = -1; + player->jump_t = 0.0; + player->jump_key = key; } - if(ev.key == KEY_RIGHT && !camera.rot_direction) { - camera.rot_direction = 1; - camera.rot_t = 0; + if((key == KEY_UP || key == KEY_SHIFT) && !player->airborne()) { + player->stance = player::Jumping; + player->jump_dir = 0; + player->jump_t = 0.0; + player->jump_key = key; + } + if(key == KEY_RIGHT && !player->airborne()) { + player->stance = player::Jumping; + player->jump_dir = +1; + player->jump_t = 0.0; + player->jump_key = key; } } if(!game_run) break; - //--- - // Simulation - //--- + //======= Simulation =======// - player_z += player_speed * dt; - level_z += player_speed * dt; - while(level_z > num(RENDER_SECTION_LENGTH)) { - level_advance(&level); - level_z -= RENDER_SECTION_LENGTH; - sections_passed++; + player->z += player->vz * dt; + + /* Increase player's height during the ascending part of a jump */ + if(player->stance == player::Jumping) { + player->height += JUMP_SPEED * dt; + player->jump_t += dt; + + /* Release the jump after the max time has elapsed */ + if(player->jump_t > TIME_JUMP_THRUST_MAX) + player->stance = player::Falling; + /* Also do it if the key has been released */ + if(player->jump_t > TIME_JUMP_THRUST_MIN + && !keydown(player->jump_key)) + player->stance = player::Falling; + } + /* TODO: Apply gravity properly */ + else if(player->stance == player::Falling) { + player->height -= GRAVITY_STRENGTH * dt; + player->jump_t += dt; } - if(camera.rot_direction != 0) { - camera.rot_t += dt; - if(camera.rot_t > CAMERA_ROTATION_DURATION) { - int p = camera.platform + camera.rot_direction; + /* Update the current platform number after rotation */ + if(player->airborne()) { + if(player->jump_t > TIME_ROTATION && player->jump_dir) { + int p = player->platform + player->jump_dir; p = (p + PLATFORM_COUNT) % PLATFORM_COUNT; - camera.platform = p; - camera.rot_direction = 0; - camera.rot_t = 0; + player->platform = p; + player->jump_dir = 0; } } - //--- + /* TODO: Reset jump after contact on platforms, not distance */ + if(player->airborne()) { + if(player->height <= 0) { + player->stance = player::Running; + player->jump_dir = 0; + player->jump_t = 0.0; + player->height = 0; + player->jump_key = 0; + } + } + + /* Eliminate dead platforms so new ones can be generated. We include + the camera's distance in the calculation because we only remove + platforms that have been passed *and* are now invisible. */ + while(player->z - RENDER_CAMERA_BACK_DISTANCE > + (game.sections_passed + 1) * SECTION_LENGTH) { + level_advance(&game.level); + game.sections_passed++; + } prof_leave_norec(perf_frame); last_frame_us = prof_time(perf_frame); @@ -190,3 +235,27 @@ int main(void) timer_stop(timer); return 0; } + +int main(void) +{ + __printf_enable_fp(); + prof_init(); + game_init(); + +#if LOG_USB_ENABLE + dclear(C_WHITE); + dtext_opt(DWIDTH/2, DHEIGHT/2, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE, + "Waiting for USB log...", -1); + dupdate(); + log_init(true); +#endif + + azrp_config_scale(1); + azrp_shader_clear_configure(); +// azrp_shader_image_rgb16_configure(); + azrp_shader_image_p8_configure(); +// azrp_shader_image_p4_configure(); + azrp_shader_triangle_configure(); + + return play_level(3); +} diff --git a/src/render.cpp b/src/render.cpp index 9f69680..a056856 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -1,162 +1,83 @@ #define __BSD_VISIBLE 1 -#include "level.h" -#include "render.h" #include + +#include "render.h" +#include "game.h" +#include "util.h" #include -/* Angle span for a platform in the level cylinder */ -num render_platform_arc(void) +void camera::set_fov(num fov) { - return 2 * 3.14159 / PLATFORM_COUNT; + this->fov = fov; + + float fov_radians = (float)fov * 3.14159 / 180; + float sd = 1 / tanf(fov_radians / 2); + this->_sd = num(sd); } -num num_cos_dl(num a) +void camera_track(struct camera *camera, struct player const *player) { - num u = 1.0; - int p = 7; - for(p = 2 * p - 1; p >= 1; p -= 2) - u = num(1) - a * a / (p * p + p) * u; - return u; + camera->pos = player->pos() + player->up() * RENDER_EYE_HEIGHT; + camera->pos.z -= RENDER_CAMERA_BACK_DISTANCE; + + camera->platform = player->platform; + if(player->jump_dir && player->jump_t >= TIME_ROTATION / 2) + camera->platform += player->jump_dir; + + num angle = player->world_angle(); + camera->angle_vector = vec2(num_cos(-angle), num_sin(-angle)); } -num num_cos(num a) +vec3 camera_project_point(struct camera *camera, vec3 u) { - if(a < 0) a = -a; - a = a % num(6.28319); - if(a > num(3.14159)) a -= num(6.28319); - return num_cos_dl(a); -} - -num num_sin(num a) -{ - return num_cos(a - num(1.57080)); -} - -/* Get enlargement factor from FOV */ -float get_near_plane(void) -{ - float fov_radians = RENDER_FOV * 3.14159 / 180; - return 2 * atanf(1 / fov_radians); -} - -static vec2 alpha_rotations[PLATFORM_COUNT]; - -/* We can't use a constructor function because g++ already generates one which - uses the default 0-constructor and it would run after us, overriding the - computed values. */ -void render_init(void) -{ - num alpha = render_platform_arc(); - - for(int i = 0; i < PLATFORM_COUNT; i++) { - num angle = -alpha * i + (alpha / 2); - alpha_rotations[i] = vec2(num_cos(angle), num_sin(angle)); - } -} - -vec3 vec_rotate_around_z(vec3 v, vec2 rotator) -{ - num c = rotator.x; - num s = rotator.y; - return vec3( - c * v.x - s * v.y, - c * v.y + s * v.x, - v.z); -} - -struct prect render_platform_position(int platform_id, num z) -{ - struct prect r; - vec3 radius(0, RENDER_RADIUS, 0); - - int angle_l = platform_id; - int angle_r = (platform_id + 1) % PLATFORM_COUNT; - - r.nl = r.fl = vec_rotate_around_z(radius, alpha_rotations[angle_l]); - r.nr = r.fr = vec_rotate_around_z(radius, alpha_rotations[angle_r]); - - r.nl.z += z; - r.nr.z += z; - r.fl.z += z + RENDER_SECTION_LENGTH; - r.fr.z += z + RENDER_SECTION_LENGTH; - return r; -} - -vec3 camera_project(struct camera *camera, vec3 u, vec2 screen_size) -{ - u -= camera->pos; - if(u.z <= 0) + if(u.z < camera->pos.z + camera->near_plane()) return u; - static num near_plane = get_near_plane(); + u = vec_rotate_around_z(u - camera->pos, camera->angle_vector); - int screen = screen_size.x < screen_size.y - ? (int)screen_size.x / 2 - : (int)screen_size.y / 2; - - num f = near_plane * screen / u.z; - u.x = u.x * f + screen_size.x / 2; - u.y = u.y * f + screen_size.y / 2; + num f = camera->screen_distance() * (camera->screen_size.y / 2) / u.z; + u.x = u.x * f + camera->screen_size.x / 2; + u.y = -u.y * f + camera->screen_size.y / 2 ; return u; } -void camera_project_prect(struct camera *camera, struct prect *p, - vec2 screen_size) +void camera_project_prect(struct camera *camera, struct prect *p) { /* Require the rectangle to be already clipped */ - if(p->nl.z <= camera->pos.z) + if(p->nl.z < camera->pos.z + camera->near_plane()) return; - static num near_plane = get_near_plane(); + vec2 half_screen(camera->screen_size.x / 2, camera->screen_size.y / 2); - vec3 half_screen(screen_size.x / 2, screen_size.y / 2, 0); - int screen = half_screen.x < half_screen.y - ? (int)half_screen.x - : (int)half_screen.y; - - p->nl = vec_rotate_around_z(p->nl, camera->_angle_rotation) - camera->pos; - p->nr = vec_rotate_around_z(p->nr, camera->_angle_rotation) - camera->pos; - p->fl = vec_rotate_around_z(p->fl, camera->_angle_rotation) - camera->pos; - p->fr = vec_rotate_around_z(p->fr, camera->_angle_rotation) - camera->pos; + p->nl = vec_rotate_around_z(p->nl - camera->pos, camera->angle_vector); + p->nr = vec_rotate_around_z(p->nr - camera->pos, camera->angle_vector); + p->fl = vec_rotate_around_z(p->fl - camera->pos, camera->angle_vector); + p->fr = vec_rotate_around_z(p->fr - camera->pos, camera->angle_vector); /* We assume nl/nr have the same z, and so do fl/fr */ - num f = near_plane * screen; + num f = camera->screen_distance() * half_screen.y; num near_f = f / p->nl.z; num far_f = f / p->fl.z; - p->nl = p->nl * near_f + half_screen; - p->nr = p->nr * near_f + half_screen; - p->fl = p->fl * far_f + half_screen; - p->fr = p->fr * far_f + half_screen; -} + p->nl.x = p->nl.x * near_f + half_screen.x; + p->nl.y = -p->nl.y * near_f + half_screen.y; -void camera_set_angle(struct camera *camera, num angle) -{ - camera->_angle = angle; - camera->_angle_rotation = vec2(num_cos(angle), num_sin(angle)); -} + p->nr.x = p->nr.x * near_f + half_screen.x; + p->nr.y = -p->nr.y * near_f + half_screen.y; -num camera_compute_platform_angle(struct camera *camera) -{ - num arc = render_platform_arc(); - num a = camera->platform * arc; - if(camera->rot_direction) { - num t = camera->rot_t / CAMERA_ROTATION_DURATION; - a += camera->rot_direction * arc * t; - } - return a; + p->fl.x = p->fl.x * far_f + half_screen.x; + p->fl.y = -p->fl.y * far_f + half_screen.y; + + p->fr.x = p->fr.x * far_f + half_screen.x; + p->fr.y = -p->fr.y * far_f + half_screen.y; } int camera_platform_color(struct camera *camera, int platform_id) { /* Accounting for the full angle is too precise and results in weird color jumps at different times across platforms, instead switch at half - movement. Instead just count entire platforms. */ - int down = camera->platform; - if(camera->rot_direction && camera->rot_t >= CAMERA_ROTATION_DURATION / 2) - down += camera->rot_direction; - - int dist = platform_id - down; + movement. */ + int dist = platform_id - camera->platform; if(dist < 0) dist += PLATFORM_COUNT; dist = std::min(dist, PLATFORM_COUNT - dist); @@ -173,3 +94,11 @@ void render_triangle(vec3 *p1, vec3 *p2, vec3 *p3, int color) (int)p3->x, (int)p3->y, color); } + +void render_dots(std::initializer_list const &&points) +{ + extern image_t img_dot; + + for(auto p: points) + azrp_image((int)p.x - 2, (int)p.y - 2, &img_dot); +} diff --git a/src/render.h b/src/render.h index 50a6bf9..707a876 100644 --- a/src/render.h +++ b/src/render.h @@ -1,69 +1,58 @@ #ifndef __RENDERH__ # define __RENDERH__ -#include -#include -using namespace libnum; +#include "settings.h" +#include "util.h" +#include -/* Vertical FOV, in degrees */ -#define RENDER_FOV 150.0 -/* Radius of the level cylinder, in world units */ -#define RENDER_RADIUS num(4.0) -/* Camera depth in the level cylinder, in world units, should be between 0 - and RENDER_RADIUS */ -#define RENDER_CAMERA_DEPTH num(2.9) -/* Section lengths, in world units */ -#define RENDER_SECTION_LENGTH num(4.0) -/* Number of sections visible in advance */ -#define RENDER_SECTION_DISTANCE 8 -/* Duration of a camera rotation by one platform */ -#define CAMERA_ROTATION_DURATION num(0.1) +struct camera +{ + //======= Fixed settings =======// -struct prect { - vec3 nl, nr; /* Near left and near right points */ - vec3 fl, fr; /* Far left and far right points */ -}; + /* Vertical FOV, in degrees. */ + num fov; + void set_fov(num fov); -struct camera { - /* Position in space - we assume looking towards (0,0,z) */ + /* Distance to the screen (world units). */ + num screen_distance() const { return this->_sd; } + + /* Distance to the near plane, for clipping. + TODO: Figure out a better choice of a near plane. */ + num near_plane() const { return this->screen_distance() * num(0.75); } + + //======= World data =======// + + /* Position in space; always looking towards +z. */ vec3 pos; - /* Current platform (or, when rotating, platform we just left) */ + /* Current platform (used to assign colors). */ int platform; + /* Vector canceling the player's angular rotation around z. */ + vec2 angle_vector; - /* Current direction of rotation (-1, 0, or +1) */ - int rot_direction; - /* How much of the movement in rot_direction has been accomplished yet - (between 0 and CAMERA_ROTATION_DURATION) */ - num rot_t; + //======= Screen settings =======// - /* Viewing angle and associated rotation vector. Both of these fields are - computed from the previous. */ - num _angle; - vec2 _angle_rotation; + /* Screen size. */ + vec2 screen_size; + +private: + num _sd; }; -/* Initialize the render module. */ -void render_init(void); +struct player; -/* Angle, in radians, of a platform arc (2π / PLATFORM_COUNT) */ -num render_platform_arc(void); +/* Move the camera to track behind a player. */ +void camera_track(struct camera *, struct player const *); -/* Position, in world units, of a flat platform in the specified platform slot - (0..PLATFORM_COUNT-1) that has its near side at the specified depth z. */ -struct prect render_platform_position(int platform_slot, num z); +/* Project a world point to the screen as viewed through a camera. The returned + vector has its x/y members set to screen coordinates stored as num; the z + member is kept unchanged. */ +vec3 camera_project_point(struct camera *, vec3 point); + +/* Optimized camera_project_point() that projects an entire prect in-place. */ +void camera_project_prect(struct camera *, struct prect *rect); -/* Project a point from world units to screen units viewed through the provided - camera. screen_size holds sizes in pixels as num values. */ -vec3 camera_project(struct camera *camera, vec3 u, vec2 screen_size); -/* Optimized camera_project() that projects an entire prect in-place. */ -void camera_project_prect(struct camera *camera, struct prect *p, - vec2 screen_size); -/* Set the camera's viewing angle and recompute its rotation vector. */ -void camera_set_angle(struct camera *camera, num angle); -/* Compute the camera's viewing angle based on its platform position. */ -num camera_compute_platform_angle(struct camera *camera); /* Compute the platform's color based on the camera's angle. */ int camera_platform_color(struct camera *camera, int platform_id); @@ -72,4 +61,8 @@ int camera_platform_color(struct camera *camera, int platform_id); x/y coordinates are used, z is ignored. */ void render_triangle(vec3 *p1, vec3 *p2, vec3 *p3, int color); +/* Queue Azur commands to render a list of dots. Only x/y coordinates are + used, z is ignored. */ +void render_dots(std::initializer_list const &&points); + #endif /* __RENDERH__ */ diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..33f7a20 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,37 @@ +#ifndef __SETTINGS_H__ +# define __SETTINGS_H__ + +/* Radius of the level cylinder, in world units. */ +#define LEVEL_RADIUS num(4.0) +/* Section lengths, in world units. */ +#define SECTION_LENGTH num(4.0) +/* Number of platforms in the world cylinder. */ +#define PLATFORM_COUNT 10 +/* Magnitude of the gravity field, locally (world units/s^2). */ +#define GRAVITY_STRENGTH num(1.0) +/* Constant speed applied during the ascending part of a jump + (world units/s). */ +#define JUMP_SPEED num(0.8) + +/* Duration of a rotation by one platform, in seconds. */ +#define TIME_ROTATION num(0.1) +/* How long a jump can provide thrust for (s). */ +#define TIME_JUMP_THRUST_MIN num(0.2) +#define TIME_JUMP_THRUST_MAX num(0.9) + +/* Vertical FOV, in degrees */ +#define RENDER_FOV 120.0 +/* Height of camera above the player's feet, in world units */ +#define RENDER_EYE_HEIGHT num(1.0) +/* Number of sections visible in advance */ +#define RENDER_DISTANCE 8 +/* Distance between the player and the camera */ +#define RENDER_CAMERA_BACK_DISTANCE num(0.7) + +/* Number of level segments that are guaranteed generated ahead-of-time. */ +#define LEVEL_BUFFER_LENGTH 12 + +/* Set to 1 to enable logging by USB. */ +#define LOG_USB_ENABLE 0 + +#endif /* __SETTINGS_H__ */ diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..04f6c66 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,75 @@ +#include "util.h" +#include +#include + +static num num_cos_dl(num a) +{ + num u = 1.0; + int p = 7; + for(p = 2 * p - 1; p >= 1; p -= 2) + u = num(1) - a * a / (p * p + p) * u; + return u; +} + +num num_cos(num a) +{ + if(a < 0) a = -a; + a = a % num(6.28319); + if(a > num(3.14159)) a -= num(6.28319); + return num_cos_dl(a); +} + +num num_sin(num a) +{ + return num_cos(a - num(1.57080)); +} + +num num_clamp(num t, num lower, num upper) +{ + if(t < lower) + return lower; + if(t > upper) + return upper; + return t; +} + +vec3 vec_rotate_around_z(vec3 v, vec2 rotator) +{ + num c = rotator.x; + num s = rotator.y; + return vec3( + c * v.x - s * v.y, + c * v.y + s * v.x, + v.z); +} + +static char strings[8][64]; +static int strings_next = 0; + +static char const *do_str(char const *fmt, ...) +{ + char *s = strings[strings_next]; + strings_next = (strings_next + 1) % 8; + + va_list args; + va_start(args, fmt); + vsnprintf(s, sizeof strings[0], fmt, args); + va_end(args); + + return s; +} + +char const *str(num x) +{ + return do_str("%g", (float)x); +} + +char const *str(vec2 u) +{ + return do_str("[%g %g]", (float)u.x, (float)u.y); +} + +char const *str(vec3 u) +{ + return do_str("[%g %g %g]", (float)u.x, (float)u.y, (float)u.z); +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..c5c2cdf --- /dev/null +++ b/src/util.h @@ -0,0 +1,36 @@ +#ifndef __UTIL_H__ +# define __UTIL_H__ + +#include +#include +using namespace libnum; + +/* A platform rectangle, aligned with the camera's view */ +struct prect +{ + vec3 nl, nr; /* Near left and near right points */ + vec3 fl, fr; /* Far left and far right points */ +}; + +/* Approximations of the sine and cosine of an angle in radians. */ +num num_cos(num a); +num num_sin(num a); + +/* Clamp a num between two bounds. */ +num num_clamp(num t, num lower_bound, num upper_bound); + +/* String representation of various objects (static strings rotating; can use + up to 8 of them at once). */ +char const *str(num x); +char const *str(vec2 u); +char const *str(vec3 u); + +/* Rotate v by θ around z, when rotator=(cos(θ), sin(θ)) */ +vec3 vec_rotate_around_z(vec3 v, vec2 rotator); + +#define RGB24(hex) \ + (((hex & 0xf80000) >> 8) | \ + ((hex & 0x00fc00) >> 5) | \ + ((hex & 0x0000f8) >> 3)) + +#endif /* __UTIL_H__ */