extends Node3D ## Terrain generator — M0 placeholder. ## ## Builds ONE heightmap chunk from deterministic noise, so the world already ## looks like rolling ground rather than a flat plane. This is intentionally ## simple: a single chunk, generated at _ready(). Chunk streaming, biomes and ## server-side determinism (see docs/ARCHITECTURE.md §4) come in later milestones. ## ## The mesh is generated in code (no asset needed) so the project runs the moment ## Godot opens it, with nothing to import. @export var size: int = 128 # chunk is size x size metres @export var resolution: int = 96 # vertices per side (higher = smoother/heavier) @export var height_scale: float = 10.0 @export var noise_seed: int = 1337 func _ready() -> void: _build() func _build() -> void: var noise := FastNoiseLite.new() noise.seed = noise_seed noise.noise_type = FastNoiseLite.TYPE_PERLIN noise.frequency = 0.012 noise.fractal_octaves = 4 var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) var step := float(size) / float(resolution) var half := size * 0.5 var uv_scale := 1.0 / float(size) # Build a grid of quads, two triangles each. Each vertex carries a planar UV # (world-XZ mapped to 0..1) so the surface is ready for texturing later. for z in range(resolution): for x in range(resolution): var x0 := x * step - half var z0 := z * step - half var x1 := (x + 1) * step - half var z1 := (z + 1) * step - half var p00 := Vector3(x0, _height(noise, x0, z0), z0) var p10 := Vector3(x1, _height(noise, x1, z0), z0) var p01 := Vector3(x0, _height(noise, x0, z1), z1) var p11 := Vector3(x1, _height(noise, x1, z1), z1) _add_tri(st, p00, p01, p11, half, uv_scale) _add_tri(st, p00, p11, p10, half, uv_scale) # Normals give the lighting its shape. Tangents are only needed for normal # maps, which this low-poly material doesn't use — so we skip them (and avoid # the "UVs required to generate tangents" error). st.generate_normals() var mesh := st.commit() var mi := MeshInstance3D.new() mi.mesh = mesh # Simple ground material. Low-poly look = flat-ish colour + lighting does the rest. var mat := StandardMaterial3D.new() mat.albedo_color = Color(0.33, 0.45, 0.22) # mossy green mat.roughness = 0.95 mi.material_override = mat add_child(mi) # Collision so the player can walk on it. var body := StaticBody3D.new() var shape := CollisionShape3D.new() var col := mesh.create_trimesh_shape() shape.shape = col body.add_child(shape) add_child(body) func _height(noise: FastNoiseLite, x: float, z: float) -> float: return noise.get_noise_2d(x, z) * height_scale func _add_tri(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, half: float, uv_scale: float) -> void: st.set_uv(Vector2((a.x + half) * uv_scale, (a.z + half) * uv_scale)) st.add_vertex(a) st.set_uv(Vector2((b.x + half) * uv_scale, (b.z + half) * uv_scale)) st.add_vertex(b) st.set_uv(Vector2((c.x + half) * uv_scale, (c.z + half) * uv_scale)) st.add_vertex(c)