generated from krampus/template-godot4
Reorganized worldgen structure hierarchy around generation LODs
This commit is contained in:
parent
e2d75f394d
commit
64a9292f9f
@ -1,25 +1,13 @@
|
||||
class_name AutoRange extends Node
|
||||
## Component that automatically adjusts the visibility range of its parent to match LOD bounds
|
||||
|
||||
enum LODLevel {
|
||||
LOW,
|
||||
MEDIUM,
|
||||
HIGH,
|
||||
}
|
||||
|
||||
@export var lod_level: LODLevel
|
||||
@export var lod: WorldGen.LOD
|
||||
@export_range(0.0, 1.0) var end_margin_pct := 0.05
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var target: GeometryInstance3D = get_parent()
|
||||
match lod_level:
|
||||
LODLevel.LOW:
|
||||
target.visibility_range_end = WorldGenManager.low_detail_radius
|
||||
LODLevel.MEDIUM:
|
||||
target.visibility_range_end = WorldGenManager.med_detail_radius
|
||||
LODLevel.HIGH:
|
||||
target.visibility_range_end = WorldGenManager.high_detail_radius
|
||||
target.visibility_range_end = WorldGenManager.get_lod_radius(lod)
|
||||
|
||||
target.visibility_range_end_margin = end_margin_pct * target.visibility_range_end
|
||||
queue_free()
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
class_name Chunk extends Node3D
|
||||
## A discrete generated chunk of the world
|
||||
|
||||
const SIZE := Vector2(64, 64)
|
||||
const SCENE := preload("res://src/world/generation/chunk/chunk.tscn")
|
||||
|
||||
@export var construct_offset := Vector2(0.1, 0.0)
|
||||
@export var construct_height_factor := 300.0
|
||||
@export var construct_height_threshold := 30.0
|
||||
|
||||
|
||||
func chunk_position() -> Vector2:
|
||||
return Chunk.world_to_chunk(position)
|
||||
|
||||
|
||||
func generate() -> void:
|
||||
# TODO: this
|
||||
|
||||
# Let's generate a building...
|
||||
var construct_noise := WorldGenManager.noise.get_noise_2dv(chunk_position() + construct_offset)
|
||||
var construct_height := construct_noise * construct_height_factor
|
||||
if construct_height > construct_height_threshold:
|
||||
var construct := CSGBox3D.new()
|
||||
add_child(construct)
|
||||
construct.size = Vector3(60, construct_height, 60)
|
||||
construct.position = Vector3(2, construct_height / 2, 2)
|
||||
construct.collision_layer = 1
|
||||
construct.use_collision = true
|
||||
|
||||
|
||||
static func chunk_to_world(chunk_pos: Vector2) -> Vector3:
|
||||
return Vector3(chunk_pos.x * SIZE.x, 0, chunk_pos.y * SIZE.y)
|
||||
|
||||
|
||||
static func world_to_chunk(world_pos: Vector3) -> Vector2:
|
||||
return Vector2(world_pos.x / SIZE.x, world_pos.z / SIZE.y)
|
||||
|
||||
|
||||
static func generate_chunk(chunk_pos: Vector2) -> Chunk:
|
||||
var instance: Chunk = SCENE.instantiate()
|
||||
instance.position = Chunk.chunk_to_world(chunk_pos)
|
||||
instance.generate()
|
||||
return instance
|
||||
@ -1 +0,0 @@
|
||||
uid://chqpqe4anvamd
|
||||
@ -1,23 +0,0 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://crs68yhijqkca"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://chqpqe4anvamd" path="res://src/world/generation/chunk/chunk.gd" id="1_87ter"]
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_0cma0"]
|
||||
size = Vector2(64, 64)
|
||||
subdivide_width = 64
|
||||
subdivide_depth = 64
|
||||
center_offset = Vector3(32, 0, 32)
|
||||
|
||||
[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_87ter"]
|
||||
points = PackedVector3Array(1.9073486e-06, 0, 1.9073486e-06, 1.9073486e-06, 0, 64, 64, 0, 1.9073486e-06, 64, 0, 64)
|
||||
|
||||
[node name="Chunk" type="Node3D"]
|
||||
script = ExtResource("1_87ter")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("PlaneMesh_0cma0")
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="MeshInstance3D"]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D"]
|
||||
shape = SubResource("ConvexPolygonShape3D_87ter")
|
||||
@ -72,6 +72,7 @@ material = ExtResource("3_yw1ox")
|
||||
|
||||
[node name="AutoRange" type="Node" parent="InnerPyramid"]
|
||||
script = ExtResource("4_kox75")
|
||||
lod = 1
|
||||
end_margin_pct = 0.01
|
||||
metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
@ -87,6 +88,7 @@ shape = SubResource("ConcavePolygonShape3D_ek7o7")
|
||||
|
||||
[node name="AutoRange" type="Node" parent="WorldFloor"]
|
||||
script = ExtResource("4_kox75")
|
||||
lod = 1
|
||||
end_margin_pct = 0.01
|
||||
metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
@ -96,11 +98,13 @@ metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
[node name="MetroGridLayer" parent="MetroQuadrant/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 55)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 100, 448)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 400, 448)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 503)
|
||||
bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
bounding_box = AABB(0, 0, 0, 448, 400, 48640)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="InnerMetro" type="Node3D" parent="MetroQuadrant"]
|
||||
|
||||
@ -108,13 +112,15 @@ bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 45047, 0, 503)
|
||||
construct_height_factor_x = SubResource("Curve_0w44q")
|
||||
construct_height_factor_z = SubResource("Curve_amoo5")
|
||||
bounding_box = AABB(0, 0, 0, 4096, 100, 44480)
|
||||
bounding_box = AABB(0, 0, 0, 4096, 400, 44480)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant/InnerMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 503, 0, 45047)
|
||||
construct_height_factor_x = SubResource("Curve_amoo5")
|
||||
construct_height_factor_z = SubResource("Curve_0w44q")
|
||||
bounding_box = AABB(0, 0, 0, 44480, 100, 4096)
|
||||
bounding_box = AABB(0, 0, 0, 44480, 400, 4096)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroQuadrant2" type="Node3D" parent="."]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 100000)
|
||||
@ -123,11 +129,13 @@ transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0
|
||||
|
||||
[node name="MetroGridLayer" parent="MetroQuadrant2/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 55)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 100, 448)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 400, 448)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant2/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 503)
|
||||
bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
bounding_box = AABB(0, 0, 0, 448, 400, 48640)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="InnerMetro" type="Node3D" parent="MetroQuadrant2"]
|
||||
|
||||
@ -135,13 +143,15 @@ bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 45047, 0, 503)
|
||||
construct_height_factor_x = SubResource("Curve_0w44q")
|
||||
construct_height_factor_z = SubResource("Curve_amoo5")
|
||||
bounding_box = AABB(0, 0, 0, 4096, 100, 44480)
|
||||
bounding_box = AABB(0, 0, 0, 4096, 400, 44480)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant2/InnerMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 503, 0, 45047)
|
||||
construct_height_factor_x = SubResource("Curve_amoo5")
|
||||
construct_height_factor_z = SubResource("Curve_0w44q")
|
||||
bounding_box = AABB(0, 0, 0, 44480, 100, 4096)
|
||||
bounding_box = AABB(0, 0, 0, 44480, 400, 4096)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroQuadrant3" type="Node3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 100000, 0, 100000)
|
||||
@ -150,11 +160,13 @@ transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 1000
|
||||
|
||||
[node name="MetroGridLayer" parent="MetroQuadrant3/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 55)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 100, 448)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 400, 448)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant3/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 503)
|
||||
bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
bounding_box = AABB(0, 0, 0, 448, 400, 48640)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="InnerMetro" type="Node3D" parent="MetroQuadrant3"]
|
||||
|
||||
@ -162,13 +174,15 @@ bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 45047, 0, 503)
|
||||
construct_height_factor_x = SubResource("Curve_0w44q")
|
||||
construct_height_factor_z = SubResource("Curve_amoo5")
|
||||
bounding_box = AABB(0, 0, 0, 4096, 100, 44480)
|
||||
bounding_box = AABB(0, 0, 0, 4096, 400, 44480)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant3/InnerMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 503, 0, 45047)
|
||||
construct_height_factor_x = SubResource("Curve_amoo5")
|
||||
construct_height_factor_z = SubResource("Curve_0w44q")
|
||||
bounding_box = AABB(0, 0, 0, 44480, 100, 4096)
|
||||
bounding_box = AABB(0, 0, 0, 44480, 400, 4096)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroQuadrant4" type="Node3D" parent="."]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 100000, 0, 0)
|
||||
@ -177,11 +191,13 @@ transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 1000
|
||||
|
||||
[node name="MetroGridLayer" parent="MetroQuadrant4/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 55)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 100, 448)
|
||||
bounding_box = AABB(0, 0, 0, 49088, 400, 448)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant4/OuterMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 55, 0, 503)
|
||||
bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
bounding_box = AABB(0, 0, 0, 448, 400, 48640)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="InnerMetro" type="Node3D" parent="MetroQuadrant4"]
|
||||
|
||||
@ -189,10 +205,12 @@ bounding_box = AABB(0, 0, 0, 448, 100, 48640)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 45047, 0, 503)
|
||||
construct_height_factor_x = SubResource("Curve_0w44q")
|
||||
construct_height_factor_z = SubResource("Curve_amoo5")
|
||||
bounding_box = AABB(0, 0, 0, 4096, 100, 44480)
|
||||
bounding_box = AABB(0, 0, 0, 4096, 400, 44480)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
[node name="MetroGridLayer2" parent="MetroQuadrant4/InnerMetro" instance=ExtResource("4_fy7wq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 503, 0, 45047)
|
||||
construct_height_factor_x = SubResource("Curve_amoo5")
|
||||
construct_height_factor_z = SubResource("Curve_0w44q")
|
||||
bounding_box = AABB(0, 0, 0, 44480, 100, 4096)
|
||||
bounding_box = AABB(0, 0, 0, 44480, 400, 4096)
|
||||
noise_offset = Vector3(1, 1, 1)
|
||||
|
||||
@ -25,5 +25,5 @@ shape = SubResource("ConcavePolygonShape3D_q70le")
|
||||
|
||||
[node name="AutoRange" type="Node" parent="WorldFloor"]
|
||||
script = ExtResource("2_q70le")
|
||||
lod_level = 1
|
||||
lod = 1
|
||||
metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
@ -1,47 +1,43 @@
|
||||
class_name GenerationFeature extends Node3D
|
||||
class_name GenerationFeature extends GeneratedElement
|
||||
## Base class for world features generated during worldgen.
|
||||
##
|
||||
## Layers contain features. Some features may contain layers.
|
||||
|
||||
@export var noise_scale := Vector3.ONE
|
||||
@export var noise_offset := Vector3.ZERO
|
||||
## Set of sub-layers that require generation at each LOD
|
||||
var sub_layers: Dictionary[WorldGen.LOD, Dictionary] = {
|
||||
WorldGen.LOD.LOW: {},
|
||||
WorldGen.LOD.MEDIUM: {},
|
||||
WorldGen.LOD.HIGH: {},
|
||||
}
|
||||
|
||||
var sub_layers: Array[GenerationLayer]:
|
||||
get():
|
||||
if not _defined_sub_layers:
|
||||
sub_layers = _find_sub_layers(self)
|
||||
_defined_sub_layers = true
|
||||
return sub_layers
|
||||
var _defined_sub_layers := false
|
||||
var _generated := false
|
||||
## Set of LODs at which this feature has been generated
|
||||
var generated: Dictionary[WorldGen.LOD, bool] = {}
|
||||
|
||||
|
||||
func _find_sub_layers(node: Node) -> Array[GenerationLayer]:
|
||||
var layers: Array[GenerationLayer] = []
|
||||
func _ready() -> void:
|
||||
_discover_sub_layers(self)
|
||||
|
||||
|
||||
func _discover_sub_layers(node: Node) -> void:
|
||||
if node is GenerationLayer:
|
||||
layers.append(node)
|
||||
for lod in WorldGen.LOD_LIST:
|
||||
sub_layers[lod][node] = false
|
||||
for c: Node in node.get_children():
|
||||
# Do not walk outside of this scene
|
||||
if c.owner in [owner, self]:
|
||||
layers.append_array(_find_sub_layers(c))
|
||||
return layers
|
||||
_discover_sub_layers(c)
|
||||
|
||||
|
||||
func probe() -> void:
|
||||
# TODO may want to make low-detail & high-detail probes distinct
|
||||
if not _generated:
|
||||
generate()
|
||||
_generated = true
|
||||
func _generate(lod: WorldGen.LOD) -> bool:
|
||||
if lod not in generated:
|
||||
generate_feature(lod)
|
||||
generated[lod] = true
|
||||
|
||||
for layer in sub_layers:
|
||||
layer.probe()
|
||||
for layer: GenerationLayer in sub_layers[lod]:
|
||||
if layer.generate(lod):
|
||||
sub_layers[lod].erase(layer)
|
||||
|
||||
return not sub_layers[lod]
|
||||
|
||||
|
||||
## Generate elements of this feature. Called by default on the first call to `probe`
|
||||
func generate() -> void:
|
||||
## Generate elements of this feature. Called by default on the first call to `probe` for this lod.
|
||||
func generate_feature(_lod: WorldGen.LOD) -> void:
|
||||
pass # Implemented in derived type
|
||||
|
||||
|
||||
func sample_noise() -> float:
|
||||
var sample_point := global_position * noise_scale + noise_offset
|
||||
return WorldGenManager.noise.get_noise_3dv(sample_point)
|
||||
|
||||
@ -6,9 +6,10 @@ class_name MetroConstructSimple extends GenerationFeature
|
||||
@onready var construct_box: CSGBox3D = %ConstructBox
|
||||
|
||||
|
||||
func generate() -> void:
|
||||
var value := absf(sample_noise())
|
||||
var height := value * height_factor + min_height
|
||||
construct_box.position.y = height / 2
|
||||
construct_box.size.y = height
|
||||
_generated = true
|
||||
func generate_feature(lod: WorldGen.LOD) -> void:
|
||||
match lod:
|
||||
WorldGen.LOD.MEDIUM:
|
||||
var value := absf(sample_noise())
|
||||
var height := value * height_factor + min_height
|
||||
construct_box.position.y = height / 2
|
||||
construct_box.size.y = height
|
||||
|
||||
@ -13,6 +13,7 @@ points = PackedVector3Array(-31.999998, 0, -31.999998, -31.999998, 0, 31.999998,
|
||||
|
||||
[node name="MetroConstructSimple" type="Node3D"]
|
||||
script = ExtResource("1_jv74y")
|
||||
generated_lods = 6
|
||||
metadata/_custom_type_script = "uid://drk82eeqk2mjs"
|
||||
|
||||
[node name="WorldFloor" type="MeshInstance3D" parent="."]
|
||||
@ -27,7 +28,7 @@ shape = SubResource("ConvexPolygonShape3D_y2o1w")
|
||||
|
||||
[node name="AutoRange" type="Node" parent="WorldFloor"]
|
||||
script = ExtResource("2_74b57")
|
||||
lod_level = 1
|
||||
lod = 2
|
||||
metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
[node name="ConstructBox" type="CSGBox3D" parent="."]
|
||||
@ -39,5 +40,5 @@ size = Vector3(48, 48, 48)
|
||||
|
||||
[node name="AutoRange" type="Node" parent="ConstructBox"]
|
||||
script = ExtResource("2_74b57")
|
||||
lod_level = 1
|
||||
lod = 2
|
||||
metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
@ -26,5 +26,5 @@ shape = SubResource("ConvexPolygonShape3D_e5j5s")
|
||||
|
||||
[node name="AutoRange" type="Node" parent="WorldFloor"]
|
||||
script = ExtResource("2_xbnbu")
|
||||
lod_level = 1
|
||||
lod = 2
|
||||
metadata/_custom_type_script = "uid://cv0o1lirqeq44"
|
||||
|
||||
40
src/world/generation/generated_element.gd
Normal file
40
src/world/generation/generated_element.gd
Normal file
@ -0,0 +1,40 @@
|
||||
class_name GeneratedElement extends Node3D
|
||||
## Base class for all generated world elements
|
||||
|
||||
@export_flags("Low", "Medium", "High") var generated_lods := 0b111
|
||||
|
||||
## Scale to apply to position before sampling world generation noise.
|
||||
@export var noise_scale := Vector3.ONE
|
||||
|
||||
## Offset to apply to position before sampling world generation noise.
|
||||
@export var noise_offset := Vector3.ZERO
|
||||
|
||||
|
||||
## Generate this element at the given level of detail.
|
||||
##
|
||||
## Returns `true` if the element has completed generation at this LOD.
|
||||
## Some composite elements may require multiple `generate` calls to complete generation.
|
||||
func generate(lod: WorldGen.LOD) -> bool:
|
||||
if lod & generated_lods:
|
||||
return _generate(lod)
|
||||
return true
|
||||
|
||||
|
||||
## Generate this element at the given level of detail.
|
||||
##
|
||||
## This method is called by `generate` and should be overridden by derived types with
|
||||
## custom generation logic.
|
||||
## Returns `true` if the element has completed generation at this LOD.
|
||||
## Some composite elements may require multiple `generate` calls to complete generation.
|
||||
func _generate(_lod: WorldGen.LOD) -> bool:
|
||||
return true # Implemented by derived types.
|
||||
|
||||
|
||||
## Sample world generation noise at this element's position with scale & offset.
|
||||
func sample_noise() -> float:
|
||||
return sample_noise_at(global_position)
|
||||
|
||||
|
||||
## Sample world generation noise at the given point, applying this element's scale & offset.
|
||||
func sample_noise_at(pos: Vector3) -> float:
|
||||
return WorldGenManager.noise.get_noise_3dv(pos * noise_scale + noise_offset)
|
||||
1
src/world/generation/generated_element.gd.uid
Normal file
1
src/world/generation/generated_element.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c7x4yin558csb
|
||||
@ -1,10 +1,24 @@
|
||||
class_name GeneratedWorld extends Node3D
|
||||
|
||||
var elements: Dictionary[WorldGen.LOD, Dictionary] = {
|
||||
WorldGen.LOD.LOW: {},
|
||||
WorldGen.LOD.MEDIUM: {},
|
||||
WorldGen.LOD.HIGH: {},
|
||||
}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for c: Node in get_children():
|
||||
if c is GeneratedElement:
|
||||
for lod in WorldGen.LOD_LIST:
|
||||
if not elements[lod]:
|
||||
elements[lod] = {}
|
||||
elements[lod][c] = false
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
# Probe all child generation layers & features each frame
|
||||
for c: Node in get_children():
|
||||
if c is GenerationLayer:
|
||||
(c as GenerationLayer).probe()
|
||||
elif c is GenerationFeature:
|
||||
(c as GenerationFeature).probe()
|
||||
for lod in WorldGen.LOD_LIST:
|
||||
for element: GeneratedElement in elements[lod]:
|
||||
if element.generate(lod):
|
||||
elements[lod].erase(element)
|
||||
|
||||
@ -1,37 +1,19 @@
|
||||
class_name ArcologyGridLayer extends GenerationLayer
|
||||
class_name ArcologyGridLayer extends GridLayer
|
||||
## Grid with each cell possibly generating an arcology
|
||||
|
||||
const GRID_SIZE := Vector2(100000, 100000)
|
||||
|
||||
@export var arcology_feature_scene: PackedScene
|
||||
@export var empty_feature_scene: PackedScene
|
||||
|
||||
@export var sample_scale := 2.0
|
||||
@export var arcology_probability := 0.1
|
||||
|
||||
var features: Dictionary[Vector2, GenerationFeature] = {}
|
||||
|
||||
|
||||
func probe() -> void:
|
||||
var world_pos := WorldGenManager.get_generation_point()
|
||||
probe_radius(world_pos, WorldGenManager.low_detail_radius)
|
||||
# TODO high-detail & medium-detail probes
|
||||
|
||||
|
||||
func probe_radius(center: Vector3, radius: float) -> void:
|
||||
var rad_diff := Vector3(radius, 0, radius)
|
||||
var grid_low := world_to_local(center - rad_diff).floor()
|
||||
var grid_high := world_to_local(center + rad_diff).floor()
|
||||
for i in range(grid_low.x, grid_high.x + 1):
|
||||
for j in range(grid_low.y, grid_high.y + 1):
|
||||
probe_grid(Vector2(i, j))
|
||||
|
||||
|
||||
func probe_grid(grid_pos: Vector2) -> void:
|
||||
func generate_grid(lod: WorldGen.LOD, grid_pos: Vector2) -> bool:
|
||||
# Generate if needed
|
||||
if grid_pos not in features:
|
||||
var feature: GenerationFeature
|
||||
var sample := absf(WorldGenManager.noise.get_noise_2dv(sample_scale * grid_pos))
|
||||
var sample := absf(sample_noise_at_grid(grid_pos))
|
||||
print_debug("Sampled ", sample, " at ", grid_pos)
|
||||
if sample < arcology_probability:
|
||||
print_debug("Generating arcology at ", grid_pos)
|
||||
@ -42,13 +24,4 @@ func probe_grid(grid_pos: Vector2) -> void:
|
||||
feature.global_position = local_to_world(grid_pos)
|
||||
features[grid_pos] = feature
|
||||
# Probe feature
|
||||
features[grid_pos].probe()
|
||||
|
||||
|
||||
func local_to_world(local_pos: Vector2) -> Vector3:
|
||||
return global_transform * Vector3(local_pos.x * GRID_SIZE.x, 0, local_pos.y * GRID_SIZE.y)
|
||||
|
||||
|
||||
func world_to_local(world_pos: Vector3) -> Vector2:
|
||||
var rel_pos := world_pos * global_transform
|
||||
return Vector2(rel_pos.x / GRID_SIZE.x, rel_pos.z / GRID_SIZE.y)
|
||||
return features[grid_pos].generate(lod)
|
||||
|
||||
@ -8,4 +8,6 @@
|
||||
script = ExtResource("1_85eh3")
|
||||
arcology_feature_scene = ExtResource("2_pxyh5")
|
||||
empty_feature_scene = ExtResource("3_p6bke")
|
||||
grid_size = Vector2(100000, 100000)
|
||||
noise_scale = Vector3(2, 2, 2)
|
||||
metadata/_custom_type_script = "uid://kp8r23lsylpk"
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
class_name GenerationLayer extends Node3D
|
||||
class_name GenerationLayer extends GeneratedElement
|
||||
## A composite layer of world generation logic.
|
||||
|
||||
|
||||
## Probe this layer and any sub-layers at the world generation point, generating features as needed.
|
||||
func probe() -> void:
|
||||
pass # Implement in derived type
|
||||
# TODO what else goes here?
|
||||
|
||||
104
src/world/generation/layer/grid_layer/bounded_grid_layer.gd
Normal file
104
src/world/generation/layer/grid_layer/bounded_grid_layer.gd
Normal file
@ -0,0 +1,104 @@
|
||||
@tool
|
||||
class_name BoundedGridLayer extends GridLayer
|
||||
## A layer that generates tiles in a locally-constrained grid.
|
||||
|
||||
## Bounding box for the grid on the local XZ plane.
|
||||
##
|
||||
## Note that only feature handles are checked to be within the bounding box.
|
||||
## The Y component of this AABB is not used.
|
||||
@export var bounding_box: AABB:
|
||||
set(value):
|
||||
bounding_box = value
|
||||
if _debug_box:
|
||||
_set_debug_box_shape(bounding_box)
|
||||
|
||||
@export_group("Debug Draw")
|
||||
@export var draw_debug := true
|
||||
@export var debug_color := Color("#a486006b"):
|
||||
set(value):
|
||||
debug_color = value
|
||||
if _debug_box:
|
||||
_set_debug_box_color(debug_color)
|
||||
|
||||
var _debug_meshinstance: MeshInstance3D
|
||||
var _debug_box: BoxMesh
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
_init_debug_draw()
|
||||
else:
|
||||
var grid := _plane_size()
|
||||
for lod in WorldGen.LOD_LIST:
|
||||
for i in grid.x:
|
||||
for j in grid.y:
|
||||
_generation_grid[lod][Vector2(i, j)] = false
|
||||
|
||||
|
||||
func _generate(lod: WorldGen.LOD) -> bool:
|
||||
var center := WorldGenManager.get_generation_point()
|
||||
var radius := WorldGenManager.get_lod_radius(lod)
|
||||
var rad_diff := Vector3(radius, 0, radius)
|
||||
|
||||
# Translate probe box limits to grid space
|
||||
var a := world_to_local(center - rad_diff).floor()
|
||||
var b := world_to_local(center + rad_diff).floor()
|
||||
var grid_low := Vector2(minf(a.x, b.x), minf(a.y, b.y))
|
||||
var grid_high := Vector2(maxf(a.x, b.x), maxf(a.y, b.y))
|
||||
var grid_max := _plane_size().floor()
|
||||
|
||||
# Constrain to bounding box
|
||||
var x_min := maxf(grid_low.x, 0)
|
||||
var x_max := minf(grid_high.x + 1, grid_max.x)
|
||||
var y_min := maxf(grid_low.y, 0)
|
||||
var y_max := minf(grid_high.y + 1, grid_max.y)
|
||||
|
||||
# Probe everything within radius
|
||||
for i in range(x_min, x_max):
|
||||
for j in range(y_min, y_max):
|
||||
var pt := Vector2(i, j)
|
||||
if pt in _generation_grid[lod]:
|
||||
if generate_grid(lod, pt):
|
||||
_generation_grid[lod].erase(pt)
|
||||
|
||||
# Return false if there are still grid points to be generated
|
||||
return not _generation_grid[lod]
|
||||
|
||||
|
||||
func _plane_size() -> Vector2:
|
||||
return Vector2(bounding_box.size.x / grid_size.x, bounding_box.size.z / grid_size.y)
|
||||
|
||||
|
||||
func local_to_world(local_pos: Vector2) -> Vector3:
|
||||
var v3_pos := Vector3(local_pos.x * grid_size.x, 0, local_pos.y * grid_size.y)
|
||||
return global_transform * (bounding_box.position + v3_pos)
|
||||
|
||||
|
||||
func world_to_local(world_pos: Vector3) -> Vector2:
|
||||
var rel_pos := world_pos * global_transform - bounding_box.position
|
||||
return Vector2(rel_pos.x / grid_size.x, rel_pos.z / grid_size.y)
|
||||
|
||||
|
||||
#region debug draw
|
||||
func _init_debug_draw() -> void:
|
||||
_debug_box = BoxMesh.new()
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
_debug_box.material = mat
|
||||
_debug_meshinstance = MeshInstance3D.new()
|
||||
_debug_meshinstance.mesh = _debug_box
|
||||
add_child(_debug_meshinstance)
|
||||
_set_debug_box_color(debug_color)
|
||||
_set_debug_box_shape(bounding_box)
|
||||
|
||||
|
||||
func _set_debug_box_color(color: Color) -> void:
|
||||
var mat := _debug_box.material as StandardMaterial3D
|
||||
mat.albedo_color = color
|
||||
|
||||
|
||||
func _set_debug_box_shape(aabb: AABB) -> void:
|
||||
_debug_meshinstance.position = aabb.position + aabb.size / 2
|
||||
_debug_box.size = aabb.size
|
||||
#endregion
|
||||
@ -0,0 +1 @@
|
||||
uid://bo2pmuy3dn5y4
|
||||
@ -1,44 +1,19 @@
|
||||
@tool
|
||||
class_name GridLayer extends GenerationLayer
|
||||
## A layer that generates tiles in a locally-constrained grid.
|
||||
|
||||
## Bounding box for the grid on the local XZ plane.
|
||||
##
|
||||
## Note that only feature handles are checked to be within the bounding box.
|
||||
## The Y component of this AABB is not used.
|
||||
@export var bounding_box: AABB:
|
||||
set(value):
|
||||
bounding_box = value
|
||||
if _debug_box:
|
||||
_set_debug_box_shape(bounding_box)
|
||||
## A layer that generates tiles on an infinite grid
|
||||
|
||||
## Size of a grid tile in the local XZ plane.
|
||||
@export var grid_size: Vector2
|
||||
|
||||
@export_group("Debug Draw")
|
||||
@export var draw_debug := true
|
||||
@export var debug_color := Color("#a486006b"):
|
||||
set(value):
|
||||
debug_color = value
|
||||
if _debug_box:
|
||||
_set_debug_box_color(debug_color)
|
||||
|
||||
var _debug_meshinstance: MeshInstance3D
|
||||
var _debug_box: BoxMesh
|
||||
var _generation_grid: Dictionary[WorldGen.LOD, Dictionary] = {
|
||||
WorldGen.LOD.LOW: {},
|
||||
WorldGen.LOD.MEDIUM: {},
|
||||
WorldGen.LOD.HIGH: {},
|
||||
}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
_init_debug_draw()
|
||||
|
||||
|
||||
func probe() -> void:
|
||||
var world_pos := WorldGenManager.get_generation_point()
|
||||
probe_radius(world_pos, WorldGenManager.med_detail_radius)
|
||||
# TODO high-detail & low-detail probes
|
||||
|
||||
|
||||
func probe_radius(center: Vector3, radius: float) -> void:
|
||||
func _generate(lod: WorldGen.LOD) -> bool:
|
||||
var center := WorldGenManager.get_generation_point()
|
||||
var radius := WorldGenManager.get_lod_radius(lod)
|
||||
var rad_diff := Vector3(radius, 0, radius)
|
||||
|
||||
# Translate probe box limits to grid space
|
||||
@ -46,73 +21,35 @@ func probe_radius(center: Vector3, radius: float) -> void:
|
||||
var b := world_to_local(center + rad_diff).floor()
|
||||
var grid_low := Vector2(minf(a.x, b.x), minf(a.y, b.y))
|
||||
var grid_high := Vector2(maxf(a.x, b.x), maxf(a.y, b.y))
|
||||
var grid_max := _plane_size().floor()
|
||||
|
||||
# Constrain to bounding box
|
||||
var x_min := maxf(grid_low.x, 0)
|
||||
var x_max := minf(grid_high.x + 1, grid_max.x)
|
||||
var y_min := maxf(grid_low.y, 0)
|
||||
var y_max := minf(grid_high.y + 1, grid_max.y)
|
||||
|
||||
# Probe everything within radius
|
||||
for i in range(x_min, x_max):
|
||||
for j in range(y_min, y_max):
|
||||
probe_grid(Vector2(i, j))
|
||||
for i in range(grid_low.x, grid_high.x + 1):
|
||||
for j in range(grid_low.y, grid_high.y + 1):
|
||||
var pt := Vector2(i, j)
|
||||
if pt not in _generation_grid[lod]:
|
||||
if generate_grid(lod, pt):
|
||||
_generation_grid[lod][pt] = true
|
||||
|
||||
# The grid is infinite, so there will always be more to generate
|
||||
return false
|
||||
|
||||
|
||||
func probe_grid(_grid_pos: Vector2) -> void:
|
||||
pass # Implement in derived type
|
||||
|
||||
|
||||
func _plane_size() -> Vector2:
|
||||
return Vector2(bounding_box.size.x / grid_size.x, bounding_box.size.z / grid_size.y)
|
||||
|
||||
|
||||
func is_bounding_box_valid() -> bool:
|
||||
return bounding_box.size.length_squared() > 0
|
||||
|
||||
|
||||
func is_point_in_bounds(local_pos: Vector2) -> bool:
|
||||
local_pos = local_pos.floor()
|
||||
var grid_max := _plane_size()
|
||||
return (
|
||||
0 <= local_pos.x
|
||||
and local_pos.x < grid_max.x
|
||||
and 0 <= local_pos.y
|
||||
and local_pos.y < grid_max.y
|
||||
)
|
||||
## Generate the given grid position.
|
||||
##
|
||||
## Like `generate`, this returns `true` when generation is complete.
|
||||
func generate_grid(_lod: WorldGen.LOD, _grid_pos: Vector2) -> bool:
|
||||
return true # Implement in derived type
|
||||
|
||||
|
||||
func local_to_world(local_pos: Vector2) -> Vector3:
|
||||
var v3_pos := Vector3(local_pos.x * grid_size.x, 0, local_pos.y * grid_size.y)
|
||||
return global_transform * (bounding_box.position + v3_pos)
|
||||
return global_transform * v3_pos
|
||||
|
||||
|
||||
func world_to_local(world_pos: Vector3) -> Vector2:
|
||||
var rel_pos := world_pos * global_transform - bounding_box.position
|
||||
var rel_pos := world_pos * global_transform
|
||||
return Vector2(rel_pos.x / grid_size.x, rel_pos.z / grid_size.y)
|
||||
|
||||
|
||||
#region debug draw
|
||||
func _init_debug_draw() -> void:
|
||||
_debug_box = BoxMesh.new()
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
_debug_box.material = mat
|
||||
_debug_meshinstance = MeshInstance3D.new()
|
||||
_debug_meshinstance.mesh = _debug_box
|
||||
add_child(_debug_meshinstance)
|
||||
_set_debug_box_color(debug_color)
|
||||
_set_debug_box_shape(bounding_box)
|
||||
|
||||
|
||||
func _set_debug_box_color(color: Color) -> void:
|
||||
var mat := _debug_box.material as StandardMaterial3D
|
||||
mat.albedo_color = color
|
||||
|
||||
|
||||
func _set_debug_box_shape(aabb: AABB) -> void:
|
||||
_debug_meshinstance.position = aabb.position + aabb.size / 2
|
||||
_debug_box.size = aabb.size
|
||||
#endregion
|
||||
func sample_noise_at_grid(grid_pos: Vector2) -> float:
|
||||
return sample_noise_at(local_to_world(grid_pos))
|
||||
|
||||
@ -1 +1 @@
|
||||
uid://bo2pmuy3dn5y4
|
||||
uid://nq1cuemdrtn2
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
@tool
|
||||
extends GridLayer
|
||||
extends BoundedGridLayer
|
||||
## Procedural cityscape generation
|
||||
|
||||
@export var empty_scene: PackedScene
|
||||
@export var simple_construct_scene: PackedScene
|
||||
|
||||
@export var noise_scale := Vector3.ONE
|
||||
@export var noise_offset := Vector3.ZERO
|
||||
|
||||
@export_group("Construct Parameters")
|
||||
@export var simple_construct_threshold := 0.3
|
||||
@export var construct_height_factor_x: Curve
|
||||
@ -26,11 +23,10 @@ func generate_simple_construct(grid_pos: Vector2) -> MetroConstructSimple:
|
||||
return instance
|
||||
|
||||
|
||||
func probe_grid(grid_pos: Vector2) -> void:
|
||||
func generate_grid(lod: WorldGen.LOD, grid_pos: Vector2) -> bool:
|
||||
if grid_pos not in features:
|
||||
var feature: GenerationFeature
|
||||
var sample_point := local_to_world(grid_pos) * noise_scale + noise_offset
|
||||
var sample := absf(WorldGenManager.noise.get_noise_3dv(sample_point))
|
||||
var sample := absf(sample_noise_at(local_to_world(grid_pos)))
|
||||
if sample > simple_construct_threshold:
|
||||
feature = generate_simple_construct(grid_pos)
|
||||
else:
|
||||
@ -38,4 +34,4 @@ func probe_grid(grid_pos: Vector2) -> void:
|
||||
add_child(feature)
|
||||
feature.global_position = local_to_world(grid_pos)
|
||||
features[grid_pos] = feature
|
||||
features[grid_pos].probe()
|
||||
return features[grid_pos].generate(lod)
|
||||
|
||||
@ -9,3 +9,4 @@ script = ExtResource("1_ng6r6")
|
||||
empty_scene = ExtResource("2_47xjc")
|
||||
simple_construct_scene = ExtResource("3_woad2")
|
||||
grid_size = Vector2(64, 64)
|
||||
generated_lods = 6
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
class_name WorldGenManagerType extends Node
|
||||
class_name WorldGen extends Node
|
||||
## Global autoloaded singleton controller for worldgen parameters
|
||||
|
||||
## Worldgen uses custom levels of detail, distinct from mesh LODs.
|
||||
enum LOD {
|
||||
LOW = 0b001,
|
||||
MEDIUM = 0b010,
|
||||
HIGH = 0b100,
|
||||
}
|
||||
|
||||
## List of LODs, for iteration convenience.
|
||||
const LOD_LIST: Array[WorldGen.LOD] = [LOD.LOW, LOD.MEDIUM, LOD.HIGH]
|
||||
|
||||
@export var noise: FastNoiseLite
|
||||
|
||||
## Generate features with the lowest detail, like large distant structures, within this radius
|
||||
@ -19,3 +29,15 @@ func get_generation_point() -> Vector3:
|
||||
if camera:
|
||||
return camera.global_position
|
||||
return Vector3.ZERO
|
||||
|
||||
|
||||
## Get the generation radius for a given LOD.
|
||||
func get_lod_radius(lod: LOD) -> float:
|
||||
match lod:
|
||||
LOD.LOW:
|
||||
return low_detail_radius
|
||||
LOD.MEDIUM:
|
||||
return med_detail_radius
|
||||
LOD.HIGH:
|
||||
return high_detail_radius
|
||||
return -1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user