Builder now uses the furniture OSM already carries (footways, lamps, benches, bins, bollards, bus stops, crossings): raised chamfered footpaths, shopfront bands, awnings in three fabrics, parapets, layered fig/palm trees, zebra bars, dashed centre lines, Queen St Mall winged canopies. ACES tonemap + SSAO + fog in game.gd. flux_texture.sh gains size/luma clamping (FLUX renders "pale" as blown-out white); gen_textures.sh + check_textures.py manage the set. Woolies carpark rebuilt textured, parking the real fleet. Fixes: fractional OSM layer tag crash, zero-length-normal UV projection, blood-red mall pavers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
195 lines
7.2 KiB
GDScript
195 lines
7.2 KiB
GDScript
extends Node3D
|
|
## Woolies carpark, built in code rather than as a level.glb -- the parked
|
|
## shitboxes have to stay shuntable RigidBodies, which a static GLB can't carry.
|
|
## Everything else uses the same FLUX textures as the OSM levels.
|
|
##
|
|
## The Trolley Run junction (junctions.json) runs convoys along the aisles at
|
|
## z = 15/12 and z = -21/-24, from x = -70 to 70. Keep those lanes clear.
|
|
|
|
const TEX := "res://assets/textures/%s.jpg"
|
|
|
|
const BAY_ROWS := [-30.0, -12.0, 6.0, 24.0]
|
|
const BAY_X0 := -27.0
|
|
const BAY_STEP := 6.0
|
|
const BAYS := 10
|
|
|
|
func _ready() -> void:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = 8
|
|
_slab(Vector3(0, -0.5, 0), Vector3(300, 1, 240), _mat("asphalt", 10.0)) # tarmac
|
|
for side in [-1, 1]: # kerb walls
|
|
_slab(Vector3(0, 0.4, side * 79.0), Vector3(240, 0.8, 2), _mat("concrete_ground", 3.0))
|
|
_slab(Vector3(side * 119.0, 0.4, 0), Vector3(2, 0.8, 160), _mat("concrete_ground", 3.0))
|
|
|
|
_bay_lines()
|
|
_store()
|
|
|
|
# park the actual fleet -- coloured boxes read as placeholder next to
|
|
# textured everything-else, and we already ship 29 shitbox models
|
|
var models: Array[String] = []
|
|
for id in Registry.cars():
|
|
var st: CarStats = load(Registry.cars()[id])
|
|
if st.model_path != "" and ResourceLoader.exists(st.model_path):
|
|
models.append(st.model_path)
|
|
models.sort() # deterministic
|
|
for row in BAY_ROWS.size():
|
|
for bay in BAYS:
|
|
if rng.randf() < 0.35:
|
|
continue
|
|
var pos := Vector3(BAY_X0 + bay * BAY_STEP, 0.75, BAY_ROWS[row])
|
|
_car(pos, models, rng)
|
|
|
|
for i in 6:
|
|
_light_pole(Vector3(-30.0 + i * 12.0, 0, 8.0))
|
|
for x in [-96.0, 96.0]: # planted islands
|
|
for j in 5:
|
|
_planter_island(Vector3(x, 0, -60.0 + j * 30.0), rng)
|
|
for p in [Vector3(-52, 0, 44), Vector3(52, 0, 44), Vector3(-52, 0, -46)]:
|
|
_trolley_bay(p)
|
|
_race_path()
|
|
|
|
func _mat(tex: String, scale: float, rough := 0.9) -> StandardMaterial3D:
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_texture = load(TEX % tex)
|
|
m.uv1_scale = Vector3(scale, scale, scale)
|
|
m.uv1_triplanar = true
|
|
m.roughness = rough
|
|
m.metallic = 0.0
|
|
return m
|
|
|
|
func _flat(color: Color, rough := 0.85) -> StandardMaterial3D:
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_color = color
|
|
m.roughness = rough
|
|
m.metallic = 0.0
|
|
return m
|
|
|
|
func _slab(pos: Vector3, size: Vector3, mat: Material, collide := true) -> void:
|
|
var body: PhysicsBody3D = StaticBody3D.new()
|
|
var mi := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = size
|
|
bm.material = mat
|
|
mi.mesh = bm
|
|
body.add_child(mi)
|
|
if collide:
|
|
var shape := CollisionShape3D.new()
|
|
var bs := BoxShape3D.new()
|
|
bs.size = size
|
|
shape.shape = bs
|
|
body.add_child(shape)
|
|
body.position = pos
|
|
add_child(body)
|
|
|
|
func _car(pos: Vector3, models: Array[String], rng: RandomNumberGenerator) -> void:
|
|
var body := RigidBody3D.new()
|
|
body.mass = 300.0
|
|
var shape := CollisionShape3D.new()
|
|
var bs := BoxShape3D.new()
|
|
bs.size = Vector3(1.9, 1.3, 4.4)
|
|
shape.shape = bs
|
|
body.add_child(shape)
|
|
if models.is_empty():
|
|
var mi := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = bs.size
|
|
bm.material = _flat(Color.from_hsv(rng.randf(), 0.5, 0.7), 0.4)
|
|
mi.mesh = bm
|
|
body.add_child(mi)
|
|
else:
|
|
# model origin already sits at the 0.75 m ride height, same as the body's
|
|
body.add_child((load(models[rng.randi() % models.size()]) as PackedScene).instantiate())
|
|
body.position = pos
|
|
body.rotate_y(rng.randf_range(-0.05, 0.05) + (PI if rng.randf() < 0.5 else 0.0))
|
|
add_child(body)
|
|
|
|
func _bay_lines() -> void:
|
|
# painted bay dividers -- visual only, a 12 cm kerb of paint would be absurd
|
|
var paint := _flat(Color(0.86, 0.86, 0.80), 0.7)
|
|
for row in BAY_ROWS:
|
|
for bay in BAYS + 1:
|
|
var x := BAY_X0 - BAY_STEP * 0.5 + bay * BAY_STEP
|
|
_slab(Vector3(x, 0.02, row), Vector3(0.14, 0.04, 5.0), paint, false)
|
|
_slab(Vector3(BAY_X0 + BAY_STEP * (BAYS - 1) * 0.5, 0.02, row - 2.6),
|
|
Vector3(BAY_STEP * BAYS, 0.04, 0.14), paint, false)
|
|
|
|
func _store() -> void:
|
|
# the Woolies box at the far end -- what you're aiming at from the spawn
|
|
var front := -84.0
|
|
var mat_wall := _mat("facade_concrete", 0.09, 0.85)
|
|
var mat_shop := _mat("shopfront", 0.16, 0.3)
|
|
var mat_sign := _flat(Color(0.06, 0.32, 0.16), 0.6) # supermarket green
|
|
_slab(Vector3(0, 7.0, front - 14.0), Vector3(130, 14, 28), mat_wall)
|
|
_slab(Vector3(0, 2.1, front + 0.2), Vector3(120, 4.2, 0.6), mat_shop, false)
|
|
_slab(Vector3(0, 5.6, front + 0.3), Vector3(120, 2.4, 0.8), mat_sign, false)
|
|
_slab(Vector3(0, 4.3, front + 2.4), Vector3(126, 0.35, 5.0),
|
|
_mat("panel_white", 0.4, 0.5), false) # entry canopy
|
|
for x in [-58.0, -29.0, 0.0, 29.0, 58.0]:
|
|
_slab(Vector3(x, 2.15, front + 4.7), Vector3(0.3, 4.3, 0.3), _flat(Color(0.2, 0.2, 0.22), 0.5), false)
|
|
|
|
func _light_pole(base: Vector3) -> void:
|
|
var steel := _flat(Color(0.22, 0.23, 0.25), 0.5)
|
|
var pole := MeshInstance3D.new()
|
|
var cm := CylinderMesh.new()
|
|
cm.top_radius = 0.12
|
|
cm.bottom_radius = 0.17
|
|
cm.height = 9.0
|
|
cm.radial_segments = 8
|
|
cm.material = steel
|
|
pole.mesh = cm
|
|
pole.position = base + Vector3(0, 4.5, 0)
|
|
add_child(pole)
|
|
for s in [-1.0, 1.0]:
|
|
_slab(base + Vector3(s * 1.4, 8.9, 0), Vector3(2.0, 0.18, 0.6), steel, false)
|
|
_slab(base + Vector3(s * 2.2, 8.7, 0), Vector3(1.1, 0.22, 0.5),
|
|
_flat(Color(0.85, 0.84, 0.72), 0.4), false)
|
|
|
|
func _planter_island(pos: Vector3, rng: RandomNumberGenerator) -> void:
|
|
_slab(pos + Vector3(0, 0.25, 0), Vector3(5.0, 0.5, 11.0), _mat("planter", 0.6, 0.75))
|
|
_slab(pos + Vector3(0, 0.52, 0), Vector3(4.4, 0.06, 10.4), _mat("grass", 1.2), false)
|
|
for j in 2:
|
|
var at := pos + Vector3(0, 0, -2.6 + j * 5.2)
|
|
var trunk := MeshInstance3D.new()
|
|
var cm := CylinderMesh.new()
|
|
cm.top_radius = 0.13
|
|
cm.bottom_radius = 0.2
|
|
cm.height = 4.4
|
|
cm.radial_segments = 7
|
|
cm.material = _mat("bark", 1.4)
|
|
trunk.mesh = cm
|
|
trunk.position = at + Vector3(0, 2.7, 0)
|
|
add_child(trunk)
|
|
var crown := MeshInstance3D.new()
|
|
var sm := SphereMesh.new()
|
|
sm.radius = rng.randf_range(1.9, 2.5)
|
|
sm.height = sm.radius * 1.7
|
|
sm.radial_segments = 12
|
|
sm.rings = 7
|
|
sm.material = _mat("foliage", 0.55)
|
|
crown.mesh = sm
|
|
crown.position = at + Vector3(0, 5.6, 0)
|
|
add_child(crown)
|
|
|
|
func _trolley_bay(pos: Vector3) -> void:
|
|
var steel := _flat(Color(0.55, 0.56, 0.58), 0.45)
|
|
_slab(pos + Vector3(0, 2.5, 0), Vector3(3.0, 0.12, 9.0), _mat("panel_white", 0.5, 0.5), false)
|
|
for sx in [-1.3, 1.3]:
|
|
for sz in [-4.0, 4.0]:
|
|
_slab(pos + Vector3(sx, 1.25, sz), Vector3(0.16, 2.5, 0.16), steel, false)
|
|
for j in 5: # the trolleys themselves
|
|
_slab(pos + Vector3(0, 0.5, -3.2 + j * 1.6), Vector3(0.9, 1.0, 1.2), steel, false)
|
|
|
|
func _race_path() -> void:
|
|
# perimeter loop road around the parking rows -- race line + traffic route
|
|
var path := Path3D.new()
|
|
path.name = "RacePath"
|
|
var c := Curve3D.new()
|
|
var pts := [Vector3(45, 0, 55), Vector3(45, 0, -55), Vector3(-45, 0, -55), Vector3(-45, 0, 55)]
|
|
for i in pts.size() + 1:
|
|
var p: Vector3 = pts[i % pts.size()]
|
|
var to_prev: Vector3 = (pts[(i - 1 + pts.size()) % pts.size()] - p).normalized() * 12.0
|
|
var to_next: Vector3 = (pts[(i + 1) % pts.size()] - p).normalized() * 12.0
|
|
c.add_point(p, to_prev, to_next)
|
|
path.curve = c
|
|
add_child(path)
|