Files

119 lines
5.6 KiB
GDScript
Raw Permalink Normal View History

2026-09-12 11:01:37 +02:00
class_name QuarryLandscape
extends RefCounted
const CLIFF := "res://assets/quarry/namaqualand_cliff_02/namaqualand_cliff_02.gltf"
const BOULDER := "res://assets/quarry/namaqualand_boulder_03/namaqualand_boulder_03.gltf"
static var templates := {}
# Absolute orientation offset for exterior scans; do not accumulate rotations.
const EXTERIOR_FACING := PI
static func build_routes(arena: RelayArena) -> void:
var root := Node3D.new(); root.name = "QuarryRouteRocks"; arena.add_child(root)
for side in [-1, 1]:
var center := Vector3(side * 32, -2.0, -side * 12)
var size := Vector3(11, 12, 22)
place(root, BOULDER, center, size, side * PI / 2, true)
arena.obstacles.append(AABB(Vector3(center.x - size.x / 2, center.y, center.z - size.z / 2), size))
static func build(arena: RelayArena) -> void:
var root := Node3D.new(); root.name = "QuarryEscarpment"; arena.add_child(root)
QuarryShell.build(root)
# Layered scan faces overlap the continuous backing; roots are buried 9 metres.
for side in [-1, 1]:
for i in 9:
var z := -72.0 + i * 18.0
place(root, CLIFF, Vector3(side * 59, -9, z), Vector3(25, 36 + sin(i * 1.4) * 4, 30), side * PI / 2 + sin(i) * 0.06 + EXTERIOR_FACING)
for i in 7:
var x := -54.0 + i * 18.0
place(root, CLIFF, Vector3(x, -9, side * 79), Vector3(30, 37 + cos(i * 1.8) * 5, 27), (PI if side > 0 else 0.0) + EXTERIOR_FACING)
for i in 10:
place(root, BOULDER, Vector3(side * 50.0, -1.9, -65 + i * 14), Vector3(7, 5 + i % 3, 12), i * 0.71 + EXTERIOR_FACING)
for i in 8:
place(root, BOULDER, Vector3(-49 + i * 14, -1.9, side * 69), Vector3(12, 5 + i % 3, 7), i * 0.6 + EXTERIOR_FACING)
dust_skirt(root, side)
static func collect(node: Node, transform: Transform3D, result: Array) -> void:
var current := transform
if node is Node3D: current = transform * node.transform
if node is MeshInstance3D: result.append([node.mesh, current])
for child in node.get_children(): collect(child, current, result)
static func template(path: String) -> Array:
if not templates.has(path):
var scene: Node3D = load(path).instantiate()
var entries := []
collect(scene, Transform3D.IDENTITY, entries)
templates[path] = entries
scene.free()
return templates[path]
static func place(root: Node3D, path: String, center: Vector3, dimensions: Vector3, yaw: float, solid := false) -> void:
var entries := template(path)
var turn := Transform3D(Basis(Vector3.UP, yaw), Vector3.ZERO)
var bounds := AABB()
var first := true
for entry in entries:
var box: AABB = (turn * entry[1]) * entry[0].get_aabb()
bounds = box if first else bounds.merge(box)
first = false
var scale_value := dimensions / bounds.size
var basis := Basis.from_scale(scale_value)
var offset := center - basis * Vector3(bounds.get_center().x, bounds.position.y, bounds.get_center().z)
var fit := Transform3D(basis, offset)
for entry in entries:
var instance := MeshInstance3D.new()
instance.mesh = entry[0]
instance.transform = fit * turn * entry[1]
for surface in instance.mesh.get_surface_count():
instance.set_surface_override_material(surface, sandstone(instance.mesh.surface_get_material(surface)))
instance.visibility_range_end = 0
instance.lod_bias = 0.55
root.add_child(instance)
if solid:
instance.create_trimesh_collision()
instance.visible = DisplayServer.get_name() != "headless"
static func dust_skirt(root: Node3D, side: int) -> void:
var material := UrbanMaterials.get_surface("sand", Color("baa081")).duplicate() as StandardMaterial3D
material.vertex_color_use_as_albedo = true
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
material.cull_mode = BaseMaterial3D.CULL_DISABLED
var tool := SurfaceTool.new(); tool.begin(Mesh.PRIMITIVE_TRIANGLES)
for i in 128:
var z := -64.0 + i
var inner := 42.9 + sin(z * 0.7) * 0.3
var points := [Vector3(side * 44.48, 0.042, z), Vector3(side * inner, 0.044, z), Vector3(side * (42.9 + sin((z + 1) * 0.7) * 0.3), 0.044, z + 1), Vector3(side * 44.48, 0.042, z + 1)]
for index in [0, 1, 2, 0, 2, 3]:
tool.set_normal(Vector3.UP); tool.set_color(Color(1, 1, 1, 1.0 if index in [0, 3] else 0.0)); tool.set_uv(Vector2(points[index].x, points[index].z)); tool.add_vertex(points[index])
var mesh := MeshInstance3D.new(); mesh.mesh = tool.commit(); mesh.material_override = material
# A separate alpha-faded mesh keeps the full floor opaque and inexpensive.
root.add_child(mesh)
static var sandstone_materials := {}
static func sandstone(source: Material) -> Material:
if source == null:
if not sandstone_materials.has(-1):
var entries := template(CLIFF)
var material := entries[0][0].surface_get_material(0).duplicate() as StandardMaterial3D
material.uv1_triplanar = true
material.uv1_world_triplanar = true
material.uv1_scale = Vector3.ONE * 0.12
material.normal_enabled = true
material.normal_scale = 0.8
sandstone_materials[-1] = material
return sandstone_materials[-1]
if source is StandardMaterial3D and source.albedo_texture != null:
return source
var key := source.get_instance_id() if source != null else 0
if not sandstone_materials.has(key):
var material := ShaderMaterial.new()
material.shader = load("res://scripts/quarry_sandstone.gdshader")
var scanned := source is StandardMaterial3D and source.albedo_texture != null
material.set_shader_parameter("scanned_uv", scanned)
material.set_shader_parameter("color_map", source.albedo_texture if scanned else UrbanMaterials.texture("res://assets/materials/rock_color.jpg"))
material.set_shader_parameter("normal_map", source.normal_texture if scanned else UrbanMaterials.texture("res://assets/materials/rock_normal.jpg"))
sandstone_materials[key] = material
return sandstone_materials[key]