Add deterministic generated worlds
This commit is contained in:
parent
19f63bd656
commit
e8888ed05a
87 changed files with 3743 additions and 139 deletions
216
world/generation/terrain_chunk_analyzer.gd
Normal file
216
world/generation/terrain_chunk_analyzer.gd
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
class_name TerrainChunkAnalyzer
|
||||
extends RefCounted
|
||||
|
||||
const DEFAULT_EDGE_EPSILON := 0.005
|
||||
const DEFAULT_PROFILE_QUANTIZATION := 0.001
|
||||
|
||||
|
||||
static func create_variants(
|
||||
definition: TerrainChunkDefinition,
|
||||
chunk_size: float,
|
||||
edge_epsilon := DEFAULT_EDGE_EPSILON,
|
||||
profile_quantization := DEFAULT_PROFILE_QUANTIZATION,
|
||||
) -> Array[TerrainChunkVariant]:
|
||||
var variants: Array[TerrainChunkVariant] = []
|
||||
if definition == null or definition.packed_scene == null:
|
||||
return variants
|
||||
var chunk_root := definition.packed_scene.instantiate()
|
||||
var primary_mesh := find_primary_mesh(
|
||||
chunk_root,
|
||||
definition.primary_mesh_name,
|
||||
)
|
||||
if primary_mesh == null or primary_mesh.mesh == null:
|
||||
push_error(
|
||||
"Terrain chunk %s has no MeshInstance3D named %s."
|
||||
% [definition.stable_id, definition.primary_mesh_name]
|
||||
)
|
||||
chunk_root.free()
|
||||
return variants
|
||||
|
||||
var mesh_transform := _transform_relative_to(primary_mesh, chunk_root)
|
||||
var boundary_points := _collect_boundary_points(
|
||||
primary_mesh.mesh,
|
||||
mesh_transform,
|
||||
chunk_size,
|
||||
edge_epsilon,
|
||||
)
|
||||
for quarter_turns: int in 4:
|
||||
if not definition.allows_quarter_turn(quarter_turns):
|
||||
continue
|
||||
var rotated_points := _rotate_points(boundary_points, quarter_turns)
|
||||
var variant := TerrainChunkVariant.new()
|
||||
variant.definition = definition
|
||||
variant.quarter_turns = quarter_turns
|
||||
variant.edge_profiles = _build_profiles(
|
||||
rotated_points,
|
||||
chunk_size,
|
||||
edge_epsilon,
|
||||
profile_quantization,
|
||||
)
|
||||
variants.append(variant)
|
||||
chunk_root.free()
|
||||
return variants
|
||||
|
||||
|
||||
static func find_primary_mesh(
|
||||
root: Node,
|
||||
primary_mesh_name: StringName,
|
||||
) -> MeshInstance3D:
|
||||
if root is MeshInstance3D and root.name == primary_mesh_name:
|
||||
return root as MeshInstance3D
|
||||
for candidate: Node in root.find_children(
|
||||
"*",
|
||||
"MeshInstance3D",
|
||||
true,
|
||||
false,
|
||||
):
|
||||
var mesh_instance := candidate as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.name == primary_mesh_name:
|
||||
return mesh_instance
|
||||
return null
|
||||
|
||||
|
||||
static func _transform_relative_to(node: Node3D, root: Node) -> Transform3D:
|
||||
var relative_transform := Transform3D.IDENTITY
|
||||
var current: Node = node
|
||||
while current != null and current != root:
|
||||
if current is Node3D:
|
||||
relative_transform = (
|
||||
(current as Node3D).transform * relative_transform
|
||||
)
|
||||
current = current.get_parent()
|
||||
return relative_transform
|
||||
|
||||
|
||||
static func _collect_boundary_points(
|
||||
mesh: Mesh,
|
||||
mesh_transform: Transform3D,
|
||||
chunk_size: float,
|
||||
edge_epsilon: float,
|
||||
) -> PackedVector3Array:
|
||||
var result := PackedVector3Array()
|
||||
var half_size := chunk_size * 0.5
|
||||
var unique_points: Dictionary[Vector3i, bool] = {}
|
||||
for surface_index: int in mesh.get_surface_count():
|
||||
var arrays := mesh.surface_get_arrays(surface_index)
|
||||
if arrays.is_empty():
|
||||
continue
|
||||
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||
for source_vertex: Vector3 in vertices:
|
||||
var vertex := mesh_transform * source_vertex
|
||||
if not _is_boundary_point(vertex, half_size, edge_epsilon):
|
||||
continue
|
||||
var key := Vector3i(
|
||||
roundi(vertex.x / edge_epsilon),
|
||||
roundi(vertex.y / edge_epsilon),
|
||||
roundi(vertex.z / edge_epsilon),
|
||||
)
|
||||
if unique_points.has(key):
|
||||
continue
|
||||
unique_points[key] = true
|
||||
result.append(vertex)
|
||||
return result
|
||||
|
||||
|
||||
static func _is_boundary_point(
|
||||
point: Vector3,
|
||||
half_size: float,
|
||||
tolerance: float,
|
||||
) -> bool:
|
||||
return (
|
||||
absf(absf(point.x) - half_size) <= tolerance
|
||||
or absf(absf(point.z) - half_size) <= tolerance
|
||||
)
|
||||
|
||||
|
||||
static func _rotate_points(
|
||||
points: PackedVector3Array,
|
||||
quarter_turns: int,
|
||||
) -> PackedVector3Array:
|
||||
var result := PackedVector3Array()
|
||||
var angle := float(posmod(quarter_turns, 4)) * PI * 0.5
|
||||
for point: Vector3 in points:
|
||||
result.append(point.rotated(Vector3.UP, angle))
|
||||
return result
|
||||
|
||||
|
||||
static func _build_profiles(
|
||||
boundary_points: PackedVector3Array,
|
||||
chunk_size: float,
|
||||
edge_epsilon: float,
|
||||
profile_quantization: float,
|
||||
) -> Array[TerrainChunkEdgeProfile]:
|
||||
var profiles: Array[TerrainChunkEdgeProfile] = []
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
var edge := edge_value as TerrainChunkTopology.Edge
|
||||
profiles.append(
|
||||
_build_profile(
|
||||
edge,
|
||||
boundary_points,
|
||||
chunk_size * 0.5,
|
||||
edge_epsilon,
|
||||
profile_quantization,
|
||||
)
|
||||
)
|
||||
return profiles
|
||||
|
||||
|
||||
static func _build_profile(
|
||||
edge: TerrainChunkTopology.Edge,
|
||||
boundary_points: PackedVector3Array,
|
||||
half_size: float,
|
||||
edge_epsilon: float,
|
||||
profile_quantization: float,
|
||||
) -> TerrainChunkEdgeProfile:
|
||||
var unique_points: Dictionary[Vector2i, bool] = {}
|
||||
var profile_points: Array[Vector2] = []
|
||||
for point: Vector3 in boundary_points:
|
||||
if not _point_is_on_edge(point, edge, half_size, edge_epsilon):
|
||||
continue
|
||||
var tangent := (
|
||||
point.x
|
||||
if edge in [
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
TerrainChunkTopology.Edge.SOUTH,
|
||||
]
|
||||
else point.z
|
||||
)
|
||||
var key := Vector2i(
|
||||
roundi(tangent / profile_quantization),
|
||||
roundi(point.y / profile_quantization),
|
||||
)
|
||||
if unique_points.has(key):
|
||||
continue
|
||||
unique_points[key] = true
|
||||
profile_points.append(
|
||||
Vector2(key.x, key.y) * profile_quantization
|
||||
)
|
||||
profile_points.sort_custom(_profile_point_less_than)
|
||||
return TerrainChunkEdgeProfile.new(
|
||||
edge,
|
||||
PackedVector2Array(profile_points),
|
||||
)
|
||||
|
||||
|
||||
static func _point_is_on_edge(
|
||||
point: Vector3,
|
||||
edge: TerrainChunkTopology.Edge,
|
||||
half_size: float,
|
||||
tolerance: float,
|
||||
) -> bool:
|
||||
match edge:
|
||||
TerrainChunkTopology.Edge.NORTH:
|
||||
return absf(point.z + half_size) <= tolerance
|
||||
TerrainChunkTopology.Edge.EAST:
|
||||
return absf(point.x - half_size) <= tolerance
|
||||
TerrainChunkTopology.Edge.SOUTH:
|
||||
return absf(point.z - half_size) <= tolerance
|
||||
TerrainChunkTopology.Edge.WEST:
|
||||
return absf(point.x + half_size) <= tolerance
|
||||
return false
|
||||
|
||||
|
||||
static func _profile_point_less_than(a: Vector2, b: Vector2) -> bool:
|
||||
if is_equal_approx(a.x, b.x):
|
||||
return a.y < b.y
|
||||
return a.x < b.x
|
||||
Loading…
Add table
Add a link
Reference in a new issue