diff --git a/game/game b/game/game index 00ad5e3..4a43ccb 100755 Binary files a/game/game and b/game/game differ diff --git a/game/terrain.odin b/game/terrain.odin index b251834..edc48de 100644 --- a/game/terrain.odin +++ b/game/terrain.odin @@ -1,227 +1,247 @@ +#+feature dynamic-literals package game -import "core:math/noise" -import "core:math" import "core:fmt" +import "core:math" +import "core:math/noise" -BIOME_SCALE : f64 : 1 +BIOME_SCALE: f64 : 1 biome_list := map[u32]Biome { - 0 = grasslands_biome, - 1 = forest_biome, - 2 = desert_biome, - 3 = lake_biome, + 0 = grasslands_biome, + 1 = forest_biome, + 2 = desert_biome, + 3 = lake_biome, } BiomeType :: enum { - GRASSLAND, - FOREST, - LAKE, - DESERT, + GRASSLAND, + FOREST, + LAKE, + DESERT, } Biome :: struct { - id:u32, - name: string, - type: BiomeType, - fauna_color: [4]u8, - valid_structures: [dynamic]u32 + id: u32, + name: string, + type: BiomeType, + fauna_color: [4]u8, + valid_structures: [dynamic]u32, } // Define biome constants grasslands_biome := Biome { - id = 0, - name = "Grasslands", - type = .GRASSLAND, - fauna_color = {50, 120, 25, 255}, - valid_structures = {} + id = 0, + name = "Grasslands", + type = .GRASSLAND, + fauna_color = {50, 120, 25, 255}, + valid_structures = {}, } forest_biome := Biome { - id = 1, - name = "Forest", - type = .FOREST, - fauna_color = {30, 80, 20, 255}, - valid_structures = {} + id = 1, + name = "Forest", + type = .FOREST, + fauna_color = {30, 80, 20, 255}, + valid_structures = {}, } desert_biome := Biome { - id = 2, - name = "Desert", - type = .DESERT, - fauna_color = {200, 180, 100, 255}, - valid_structures = {} + id = 2, + name = "Desert", + type = .DESERT, + fauna_color = {200, 180, 100, 255}, + valid_structures = {}, } lake_biome := Biome { - id = 3, - name = "Lake", - type = .LAKE, - fauna_color = {0, 50, 150, 255}, - valid_structures = {} + id = 3, + name = "Lake", + type = .LAKE, + fauna_color = {0, 50, 150, 255}, + valid_structures = {}, } -get_biome_from_id :: proc(id:u32) -> Biome { - return biome_list[id] +get_biome_from_id :: proc(id: u32) -> Biome { + return biome_list[id] } get_biome_type :: proc(world_pos: Vec2i, seed: i64) -> Biome { - // Use multiple noise scales for different features - continent_scale := 0.0008 // Very large scale features (continents) - region_scale := 0.007 // Medium scale features (regions) - local_scale := 0.025 // Local variations - - // Use different seed offsets for each noise layer - continent_seed := seed - region_seed := seed + 10000 - moisture_seed := seed + 20000 - temperature_seed := seed + 30000 - - // Generate base continent shapes - continent := noise.noise_2d(continent_seed, {f64(world_pos.x) * continent_scale, f64(world_pos.y) * continent_scale}) - // Amplify to get more defined continents - continent = math.pow(continent * 0.5 + 0.5, 1.5) * 2.0 - 1.0 - - // Generate regional variations - region := noise.noise_2d(region_seed, {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}) - - // Generate moisture and temperature maps for biome determination - moisture := noise.noise_2d(moisture_seed, {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}) - temperature := noise.noise_2d(temperature_seed, {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}) - - // Adjust temperature to create larger hot regions - // This skews the distribution to have more areas with higher temperature - // temperature = math.pow(temperature * 0.5 + 0.5, 0.8) * 2.0 - 1.0 - - // Local variations (small details) - local_var := noise.noise_2d(seed, {f64(world_pos.x) * local_scale, f64(world_pos.y) * local_scale}) * 0.1 - - // Combine all factors with proper weighting - elevation := continent * 0.7 + region * 0.3 + local_var - - // Convert noise values to 0-1 range for easier thresholding - normalized_elevation := elevation * 0.5 + 0.5 - normalized_moisture := moisture * 0.5 + 0.5 - normalized_temperature := temperature * 0.5 + 0.5 - - if normalized_elevation < 0.3 { - return lake_biome - } - - if normalized_temperature > 0.7 && normalized_moisture < 0.2 { - return desert_biome - } - - // Forests need moderate to high moisture - if normalized_moisture > 0.55 { - return forest_biome - } - - // Default to grasslands - return grasslands_biome + // Use multiple noise scales for different features + continent_scale := 0.0008 // Very large scale features (continents) + region_scale := 0.007 // Medium scale features (regions) + local_scale := 0.025 // Local variations + + // Use different seed offsets for each noise layer + continent_seed := seed + region_seed := seed + 10000 + moisture_seed := seed + 20000 + temperature_seed := seed + 30000 + + // Generate base continent shapes + continent := noise.noise_2d( + continent_seed, + {f64(world_pos.x) * continent_scale, f64(world_pos.y) * continent_scale}, + ) + // Amplify to get more defined continents + continent = math.pow(continent * 0.5 + 0.5, 1.5) * 2.0 - 1.0 + + // Generate regional variations + region := noise.noise_2d( + region_seed, + {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}, + ) + + // Generate moisture and temperature maps for biome determination + moisture := noise.noise_2d( + moisture_seed, + {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}, + ) + temperature := noise.noise_2d( + temperature_seed, + {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}, + ) + + // Adjust temperature to create larger hot regions + // This skews the distribution to have more areas with higher temperature + // temperature = math.pow(temperature * 0.5 + 0.5, 0.8) * 2.0 - 1.0 + + // Local variations (small details) + local_var := + noise.noise_2d(seed, {f64(world_pos.x) * local_scale, f64(world_pos.y) * local_scale}) * + 0.1 + + // Combine all factors with proper weighting + elevation := continent * 0.7 + region * 0.3 + local_var + + // Convert noise values to 0-1 range for easier thresholding + normalized_elevation := elevation * 0.5 + 0.5 + normalized_moisture := moisture * 0.5 + 0.5 + normalized_temperature := temperature * 0.5 + 0.5 + + if normalized_elevation < 0.3 { + return lake_biome + } + + if normalized_temperature > 0.7 && normalized_moisture < 0.2 { + return desert_biome + } + + // Forests need moderate to high moisture + if normalized_moisture > 0.55 { + return forest_biome + } + + // Default to grasslands + return grasslands_biome } // Improved chunk generation that considers neighboring chunks generate_chunk :: proc(pos: Vec2i, seed: i64) -> Chunk { - chunk := Chunk{position = pos} - - // Store the biome for this chunk for consistency - chunk_center := Vec2i{pos.x * CHUNK_SIZE + CHUNK_SIZE/2, pos.y * CHUNK_SIZE + CHUNK_SIZE/2} - biome := get_biome_type(chunk_center, seed) - chunk.biome_id = biome.id + chunk := Chunk { + position = pos, + } - // Generate each tile, allowing for biome blending at edges - for x in 0.. blend_factor { - biome_to_use = tile_biome - } - } - - chunk.tiles[x][y] = generate_tile(world_pos, seed, biome_to_use) - } - } - - return chunk + // Store the biome for this chunk for consistency + chunk_center := Vec2i{pos.x * CHUNK_SIZE + CHUNK_SIZE / 2, pos.y * CHUNK_SIZE + CHUNK_SIZE / 2} + biome := get_biome_type(chunk_center, seed) + chunk.biome_id = biome.id + + // Generate each tile, allowing for biome blending at edges + for x in 0 ..< CHUNK_SIZE { + for y in 0 ..< CHUNK_SIZE { + world_x := pos.x * CHUNK_SIZE + x + world_y := pos.y * CHUNK_SIZE + y + world_pos := Vec2i{world_x, world_y} + + // Check the tile's specific biome (for transitions) + tile_biome := get_biome_type(world_pos, seed) + + // Calculate distances to chunk edges for potential blending + edge_dist_x := min(x, CHUNK_SIZE - 1 - x) + edge_dist_y := min(y, CHUNK_SIZE - 1 - y) + edge_dist := min(edge_dist_x, edge_dist_y) + + // Blend between chunk biome and tile biome near edges + // for smoother transitions between chunks + biome_to_use := biome + if edge_dist < 4 { // Within 4 tiles of chunk edge + blend_factor := f32(edge_dist) / 4.0 + + // Simple way to blend biomes - just pick one based on blend factor + // For a more sophisticated approach, you could actually blend features + if hash_noise(world_x, world_y, seed) > blend_factor { + biome_to_use = tile_biome + } + } + + chunk.tiles[x][y] = generate_tile(world_pos, seed, biome_to_use) + } + } + + return chunk } // Improved tile generation with biome transition support generate_tile :: proc(pos: Vec2i, seed: i64, biome: Biome) -> Tile { - hash_value := hash_noise(pos.x, pos.y, seed) - - // Use multiple noise scales for natural-looking features - large_scale := 0.025 - medium_scale := 0.07 - small_scale := 0.20 - - large_noise := noise.noise_2d(seed, {f64(pos.x) * large_scale, f64(pos.y) * large_scale}) - medium_noise := noise.noise_2d(seed + 5000, {f64(pos.x) * medium_scale, f64(pos.y) * medium_scale}) - small_noise := noise.noise_2d(seed + 10000, {f64(pos.x) * small_scale, f64(pos.y) * small_scale}) - - // Combine noise at different scales - combined_noise := large_noise * 0.6 + medium_noise * 0.3 + small_noise * 0.1 - - // Different biomes use the noise differently - switch biome.type { - case .GRASSLAND: - if combined_noise > 0.8 { - return tree_tile - } else if combined_noise > 0.2 { - return grass_tile - } else { - return nothing_tile - } - case .FOREST: - if combined_noise > 0.75 { - return double_tree_tile - } else if combined_noise > 0.4 { - return tree_tile - } else if combined_noise > 0.0 { - return grass_tile - } else { - return nothing_tile - } - case .DESERT: + hash_value := hash_noise(pos.x, pos.y, seed) - cactus_noise := medium_noise * 0.5 + 0.5 // Normalize to 0-1 + // Use multiple noise scales for natural-looking features + large_scale := 0.025 + medium_scale := 0.07 + small_scale := 0.20 - if cactus_noise > 0.8 && hash_value > 0.65 { - return cactus_tile - } else if combined_noise > 0.85 { - return dead_bush_tile - } else { - return nothing_tile - } - case .LAKE: - // Lakes can have different depths - if combined_noise > 0.7 { - return shallow_water_tile // You'd need to define this - } else { - return water_tile - } - case: - return nothing_tile - } + large_noise := noise.noise_2d(seed, {f64(pos.x) * large_scale, f64(pos.y) * large_scale}) + medium_noise := noise.noise_2d( + seed + 5000, + {f64(pos.x) * medium_scale, f64(pos.y) * medium_scale}, + ) + small_noise := noise.noise_2d( + seed + 10000, + {f64(pos.x) * small_scale, f64(pos.y) * small_scale}, + ) + + // Combine noise at different scales + combined_noise := large_noise * 0.6 + medium_noise * 0.3 + small_noise * 0.1 + + // Different biomes use the noise differently + switch biome.type { + case .GRASSLAND: + if combined_noise > 0.8 { + return tree_tile + } else if combined_noise > 0.2 { + return grass_tile + } else { + return nothing_tile + } + case .FOREST: + if combined_noise > 0.75 { + return double_tree_tile + } else if combined_noise > 0.4 { + return tree_tile + } else if combined_noise > 0.0 { + return grass_tile + } else { + return nothing_tile + } + case .DESERT: + cactus_noise := medium_noise * 0.5 + 0.5 // Normalize to 0-1 + + if cactus_noise > 0.8 && hash_value > 0.65 { + return cactus_tile + } else if combined_noise > 0.85 { + return dead_bush_tile + } else { + return nothing_tile + } + case .LAKE: + // Lakes can have different depths + if combined_noise > 0.7 { + return shallow_water_tile // You'd need to define this + } else { + return water_tile + } + case: + return nothing_tile + } } - -