@tool
class_name WallRunRange extends Node3D
## A place to actually TEST a wall run, east of the main ground slab.
##
## map_test's only wall was 0.5 x 4 x 6 metres. A wall run reaches ~12 m/s, so
## that is half a second of contact -- less than the lean's own slerp takes to
## arrive. Nothing about the feature could be seen on it, let alone the failures
## that need contact to persist.
##
## THREE WALLS, because the measured failures need different geometry:
##
## STRAIGHT 80 m of flat wall. The happy path, and what wall_run_gate covers.
##
## CORRIDOR two 60 m walls 6 m apart, for wall-to-wall jumps and for entering
## a run from either side.
##
## FACETED 80 m built from 2 m segments alternating +/- FACET_DEG. This is
## the one that matters: on a perfectly flat wall the facing travels
## 0.0 degrees, and on this it travelled 131 and ended 95 degrees off
## the wall tangent before c165a93. A dual-contoured surface looks
## like this to a sweep, and a plane does not. Anything tested only
## on the straight wall is being tested on the case that works.
##
## Built in code rather than authored because the faceted wall is 40 segments and
## the angle wants to be a knob, not 40 hand-edited transforms.
const LENGTH := 80.0 ## straight + faceted wall length
const CORRIDOR_LENGTH := 60.0
const CORRIDOR_GAP := 6.0 ## wall-to-wall jump distance
const HEIGHT := 8.0
const THICK := 1.0
const SEG := 2.0 ## faceted segment length
const FACET_DEG := 3.0
const SLAB := Vector3(100.0, 0.4, 60.0)
const ORIGIN := Vector3(100.0, 0.0, 0.0) ## ground slab ends at x = 50
## Layer 3 is what every other body in map_test uses; a wall on the wrong layer is
## invisible to _detect_walls and the range would silently do nothing.
const LAYER := 3
func _ready() -> void:
for c in get_children():
c.queue_free()
_slab()
_wall("Straight", Vector3(ORIGIN.x, HEIGHT * 0.5, ORIGIN.z - 22.0), LENGTH, 0.0)
_label("Straight", Vector3(ORIGIN.x, HEIGHT + 2.0, ORIGIN.z - 22.0), Color(0.6, 0.85, 1.0))
_wall("CorridorA", Vector3(ORIGIN.x, HEIGHT * 0.5, ORIGIN.z - CORRIDOR_GAP * 0.5),
CORRIDOR_LENGTH, 0.0)
_wall("CorridorB", Vector3(ORIGIN.x, HEIGHT * 0.5, ORIGIN.z + CORRIDOR_GAP * 0.5),
CORRIDOR_LENGTH, 0.0)
_label("Corridor %.0f m gap" % CORRIDOR_GAP,
Vector3(ORIGIN.x, HEIGHT + 2.0, ORIGIN.z), Color(1.0, 0.9, 0.6))
_faceted(Vector3(ORIGIN.x, HEIGHT * 0.5, ORIGIN.z + 22.0))
_label("Faceted +/- %.0f deg" % FACET_DEG,
Vector3(ORIGIN.x, HEIGHT + 2.0, ORIGIN.z + 22.0), Color(1.0, 0.6, 0.6))
func _slab() -> void:
_body("Slab", Vector3(ORIGIN.x, -SLAB.y * 0.5, ORIGIN.z), Basis.IDENTITY, SLAB)
# No ramp: map_test's ground is 100 x 100 centred on the origin and this slab
# is 100 long centred at x = 100, so they meet exactly at x = 50 -- and both
# have their top face at y = 0. You walk straight on.
func _wall(nm: String, centre: Vector3, length: float, yaw_deg: float) -> void:
_body(nm, centre, Basis(Vector3.UP, deg_to_rad(yaw_deg)),
Vector3(length, HEIGHT, THICK))
## Segments share one plane and only their NORMAL alternates, so the wall does not
## drift away from the line -- the run stays straight and only the sweep's answer
## changes, which is the thing under test.
func _faceted(centre: Vector3) -> void:
var n: int = int(LENGTH / SEG)
for i in range(n):
var x: float = centre.x - LENGTH * 0.5 + (float(i) + 0.5) * SEG
var yaw: float = FACET_DEG if i % 2 == 0 else -FACET_DEG
_body("Facet%d" % i, Vector3(x, centre.y, centre.z),
Basis(Vector3.UP, deg_to_rad(yaw)),
Vector3(SEG * 1.1, HEIGHT, THICK))
func _body(nm: String, pos: Vector3, basis: Basis, size: Vector3) -> void:
var b := StaticBody3D.new()
b.name = nm
b.collision_layer = LAYER
b.transform = Transform3D(basis, pos)
var cs := CollisionShape3D.new()
var box := BoxShape3D.new()
box.size = size
cs.shape = box
b.add_child(cs)
var mi := MeshInstance3D.new()
var bm := BoxMesh.new()
bm.size = size
mi.mesh = bm
b.add_child(mi)
add_child(b)
func _label(text: String, pos: Vector3, col: Color) -> void:
var l := Label3D.new()
l.text = text
l.position = pos
l.billboard = BaseMaterial3D.BILLBOARD_ENABLED
l.font_size = 48
l.outline_size = 8
l.modulate = col
add_child(l)