block platforms in level 2

This commit is contained in:
Lephenixnoir 2023-08-02 18:37:03 +02:00
parent 23716a7637
commit b147f74822
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
12 changed files with 381 additions and 27 deletions

View File

@ -115,6 +115,49 @@ struct prect space_platform_position(struct platform const *p)
return r;
}
struct frect space_block_position(struct platform const *p)
{
vec3 r1(0, -2 * PLATFORM_HEIGHT_STEP, p->z);
vec3 r2(0, -space_cylinder_quasiradius() + p->height, p->z);
int angle_l = p->face;
int angle_r = (p->face + 1) % PLATFORM_COUNT;
struct frect f;
f.tr = vec_rotate_around_z(r1, _platform_rotations[angle_r]);
f.tl = vec_rotate_around_z(r1, _platform_rotations[angle_l]);
f.br = vec_rotate_around_z(r2, _platform_rotations[angle_r]);
f.bl = vec_rotate_around_z(r2, _platform_rotations[angle_l]);
return f;
}
struct prect space_block_top_position(struct platform const *p)
{
vec3 r1(0, -2 * PLATFORM_HEIGHT_STEP, p->z);
int angle_l = p->face;
int angle_r = (p->face + 1) % PLATFORM_COUNT;
struct prect t;
t.nl = t.fl = vec_rotate_around_z(r1, _platform_rotations[angle_l]);
t.nr = t.fr = vec_rotate_around_z(r1, _platform_rotations[angle_r]);
t.fl.z += p->length;
t.fr.z += p->length;
return t;
}
struct srect space_wall_position(struct platform const *p)
{
vec3 r1(0, -2 * PLATFORM_HEIGHT_STEP, p->z);
vec3 r2(0, -space_cylinder_quasiradius() + p->height, p->z);
struct srect s;
s.nt = s.ft = vec_rotate_around_z(r1, _platform_rotations[p->face]);
s.nb = s.fb = vec_rotate_around_z(r2, _platform_rotations[p->face]);
s.ft.z += p->length;
s.fb.z += p->length;
return s;
}
//======= Game implementation =======//
void game_advance(struct game *game)

View File

@ -116,8 +116,11 @@ consteval num space_platform_arc()
platform itself cuts into it. */
num space_cylinder_quasiradius(void);
/* Position, in world units, of the provided flat platform. */
/* Position, in world units, of the provided platform. */
struct prect space_platform_position(struct platform const *p);
struct frect space_block_position(struct platform const *p);
struct prect space_block_top_position(struct platform const *p);
struct srect space_wall_position(struct platform const *p);
//======= Game management =======//

View File

@ -42,7 +42,10 @@ struct MultiPathCarver
~MultiPathCarver();
/* Set initial positions. */
void set_faces(int *faces /* size N */);
void set_faces(int const *faces /* size N */);
/* Get current positions. */
void get_faces(int *faces /* size N */);
/* Force the carver to generate all platforms and never skip. */
void set_noskip(bool noskip);
@ -130,6 +133,9 @@ struct AcceleronGenerator: public Generator
~AcceleronGenerator() override = default;
num generate_ascending_funnel(struct level *level, num z);
num generate_tunnel_block(struct level *level, num z, int sections);
void change_phase(void);
num m_z;
bool m_initial;

View File

