115 lines
4.6 KiB
GDScript
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]])
|