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]