extends RefCounted ## Decorative only: its own random stream keeps POI, loot and events unchanged. const ATLAS_PATH := "res://assets/planets/nature/nature_atlas.png" const TREE_CELLS := [[0, 1, 2], [1, 2], [1, 2], [0, 2, 3], [3], [0, 3]] const DETAIL_CELLS := [[7, 8, 9, 10, 13, 14], [7, 8, 9, 10, 13, 14], [6, 8, 9, 14, 15], [4, 5, 7, 11], [4, 5, 7, 11], [4, 5, 6, 7, 11]] static func generate(data: Dictionary) -> void: var rng := RandomNumberGenerator.new() rng.seed = int(data.seed) ^ 0x4e415455 var biome := int(data.landscape.index) var trees: Array = TREE_CELLS[biome] for tree in data.trees: # Retain some of the original canopy alongside the new species. tree.variant = -1 if rng.randf() < 0.35 else int(trees[rng.randi_range(0, trees.size()-1)]) for rock in data.rocks: rock.variant = rng.randi_range(4, 6) data.details = [] var kinds: Array = DETAIL_CELLS[biome] # Local clusters read as vegetation patches, rather than a uniform scatter. for index in 150: var anchor := Vector2(rng.randf_range(-1760, 1760), rng.randf_range(-1760, 1760)) var cell := int(kinds[rng.randi_range(0, kinds.size()-1)]) for member in rng.randi_range(2, 5): var point := anchor + Vector2.from_angle(rng.randf()*TAU)*rng.randf_range(0, 65) var width := rng.randf_range(16, 32) if cell == 4 or cell == 5 or cell == 6: width = rng.randf_range(24, 43) elif cell == 8 or cell == 9 or cell == 15: width = rng.randf_range(26, 44) if not _clear_site(data, point, width*0.5): continue if cell == 15 and _height(data, point) > 0.39: continue data.details.append({"position":point,"size":width,"rotation":rng.randf_range(-PI, PI),"variant":cell,"shade":rng.randf_range(0.88, 1.08)}) # At most one fallen tree, only on 8% of forested landing maps. if biome < 4 and rng.randf() < 0.08: for attempt in 80: if data.trees.is_empty(): break var tree: Dictionary = data.trees[rng.randi_range(0, data.trees.size()-1)] var point: Vector2 = tree.position + Vector2.from_angle(rng.randf()*TAU)*rng.randf_range(70, 110) if _clear_site(data, point, 65): data.details.append({"position":point,"size":92.0,"rotation":rng.randf_range(-PI, PI),"variant":12,"shade":1.0}) break static func _height(data: Dictionary, point: Vector2) -> float: var x := clampi(int((point.x+1920)/40), 0, 95) var y := clampi(int((point.y+1920)/40), 0, 95) return float(data.heights[y*96+x]) static func _clear_site(data: Dictionary, point: Vector2, radius: float) -> bool: if point.length() < 210+radius or absf(point.x)+radius > 1860 or absf(point.y)+radius > 1860: return false # Sample the full footprint, keeping props away from water and steep peaks. for offset in [Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN, Vector2(-0.7,-0.7), Vector2(0.7,-0.7), Vector2(-0.7,0.7), Vector2(0.7,0.7)]: var h := _height(data, point+offset*(radius+25)) if h < 0.315 or h > 0.68: return false for poi in data.pois: if point.distance_to(poi.position) < 135+radius: return false for event in data.events: if point.distance_to(event.position) < 115+radius: return false for structure in data.structures: if point.distance_to(structure.position) < float(structure.size)*0.7+radius: return false for landmark in data.get("landmarks", []): if point.distance_to(landmark.position) < float(landmark.size)*0.58+radius+15: return false for mountain in data.mountains: if point.distance_to(mountain.position) < float(mountain.size)*0.48+radius: return false return true