refactor physics/rendering, game structure, basic jump

This commit is contained in:
Lephenixnoir 2022-08-27 14:03:44 +02:00
parent 6cf0b59913
commit d9af0735ae
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
17 changed files with 713 additions and 284 deletions

View File

@ -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)

BIN
assets-cg/dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

View File

@ -0,0 +1,4 @@
dot.png:
type: bopti-image
name: img_dot
profile: p8_rgb565a

95
src/game.cpp Normal file
View File

@ -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;
}

98
src/game.h Normal file
View File

@ -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__ */

View File

@ -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;
}

View File

@ -1,8 +1,9 @@
#include "../generator.h"
#include "../level.h"
#include <stdlib.h>
#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

View File

@ -1,8 +1,9 @@
#include "../generator.h"
#include "../level.h"
#include <stdlib.h>
#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]

View File

@ -1,13 +1,10 @@
#ifndef LEVEL
# define LEVEL
#ifndef __LEVEL_H__
# define __LEVEL_H__
#include "settings.h"
#include <vector>
#include <memory>
#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__ */

71
src/log.cpp Normal file
View File

@ -0,0 +1,71 @@
#include "log.h"
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/cpu.h>
#include <stdio.h>
#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 */

17
src/log.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __LOG_H__
# define __LOG_H__
#include <stdarg.h>
#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__ */

View File

@ -1,82 +1,82 @@
#include "level.h"
#include "generator.h"
#include "render.h"
#include "game.h"
#include "util.h"
#include "log.h"
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/timer.h>
#include <gint/clock.h>
#include <gint/cpu.h>
#include <azur/gint/render.h>
#include <gint/drivers/r61524.h>
#include <fxlibc/printf.h>
#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);
}

View File

@ -1,162 +1,83 @@
#define __BSD_VISIBLE 1
#include "level.h"
#include "render.h"
#include <math.h>
#include "render.h"
#include "game.h"
#include "util.h"
#include <azur/gint/render.h>
/* 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<vec3> const &&points)
{
extern image_t img_dot;
for(auto p: points)
azrp_image((int)p.x - 2, (int)p.y - 2, &img_dot);
}

View File

@ -1,69 +1,58 @@
#ifndef __RENDERH__
# define __RENDERH__
#include <num/num.h>
#include <num/vec.h>
using namespace libnum;
#include "settings.h"
#include "util.h"
#include <utility>
/* 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<vec3> const &&points);
#endif /* __RENDERH__ */

37
src/settings.h Normal file
View File

@ -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__ */

75
src/util.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "util.h"
#include <stdio.h>
#include <stdarg.h>
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);
}

36
src/util.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef __UTIL_H__
# define __UTIL_H__
#include <num/num.h>
#include <num/vec.h>
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__ */