BosonX/src/render.cpp

596 lines
19 KiB
C++

#define __BSD_VISIBLE 1
#include <math.h>
#include "render.h"
#include "game.h"
#include "util.h"
#include <azur/gint/render.h>
#include <gint/kmalloc.h>
void camera::set_fov(num fov)
{
this->fov = fov;
float fov_radians = (float)fov * 3.14159 / 180;
float sd = 1 / tanf(fov_radians / 2);
this->_sd = num(sd);
}
void camera_track(struct camera *camera, struct player const *player)
{
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));
}
vec3 camera_project_point(struct camera *camera, vec3 u)
{
if(u.z < camera->pos.z + camera->near_plane())
return u;
u = vec_rotate_around_z(u - camera->pos, camera->angle_vector);
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)
{
/* Require the rectangle to be already clipped */
if(p->nl.z < camera->pos.z + camera->near_plane())
return;
vec2 half_screen(camera->screen_size.x / 2, camera->screen_size.y / 2);
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 = camera->screen_distance() * half_screen.y;
num near_f = f / p->nl.z;
num far_f = f / p->fl.z;
p->nl.x = p->nl.x * near_f + half_screen.x;
p->nl.y = -p->nl.y * near_f + half_screen.y;
p->nr.x = p->nr.x * near_f + half_screen.x;
p->nr.y = -p->nr.y * near_f + half_screen.y;
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;
}
void camera_project_frect(struct camera *camera, struct frect *f)
{
f->tr = camera_project_point(camera, f->tr);
f->tl = camera_project_point(camera, f->tl);
f->br = camera_project_point(camera, f->br);
f->bl = camera_project_point(camera, f->bl);
}
void camera_project_srect(struct camera *camera, struct srect *s)
{
s->nt = camera_project_point(camera, s->nt);
s->nb = camera_project_point(camera, s->nb);
s->ft = camera_project_point(camera, s->ft);
s->fb = camera_project_point(camera, s->fb);
}
int camera_platform_color(struct camera const *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. */
int dist = platform_id - camera->platform;
if(dist < 0)
dist += PLATFORM_COUNT;
dist = std::min(dist, PLATFORM_COUNT - dist);
int gray = 31 - 2 * dist;
return C_RGB(gray, gray, gray);
}
//======= Rendering tools =======//
static uint16_t blue_ramp[32];
static void split_RGB(uint32_t RGB, int *R, int *G, int *B)
{
*R = (RGB >> 16) & 0xff;
*G = (RGB >> 8) & 0xff;
*B = (RGB >> 0) & 0xff;
}
static uint32_t make_RGB(int R, int G, int B)
{
return ((R & 0xff) << 16) | ((G & 0xff) << 8) | (B & 0xff);
}
static void gradient(uint32_t RGB1, uint32_t RGB2, uint16_t *gradient, int N)
{
int R1, G1, B1, R2, G2, B2;
split_RGB(RGB1, &R1, &G1, &B1);
split_RGB(RGB2, &R2, &G2, &B2);
for(int i = 0; i < N; i++) {
int R = ((N-1-i) * R1 + i * R2) / (N-1);
int G = ((N-1-i) * G1 + i * G2) / (N-1);
int B = ((N-1-i) * B1 + i * B2) / (N-1);
gradient[i] = RGB24(make_RGB(R, G, B));
}
}
void render_init(void)
{
/* Generate a gradient ramp for blue platforms */
gradient(0x56a3ef, 0x25c5ba, blue_ramp, 32);
}
uint16_t render_color_blue_platform(num t)
{
int step = (int)(32 * t.frac()) & 31;
int phase = (int)t & 1;
return phase ? blue_ramp[31 - step] : blue_ramp[step];
}
uint16_t render_platform_color(struct platform const *p,
struct camera const *camera, num t)
{
if(p->type == PLATFORM_WHITE)
return camera_platform_color(camera, p->face);
if(p->type == PLATFORM_BLUE)
return render_color_blue_platform(t);
if(p->type == PLATFORM_SEPARATING_WALL)
return RGB24(0x909090);
if(p->type == PLATFORM_BLOCK_TOP)
return RGB24(0xa0a0a0);
if(p->type == PLATFORM_BLOCK)
return RGB24(0x808080);
if(p->type == PLATFORM_RED) {
return t.frac() >= num(0.6)
? camera_platform_color(camera, p->face)
: RGB24(0xd06060);
}
return RGB24(0xff00ff);
}
void render_triangle(vec3 *p1, vec3 *p2, vec3 *p3, int color)
{
azrp_triangle(
(int)p1->x, (int)p1->y,
(int)p2->x, (int)p2->y,
(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);
}
void render_anim_frame(int x, int y, struct anim *anim, int frame)
{
azrp_image(
x + anim->x_offsets[frame] - anim->x_anchor,
y + anim->y_offsets[frame] - anim->y_anchor,
anim->frames[frame]);
}
void render_anim_frame_palette(int x, int y, struct anim *anim, int frame,
uint16_t const *palette)
{
image_t img = *anim->frames[frame];
img.palette = (uint16_t *)palette;
azrp_image(
x + anim->x_offsets[frame] - anim->x_anchor,
y + anim->y_offsets[frame] - anim->y_anchor,
&img);
}
static bool render_energy_str_glyph_params(int size, int c, int *ix, int *w)
{
size = (size >= 1 && size <= 3) ? size-1 : 0;
int dw[3] = { 11, 14, 18 };
int ow[3] = { 8, 11, 16 };
int dotx[3] = { 127, 157, 198 };
int dotw[3] = { 4, 5, 7 };
int perx[3] = { 132, 164, 207 };
int perw[3] = { 8, 13, 16 };
if(c >= '0' && c <= '9') {
*ix = (dw[size] + 2) * (c-'0') - (c >= '2' ? (dw[size]-ow[size]) : 0);
*w = (c == '1') ? ow[size] : dw[size];
return true;
}
if(c == '.') {
*ix = dotx[size];
*w = dotw[size];
return true;
}
if(c == '%') {
*ix = perx[size];
*w = perw[size];
return true;
}
return false;
}
void render_energy_str(int x, int y, int halign, int size, char const *str)
{
extern image_t img_font_energy, img_font_energy2, img_font_energy3;
image_t const *img =
(size == 3) ? &img_font_energy3 :
(size == 2) ? &img_font_energy2 :
&img_font_energy;
int ix, w;
/* First compute width of string */
int total_width = 0;
for(int i = 0; str[i]; i++) {
if(render_energy_str_glyph_params(size, str[i], &ix, &w))
total_width += w + 2;
}
total_width -= (total_width ? 2 : 0);
/* Apply horizontal alignment */
if(halign == DTEXT_CENTER)
x -= total_width >> 1;
if(halign == DTEXT_RIGHT)
x -= total_width - 1;
/* Render string */
for(int i = 0; str[i]; i++) {
if(!render_energy_str_glyph_params(size, str[i], &ix, &w))
continue;
azrp_subimage(x, y, img, ix, 0, w, img->height, DIMAGE_NONE);
x += w + 2;
}
}
static void render_effect_bpp_internal(image_t **image, num16 t)
{
static uint16_t bpp_palette[] = {
RGB24(0xff00ff), /* alpha */
C_WHITE,
};
static int const bpp_W = 35;
static int const bpp_H = 68;
if(!*image) {
*image = image_alloc(bpp_W, bpp_H, IMAGE_P8_RGB565A);
if(!*image)
return;
image_set_palette(*image, bpp_palette, 2, false);
}
image_clear(*image);
uint8_t *pixels = (uint8_t *)(*image)->data;
for(int i = 0; i < 32; i++) {
/* Particule #i is visible from t=i/32 until t=i/32+1. Local time
variable is normalized to 0..1. */
num16 start = num16(i) / 32;
if(t < start)
continue;
num16 local_time = (t - start) % num16(1);
int x0_int = (bpp_W / 2 - 17) + (((967 * i) >> 4) & 31);
/* 0+(...)&31 => [0,31] = [0,W-3] */
num16 x0 = num16(x0_int);
num16 y0 = num16(bpp_H - 4 - ((37 * i) & 15)); /* [H-19, H-4] */
num16 vx = num16(bpp_W / 2 - x0_int) / 2;
num16 x = x0 + vx * local_time;
num16 y = y0 - (bpp_H - 19) * local_time; /* (0, H-4] */
int size = 4 - (y.ifloor() >> 4);
for(int dy = 0; dy <= size; dy++)
for(int dx = 0; dx <= size; dx++) {
// if((unsigned)((int)y + dy) >= bpp_H)
// abort();
// if((unsigned)((int)x + dx) >= bpp_W)
// abort();
pixels[((int)y + dy) * (*image)->stride + (int)x + dx] = 0x81;
}
}
}
void render_effect_bpp(image_t **image, num global_t)
{
/* Time within the animation (loops every second). Two segments:
- t = [0..1) for the animation startup (particles appear progressively)
- t = [1..2) for the loop (all particles visible) */
num16 t = num16(global_t).frac() + num16(global_t >= 1);
return render_effect_bpp_internal(image, t);
}
void render_effect_dp(int x, int y, int count, num t, int color)
{
for(int i = 0; i < count; i++) {
num lifetime = (num(37 * i) / 16).frac() + num(0.75);
if(t >= lifetime)
continue;
num v = (num(113 * i * i) / 24) % num(8);
num t0 = v / 8;
v = v * v + num(4);
num alpha = num((163 * i) / 32) % num(6.28);
num vx = v * num_cos(alpha);
num vy = v * num_sin(alpha);
num local_t = TIME_DEATH_ANIMATION * TIME_DEATH_ANIMATION -
(TIME_DEATH_ANIMATION - t) * (TIME_DEATH_ANIMATION - t);
int rx = x + (int)((t0 + local_t) * vx);
int ry = y + (int)((t0 + local_t) * vy);
azrp_rect(rx, ry, 3, 3, color);
}
}
uint16_t render_blend(uint16_t c1, uint16_t c2, int t)
{
uint32_t o1 = ((c1 << 16) | c1) & 0x07e0f81f;
uint32_t o2 = ((c2 << 16) | c2) & 0x07e0f81f;
uint32_t r = (t * (o2 - o1) >> 5) + o1;
r &= 0x07e0f81f;
return (r >> 16) | r;
}
static void render_game_platforms(struct game &game)
{
num near = game.camera.pos.z + game.camera.near_plane();
for(auto it = game.level.platform_buffer.rbegin();
it != game.level.platform_buffer.rend();
++it) {
int color = render_platform_color(&*it, &game.camera, game.t);
if(it->type == PLATFORM_SEPARATING_WALL) {
struct srect side = space_wall_position(&*it);
/* Check if the wall should be rendered at all; it might be on the
side of a block that's hiding it from the player's view */
int d = (it->face - game.player.platform) % PLATFORM_COUNT;
if(d > PLATFORM_COUNT / 2)
d -= PLATFORM_COUNT;
if(d <= -PLATFORM_COUNT / 2)
d += PLATFORM_COUNT;
if((d < 0 && !it->wall_left_sided) ||
(d > 0 && !it->wall_right_sided))
continue;
if(side.ft.z > near) {
if(side.nt.z <= near) side.nt.z = near;
if(side.nb.z <= near) side.nb.z = near;
camera_project_srect(&game.camera, &side);
render_triangle(&side.ft, &side.fb, &side.nb, color);
render_triangle(&side.ft, &side.nb, &side.nt, color);
}
}
else if(it->type == PLATFORM_BLOCK) {
struct frect front = space_block_position(&*it);
if(front.tl.z > near) {
camera_project_frect(&game.camera, &front);
render_triangle(&front.tr, &front.tl, &front.bl, color);
render_triangle(&front.tr, &front.bl, &front.br, color);
}
}
else if(it->type == PLATFORM_BLOCK_TOP) {
struct prect top = space_block_top_position(&*it);
if(top.fl.z > near) {
if(top.nl.z <= near) top.nl.z = near;
if(top.nr.z <= near) top.nr.z = near;
camera_project_prect(&game.camera, &top);
render_triangle(&top.nl, &top.fr, &top.fl, color);
render_triangle(&top.fr, &top.nl, &top.nr, color);
}
}
/* Normal, flat platforms */
else {
struct prect p = space_platform_position(&*it);
/* Near plane clipping */
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);
}
}
}
/* Blue platform particle effect's buffer image */
static image_t *_effect_bpp = NULL;
static int cubic(int start, int end, num t, num tmax)
{
if(t >= tmax)
return end;
t = t / tmax;
num x = num(1.0) - (num(1.0) - t) * (num(1.0) - t) * (num(1.0) - t);
return (int)(num(start) + num(end - start) * x + num(0.5));
}
void render_game(struct game &game, prof_t *perf_comp)
{
azrp_perf_clear();
game.perf.effect_bpp = prof_make();
struct player *player = &game.player;
vec3 player_dot = camera_project_point(&game.camera, player->pos());
bool alive = (player->stance != player::Collided);
char energy_str[32];
sprintf(energy_str, "%.2f%%", (float)player->energy_percent);
bool bg_done = false;
if(alive || game.t_death < num(1.25)) {
azrp_clear(game.level.bgcolor);
bg_done = true;
}
/* Standard gameplay before collision */
if(alive) {
prof_enter_norec(*perf_comp);
render_game_platforms(game);
prof_leave_norec(*perf_comp);
struct anim *player_anim;
int player_frame;
player->get_anim(&player_anim, &player_frame);
/* Make a bluer palette palette when running on blue platforms */
uint16_t palette[16];
int blue_tint_alpha = 0;
if(player->blueanim > 0) {
num t = (player->blueanim >= 2)
? (num(2.5) - player->blueanim) / 2
: min(player->blueanim, num(0.25));
blue_tint_alpha = (t * 96).ifloor();
}
for(int i = 0; i < 16; i++) {
palette[i] = render_blend(
player_anim->frames[player_frame]->palette[i],
RGB24(0xb0d0f0), blue_tint_alpha);
}
render_anim_frame_palette((int)player_dot.x, (int)player_dot.y,
player_anim, player_frame, palette);
if(player->blueanim > 0) {
prof_enter(game.perf.effect_bpp);
render_effect_bpp(&_effect_bpp, player->blueanim);
azrp_image((int)player_dot.x - _effect_bpp->width / 2,
(int)player_dot.y - _effect_bpp->height + 2,
_effect_bpp);
prof_leave(game.perf.effect_bpp);
}
/* Render energy score */
azrp_rect(DWIDTH-100, 4, 96, 20, AZRP_RECT_DARKEN);
render_energy_str(DWIDTH-8, 8, DTEXT_RIGHT, 1, energy_str);
}
/* End transition and end screen */
else {
/* Background flash of light */
if(game.t_death < num(0.25)) {
int alpha = 31 - (int)(31 * (game.t_death / num(0.25)));
shader_fsblend(C_WHITE, alpha);
}
/* Background fading to end screen */
if(game.t_death > num(0.75)) {
if(bg_done) {
num t_fadeout = (game.t_death - num(0.75)) * num(2.0);
int alpha = (int)(16 * min(t_fadeout, num(1.0)));
shader_fsblend(C_BLACK, alpha);
}
else {
azrp_clear(render_blend(game.level.bgcolor, C_BLACK, 16));
}
}
/* Particle explosion */
if(game.t_death < TIME_DEATH_ANIMATION) {
int color_t = (int)(min(game.t_death, num(1.0)) * 32);
render_effect_dp((int)player_dot.x, (int)player_dot.y - 40,
256, game.t_death, render_blend(C_WHITE, 0x5555, color_t));
}
/* End screen elements appearing in order */
num t_endscreen = game.t_death - num(1.0);
int xe = 40, ye0 = 20, ye = ye0;
if(t_endscreen >= num(0.0)) {
azrp_print(xe, ye, C_WHITE, "experiments %s", game.level.name);
}
if(t_endscreen >= num(0.1)) {
ye += 15;
azrp_text(xe, ye, C_WHITE, "UNDISCOVERED");
}
if(t_endscreen >= num(0.2)) {
ye += 20;
azrp_text(xe, ye+2, C_WHITE, "energy");
azrp_text(xe, ye+11, C_WHITE, "reading");
render_energy_str(xe+65, ye, DTEXT_LEFT, 3, energy_str);
azrp_text(xe+65, ye+22, RGB24(0x9cefe3), "new peak");
}
if(t_endscreen >= num(0.3)) {
ye += 40;
azrp_text(xe, ye+1, C_WHITE, "old peak");
// TODO: Old energy str
render_energy_str(xe+65, ye, DTEXT_LEFT, 1, "0.00%");
}
if(t_endscreen >= num(0.4)) {
ye += 20;
azrp_text(xe, ye, C_WHITE, "100%");
azrp_rect(xe, ye+10, 160, 40, AZRP_RECT_WHITEN);
azrp_text(xe, ye+49, C_WHITE, "0%");
int h_footer = 30;
int y_footer = cubic(DHEIGHT, DHEIGHT - h_footer,
t_endscreen - num(0.4), num(0.2));
azrp_rect(0, y_footer, DWIDTH, h_footer, C_WHITE);
azrp_text_opt(8, y_footer + 14, NULL, RGB24(0x49759f),
DTEXT_LEFT, DTEXT_MIDDLE, "exit: back", -1);
azrp_text_opt(DWIDTH-8, y_footer + 14, NULL, RGB24(0x49759f),
DTEXT_RIGHT, DTEXT_MIDDLE, "SHIFT: AGAIN",-1);
}
if(t_endscreen >= num(0.5)) {
xe = 230;
ye = ye0;
// TODO: Run count
azrp_text(xe, ye, C_WHITE, "run count 42");
azrp_text(xe, ye+10, C_WHITE, "particle trace");
}
if(t_endscreen >= num(0.6)) {
ye += 30;
// TODO: Level-specific particle trace animation color?
shader_ptrace(xe, ye, C_RGB(24, 24, 8), game.level.bgcolor,
game.player.energy_percent >= 100, t_endscreen - num(0.6));
/*
// Debugging overflows on BPP effect
static num16 t = 0;
render_effect_bpp_internal(&_effect_bpp, t);
t.v = (t.v + 1) & 511;
azrp_image(azrp_width / 2 - _effect_bpp->width / 2,
azrp_height / 2 - _effect_bpp->height + 2,
_effect_bpp);
*/
}
}
azrp_update();
}