92 lines
4.3 KiB
GDScript
92 lines
4.3 KiB
GDScript
extends RefCounted
|
|
const LANDSCAPE := preload("res://scripts/planets/surface_landscape.gd")
|
|
const GRID := 96
|
|
const CELL := 40.0
|
|
const WORLD_SIZE := GRID * CELL
|
|
|
|
static func generate(seed_value: int, profile: Dictionary) -> Dictionary:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = seed_value
|
|
var landscape := LANDSCAPE.choose(seed_value,profile)
|
|
var noise := FastNoiseLite.new()
|
|
noise.seed = seed_value
|
|
noise.frequency = 0.0008 + posmod(seed_value,8)*0.0001
|
|
noise.fractal_octaves = 4
|
|
var humidity := FastNoiseLite.new()
|
|
humidity.seed = seed_value ^ 0x464f5245
|
|
humidity.frequency = 0.003
|
|
var heights := PackedFloat32Array()
|
|
heights.resize(GRID * GRID)
|
|
for y in GRID:
|
|
for x in GRID:
|
|
var point := Vector2(x, y) * CELL - Vector2.ONE * WORLD_SIZE * 0.5
|
|
var h := clampf(0.5 + noise.get_noise_2d(point.x, point.y) * 0.6, 0.0, 1.0)
|
|
if int(landscape.index) == 2:
|
|
h = 0.35 + noise.get_noise_2d(point.x,point.y)*0.45
|
|
elif int(landscape.index) == 3:
|
|
h = 0.43 + absf(noise.get_noise_2d(point.x,point.y))*0.95
|
|
if point.length() < 200:
|
|
h = lerpf(0.5, h, smoothstep(110, 200, point.length()))
|
|
heights[y * GRID + x] = h
|
|
var trees: Array[Dictionary] = []
|
|
var mountains: Array[Dictionary] = []
|
|
var rocks: Array[Dictionary] = []
|
|
for y in range(2, GRID - 2, 2):
|
|
for x in range(2, GRID - 2, 2):
|
|
var point := Vector2(x, y) * CELL - Vector2.ONE * WORLD_SIZE * 0.5 + Vector2(rng.randf_range(-25, 25), rng.randf_range(-25, 25))
|
|
var h: float = heights[y * GRID + x]
|
|
if point.length() < 190:
|
|
continue
|
|
if h > 0.66 and rng.randf() < 0.65:
|
|
mountains.append({"position": point, "size": rng.randf_range(110, 210), "rotation": rng.randf_range(-PI, PI)})
|
|
elif h > 0.28 and h < 0.65 and humidity.get_noise_2d(point.x, point.y) > -0.12 and rng.randf() < float(landscape.density):
|
|
trees.append({"position": point, "size": rng.randf_range(65, 115), "rotation": rng.randf_range(-PI, PI), "shade": rng.randf_range(0.75, 1.1)})
|
|
for index in 180:
|
|
var point := Vector2(rng.randf_range(-1750,1750),rng.randf_range(-1750,1750))
|
|
var gx := clampi(int((point.x+1920)/CELL),0,GRID-1)
|
|
var gy := clampi(int((point.y+1920)/CELL),0,GRID-1)
|
|
if point.length() > 200 and heights[gy*GRID+gx] > 0.30:
|
|
rocks.append({"position":point,"size":rng.randf_range(14,45),"rotation":rng.randf_range(-PI,PI)})
|
|
var pois: Array[Dictionary] = []
|
|
for index in 8:
|
|
var kind: String = ["mine", "ruins", "deposit"][index % 3]
|
|
var chosen := Vector2.ZERO
|
|
var found := false
|
|
for attempt in 1500:
|
|
var x := rng.randi_range(5, GRID - 6)
|
|
var y := rng.randi_range(5, GRID - 6)
|
|
var h: float = heights[y * GRID + x]
|
|
var point := Vector2(x, y) * CELL - Vector2.ONE * WORLD_SIZE * 0.5
|
|
if h < 0.29 or h > 0.71 or point.length() < 400:
|
|
continue
|
|
var clear := true
|
|
for other in pois:
|
|
if point.distance_to(other.position) < 420:
|
|
clear = false
|
|
if clear:
|
|
chosen = point
|
|
found = true
|
|
break
|
|
if not found:
|
|
# A lander can cross every terrain; a remote fallback never traps it.
|
|
chosen = Vector2.from_angle(index * TAU / 8) * 1250
|
|
pois.append({"id": index, "kind": kind, "position": chosen, "stage": 0, "complete": false, "discovered": false})
|
|
# Clear vegetation around explorable structures so entrances stay readable.
|
|
for poi in pois:
|
|
trees = trees.filter(func(prop): return prop.position.distance_to(poi.position) > 100)
|
|
mountains = mountains.filter(func(prop): return prop.position.distance_to(poi.position) > 120)
|
|
rocks = rocks.filter(func(prop): return prop.position.distance_to(poi.position) > 65)
|
|
var data := {"seed":seed_value,"heights":heights,"height_texture":LANDSCAPE.height_texture(heights),"landscape":landscape,"rocks":rocks,"trees":trees,"mountains":mountains,"pois":pois,"profile":profile.duplicate(true)}
|
|
preload("res://scripts/planets/surface_scenery.gd").generate(data,rng)
|
|
return data
|
|
|
|
static func terrain_texture(data: Dictionary) -> ImageTexture:
|
|
# Only the minimap is baked. The world uses full-resolution GPU terrain shading.
|
|
var image := Image.create(256,256,false,Image.FORMAT_RGB8)
|
|
for y in 256:
|
|
for x in 256:
|
|
var gx := clampi(int(float(x)/256*GRID),0,GRID-1)
|
|
var gy := clampi(int(float(y)/256*GRID),0,GRID-1)
|
|
image.set_pixel(x,y,LANDSCAPE.color_at(data.heights[gy*GRID+gx],0.0,data.landscape))
|
|
return ImageTexture.create_from_image(image)
|