Add layered inland cliff generation
|
|
@ -82,8 +82,14 @@ func _validate_generated_region(
|
|||
generator: TerrainChunkGenerator,
|
||||
) -> void:
|
||||
var placements := generator.placement_keys()
|
||||
assert(placements.size() == 49)
|
||||
assert(placements[24].begins_with("chunk_spawn@"))
|
||||
var expected_chunk_count := generator.grid_size.x * generator.grid_size.y
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
)
|
||||
var center_index := center.y * generator.grid_size.x + center.x
|
||||
assert(placements.size() == expected_chunk_count)
|
||||
assert(placements[center_index].begins_with("chunk_spawn@"))
|
||||
assert(_placement_count(placements, "chunk_spawn") == 1)
|
||||
assert(_placement_count(placements, "chunk_0001") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0002") >= 1)
|
||||
|
|
@ -92,8 +98,24 @@ func _validate_generated_region(
|
|||
assert(_placement_count(placements, "chunk_0005") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0006") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0007") >= 1)
|
||||
assert(generator.get_generated_chunks_root().get_child_count() == 49)
|
||||
assert(generator.get_primary_terrain_meshes().size() == 49)
|
||||
assert(_placement_count(placements, "chunk_0008") >= 1)
|
||||
assert(_placement_count(placements, "chunk_0009") == 1)
|
||||
assert(_placement_count(placements, "chunk_0010") == 4)
|
||||
assert(
|
||||
_placement_count(placements, "chunk_0011")
|
||||
+ _placement_count(placements, "chunk_0012")
|
||||
== 4
|
||||
)
|
||||
assert(_placement_count(placements, "chunk_0012") <= 1)
|
||||
assert(
|
||||
generator.get_generated_chunks_root().get_child_count()
|
||||
== expected_chunk_count
|
||||
)
|
||||
assert(
|
||||
generator.get_primary_terrain_meshes().size()
|
||||
== expected_chunk_count
|
||||
)
|
||||
_validate_elevated_cliff_feature(generator)
|
||||
|
||||
var shop := region.get_node("Interactables/FishingShopWorld") as Node3D
|
||||
var storage := region.get_node("Interactables/PlayerStorageBox") as Node3D
|
||||
|
|
@ -111,6 +133,7 @@ func _validate_generated_region(
|
|||
var fresh_root := region.get_node("WaterBodies/FreshWaterBodies") as Node3D
|
||||
assert(ocean != null and fresh_root != null)
|
||||
assert(ocean.water_type == WaterType.Type.SALT_WATER)
|
||||
assert(is_equal_approx(ocean.position.y, GeneratedWorldRegion.WATER_HEIGHT))
|
||||
var fresh_placement_count := 0
|
||||
for record: Dictionary in generator.placement_records():
|
||||
var tags: PackedStringArray = record.get("tags", PackedStringArray())
|
||||
|
|
@ -123,6 +146,7 @@ func _validate_generated_region(
|
|||
var fresh := child as WaterBodyAuthoring
|
||||
assert(fresh != null)
|
||||
assert(fresh.water_type == WaterType.Type.FRESH_WATER)
|
||||
assert(is_equal_approx(fresh.position.y, GeneratedWorldRegion.WATER_HEIGHT))
|
||||
assert(fresh.visible)
|
||||
assert(fresh.surface_size.x < 10.0 or fresh.surface_size.y < 10.0)
|
||||
assert(not fresh.visual_surface_enabled)
|
||||
|
|
@ -155,13 +179,13 @@ func _validate_generated_region(
|
|||
&"terrain_chunk_coordinate",
|
||||
Vector2i.ZERO,
|
||||
)
|
||||
var center := Vector2i(
|
||||
var spawn_coordinate := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
)
|
||||
var spawn_distance := (
|
||||
absi(coordinate.x - center.x)
|
||||
+ absi(coordinate.y - center.y)
|
||||
absi(coordinate.x - spawn_coordinate.x)
|
||||
+ absi(coordinate.y - spawn_coordinate.y)
|
||||
)
|
||||
assert(spawn_distance >= definition.minimum_spawn_chunk_distance)
|
||||
_validate_decoration_transform(
|
||||
|
|
@ -169,6 +193,13 @@ func _validate_generated_region(
|
|||
child as Node3D,
|
||||
definition,
|
||||
)
|
||||
if prop_id in [&"prop_tree_1", &"prop_tree_2", &"prop_tree_3"]:
|
||||
assert(
|
||||
_has_material_variant_override(
|
||||
child.get_node_or_null("Visual"),
|
||||
definition.material_variants,
|
||||
)
|
||||
)
|
||||
assert(_decoration_group_count(region, &"grass_tree") > 0)
|
||||
assert(_decoration_group_count(region, &"sand_tree") > 0)
|
||||
assert(
|
||||
|
|
@ -187,6 +218,69 @@ func _validate_generated_region(
|
|||
)
|
||||
|
||||
|
||||
func _validate_elevated_cliff_feature(
|
||||
generator: TerrainChunkGenerator,
|
||||
) -> void:
|
||||
var elevated_coordinates: Dictionary[Vector2i, StringName] = {}
|
||||
var top_coordinate := Vector2i(-1, -1)
|
||||
for record: Dictionary in generator.placement_records():
|
||||
var stable_id: StringName = record.get("stable_id", &"")
|
||||
if stable_id not in [
|
||||
&"chunk_0009",
|
||||
&"chunk_0010",
|
||||
&"chunk_0011",
|
||||
&"chunk_0012",
|
||||
]:
|
||||
continue
|
||||
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
|
||||
assert(coordinate.x > 0 and coordinate.x < generator.grid_size.x - 1)
|
||||
assert(coordinate.y > 0 and coordinate.y < generator.grid_size.y - 1)
|
||||
elevated_coordinates[coordinate] = stable_id
|
||||
if stable_id == &"chunk_0009":
|
||||
top_coordinate = coordinate
|
||||
assert(elevated_coordinates.size() == 9)
|
||||
assert(top_coordinate != Vector2i(-1, -1))
|
||||
for row_offset: int in range(-1, 2):
|
||||
for column_offset: int in range(-1, 2):
|
||||
assert(
|
||||
elevated_coordinates.has(
|
||||
top_coordinate + Vector2i(column_offset, row_offset)
|
||||
)
|
||||
)
|
||||
|
||||
var generated := generator.get_generated_chunks_root()
|
||||
assert(generated != null)
|
||||
var layered_count := 0
|
||||
for chunk_root: Node in generated.get_children():
|
||||
var stable_id := StringName(
|
||||
chunk_root.get_meta(&"terrain_chunk_id", &"")
|
||||
)
|
||||
if stable_id not in [
|
||||
&"chunk_0009",
|
||||
&"chunk_0010",
|
||||
&"chunk_0011",
|
||||
&"chunk_0012",
|
||||
]:
|
||||
continue
|
||||
layered_count += 1
|
||||
var base_layer := chunk_root.get_node_or_null("TerrainBaseLayer")
|
||||
var overlay := chunk_root.get_node_or_null("TerrainOverlay")
|
||||
assert(base_layer != null and overlay != null)
|
||||
var base_mesh := TerrainChunkAnalyzer.find_primary_mesh(
|
||||
base_layer,
|
||||
&"chunk_0000",
|
||||
)
|
||||
var overlay_mesh := TerrainChunkAnalyzer.find_primary_mesh(
|
||||
overlay,
|
||||
stable_id,
|
||||
)
|
||||
assert(base_mesh != null and base_mesh.mesh != null)
|
||||
assert(overlay_mesh != null and overlay_mesh.mesh != null)
|
||||
assert(base_mesh.has_node("TerrainBaseLayerCollision"))
|
||||
assert(overlay_mesh.has_node("TerrainCollision"))
|
||||
assert(layered_count == 9)
|
||||
|
||||
|
||||
func _validate_ocean_facing_record(
|
||||
record: Dictionary,
|
||||
grid_size: Vector2i,
|
||||
|
|
@ -224,6 +318,23 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
|
|||
assert(instance.position.is_zero_approx())
|
||||
assert(_find_mesh_instance(instance) != null)
|
||||
instance.free()
|
||||
var mushroom := catalog.definition_for_id(&"prop_mushroom")
|
||||
assert(mushroom != null and mushroom.has_collision())
|
||||
assert(mushroom.visual_offset.y < 0.0)
|
||||
for tree_id: StringName in [
|
||||
&"prop_tree_1",
|
||||
&"prop_tree_2",
|
||||
&"prop_tree_3",
|
||||
]:
|
||||
var tree := catalog.definition_for_id(tree_id)
|
||||
assert(tree != null)
|
||||
assert(tree.minimum_visual_scale < tree.maximum_visual_scale)
|
||||
assert(tree.material_variants.size() >= 3)
|
||||
assert(not tree.variant_material_slot_names.is_empty())
|
||||
var pine := catalog.definition_for_id(&"prop_pine")
|
||||
assert(pine != null)
|
||||
assert(pine.minimum_visual_scale < pine.maximum_visual_scale)
|
||||
assert(pine.material_variants.is_empty())
|
||||
|
||||
|
||||
func _validate_biome_catalog(
|
||||
|
|
@ -276,7 +387,16 @@ func _validate_decoration_transform(
|
|||
var collision := prop.get_node_or_null(
|
||||
"TrunkCollision/CollisionShape"
|
||||
) as CollisionShape3D
|
||||
assert(visual_root != null and visual_root.position.is_zero_approx())
|
||||
assert(visual_root != null)
|
||||
assert(visual_root.position.is_equal_approx(definition.visual_offset))
|
||||
var visual_scale := float(
|
||||
prop.get_meta(&"terrain_prop_visual_scale", 1.0)
|
||||
)
|
||||
assert(visual_scale >= definition.minimum_visual_scale - 0.001)
|
||||
assert(visual_scale <= definition.maximum_visual_scale + 0.001)
|
||||
assert(
|
||||
visual_root.scale.is_equal_approx(Vector3.ONE * visual_scale)
|
||||
)
|
||||
assert(visual != null and visual.mesh != null)
|
||||
if definition.has_collision():
|
||||
assert(collision != null)
|
||||
|
|
@ -319,6 +439,26 @@ func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
|
|||
return null
|
||||
|
||||
|
||||
func _has_material_variant_override(
|
||||
root_node: Node,
|
||||
variants: Array[Material],
|
||||
) -> bool:
|
||||
if root_node == null:
|
||||
return false
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var override := mesh_instance.get_surface_override_material(
|
||||
surface_index
|
||||
)
|
||||
if override != null and override in variants:
|
||||
return true
|
||||
for child: Node in root_node.get_children():
|
||||
if _has_material_variant_override(child, variants):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _validate_authored_chunk_surfaces(
|
||||
region: GeneratedWorldRegion,
|
||||
generator: TerrainChunkGenerator,
|
||||
|
|
@ -329,6 +469,7 @@ func _validate_authored_chunk_surfaces(
|
|||
var found_grass_ocean_edge := false
|
||||
var found_grass_beach_transition := false
|
||||
var found_grass_ocean_corner := false
|
||||
var found_beach_ocean_corner := false
|
||||
for chunk_root: Node in generated.get_children():
|
||||
if chunk_root.name.begins_with("chunk_0002r"):
|
||||
var beach := chunk_root.find_child(
|
||||
|
|
@ -383,11 +524,20 @@ func _validate_authored_chunk_surfaces(
|
|||
assert("grass_lite" in corner_materials)
|
||||
assert("cliff_wall" in corner_materials)
|
||||
found_grass_ocean_corner = true
|
||||
elif chunk_root.name.begins_with("chunk_0008r"):
|
||||
var beach_ocean_corner := chunk_root.find_child(
|
||||
"chunk_0008", true, false
|
||||
) as MeshInstance3D
|
||||
assert(beach_ocean_corner != null and beach_ocean_corner.mesh != null)
|
||||
var beach_corner_materials := _material_names(beach_ocean_corner)
|
||||
assert("sand" in beach_corner_materials)
|
||||
found_beach_ocean_corner = true
|
||||
assert(found_beach)
|
||||
assert(found_pond)
|
||||
assert(found_grass_ocean_edge)
|
||||
assert(found_grass_beach_transition)
|
||||
assert(found_grass_ocean_corner)
|
||||
assert(found_beach_ocean_corner)
|
||||
|
||||
|
||||
func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray:
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ func _make_generator() -> TerrainChunkGenerator:
|
|||
"chunk_0005",
|
||||
"chunk_0006",
|
||||
"chunk_0007",
|
||||
"chunk_0008",
|
||||
]
|
||||
)
|
||||
return generator
|
||||
|
|
|
|||
|
|
@ -21,12 +21,13 @@ shadow_enabled = true
|
|||
[node name="TerrainChunkGenerator" type="Node3D" parent="."]
|
||||
script = ExtResource("2_generator")
|
||||
catalog = ExtResource("3_catalog")
|
||||
grid_size = Vector2i(7, 7)
|
||||
grid_size = Vector2i(11, 9)
|
||||
generation_seed = 13001
|
||||
build_collision = true
|
||||
show_chunk_labels = false
|
||||
force_center_chunk_id = &"chunk_spawn"
|
||||
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007")
|
||||
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007", "chunk_0008")
|
||||
elevated_cliff_feature_enabled = true
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("4_player")]
|
||||
position = Vector3(0, 0.3, 0)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ const EXPECTED_IDS: Array[String] = [
|
|||
"chunk_0005",
|
||||
"chunk_0006",
|
||||
"chunk_0007",
|
||||
"chunk_0008",
|
||||
"chunk_0009",
|
||||
"chunk_0010",
|
||||
"chunk_0011",
|
||||
"chunk_0012",
|
||||
"chunk_spawn",
|
||||
]
|
||||
const REQUIRED_IDS: Array[String] = [
|
||||
|
|
@ -23,10 +28,11 @@ const REQUIRED_IDS: Array[String] = [
|
|||
"chunk_0005",
|
||||
"chunk_0006",
|
||||
"chunk_0007",
|
||||
"chunk_0008",
|
||||
]
|
||||
const TEST_SEED := 13001
|
||||
const CONNECTOR_STRESS_SEED := 287
|
||||
const EXPECTED_SOLVER_VARIANT_COUNT := 27
|
||||
const EXPECTED_SOLVER_VARIANT_COUNT := 31
|
||||
const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = {
|
||||
&"chunk_0000": 4,
|
||||
&"chunk_0001": 4,
|
||||
|
|
@ -36,6 +42,11 @@ const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = {
|
|||
&"chunk_0005": 4,
|
||||
&"chunk_0006": 4,
|
||||
&"chunk_0007": 4,
|
||||
&"chunk_0008": 4,
|
||||
&"chunk_0009": 4,
|
||||
&"chunk_0010": 4,
|
||||
&"chunk_0011": 4,
|
||||
&"chunk_0012": 4,
|
||||
&"chunk_spawn": 1,
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +86,11 @@ func _validate_catalog() -> void:
|
|||
var grass_ocean_edge := CATALOG.definition_for_id(&"chunk_0005")
|
||||
var grass_beach_transition := CATALOG.definition_for_id(&"chunk_0006")
|
||||
var grass_ocean_corner := CATALOG.definition_for_id(&"chunk_0007")
|
||||
var beach_ocean_corner := CATALOG.definition_for_id(&"chunk_0008")
|
||||
var cliff_top := CATALOG.definition_for_id(&"chunk_0009")
|
||||
var cliff_corner := CATALOG.definition_for_id(&"chunk_0010")
|
||||
var cliff_edge := CATALOG.definition_for_id(&"chunk_0011")
|
||||
var cliff_ramp := CATALOG.definition_for_id(&"chunk_0012")
|
||||
var spawn := CATALOG.definition_for_id(&"chunk_spawn")
|
||||
_check(
|
||||
(
|
||||
|
|
@ -84,6 +100,11 @@ func _validate_catalog() -> void:
|
|||
and grass_ocean_edge != null
|
||||
and grass_beach_transition != null
|
||||
and grass_ocean_corner != null
|
||||
and beach_ocean_corner != null
|
||||
and cliff_top != null
|
||||
and cliff_corner != null
|
||||
and cliff_edge != null
|
||||
and cliff_ramp != null
|
||||
and spawn != null
|
||||
),
|
||||
"The authored biome-rule definitions must be available.",
|
||||
|
|
@ -95,6 +116,11 @@ func _validate_catalog() -> void:
|
|||
and grass_ocean_edge != null
|
||||
and grass_beach_transition != null
|
||||
and grass_ocean_corner != null
|
||||
and beach_ocean_corner != null
|
||||
and cliff_top != null
|
||||
and cliff_corner != null
|
||||
and cliff_edge != null
|
||||
and cliff_ramp != null
|
||||
and spawn != null
|
||||
):
|
||||
_check(
|
||||
|
|
@ -193,6 +219,45 @@ func _validate_catalog() -> void:
|
|||
),
|
||||
"Both corner land seams must accept straight grass ocean edges.",
|
||||
)
|
||||
_check(
|
||||
beach_ocean_corner.ocean_facing_edges
|
||||
== (
|
||||
(1 << int(TerrainChunkTopology.Edge.EAST))
|
||||
| (1 << int(TerrainChunkTopology.Edge.SOUTH))
|
||||
),
|
||||
"The authored beach corner must expose east and south to ocean.",
|
||||
)
|
||||
_check(
|
||||
beach_ocean_corner.minimum_required_neighbors == 2
|
||||
and (
|
||||
"beach_ocean_edge"
|
||||
in beach_ocean_corner.required_neighbor_tags
|
||||
),
|
||||
"The beach corner must join two straight beach ocean edges.",
|
||||
)
|
||||
for cliff_definition: TerrainChunkDefinition in [
|
||||
cliff_top,
|
||||
cliff_corner,
|
||||
cliff_edge,
|
||||
cliff_ramp,
|
||||
]:
|
||||
_check(
|
||||
cliff_definition.overlay_only
|
||||
and cliff_definition.must_be_interior
|
||||
and cliff_definition.base_layer_scene != null
|
||||
and cliff_definition.base_layer_mesh_name == &"chunk_0000",
|
||||
"Every second-tier piece must overlay level-one grass inland.",
|
||||
)
|
||||
_check(
|
||||
cliff_top.minimum_required_neighbors == 4
|
||||
and "elevation_2" in cliff_top.required_neighbor_tags,
|
||||
"The second-tier top must be enclosed on all four sides.",
|
||||
)
|
||||
_check(
|
||||
"ramp" in cliff_ramp.tags
|
||||
and cliff_ramp.maximum_placements > 0,
|
||||
"The second-tier ramp must remain available without being required.",
|
||||
)
|
||||
|
||||
|
||||
func _validate_profiles() -> void:
|
||||
|
|
@ -443,11 +508,13 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
var grass_ocean_edge := CATALOG.definition_for_id(&"chunk_0005")
|
||||
var grass_beach_transition := CATALOG.definition_for_id(&"chunk_0006")
|
||||
var grass_ocean_corner := CATALOG.definition_for_id(&"chunk_0007")
|
||||
var beach_ocean_corner := CATALOG.definition_for_id(&"chunk_0008")
|
||||
_check(
|
||||
(
|
||||
grass_ocean_edge != null
|
||||
and grass_beach_transition != null
|
||||
and grass_ocean_corner != null
|
||||
and beach_ocean_corner != null
|
||||
),
|
||||
"The authored coastline additions must be available.",
|
||||
)
|
||||
|
|
@ -455,6 +522,7 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
grass_ocean_edge == null
|
||||
or grass_beach_transition == null
|
||||
or grass_ocean_corner == null
|
||||
or beach_ocean_corner == null
|
||||
):
|
||||
return
|
||||
var beach_zero := _find_variant(generator, &"chunk_0002", 0)
|
||||
|
|
@ -462,12 +530,14 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
var grass_edge_south := _find_variant(generator, &"chunk_0005", 3)
|
||||
var transition_zero := _find_variant(generator, &"chunk_0006", 0)
|
||||
var corner_zero := _find_variant(generator, &"chunk_0007", 0)
|
||||
var beach_corner_zero := _find_variant(generator, &"chunk_0008", 0)
|
||||
_check(
|
||||
beach_zero != null
|
||||
and grass_edge_zero != null
|
||||
and grass_edge_south != null
|
||||
and transition_zero != null
|
||||
and corner_zero != null,
|
||||
and corner_zero != null
|
||||
and beach_corner_zero != null,
|
||||
"The unrotated coastline variants must be available.",
|
||||
)
|
||||
if (
|
||||
|
|
@ -476,6 +546,7 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
and grass_edge_south != null
|
||||
and transition_zero != null
|
||||
and corner_zero != null
|
||||
and beach_corner_zero != null
|
||||
):
|
||||
_check(
|
||||
grass_edge_zero.profile(
|
||||
|
|
@ -522,6 +593,24 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
),
|
||||
"The corner's west side must join a straight grass ocean edge.",
|
||||
)
|
||||
_check(
|
||||
beach_corner_zero.profile(TerrainChunkTopology.Edge.NORTH).matches(
|
||||
beach_zero.profile(TerrainChunkTopology.Edge.SOUTH),
|
||||
generator.edge_match_tolerance,
|
||||
),
|
||||
"The beach corner's north side must join a straight beach.",
|
||||
)
|
||||
var beach_south := _find_variant(generator, &"chunk_0002", 3)
|
||||
_check(
|
||||
beach_south != null
|
||||
and beach_corner_zero.profile(
|
||||
TerrainChunkTopology.Edge.WEST
|
||||
).matches(
|
||||
beach_south.profile(TerrainChunkTopology.Edge.EAST),
|
||||
generator.edge_match_tolerance,
|
||||
),
|
||||
"The beach corner's west side must join a rotated straight beach.",
|
||||
)
|
||||
var flat_grass := _find_variant(generator, &"chunk_0000", 0)
|
||||
var flat_sand := _find_variant(generator, &"chunk_0001", 0)
|
||||
_check(
|
||||
|
|
@ -560,7 +649,12 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
),
|
||||
"No transition rotation may accept sand behind it.",
|
||||
)
|
||||
for stable_id: StringName in [&"chunk_0005", &"chunk_0006", &"chunk_0007"]:
|
||||
for stable_id: StringName in [
|
||||
&"chunk_0005",
|
||||
&"chunk_0006",
|
||||
&"chunk_0007",
|
||||
&"chunk_0008",
|
||||
]:
|
||||
for quarter_turns: int in 4:
|
||||
var coast_variant := _find_variant(
|
||||
generator,
|
||||
|
|
@ -580,11 +674,15 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
),
|
||||
"%s must never place its ocean edge inside the map." % stable_id,
|
||||
)
|
||||
if stable_id != &"chunk_0007":
|
||||
if stable_id != &"chunk_0007" and stable_id != &"chunk_0008":
|
||||
continue
|
||||
var boundary_coordinate := Vector2i(3, 3)
|
||||
var ocean_edges := coast_variant.rotated_edge_mask(
|
||||
(
|
||||
grass_ocean_corner.ocean_facing_edges
|
||||
if stable_id == &"chunk_0007"
|
||||
else beach_ocean_corner.ocean_facing_edges
|
||||
)
|
||||
)
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
if (ocean_edges & (1 << edge_value)) == 0:
|
||||
|
|
@ -603,15 +701,15 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
|
|||
coast_variant,
|
||||
boundary_coordinate,
|
||||
),
|
||||
"Each grass corner rotation must fit its matching map corner.",
|
||||
"Each coast corner rotation must fit its matching map corner.",
|
||||
)
|
||||
|
||||
|
||||
func _validate_generation_summary(generator: TerrainChunkGenerator) -> void:
|
||||
var summary := generator._build_summary()
|
||||
_check(
|
||||
int(summary.get("variant_count", 0)) == 33,
|
||||
"The current catalog must expose all 33 authored rotations.",
|
||||
int(summary.get("variant_count", 0)) == 53,
|
||||
"The current catalog must expose all 53 authored rotations.",
|
||||
)
|
||||
_check(
|
||||
int(summary.get("solver_variant_count", 0))
|
||||
|
|
@ -811,6 +909,27 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
|
|||
straight_edge_neighbors == 2,
|
||||
"Every grass corner must join two straight grass ocean edges.",
|
||||
)
|
||||
if placement.definition.stable_id == &"chunk_0008":
|
||||
var straight_beach_neighbors := 0
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
var neighbor_coordinate := (
|
||||
coordinate
|
||||
+ TerrainChunkTopology.grid_offset(
|
||||
edge_value as TerrainChunkTopology.Edge
|
||||
)
|
||||
)
|
||||
if not generator._coordinate_is_inside_grid(neighbor_coordinate):
|
||||
continue
|
||||
var neighbor := generator._placements[
|
||||
neighbor_coordinate.y * generator.grid_size.x
|
||||
+ neighbor_coordinate.x
|
||||
]
|
||||
if neighbor.definition.stable_id == &"chunk_0002":
|
||||
straight_beach_neighbors += 1
|
||||
_check(
|
||||
straight_beach_neighbors == 2,
|
||||
"Every beach corner must join two straight beach ocean edges.",
|
||||
)
|
||||
if placement.definition.stable_id != &"chunk_0001":
|
||||
continue
|
||||
var touches_coast := false
|
||||
|
|
|
|||
|
|
@ -282,10 +282,16 @@ def _discover_chunks(tolerance: float) -> list[ChunkSource]:
|
|||
# Coastline meshes may intentionally stop before the positive-X edge,
|
||||
# leaving the surrounding ocean visible. Positive X is the canonical
|
||||
# authored ocean direction; Godot supplies the quarter-turn variants.
|
||||
allow_open_east_edge = "ocean_edge" in label
|
||||
# Elevated inland cliff overlays intentionally stop at their downhill
|
||||
# edge. The runtime places them over the existing flat grass chunk, so
|
||||
# these omissions are not coastline openings or holes in the final cell.
|
||||
layered_inland_cliff = "cliff_2" in label
|
||||
allow_open_east_edge = "ocean_edge" in label or layered_inland_cliff
|
||||
# Corner pieces additionally leave canonical negative Y open. Their
|
||||
# two marked ocean edges rotate together at runtime.
|
||||
allow_open_south_edge = "ocean_edge_corner" in label
|
||||
allow_open_south_edge = (
|
||||
"ocean_edge_corner" in label or layered_inland_cliff
|
||||
)
|
||||
bound_problems = _validate_bounds(
|
||||
bounds,
|
||||
tolerance,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@
|
|||
[sub_resource type="Resource" id="Rule_grass_tree"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_tree"
|
||||
placement_chance = 9000
|
||||
adjacency_bonus = 1000
|
||||
maximum_density = 10000
|
||||
placement_chance = 8200
|
||||
adjacency_bonus = 1200
|
||||
maximum_density = 8500
|
||||
minimum_placements = 2
|
||||
placement_attempts_per_chunk = 3
|
||||
allowed_prop_ids = PackedStringArray("prop_tree_1", "prop_tree_2", "prop_tree_3")
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_detail"]
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@
|
|||
[sub_resource type="Resource" id="Rule_grass_tree"]
|
||||
script = ExtResource("2_rule")
|
||||
procedural_group = &"grass_tree"
|
||||
placement_chance = 9000
|
||||
adjacency_bonus = 1000
|
||||
maximum_density = 10000
|
||||
placement_chance = 8200
|
||||
adjacency_bonus = 1200
|
||||
maximum_density = 8500
|
||||
minimum_placements = 2
|
||||
placement_attempts_per_chunk = 3
|
||||
allowed_prop_ids = PackedStringArray("prop_pine")
|
||||
|
||||
[sub_resource type="Resource" id="Rule_grass_detail"]
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
|
|||
BIN
world/generation/chunks/assets/chunk_0008.glb
Normal file
45
world/generation/chunks/assets/chunk_0008.glb.import
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bm1teitx5ijxl"
|
||||
path="res://.godot/imported/chunk_0008.glb-0b0a6712e5384c3f72c65e6ef1fc12c4.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0008.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0008.glb-0b0a6712e5384c3f72c65e6ef1fc12c4.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
gltf/texture_map_mode=1
|
||||
BIN
world/generation/chunks/assets/chunk_0008_sand.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
45
world/generation/chunks/assets/chunk_0008_sand.png.import
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dj24j2d1u8712"
|
||||
path.s3tc="res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "adfa66b8fe9da7f3450d3ddfd432b86a"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0008_sand.png"
|
||||
dest_files=["res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.s3tc.ctex", "res://.godot/imported/chunk_0008_sand.png-55d7cf4f540d9cfb7d4ba59e9f9f38da.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
|
||||
BIN
world/generation/chunks/assets/chunk_0009.glb
Normal file
45
world/generation/chunks/assets/chunk_0009.glb.import
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://hasempwso5gi"
|
||||
path="res://.godot/imported/chunk_0009.glb-397f642bee3da14146c56fddf8e21369.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0009.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0009.glb-397f642bee3da14146c56fddf8e21369.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
gltf/texture_map_mode=1
|
||||
BIN
world/generation/chunks/assets/chunk_0009_grass_lite.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://djjyaoih5vcnd"
|
||||
path="res://.godot/imported/chunk_0009_grass_lite.png-5d49eb465b8a8f8739796ce13c646f7d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0009_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0009_grass_lite.png-5d49eb465b8a8f8739796ce13c646f7d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0010.glb
Normal file
45
world/generation/chunks/assets/chunk_0010.glb.import
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://b5tf5e1g5auu7"
|
||||
path="res://.godot/imported/chunk_0010.glb-6ffffa0064f5930acc433afe5e4334e4.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0010.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0010.glb-6ffffa0064f5930acc433afe5e4334e4.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
gltf/texture_map_mode=1
|
||||
BIN
world/generation/chunks/assets/chunk_0010_cliff_wall.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cwwraho3pj5aa"
|
||||
path="res://.godot/imported/chunk_0010_cliff_wall.png-78830d69b22e3c32cc295e574d596c3a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "fc97184f537cc23f83a731327e10c836"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0010_cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0010_cliff_wall.png-78830d69b22e3c32cc295e574d596c3a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0010_grass_lite.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://q7k2r4mb3bsr"
|
||||
path="res://.godot/imported/chunk_0010_grass_lite.png-694b30e6e36cc16c1cf98b2f3823a0c2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0010_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0010_grass_lite.png-694b30e6e36cc16c1cf98b2f3823a0c2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0011.glb
Normal file
45
world/generation/chunks/assets/chunk_0011.glb.import
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cym6ilrdikjcj"
|
||||
path="res://.godot/imported/chunk_0011.glb-cda3ab701cf5e469b53437c043314d05.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0011.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0011.glb-cda3ab701cf5e469b53437c043314d05.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
gltf/texture_map_mode=1
|
||||
BIN
world/generation/chunks/assets/chunk_0011_cliff_wall.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dggc1jbgenl11"
|
||||
path="res://.godot/imported/chunk_0011_cliff_wall.png-9a4d64a03d75f23c2ef3614386484a4e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "fc97184f537cc23f83a731327e10c836"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0011_cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0011_cliff_wall.png-9a4d64a03d75f23c2ef3614386484a4e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0011_grass_lite.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bhuhcaowvcyrk"
|
||||
path="res://.godot/imported/chunk_0011_grass_lite.png-3a0a1a7ad2d55bd853b238730af91a7c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0011_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0011_grass_lite.png-3a0a1a7ad2d55bd853b238730af91a7c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0012.glb
Normal file
45
world/generation/chunks/assets/chunk_0012.glb.import
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://c4i32u0bdtix5"
|
||||
path="res://.godot/imported/chunk_0012.glb-1c818ae539bef19d5ca4fbe3ace96887.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0012.glb"
|
||||
dest_files=["res://.godot/imported/chunk_0012.glb-1c818ae539bef19d5ca4fbe3ace96887.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
gltf/texture_map_mode=1
|
||||
BIN
world/generation/chunks/assets/chunk_0012_cliff_wall.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cf27tcolkmfog"
|
||||
path="res://.godot/imported/chunk_0012_cliff_wall.png-3d27d4041790b1c3dbc838d6a0c94d40.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "fc97184f537cc23f83a731327e10c836"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0012_cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/chunk_0012_cliff_wall.png-3d27d4041790b1c3dbc838d6a0c94d40.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0012_dirt.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
43
world/generation/chunks/assets/chunk_0012_dirt.png.import
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c3f1w276lmg7i"
|
||||
path="res://.godot/imported/chunk_0012_dirt.png-5c3a2a10642f9d078ad1d1c6333d8264.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "07d90b3c46a4c3d0c172bed143dc9436"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0012_dirt.png"
|
||||
dest_files=["res://.godot/imported/chunk_0012_dirt.png-5c3a2a10642f9d078ad1d1c6333d8264.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
BIN
world/generation/chunks/assets/chunk_0012_grass_lite.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://lv8xn2moas2s"
|
||||
path="res://.godot/imported/chunk_0012_grass_lite.png-ccfdc53acb9a967eb5d00e669258fc3c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "4dc1ac400d85d9b3e4f43a5af0a38ed4"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://world/generation/chunks/assets/chunk_0012_grass_lite.png"
|
||||
dest_files=["res://.godot/imported/chunk_0012_grass_lite.png-ccfdc53acb9a967eb5d00e669258fc3c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
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=1
|
||||
|
|
@ -11,7 +11,7 @@ packed_scene = ExtResource("2_scene")
|
|||
primary_mesh_name = &"chunk_0002"
|
||||
selection_weight = 0.75
|
||||
maximum_placements = 20
|
||||
tags = PackedStringArray("land", "walkable", "sand", "coast", "slope")
|
||||
tags = PackedStringArray("land", "walkable", "sand", "coast", "slope", "beach_ocean_edge")
|
||||
allowed_neighbor_tags = PackedStringArray("sand", "grass")
|
||||
required_neighbor_tags = PackedStringArray("sand")
|
||||
minimum_required_neighbors = 1
|
||||
|
|
|
|||
22
world/generation/chunks/definitions/chunk_0008.tres
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0008.glb" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0008"
|
||||
label = "beach corner"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0008"
|
||||
selection_weight = 0.2
|
||||
maximum_placements = 4
|
||||
tags = PackedStringArray("land", "walkable", "sand", "coast", "slope", "corner")
|
||||
allowed_neighbor_tags = PackedStringArray("sand", "coast")
|
||||
north_allowed_neighbor_tags = PackedStringArray("beach_ocean_edge")
|
||||
west_allowed_neighbor_tags = PackedStringArray("beach_ocean_edge")
|
||||
required_neighbor_tags = PackedStringArray("beach_ocean_edge")
|
||||
minimum_required_neighbors = 2
|
||||
preferred_neighbor_tags = PackedStringArray("coast")
|
||||
prefers_map_boundary = true
|
||||
ocean_facing_edges = 6
|
||||
23
world/generation/chunks/definitions/chunk_0009.tres
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0009.glb" id="2_scene"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0009"
|
||||
label = "second-tier grass top"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0009"
|
||||
base_layer_scene = ExtResource("3_base_grass")
|
||||
base_layer_mesh_name = &"chunk_0000"
|
||||
overlay_only = true
|
||||
selection_weight = 0.22
|
||||
maximum_placements = 8
|
||||
tags = PackedStringArray("land", "walkable", "grass", "elevation_2", "elevation_2_top")
|
||||
allowed_neighbor_tags = PackedStringArray("elevation_2")
|
||||
required_neighbor_tags = PackedStringArray("elevation_2")
|
||||
minimum_required_neighbors = 4
|
||||
preferred_neighbor_tags = PackedStringArray("elevation_2_top")
|
||||
must_be_interior = true
|
||||
27
world/generation/chunks/definitions/chunk_0010.tres
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0010.glb" id="2_scene"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0010"
|
||||
label = "second-tier grass corner"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0010"
|
||||
base_layer_scene = ExtResource("3_base_grass")
|
||||
base_layer_mesh_name = &"chunk_0000"
|
||||
overlay_only = true
|
||||
selection_weight = 0.12
|
||||
maximum_placements = 16
|
||||
tags = PackedStringArray("land", "walkable", "grass", "cliff", "corner", "elevation_2", "elevation_2_border")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "elevation_2")
|
||||
north_allowed_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
east_allowed_neighbor_tags = PackedStringArray("grass")
|
||||
south_allowed_neighbor_tags = PackedStringArray("grass")
|
||||
west_allowed_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
required_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
minimum_required_neighbors = 2
|
||||
preferred_neighbor_tags = PackedStringArray("elevation_2")
|
||||
must_be_interior = true
|
||||
27
world/generation/chunks/definitions/chunk_0011.tres
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0011.glb" id="2_scene"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0011"
|
||||
label = "second-tier grass edge"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0011"
|
||||
base_layer_scene = ExtResource("3_base_grass")
|
||||
base_layer_mesh_name = &"chunk_0000"
|
||||
overlay_only = true
|
||||
selection_weight = 0.28
|
||||
maximum_placements = 32
|
||||
tags = PackedStringArray("land", "walkable", "grass", "cliff", "edge", "elevation_2", "elevation_2_border")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "elevation_2")
|
||||
north_allowed_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
east_allowed_neighbor_tags = PackedStringArray("grass")
|
||||
south_allowed_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
west_allowed_neighbor_tags = PackedStringArray("elevation_2_top")
|
||||
required_neighbor_tags = PackedStringArray("elevation_2_top")
|
||||
minimum_required_neighbors = 1
|
||||
preferred_neighbor_tags = PackedStringArray("elevation_2")
|
||||
must_be_interior = true
|
||||
27
world/generation/chunks/definitions/chunk_0012.tres
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0012.glb" id="2_scene"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0000.glb" id="3_base_grass"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
stable_id = &"chunk_0012"
|
||||
label = "second-tier grass ramp"
|
||||
packed_scene = ExtResource("2_scene")
|
||||
primary_mesh_name = &"chunk_0012"
|
||||
base_layer_scene = ExtResource("3_base_grass")
|
||||
base_layer_mesh_name = &"chunk_0000"
|
||||
overlay_only = true
|
||||
selection_weight = 0.12
|
||||
maximum_placements = 8
|
||||
tags = PackedStringArray("land", "walkable", "grass", "cliff", "edge", "ramp", "elevation_2", "elevation_2_border")
|
||||
allowed_neighbor_tags = PackedStringArray("grass", "elevation_2")
|
||||
north_allowed_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
east_allowed_neighbor_tags = PackedStringArray("grass")
|
||||
south_allowed_neighbor_tags = PackedStringArray("elevation_2_border")
|
||||
west_allowed_neighbor_tags = PackedStringArray("elevation_2_top")
|
||||
required_neighbor_tags = PackedStringArray("elevation_2_top")
|
||||
minimum_required_neighbors = 1
|
||||
preferred_neighbor_tags = PackedStringArray("elevation_2")
|
||||
must_be_interior = true
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="TerrainChunkCatalog" load_steps=12 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainChunkCatalog" load_steps=17 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_chunk_catalog.gd" id="1_catalog"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0000.tres" id="2_grass"]
|
||||
|
|
@ -11,8 +11,13 @@
|
|||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0005.tres" id="9_grass_ocean_edge"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0006.tres" id="10_grass_beach_transition"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0007.tres" id="11_grass_ocean_corner"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0008.tres" id="12_beach_ocean_corner"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0009.tres" id="13_cliff_top"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0010.tres" id="14_cliff_corner"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0011.tres" id="15_cliff_edge"]
|
||||
[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0012.tres" id="16_cliff_ramp"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_catalog")
|
||||
chunk_size = 10.0
|
||||
definitions = Array[ExtResource("7_definition_script")]([ExtResource("2_grass"), ExtResource("3_sand"), ExtResource("4_beach"), ExtResource("5_stream"), ExtResource("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("8_spawn")])
|
||||
definitions = Array[ExtResource("7_definition_script")]([ExtResource("2_grass"), ExtResource("3_sand"), ExtResource("4_beach"), ExtResource("5_stream"), ExtResource("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("12_beach_ocean_corner"), ExtResource("13_cliff_top"), ExtResource("14_cliff_corner"), ExtResource("15_cliff_edge"), ExtResource("16_cliff_ramp"), ExtResource("8_spawn")])
|
||||
|
|
|
|||
|
|
@ -78,6 +78,11 @@ func _ready() -> void:
|
|||
func generate_world(seed: int) -> bool:
|
||||
if seed <= 0 or seed > PlayerSaveManager.MAX_WORLD_SEED:
|
||||
return false
|
||||
if (
|
||||
seed == _current_seed
|
||||
and _generator.get_generated_chunks_root() != null
|
||||
):
|
||||
return true
|
||||
_current_seed = seed
|
||||
_generator.generation_seed = seed
|
||||
return _generator.generate()
|
||||
|
|
@ -237,6 +242,11 @@ func _on_generation_completed(summary: Dictionary) -> void:
|
|||
_prop_definitions_for(rule, tags),
|
||||
coordinate,
|
||||
)
|
||||
for _placement_attempt: int in (
|
||||
rule.placement_attempts_per_chunk
|
||||
):
|
||||
if group_counts.get(group_key, 0) >= maximum:
|
||||
break
|
||||
if (
|
||||
definitions.is_empty()
|
||||
or not _prop_group_roll_succeeds(
|
||||
|
|
@ -322,7 +332,7 @@ func _configure_static_water() -> void:
|
|||
_ocean.fish_pool = OCEAN_POOL
|
||||
_ocean.location_tags = [&"coast", &"ocean", &"generated_ocean"]
|
||||
_ocean.surface_size = Vector2(10000.0, 10000.0)
|
||||
_ocean.position.y = WATER_HEIGHT - 0.025
|
||||
_ocean.position.y = WATER_HEIGHT
|
||||
_apply_water_materials()
|
||||
|
||||
|
||||
|
|
@ -451,6 +461,11 @@ func _add_prop(
|
|||
) -> bool:
|
||||
if definition == null or definition.packed_scene == null:
|
||||
return false
|
||||
var visual_scale := random.randf_range(
|
||||
definition.minimum_visual_scale,
|
||||
definition.maximum_visual_scale,
|
||||
)
|
||||
var scaled_clearance := definition.clearance_radius * visual_scale
|
||||
var placement := Vector3.ZERO
|
||||
var found_surface := false
|
||||
for attempt: int in PROP_PLACEMENT_ATTEMPTS:
|
||||
|
|
@ -470,7 +485,7 @@ func _add_prop(
|
|||
> WATER_HEIGHT + PROP_MINIMUM_GROUND_CLEARANCE
|
||||
and _has_prop_clearance(
|
||||
placement,
|
||||
definition.clearance_radius,
|
||||
scaled_clearance,
|
||||
)
|
||||
):
|
||||
placement.y = surface_height
|
||||
|
|
@ -492,22 +507,26 @@ func _add_prop(
|
|||
prop.set_meta(&"terrain_prop_group", definition.procedural_group)
|
||||
prop.set_meta(&"terrain_chunk_coordinate", chunk_coordinate)
|
||||
prop.set_meta(&"terrain_biome_id", biome_id)
|
||||
prop.set_meta(&"terrain_prop_visual_scale", visual_scale)
|
||||
prop.position = placement
|
||||
prop.rotation.y = random.randf_range(-PI, PI)
|
||||
_decorations.add_child(prop)
|
||||
visual_root.name = "Visual"
|
||||
prop.add_child(visual_root)
|
||||
visual_root.position = definition.visual_offset
|
||||
visual_root.scale = Vector3.ONE * visual_scale
|
||||
_configure_prop_visuals(visual_root)
|
||||
_add_prop_collision(prop, definition)
|
||||
_apply_prop_material_variant(visual_root, definition, random)
|
||||
_add_prop_collision(prop, definition, visual_scale)
|
||||
_placed_prop_positions.append(placement)
|
||||
_placed_prop_clearance_radii.append(definition.clearance_radius)
|
||||
_placed_prop_clearance_radii.append(scaled_clearance)
|
||||
_placed_prop_groups.append(definition.procedural_group)
|
||||
if definition.gatherable_anchor_height > 0.0:
|
||||
var anchor := Marker3D.new()
|
||||
anchor.name = "TreeAnchor_%d" % _tree_anchors.get_child_count()
|
||||
anchor.position = prop.position + Vector3(
|
||||
0.0,
|
||||
definition.gatherable_anchor_height,
|
||||
definition.gatherable_anchor_height * visual_scale,
|
||||
0.0,
|
||||
)
|
||||
_tree_anchors.add_child(anchor)
|
||||
|
|
@ -624,7 +643,11 @@ func _prop_group_maximum(
|
|||
if eligible_count <= 0:
|
||||
return 0
|
||||
var maximum := ceili(
|
||||
float(eligible_count * rule.maximum_density)
|
||||
float(
|
||||
eligible_count
|
||||
* rule.placement_attempts_per_chunk
|
||||
* rule.maximum_density
|
||||
)
|
||||
/ float(PROP_CHANCE_SCALE)
|
||||
)
|
||||
return maxi(maximum, rule.minimum_placements)
|
||||
|
|
@ -777,6 +800,51 @@ func _configure_prop_visuals(root_node: Node) -> void:
|
|||
_configure_prop_visuals(child)
|
||||
|
||||
|
||||
func _apply_prop_material_variant(
|
||||
root_node: Node,
|
||||
definition: TerrainPropDefinition,
|
||||
random: RandomNumberGenerator,
|
||||
) -> void:
|
||||
if (
|
||||
definition.material_variants.is_empty()
|
||||
or definition.variant_material_slot_names.is_empty()
|
||||
):
|
||||
return
|
||||
var variant := definition.material_variants[
|
||||
random.randi_range(0, definition.material_variants.size() - 1)
|
||||
]
|
||||
_apply_material_variant_to_meshes(
|
||||
root_node,
|
||||
definition.variant_material_slot_names,
|
||||
variant,
|
||||
)
|
||||
|
||||
|
||||
func _apply_material_variant_to_meshes(
|
||||
root_node: Node,
|
||||
target_material_names: PackedStringArray,
|
||||
variant: Material,
|
||||
) -> void:
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var active_material := mesh_instance.get_active_material(surface_index)
|
||||
if (
|
||||
active_material != null
|
||||
and active_material.resource_name in target_material_names
|
||||
):
|
||||
mesh_instance.set_surface_override_material(
|
||||
surface_index,
|
||||
variant,
|
||||
)
|
||||
for child: Node in root_node.get_children():
|
||||
_apply_material_variant_to_meshes(
|
||||
child,
|
||||
target_material_names,
|
||||
variant,
|
||||
)
|
||||
|
||||
|
||||
func _terrain_surface_triangles() -> Array[PackedVector3Array]:
|
||||
var triangles: Array[PackedVector3Array] = []
|
||||
for mesh_instance: MeshInstance3D in _generator.get_primary_terrain_meshes():
|
||||
|
|
@ -818,6 +886,7 @@ func _surface_height_at(
|
|||
func _add_prop_collision(
|
||||
prop: Node3D,
|
||||
definition: TerrainPropDefinition,
|
||||
visual_scale: float,
|
||||
) -> void:
|
||||
if not definition.has_collision():
|
||||
return
|
||||
|
|
@ -830,16 +899,16 @@ func _add_prop_collision(
|
|||
collision.name = "CollisionShape"
|
||||
if definition.has_box_collision():
|
||||
var box := BoxShape3D.new()
|
||||
box.size = definition.collision_box_size
|
||||
box.size = definition.collision_box_size * visual_scale
|
||||
collision.shape = box
|
||||
collision.position = definition.collision_offset
|
||||
collision.position = definition.collision_offset * visual_scale
|
||||
else:
|
||||
var cylinder := CylinderShape3D.new()
|
||||
cylinder.radius = definition.collision_radius
|
||||
cylinder.height = definition.collision_height
|
||||
cylinder.radius = definition.collision_radius * visual_scale
|
||||
cylinder.height = definition.collision_height * visual_scale
|
||||
collision.shape = cylinder
|
||||
collision.position = (
|
||||
definition.collision_offset
|
||||
definition.collision_offset * visual_scale
|
||||
+ Vector3.UP * cylinder.height * 0.5
|
||||
)
|
||||
body.add_child(collision)
|
||||
|
|
|
|||
|
|
@ -31,12 +31,13 @@ gatherable_anchor_root = NodePath("GatherableAnchors")
|
|||
unique_name_in_owner = true
|
||||
script = ExtResource("2_generator")
|
||||
catalog = ExtResource("3_catalog")
|
||||
grid_size = Vector2i(7, 7)
|
||||
grid_size = Vector2i(15, 13)
|
||||
generation_seed = 13001
|
||||
generate_on_ready = false
|
||||
build_collision = true
|
||||
force_center_chunk_id = &"chunk_spawn"
|
||||
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007")
|
||||
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007", "chunk_0008")
|
||||
elevated_cliff_feature_enabled = true
|
||||
required_chunk_weight_multiplier = 64.0
|
||||
maximum_backtracks = 100000
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ script = ExtResource("8_diggable")
|
|||
area_id = &"starter_beach"
|
||||
terrain_source = NodePath("../../Terrain/TerrainChunkGenerator")
|
||||
surface_materials = Array[StringName]([&"sand"])
|
||||
generation_bounds = Rect2(-35, -35, 70, 70)
|
||||
generation_bounds = Rect2(-55, -45, 110, 90)
|
||||
minimum_global_y = -0.24
|
||||
maximum_global_y = 0.5
|
||||
minimum_up_dot = 0.72
|
||||
|
|
|
|||
|
|
@ -16,3 +16,7 @@ preferred_nearby_prop_groups = PackedStringArray("grass_tree")
|
|||
preferred_nearby_radius = 12.0
|
||||
nearby_preference_weight_multiplier = 3.0
|
||||
clearance_radius = 0.8
|
||||
visual_offset = Vector3(0, -0.08, 0)
|
||||
collision_radius = 0.65
|
||||
collision_height = 1.0
|
||||
collision_offset = Vector3(0, -0.08, 0)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ required_chunk_tags = PackedStringArray("grass")
|
|||
selection_weight = 0.7
|
||||
minimum_spawn_chunk_distance = 2
|
||||
clearance_radius = 1.55
|
||||
minimum_visual_scale = 0.75
|
||||
maximum_visual_scale = 1.22
|
||||
collision_radius = 0.5
|
||||
collision_height = 4.0
|
||||
gatherable_anchor_height = 2.15
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_1.glb" id="2_scene"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_light.tres" id="3_leaf_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_mid.tres" id="4_leaf_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_dark.tres" id="5_leaf_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -12,6 +15,10 @@ procedural_group = &"grass_tree"
|
|||
required_chunk_tags = PackedStringArray("grass")
|
||||
minimum_spawn_chunk_distance = 2
|
||||
clearance_radius = 1.3
|
||||
minimum_visual_scale = 0.75
|
||||
maximum_visual_scale = 1.15
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
collision_radius = 0.4
|
||||
collision_height = 3.2
|
||||
gatherable_anchor_height = 2.15
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_2.glb" id="2_scene"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_light.tres" id="3_leaf_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_mid.tres" id="4_leaf_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_dark.tres" id="5_leaf_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -12,6 +15,10 @@ procedural_group = &"grass_tree"
|
|||
required_chunk_tags = PackedStringArray("grass")
|
||||
minimum_spawn_chunk_distance = 2
|
||||
clearance_radius = 1.5
|
||||
minimum_visual_scale = 0.8
|
||||
maximum_visual_scale = 1.18
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
collision_radius = 0.5
|
||||
collision_height = 3.8
|
||||
gatherable_anchor_height = 2.15
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=3 format=3]
|
||||
[gd_resource type="Resource" script_class="TerrainPropDefinition" load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/generation/terrain_prop_definition.gd" id="1_definition"]
|
||||
[ext_resource type="PackedScene" path="res://world/generation/props/assets/prop_tree_3.glb" id="2_scene"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_light.tres" id="3_leaf_light"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_mid.tres" id="4_leaf_mid"]
|
||||
[ext_resource type="Material" path="res://world/generation/props/materials/leaf_dark.tres" id="5_leaf_dark"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_definition")
|
||||
|
|
@ -12,6 +15,10 @@ procedural_group = &"grass_tree"
|
|||
required_chunk_tags = PackedStringArray("grass")
|
||||
minimum_spawn_chunk_distance = 2
|
||||
clearance_radius = 1.8
|
||||
minimum_visual_scale = 0.85
|
||||
maximum_visual_scale = 1.15
|
||||
variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark")
|
||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||
collision_radius = 0.55
|
||||
collision_height = 4.2
|
||||
gatherable_anchor_height = 2.15
|
||||
|
|
|
|||
9
world/generation/props/materials/leaf_dark.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_variant_dark"
|
||||
albedo_color = Color(0.1712, 0.252, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/leaf_light.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_variant_light"
|
||||
albedo_color = Color(0.3388, 0.7187, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
9
world/generation/props/materials/leaf_mid.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="StandardMaterial3D" format=3]
|
||||
|
||||
[resource]
|
||||
resource_name = "leaf_variant_mid"
|
||||
albedo_color = Color(0.3147, 0.4631, 0, 1)
|
||||
metallic = 0.0
|
||||
roughness = 1.0
|
||||
shading_mode = 1
|
||||
texture_filter = 0
|
||||
|
|
@ -6,6 +6,9 @@ extends Resource
|
|||
@export_range(0, 10000, 1) var adjacency_bonus := 0
|
||||
@export_range(0, 10000, 1) var maximum_density := 10000
|
||||
@export_range(0, 64, 1) var minimum_placements := 0
|
||||
## More than one attempt permits intentionally dense biomes without changing
|
||||
## the density of every biome that uses the same procedural prop group.
|
||||
@export_range(1, 8, 1) var placement_attempts_per_chunk := 1
|
||||
## Empty permits every compatible prop in the procedural group.
|
||||
@export var allowed_prop_ids := PackedStringArray()
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,29 @@ static func create_variants(
|
|||
chunk_size,
|
||||
edge_epsilon,
|
||||
)
|
||||
if definition.base_layer_scene != null:
|
||||
var base_layer_root := definition.base_layer_scene.instantiate()
|
||||
var base_layer_mesh := find_primary_mesh(
|
||||
base_layer_root,
|
||||
definition.base_layer_mesh_name,
|
||||
)
|
||||
if base_layer_mesh == null or base_layer_mesh.mesh == null:
|
||||
push_error(
|
||||
"Terrain chunk %s has no base-layer MeshInstance3D named %s."
|
||||
% [definition.stable_id, definition.base_layer_mesh_name]
|
||||
)
|
||||
base_layer_root.free()
|
||||
chunk_root.free()
|
||||
return variants
|
||||
boundary_points.append_array(
|
||||
_collect_boundary_points(
|
||||
base_layer_mesh.mesh,
|
||||
_transform_relative_to(base_layer_mesh, base_layer_root),
|
||||
chunk_size,
|
||||
edge_epsilon,
|
||||
)
|
||||
)
|
||||
base_layer_root.free()
|
||||
for quarter_turns: int in 4:
|
||||
if not definition.allows_quarter_turn(quarter_turns):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -30,6 +30,19 @@ func validation_errors() -> PackedStringArray:
|
|||
errors.append("%s has no PackedScene." % definition.stable_id)
|
||||
if definition.primary_mesh_name == &"":
|
||||
errors.append("%s has no primary mesh name." % definition.stable_id)
|
||||
if (
|
||||
(definition.base_layer_scene == null)
|
||||
!= (definition.base_layer_mesh_name == &"")
|
||||
):
|
||||
errors.append(
|
||||
"%s must define both base-layer scene and mesh name."
|
||||
% definition.stable_id
|
||||
)
|
||||
if definition.overlay_only and definition.base_layer_scene == null:
|
||||
errors.append(
|
||||
"%s is overlay-only but has no base-layer scene."
|
||||
% definition.stable_id
|
||||
)
|
||||
if definition.allowed_rotation_mask == 0:
|
||||
errors.append("%s allows no rotations." % definition.stable_id)
|
||||
var has_coast := "coast" in definition.tags
|
||||
|
|
@ -44,6 +57,11 @@ func validation_errors() -> PackedStringArray:
|
|||
"%s has an ocean-facing edge without the coast tag."
|
||||
% definition.stable_id
|
||||
)
|
||||
if definition.must_be_interior and definition.prefers_map_boundary:
|
||||
errors.append(
|
||||
"%s cannot require the interior and prefer the boundary."
|
||||
% definition.stable_id
|
||||
)
|
||||
var has_fresh_water := "fresh_water" in definition.tags
|
||||
var has_water_footprint := (
|
||||
definition.water_surface_size.x > 0.0
|
||||
|
|
|
|||
|
|
@ -5,6 +5,17 @@ extends Resource
|
|||
@export var label := ""
|
||||
@export var packed_scene: PackedScene
|
||||
@export var primary_mesh_name: StringName
|
||||
## Optional existing level-one chunk placed beneath an authored elevated
|
||||
## overlay. This lets cliff edges leave their downhill side open while the
|
||||
## ordinary base terrain remains present in the same generated cell.
|
||||
@export_group("Layered Terrain")
|
||||
@export var base_layer_scene: PackedScene
|
||||
@export var base_layer_mesh_name: StringName
|
||||
## Overlay-only chunks replace a reserved base-terrain placement after the
|
||||
## ordinary terrain solve. They remain in placement manifests and runtime
|
||||
## output, but do not inflate every cell's global solver domain.
|
||||
@export var overlay_only := false
|
||||
@export_group("")
|
||||
@export_flags("0 degrees", "90 degrees", "180 degrees", "270 degrees")
|
||||
var allowed_rotation_mask := 15
|
||||
@export_range(0.01, 100.0, 0.01) var selection_weight := 1.0
|
||||
|
|
@ -36,6 +47,9 @@ var allowed_rotation_mask := 15
|
|||
## weight. This shapes regions without turning a visual preference into a hard
|
||||
## generation constraint.
|
||||
@export var preferred_neighbor_tags := PackedStringArray()
|
||||
## Interior-only pieces may not occupy any outer grid cell. Coastal elevated
|
||||
## pieces use separate authored definitions rather than weakening this rule.
|
||||
@export var must_be_interior := false
|
||||
## Coastal transition pieces should normally migrate toward the generated
|
||||
## region's perimeter while remaining legal in the interior.
|
||||
@export var prefers_map_boundary := false
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ signal generation_completed(summary: Dictionary)
|
|||
|
||||
const CONSTRAINT_PROFILE_QUANTIZATION := 0.001
|
||||
const CANDIDATE_WEIGHT_SCALE := 10000.0
|
||||
const LARGE_GRID_LIGHTWEIGHT_THRESHOLD := 128
|
||||
const LARGE_GRID_PROPAGATION_INTERVAL := 8
|
||||
|
||||
@export var catalog: TerrainChunkCatalog
|
||||
@export var grid_size := Vector2i(7, 7)
|
||||
|
|
@ -14,6 +16,15 @@ const CANDIDATE_WEIGHT_SCALE := 10000.0
|
|||
@export var show_chunk_labels := false
|
||||
@export var force_center_chunk_id: StringName = &"chunk_0000"
|
||||
@export var required_chunk_ids := PackedStringArray()
|
||||
@export_group("Elevated Inland Feature")
|
||||
@export var elevated_cliff_feature_enabled := false
|
||||
@export var elevated_cliff_base_chunk_id: StringName = &"chunk_0000"
|
||||
@export var elevated_cliff_top_chunk_id: StringName = &"chunk_0009"
|
||||
@export var elevated_cliff_corner_chunk_id: StringName = &"chunk_0010"
|
||||
@export var elevated_cliff_edge_chunk_id: StringName = &"chunk_0011"
|
||||
@export var elevated_cliff_ramp_chunk_id: StringName = &"chunk_0012"
|
||||
@export_range(0.0, 1.0, 0.05) var elevated_cliff_ramp_chance := 0.75
|
||||
@export_group("")
|
||||
@export_range(1.0, 100.0, 1.0) var required_chunk_weight_multiplier := 64.0
|
||||
@export_range(1.0, 4.0, 0.05) var preferred_neighbor_weight_multiplier := 1.6
|
||||
@export_range(0.01, 1.0, 0.01) var long_repeat_weight_multiplier := 0.3
|
||||
|
|
@ -33,6 +44,8 @@ var _placements: Array[TerrainChunkVariant] = []
|
|||
var _random := RandomNumberGenerator.new()
|
||||
var _generated_chunks: Node3D
|
||||
var _backtrack_count := 0
|
||||
var _elevated_feature_center := Vector2i(-1, -1)
|
||||
var _elevated_feature_reserved_grass_indices: Dictionary[int, bool] = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -49,6 +62,9 @@ func generate() -> bool:
|
|||
_backtrack_count = 0
|
||||
_placements.clear()
|
||||
_placements.resize(grid_size.x * grid_size.y)
|
||||
if not _prepare_elevated_feature_region():
|
||||
_placements.assign(previous_placements)
|
||||
return false
|
||||
if not _solve_cell(0):
|
||||
_placements.assign(previous_placements)
|
||||
push_error(
|
||||
|
|
@ -57,6 +73,9 @@ func generate() -> bool:
|
|||
)
|
||||
return false
|
||||
_resolve_equivalent_rotations()
|
||||
if not _apply_elevated_feature():
|
||||
_placements.assign(previous_placements)
|
||||
return false
|
||||
var solution_root := _build_solution_root()
|
||||
if solution_root == null:
|
||||
_placements.assign(previous_placements)
|
||||
|
|
@ -195,6 +214,8 @@ func _prepare_catalog() -> bool:
|
|||
return false
|
||||
for variant: TerrainChunkVariant in analyzed:
|
||||
_variants.append(variant)
|
||||
if definition.overlay_only:
|
||||
continue
|
||||
var constraint_key := _constraint_key(variant)
|
||||
var rotations: PackedInt32Array = _equivalent_rotations.get(
|
||||
constraint_key,
|
||||
|
|
@ -207,6 +228,9 @@ func _prepare_catalog() -> bool:
|
|||
if _variants.is_empty():
|
||||
push_error("Terrain chunk catalog produced no usable variants.")
|
||||
return false
|
||||
if _solver_variants.is_empty():
|
||||
push_error("Terrain chunk catalog produced no base-terrain solver variants.")
|
||||
return false
|
||||
for index: int in _solver_variants.size():
|
||||
_solver_variant_indices[_solver_variants[index]] = index
|
||||
_build_edge_compatibility_cache()
|
||||
|
|
@ -303,7 +327,7 @@ func _solve_cell(placed_count: int) -> bool:
|
|||
return false
|
||||
if not _requirements_can_still_be_satisfied():
|
||||
return false
|
||||
var next_cell := _select_next_cell()
|
||||
var next_cell := _select_next_cell(placed_count)
|
||||
var index := int(next_cell.get("index", -1))
|
||||
if index < 0:
|
||||
return false
|
||||
|
|
@ -325,7 +349,12 @@ func _solve_cell(placed_count: int) -> bool:
|
|||
return false
|
||||
|
||||
|
||||
func _select_next_cell() -> Dictionary:
|
||||
func _select_next_cell(placed_count: int) -> Dictionary:
|
||||
if (
|
||||
_placements.size() >= LARGE_GRID_LIGHTWEIGHT_THRESHOLD
|
||||
and placed_count % LARGE_GRID_PROPAGATION_INTERVAL != 0
|
||||
):
|
||||
return _select_next_large_grid_cell()
|
||||
# Rebuild and propagate the small domain table on every branch. This catches
|
||||
# unsupported connector chains before they turn into a deep recursive dead
|
||||
# end, while keeping placement state simple and deterministic.
|
||||
|
|
@ -356,6 +385,54 @@ func _select_next_cell() -> Dictionary:
|
|||
return {"index": best_index, "candidates": best_candidates}
|
||||
|
||||
|
||||
func _select_next_large_grid_cell() -> Dictionary:
|
||||
# Full all-cell arc propagation scales cubically as the map grows. Large
|
||||
# worlds instead use the same compatibility checks with a frontier-aware MRV
|
||||
# pass. Exact placement validation and backtracking remain unchanged.
|
||||
var best_index := -1
|
||||
var best_candidates: Array[TerrainChunkVariant] = []
|
||||
var best_placed_neighbors := -1
|
||||
for index: int in _placements.size():
|
||||
if _placements[index] != null:
|
||||
continue
|
||||
var candidates := _compatible_candidates(index)
|
||||
if candidates.is_empty():
|
||||
return {"index": index, "candidates": candidates}
|
||||
var placed_neighbors := _placed_neighbor_count(index)
|
||||
if (
|
||||
best_index < 0
|
||||
or candidates.size() < best_candidates.size()
|
||||
or (
|
||||
candidates.size() == best_candidates.size()
|
||||
and placed_neighbors > best_placed_neighbors
|
||||
)
|
||||
):
|
||||
best_index = index
|
||||
best_candidates = candidates
|
||||
best_placed_neighbors = placed_neighbors
|
||||
return {"index": best_index, "candidates": best_candidates}
|
||||
|
||||
|
||||
func _placed_neighbor_count(index: int) -> int:
|
||||
var coordinate := Vector2i(index % grid_size.x, index / grid_size.x)
|
||||
var count := 0
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
var neighbor_coordinate := (
|
||||
coordinate
|
||||
+ TerrainChunkTopology.grid_offset(
|
||||
edge_value as TerrainChunkTopology.Edge
|
||||
)
|
||||
)
|
||||
if not _coordinate_is_inside_grid(neighbor_coordinate):
|
||||
continue
|
||||
var neighbor_index := (
|
||||
neighbor_coordinate.y * grid_size.x + neighbor_coordinate.x
|
||||
)
|
||||
if _placements[neighbor_index] != null:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _propagate_domains(domains: Dictionary) -> bool:
|
||||
var changed := true
|
||||
while changed:
|
||||
|
|
@ -568,6 +645,11 @@ func _candidate_can_occupy_cell(
|
|||
if not _definition_has_capacity(candidate.definition):
|
||||
return false
|
||||
var coordinate := Vector2i(index % grid_size.x, index / grid_size.x)
|
||||
if (
|
||||
_elevated_feature_reserved_grass_indices.has(index)
|
||||
and candidate.definition.stable_id != elevated_cliff_base_chunk_id
|
||||
):
|
||||
return false
|
||||
var center := Vector2i(grid_size.x / 2, grid_size.y / 2)
|
||||
var center_index := center.y * grid_size.x + center.x
|
||||
if (
|
||||
|
|
@ -599,10 +681,123 @@ func _candidate_can_occupy_cell(
|
|||
)
|
||||
|
||||
|
||||
func _prepare_elevated_feature_region() -> bool:
|
||||
_elevated_feature_center = Vector2i(-1, -1)
|
||||
_elevated_feature_reserved_grass_indices.clear()
|
||||
if not elevated_cliff_feature_enabled:
|
||||
return true
|
||||
for stable_id: StringName in [
|
||||
elevated_cliff_base_chunk_id,
|
||||
elevated_cliff_top_chunk_id,
|
||||
elevated_cliff_corner_chunk_id,
|
||||
elevated_cliff_edge_chunk_id,
|
||||
elevated_cliff_ramp_chunk_id,
|
||||
]:
|
||||
if catalog.definition_for_id(stable_id) == null:
|
||||
push_error("Elevated cliff feature references missing chunk %s." % stable_id)
|
||||
return false
|
||||
if grid_size.x < 11 or grid_size.y < 9:
|
||||
push_error(
|
||||
"Elevated cliff feature requires at least an 11x9 terrain grid."
|
||||
)
|
||||
return false
|
||||
var forced_center := Vector2i(grid_size.x / 2, grid_size.y / 2)
|
||||
var candidates: Array[Vector2i] = []
|
||||
for row: int in range(2, grid_size.y - 2):
|
||||
for column: int in range(2, grid_size.x - 2):
|
||||
var candidate := Vector2i(column, row)
|
||||
if (
|
||||
absi(candidate.x - forced_center.x) <= 2
|
||||
and absi(candidate.y - forced_center.y) <= 2
|
||||
):
|
||||
continue
|
||||
candidates.append(candidate)
|
||||
if candidates.is_empty():
|
||||
push_error("Terrain grid has no inland 3x3 cliff feature location.")
|
||||
return false
|
||||
var feature_random := RandomNumberGenerator.new()
|
||||
feature_random.seed = generation_seed ^ 0x2E1E7A7ED
|
||||
_elevated_feature_center = candidates[
|
||||
feature_random.randi_range(0, candidates.size() - 1)
|
||||
]
|
||||
# Solve the authored 3x3 cliff assembly into a one-cell ring of ordinary
|
||||
# level-one flat grass. Every raised piece still occupies one of the inner
|
||||
# nine grass cells; the outer ring prevents a stream, pond, or coast profile
|
||||
# from being exposed directly against its downhill edge.
|
||||
for row_offset: int in range(-2, 3):
|
||||
for column_offset: int in range(-2, 3):
|
||||
var coordinate := (
|
||||
_elevated_feature_center
|
||||
+ Vector2i(column_offset, row_offset)
|
||||
)
|
||||
_elevated_feature_reserved_grass_indices[
|
||||
coordinate.y * grid_size.x + coordinate.x
|
||||
] = true
|
||||
return true
|
||||
|
||||
|
||||
func _apply_elevated_feature() -> bool:
|
||||
if not elevated_cliff_feature_enabled:
|
||||
return true
|
||||
if _elevated_feature_center.x < 0 or _elevated_feature_center.y < 0:
|
||||
push_error("Elevated cliff feature has no reserved base region.")
|
||||
return false
|
||||
var edge_specs: Array[Dictionary] = [
|
||||
{"offset": Vector2i(0, -1), "turns": 1},
|
||||
{"offset": Vector2i(1, 0), "turns": 0},
|
||||
{"offset": Vector2i(0, 1), "turns": 3},
|
||||
{"offset": Vector2i(-1, 0), "turns": 2},
|
||||
]
|
||||
var feature_random := RandomNumberGenerator.new()
|
||||
feature_random.seed = generation_seed ^ 0x51A7C11FF
|
||||
var ramp_edge := -1
|
||||
if feature_random.randf() < elevated_cliff_ramp_chance:
|
||||
ramp_edge = feature_random.randi_range(0, edge_specs.size() - 1)
|
||||
var placements: Array[Dictionary] = [
|
||||
{"offset": Vector2i(-1, -1), "id": elevated_cliff_corner_chunk_id, "turns": 2},
|
||||
{"offset": Vector2i(1, -1), "id": elevated_cliff_corner_chunk_id, "turns": 1},
|
||||
{"offset": Vector2i(0, 0), "id": elevated_cliff_top_chunk_id, "turns": 0},
|
||||
{"offset": Vector2i(-1, 1), "id": elevated_cliff_corner_chunk_id, "turns": 3},
|
||||
{"offset": Vector2i(1, 1), "id": elevated_cliff_corner_chunk_id, "turns": 0},
|
||||
]
|
||||
for edge_index: int in edge_specs.size():
|
||||
var edge_spec := edge_specs[edge_index]
|
||||
placements.append({
|
||||
"offset": edge_spec["offset"],
|
||||
"id": (
|
||||
elevated_cliff_ramp_chunk_id
|
||||
if edge_index == ramp_edge
|
||||
else elevated_cliff_edge_chunk_id
|
||||
),
|
||||
"turns": edge_spec["turns"],
|
||||
})
|
||||
for spec: Dictionary in placements:
|
||||
var definition := catalog.definition_for_id(spec["id"] as StringName)
|
||||
var variant := _authored_variant(definition, int(spec["turns"]))
|
||||
if variant == null:
|
||||
push_error(
|
||||
"Elevated cliff feature cannot resolve %s rotation %d."
|
||||
% [spec["id"], spec["turns"]]
|
||||
)
|
||||
return false
|
||||
var coordinate := _elevated_feature_center + (spec["offset"] as Vector2i)
|
||||
_placements[coordinate.y * grid_size.x + coordinate.x] = variant
|
||||
var validation_error := _resolved_layout_validation_error(_placements)
|
||||
if not validation_error.is_empty():
|
||||
push_error("Elevated cliff feature is invalid: " + validation_error)
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _variant_respects_ocean_boundary(
|
||||
variant: TerrainChunkVariant,
|
||||
coordinate: Vector2i,
|
||||
) -> bool:
|
||||
if (
|
||||
variant.definition.must_be_interior
|
||||
and _coordinate_is_on_boundary(coordinate)
|
||||
):
|
||||
return false
|
||||
var ocean_edges := variant.rotated_edge_mask(
|
||||
variant.definition.ocean_facing_edges
|
||||
)
|
||||
|
|
@ -1231,7 +1426,7 @@ func _build_solution_root() -> Node3D:
|
|||
if variant == null:
|
||||
continue
|
||||
var coordinate := Vector2i(index % grid_size.x, index / grid_size.x)
|
||||
var chunk_root := variant.definition.packed_scene.instantiate() as Node3D
|
||||
var chunk_root := _instantiate_chunk_root(variant.definition)
|
||||
if chunk_root == null:
|
||||
push_error("%s does not instantiate as Node3D." % variant.stable_key())
|
||||
solution_root.free()
|
||||
|
|
@ -1260,6 +1455,27 @@ func _build_solution_root() -> Node3D:
|
|||
return solution_root
|
||||
|
||||
|
||||
func _instantiate_chunk_root(
|
||||
definition: TerrainChunkDefinition,
|
||||
) -> Node3D:
|
||||
var terrain_visual := definition.packed_scene.instantiate() as Node3D
|
||||
if terrain_visual == null:
|
||||
return null
|
||||
if definition.base_layer_scene == null:
|
||||
return terrain_visual
|
||||
var base_layer_visual := definition.base_layer_scene.instantiate() as Node3D
|
||||
if base_layer_visual == null:
|
||||
terrain_visual.free()
|
||||
return null
|
||||
var layered_root := Node3D.new()
|
||||
layered_root.name = "LayeredTerrainChunk"
|
||||
base_layer_visual.name = "TerrainBaseLayer"
|
||||
terrain_visual.name = "TerrainOverlay"
|
||||
layered_root.add_child(base_layer_visual)
|
||||
layered_root.add_child(terrain_visual)
|
||||
return layered_root
|
||||
|
||||
|
||||
func _replace_generated_chunks(solution_root: Node3D) -> void:
|
||||
_clear_generated_chunks()
|
||||
_generated_chunks = solution_root
|
||||
|
|
@ -1274,11 +1490,34 @@ func _add_collision(
|
|||
chunk_root,
|
||||
definition.primary_mesh_name,
|
||||
)
|
||||
_add_collision_to_mesh(
|
||||
primary_mesh,
|
||||
definition.stable_id,
|
||||
"TerrainCollision",
|
||||
)
|
||||
if definition.base_layer_scene == null:
|
||||
return
|
||||
var base_layer_mesh := TerrainChunkAnalyzer.find_primary_mesh(
|
||||
chunk_root,
|
||||
definition.base_layer_mesh_name,
|
||||
)
|
||||
_add_collision_to_mesh(
|
||||
base_layer_mesh,
|
||||
definition.stable_id,
|
||||
"TerrainBaseLayerCollision",
|
||||
)
|
||||
|
||||
|
||||
func _add_collision_to_mesh(
|
||||
primary_mesh: MeshInstance3D,
|
||||
stable_id: StringName,
|
||||
collision_name: String,
|
||||
) -> void:
|
||||
if primary_mesh == null or primary_mesh.mesh == null:
|
||||
return
|
||||
var terrain_shape := primary_mesh.mesh.create_trimesh_shape()
|
||||
if terrain_shape == null or terrain_shape.get_faces().is_empty():
|
||||
push_warning("%s produced no terrain collision." % definition.stable_id)
|
||||
push_warning("%s produced no terrain collision." % stable_id)
|
||||
return
|
||||
var concave_shape := terrain_shape as ConcavePolygonShape3D
|
||||
if concave_shape != null:
|
||||
|
|
@ -1287,7 +1526,7 @@ func _add_collision(
|
|||
# can never become an invisible collision gap.
|
||||
concave_shape.backface_collision = true
|
||||
var collision_body := StaticBody3D.new()
|
||||
collision_body.name = "TerrainCollision"
|
||||
collision_body.name = collision_name
|
||||
collision_body.collision_layer = 1
|
||||
collision_body.collision_mask = 0
|
||||
primary_mesh.add_child(collision_body)
|
||||
|
|
|
|||
|
|
@ -82,4 +82,23 @@ func validation_errors() -> PackedStringArray:
|
|||
"%s has a negative box collision dimension."
|
||||
% definition.stable_id
|
||||
)
|
||||
if definition.minimum_visual_scale > definition.maximum_visual_scale:
|
||||
errors.append(
|
||||
"%s has an inverted visual scale range."
|
||||
% definition.stable_id
|
||||
)
|
||||
if (
|
||||
not definition.material_variants.is_empty()
|
||||
and definition.variant_material_slot_names.is_empty()
|
||||
):
|
||||
errors.append(
|
||||
"%s has material variants but no target material slots."
|
||||
% definition.stable_id
|
||||
)
|
||||
for material: Material in definition.material_variants:
|
||||
if material == null:
|
||||
errors.append(
|
||||
"%s contains an empty material variant."
|
||||
% definition.stable_id
|
||||
)
|
||||
return errors
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ extends Resource
|
|||
@export_range(0.0, 100.0, 0.5) var preferred_nearby_radius := 0.0
|
||||
@export_range(1.0, 10.0, 0.1) var nearby_preference_weight_multiplier := 1.0
|
||||
@export_range(0.0, 10.0, 0.05) var clearance_radius := 0.5
|
||||
## Procedural instances use a deterministic uniform scale in this range.
|
||||
@export_range(0.1, 4.0, 0.05) var minimum_visual_scale := 1.0
|
||||
@export_range(0.1, 4.0, 0.05) var maximum_visual_scale := 1.0
|
||||
## Presentation-only offset. The prop root remains exactly terrain-aligned.
|
||||
@export var visual_offset := Vector3.ZERO
|
||||
## A single material is chosen per prop and applied only to matching imported
|
||||
## material slots. This keeps trunks untouched while varying foliage.
|
||||
@export var variant_material_slot_names := PackedStringArray()
|
||||
@export var material_variants: Array[Material] = []
|
||||
@export_range(0.0, 5.0, 0.05) var collision_radius := 0.0
|
||||
@export_range(0.0, 20.0, 0.05) var collision_height := 0.0
|
||||
@export var collision_box_size := Vector3.ZERO
|
||||
|
|
|
|||