@ -163,7 +163,6 @@ num GeonGenerator::generate_random_tunnel(struct level *level, num z, int sec)
return z;
}
void GeonGenerator::change_phase(void)
{
static int const freq_table[] = {

View File

@ -9,6 +9,8 @@
#define G_PLATFORM_LEN_INITIAL num(6.0)
/* Probabilities */
#define P_ASCF_RED num(0.2)
#define P_TUNNEL_RED num(0.2)
#define P_TUNNEL_RED_RING num(0.15)
AcceleronGenerator::AcceleronGenerator():
m_checkerboard_carver{3}
@ -39,9 +41,9 @@ AcceleronGenerator::AcceleronGenerator():
....R..... [h=8]
....|.....
....|.....
....|.....
...B...... [h=5]
...|.#.... [h=4]
....|#.... [h=4]
...B.|.... [h=5]
...|.|....
...|.|....
....#..... [h=1]
@ -91,18 +93,18 @@ num AcceleronGenerator::generate_ascending_funnel(struct level *level, num z)
p.length = G_PLATFORM_LEN * 3;
p.height = 8 * PLATFORM_HEIGHT_STEP;
add(level, p);
z += G_PLATFORM_LEN * 3 + G_PLATFORM_SPC;
z += G_PLATFORM_LEN * 2 + G_PLATFORM_SPC;
p.type = PLATFORM_BLUE;
p.face = 3;
p.z = z;
p.z = z + G_PLATFORM_LEN;
p.length = G_PLATFORM_LEN * 3;
p.height = 5 * PLATFORM_HEIGHT_STEP;
add(level, p);
p.type = PLATFORM_WHITE;
p.face = 5;
p.z = z + G_PLATFORM_LEN;
p.z = z;
p.length = G_PLATFORM_LEN * 2;
p.height = 4 * PLATFORM_HEIGHT_STEP;
add(level, p);
@ -121,6 +123,79 @@ num AcceleronGenerator::generate_ascending_funnel(struct level *level, num z)
return z;
}
/* Generates a tunnel block, which is full ring alternating with half rings.
The total length (which should be odd) is specified as a parameter.
0123456789
########## [h=0]
#.#.#.#.#. [h=0]
|.|.|.|.|.
########## [h=0]
(...repeats)
There is a random probability for any of the platforms to be red, and also a
random probability for entire rings to be red. */
num AcceleronGenerator::generate_tunnel_block(struct level *level, num z,
int sec)
{
for(int i = 0; i < sec; i++) {
num len = (1 + (i & 1)) * G_PLATFORM_LEN;
platform_type_t type = PLATFORM_WHITE;
if(num_rand() <= P_TUNNEL_RED_RING)
type = PLATFORM_RED;
/* Generate all/even platforms on even/odd i */
for(int j = 0; j < PLATFORM_COUNT; j++) {
if((i & j) & 1)
continue;
struct platform p;
p.type = (num_rand() <= P_TUNNEL_RED) ? PLATFORM_RED : type;
p.face = j;
p.z = z;
p.length = len;
p.height = 0;
add(level, p);
}
z += len;
}
return z;
}
void AcceleronGenerator::change_phase(void)
{
static int const freq_table[] = {
0, /* Ascending funnel */
1, /* Random paths */
2, /* Tunnel block */
3, /* Random path with walls */
4, /* Incomplete tunnel block */
};
int const FREQ_TABLE_SIZE = sizeof freq_table / sizeof freq_table[0];
int p = m_phase;
while(1) {
p = freq_table[rand() % FREQ_TABLE_SIZE];
/* Do not repeat the same phase twice in a row */
if(p == m_phase)
continue;
/* Only allow ascending funnel after tunnel blocks */
if(p == 0 && (m_phase != 2 && m_phase != 4))
continue;
/* Do not follow tunnel blocks together */
if((p == 2 && m_phase == 4) || (p == 4 && m_phase == 2))
continue;
break;
}
m_phase = p;
}
void AcceleronGenerator::generate(struct level *level)
{
/* Friendly platform at start of level */
@ -135,14 +210,16 @@ void AcceleronGenerator::generate(struct level *level)
add(level, p);
m_z += p.length;
m_initial = false;
m_phase = 3;
}
/* Phase #0: Ascending funnel and long/tight red platform */
if(m_phase == 0) {
m_z = this->generate_ascending_funnel(level, m_z);
m_checkerboard_carver.force_to_match(4);
m_phase = 1;
this->change_phase();
}
/* Phase #1: Checkerboard random paths */
else if(m_phase == 1) {
int length = 4 + 2 * (rand() % 2);
@ -150,7 +227,90 @@ void AcceleronGenerator::generate(struct level *level)
m_checkerboard_carver.next(level, m_z, G_PLATFORM_LEN);
m_z += G_PLATFORM_LEN + G_PLATFORM_SPC;
}
m_phase = 0;
this->change_phase();
}
/* Phase #2: Simple tunnel block */
else if(m_phase == 2) {
int length = 5 + 2 * (rand() % 2);
m_z = this->generate_tunnel_block(level, m_z, length);
this->change_phase();
}
/* Phase #3: Checkerboard random paths, with dividing walls */
else if(m_phase == 3) {
int length = 14;
bool occupied[PLATFORM_COUNT];
int faces[3];
for(int i = 0; i < length; i++) {
/* Add walls at i=2,5,8,11 on any face where there is no platform
at iteration i or iteration i+1. This is to prevent the
following situation:
W#W
#..
where you can't side-jump from the middle platform to the left
one because the wall prevents it. */
bool walls = (i == 2 || i == 5 || i == 8 || i == 1);
/* Start by excluding platforms from iteration #i */
if(walls) {
for(int j = 0; j < PLATFORM_COUNT; j++)
occupied[j] = false;
m_checkerboard_carver.get_faces(faces);
for(int j = 0; j < 3; j++)
occupied[faces[j]] = true;
}
/* Advance */
m_checkerboard_carver.next(level, m_z, G_PLATFORM_LEN);
/* Generate blocks and walls */
if(walls) {
/* Exclude platforms from iteration #i+1 */
m_checkerboard_carver.get_faces(faces);
for(int j = 0; j < 3; j++)
occupied[faces[j]] = true;
for(int j = 0; j < PLATFORM_COUNT; j++) {
/* Generate blocks in empty spaces */
if(!occupied[j]) {
struct platform p;
p.type = PLATFORM_BLOCK;
p.face = j;
p.z = m_z;
p.length = G_PLATFORM_LEN;
p.height = 0.0;
add(level, p);
p.type = PLATFORM_BLOCK_TOP;
add(level, p);
}
/* Generate dividing walls on edges */
if(occupied[j] != occupied[(j+1) % PLATFORM_COUNT]) {
struct platform p;
p.type = PLATFORM_SEPARATING_WALL;
p.face = (j+1) % PLATFORM_COUNT;
p.z = m_z;
p.length = G_PLATFORM_LEN;
p.height = 0.0;
p.wall_left_sided = !occupied[j];
p.wall_right_sided = occupied[j];
add(level, p);
}
}
}
m_z += G_PLATFORM_LEN + G_PLATFORM_SPC;
}
this->change_phase();
}
/* Phase #4: Incomplete tunnel block */
else if(m_phase == 4) {
// TODO
this->change_phase();
}
/* Phase #1: Almost-full tunnel with random holes? */

