Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn build(b: *std.Build) void {

const chunk_debug_enable = b.option([]const u8, "chunk-debug-enable", "Re-enable one subsystem in chunk-debug-mode (lod, water, caves, decorations)") orelse "";
options.addOption([]const u8, "chunk_debug_enable", chunk_debug_enable);
const auto_world = b.option([]const u8, "auto-world", "Auto-open a world generator directly by id or alias (normal, overworld, flat, test)") orelse "";
const auto_world = b.option([]const u8, "auto-world", "Auto-open a world generator directly by id or alias (normal, overworld, overworld-v2, flat, test)") orelse "";
options.addOption([]const u8, "auto_world", auto_world);

const auto_preset = b.option([]const u8, "auto-preset", "Graphics preset to apply for auto-world launches (low, medium, high, ultra, extreme)") orelse "";
Expand Down Expand Up @@ -141,6 +141,7 @@ pub fn build(b: *std.Build) void {
const worldgen_api = b.createModule(.{ .root_source_file = b.path("modules/worldgen-api/src/root.zig"), .target = target, .optimize = optimize });
const worldgen_common = b.createModule(.{ .root_source_file = b.path("modules/worldgen-common/src/root.zig"), .target = target, .optimize = optimize });
const worldgen_overworld = b.createModule(.{ .root_source_file = b.path("modules/worldgen-overworld/src/root.zig"), .target = target, .optimize = optimize });
const worldgen_overworld_v2 = b.createModule(.{ .root_source_file = b.path("modules/worldgen-overworld-v2/src/root.zig"), .target = target, .optimize = optimize });
const worldgen_flat = b.createModule(.{ .root_source_file = b.path("modules/worldgen-flat/src/root.zig"), .target = target, .optimize = optimize });
const worldgen_test = b.createModule(.{ .root_source_file = b.path("modules/worldgen-test/src/root.zig"), .target = target, .optimize = optimize });
const world_worldgen = b.createModule(.{ .root_source_file = b.path("modules/world-worldgen/src/root.zig"), .target = target, .optimize = optimize });
Expand Down Expand Up @@ -242,6 +243,10 @@ pub fn build(b: *std.Build) void {
worldgen_overworld.addImport("worldgen-api", worldgen_api);
worldgen_overworld.addImport("worldgen-common", worldgen_common);
worldgen_overworld.addOptions("worldgen_overworld_options", worldgen_overworld_options);
addSharedImports(worldgen_overworld_v2, zig_math, zig_noise, fs_module, sync_module, c_module, options);
worldgen_overworld_v2.addImport("world-core", world_core);
worldgen_overworld_v2.addImport("worldgen-api", worldgen_api);
worldgen_overworld_v2.addImport("worldgen-common", worldgen_common);
addSharedImports(worldgen_flat, zig_math, zig_noise, fs_module, sync_module, c_module, options);
worldgen_flat.addImport("world-core", world_core);
worldgen_flat.addImport("worldgen-api", worldgen_api);
Expand Down Expand Up @@ -278,6 +283,7 @@ pub fn build(b: *std.Build) void {
world_worldgen.addImport("worldgen-api", worldgen_api);
world_worldgen.addImport("worldgen-common", worldgen_common);
world_worldgen.addImport("worldgen-overworld", worldgen_overworld);
world_worldgen.addImport("worldgen-overworld-v2", worldgen_overworld_v2);
world_worldgen.addImport("worldgen-flat", worldgen_flat);
world_worldgen.addImport("worldgen-test", worldgen_test);

Expand Down
6 changes: 6 additions & 0 deletions modules/engine-graphics/src/resource_pack.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ pub const BLOCK_TEXTURES = [_]TextureMapping{
.{ .name = "flower_red", .files = &.{ "flower_red.png", "flower_rose.png", "poppy.png" } },
.{ .name = "flower_yellow", .files = &.{ "flower_yellow.png", "flower_dandelion.png", "dandelion.png" } },
.{ .name = "dead_bush", .files = &.{ "dead_bush.png", "deadbush.png" } },
.{ .name = "seagrass", .files = &.{ "seagrass.png", "short_grass.png", "tall_grass.png" } },
.{ .name = "tall_seagrass", .files = &.{ "seagrass.png", "tall_grass_top.png", "tall_grass_bottom.png", "tall_grass.png" } },
.{ .name = "kelp", .files = &.{ "kelp.png", "seagrass.png", "dried_kelp_side.png" } },
.{ .name = "seaweed", .files = &.{ "seagrass.png", "kelp.png", "tall_grass.png" } },
.{ .name = "coral_block", .files = &.{ "brain_coral_block.png", "bubble_coral_block.png", "fire_coral_block.png", "tube_coral_block.png", "horn_coral_block.png" } },
.{ .name = "coral_fan", .files = &.{ "brain_coral_fan.png", "bubble_coral_fan.png", "fire_coral_fan.png", "tube_coral_fan.png", "horn_coral_fan.png" } },
.{ .name = "torch", .files = &.{ "torch.png", "torch_on.png" } },
.{ .name = "lava", .files = &.{ "lava.png", "lava_still.png" } },
.{ .name = "snow", .files = &.{ "snow.png", "snow_block.png" } },
Expand Down
9 changes: 6 additions & 3 deletions modules/world-core/src/lod_data.zig
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,17 @@ pub const LODSimplifiedData = struct {
allocator: std.mem.Allocator,

pub fn getGridSize(lod_level: LODLevel) u32 {
if (lod_level == .lod0) return 16;
return 32;
return switch (lod_level) {
.lod0 => 16,
.lod1 => 32,
.lod2, .lod3 => 48,
};
}

pub fn getCellSizeBlocks(lod_level: LODLevel) u32 {
const region_size = regionSizeBlocks(lod_level);
const grid_size = getGridSize(lod_level);
return region_size / grid_size;
return region_size / @max(grid_size - 1, 1);
}

pub fn init(allocator: std.mem.Allocator, lod_level: LODLevel) !LODSimplifiedData {
Expand Down
135 changes: 43 additions & 92 deletions modules/world-lod/src/lod_mesh.zig
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,22 @@ pub const LODMesh = struct {
const side_tile = material.side;
const top_block = blockForLODQuad(data, gx, gz);
const top_color = getLodTopColor(top_block, top_tile, lit_avg_color);
const flat_normal = [3]f32{ 0, 1, 0 };
const n00 = if (top_block == .water) flat_normal else normalForHeightPoint(data, gx, gz, cell_size);
const n10 = if (top_block == .water) flat_normal else normalForHeightPoint(data, gx + 1, gz, cell_size);
const n01 = if (top_block == .water) flat_normal else normalForHeightPoint(data, gx, gz + 1, cell_size);
const n11 = if (top_block == .water) flat_normal else normalForHeightPoint(data, gx + 1, gz + 1, cell_size);

try addSmoothQuad(self.allocator, &vertices, wx, wz, size, h00, h10, h01, h11, top_color, top_color, top_color, top_color, top_tile, world_x, world_z);
try addSmoothQuad(self.allocator, &vertices, wx, wz, size, h00, h10, h01, h11, top_color, top_color, top_color, top_color, n00, n10, n01, n11, top_tile, world_x, world_z);

const skirt_depth: f32 = size * 4.0;
if (gx == 0) try addSideFaceQuad(self.allocator, &vertices, wx, (h00 + h01) * 0.5, wz, size, (h00 + h01) * 0.5 - skirt_depth, unpackR(lit_avg_color) * 0.6, unpackG(lit_avg_color) * 0.6, unpackB(lit_avg_color) * 0.6, .west, side_tile, world_x, world_z);
if (gx == data.width - 2) try addSideFaceQuad(self.allocator, &vertices, wx, (h10 + h11) * 0.5, wz, size, (h10 + h11) * 0.5 - skirt_depth, unpackR(lit_avg_color) * 0.6, unpackG(lit_avg_color) * 0.6, unpackB(lit_avg_color) * 0.6, .east, side_tile, world_x, world_z);
if (gz == 0) try addSideFaceQuad(self.allocator, &vertices, wx, (h00 + h10) * 0.5, wz, size, (h00 + h10) * 0.5 - skirt_depth, unpackR(lit_avg_color) * 0.7, unpackG(lit_avg_color) * 0.7, unpackB(lit_avg_color) * 0.7, .north, side_tile, world_x, world_z);
if (gz == data.width - 2) try addSideFaceQuad(self.allocator, &vertices, wx, (h01 + h11) * 0.5, wz, size, (h01 + h11) * 0.5 - skirt_depth, unpackR(lit_avg_color) * 0.7, unpackG(lit_avg_color) * 0.7, unpackB(lit_avg_color) * 0.7, .south, side_tile, world_x, world_z);

try addHeightDeltaFaces(self.allocator, &vertices, data, gx, gz, wx, wz, size, h00, h10, h01, h11, lit_avg_color, side_tile, world_x, world_z);
// Adjacent cells share edge heights, so internal vertical walls are not needed.
// Boundary skirts above handle region edges without adding visible wall shards.
if (shouldRenderLODTree(self.lod_level, top_block)) {
const vegetation = representativeVegetation(data, gx, gz);
if (vegetation.tree_coverage >= 0.08) {
Expand Down Expand Up @@ -787,59 +793,6 @@ fn averageWaterCoverage(data: *const LODSimplifiedData, gx: u32, gz: u32) f32 {
return (c00 + c10 + c01 + c11) * 0.25;
}

fn addHeightDeltaFaces(
allocator: std.mem.Allocator,
vertices: *std.ArrayListUnmanaged(Vertex),
data: *const LODSimplifiedData,
gx: u32,
gz: u32,
wx: f32,
wz: f32,
size: f32,
h00: f32,
h10: f32,
h01: f32,
h11: f32,
color: u32,
side_tile: u16,
world_x: i32,
world_z: i32,
) !void {
const threshold = @max(2.0, size * 0.12);
const west_edge = (h00 + h01) * 0.5;
const east_edge = (h10 + h11) * 0.5;
const north_edge = (h00 + h10) * 0.5;
const south_edge = (h01 + h11) * 0.5;
const r = unpackR(color);
const g = unpackG(color);
const b = unpackB(color);

if (gx > 0) {
const neighbor_edge = (data.getHeight(gx - 1, gz) + data.getHeight(gx - 1, gz + 1)) * 0.5;
if (west_edge > neighbor_edge + threshold) {
try addSideFaceQuad(allocator, vertices, wx, west_edge, wz, size, neighbor_edge, r * 0.6, g * 0.6, b * 0.6, .west, side_tile, world_x, world_z);
}
}
if (gx + 2 < data.width) {
const neighbor_edge = (data.getHeight(gx + 2, gz) + data.getHeight(gx + 2, gz + 1)) * 0.5;
if (east_edge > neighbor_edge + threshold) {
try addSideFaceQuad(allocator, vertices, wx, east_edge, wz, size, neighbor_edge, r * 0.6, g * 0.6, b * 0.6, .east, side_tile, world_x, world_z);
}
}
if (gz > 0) {
const neighbor_edge = (data.getHeight(gx, gz - 1) + data.getHeight(gx + 1, gz - 1)) * 0.5;
if (north_edge > neighbor_edge + threshold) {
try addSideFaceQuad(allocator, vertices, wx, north_edge, wz, size, neighbor_edge, r * 0.7, g * 0.7, b * 0.7, .north, side_tile, world_x, world_z);
}
}
if (gz + 2 < data.width) {
const neighbor_edge = (data.getHeight(gx, gz + 2) + data.getHeight(gx + 1, gz + 2)) * 0.5;
if (south_edge > neighbor_edge + threshold) {
try addSideFaceQuad(allocator, vertices, wx, south_edge, wz, size, neighbor_edge, r * 0.7, g * 0.7, b * 0.7, .south, side_tile, world_x, world_z);
}
}
}

// Helper functions for unpacking colors
fn unpackR(color: u32) f32 {
return @as(f32, @floatFromInt((color >> 16) & 0xFF)) / 255.0;
Expand Down Expand Up @@ -929,6 +882,31 @@ fn makeLODVertex(pos: [3]f32, col: [3]f32, norm: [3]f32, uv: [2]f32, tile_id: u1
};
}

fn normalForHeightPoint(data: *const LODSimplifiedData, gx: u32, gz: u32, cell_size: f32) [3]f32 {
if (data.width < 2) return .{ 0, 1, 0 };

const left_x = if (gx == 0) gx else gx - 1;
const right_x = @min(gx + 1, data.width - 1);
const north_z = if (gz == 0) gz else gz - 1;
const south_z = @min(gz + 1, data.width - 1);
const h_left = data.getHeight(left_x, gz);
const h_right = data.getHeight(right_x, gz);
const h_north = data.getHeight(gx, north_z);
const h_south = data.getHeight(gx, south_z);
const x_span = @max(@as(f32, @floatFromInt(right_x - left_x)) * cell_size, 0.001);
const z_span = @max(@as(f32, @floatFromInt(south_z - north_z)) * cell_size, 0.001);
const dhdx = (h_right - h_left) / x_span;
const dhdz = (h_south - h_north) / z_span;

var n = [3]f32{ -dhdx, 1.0, -dhdz };
const len = @sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
if (len <= 0.0001) return .{ 0, 1, 0 };
n[0] /= len;
n[1] /= len;
n[2] /= len;
return n;
}

/// Add a smooth quad with per-vertex heights and colors
fn addSmoothQuad(
allocator: std.mem.Allocator,
Expand All @@ -944,6 +922,10 @@ fn addSmoothQuad(
c10: u32,
c01: u32,
c11: u32,
n00: [3]f32,
n10: [3]f32,
n01: [3]f32,
n11: [3]f32,
tile_id: u16,
world_x: i32,
world_z: i32,
Expand All @@ -953,45 +935,14 @@ fn addSmoothQuad(
const y01 = h01;
const y11 = h11;

// Calculate normals for each triangle
// Tri 1: (0,0) -> (1,1) -> (1,0)
const v1_0 = [3]f32{ size, y11 - y00, size };
const v1_1 = [3]f32{ size, y10 - y00, 0 };
var n1 = [3]f32{
v1_0[1] * v1_1[2] - v1_0[2] * v1_1[1],
v1_0[2] * v1_1[0] - v1_0[0] * v1_1[2],
v1_0[0] * v1_1[1] - v1_0[1] * v1_1[0],
};
const len1 = @sqrt(n1[0] * n1[0] + n1[1] * n1[1] + n1[2] * n1[2]);
if (len1 > 0.0001) {
n1[0] /= len1;
n1[1] /= len1;
n1[2] /= len1;
}

// Tri 2: (0,0) -> (0,1) -> (1,1)
const v2_0 = [3]f32{ 0, y01 - y00, size };
const v2_1 = [3]f32{ size, y11 - y00, size };
var n2 = [3]f32{
v2_0[1] * v2_1[2] - v2_0[2] * v2_1[1],
v2_0[2] * v2_1[0] - v2_0[0] * v2_1[2],
v2_0[0] * v2_1[1] - v2_0[1] * v2_1[0],
};
const len2 = @sqrt(n2[0] * n2[0] + n2[1] * n2[1] + n2[2] * n2[2]);
if (len2 > 0.0001) {
n2[0] /= len2;
n2[1] /= len2;
n2[2] /= len2;
}

// Triangle 1: (0,0), (1,1), (1,0)
try vertices.append(allocator, makeLODVertex(.{ x, y00, z }, .{ unpackR(c00), unpackG(c00), unpackB(c00) }, n1, topFaceUV(.{ x, y00, z }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x + size, y11, z + size }, .{ unpackR(c11), unpackG(c11), unpackB(c11) }, n1, topFaceUV(.{ x + size, y11, z + size }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x + size, y10, z }, .{ unpackR(c10), unpackG(c10), unpackB(c10) }, n1, topFaceUV(.{ x + size, y10, z }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x, y00, z }, .{ unpackR(c00), unpackG(c00), unpackB(c00) }, n00, topFaceUV(.{ x, y00, z }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x + size, y11, z + size }, .{ unpackR(c11), unpackG(c11), unpackB(c11) }, n11, topFaceUV(.{ x + size, y11, z + size }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x + size, y10, z }, .{ unpackR(c10), unpackG(c10), unpackB(c10) }, n10, topFaceUV(.{ x + size, y10, z }, world_x, world_z), tile_id));

try vertices.append(allocator, makeLODVertex(.{ x, y00, z }, .{ unpackR(c00), unpackG(c00), unpackB(c00) }, n2, topFaceUV(.{ x, y00, z }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x, y01, z + size }, .{ unpackR(c01), unpackG(c01), unpackB(c01) }, n2, topFaceUV(.{ x, y01, z + size }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x + size, y11, z + size }, .{ unpackR(c11), unpackG(c11), unpackB(c11) }, n2, topFaceUV(.{ x + size, y11, z + size }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x, y00, z }, .{ unpackR(c00), unpackG(c00), unpackB(c00) }, n00, topFaceUV(.{ x, y00, z }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x, y01, z + size }, .{ unpackR(c01), unpackG(c01), unpackB(c01) }, n01, topFaceUV(.{ x, y01, z + size }, world_x, world_z), tile_id));
try vertices.append(allocator, makeLODVertex(.{ x + size, y11, z + size }, .{ unpackR(c11), unpackG(c11), unpackB(c11) }, n11, topFaceUV(.{ x + size, y11, z + size }, world_x, world_z), tile_id));
}

/// Add a top-facing quad (two triangles)
Expand Down
11 changes: 11 additions & 0 deletions modules/world-worldgen/src/registry.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const log = @import("engine-core").log;
const gen_interface = @import("generator_interface.zig");
const Generator = gen_interface.Generator;
const overworld = @import("worldgen-overworld");
const overworld_v2 = @import("worldgen-overworld-v2");
const flat_world = @import("worldgen-flat");
const shadow_test_world = @import("worldgen-test");
const worldgen_api = @import("worldgen-api");
Expand Down Expand Up @@ -39,6 +40,12 @@ pub const GENERATORS = [_]GeneratorType{
.info = shadow_test_world.descriptor.info,
.initFn = initShadowTestWorld,
},
.{
.id = overworld_v2.descriptor.id,
.aliases = overworld_v2.descriptor.aliases,
.info = overworld_v2.descriptor.info,
.initFn = initOverworldV2,
},
};

fn initOverworld(seed: u64, allocator: std.mem.Allocator) RegistryError!Generator {
Expand All @@ -53,6 +60,10 @@ fn initShadowTestWorld(seed: u64, allocator: std.mem.Allocator) RegistryError!Ge
return shadow_test_world.descriptor.create(.{ .seed = seed, .allocator = allocator }) catch |err| return mapApiError(err);
}

fn initOverworldV2(seed: u64, allocator: std.mem.Allocator) RegistryError!Generator {
return overworld_v2.descriptor.create(.{ .seed = seed, .allocator = allocator }) catch |err| return mapApiError(err);
}

fn mapApiError(err: worldgen_api.RegistryError) RegistryError {
return switch (err) {
error.InvalidGeneratorIndex => error.InvalidGeneratorIndex,
Expand Down
2 changes: 2 additions & 0 deletions modules/world-worldgen/src/root.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub const overworld = @import("worldgen-overworld");
pub const overworld_v2 = @import("worldgen-overworld-v2");
pub const biome = overworld.biome;
pub const biome_color_provider = overworld.biome_color_provider;
pub const biome_edge_detector = overworld.biome_edge_detector;
Expand Down Expand Up @@ -56,6 +57,7 @@ pub const TerrainReport = terrain_report.TerrainReport;
pub const ChunkPhaseData = terrain_shape_generator.ChunkPhaseData;
pub const StandardDecorationProvider = decoration_registry.StandardDecorationProvider;
pub const OverworldGenerator = overworld_generator.OverworldGenerator;
pub const OverworldV2Generator = overworld_v2.OverworldV2Generator;
pub const ShadowTestWorldGenerator = shadow_test_world.ShadowTestWorldGenerator;
pub const GeneratorType = registry.GeneratorType;
pub const GENERATORS = registry.GENERATORS;
Expand Down
5 changes: 5 additions & 0 deletions modules/worldgen-overworld-v2/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
_ = b.addModule("worldgen-overworld-v2", .{ .root_source_file = b.path("src/root.zig") });
}
Loading
Loading