119 lines
5.6 KiB
GDScript
119 lines
5.6 KiB
GDScript
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]
|