Files

179 lines
9.2 KiB
GDScript
Raw Permalink Normal View History

2026-09-12 11:01:37 +02:00
class_name DepotDetails
extends RefCounted
# Fabricated depot details use the existing collision shell. Four freight stacks
# replace the art of concrete cover, never its size or authoritative collision.
# Everything else is wall-mounted or above the highest opening in a facade.
const STEEL := Color("424b4b")
const GALVANIZED := Color("89918b")
const RUST := Color("74634e")
const WOOD := Color("c8b79c")
const DARK_WOOD := Color("9b896e")
const CABLE := Color("252e2e")
const MARKING := Color("bdac72")
const FREIGHT_POSITIONS := [Vector3(-19, 0.7, 12), Vector3(20, 0.7, -10),
Vector3(-32, 0.7, 23), Vector3(32, 0.7, -19)]
static func build(arena: RelayArena) -> void:
if DisplayServer.get_name() == "headless":
return
var detail := DepotDetails.new()
detail._construct(arena)
func _construct(arena: RelayArena) -> void:
var root := Node3D.new()
root.name = "DepotDetails"
arena.add_child(root)
var batch := UrbanMeshBatch.new()
var lamps := UrbanMeshBatch.new()
for location in FREIGHT_POSITIONS:
_replace_cover_art(arena, location)
# QuarryRealism supplies scanned crates inside the same cover bounds.
for side in [-1.0, 1.0]:
_perimeter_services(batch, side)
_gantry(batch, side * 18.0)
# Consistent repeated fittings make the eight structures read as one facility.
for origin in arena.building_origins:
_wall_services(batch, lamps, origin)
for side in [-1.0, 1.0]:
for z in [-48.0, -16.0, 16.0, 48.0]:
_boundary_lamp(batch, lamps, Vector3(side * 44.44, 3.35, z), side)
batch.finish(root, "DepotFabrication")
var luminaires := Node3D.new()
luminaires.name = "DepotLuminaireDiffusers"
root.add_child(luminaires)
lamps.finish(luminaires, "LightDiffusers")
var diffuser := StandardMaterial3D.new()
diffuser.albedo_color = Color("c2d3ce")
diffuser.emission_enabled = true
diffuser.emission = Color("b9d8d6")
diffuser.emission_energy_multiplier = 2.0
diffuser.roughness = 0.4
for child in luminaires.get_children():
(child as MeshInstance3D).material_override = diffuser
(child as MeshInstance3D).cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
func _replace_cover_art(arena: RelayArena, position: Vector3) -> void:
for child in arena.get_children():
if not child is Node3D or not (child as Node3D).position.is_equal_approx(position):
continue
# Collision is under an arena MeshInstance3D, whereas UrbanCover is a
# plain Node3D whose only children are these static visual batches.
if child is MeshInstance3D:
continue
for surface in child.get_children():
if surface is MeshInstance3D and str(surface.name).begins_with("ConcreteBarrier"):
(child as Node3D).visible = false
func _rod(batch: UrbanMeshBatch, a: Vector3, b: Vector3, radius: float,
kind: String = "metal", tint: Color = STEEL) -> void:
var rotation := Quaternion(Vector3.UP, (b - a).normalized()).get_euler()
batch.cylinder((a + b) * 0.5, radius, a.distance_to(b), kind, tint, rotation)
func _beam(batch: UrbanMeshBatch, a: Vector3, b: Vector3, width: float,
depth: float, tint: Color = STEEL) -> void:
var rotation := Quaternion(Vector3.UP, (b - a).normalized()).get_euler()
batch.box((a + b) * 0.5, Vector3(width, a.distance_to(b), depth), "metal", tint, rotation)
func _freight(batch: UrbanMeshBatch, origin: Vector3) -> void:
# Pallet stringers and deck carry two framed export cases. The combined
# silhouette fills the unchanged 3.2 x 1.4 x 1.5 cover bounds.
for z in [-0.59, 0.0, 0.59]:
batch.box(origin + Vector3(0, 0.085, z), Vector3(3.19, 0.17, 0.18), "wood", DARK_WOOD)
for slat in 10:
batch.box(origin + Vector3(-1.435 + slat * 0.319, 0.205, 0), Vector3(0.298, 0.07, 1.49), "wood", WOOD)
for side in [-1.0, 1.0]:
var at := origin + Vector3(side * 0.805, 0.805, 0)
batch.bevel_box(at, Vector3(1.51, 1.12, 1.4), "wood", WOOD, 0.016)
# Vertical corner battens meet the top and bottom ledgers exactly.
for z in [-0.715, 0.715]:
for x in [-0.665, 0.665]:
batch.box(at + Vector3(x, 0, z), Vector3(0.115, 1.18, 0.07), "wood", DARK_WOOD)
for y in [-0.525, 0.525]:
batch.box(at + Vector3(0, y, z), Vector3(1.52, 0.13, 0.068), "wood", DARK_WOOD)
# Two steel packing straps sit against the wood, including over the lid.
for x in [-0.37, 0.37]:
batch.box(at + Vector3(x, 0, z * 0.985), Vector3(0.037, 1.12, 0.018), "metal", STEEL)
# Small shipping label and two arrow stencils are mounted flush.
batch.box(at + Vector3(0.14, 0.1, z * 1.011), Vector3(0.36, 0.19, 0.008), "paint", Color("bcb8a3"))
for arrow in [-0.52, -0.42]:
batch.box(at + Vector3(arrow, -0.23, z * 1.012), Vector3(0.02, 0.14, 0.01), "paint", CABLE)
batch.box(at + Vector3(arrow, -0.17, z * 1.012), Vector3(0.065, 0.025, 0.01), "paint", CABLE)
for x in [-0.37, 0.37]:
batch.box(at + Vector3(x, 0.566, 0), Vector3(0.037, 0.018, 1.41), "metal", STEEL)
for x in [-0.759, 0.759]:
for y in [-0.525, 0.525]:
batch.box(at + Vector3(x, y, 0), Vector3(0.065, 0.13, 1.45), "wood", DARK_WOOD)
func _perimeter_services(batch: UrbanMeshBatch, side: float) -> void:
# Pipes are secured against the inner face of the existing 4.4 m wall;
# no freestanding poles or cables occupy a walking route.
var x := side * 44.37
for level in [3.52, 3.99]:
_rod(batch, Vector3(x, level, -62.4), Vector3(x, level, 62.4), 0.12, "metal", RUST if level < 3.8 else GALVANIZED)
for z in range(-60, 61, 12):
batch.cylinder(Vector3(x, level, z), 0.165, 0.085, "metal", STEEL, Vector3(PI / 2, 0, 0))
for bolt in 6:
var angle := TAU * bolt / 6.0
batch.cylinder(Vector3(x + cos(angle) * 0.14, level + sin(angle) * 0.14, z + 0.053), 0.017, 0.035, "metal", GALVANIZED, Vector3(PI / 2, 0, 0))
for z in range(-60, 61, 6):
batch.box(Vector3(side * 44.48, 3.71, z), Vector3(0.06, 1.02, 0.1), "metal", STEEL)
for level in [3.52, 3.99]:
batch.box(Vector3(side * 44.36, level - 0.18, z), Vector3(0.32, 0.08, 0.1), "metal", STEEL)
for z in [-47.0, 47.0]:
# A wall-mounted junction box with real conduit connections.
batch.bevel_box(Vector3(side * 44.42, 2.55, z), Vector3(0.14, 0.68, 0.5), "paint", STEEL, 0.025)
_rod(batch, Vector3(side * 44.33, 2.9, z), Vector3(side * 44.33, 3.52, z), 0.026)
batch.box(Vector3(side * 44.338, 2.55, z), Vector3(0.014, 0.16, 0.18), "paint", MARKING)
func _gantry(batch: UrbanMeshBatch, z: float) -> void:
# Utility truss bridges the centre lane between the two inner facades.
# End shoes bolt into the solid lintel, above all upper firing windows.
for side in [-1.0, 1.0]:
batch.box(Vector3(side * 6.39, 6.79, z), Vector3(0.22, 0.85, 1.08), "metal", STEEL)
for dz in [-0.4, 0.4]:
batch.box(Vector3(side * 6.27, 7.07, z + dz), Vector3(0.38, 0.12, 0.16), "metal", GALVANIZED)
for dz in [-0.39, 0.39]:
batch.box(Vector3(0, 7.13, z + dz), Vector3(12.65, 0.15, 0.12), "metal", STEEL)
batch.box(Vector3(0, 6.57, z + dz), Vector3(12.65, 0.12, 0.1), "metal", STEEL)
for bay in 10:
var x0 := -6.25 + bay * 1.25
_beam(batch, Vector3(x0, 6.62, z + dz), Vector3(x0 + 0.625, 7.06, z + dz), 0.045, 0.06)
_beam(batch, Vector3(x0 + 0.625, 7.06, z + dz), Vector3(x0 + 1.25, 6.62, z + dz), 0.045, 0.06)
for x in [-6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0]:
batch.box(Vector3(x, 7.06, z), Vector3(0.1, 0.12, 0.86), "metal", GALVANIZED)
for dz in [-0.18, 0.17]:
_rod(batch, Vector3(-6.45, 7.27, z + dz), Vector3(6.45, 7.27, z + dz), 0.1, "metal", RUST if dz < 0 else GALVANIZED)
# Modest cable sag supplies a curved silhouette against the rigid steel.
for offset in [-0.12, 0.12]:
for segment in 20:
var t0 := segment / 20.0
var t1 := (segment + 1) / 20.0
_rod(batch, Vector3(lerpf(-6.4, 6.4, t0), 6.65 - sin(t0 * PI) * 0.37, z + offset),
Vector3(lerpf(-6.4, 6.4, t1), 6.65 - sin(t1 * PI) * 0.37, z + offset), 0.017, "rubber", CABLE)
func _wall_services(batch: UrbanMeshBatch, lamps: UrbanMeshBatch, origin: Vector3) -> void:
# Rear corners have continuous solid wall. These small fixtures follow it,
# staying well away from doors, exterior stairs and the ascender ropes.
for side in [-1.0, 1.0]:
var at := origin + Vector3(side * 3.75, 2.88, 5.735)
batch.bevel_box(at, Vector3(0.61, 0.21, 0.14), "metal", STEEL, 0.025)
lamps.box(at + Vector3(0, -0.004, 0.077), Vector3(0.48, 0.11, 0.024), "paint", Color.WHITE)
for x in [-0.22, 0.0, 0.22]:
batch.box(at + Vector3(x, 0, 0.097), Vector3(0.018, 0.14, 0.016), "metal", STEEL)
_rod(batch, at + Vector3(0, 0.12, -0.01), origin + Vector3(side * 3.75, 3.22, 5.725), 0.018)
# Cable tray and louvers use the solid band above first-floor openings.
batch.box(origin + Vector3(0, 6.75, 5.712), Vector3(8.74, 0.14, 0.07), "metal", STEEL)
for x in [-3.8, -1.9, 0.0, 1.9, 3.8]:
batch.box(origin + Vector3(x, 6.75, 5.758), Vector3(0.055, 0.19, 0.04), "metal", GALVANIZED)
var vent := origin + Vector3(0, 6.26, 5.724)
batch.box(vent, Vector3(1.24, 0.51, 0.1), "metal", CABLE)
for slat in 7:
batch.box(vent + Vector3(0, -0.206 + slat * 0.069, 0.062), Vector3(1.14, 0.024, 0.052), "metal", GALVANIZED, Vector3(-0.25, 0, 0))
func _boundary_lamp(batch: UrbanMeshBatch, lamps: UrbanMeshBatch, position: Vector3, side: float) -> void:
batch.box(position, Vector3(0.11, 0.23, 0.55), "metal", STEEL)
lamps.box(position + Vector3(-side * 0.065, 0, 0), Vector3(0.035, 0.14, 0.45), "paint", Color.WHITE)
for z in [-0.18, 0.0, 0.18]:
batch.box(position + Vector3(-side * 0.087, 0, z), Vector3(0.016, 0.17, 0.018), "metal", STEEL)