311 lines
7.2 KiB
C
311 lines
7.2 KiB
C
#include "chunkman.h"
|
|
|
|
extern int render_distance;
|
|
|
|
int n_frame_chunks;
|
|
|
|
static GHashTable *chunks;
|
|
static GHashTable *chunk_meshes;
|
|
|
|
static bool bound = false;
|
|
|
|
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);
|
|
|
|
uint64_t start = SDL_GetPerformanceCounter ();
|
|
uint64_t freq = SDL_GetPerformanceFrequency ();
|
|
double elapsed_us = 0;
|
|
|
|
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)
|
|
{
|
|
uint64_t now = SDL_GetPerformanceCounter ();
|
|
elapsed_us = (double) (now - start) * 1000000.0 / (double) freq;
|
|
|
|
if (elapsed_us > CHUNK_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)
|
|
{
|
|
uint64_t now = SDL_GetPerformanceCounter ();
|
|
elapsed_us = (double) (now - start) * 1000000.0 / (double) freq;
|
|
|
|
if (elapsed_us > CHUNK_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 (RESPRG ("chunk"));
|
|
|
|
glActiveTexture (GL_TEXTURE0);
|
|
glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain0"));
|
|
glActiveTexture (GL_TEXTURE1);
|
|
glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain1"));
|
|
glActiveTexture (GL_TEXTURE2);
|
|
glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain2"));
|
|
glActiveTexture (GL_TEXTURE3);
|
|
glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain3"));
|
|
glActiveTexture (GL_TEXTURE4);
|
|
glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain4"));
|
|
|
|
n_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 (RESPRG ("chunk"), "chunk_position", x * CHUNK_DIM, 0.0, z * CHUNK_DIM);
|
|
ChunkMesh_render (mesh, flags_get ("wireframe_enabled"));
|
|
n_frame_chunks++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bound = false;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|