Bigger houses (Gitea #7): a second row of roof on a wider house, a Roof Ridge for a 6 m-wide one, and a Half Wall and Ridge Gable to close their taller ends; also no error when a piece is dismantled straight after it's placed

This commit is contained in:
Paul Bailey
2026-11-08 19:56:16 +00:00
parent 415e673b28
commit 31f8e0d22c
10 changed files with 363 additions and 5 deletions

View File

@@ -61,6 +61,15 @@ static func _define() -> Dictionary:
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0)],
"center": Vector3(0.33, 0.33, 0),
}
# Half wall: 2 m wide, 1 m tall (0.6.2, Gitea #7). On a wall's top, under
# a gable, it closes the end of a taller roof (a wider house, with a
# second row of roof panels); on a floor, it's a low wall or a railing.
k["half_wall"] = {
"shapes": [_box(Vector3(0, 0.5, 0), Vector3(2, 1, 0.2))],
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0),
Vector3(-1, 1, 0), Vector3(0, 1, 0), Vector3(1, 1, 0)],
"center": Vector3(0, 0.5, 0),
}
# --- P2 (docs/PROGRESSION_DESIGN.md §3) ---
# Pillar: a 0.4 m post standing on a floor corner or edge, carrying what's
# on top (a floor's corner meets its top).
@@ -95,6 +104,29 @@ static func _define() -> Dictionary:
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0)],
"center": Vector3(0, 1.5, 0),
}
# Roof ridge (0.6.2, Gitea #7): a 2 m cap, an upside-down V at the roof's
# slope, for houses whose width leaves a 2 m gap between the two sides'
# top rows (6 m, 10 m). Origin = centre at the level of the rows' top
# edges (local z = +-1), rising 0.5 m to the peak along z = 0.
var ns := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE))
var nn := Vector3(0, cos(ROOF_ANGLE), -sin(ROOF_ANGLE))
var half := sqrt(1.25)
k["roof_ridge"] = {
"shapes": [
{"type": "box", "center": Vector3(0, 0.25, 0.5) + ns * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, half), "basis": Basis(Vector3.RIGHT, ROOF_ANGLE)},
{"type": "box", "center": Vector3(0, 0.25, -0.5) + nn * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, half), "basis": Basis(Vector3.RIGHT, -ROOF_ANGLE)},
_box(Vector3(0, 0.55, 0), Vector3(2, 0.08, 0.3))], # (the ridge board: no gap at the peak for rain, or the shelter test)
"snaps": [Vector3(-1, 0, 1), Vector3(0, 0, 1), Vector3(1, 0, 1),
Vector3(-1, 0, -1), Vector3(0, 0, -1), Vector3(1, 0, -1)],
"center": Vector3(0, 0.25, 0),
}
# Ridge gable: the small triangle (2 m wide, 0.5 m tall, peak in the
# middle) that closes the end under a roof ridge, on a half wall or a wall.
k["ridge_gable"] = {
"shapes": [_prism([Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0.5)], 0.2)],
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 0.5, 0)],
"center": Vector3(0, 0.17, 0),
}
var n := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE)) # panel normal (up and out)
# (Ends exactly at the ridge. The seam where two roofs meet is handled by
# build_system.roofed_at sampling several rays.)

View File

