Flatten chunk and entity managers

This commit is contained in:
2025-07-23 18:48:46 +02:00
parent d09cd20cdc
commit 09c142e923
18 changed files with 543 additions and 686 deletions
-304
View File
@@ -1,304 +0,0 @@
#include "chunkman.h"
static GHashTable *chunks;
static GHashTable *chunk_meshes;
static gboolean bound = false;
static Profiler profiler;
static const int frame_budget_us = 500;
guint
chunkman_ivec2_hash (gconstpointer key)
{
/* This makes sure there's no collisions, thus making the whole
table much, much faster. */
const ivec2 *v = key;
guint x = (guint) (*v)[0];
guint y = (guint) (*v)[1];
return (x * 73856093u) ^ (y * 19349663u);
}
gboolean
chunkman_ivec2_equal (gconstpointer a, gconstpointer b)
{
return memcmp (a, b, sizeof (int) * 2) == 0;
}
ivec2 *
chunkman_make_key(int x, int z)
{
ivec2 key = { x, z };
return g_memdup2 (&key, sizeof (ivec2));
}
void
chunkman_world_to_chunk (float x, float z, int *cx, int *cz)
{
*cx = floor (x / (float) CHUNK_DIM);
*cz = floor (z / (float) CHUNK_DIM);
}
static inline float
c_get_height (Chunk *c, int x, int z)
{
return c ? c->heightmap[INDEX (x, z)] : 0.0;
}
float
chunkman_get_height_at (float x, float z)
{
int cx, cz;
chunkman_world_to_chunk (x, z, &cx, &cz);
Chunk *chunk = chunkman_get_Chunk_at (cx, cz);
if (chunk == NULL)
{
return 0.0;
}
float lx = x - cx * CHUNK_DIM;
float lz = z - cz * CHUNK_DIM;
int x0 = (int) floor (lx);
int z0 = (int) floor (lz);
int x1 = x0 + 1;
int z1 = z0 + 1;
float tx = lx - x0;
float tz = lz - z0;
Chunk *r = NULL, *d = NULL, *c = NULL;
chunkman_get_Chunk_neighbors_at (cx, cz, &r, &d, &c);
float h00 = c_get_height (chunk, x0, z0);
float h10 = (x1 < CHUNK_DIM) ? c_get_height (chunk, x1, z0) :
(r ? c_get_height (r, 0, z0) : h00);
float h01 = (z1 < CHUNK_DIM) ? c_get_height (chunk, x0, z1) :
(d ? c_get_height (d, x0, 0) : h00);
float h11;
if (x1 < CHUNK_DIM && z1 < CHUNK_DIM)
h11 = c_get_height (chunk, x1, z1);
else if (x1 >= CHUNK_DIM && z1 < CHUNK_DIM)
h11 = r ? c_get_height (r, 0, z1) : h10;
else if (x1 < CHUNK_DIM && z1 >= CHUNK_DIM)
h11 = d ? c_get_height (d, x1, 0) : h01;
else
h11 = c ? c_get_height (c, 0, 0) : h00;
// Bilinear interpolation.
float h0 = h00 * (1 - tx) + h10 * tx;
float h1 = h01 * (1 - tx) + h11 * tx;
float height = h0 * (1 - tz) + h1 * tz;
return height;
}
void
chunkman_init ()
{
chunks = g_hash_table_new_full (chunkman_ivec2_hash, chunkman_ivec2_equal, free, (GDestroyNotify) Chunk_free);
chunk_meshes = g_hash_table_new_full (chunkman_ivec2_hash, chunkman_ivec2_equal, free, (GDestroyNotify) Chunk_free);
}
void
chunkman_tick (vec3 position)
{
int cx, cz;
chunkman_world_to_chunk (position[0], position[2], &cx, &cz);
Profiler_begin (&profiler);
int render_distance = gvar_get ("render_distance");
for (int z = cz - render_distance - 1; z <= cz + render_distance + 1; ++z)
{
for (int x = cx - render_distance - 1; x <= cx + render_distance + 1; ++x)
{
double elapsed = Profiler_sample (&profiler);
if (elapsed > frame_budget_us)
{
return;
}
if (chunkman_get_Chunk_at (x, z) == NULL)
{
chunkman_generate_Chunk_at (x, z);
}
}
}
for (int z = cz - render_distance; z <= cz + render_distance; ++z)
{
for (int x = cx - render_distance; x <= cx + render_distance; ++x)
{
double elapsed = Profiler_sample (&profiler);
if (elapsed > frame_budget_us)
{
return;
}
if (chunkman_get_ChunkMesh_at (x, z) == NULL)
{
chunkman_generate_ChunkMesh_at (x, z);
}
}
}
}
void
chunkman_render (vec3 position, vec4 planes[6])
{
#if !FRUSTUM_CULL
UNUSED (planes);
#endif
int cx = 0, cz = 0;
chunkman_world_to_chunk (position[0], position[2], &cx, &cz);
program_use (PMPRG ("chunk"));
texture_use (PMTEX ("terrain0"), 0);
texture_use (PMTEX ("terrain1"), 1);
texture_use (PMTEX ("terrain2"), 2);
texture_use (PMTEX ("terrain3"), 3);
texture_use (PMTEX ("terrain4"), 4);
int render_distance = gvar_get ("render_distance");
int frame_chunks = 0;
for (int z = cz - render_distance; z <= cz + render_distance; ++z)
{
for (int x = cx - render_distance; x <= cx + render_distance; ++x)
{
/* Render in a circle instead of a square. */
if ((x - cx) * (x - cx) + (z - cz) * (z - cz) > render_distance*render_distance)
{
continue;
}
ChunkMesh *mesh = chunkman_get_ChunkMesh_at (x, z);
if (mesh != NULL)
{
#if FRUSTUM_CULL
vec3 aabb[2] = {
{ x * CHUNK_DIM, 0.0, z * CHUNK_DIM },
{ (x + 1) * CHUNK_DIM, TERRAIN_HEIGHT, (z + 1) * CHUNK_DIM }
};
if (!glm_aabb_frustum (aabb, planes))
{
continue;
}
#endif
if (!bound)
{
ChunkMesh_bind (mesh);
bound = true;
}
program_set_vec3_ind (PMPRG ("chunk"), "chunk_position", x * CHUNK_DIM, 0.0, z * CHUNK_DIM);
ChunkMesh_render (mesh, FLAG_GET ("wireframe_enabled"));
frame_chunks++;
}
}
}
bound = false;
gvar_set ("frame_chunks", frame_chunks);
}
Chunk *
chunkman_get_Chunk_at (int x, int z)
{
ivec2 key = { x, z };
return (Chunk *) g_hash_table_lookup (chunks, &key);
}
void
chunkman_set_Chunk_at (int x, int z, Chunk *chunk)
{
g_hash_table_insert (chunks, chunkman_make_key (x, z), chunk);
}
void
chunkman_generate_Chunk_at (int cx, int cz)
{
Chunk *chunk = Chunk_new ();
int xoffset = cx * CHUNK_DIM;
int zoffset = cz * CHUNK_DIM;
for (int z = 0; z < CHUNK_DIM; ++z)
{
for (int x = 0; x < CHUNK_DIM; ++x)
{
chunk->heightmap[INDEX (x, z)] =
(glm_perlin_vec2
((vec2) { (x + xoffset + 0.333) / 10.0, (z + zoffset + 0.333) / 10.0 }) + 0.6) * 2;
chunk->materialmap[INDEX (x, z)] = (int)
(glm_perlin_vec2 ((vec2) { (x + xoffset + 0.333) / 20.0, (z + zoffset + 0.333) / 20.0 }) + 0.6 * 5) % 5;
if (rand() % 100 < 2)
{
entman_add_spawn_request
("tree",
(vec3) { x + xoffset, chunk->heightmap[INDEX (x, z)], z + zoffset },
(vec3) { 0.0, rand () % 180, 0.0 },
(vec3) { 3.0, (rand () % 5) + 5, 3.0 });
}
if (rand() % 100 < 10)
{
entman_add_spawn_request
("grass",
(vec3) { x + xoffset, chunk->heightmap[INDEX (x, z)], z + zoffset },
(vec3) { 0.0, 0.0, 0.0 },
(vec3) { 1.0, 1.0, 1.0 });
}
}
}
chunkman_set_Chunk_at (cx, cz, chunk);
}
void
chunkman_get_Chunk_neighbors_at (int x, int z, Chunk **nr, Chunk **nd, Chunk **nc)
{
*nr = chunkman_get_Chunk_at (x + 1, z);
*nd = chunkman_get_Chunk_at (x, z + 1);
*nc = chunkman_get_Chunk_at (x + 1, z + 1);
}
ChunkMesh *
chunkman_get_ChunkMesh_at (int x, int z)
{
ivec2 key = { x, z };
return (ChunkMesh *) g_hash_table_lookup (chunk_meshes, key);
}
void
chunkman_set_ChunkMesh_at (int x, int z, ChunkMesh *mesh)
{
g_hash_table_insert (chunk_meshes, chunkman_make_key (x, z), mesh);
}
void
chunkman_generate_ChunkMesh_at (int x, int z)
{
Chunk *nr, *nd, *nc;
chunkman_get_Chunk_neighbors_at (x, z, &nr, &nd, &nc);
g_assert (nr != NULL);
g_assert (nd != NULL);
g_assert (nc != NULL);
chunkman_set_ChunkMesh_at
(x, z, ChunkMesh_build (chunkman_get_Chunk_at (x, z), nr, nd, nc));
}
-31
View File
@@ -1,31 +0,0 @@
#pragma once
/* Yes, glib. */
#include <glib-2.0/glib.h>
#include "chunk.h"
#include "chunkmesh.h"
#include "../gl/program.h"
#include "../entity/entman.h"
#include "../graphics.h"
#include "../gvar.h"
#include "../pakman.h"
#include "../profiler.h"
#define TERRAIN_HEIGHT 8.0
void chunkman_init ();
void chunkman_tick (vec3 position);
void chunkman_render (vec3 position, vec4 planes[6]);
float chunkman_get_height_at (float x, float z);
Chunk *chunkman_get_Chunk_at (int x, int z);
void chunkman_get_Chunk_neighbors_at (int x, int z, Chunk **nr, Chunk **nd, Chunk **nc);
void chunkman_set_Chunk_at (int x, int z, Chunk *chunk);
ChunkMesh *chunkman_get_ChunkMesh_at (int x, int z);
void chunkman_set_ChunkMesh_at (int x, int z, ChunkMesh *mesh);
void chunkman_generate_Chunk_at (int x, int z);
void chunkman_generate_ChunkMesh_at (int x, int z);
+114
View File
@@ -1,5 +1,7 @@
#include "entity.h"
extern SimpleMesh mesh_quad1, mesh_quad2, mesh_quad_x;
Entity *
Entity_new ()
{
@@ -9,3 +11,115 @@ Entity_new ()
e->origin = ORIGIN_BOTTOM; /* Usually is. */
return e;
}
gboolean
Entity_render (Entity *entity, vec3 camera_position, mat4 view, vec4 frustum_planes[6])
{
if (!entity->visible)
{
return FALSE;
}
int render_distance = gvar_get ("render_distance");
gboolean wireframe_enabled = gvar_get ("wireframe_enabled");
/* Why an int, why int division? It's simply much faster than
floating point operations. No need to have super accurate cull
distance anyway, and might as well save a lot when rendering entities. */
unsigned int cull_dist = (render_distance * 16 * entity->cull_distance) / 100;
/* Underneath is the slowest (for the CPU) part of the entity render
process. Moving the camera positions from memory into some
registers. Thus the entity rendering function optimization is
finished, as the last bottleneck is the amount of entities. */
vec2 c_xz = { camera_position[0], camera_position[2] };
vec2 e_xz = { entity->position[0], entity->position[2] };
if (glm_vec2_distance2 (c_xz, e_xz) > cull_dist*cull_dist && !entity->dont_cull)
{
return FALSE;
}
vec3 aabb[2];
entity_compute_aabb (entity, aabb[0], aabb[1]);
if (!glm_aabb_frustum (aabb, frustum_planes))
{
return FALSE;
}
SimpleMesh to_render;
unsigned int render_type = entity->render_type;
switch (entity->mesh_type)
{
case MESH_QUAD_X:
to_render = mesh_quad_x;
break;
case MESH_QUAD1:
to_render = mesh_quad1;
break;
default:
to_render = mesh_quad2;
break;
}
mat4 model = GLM_MAT4_IDENTITY_INIT;
switch (render_type)
{
case RENDER_BILLBOARD:
mat4 inv_view;
glm_mat4_copy (view, inv_view);
glm_mat4_inv (inv_view, inv_view);
vec3 right = { inv_view[0][0], inv_view[1][0], inv_view[2][0] };
vec3 up = { inv_view[0][1], inv_view[1][1], inv_view[2][1] };
vec3 forward = { inv_view[0][2], inv_view[1][2], inv_view[2][2] };
mat4 rotation = GLM_MAT4_IDENTITY_INIT;
for (int i = 0; i < 3; ++i)
{
rotation[i][0] = right[i];
rotation[i][1] = up[i];
rotation[i][2] = forward[i];
}
glm_translate (model, entity->position);
glm_mat4_mul (model, rotation, model);
break;
case RENDER_BILLBOARD_Y:
/* It's not quite a "real" billboard, but the effect is much
better on the eye. */
vec3 delta;
glm_vec3_sub (camera_position, entity->position, delta);
delta[1] = 0.0;
glm_normalize (delta);
glm_rotate_at(model, entity->position, atan2f (delta[0], delta[2]), (vec3) { 0.0, 1.0, 0.0 });
glm_translate (model, entity->position);
break;
default:
glm_rotate_at (model, entity->position, glm_rad (entity->rotation[0]), (vec3) { 1.0, 0.0, 0.0 });
glm_rotate_at (model, entity->position, glm_rad (entity->rotation[1]), (vec3) { 0.0, 1.0, 0.0 });
glm_rotate_at (model, entity->position, glm_rad (entity->rotation[2]), (vec3) { 0.0, 0.0, 1.0 });
glm_translate (model, entity->position);
break;
}
if (entity->origin == ORIGIN_BOTTOM)
{
glm_translate (model, (vec3) { 0.0, entity->scale[1], 0.0 });
}
glm_scale (model, entity->scale);
program_use (entity->program);
program_set_mat4 (entity->program, "model", model);
program_set_float (entity->program, "wavy", entity->wavy);
texture_use (entity->texture, 0);
SimpleMesh_bind (&to_render);
SimpleMesh_render (&to_render, wireframe_enabled);
return TRUE;
}
+14 -8
View File
@@ -3,6 +3,11 @@
#include <glib-2.0/glib.h>
#include <cglm/cglm.h>
#include "../gvar.h"
#include "../gl/mesh_globals.h"
#include "../gl/program.h"
#include "../gl/texture.h"
typedef struct Entity
{
enum { MESH_QUAD1, MESH_QUAD2, MESH_QUAD_X } mesh_type;
@@ -37,18 +42,19 @@ entity_compute_aabb (Entity *e, vec3 out_min, vec3 out_max)
out_max[0] = e->position[0] + half[0];
if (e->origin == ORIGIN_BOTTOM)
{
out_min[1] = e->position[1];
out_max[1] = e->position[1] + 2.0f * half[1];
}
{
out_min[1] = e->position[1];
out_max[1] = e->position[1] + 2.0f * half[1];
}
else
{
out_min[1] = e->position[1] - half[1];
out_max[1] = e->position[1] + half[1];
}
{
out_min[1] = e->position[1] - half[1];
out_max[1] = e->position[1] + half[1];
}
out_min[2] = e->position[2] - half[2];
out_max[2] = e->position[2] + half[2];
}
Entity *Entity_new ();
gboolean Entity_render (Entity *entity, vec3 camera_position, mat4 view, vec4 frustum_planes[6]);
-269
View File
@@ -1,269 +0,0 @@
#include "entman.h"
/* TODO: What COULD be done, but not necessarily SHOULD be done, is to
generate a mesh for say, 16 entities at once, then, the number of
drawcalls would drop substantially. */
/* TODO: Instead of having a huge far plane, render the clouds
seperately somehow. */
Entity *player_entity, *sky_entity;
/* The GArray is the fastest of all sequence types. Should work fine
for a billion entities. */
static GArray *entities;
static GQueue *spawn_requests;
static SimpleMesh mesh_quad1, mesh_quad2, mesh_quad_x;
static unsigned int last_program = 0;
static unsigned int last_type = 0;
static unsigned int last_texture = 0;
/* These are defined as statics so we don't get those values from gvar
for every entity... */
static int render_distance = 2;
static gboolean wireframe_enabled = FALSE;
static Profiler profiler = { 0 };
static const int frame_budget_us = 500;
void
character_on_tick (Entity *entity)
{
if (!FLAG_GET ("flight_enabled"))
{
entity->position[1] = chunkman_get_height_at (entity->position[0], entity->position[2]);
}
}
void
entman_init ()
{
entities = g_array_new (false, false, sizeof (Entity *));
spawn_requests = g_queue_new ();
mesh_quad1 = SimpleMesh_make_quad1 ();
mesh_quad2 = SimpleMesh_make_quad2 ();
mesh_quad_x = SimpleMesh_make_quad_x ();
player_entity = Entity_new ();
player_entity->visible = false;
glm_vec3_copy ((vec3) { 1.0, 1.8, 1.0 }, player_entity->scale);
player_entity->on_tick = character_on_tick;
ENTADD (player_entity);
sky_entity = Entity_new ();
sky_entity->mesh_type = MESH_QUAD1;
sky_entity->origin = ORIGIN_CENTER;
sky_entity->dont_cull = true;
glm_vec3_copy ((vec3) { 5000.0, 5000.0, 5000.0 }, sky_entity->scale);
glm_vec3_copy ((vec3) { 0.0, 200.0, 0.0 }, sky_entity->position);
glm_vec3_copy ((vec3) { 90.0, 0.0, 0.0 }, sky_entity->rotation);
sky_entity->texture = PMTEX ("clouds");
sky_entity->program = PMPRG ("clouds");
ENTADD (sky_entity);
}
void
spawn_queue_process_one ()
{
if (g_queue_is_empty(spawn_requests))
{
return;
}
SpawnRequest *req = g_queue_pop_head (spawn_requests);
Entity *e = ent_templates_clone (req->template_name);
glm_vec3_copy (req->position, e->position);
glm_vec3_copy (req->rotation, e->rotation);
glm_vec3_copy (req->scale, e->scale);
ENTADD (e);
g_free (req->template_name);
g_free (req);
}
void
entman_tick ()
{
Profiler_begin (&profiler);
while (!g_queue_is_empty (spawn_requests))
{
double elapsed = Profiler_sample (&profiler);
if (elapsed > frame_budget_us)
{
break;
}
spawn_queue_process_one ();
}
/* Handle on tick events. */
for (guint i = 0; i < entities->len; ++i)
{
Entity *e = g_array_index (entities, Entity*, i);
if (e->on_tick)
{
e->on_tick (e);
}
}
}
gboolean
entman_render_entity (Entity *entity, vec3 camera_position, mat4 view, vec4 frustum_planes[6])
{
if (!entity->visible)
{
return FALSE;
}
/* Why an int, why int division? It's simply much faster than
floating point operations. No need to have super accurate cull
distance anyway, and might as well save a lot when rendering entities. */
unsigned int cull_dist = (render_distance * CHUNK_DIM * entity->cull_distance) / 100;
/* Underneath is the slowest (for the CPU) part of the entity render
process. Moving the camera positions from memory into some
registers. Thus the entity rendering function optimization is
finished, as the last bottleneck is the amount of entities. */
vec2 c_xz = { camera_position[0], camera_position[2] };
vec2 e_xz = { entity->position[0], entity->position[2] };
if (glm_vec2_distance2 (c_xz, e_xz) > cull_dist*cull_dist && !entity->dont_cull)
{
return FALSE;
}
#if FRUSTUM_CULL
vec3 aabb[2];
entity_compute_aabb (entity, aabb[0], aabb[1]);
if (!glm_aabb_frustum (aabb, frustum_planes))
{
return FALSE;
}
#endif
SimpleMesh to_render;
unsigned int render_type = entity->render_type;
switch (entity->mesh_type)
{
case MESH_QUAD_X:
to_render = mesh_quad_x;
break;
case MESH_QUAD1:
to_render = mesh_quad1;
break;
default:
to_render = mesh_quad2;
break;
}
mat4 model = GLM_MAT4_IDENTITY_INIT;
switch (render_type)
{
case RENDER_BILLBOARD:
mat4 inv_view;
glm_mat4_copy (view, inv_view);
glm_mat4_inv (inv_view, inv_view);
vec3 right = { inv_view[0][0], inv_view[1][0], inv_view[2][0] };
vec3 up = { inv_view[0][1], inv_view[1][1], inv_view[2][1] };
vec3 forward = { inv_view[0][2], inv_view[1][2], inv_view[2][2] };
mat4 rotation = GLM_MAT4_IDENTITY_INIT;
for (int i = 0; i < 3; ++i)
{
rotation[i][0] = right[i];
rotation[i][1] = up[i];
rotation[i][2] = forward[i];
}
glm_translate (model, entity->position);
glm_mat4_mul (model, rotation, model);
break;
case RENDER_BILLBOARD_Y:
/* It's not quite a "real" billboard, but the effect is much
better on the eye. */
vec3 delta;
glm_vec3_sub (camera_position, entity->position, delta);
delta[1] = 0.0;
glm_normalize (delta);
glm_rotate_at(model, entity->position, atan2f (delta[0], delta[2]), (vec3) { 0.0, 1.0, 0.0 });
glm_translate (model, entity->position);
break;
default:
glm_rotate_at (model, entity->position, glm_rad (entity->rotation[0]), (vec3) { 1.0, 0.0, 0.0 });
glm_rotate_at (model, entity->position, glm_rad (entity->rotation[1]), (vec3) { 0.0, 1.0, 0.0 });
glm_rotate_at (model, entity->position, glm_rad (entity->rotation[2]), (vec3) { 0.0, 0.0, 1.0 });
glm_translate (model, entity->position);
break;
}
if (entity->origin == ORIGIN_BOTTOM)
{
glm_translate (model, (vec3) { 0.0, entity->scale[1], 0.0 });
}
glm_scale (model, entity->scale);
if (entity->program != last_program)
{
last_program = program_use (entity->program);
}
program_set_mat4 (entity->program, "model", model);
program_set_float (entity->program, "wavy", entity->wavy);
if (entity->texture != last_texture)
{
last_texture = texture_use (entity->texture, 0);
}
if (entity->mesh_type != last_type)
{
SimpleMesh_bind (&to_render);
last_type = entity->mesh_type;
}
SimpleMesh_render (&to_render, wireframe_enabled);
return TRUE;
}
void
entman_render (vec3 camera_position, mat4 view, vec4 planes[6])
{
int frame_entities = 0;
render_distance = gvar_get ("render_distance");
wireframe_enabled = gvar_get ("wireframe_enabled");
for (guint i = 0; i < entities->len; ++i)
{
Entity *e = g_array_index (entities, Entity*, i);
frame_entities += entman_render_entity (e, camera_position, view, planes) ? 1 : 0;
}
gvar_set ("frame_entities", frame_entities);
}
void
entman_add_entity (Entity *entity)
{
g_array_append_val (entities, entity);
}
void
entman_add_spawn_request (char *template_name, vec3 position, vec3 rotation, vec3 scale)
{
SpawnRequest *req = g_new (SpawnRequest, 1);
req->template_name = g_strdup (template_name);
glm_vec3_copy (position, req->position);
glm_vec3_copy (rotation, req->rotation);
glm_vec3_copy (scale, req->scale);
g_queue_push_tail (spawn_requests, req);
}
-33
View File
@@ -1,33 +0,0 @@
#pragma once
/* TODO: Add a type of spawn request; either TYPE_TEMPLATE, or
TYPE_ENTITY. */
#include <glib-2.0/glib.h>
#include <cglm/cglm.h>
#include "entity.h"
#include "templates.h"
#include "../gl/mesh.h"
#include "../util.h"
#include "../chunk/chunkman.h"
#include "../graphics.h"
#include "../gvar.h"
#include "../pakman.h"
#include "../profiler.h"
#define ENTADD(entity) (entman_add_entity (entity))
typedef struct SpawnRequest
{
char *template_name;
vec3 position;
vec3 rotation;
vec3 scale;
} SpawnRequest;
void entman_init ();
void entman_tick ();
void entman_render (vec3 camera_position, mat4 view, vec4 planes[6]);
void entman_add_entity (Entity *entity);
void entman_add_spawn_request (char *template_name, vec3 position, vec3 rotation, vec3 scale);
+15
View File
@@ -0,0 +1,15 @@
#include "mesh_globals.h"
SimpleMesh mesh_quad1_ui;
SimpleMesh mesh_quad1;
SimpleMesh mesh_quad2;
SimpleMesh mesh_quad_x;
void
mesh_globals_init ()
{
mesh_quad1_ui = SimpleMesh_make_quad1_ui ();
mesh_quad1 = SimpleMesh_make_quad1 ();
mesh_quad2 = SimpleMesh_make_quad2 ();
mesh_quad_x = SimpleMesh_make_quad_x ();
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "mesh.h"
void mesh_globals_init ();
-2
View File
@@ -8,8 +8,6 @@
#include "util.h"
#include "gvar.h"
#define FRUSTUM_CULL 1
void graphics_init ();
void graphics_prerender ();
void graphics_postrender ();
+3 -6
View File
@@ -2,7 +2,6 @@
#include <SDL3/SDL_main.h>
#include <libguile.h>
#include "chunk/chunkman.h"
#include "entity/templates.h"
#include "entity/entity_guile.h"
#include "ui/widget_guile.h"
@@ -11,6 +10,8 @@
#include "pakman.h"
#include "stage/stageman.h"
#include "gl/mesh_globals.h"
SDL_AppResult
SDL_AppInit (void **appstate, int argc, char **argv)
{
@@ -36,14 +37,10 @@ SDL_AppInit (void **appstate, int argc, char **argv)
pakman_load_pak ("res/");
stageman_init ();
/* I don't know. This probably needs to be moved somewhere. */
ui_widget_mesh_init ();
mesh_globals_init ();
/* The UI, entity and chunk subsystems. */
ent_templates_init ();
entman_init ();
chunkman_init ();
stageman_switch_stage ("main_menu");
+11 -8
View File
@@ -6,7 +6,10 @@ GameStage_new ()
GameStage *g = g_new0 (GameStage, 1);
g->ui_context = UIContext_new ();
g->ui_context->tooltip = PMUID ("tooltip");
g->world = World_new ();
g->camera = g_new0 (Camera, 1);
return g;
}
@@ -18,14 +21,14 @@ GameStage_register_UIContext_handlers (GameStage *self)
christ, it's so complex... */
#define GAEH GameStage_add_event_handler
GAEH (self, 1, SDL_EVENT_WINDOW_RESIZED, NULL, NULL, UIContext_window_resize,self->ui_context);
GAEH (self, 1, SDL_EVENT_KEY_DOWN, evt_pred_flag, "ui_has_focus", UIContext_key_down, self->ui_context);
GAEH (self, 1, SDL_EVENT_KEY_UP, evt_pred_flag, "ui_has_focus", UIContext_key_up, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_MOTION, evt_pred_flag, "ui_has_focus", UIContext_mouse_motion, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_BUTTON_DOWN, evt_pred_flag, "ui_has_focus", UIContext_button_down, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_BUTTON_UP, evt_pred_flag, "ui_has_focus", UIContext_button_up, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_WHEEL, evt_pred_flag, "ui_has_focus", UIContext_mouse_wheel, self->ui_context);
GAEH (self, 1, SDL_EVENT_TEXT_INPUT, evt_pred_flag, "ui_has_focus", UIContext_text_input, self->ui_context);
GAEH (self, 1, SDL_EVENT_WINDOW_RESIZED, NULL, NULL, UIContext_window_resize, self->ui_context);
GAEH (self, 1, SDL_EVENT_KEY_DOWN, evt_pred_flag, "ui_has_focus", UIContext_key_down, self->ui_context);
GAEH (self, 1, SDL_EVENT_KEY_UP, evt_pred_flag, "ui_has_focus", UIContext_key_up, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_MOTION, evt_pred_flag, "ui_has_focus", UIContext_mouse_motion, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_BUTTON_DOWN, evt_pred_flag, "ui_has_focus", UIContext_button_down, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_BUTTON_UP, evt_pred_flag, "ui_has_focus", UIContext_button_up, self->ui_context);
GAEH (self, 1, SDL_EVENT_MOUSE_WHEEL, evt_pred_flag, "ui_has_focus", UIContext_mouse_wheel, self->ui_context);
GAEH (self, 1, SDL_EVENT_TEXT_INPUT, evt_pred_flag, "ui_has_focus", UIContext_text_input, self->ui_context);
#undef GAEH
}
+4 -3
View File
@@ -8,6 +8,7 @@
#include "../util.h"
#include "../gvar.h"
#include "../camera.h"
#include "../world.h"
typedef struct {
unsigned int on_event;
@@ -21,10 +22,10 @@ typedef struct GameStage
{
UIContext *ui_context; /* Could be NULL... */
Camera *camera; /* Doesn't have to be defined. */
GList *event_handlers;
void *userdata;
World *world;
GList *event_handlers;
void (*on_enter) (struct GameStage *);
void (*on_exit) (struct GameStage *);
void (*on_update) (struct GameStage *);
+22 -8
View File
@@ -1,7 +1,6 @@
#include "stage_ingame.h"
extern SDL_Window *sdl_window;
extern Entity *player_entity, *sky_entity;
static Profiler profiler = { 0 };
static const int profiler_check_freq_s = 10;
@@ -10,6 +9,8 @@ static const int render_frame_budget_us = 16666;
static const int render_distance_min = 2;
static const int render_distance_max = 12;
static Entity *player_entity, *sky_entity;
static void
_on_enter ()
{
@@ -19,9 +20,6 @@ _on_enter ()
static void
_on_update (GameStage *self)
{
chunkman_tick (self->camera->position);
entman_tick ();
/* We pin the camera to the player position, with eye level 20
centimeters below the height. */
glm_vec3_copy (player_entity->position, self->camera->position);
@@ -33,6 +31,8 @@ _on_update (GameStage *self)
sky_entity->position[0] = self->camera->position[0];
sky_entity->position[2] = self->camera->position[2];
World_update (self->world, self->camera->position);
int speed = 5;
float delta_time = gvar_get ("delta_time");
@@ -109,14 +109,11 @@ _on_render (GameStage *self)
pakman_all_paks_set_float ("time", gvar_get ("last_time"));
vec4 planes[6];
#if FRUSTUM_CULL
mat4 view_proj;
glm_mat4_mul (projection, view, view_proj);
glm_frustum_planes (view_proj, planes);
#endif
chunkman_render (self->camera->position, planes);
entman_render (self->camera->position, view, planes);
World_render (self->world, self->camera->position, view, planes);
Profiler_sample (&profiler);
@@ -198,6 +195,23 @@ stage_ingame_new ()
/* If your GameStage uses the UI, always call this. */
GameStage_register_UIContext_handlers (self);
self->ui_context->root = PMUID ("debug_window");
player_entity = Entity_new ();
player_entity->visible = false;
glm_vec3_copy ((vec3) { 1.0, 1.8, 1.0 }, player_entity->scale);
/* player_entity->on_tick = character_on_tick; */
World_add_Entity (self->world, player_entity);
sky_entity = Entity_new ();
sky_entity->mesh_type = MESH_QUAD1;
sky_entity->origin = ORIGIN_CENTER;
sky_entity->dont_cull = true;
glm_vec3_copy ((vec3) { 5000.0, 5000.0, 5000.0 }, sky_entity->scale);
glm_vec3_copy ((vec3) { 0.0, 200.0, 0.0 }, sky_entity->position);
glm_vec3_copy ((vec3) { 90.0, 0.0, 0.0 }, sky_entity->rotation);
sky_entity->texture = PMTEX ("clouds");
sky_entity->program = PMPRG ("clouds");
World_add_Entity (self->world, sky_entity);
return self;
}
-2
View File
@@ -7,7 +7,5 @@
#include "../profiler.h"
#include "../graphics.h"
#include "../entity/entman.h"
#include "../chunk/chunkman.h"
GameStage *stage_ingame_new ();
+5 -11
View File
@@ -1,12 +1,6 @@
#include "widget_render.h"
static SimpleMesh quad;
void
ui_widget_mesh_init ()
{
quad = SimpleMesh_make_quad1_ui ();
}
extern SimpleMesh mesh_quad1_ui;
static inline void
_Widget_get_aabb (Widget *self, vec2 aabb[2])
@@ -52,7 +46,7 @@ _Widget_render_text_line(const char *text, TextAlignment alignment, ivec2 positi
glm_scale2d (model, (vec2) { size[0], size[1] });
program_set_mat3 (PMPRG ("uitext"), "model", model);
SimpleMesh_render (&quad, FALSE);
SimpleMesh_render (&mesh_quad1_ui, FALSE);
xoff += size[0];
p = g_utf8_next_char (p);
@@ -69,7 +63,7 @@ Widget_render_base (Widget *self, mat4 projection, int window_height)
if (self->parent == NULL)
{
SimpleMesh_bind (&quad);
SimpleMesh_bind (&mesh_quad1_ui);
}
const WidgetStyle *style = self->style;
@@ -132,7 +126,7 @@ Widget_render_base (Widget *self, mat4 projection, int window_height)
/* X, Y is the lower left corner of the box. */
glScissor (x, y, w, h);
SimpleMesh_render (&quad, FALSE);
SimpleMesh_render (&mesh_quad1_ui, FALSE);
}
void
@@ -277,7 +271,7 @@ Widget_render_entry (Widget *self, mat4 projection, int window_height)
program_set_mat4 (PMPRG ("uidummy"), "projection", projection);
program_set_vec4 (PMPRG ("uidummy"), "background_color", self->style->text_color);
SimpleMesh_render (&quad, FALSE);
SimpleMesh_render (&mesh_quad1_ui, FALSE);
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
#include "widget_fw.h"
#include "widget_core.h"
void ui_widget_mesh_init ();
#include "../gl/mesh_globals.h"
void Widget_render_base (Widget *self, mat4 projection, int window_height);
void Widget_render_label (Widget *self, mat4 projection, int window_height);
+307
View File
@@ -0,0 +1,307 @@
#include "world.h"
guint
_ivec2_hash (gconstpointer key)
{
/* This makes sure there's no collisions, thus making the whole
table much, much faster. */
const ivec2 *v = key;
guint x = (guint) (*v)[0];
guint y = (guint) (*v)[1];
return (x * 73856093u) ^ (y * 19349663u);
}
gboolean
_ivec2_equal (gconstpointer a, gconstpointer b)
{
return memcmp (a, b, sizeof (int) * 2) == 0;
}
ivec2 *
_make_key(int x, int z)
{
ivec2 key = { x, z };
return g_memdup2 (&key, sizeof (ivec2));
}
World *
World_new ()
{
World *w = g_new0 (World, 1);
w->entities = g_array_new (FALSE, FALSE, sizeof (Entity *));
w->chunks = g_hash_table_new_full (_ivec2_hash, _ivec2_equal, free, (GDestroyNotify) Chunk_free);
/* Chunk_free is wrong here probably... */
w->chunk_meshes = g_hash_table_new_full (_ivec2_hash, _ivec2_equal, free, (GDestroyNotify) Chunk_free);
return w;
}
void
_world_to_chunk (float x, float z, int *cx, int *cz)
{
*cx = floor (x / (float) CHUNK_DIM);
*cz = floor (z / (float) CHUNK_DIM);
}
void
World_update (World *w, vec3 position)
{
int render_distance = gvar_get ("render_distance");
int cx, cz;
_world_to_chunk (position[0], position[2], &cx, &cz);
for (int z = cz - render_distance - 1; z <= cz + render_distance + 1; ++z)
{
for (int x = cx - render_distance - 1; x <= cx + render_distance + 1; ++x)
{
if (World_get_Chunk_at (w, x, z) == NULL)
{
World_generate_Chunk_at (w, x, z);
}
}
}
for (int z = cz - render_distance; z <= cz + render_distance; ++z)
{
for (int x = cx - render_distance; x <= cx + render_distance; ++x)
{
if (World_get_ChunkMesh_at (w, x, z) == NULL)
{
World_generate_ChunkMesh_at (w, x, z);
}
}
}
/* Handle on tick events. */
for (guint i = 0; i < w->entities->len; ++i)
{
Entity *e = g_array_index (w->entities, Entity*, i);
if (e->on_tick)
{
e->on_tick (e);
}
}
}
void
World_render (World *w, vec3 position, mat4 view, vec4 planes[6])
{
int cx = 0, cz = 0;
_world_to_chunk (position[0], position[2], &cx, &cz);
program_use (PMPRG ("chunk"));
texture_use (PMTEX ("terrain0"), 0);
texture_use (PMTEX ("terrain1"), 1);
texture_use (PMTEX ("terrain2"), 2);
texture_use (PMTEX ("terrain3"), 3);
texture_use (PMTEX ("terrain4"), 4);
int render_distance = gvar_get ("render_distance");
int frame_chunks = 0, frame_entities = 0;
for (int z = cz - render_distance; z <= cz + render_distance; ++z)
{
for (int x = cx - render_distance; x <= cx + render_distance; ++x)
{
/* Render in a circle instead of a square. */
if ((x - cx) * (x - cx) + (z - cz) * (z - cz) > render_distance*render_distance)
{
continue;
}
ChunkMesh *mesh = World_get_ChunkMesh_at (w, x, z);
if (mesh != NULL)
{
vec3 aabb[2] = {
{ x * CHUNK_DIM, 0.0, z * CHUNK_DIM },
{ (x + 1) * CHUNK_DIM, TERRAIN_HEIGHT, (z + 1) * CHUNK_DIM }
};
if (!glm_aabb_frustum (aabb, planes))
{
continue;
}
ChunkMesh_bind (mesh);
program_set_vec3_ind (PMPRG ("chunk"), "chunk_position", x * CHUNK_DIM, 0.0, z * CHUNK_DIM);
ChunkMesh_render (mesh, FLAG_GET ("wireframe_enabled"));
frame_chunks++;
}
}
}
for (guint i = 0; i < w->entities->len; ++i)
{
Entity *ent = g_array_index (w->entities, Entity*, i);
frame_entities += Entity_render (ent, position, view, planes) ? 1 : 0;
}
gvar_set ("frame_chunks", frame_chunks);
gvar_set ("frame_entities", frame_entities);
}
/* Entity-specific */
void
World_add_Entity (World *w, Entity *e)
{
g_array_append_val (w->entities, e);
}
/* Chunk-specific */
static inline float
_Chunk_get_height (Chunk *c, int x, int z)
{
return c ? c->heightmap[INDEX (x, z)] : 0.0;
}
float
World_get_height_at (World *w, float x, float z)
{
int cx, cz;
_world_to_chunk (x, z, &cx, &cz);
Chunk *chunk = World_get_Chunk_at (w, cx, cz);
if (chunk == NULL)
{
return 0.0;
}
float lx = x - cx * CHUNK_DIM;
float lz = z - cz * CHUNK_DIM;
int x0 = (int) floor (lx);
int z0 = (int) floor (lz);
int x1 = x0 + 1;
int z1 = z0 + 1;
float tx = lx - x0;
float tz = lz - z0;
Chunk *r = NULL, *d = NULL, *c = NULL;
World_get_Chunk_neighbors_at (w, cx, cz, &r, &d, &c);
float h00 = _Chunk_get_height (chunk, x0, z0);
float h10 = (x1 < CHUNK_DIM) ? _Chunk_get_height (chunk, x1, z0) :
(r ? _Chunk_get_height (r, 0, z0) : h00);
float h01 = (z1 < CHUNK_DIM) ? _Chunk_get_height (chunk, x0, z1) :
(d ? _Chunk_get_height (d, x0, 0) : h00);
float h11;
if (x1 < CHUNK_DIM && z1 < CHUNK_DIM)
{
h11 = _Chunk_get_height (chunk, x1, z1);
}
else if (x1 >= CHUNK_DIM && z1 < CHUNK_DIM)
{
h11 = r ? _Chunk_get_height (r, 0, z1) : h10;
}
else if (x1 < CHUNK_DIM && z1 >= CHUNK_DIM)
{
h11 = d ? _Chunk_get_height (d, x1, 0) : h01;
}
else
{
h11 = c ? _Chunk_get_height (c, 0, 0) : h00;
}
// Bilinear interpolation.
float h0 = h00 * (1 - tx) + h10 * tx;
float h1 = h01 * (1 - tx) + h11 * tx;
float height = h0 * (1 - tz) + h1 * tz;
return height;
}
Chunk *
World_get_Chunk_at (World *w, int x, int z)
{
ivec2 key = { x, z };
return (Chunk *) g_hash_table_lookup (w->chunks, &key);
}
void
World_get_Chunk_neighbors_at (World *w, int x, int z, Chunk **nr, Chunk **nd, Chunk **nc)
{
*nr = World_get_Chunk_at (w, x + 1, z);
*nd = World_get_Chunk_at (w, x, z + 1);
*nc = World_get_Chunk_at (w, x + 1, z + 1);
}
void
World_set_Chunk_at (World *w, int x, int z, Chunk *chunk)
{
g_hash_table_insert (w->chunks, _make_key (x, z), chunk);
}
ChunkMesh *
World_get_ChunkMesh_at (World *w, int x, int z)
{
ivec2 key = { x, z };
return (ChunkMesh *) g_hash_table_lookup (w->chunk_meshes, key);
}
void
World_set_ChunkMesh_at (World *w, int x, int z, ChunkMesh *mesh)
{
g_hash_table_insert (w->chunk_meshes, _make_key (x, z), mesh);
}
void
World_generate_Chunk_at (World *w, int cx, int cz)
{
Chunk *chunk = Chunk_new ();
int xoffset = cx * CHUNK_DIM;
int zoffset = cz * CHUNK_DIM;
for (int z = 0; z < CHUNK_DIM; ++z)
{
for (int x = 0; x < CHUNK_DIM; ++x)
{
chunk->heightmap[INDEX (x, z)] =
(glm_perlin_vec2
((vec2) { (x + xoffset + 0.333) / 10.0, (z + zoffset + 0.333) / 10.0 }) + 0.6) * 2;
chunk->materialmap[INDEX (x, z)] = (int)
(glm_perlin_vec2 ((vec2) { (x + xoffset + 0.333) / 20.0, (z + zoffset + 0.333) / 20.0 }) + 0.6 * 5) % 5;
if (rand() % 100 < 2)
{
Entity *e = ent_templates_clone ("tree");
glm_vec3_copy ((vec3) { x + xoffset, chunk->heightmap[INDEX (x, z)], z + zoffset }, e->position);
glm_vec3_copy ((vec3) { 0.0, rand () % 180, 0.0 }, e->rotation);
glm_vec3_copy ((vec3) { 3.0, (rand () % 5) + 5, 3.0 }, e->scale);
World_add_Entity (w, e);
}
if (rand() % 100 < 10)
{
Entity *e = ent_templates_clone ("grass");
glm_vec3_copy ((vec3) { x + xoffset, chunk->heightmap[INDEX (x, z)], z + zoffset }, e->position);
glm_vec3_copy ( (vec3) { 0.0, 0.0, 0.0 }, e->rotation);
glm_vec3_copy ( (vec3) { 1.0, 1.0, 1.0 }, e->scale);
World_add_Entity (w, e);
}
}
}
World_set_Chunk_at (w, cx, cz, chunk);
}
void
World_generate_ChunkMesh_at (World *w, int x, int z)
{
Chunk *nr, *nd, *nc;
World_get_Chunk_neighbors_at (w, x, z, &nr, &nd, &nc);
g_assert (nr != NULL);
g_assert (nd != NULL);
g_assert (nc != NULL);
World_set_ChunkMesh_at
(w, x, z, ChunkMesh_build (World_get_Chunk_at (w, x, z), nr, nd, nc));
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <glib-2.0/glib.h>
#include <cglm/cglm.h>
#include "util.h"
#include "gvar.h"
#include "pakman.h"
#include "entity/entity.h"
#include "entity/templates.h"
#include "chunk/chunk.h"
#include "chunk/chunkmesh.h"
#define TERRAIN_HEIGHT 8.0
typedef struct World
{
GArray *entities;
GHashTable *chunks, *chunk_meshes;
} World;
World *World_new ();
void World_update (World *w, vec3 position);
void World_render (World *w, vec3 position, mat4 view, vec4 frustum[6]);
/* Entity-specific */
void World_add_Entity (World *w, Entity *e);
/* Chunk-specific */
float World_get_height_at (World *w, float x, float z);
Chunk *World_get_Chunk_at (World *w, int x, int z);
void World_get_Chunk_neighbors_at (World *w, int x, int z, Chunk **nr, Chunk **nd, Chunk **nc);
void World_set_Chunk_at (World *w, int x, int z, Chunk *chunk);
ChunkMesh *World_get_ChunkMesh_at (World *w, int x, int z);
void World_set_ChunkMesh_at (World *w, int x, int z, ChunkMesh *mesh);
void World_generate_Chunk_at (World *w, int cx, int cz);
void World_generate_ChunkMesh_at (World *w, int cx, int cz);