From: Brendan Hansen Date: Mon, 17 Jan 2022 00:02:52 +0000 (-0600) Subject: infinite terrain baby! X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=32cf6424ef2841ce53d83bc5c9b25a728ae754fc;p=voxel-shooter.git infinite terrain baby! --- diff --git a/src/build.onyx b/src/build.onyx index c4562a1..8667782 100644 --- a/src/build.onyx +++ b/src/build.onyx @@ -18,6 +18,7 @@ #load "player" #load "shader" #load "texture" +#load "utils" #load "vecmath" #load "world" #load "worldgen" diff --git a/src/chunk.onyx b/src/chunk.onyx index be71bb2..ef3b5ef 100644 --- a/src/chunk.onyx +++ b/src/chunk.onyx @@ -59,6 +59,12 @@ chunk_make :: (x, y, z: i32, allocator := context.allocator) -> ^Chunk { return chunk; } +chunk_free :: (chunk: ^Chunk) { + chunk_destroy_mesh(chunk); + memory.free_slice(^chunk.blocks); + cfree(chunk); +} + #local in_chunk_bounds :: macro (x, y, z: i32) -> bool { if x < 0 do return false; if x >= Chunk_Size do return false; @@ -171,7 +177,22 @@ chunk_build_mesh :: (use chunk: ^Chunk) { mesh_dirty = false; } +chunk_destroy_mesh :: (use chunk: ^Chunk) { + if mesh == null do return; + + mesh_free(mesh); + mesh = null; + + mesh_dirty = true; // Just in case this chunk will be reused later. +} + +#local block_texture: Texture; chunk_draw :: (chunk: ^Chunk) { + if block_texture.filename.data == null { + block_texture = texture_make(#cstr "assets/textures/block.png"); + } + + texture_use(^block_texture); update_model_matrix(chunk.position); mesh_draw(chunk.mesh); } diff --git a/src/main.onyx b/src/main.onyx index 3da9d41..73fcb8a 100644 --- a/src/main.onyx +++ b/src/main.onyx @@ -75,8 +75,6 @@ toggle_cursor_grabbed :: () { } } -#local block_texture: Texture; - setup_opengl :: () { glInit(glfwGetLoadProcAddress()); @@ -99,8 +97,6 @@ setup_opengl :: () { font = font_lookup(.{"./assets/fonts/calibri.ttf", 32}); - block_texture = texture_make(#cstr "assets/textures/block.png"); - shader_use(world_shader); world = world_make(); player = player_make(); @@ -130,6 +126,9 @@ update :: (dt: f32) { } player_update(^player, dt); + player_chunk := world_position_to_chunk(world, player.body.pos); + world_move_center(world, player_chunk); + world_update(world, dt); if is_key_just_down(GLFW_KEY_F7) { debug_screen = !debug_screen; @@ -150,7 +149,6 @@ draw :: () { shader_use(world_shader); shader_set_uniform(world_shader, #cstr "u_texture", 0); - texture_use(^block_texture); world_draw(world); glLineWidth(2); @@ -161,10 +159,12 @@ draw :: () { font_print(font, ~~(ww / 2), ~~(wh / 2), "."); if debug_screen { + player_chunk := world_position_to_chunk(world, player.body.pos); font_print(font, 0, 32, "FPS: {}", game_fps); font_print(font, 0, 64, "Position: {}", camera.position); font_print(font, 0, 96, "Facing: {}", camera.y_rot * 180 / math.PI); font_print(font, 0, 128, "Looking at: {}", selected_block); + font_print(font, 0, 160, "Chunk: {}", player_chunk); } glfwSwapBuffers(window); diff --git a/src/physics.onyx b/src/physics.onyx index 8b89b6a..746e39d 100644 --- a/src/physics.onyx +++ b/src/physics.onyx @@ -34,8 +34,6 @@ physics_simulate :: (use body: ^PhysicsBody, dt: f32, world: ^World) { aabb_buffer := (cast(^AABB) alloc.from_stack(sizeof [256] AABB))[0..256]; aabbs := world_get_aabbs(world, pos, 3, aabb_buffer); - vel += acc * dt; - try_move :: macro (delta: Vector3) -> f32 { new_pos := body.pos + delta; body_aabb := get_collision_object(new_pos); @@ -51,14 +49,16 @@ physics_simulate :: (use body: ^PhysicsBody, dt: f32, world: ^World) { } on_ground = false; - move_x := try_move(.{vel.x * dt, 0, 0}); - move_y := try_move(.{0, vel.y * dt, 0}); - move_z := try_move(.{0, 0, vel.z * dt}); + move_x := try_move(.{acc.x*0.5*dt*dt + vel.x * dt, 0, 0}); + move_y := try_move(.{0, acc.y*0.5*dt*dt + vel.y * dt, 0}); + move_z := try_move(.{0, 0, acc.z*0.5*dt*dt + vel.z * dt}); pos.x += vel.x * dt * move_x; pos.y += vel.y * dt * move_y; pos.z += vel.z * dt * move_z; + vel += acc * dt; + if on_ground do vel.y = 0; } diff --git a/src/player.onyx b/src/player.onyx index 52a1b41..c10d6d1 100644 --- a/src/player.onyx +++ b/src/player.onyx @@ -61,7 +61,7 @@ player_update :: (use player: ^Player, dt: f32) { physics_simulate(^body, dt, world); // Fix falling off the world - if body.pos.y < -10 do body.pos = .{0,48,0}; + if body.pos.y < -100 do body.pos = .{0,48,0}; camera.position = body.pos; diff --git a/src/utils.onyx b/src/utils.onyx new file mode 100644 index 0000000..25a9873 --- /dev/null +++ b/src/utils.onyx @@ -0,0 +1,72 @@ +use package core + +Ray :: struct { + origin, direction: Vector3; +} + + +ray_cast :: (ray: Ray, max_distance: f32, ctx: rawptr, is_solid: (rawptr, Vector3i) -> bool, out_block: ^Vector3i, out_dir: ^Vector3i) -> bool { + pos := Vector3i.{ ~~math.floor(ray.origin.x), ~~math.floor(ray.origin.y), ~~math.floor(ray.origin.z) }; + + dir := ray.direction; + step := Vector3i.{ ~~math.sign(dir.x), ~~math.sign(dir.y), ~~math.sign(dir.z) }; + + tmax: Vector3; + tmax.x = ((math.ceil(ray.origin.x) - ray.origin.x) if dir.x > 0 else (ray.origin.x - math.floor(ray.origin.x))) / math.abs(dir.x); + tmax.y = ((math.ceil(ray.origin.y) - ray.origin.y) if dir.y > 0 else (ray.origin.y - math.floor(ray.origin.y))) / math.abs(dir.y); + tmax.z = ((math.ceil(ray.origin.z) - ray.origin.z) if dir.z > 0 else (ray.origin.z - math.floor(ray.origin.z))) / math.abs(dir.z); + + tdelta := Vector3.{ ~~step.x / dir.x, ~~step.y / dir.y, ~~step.z / dir.z }; + radius := max_distance / Vector3.mag(dir); + + while true { + if is_solid(ctx, pos) { + *out_block = pos; + return true; + } + + if tmax.x < tmax.y { + if tmax.x < tmax.z { + if tmax.x > radius { + break; + } + + pos.x += step.x; + tmax.x += tdelta.x; + *out_dir = .{ -step.x, 0, 0 }; + + } else { + if tmax.z > radius { + break; + } + + pos.z += step.z; + tmax.z += tdelta.z; + *out_dir = .{ 0, 0, -step.z }; + } + + } else { + if tmax.y < tmax.z { + if tmax.y > radius { + break; + } + + pos.y += step.y; + tmax.y += tdelta.y; + *out_dir = .{ 0, -step.y, 0 }; + + } else { + if tmax.z > radius { + break; + } + + pos.z += step.z; + tmax.z += tdelta.z; + *out_dir = .{ 0, 0, -step.z }; + } + } + } + + return false; +} + diff --git a/src/vecmath.onyx b/src/vecmath.onyx index 95abdf1..caf6a3f 100644 --- a/src/vecmath.onyx +++ b/src/vecmath.onyx @@ -42,39 +42,25 @@ Vector3 :: struct [conv.Custom_Format.{format_vector3}] { #operator + macro (v1, v2: Vector3) => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; #operator - macro (v1, v2: Vector3) => Vector3.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; #operator * macro (v: Vector3, s: f32) => Vector3.{ v.x * s, v.y * s, v.z * s }; +#operator == macro (v1, v2: Vector3) => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; #operator + macro (v1, v2: Vector3i) => Vector3i.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; #operator - macro (v1, v2: Vector3i) => Vector3i.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; #operator * macro (v: Vector3i, s: i32) => Vector3i.{ v.x * s, v.y * s, v.z * s }; +#operator == macro (v1, v2: Vector3i) => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; #local { conv :: package core.conv format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) { - output->write("("); - conv.format_any(output, format, .{^v.x, f32}); - output->write(", "); - conv.format_any(output, format, .{^v.y, f32}); - output->write(")"); + conv.format(output, "({}, {})", v.x, v.y); } format_vector3 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3) { - output->write("("); - conv.format_any(output, format, .{^v.x, f32}); - output->write(", "); - conv.format_any(output, format, .{^v.y, f32}); - output->write(", "); - conv.format_any(output, format, .{^v.z, f32}); - output->write(")"); + conv.format(output, "({}, {}, {})", v.x, v.y, v.z); } format_vector3i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3i) { - output->write("("); - conv.format_any(output, format, .{^v.x, i32}); - output->write(", "); - conv.format_any(output, format, .{^v.y, i32}); - output->write(", "); - conv.format_any(output, format, .{^v.z, i32}); - output->write(")"); + conv.format(output, "({}, {}, {})", v.x, v.y, v.z); } -} \ No newline at end of file +} diff --git a/src/world.onyx b/src/world.onyx index 72238b0..4c691b9 100644 --- a/src/world.onyx +++ b/src/world.onyx @@ -2,46 +2,163 @@ use package core World :: struct { - chunk_dist: u32; + chunk_dist: i32; chunks: [] ^Chunk; + + center_chunk: Vector3i; + + chunks_to_load: [..] Vector3i; } world_make :: (allocator := context.allocator) -> ^World { world := new(World, allocator); - world.chunk_dist = 3; - memory.alloc_slice(^world.chunks, world.chunk_dist * world.chunk_dist * world.chunk_dist); + world.chunk_dist = 2; + world.center_chunk = .{0,0,0}; - for x: 0 .. 3 do for y: 0 .. 3 do for z: 0 .. 3 { - chunk := chunk_make(x, y, z); - world.chunks[x * world.chunk_dist * world.chunk_dist + y * world.chunk_dist + z] = chunk; - generate_chunk(world, chunk); + sl := world.chunk_dist * 2 + 1; + memory.alloc_slice(^world.chunks, sl * sl * sl); + memory.fill_slice(world.chunks, null); + + for x: (world.center_chunk.x-world.chunk_dist) .. world.center_chunk.x+world.chunk_dist+1 + do for y: (world.center_chunk.y-world.chunk_dist) .. world.center_chunk.y+world.chunk_dist+1 + do for z: (world.center_chunk.z-world.chunk_dist) .. world.center_chunk.z+world.chunk_dist+1 { + world.chunks_to_load << .{x, y, z}; } return world; } +world_load_chunk :: (world: ^World, chunk_to_load: Vector3i) { + // + // For now, no persistence will happen to the chunk, so generate a completely new chunk. + // + chunk := chunk_make(chunk_to_load.x, chunk_to_load.y, chunk_to_load.z); + generate_chunk(world, chunk); + world_set_chunk(world, chunk_to_load, chunk); +} + +world_unload_chunk :: #match { + (world: ^World, chunk_to_unload: Vector3i) { + chunk := world_get_chunk(world, chunk_to_unload); + world_unload_chunk(world, chunk); + world_set_chunk(world, chunk_to_unload, null); + }, + + (world: ^World, chunk: ^Chunk) { + if chunk != null { + chunk_free(chunk); + } + } +} + +world_get_chunk :: #match { + @CompilerBug // If I write the following function in quick form: + // + // (world: ^World, v: Vector3i) => world_get_chunk(world, v.x, v.y, v.z), + // + // the ',' at the end appears to mean "compound expression", so the following + // function defintion gets lumped as the second return result. Maybe there + // should be a special case for parsing this? I've always hated how parsing + // inside of a #match block works. + // + (world: ^World, v: Vector3i) -> #auto { return world_get_chunk(world, v.x, v.y, v.z); }, + + (world: ^World, x, y, z: i32) -> ^Chunk { + d := world.chunk_dist; + sl := d * 2 + 1; + + ix := x - world.center_chunk.x + d; + iy := y - world.center_chunk.y + d; + iz := z - world.center_chunk.z + d; + if ix < 0 || ix > 2 * world.chunk_dist do return null; + if iy < 0 || iy > 2 * world.chunk_dist do return null; + if iz < 0 || iz > 2 * world.chunk_dist do return null; + + return world.chunks[ix * sl * sl + iy * sl + iz]; + } +} + +world_set_chunk :: #match { + (world: ^World, v: Vector3i, chunk: ^Chunk) -> #auto { return world_set_chunk(world, v.x, v.y, v.z, chunk); }, + + (world: ^World, x, y, z: i32, chunk: ^Chunk) -> bool { + d := world.chunk_dist; + sl := d * 2 + 1; + + ix := x - world.center_chunk.x + d; + iy := y - world.center_chunk.y + d; + iz := z - world.center_chunk.z + d; + if ix < 0 || ix > 2 * world.chunk_dist do return false; + if iy < 0 || iy > 2 * world.chunk_dist do return false; + if iz < 0 || iz > 2 * world.chunk_dist do return false; + + world.chunks[ix * sl * sl + iy * sl + iz] = chunk; + return true; + } +} + +// Getting around the stupidness that is integer arithmetic on modern processors +#local world_coord_to_chunk_and_block :: (world: ^World, x, y, z: i32) -> (chunk: Vector3i, block: Vector3i) { + chunk, block: Vector3i; + chunk.x = x / Chunk_Size; + chunk.y = y / Chunk_Size; + chunk.z = z / Chunk_Size; + block.x = x % Chunk_Size; + block.y = y % Chunk_Size; + block.z = z % Chunk_Size; + + if x < 0 && block.x != 0 { chunk.x -= 1; block.x += Chunk_Size; } + if y < 0 && block.y != 0 { chunk.y -= 1; block.y += Chunk_Size; } + if z < 0 && block.z != 0 { chunk.z -= 1; block.z += Chunk_Size; } + + return chunk, block; +} + +world_position_to_chunk :: (world: ^World, use pos: Vector3) -> Vector3i { + chunk: Vector3i; + chunk.x = ~~math.floor(x / ~~Chunk_Size); + chunk.y = ~~math.floor(y / ~~Chunk_Size); + chunk.z = ~~math.floor(z / ~~Chunk_Size); + return chunk; +} + world_get_block :: (world: ^World, x, y, z: i32) -> Block { - @WorldCenter - cx := x / Chunk_Size; - cy := y / Chunk_Size; - cz := z / Chunk_Size; - if cx < 0 || cx >= world.chunk_dist do return Block_Empty; - if cy < 0 || cy >= world.chunk_dist do return Block_Empty; - if cz < 0 || cz >= world.chunk_dist do return Block_Empty; - chunk := world.chunks[cx * world.chunk_dist * world.chunk_dist + cy * world.chunk_dist + cz]; - return chunk_get(chunk, x % Chunk_Size, y % Chunk_Size, z % Chunk_Size); + chunk_pos, block_pos := world_coord_to_chunk_and_block(world, x, y, z); + chunk := world_get_chunk(world, chunk_pos); + if chunk == null do return Block_Empty; + return chunk_get(chunk, block_pos.x, block_pos.y, block_pos.z); } world_set_block :: (world: ^World, x, y, z: i32, block: Block) { - @WorldCenter - cx := x / Chunk_Size; - cy := y / Chunk_Size; - cz := z / Chunk_Size; - if cx < 0 || cx >= world.chunk_dist do return; - if cy < 0 || cy >= world.chunk_dist do return; - if cz < 0 || cz >= world.chunk_dist do return; - chunk := world.chunks[cx * world.chunk_dist * world.chunk_dist + cy * world.chunk_dist + cz]; - chunk_set(chunk, x % Chunk_Size, y % Chunk_Size, z % Chunk_Size, block); + chunk_pos, block_pos := world_coord_to_chunk_and_block(world, x, y, z); + chunk := world_get_chunk(world, chunk_pos); + if chunk == null do return; + chunk_set(chunk, block_pos.x, block_pos.y, block_pos.z, block); +} + +world_move_center :: (use world: ^World, new_center: Vector3i) { + if center_chunk == new_center do return; + + sl := world.chunk_dist * 2 + 1; + current_chunks := memory.copy_slice(world.chunks); + memory.alloc_slice(^world.chunks, sl * sl * sl); + memory.fill_slice(world.chunks, null); + + center_chunk = new_center; + for current_chunks { + if it == null do continue; + if !world_set_chunk(world, it.coord, it) { + world_unload_chunk(world, it); + } + } + + for x: (center_chunk.x-world.chunk_dist) .. center_chunk.x+world.chunk_dist+1 + do for y: (center_chunk.y-world.chunk_dist) .. center_chunk.y+world.chunk_dist+1 + do for z: (center_chunk.z-world.chunk_dist) .. center_chunk.z+world.chunk_dist+1 { + if world_get_chunk(world, x, y, z) == null do world.chunks_to_load << .{x, y, z}; + } + + memory.free_slice(^current_chunks); } world_get_aabbs :: (use world: ^World, center: Vector3, radius: f32, buffer: [] AABB = .[]) -> [] AABB { @@ -77,81 +194,19 @@ world_get_aabbs :: (use world: ^World, center: Vector3, radius: f32, buffer: [] return buffer[0 .. buffer_pos]; } +world_update :: (use world: ^World, dt: f32) { + if chunks_to_load.count > 0 { + chunk_to_load := chunks_to_load[0]; + array.delete(^chunks_to_load, 0); + printf("Loading {}\n", chunk_to_load); -@Relocate // to a utils file -Ray :: struct { - origin, direction: Vector3; -} - -@Relocate // to a utils file -ray_cast :: (ray: Ray, max_distance: f32, ctx: rawptr, is_solid: (rawptr, Vector3i) -> bool, out_block: ^Vector3i, out_dir: ^Vector3i) -> bool { - pos := Vector3i.{ ~~math.floor(ray.origin.x), ~~math.floor(ray.origin.y), ~~math.floor(ray.origin.z) }; - - dir := ray.direction; - step := Vector3i.{ ~~math.sign(dir.x), ~~math.sign(dir.y), ~~math.sign(dir.z) }; - - tmax: Vector3; - tmax.x = ((math.ceil(ray.origin.x) - ray.origin.x) if dir.x > 0 else (ray.origin.x - math.floor(ray.origin.x))) / math.abs(dir.x); - tmax.y = ((math.ceil(ray.origin.y) - ray.origin.y) if dir.y > 0 else (ray.origin.y - math.floor(ray.origin.y))) / math.abs(dir.y); - tmax.z = ((math.ceil(ray.origin.z) - ray.origin.z) if dir.z > 0 else (ray.origin.z - math.floor(ray.origin.z))) / math.abs(dir.z); - - tdelta := Vector3.{ ~~step.x / dir.x, ~~step.y / dir.y, ~~step.z / dir.z }; - radius := max_distance / Vector3.mag(dir); - - while true { - if is_solid(ctx, pos) { - *out_block = pos; - return true; - } - - if tmax.x < tmax.y { - if tmax.x < tmax.z { - if tmax.x > radius { - break; - } - - pos.x += step.x; - tmax.x += tdelta.x; - *out_dir = .{ -step.x, 0, 0 }; - - } else { - if tmax.z > radius { - break; - } - - pos.z += step.z; - tmax.z += tdelta.z; - *out_dir = .{ 0, 0, -step.z }; - } - - } else { - if tmax.y < tmax.z { - if tmax.y > radius { - break; - } - - pos.y += step.y; - tmax.y += tdelta.y; - *out_dir = .{ 0, -step.y, 0 }; - - } else { - if tmax.z > radius { - break; - } - - pos.z += step.z; - tmax.z += tdelta.z; - *out_dir = .{ 0, 0, -step.z }; - } - } + world_load_chunk(world, chunk_to_load); } - - return false; } - world_draw :: (use world: ^World) { for chunks { + if it == null do continue; chunk_build_mesh(it); chunk_draw(it); } diff --git a/src/worldgen.onyx b/src/worldgen.onyx index 96041d2..e3a3e59 100644 --- a/src/worldgen.onyx +++ b/src/worldgen.onyx @@ -7,14 +7,20 @@ use package core generate_chunk :: (world: ^World, chunk: ^Chunk) { for cz: 32 do for cx: 32 { t := chunk_coords_to_world(chunk, cx, 0, cz); - px := cast(f64) t.x / 16; - pz := cast(f64) t.z / 16; - h: i32 = ~~(perlin.noise(px, ~~(10 * seed), pz) * 24 + 14); + px := cast(f64) t.x / 24; + pz := cast(f64) t.z / 24; + + n := perlin.noise(px, ~~(10 * seed), pz); + h := cast(i32) (n * 32 + 18); + h = math.max(h, 14); + if n < -0.9 do h += ~~((n + 0.9) * 4); h -= chunk.coord.y * Chunk_Size; if h >= 0 { chunk_set(chunk, cx, h, cz, block_make(0.2, 1, 0.2, 1)); h -= 1; } for cy: 2 .. h+1 do chunk_set(chunk, cx, cy, cz, block_make(0.3, 0.3, 0.1, 1, .{texture_enabled=false})); for cy: 0 .. math.min(h, 2)+1 do chunk_set(chunk, cx, cy, cz, block_make(0.2, 0.2, 0.2, 0.5, .{texture_enabled=false})); } + + chunk_set(chunk, 0, 0, 0, block_make(1, 0, 1, 1)); }