From e6f46dcad9b574859027902ad524de30af88646f Mon Sep 17 00:00:00 2001 From: Voyager Date: Mon, 24 Aug 2026 20:21:49 -0400 Subject: [PATCH] Improve generated river terrain --- docs/DEVELOPMENT.md | 8 +- scripts/normalize_texture_imports.gd | 12 ++ tests/generated_world_runtime_validation.gd | 87 ++++++++ tests/terrain_chunk_generator_validation.gd | 52 ++++- tests/texture_sampling_validation.gd | 14 +- .../assets/chunk_0000_grass_lite.png.import | 2 +- .../chunks/assets/chunk_0001_sand.png.import | 2 +- .../chunks/assets/chunk_0031_dirt.png.import | 2 +- world/generation/chunks/assets/chunk_0038.glb | Bin 0 -> 36704 bytes .../chunks/assets/chunk_0038.glb.import | 45 ++++ .../chunks/assets/chunk_0038_cliff_wall.png | Bin 0 -> 9025 bytes .../assets/chunk_0038_cliff_wall.png.import | 45 ++++ .../chunks/assets/chunk_0038_dirt.png | Bin 0 -> 5589 bytes .../chunks/assets/chunk_0038_dirt.png.import | 45 ++++ .../chunks/assets/chunk_0038_grass_lite.png | Bin 0 -> 6317 bytes .../assets/chunk_0038_grass_lite.png.import | 45 ++++ .../chunks/definitions/chunk_0038.tres | 23 +++ .../chunks/terrain_chunk_catalog.tres | 5 +- world/generation/generated_world_region.gd | 195 +++++++++++++++++- world/generation/terrain_chunk_generator.gd | 20 ++ .../terrain_surface_projection.gdshader | 2 +- 21 files changed, 586 insertions(+), 18 deletions(-) create mode 100644 world/generation/chunks/assets/chunk_0038.glb create mode 100644 world/generation/chunks/assets/chunk_0038.glb.import create mode 100644 world/generation/chunks/assets/chunk_0038_cliff_wall.png create mode 100644 world/generation/chunks/assets/chunk_0038_cliff_wall.png.import create mode 100644 world/generation/chunks/assets/chunk_0038_dirt.png create mode 100644 world/generation/chunks/assets/chunk_0038_dirt.png.import create mode 100644 world/generation/chunks/assets/chunk_0038_grass_lite.png create mode 100644 world/generation/chunks/assets/chunk_0038_grass_lite.png.import create mode 100644 world/generation/chunks/definitions/chunk_0038.tres diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 9fbd5b9..09db779 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -261,9 +261,11 @@ copies of the entire customization catalog. ### Texture sampling -NETfishing artwork always uses nearest-neighbor sampling. Do not enable linear, -bilinear, trilinear, or anisotropic texture filtering, and do not generate -mipmaps. Shader samplers must declare `filter_nearest`. The +NETfishing artwork uses nearest-neighbor sampling. Do not enable linear, +bilinear, trilinear, or anisotropic texture filtering. Shader samplers must +declare `filter_nearest`. The three canonical repeating terrain textures are +the sole mipmap exception: their projected shader uses nearest-filtered +mipmaps to prevent distance aliasing without smoothing nearby pixels. The `TextureSamplingPolicy` autoload applies nearest sampling to 2D canvas items, Sprite3D presentations, imported 3D materials, and dynamically constructed mesh materials at runtime. diff --git a/scripts/normalize_texture_imports.gd b/scripts/normalize_texture_imports.gd index 5b78ec1..e7b2406 100644 --- a/scripts/normalize_texture_imports.gd +++ b/scripts/normalize_texture_imports.gd @@ -31,6 +31,11 @@ const FACIAL_FEATURE_IMPORT_PATH: String = ( "art/exported/characters/faces/" ) const FACIAL_FEATURE_SIZE_LIMIT: int = 512 +const PROJECTED_TERRAIN_TEXTURE_IMPORTS: Array[String] = [ + "res://world/generation/chunks/assets/chunk_0000_grass_lite.png.import", + "res://world/generation/chunks/assets/chunk_0001_sand.png.import", + "res://world/generation/chunks/assets/chunk_0031_dirt.png.import", +] var apply_changes: bool = false var verbose: bool = false @@ -153,6 +158,8 @@ func _handle_import_file(import_path: String) -> void: var desired: Variant = section_data[section_key] if section_key == "process/size_limit": desired = _size_limit_for(import_path) + elif section_key == "mipmaps/generate": + desired = _generates_mipmaps(import_path) var existing: Variant = null if cfg.has_section(section_name) and cfg.has_section_key(section_name, section_key): existing = cfg.get_value(section_name, section_key) @@ -187,6 +194,11 @@ func _size_limit_for(import_path: String) -> int: return int(DEFAULT_PROFILE["params"]["process/size_limit"]) +func _generates_mipmaps(import_path: String) -> bool: + var normalized_path := import_path.replace("\\", "/") + return normalized_path in PROJECTED_TERRAIN_TEXTURE_IMPORTS + + func print_summary() -> void: if apply_changes: _print( diff --git a/tests/generated_world_runtime_validation.gd b/tests/generated_world_runtime_validation.gd index ef933bf..0143322 100644 --- a/tests/generated_world_runtime_validation.gd +++ b/tests/generated_world_runtime_validation.gd @@ -247,6 +247,7 @@ func _validate_generated_region( assert(_placement_count(placements, "chunk_0034") == 1) assert(_placement_count(placements, "chunk_0035") == 1) assert(_placement_count(placements, "chunk_0036") == 1) + assert(_placement_count(placements, "chunk_0038") == 2) assert( _placement_count(placements, "chunk_0014") == (2 if has_secondary_elevation else 0) @@ -285,10 +286,31 @@ func _validate_generated_region( var river_edge_count := ( _placement_count(placements, "chunk_0024") + _placement_count(placements, "chunk_0025") + + _placement_count(placements, "chunk_0038") ) assert(river_coordinate_count >= 12) assert(generator._river_feature_placements.size() == river_coordinate_count) assert(river_edge_count == river_coordinate_count - 8) + var stone_bridge_specs: Array[Dictionary] = [] + for spec: Dictionary in generator._river_feature_placements: + if spec.get("id", &"") == &"chunk_0038": + stone_bridge_specs.append(spec) + assert(stone_bridge_specs.size() == 2) + var first_bridge_coordinate := stone_bridge_specs[0].get( + "coordinate", + Vector2i(-1, -1), + ) as Vector2i + var second_bridge_coordinate := stone_bridge_specs[1].get( + "coordinate", + Vector2i(-1, -1), + ) as Vector2i + var bridge_delta := second_bridge_coordinate - first_bridge_coordinate + assert(absi(bridge_delta.x) + absi(bridge_delta.y) == 1) + assert(posmod( + int(stone_bridge_specs[1].get("turns", -1)) + - int(stone_bridge_specs[0].get("turns", -1)), + 4, + ) == 2) var river_source_coordinates := generator._river_source_coordinates( generator._river_feature_origin, generator._river_feature_footprint_size, @@ -446,6 +468,20 @@ func _validate_generated_region( assert(fresh.surface_size.x <= 10.0 and fresh.surface_size.y <= 10.0) assert(not fresh.visual_surface_enabled) assert(not (fresh.get_node("VisualWater") as MeshInstance3D).visible) + var generated_bed := fresh.get_node_or_null( + "GeneratedBed", + ) as MeshInstance3D + assert(generated_bed != null and generated_bed.mesh != null) + assert( + is_equal_approx( + generated_bed.global_position.y, + GeneratedWorldRegion.GENERATED_FRESH_WATER_BED_HEIGHT, + ) + ) + assert( + generated_bed.material_override != null + and generated_bed.material_override.resource_name == "dirt" + ) var fresh_recovery := ( fresh.get_node("RecoveryRegion") as PlayerWaterTrigger ) @@ -486,6 +522,16 @@ func _validate_generated_region( ) var decorations := region.get_node("Decorations") as Node3D + var grass_prop_surface_triangles := region.get_spawn_surface_triangles( + [&"grass_lite"], + -INF, + 0.35, + ) + var sand_prop_surface_triangles := region.get_spawn_surface_triangles( + [&"sand"], + -INF, + 0.35, + ) var anchors := region.get_node( "GatherableAnchors/ReachableTreeTrunks" ) as GatherableAnchorSet3D @@ -540,6 +586,8 @@ func _validate_generated_region( region, child as Node3D, definition, + grass_prop_surface_triangles, + sand_prop_surface_triangles, ) if prop_id == &"prop_palm": var cluster_id := int( @@ -1063,6 +1111,8 @@ func _validate_decoration_transform( region: GeneratedWorldRegion, prop: Node3D, definition: TerrainPropDefinition, + grass_surface_triangles: Array[PackedVector3Array], + sand_surface_triangles: Array[PackedVector3Array], ) -> void: assert(prop != null) assert(prop.scale.is_equal_approx(Vector3.ONE)) @@ -1142,6 +1192,43 @@ func _validate_decoration_transform( ], ], ) + var required_surface_triangles: Array[PackedVector3Array] = ( + sand_surface_triangles + if definition.procedural_group == &"sand_tree" + else grass_surface_triangles + ) + var required_surface_height := _triangle_surface_height_at( + prop.global_position, + required_surface_triangles, + ) + assert( + required_surface_height > -INF + and absf(required_surface_height - prop.global_position.y) <= 0.01, + "%s (%s) is not rooted on its required terrain material at %s." + % [prop.name, definition.stable_id, prop.global_position], + ) + + +func _triangle_surface_height_at( + position: Vector3, + triangles: Array[PackedVector3Array], +) -> float: + var highest := -INF + var segment_start := position + Vector3.UP * 100.0 + var segment_end := position + Vector3.DOWN * 100.0 + for triangle: PackedVector3Array in triangles: + if triangle.size() != 3: + continue + var hit: Variant = Geometry3D.segment_intersects_triangle( + segment_start, + segment_end, + triangle[0], + triangle[1], + triangle[2], + ) + if hit is Vector3: + highest = maxf(highest, (hit as Vector3).y) + return highest func _validate_tree_gatherable_anchors( diff --git a/tests/terrain_chunk_generator_validation.gd b/tests/terrain_chunk_generator_validation.gd index b8d24cf..a46d1d7 100644 --- a/tests/terrain_chunk_generator_validation.gd +++ b/tests/terrain_chunk_generator_validation.gd @@ -42,6 +42,7 @@ const EXPECTED_IDS: Array[String] = [ "chunk_0035", "chunk_0036", "chunk_0037", + "chunk_0038", "chunk_9999", "chunk_9998", "chunk_9997", @@ -98,6 +99,7 @@ const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = { &"chunk_0035": 4, &"chunk_0036": 4, &"chunk_0037": 4, + &"chunk_0038": 4, &"chunk_9999": 4, &"chunk_9998": 4, &"chunk_9997": 4, @@ -163,6 +165,7 @@ func _validate_catalog() -> void: var lake_narrow_to_regular := CATALOG.definition_for_id(&"chunk_0023") var river_edge := CATALOG.definition_for_id(&"chunk_0024") var river_edge_variant := CATALOG.definition_for_id(&"chunk_0025") + var river_stone_bridge := CATALOG.definition_for_id(&"chunk_0038") var river_source_right := CATALOG.definition_for_id(&"chunk_0029") var river_source_left := CATALOG.definition_for_id(&"chunk_0030") var river_outlet_right := CATALOG.definition_for_id(&"chunk_0031") @@ -208,6 +211,7 @@ func _validate_catalog() -> void: and lake_narrow_to_regular != null and river_edge != null and river_edge_variant != null + and river_stone_bridge != null and river_source_right != null and river_source_left != null and river_outlet_right != null @@ -255,6 +259,7 @@ func _validate_catalog() -> void: and lake_narrow_to_regular != null and river_edge != null and river_edge_variant != null + and river_stone_bridge != null and river_source_right != null and river_source_left != null and river_outlet_right != null @@ -509,6 +514,8 @@ func _validate_catalog() -> void: ( not river_edge.participates_in_base_solver and not river_edge_variant.participates_in_base_solver + and not river_stone_bridge.participates_in_base_solver + and river_stone_bridge.maximum_placements == 2 and not river_source_right.participates_in_base_solver and not river_source_left.participates_in_base_solver and not river_outlet_right.participates_in_base_solver @@ -522,6 +529,8 @@ func _validate_catalog() -> void: and river_edge.water_outlet_edges == 14 and river_edge_variant.water_inlet_edges == 14 and river_edge_variant.water_outlet_edges == 14 + and river_stone_bridge.water_inlet_edges == 14 + and river_stone_bridge.water_outlet_edges == 14 and river_source_right.water_inlet_edges == 6 and river_source_right.water_outlet_edges == 6 and river_source_left.water_inlet_edges == 3 @@ -544,6 +553,7 @@ func _validate_catalog() -> void: and river_cap.water_outlet_edges == 6 and river_edge.water_surface_polygon.size() == 13 and river_edge_variant.water_surface_polygon.size() == 13 + and river_stone_bridge.water_surface_polygon.size() == 13 and river_source_right.water_surface_polygon.size() == 13 and river_source_left.water_surface_polygon.size() == 13 and river_outlet_right.water_surface_polygon.size() == 19 @@ -554,6 +564,8 @@ func _validate_catalog() -> void: and river_cap.primary_mesh_name == &"chunk_0020" and "river" in river_edge.tags and "river" in river_edge_variant.tags + and "river_bridge" in river_stone_bridge.tags + and "stepping_stones" in river_stone_bridge.tags and "river_source_right" in river_source_right.tags and "river_source_left" in river_source_left.tags and "river_outlet_right" in river_outlet_right.tags @@ -1461,6 +1473,10 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: var river_edge_variant_bottom := _find_variant(generator, &"chunk_0025", 2) var river_edge_vertical_left := _find_variant(generator, &"chunk_0024", 1) var river_edge_vertical_right := _find_variant(generator, &"chunk_0025", 3) + var river_stones_top := _find_variant(generator, &"chunk_0038", 0) + var river_stones_bottom := _find_variant(generator, &"chunk_0038", 2) + var river_stones_left := _find_variant(generator, &"chunk_0038", 1) + var river_stones_right := _find_variant(generator, &"chunk_0038", 3) var river_source_right := _find_variant(generator, &"chunk_0029", 0) var river_source_left := _find_variant(generator, &"chunk_0030", 0) var river_outlet_right := _find_variant(generator, &"chunk_0031", 0) @@ -1500,6 +1516,10 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: and river_edge_variant_bottom != null and river_edge_vertical_left != null and river_edge_vertical_right != null + and river_stones_top != null + and river_stones_bottom != null + and river_stones_left != null + and river_stones_right != null and river_source_right != null and river_source_left != null and river_outlet_right != null @@ -1525,6 +1545,10 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: and river_edge_variant_bottom != null and river_edge_vertical_left != null and river_edge_vertical_right != null + and river_stones_top != null + and river_stones_bottom != null + and river_stones_left != null + and river_stones_right != null and river_source_right != null and river_source_left != null and river_outlet_right != null @@ -1535,6 +1559,30 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: and river_bend_d != null and beach_east != null ): + _check( + generator._edges_are_compatible( + river_edge_top, + TerrainChunkTopology.Edge.EAST, + river_stones_top, + TerrainChunkTopology.Edge.WEST, + ) + and generator._edges_are_compatible( + river_stones_top, + TerrainChunkTopology.Edge.SOUTH, + river_stones_bottom, + TerrainChunkTopology.Edge.NORTH, + ) + and generator._edges_are_compatible( + river_stones_left, + TerrainChunkTopology.Edge.EAST, + river_stones_right, + TerrainChunkTopology.Edge.WEST, + ), + ( + "The stepping-stone banks must replace one paired straight river " + + "station in either orientation." + ), + ) _check( generator._edges_are_compatible( river_source_right, @@ -1750,8 +1798,8 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: func _validate_generation_summary(generator: TerrainChunkGenerator) -> void: var summary := generator._build_summary() _check( - int(summary.get("variant_count", 0)) == 170, - "The current catalog must expose all 170 authored rotations.", + int(summary.get("variant_count", 0)) == 174, + "The current catalog must expose all 174 authored rotations.", ) _check( int(summary.get("solver_variant_count", 0)) diff --git a/tests/texture_sampling_validation.gd b/tests/texture_sampling_validation.gd index deeabce..dcccc38 100644 --- a/tests/texture_sampling_validation.gd +++ b/tests/texture_sampling_validation.gd @@ -1,6 +1,11 @@ extends SceneTree const POLICY := preload("res://main/texture_sampling_policy.gd") +const PROJECTED_TERRAIN_TEXTURE_IMPORTS: Array[String] = [ + "res://world/generation/chunks/assets/chunk_0000_grass_lite.png.import", + "res://world/generation/chunks/assets/chunk_0001_sand.png.import", + "res://world/generation/chunks/assets/chunk_0031_dirt.png.import", +] var failures: Array[String] = [] @@ -83,9 +88,14 @@ func _validate_repository_files(path: String) -> void: func _validate_import_file(path: String) -> void: var source := FileAccess.get_file_as_string(path) + var expects_mipmaps := path in PROJECTED_TERRAIN_TEXTURE_IMPORTS _check( - "mipmaps/generate=true" not in source, - "Texture import generates mipmaps: %s" % path, + ("mipmaps/generate=true" in source) == expects_mipmaps, + ( + "Projected terrain mipmap policy mismatch: %s" + if expects_mipmaps + else "Texture import generates unapproved mipmaps: %s" + ) % path, ) diff --git a/world/generation/chunks/assets/chunk_0000_grass_lite.png.import b/world/generation/chunks/assets/chunk_0000_grass_lite.png.import index ff2855e..bff65f3 100644 --- a/world/generation/chunks/assets/chunk_0000_grass_lite.png.import +++ b/world/generation/chunks/assets/chunk_0000_grass_lite.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0001_sand.png.import b/world/generation/chunks/assets/chunk_0001_sand.png.import index 3e91993..290b8f9 100644 --- a/world/generation/chunks/assets/chunk_0001_sand.png.import +++ b/world/generation/chunks/assets/chunk_0001_sand.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0031_dirt.png.import b/world/generation/chunks/assets/chunk_0031_dirt.png.import index 3d13618..8e60684 100644 --- a/world/generation/chunks/assets/chunk_0031_dirt.png.import +++ b/world/generation/chunks/assets/chunk_0031_dirt.png.import @@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/world/generation/chunks/assets/chunk_0038.glb b/world/generation/chunks/assets/chunk_0038.glb new file mode 100644 index 0000000000000000000000000000000000000000..5fa83e3db2b6354ee4c5beaadafbb12a72eb474f GIT binary patch literal 36704 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 literal 0 HcmV?d00001 diff --git a/world/generation/chunks/assets/chunk_0038_dirt.png.import b/world/generation/chunks/assets/chunk_0038_dirt.png.import new file mode 100644 index 0000000..3a73467 --- /dev/null +++ b/world/generation/chunks/assets/chunk_0038_dirt.png.import @@ -0,0 +1,45 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvffkp5rc3xnq" +path.s3tc="res://.godot/imported/chunk_0038_dirt.png-523fbe5835e3869864352e6e698e49e3.s3tc.ctex" +path.etc2="res://.godot/imported/chunk_0038_dirt.png-523fbe5835e3869864352e6e698e49e3.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} +generator_parameters={ +"md5": "07d90b3c46a4c3d0c172bed143dc9436" +} + +[deps] + +source_file="res://world/generation/chunks/assets/chunk_0038_dirt.png" +dest_files=["res://.godot/imported/chunk_0038_dirt.png-523fbe5835e3869864352e6e698e49e3.s3tc.ctex", "res://.godot/imported/chunk_0038_dirt.png-523fbe5835e3869864352e6e698e49e3.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/world/generation/chunks/assets/chunk_0038_grass_lite.png b/world/generation/chunks/assets/chunk_0038_grass_lite.png new file mode 100644 index 0000000000000000000000000000000000000000..52c7ccb8088ddfae88f9b861a807f1de2d38f855 GIT binary patch literal 6317 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 void: _placed_group_coordinates.clear() var random := RandomNumberGenerator.new() random.seed = _current_seed ^ 0x5EED71 - var terrain_triangles := _terrain_surface_triangles() + var grass_surface_triangles_by_coordinate := ( + _terrain_surface_triangles_by_coordinate( + GRASS_PROP_SURFACE_MATERIALS, + ) + ) + var sand_surface_triangles_by_coordinate := ( + _terrain_surface_triangles_by_coordinate( + SAND_PROP_SURFACE_MATERIALS, + ) + ) var biomes: Array[TerrainBiomeDefinition] = [] if biome_catalog != null: biomes.assign(biome_catalog.definitions) @@ -250,6 +276,14 @@ func _on_generation_completed(summary: Dictionary) -> void: "tags", PackedStringArray(), ) + var terrain_triangles := _surface_triangles_for_coordinate( + ( + sand_surface_triangles_by_coordinate + if group == &"sand_tree" + else grass_surface_triangles_by_coordinate + ), + coordinate, + ) var definitions := _spawn_distance_eligible_definitions( _prop_definitions_for(rule, tags), coordinate, @@ -287,7 +321,8 @@ func _on_generation_completed(summary: Dictionary) -> void: eligible_records, group_counts, random, - terrain_triangles, + grass_surface_triangles_by_coordinate, + sand_surface_triangles_by_coordinate, ) _place_spawn_amenities(spawn_position) _configure_fresh_water(records) @@ -406,6 +441,11 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void: body.surface_polygon = surface_polygon else: body.surface_size = surface_size + _add_generated_fresh_water_bed( + body, + surface_polygon, + surface_size, + ) body.visual_surface_enabled = false body.water_material = _fresh_water_material() body.water_type = WaterType.Type.FRESH_WATER @@ -418,6 +458,59 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void: body.selection_priority = 10 +func _add_generated_fresh_water_bed( + body: WaterBodyAuthoring, + surface_polygon: PackedVector2Array, + surface_size: Vector2, +) -> void: + var polygon := surface_polygon + if polygon.size() < 3: + var half_size := surface_size * 0.5 + polygon = PackedVector2Array([ + Vector2(-half_size.x, -half_size.y), + Vector2(half_size.x, -half_size.y), + Vector2(half_size.x, half_size.y), + Vector2(-half_size.x, half_size.y), + ]) + var triangle_indices := Geometry2D.triangulate_polygon(polygon) + if triangle_indices.size() < 3: + return + var vertices := PackedVector3Array() + var normals := PackedVector3Array() + for offset: int in range(0, triangle_indices.size(), 3): + var triangle := PackedVector3Array() + for point_offset: int in 3: + var point := polygon[triangle_indices[offset + point_offset]] + triangle.append(Vector3(point.x, 0.0, point.y)) + if ( + (triangle[1] - triangle[0]).cross( + triangle[2] - triangle[0] + ).dot(Vector3.UP) < 0.0 + ): + var swap := triangle[1] + triangle[1] = triangle[2] + triangle[2] = swap + vertices.append_array(triangle) + normals.append_array(PackedVector3Array([ + Vector3.UP, + Vector3.UP, + Vector3.UP, + ])) + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertices + arrays[Mesh.ARRAY_NORMAL] = normals + var mesh := ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + var bed := MeshInstance3D.new() + bed.name = "GeneratedBed" + bed.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + bed.position.y = GENERATED_FRESH_WATER_BED_HEIGHT - WATER_HEIGHT + bed.material_override = GENERATED_TERRAIN_DIRT_MATERIAL + bed.mesh = mesh + body.add_child(bed) + + func _fresh_water_location_tags(tags: PackedStringArray) -> Array[StringName]: var result: Array[StringName] = [&"generated_fresh_water"] if "pond" in tags: @@ -524,6 +617,7 @@ func _add_prop( ) var primary_scale := visual_scales[0] var scaled_clearance := definition.clearance_radius * primary_scale + var support_radius := clampf(scaled_clearance * 0.35, 0.25, 0.65) var placement := Vector3.ZERO var found_surface := false for _attempt: int in PROP_PLACEMENT_ATTEMPTS: @@ -541,6 +635,12 @@ func _add_prop( surface_height > -INF and surface_height > WATER_HEIGHT + PROP_MINIMUM_GROUND_CLEARANCE + and _surface_supports_prop_footprint( + placement, + surface_height, + support_radius, + terrain_triangles, + ) and _has_prop_clearance( placement, scaled_clearance, @@ -572,6 +672,11 @@ func _add_prop( for member_index: int in range(1, cluster_size): var member_scale := visual_scales[member_index] var member_clearance := definition.clearance_radius * member_scale + var member_support_radius := clampf( + member_clearance * 0.35, + 0.25, + 0.65, + ) for _attempt: int in PROP_CLUSTER_PLACEMENT_ATTEMPTS: var sector_angle := ( cluster_angle @@ -598,6 +703,12 @@ func _add_prop( ) if ( surface_height <= WATER_HEIGHT + PROP_MINIMUM_GROUND_CLEARANCE + or not _surface_supports_prop_footprint( + candidate, + surface_height, + member_support_radius, + terrain_triangles, + ) or not _has_prop_clearance(candidate, member_clearance) ): continue @@ -959,7 +1070,8 @@ func _ensure_minimum_props( eligible_records: Dictionary[StringName, Array], group_counts: Dictionary[StringName, int], random: RandomNumberGenerator, - terrain_triangles: Array[PackedVector3Array], + grass_surface_triangles_by_coordinate: Dictionary[Vector2i, Array], + sand_surface_triangles_by_coordinate: Dictionary[Vector2i, Array], ) -> void: if biome_catalog == null: return @@ -993,6 +1105,14 @@ func _ensure_minimum_props( "coordinate", Vector2i.ZERO, ) + var terrain_triangles := _surface_triangles_for_coordinate( + ( + sand_surface_triangles_by_coordinate + if group == &"sand_tree" + else grass_surface_triangles_by_coordinate + ), + coordinate, + ) var definitions := _spawn_distance_eligible_definitions( _prop_definitions_for(rule, tags), coordinate, @@ -1101,12 +1221,26 @@ func _apply_material_variant_to_meshes( ) -func _terrain_surface_triangles() -> Array[PackedVector3Array]: - var triangles: Array[PackedVector3Array] = [] +func _terrain_surface_triangles_by_coordinate( + material_names: Array[StringName], +) -> Dictionary[Vector2i, Array]: + var triangles_by_coordinate: Dictionary[Vector2i, Array] = {} + if material_names.is_empty(): + return triangles_by_coordinate for mesh_instance: MeshInstance3D in _generator.get_primary_terrain_meshes(): if mesh_instance.mesh == null: continue + var coordinate := _terrain_coordinate_for(mesh_instance) + if coordinate.x < 0 or coordinate.y < 0: + continue + var triangles: Array[PackedVector3Array] = [] for surface_index: int in mesh_instance.mesh.get_surface_count(): + var material := mesh_instance.get_active_material(surface_index) + if ( + material == null + or StringName(material.resource_name) not in material_names + ): + continue _append_surface_triangles( triangles, mesh_instance, @@ -1114,6 +1248,35 @@ func _terrain_surface_triangles() -> Array[PackedVector3Array]: -INF, 0.35, ) + if triangles.is_empty(): + continue + var coordinate_triangles: Array = triangles_by_coordinate.get( + coordinate, + [], + ) + coordinate_triangles.append_array(triangles) + triangles_by_coordinate[coordinate] = coordinate_triangles + return triangles_by_coordinate + + +func _terrain_coordinate_for(mesh_instance: MeshInstance3D) -> Vector2i: + var current: Node = mesh_instance + while current != null and current != _generator: + if current.has_meta(&"terrain_chunk_coordinate"): + return current.get_meta( + &"terrain_chunk_coordinate", + Vector2i(-1, -1), + ) as Vector2i + current = current.get_parent() + return Vector2i(-1, -1) + + +func _surface_triangles_for_coordinate( + triangles_by_coordinate: Dictionary[Vector2i, Array], + coordinate: Vector2i, +) -> Array[PackedVector3Array]: + var triangles: Array[PackedVector3Array] = [] + triangles.assign(triangles_by_coordinate.get(coordinate, [])) return triangles @@ -1139,6 +1302,28 @@ func _surface_height_at( return highest +func _surface_supports_prop_footprint( + position: Vector3, + surface_height: float, + radius: float, + triangles: Array[PackedVector3Array], +) -> bool: + for direction: Vector2 in PROP_SURFACE_SAMPLE_DIRECTIONS: + var sample_position := position + Vector3( + direction.x * radius, + 0.0, + direction.y * radius, + ) + var sample_height := _surface_height_at(sample_position, triangles) + if ( + sample_height <= -INF + or absf(sample_height - surface_height) + > PROP_MAXIMUM_SUPPORT_HEIGHT_DIFFERENCE + ): + return false + return true + + func _add_prop_collision( prop: Node3D, definition: TerrainPropDefinition, diff --git a/world/generation/terrain_chunk_generator.gd b/world/generation/terrain_chunk_generator.gd index 0d1703c..49bf693 100644 --- a/world/generation/terrain_chunk_generator.gd +++ b/world/generation/terrain_chunk_generator.gd @@ -64,6 +64,7 @@ const PROJECTED_DIRT_MATERIAL: Material = preload( @export var river_feature_enabled := false @export var river_edge_chunk_id: StringName = &"chunk_0024" @export var river_edge_variant_chunk_id: StringName = &"chunk_0025" +@export var river_stone_bridge_chunk_id: StringName = &"chunk_0038" @export var river_source_right_chunk_id: StringName = &"chunk_0029" @export var river_source_left_chunk_id: StringName = &"chunk_0030" @export var river_outlet_right_chunk_id: StringName = &"chunk_0031" @@ -1728,6 +1729,7 @@ func _prepare_river_feature_region(report_no_room_error := true) -> bool: for stable_id: StringName in [ river_edge_chunk_id, river_edge_variant_chunk_id, + river_stone_bridge_chunk_id, river_source_right_chunk_id, river_source_left_chunk_id, river_outlet_right_chunk_id, @@ -2190,6 +2192,16 @@ func _build_river_feature_candidate( + bend_origin.y, 2, ) + var straight_station_count := bend_distance + outlet_distance - 2 + var bridge_station_index := posmod( + generation_seed + + source_coordinates[0].x * 73856093 + + source_coordinates[0].y * 19349663 + + bend_origin.x * 83492791 + + bend_origin.y * 2971215073 + + bend_turns * 433494437, + straight_station_count, + ) var station_index := 0 for step: int in range(1, bend_distance): var pair: Array[Vector2i] = [] @@ -2200,6 +2212,7 @@ func _build_river_feature_candidate( pair, flow_direction, phase + station_index, + station_index == bridge_station_index, ) ) station_index += 1 @@ -2215,6 +2228,7 @@ func _build_river_feature_candidate( pair, outlet_direction, phase + station_index, + station_index == bridge_station_index, ) ) station_index += 1 @@ -2355,6 +2369,7 @@ func _river_edge_pair_specs( pair: Array[Vector2i], flow_direction: Vector2i, phase: int, + use_stone_bridge := false, ) -> Array[Dictionary]: var ordered := _river_sorted_pair(pair, flow_direction) if ordered.size() != 2: @@ -2363,6 +2378,11 @@ func _river_edge_pair_specs( river_edge_chunk_id, river_edge_variant_chunk_id, ] + if use_stone_bridge: + edge_ids.assign([ + river_stone_bridge_chunk_id, + river_stone_bridge_chunk_id, + ]) var first_index := posmod(phase, edge_ids.size()) var first_turns := 0 if flow_direction.x != 0 else 1 var second_turns := 2 if flow_direction.x != 0 else 3 diff --git a/world/materials/terrain_surface_projection.gdshader b/world/materials/terrain_surface_projection.gdshader index b879c63..a669a14 100644 --- a/world/materials/terrain_surface_projection.gdshader +++ b/world/materials/terrain_surface_projection.gdshader @@ -1,7 +1,7 @@ shader_type spatial; render_mode cull_back; -uniform sampler2D albedo_texture : source_color, repeat_enable, filter_nearest; +uniform sampler2D albedo_texture : source_color, repeat_enable, filter_nearest_mipmap; uniform vec4 albedo_tint : source_color = vec4(1.0); uniform float tile_world_size : hint_range(0.1, 20.0, 0.05) = 2.5; uniform float blend_sharpness : hint_range(1.0, 16.0, 0.25) = 6.0;