OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / maps / shapes / param / param_tunnel.gd

143 lines · gd ref:
extends "res://scenes/maps/shapes/param/param_shape.gd"
## TUNNEL. Local: x = along the tunnel, y = up, z = across; size = the OUTER box
## (length, outer height, outer width). The lining is `wall` thick; the bore is the
## box minus the wall at the sides and the top.
##
## Profiles:
##   round   — a half ellipse (springing at `spring`), straight side walls below it
##   pointed — a two-centred (gothic) arch, scaled to the height
##   flat    — two walls and a roof slab
##   circle  — a full ring (no floor)
## `facets` = how many voussoirs make the curved part (the full ring for circle).
## The inner and outer curves use the SAME parametrisation, so voussoir j is the quad
## between the same parameter on both: radial, convex, nothing to clip.

const AXIAL_MARGIN_M := 1.0      # the clearance bore runs past each mouth (ArchShape)


func id() -> String:
	return "tunnel"


func label() -> String:
	return "Tunnel"


func params() -> Array:
	return [
		{"id": "profile", "label": "Arch profile", "type": "enum", "default": "round",
			"options": ["round", "pointed", "flat", "circle"]},
		{"id": "facets", "label": "Facets", "type": "int", "default": 8, "min": 2, "max": 64,
			"step": 1, "tip": "Voussoirs in the curved part (the whole ring for circle)."},
		{"id": "wall", "label": "Wall width", "type": "float", "default": 0.5, "min": 0.05,
			"max": 5.0, "step": 0.05, "unit": "m"},
		{"id": "spring", "label": "Wall height", "type": "float", "default": 0.0, "min": 0.0,
			"max": 50.0, "step": 0.1, "unit": "m", "tip": "Straight walls before the arch starts."},
		{"id": "floor", "label": "Floor", "type": "bool", "default": false},
		{"id": "clearance", "label": "Cut terrain", "type": "bool", "default": true},
	]


## The curved part's points, (z, y), for one of the two curves. `w` = the lining
## thickness to subtract (0 = the outer curve, wall = the inner).
func _curve(size: Vector3, p: Dictionary, w: float) -> Array:
	var W := size.z
	var H := size.y
	var spring: float = minf(float(p["spring"]), H * 0.9)
	var facets: int = p["facets"]
	var pts: Array = []
	match String(p["profile"]):
		"round":
			var rz := W * 0.5 - w
			var ry := H - w - spring
			for j in facets + 1:
				var t := PI * float(j) / float(facets)
				pts.append(Vector2(W * 0.5 + rz * cos(t), spring + ry * sin(t)))
		"pointed":
			# Two arcs of radius = the span, each centred on the opposite springing
			# point; apex height span*sqrt(3)/2, then scaled to the box.
			var zl := w
			var zr := W - w
			var span := zr - zl
			var apex := span * sqrt(3.0) * 0.5
			var scale := (H - w - spring) / maxf(apex, 1.0e-6)
			var half := maxi(1, facets / 2)
			# Left arc, centred on the RIGHT springing point: angle pi (the left
			# springing point) up to 2pi/3 (the apex).
			for j in half + 1:
				var th := PI - (PI / 3.0) * float(j) / float(half)
				pts.append(Vector2(zr + span * cos(th), spring + span * sin(th) * scale))
			# Right arc, centred on the LEFT springing point: angle pi/3 (the apex,
			# already added) down to 0 (the right springing point).
			for j in range(1, half + 1):
				var ph := (PI / 3.0) * (1.0 - float(j) / float(half))
				pts.append(Vector2(zl + span * cos(ph), spring + span * sin(ph) * scale))
		"circle":
			var cz := W * 0.5
			var cy := H * 0.5
			for j in facets + 1:
				var t2 := TAU * float(j) / float(facets)
				pts.append(Vector2(cz + (W * 0.5 - w) * cos(t2), cy + (H * 0.5 - w) * sin(t2)))
	return pts


func _xyz(p2: Vector2, x: float) -> Vector3:
	return Vector3(x, p2.y, p2.x)


func local_pieces(size: Vector3, p: Dictionary) -> Array:
	var L := size.x
	var H := size.y
	var W := size.z
	var wall: float = minf(float(p["wall"]), minf(W, H) * 0.45)
	var prof := String(p["profile"])
	var out: Array = []
	if prof == "flat":
		out.append(tagged(box(Vector3(0, 0, 0), Vector3(L, H, wall)), "wall"))
		out.append(tagged(box(Vector3(0, 0, W - wall), Vector3(L, H, W)), "wall"))
		out.append(tagged(box(Vector3(0, H - wall, wall), Vector3(L, H, W - wall)), "roof"))
	else:
		var outer := _curve(size, p, 0.0)
		var inner := _curve(size, p, wall)
		for j in outer.size() - 1:
			var v := [_xyz(outer[j], 0.0), _xyz(outer[j], L), _xyz(outer[j + 1], 0.0), _xyz(outer[j + 1], L),
				_xyz(inner[j], 0.0), _xyz(inner[j], L), _xyz(inner[j + 1], 0.0), _xyz(inner[j + 1], L)]
			var piece: Dictionary = ShapeKit.hex_piece(v)
			if not piece.is_empty():
				out.append(tagged(piece, "voussoir %d" % (j + 1)))
		var spring: float = minf(float(p["spring"]), H * 0.9)
		if prof != "circle" and spring > 0.0:
			out.append(tagged(box(Vector3(0, 0, 0), Vector3(L, spring, wall)), "wall"))
			out.append(tagged(box(Vector3(0, 0, W - wall), Vector3(L, spring, W)), "wall"))
	if bool(p["floor"]) and prof != "circle":
		out.append(tagged(box(Vector3(0, 0, wall), Vector3(L, wall, W - wall)), "floor"))
	return out


## The OUTER envelope extended past each mouth (ArchShape's rule: the terrain meets
## the outside of the lining; the axial margin is what makes the bore break through).
func local_clearance(size: Vector3, p: Dictionary) -> Array:
	if not bool(p["clearance"]):
		return []
	var L := size.x
	var prof := String(p["profile"])
	var m := AXIAL_MARGIN_M
	if prof == "flat":
		return [box(Vector3(-m, 0, 0), Vector3(L + m, size.y, size.z))]
	var profile: Array[Vector2] = []
	var spring: float = minf(float(p["spring"]), size.y * 0.9)
	var curve := _curve(size, p, 0.0)
	if prof == "circle":
		for j in curve.size() - 1:
			profile.append(curve[j])
	else:
		if spring > 0.0:
			profile.append(Vector2(size.z, 0.0))
		for q: Vector2 in curve:
			profile.append(q)
		if spring > 0.0:
			profile.append(Vector2(0.0, 0.0))
	# (z, y) profile, extruded along x: span = z (2), vert = y (1), tunnel = x (0).
	var pc: Dictionary = ShapeKit.prism_piece(profile, 2, 1, 0, -m, L + m)
	return [] if pc.is_empty() else [pc]