OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / bare_slide_probe.gd

107 lines · gd ref:
extends Node3D
## MINIMAL REPRO. A bare CharacterBody3D on the same planar ramp, driven with a
## constant horizontal velocity and nothing else -- no controller, no animator, no
## foot IK. If this loses the floor, the bounce is Godot's grounded move on a
## trimesh and our movement model is innocent. If it does not, it is ours.
##
##   "$HYPER_TPS_GODOT" --headless res://scenes/test/bare_slide_probe.tscn

const SLOPES := [5.0, 15.0, 20.0, 50.0]
const FRAMES := 200
const SPEED := 8.0

var _cases: Array = []
var _idx := 0
var _f := 0
var _body: CharacterBody3D = null
var _ground: StaticBody3D = null
var _grounded := 0
var _rows: Array = []
var _done := false


func _ready() -> void:
	for s in SLOPES:
		_cases.append({"deg": s, "const_speed": true,  "snap": 0.2})
		_cases.append({"deg": s, "const_speed": false, "snap": 0.2})
	_build()


func _build() -> void:
	var c: Dictionary = _cases[_idx]
	var s: float = tan(deg_to_rad(float(c["deg"])))
	_ground = StaticBody3D.new()
	var cs := CollisionShape3D.new()
	var f := PackedVector3Array()
	var i := 0
	while i < 280:
		var j := 0
		while j < 20:
			var x0 := float(i) * 1.5 - 210.0
			var z0 := float(j) * 1.5 - 15.0
			var x1 := x0 + 1.5
			var z1 := z0 + 1.5
			f.push_back(Vector3(x0, x0 * s, z0)); f.push_back(Vector3(x1, x1 * s, z1)); f.push_back(Vector3(x0, x0 * s, z1))
			f.push_back(Vector3(x0, x0 * s, z0)); f.push_back(Vector3(x1, x1 * s, z0)); f.push_back(Vector3(x1, x1 * s, z1))
			j += 1
		i += 1
	var tri := ConcavePolygonShape3D.new()
	tri.set_faces(f)
	cs.shape = tri
	_ground.add_child(cs)
	add_child(_ground)

	_body = CharacterBody3D.new()
	_body.floor_snap_length = float(c["snap"])
	_body.floor_constant_speed = bool(c["const_speed"])
	_body.floor_max_angle = deg_to_rad(60.0)
	var shape := CollisionShape3D.new()
	var cap := CapsuleShape3D.new()
	cap.radius = 0.4
	cap.height = 1.8
	shape.shape = cap
	shape.transform = Transform3D(Basis.IDENTITY, Vector3(0.0, 0.79, 0.0))
	_body.add_child(shape)
	add_child(_body)
	_body.global_position = Vector3(0.0, 0.6, 0.0)
	_f = 0
	_grounded = 0


func _physics_process(_d: float) -> void:
	if _done or _body == null:
		return
	_f += 1
	# Exactly what the controller hands move_and_slide: flat and constant while
	# grounded (State.MOVEMENT), gravity while not (State.AIRBORNE).
	if _body.is_on_floor():
		_body.velocity = Vector3(-SPEED, 0.0, 0.0)
	else:
		_body.velocity.x = -SPEED
		_body.velocity.z = 0.0
		_body.velocity.y -= 9.8 * _d
	_body.move_and_slide()
	if _f > 40:
		if _body.is_on_floor():
			_grounded += 1
	if _f < FRAMES:
		return
	var c: Dictionary = _cases[_idx]
	_rows.append({"deg": c["deg"], "cs": c["const_speed"],
			"pct": 100.0 * float(_grounded) / float(FRAMES - 40)})
	_body.queue_free(); _body = null
	_ground.queue_free(); _ground = null
	_idx += 1
	if _idx >= _cases.size():
		print("")
		print("slope   floor_constant_speed   grounded")
		print("---------------------------------------")
		for r in _rows:
			print("%5.0f   %-20s   %6.1f %%" % [r["deg"], str(r["cs"]), r["pct"]])
		print("")
		print("A bare body, constant horizontal velocity, nothing else.")
		_done = true
		get_tree().quit(0)
		return
	_build()