@@ -69,6 +69,9 @@ static func build(key: String) -> ArrayMesh:
"wood_door_panel": m = _door_panel()
"wood_roof": m = _roof()
"wood_gable": m = _gable()
"wood_half_wall": m = _half_wall()
"wood_roof_ridge": m = _roof_ridge()
"wood_ridge_gable": m = _ridge_gable()
"bed": m = _bed()
"raw_meat": m = _meat(MEAT_RAW)
"cooked_meat": m = _meat(MEAT_COOKED)
@@ -546,6 +549,17 @@ static func _wall() -> ArrayMesh:
_box(st, Vector3(0, y, 0), Vector3(1.68, 0.12, 0.2), BEAM)
return P._commit(st)
## Half wall: the wall's boards and frame, 2 x 1 m.
static func _half_wall() -> ArrayMesh:
var st := P._begin()
var r := _rng(75)
_wall_body(st, r, -0.84, 0.84, 0.1, 0.9)
for x in [-0.92, 0.92]:
_box(st, Vector3(x, 0.5, 0), Vector3(0.16, 1.0, 0.2), BEAM)
for y in [0.06, 0.94]:
_box(st, Vector3(0, y, 0), Vector3(1.68, 0.12, 0.2), BEAM)
return P._commit(st)
## Door frame (1.1 x 1.9 m opening); `with_panel` adds the closed door, for the
## inventory icon and the placement ghost.
static func _door(with_panel: bool) -> ArrayMesh:
@@ -597,6 +611,37 @@ static func _roof() -> ArrayMesh:
_box(st, along * s + n * 0.085, Vector3(2.04, 0.05, (length + 0.3) / rows + 0.1), _board(r, SHINGLE), tilt)
return P._commit(st)
## Roof ridge: two short shingled panels meeting at a peak 0.5 m up (an
## upside-down V across local Z), origin at the centre of the low edges'
## level. Bridges the gap between two roof rows on a 6 m-wide house.
static func _roof_ridge() -> ArrayMesh:
var st := P._begin()
var r := _rng(91)
var a := 0.4636476
var half := sqrt(1.25)
for side in [1.0, -1.0]:
var b := Basis(Vector3.RIGHT, a * side)
var n := Vector3(0, cos(a), sin(a) * side)
var mid := Vector3(0, 0.25, 0.5 * side)
_box(st, mid + n * 0.03, Vector3(2.0, 0.06, half + 0.06), _board(r, BEAM), b)
for i in range(2):
var along := Vector3(0, -sin(a), cos(a) * side) * (half * (0.25 - 0.5 * i))
_box(st, mid + along + n * 0.085, Vector3(2.04, 0.05, half * 0.5 + 0.1), _board(r, SHINGLE), Basis(Vector3.RIGHT, (a + 0.06) * side))
_box(st, Vector3(0, 0.55, 0), Vector3(2.06, 0.08, 0.16), BEAM) # (the ridge board)
return P._commit(st)
## Ridge gable: a small triangle, 2 m wide and 0.5 m tall, peak in the middle.
static func _ridge_gable() -> ArrayMesh:
var st := P._begin()
var r := _rng(98)
for i in range(2):
var y0 := 0.25 * i
var hw := 0.92 * (1.0 - y0 / 0.5) - 0.06
_box(st, Vector3(0, y0 + 0.125, 0), Vector3(hw * 2.0, 0.25 - 0.015, 0.12), _board(r))
_box(st, Vector3(0, 0.06, 0), Vector3(2.0, 0.12, 0.2), BEAM)
_box(st, Vector3(0, 0.25, 0), Vector3(0.14, 0.5, 0.2), BEAM)
return P._commit(st)
## Triangular gable end: 0 m tall at -X, 1 m tall at +X.
static func _gable() -> ArrayMesh:
var st := P._begin()

View File

@@ -673,7 +673,7 @@ func _wall_hit(space: PhysicsDirectSpaceState3D, a: Vector3, b: Vector3) -> bool
var piece: Node = c as Node
while piece != null and piece.get("kind") == null:
piece = piece.get_parent()
if piece != null and String(piece.kind) in ["wall", "door", "gable", "window", "palisade", "pillar"]:
if piece != null and String(piece.kind) in ["wall", "half_wall", "door", "gable", "ridge_gable", "window", "palisade", "pillar"]:
return true
ex.append(hit.rid) # a floor or roof edge: look past it
return false

View File

@@ -173,8 +173,8 @@ func clear_under(piece: Node3D) -> void:
if not is_instance_valid(piece) or not is_inside_tree():
return
await get_tree().physics_frame # (the piece's collider is in the world from the next step)
if not is_instance_valid(piece):
return
if not is_instance_valid(piece) or not piece.is_inside_tree():
return # (dismantled straight away)
var space := get_world_3d().direct_space_state
var at := piece.global_position
for pk in pickups.values():