Files
Cinderwild/scripts/world/pier.gd

115 lines
4.6 KiB
GDScript

extends Node3D
## The Cove's pier (W3, docs/WORLDLETS_DESIGN.md §5): a wooden walkway from
## the sand straight out over the bay, so a cast from its end reaches deep
## sea water (mackerel, and cod further out). Part of the world, like the
## barrow: worked out from the terrain alone, so every player and the server
## get the same one, and it's never saved, built or dismantled.
##
## It starts where the beach has risen to deck height (so you walk straight
## on) and runs `LENGTH_OUT` metres past the waterline.
const WIDTH := 2.4
const DECK_ABOVE := 1.0 ## deck height over the water
const LENGTH_OUT := 24.0 ## how far past the waterline it reaches
const POST_EVERY := 3.0
const WOOD := Color(0.42, 0.30, 0.19)
const POST := Color(0.30, 0.22, 0.15)
var start := Vector3.ZERO ## the landward end, on the sand
var end := Vector3.ZERO ## the seaward end (fish from here)
var dir := Vector3.ZERO ## out to sea along the deck
## Lays the pier off the terrain's beach. Returns false (and adds nothing)
## when the worldlet has no beach.
func place(terrain: Node3D) -> bool:
if not terrain._has_beach or not terrain.worldlet.get("places", []).any(func(p): return p is Dictionary and p.get("type", "") == "pier"):
return false
var deck_y: float = terrain.water_level + DECK_ABOVE
var out2: Vector2 = terrain.shore_point(LENGTH_OUT)
var root2: Vector2 = terrain.shore_point(-2.0)
var d2 := (out2 - root2).normalized()
# Walk inland from the waterline to where the sand is at deck height.
var s2 := root2
for _i in range(40):
if terrain.sample_height(s2.x, s2.y) >= deck_y - 0.15:
break
s2 -= d2 * 0.5
start = Vector3(s2.x, deck_y, s2.y)
end = Vector3(out2.x, deck_y, out2.y)
dir = (end - start).normalized()
_build(terrain)
return true
## Whether (x, z) is under or right beside the pier (scenery keeps clear).
func covers(x: float, z: float, margin: float = 1.0) -> bool:
var a := Vector2(start.x, start.z)
var b := Vector2(end.x, end.z)
var p := Vector2(x, z)
var t := clampf((p - a).dot(b - a) / maxf((b - a).length_squared(), 0.001), 0.0, 1.0)
return p.distance_to(a.lerp(b, t)) < WIDTH * 0.5 + margin
func _build(terrain: Node3D) -> void:
var length := start.distance_to(end)
var yaw := atan2(dir.x, dir.z)
var basis := Basis(Vector3.UP, yaw)
var mid := (start + end) * 0.5
# The deck: planks across it, with a little gap and wobble between them.
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var n := int(length / 0.32)
var rng := RandomNumberGenerator.new()
rng.seed = 7171
for i in range(n):
var z := -length * 0.5 + (i + 0.5) * length / n
var h := 0.08 + rng.randf_range(-0.01, 0.01)
_box(st, Vector3(rng.randf_range(-0.04, 0.04), -h * 0.5, z), Vector3(WIDTH, h, length / n - 0.03),
WOOD.darkened(rng.randf_range(0.0, 0.18)))
# Posts down to the bed, in pairs, and a rail beam along each side.
var k := 0.0
while k <= length + 0.01:
var along := -length * 0.5 + k
for side in [-1.0, 1.0]:
var local := Vector3(side * (WIDTH * 0.5 - 0.1), 0.0, along)
var world_p: Vector3 = mid + basis * local
var bed: float = terrain.sample_height(world_p.x, world_p.z)
var tall := start.y - bed + 0.3
if tall > 0.2:
_box(st, local + Vector3(0, -tall * 0.5 - 0.05, 0), Vector3(0.22, tall, 0.22), POST)
k += POST_EVERY
for side in [-1.0, 1.0]:
_box(st, Vector3(side * (WIDTH * 0.5 - 0.06), -0.16, 0.0), Vector3(0.12, 0.14, length), POST)
st.generate_normals()
var mi := MeshInstance3D.new()
mi.name = "Deck"
mi.mesh = st.commit()
var mat := StandardMaterial3D.new()
mat.vertex_color_use_as_albedo = true
mat.roughness = 0.9
mi.material_override = mat
add_child(mi)
# Walkable deck (on the ground layer, like the terrain).
var body := StaticBody3D.new()
body.name = "DeckBody"
var cs := CollisionShape3D.new()
var box := BoxShape3D.new()
box.size = Vector3(WIDTH, 0.12, length)
cs.shape = box
cs.position = Vector3(0, -0.06, 0)
body.add_child(cs)
add_child(body)
transform = Transform3D(basis, mid)
add_to_group("pier")
static func _box(st: SurfaceTool, c: Vector3, s: Vector3, col: Color) -> void:
var h := s * 0.5
var v := [
c + Vector3(-h.x, -h.y, -h.z), c + Vector3(h.x, -h.y, -h.z), c + Vector3(h.x, h.y, -h.z), c + Vector3(-h.x, h.y, -h.z),
c + Vector3(-h.x, -h.y, h.z), c + Vector3(h.x, -h.y, h.z), c + Vector3(h.x, h.y, h.z), c + Vector3(-h.x, h.y, h.z),
]
var faces := [[0, 3, 2, 1], [4, 5, 6, 7], [0, 4, 7, 3], [1, 2, 6, 5], [3, 7, 6, 2], [0, 1, 5, 4]]
st.set_color(col)
for f in faces:
# (listed anticlockwise from outside; Godot's front faces are clockwise)
st.add_vertex(v[f[0]]); st.add_vertex(v[f[2]]); st.add_vertex(v[f[1]])
st.add_vertex(v[f[0]]); st.add_vertex(v[f[3]]); st.add_vertex(v[f[2]])