View File

@ -55,12 +55,19 @@ MultiPathCarver::~MultiPathCarver()
delete[] m_paths;
}
void MultiPathCarver::set_faces(int *faces)
void MultiPathCarver::set_faces(int const *faces)
{
for(int i = 0; i < m_N; i++)
m_paths[i].face = faces[i];
}
void MultiPathCarver::get_faces(int *faces)
{
for(int i = 0; i < m_N; i++) {
faces[i] = m_paths[i].face;
}
}
void MultiPathCarver::set_noskip(bool noskip)
{
m_noskip = noskip;

View File

@ -2,6 +2,7 @@
#include "generator.h"
#include "util.h"
#include <gint/display.h>
#include <algorithm>
struct level level_create(int level)
{
@ -27,10 +28,51 @@ struct level level_create(int level)
return l;
}
void level_update(struct level *level, num z)
static int local_depth(struct platform const &p)
{
if(p.type == PLATFORM_BLOCK)
return 0;
else if(p.type == PLATFORM_BLOCK_TOP)
return 2;
else
return 1;
}
static int relative_distance(struct platform const &p, int face)
{
int d = abs(p.face - face) % PLATFORM_COUNT;
return (d <= PLATFORM_COUNT / 2) ? d : PLATFORM_COUNT - d;
}
/* Hack */
static int reference_face = 0;
static bool compare_platforms(struct platform const &p1,
struct platform const &p2)
{
if(p1.z != p2.z)
return p1.z < p2.z;
int d1 = local_depth(p1);
int d2 = local_depth(p2);
if(d1 != d2)
return d1 < d2;
int rd1 = relative_distance(p1, reference_face);
int rd2 = relative_distance(p2, reference_face);
return rd1 < rd2;
}
void level_update(struct level *level, num z, int face)
{
while(!level->platform_buffer.size()
|| level->platform_buffer[level->platform_buffer.size() - 1].z
< z + RENDER_DEPTH)
level->gen->generate(level);
reference_face = face;
std::stable_sort(level->platform_buffer.begin(),
level->platform_buffer.end(),
compare_platforms);
}

View File

@ -10,7 +10,9 @@ typedef enum {
PLATFORM_WHITE,
PLATFORM_RED,
PLATFORM_BLUE,
PLATFORM_BLOCK
PLATFORM_SEPARATING_WALL,
PLATFORM_BLOCK,
PLATFORM_BLOCK_TOP,
} platform_type_t;
struct platform {
@ -27,6 +29,10 @@ struct platform {
/* Whether the platform is currently falling. This is only used for red
platforms. */
bool falling;
/* Whether the SEPARATING_WALL platform is seen from the left side and/or
seen from the right side. */
bool wall_left_sided;
bool wall_right_sided;
};
struct Generator;
@ -40,7 +46,7 @@ struct level {
extern void level_update(struct level *level, num z);
extern void level_update(struct level *level, num z, int reference_face);
extern struct level level_create(int level);
#endif /* __LEVEL_H__ */

View File

@ -137,7 +137,7 @@ int play_level(int level_id)
player->energy_percent = 350.0;
player->height = num(-10.0);
game.t_death = num(10.0);
level_update(&game.level, num(350.0));
level_update(&game.level, num(350.0), 0);
}
bool game_run = true;
@ -152,7 +152,7 @@ int play_level(int level_id)
prof_t perf_frame = prof_make();
prof_enter_norec(perf_frame);
level_update(&game.level, player->z);
level_update(&game.level, player->z, player->platform);
camera_track(camera, player);
struct platform *standing_on = game_platform_under_player(&game,

View File

@ -73,6 +73,22 @@ void camera_project_prect(struct camera *camera, struct prect *p)
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
@ -137,6 +153,10 @@ uint16_t render_platform_color(struct platform const *p,
return camera_platform_color(camera, p->face);
if(p->type == PLATFORM_BLUE)
return render_color_blue_platform(t);
if(p->type == PLATFORM_SEPARATING_WALL || p->type == PLATFORM_BLOCK_TOP)
return RGB24(0x909090);
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)
@ -338,21 +358,72 @@ uint16_t render_blend(uint16_t c1, uint16_t c2, int t)
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);
struct prect p = space_platform_position(&*it);
/* Near plane clipping */
num near = game.camera.pos.z + game.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);
if(it->type == PLATFORM_SEPARATING_WALL) {
struct srect side = space_wall_position(&*it);
render_triangle(&p.nl, &p.fr, &p.fl, color);
render_triangle(&p.fr, &p.nl, &p.nr, color);
/* 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);
}
}
}

View File

@ -66,8 +66,11 @@ void camera_track(struct camera *, struct player const *);
member is kept unchanged. */
vec3 camera_project_point(struct camera *, vec3 point);
/* Optimized camera_project_point() that projects an entire prect in-place. */
/* Optimized camera_project_point() that projects an entire prect, frect or
srect in-place. */
void camera_project_prect(struct camera *, struct prect *rect);
void camera_project_frect(struct camera *, struct frect *frect);
void camera_project_srect(struct camera *, struct srect *srect);
/* Compute the platform's color based on the camera's angle. */
int camera_platform_color(struct camera const *camera, int platform_id);

View File

@ -13,6 +13,20 @@ struct prect
vec3 fl, fr; /* Far left and far right points */
};
/* A side rectangle, aligned with the camera's view */
struct srect
{
vec3 nt, nb; /* Near top and near bottom points */
vec3 ft, fb; /* Far top and far bottom points */
};
/* A flat rectangle, normal to the camera's view */
struct frect
{
vec3 tl, tr; /* Top left an top right points */
vec3 bl, br; /* Bottom left and bottom right points */
};
/* Approximations of the sine and cosine of an angle in radians. */
num num_cos(num a);
num num_sin(num a);