extends RefCounted ## Procedural meshes for items and placeable pieces, keyed by the "mesh" field ## in data/items.json. The same mesh is used for the inventory icon, the tool ## held in your hand and the object placed in the world. ## ## Convention: origin at the grip (tools) or at ground level, centred ## (placeables). Units are metres. Built from the low-poly helpers in ## proc_mesh.gd, so everything is flat-shaded with vertex colours. const P = preload("res://scripts/world/proc_mesh.gd") const HANDLE := Color(0.55, 0.40, 0.24) const STONE := Color(0.55, 0.54, 0.52) const FLINT := Color(0.22, 0.23, 0.27) const PLANK := Color(0.62, 0.45, 0.27) const PLANK_DARK := Color(0.42, 0.29, 0.17) const IRON := Color(0.30, 0.30, 0.32) const ASH := Color(0.18, 0.17, 0.16) const BINDING := Color(0.75, 0.68, 0.48) const FIBER := Color(0.45, 0.55, 0.22) const FIBER_PLANT := Color(0.68, 0.70, 0.36) ## paler than the grass, so it shows const FIBER_SEED := Color(0.80, 0.68, 0.40) const BERRY := Color(0.62, 0.08, 0.16) const MUSH_CAP := Color(0.72, 0.30, 0.18) const MUSH_ROAST := Color(0.40, 0.24, 0.12) const MUSH_STEM := Color(0.88, 0.84, 0.74) const CLOTH := Color(0.60, 0.50, 0.32) const SOIL := Color(0.30, 0.22, 0.15) static var _cache := {} static func build(key: String) -> ArrayMesh: if _cache.has(key): return _cache[key] var m: ArrayMesh match key: "stick": m = _stick() "stone": m = _pebble(11, STONE, Vector3(0.14, 0.10, 0.12)) "flint": m = _pebble(23, FLINT, Vector3(0.12, 0.07, 0.15), 0.5) "wood": m = _wood() "stone_axe": m = _axe() "stone_pickaxe": m = _pickaxe() "club": m = _club() "campfire": m = _campfire() "workbench": m = _workbench() "chest": m = _chest() "fiber": m = _fiber() "berries": m = _berries() "mushroom": m = _mushroom(MUSH_CAP) "roasted_mushroom": m = _mushroom(MUSH_ROAST) "torch": m = _torch() "grave": m = _grave() "wood_floor": m = _floor() "wood_wall": m = _wall() "wood_door": m = _door(true) "wood_door_frame": m = _door(false) "wood_door_panel": m = _door_panel() "wood_roof": m = _roof() "wood_gable": m = _gable() "bed": m = _bed() "raw_meat": m = _meat(MEAT_RAW) "cooked_meat": m = _meat(MEAT_COOKED) "hide": m = _hide() "feather": m = _feather() "spear": m = _spear() "bow": m = _bow() "arrow": m = _arrow() "hide_cloak": m = _cloak() "wolf_pelt": m = _pelt(WOLF_FUR) "wolf_cloak": m = _cloak(WOLF_FUR) "fishing_rod": m = _rod() "bobber": m = _bobber() "perch": m = _fish(PERCH, 0.16, false) "trout": m = _fish(TROUT, 0.24, false) "grilled_perch": m = _fish(PERCH, 0.16, true) "grilled_trout": m = _fish(TROUT, 0.24, true) "mackerel": m = _fish(MACKEREL, 0.2, false) "cod": m = _fish(COD, 0.3, false) "grilled_mackerel": m = _fish(MACKEREL, 0.2, true) "grilled_cod": m = _fish(COD, 0.3, true) "fiber_plant": m = _fiber_plant() "iron_nugget": m = _pebble(61, RUST, Vector3(0.07, 0.05, 0.06), 0.6) "iron_ore": m = _ore() "clay": m = _pebble(67, CLAY, Vector3(0.12, 0.07, 0.1), 0.12) "charcoal": m = _charcoal() "iron_bar": m = _iron_bar() "nails": m = _nails() "iron_axe": m = _iron_axe() "iron_pickaxe": m = _iron_pickaxe() "iron_spear": m = _iron_spear() "iron_arrow": m = _arrow(IRON_BRIGHT) "lantern": m = _lantern() "charcoal_kiln": m = _kiln() "smelter": m = _smelter() "forge": m = _forge() "cut_stone": m = _cut_stone() "stonecutter": m = _stonecutter() "stone_floor": m = _stone_floor() "stone_wall": m = _stone_wall() "stone_pillar": m = _stone_pillar() "stone_stairs": m = _stairs(true) "stone_window": m = _stone_window() "stone_doorway": m = _stone_doorway(true) "stone_doorway_frame": m = _stone_doorway(false) "stone_hearth": m = _hearth() "wood_stairs": m = _stairs(false) "wood_window": m = _wood_window() "wood_pillar": m = _wood_pillar() "palisade": m = _palisade() "palisade_gate": m = _palisade_gate(true) "palisade_gate_frame": m = _palisade_gate(false) "iron_door": m = _iron_door(true) "iron_door_frame": m = _door(false) "iron_door_panel": m = _iron_door(false) "alpha_pelt": m = _pelt(ALPHA_FUR) "alpha_cloak": m = _cloak(ALPHA_FUR) "wolf_fang": m = _fang() "bear_hide": m = _pelt(BEAR_FUR) "bear_fat": m = _pebble(271, Color(0.92, 0.86, 0.68), Vector3(0.1, 0.06, 0.08), 0.1) "fur_rug": m = _rug() "pelt_rug": m = _rug(WOLF_FUR, 0.85) "bear_cloak": m = _cloak(BEAR_FUR) "clay_jar": m = _jar() "tallow_candle": m = _candle() "torch_stand": m = _torch_stand() "bedroll": m = _bedroll() "fang_necklace": m = _necklace() "pemmican": m = _pebble(331, Color(0.42, 0.24, 0.16), Vector3(0.1, 0.05, 0.07), 0.15) "barrow_key": m = _barrow_key() "rotten_food": m = _pebble(321, Color(0.33, 0.3, 0.16), Vector3(0.11, 0.06, 0.1), 0.45) "salt": m = _salt() "smoking_rack": m = _smoking_rack() "stag_antler": m = _antler() "stag_trophy": m = _stag_trophy() "antler_pickaxe": m = _antler_pickaxe() "smoked_meat": m = _meat(Color(0.36, 0.17, 0.1)) "salted_meat": m = _meat(Color(0.68, 0.42, 0.38)) "smoked_fish": m = _fish(Color(0.72, 0.5, 0.22), 0.2, true) "salted_fish": m = _fish(Color(0.75, 0.73, 0.66), 0.2, false) "berry_bush": m = _berry_bush(true) "berry_bush_bare": m = _berry_bush(false) _: push_warning("item_meshes: no mesh for '%s', using a stone" % key) m = _pebble(1, Color.MAGENTA, Vector3.ONE * 0.15) _cache[key] = m return m static func _rng(seed: int) -> RandomNumberGenerator: var r := RandomNumberGenerator.new() r.seed = seed return r static func _box(st: SurfaceTool, center: Vector3, size: Vector3, col: Color, basis: Basis = Basis()) -> void: var h := size * 0.5 var c := [ Vector3(-h.x, -h.y, -h.z), Vector3(h.x, -h.y, -h.z), Vector3(h.x, h.y, -h.z), Vector3(-h.x, h.y, -h.z), Vector3(-h.x, -h.y, h.z), Vector3(h.x, -h.y, h.z), Vector3(h.x, h.y, h.z), Vector3(-h.x, h.y, h.z), ] for i in range(8): c[i] = center + basis * c[i] var faces := [[0, 1, 2, 3], [5, 4, 7, 6], [4, 0, 3, 7], [1, 5, 6, 2], [3, 2, 6, 7], [4, 5, 1, 0]] for f in faces: var a: Vector3 = c[f[0]] var b: Vector3 = c[f[1]] var cc: Vector3 = c[f[2]] var d: Vector3 = c[f[3]] var out := (a + b + cc + d) * 0.25 - center P._tri(st, a, b, cc, out, col) P._tri(st, a, cc, d, out, col) static func _pebble(seed: int, col: Color, radii: Vector3, sharp: float = 0.3) -> ArrayMesh: var n := FastNoiseLite.new() n.seed = seed n.frequency = 1.4 var st := P._begin() P._blob(st, Vector3(0, radii.y * 0.6, 0), radii, n, sharp, col, _rng(seed), 1) return P._commit(st) static func _stick() -> ArrayMesh: var st := P._begin() var r := _rng(5) P._cylinder(st, Vector3(-0.35, 0.03, 0), Vector3(0.35, 0.03, 0.02), 0.028, 0.02, 5, HANDLE, r, -1.0, PLANK) P._cylinder(st, Vector3(0.05, 0.03, 0.0), Vector3(0.2, 0.05, 0.1), 0.012, 0.006, 4, HANDLE, r) return P._commit(st) static func _wood() -> ArrayMesh: var st := P._begin() var r := _rng(9) P._cylinder(st, Vector3(-0.3, 0.1, -0.06), Vector3(0.3, 0.1, -0.06), 0.1, 0.1, 6, P.BARK, r, -1.0, PLANK) P._cylinder(st, Vector3(-0.28, 0.1, 0.13), Vector3(0.3, 0.1, 0.13), 0.09, 0.09, 6, P.BARK, r, -1.0, PLANK) P._cylinder(st, Vector3(-0.29, 0.27, 0.04), Vector3(0.29, 0.27, 0.04), 0.09, 0.09, 6, P.BARK, r, -1.0, PLANK) return P._commit(st) ## Handle along +Y from the grip at the origin. static func _handle(st: SurfaceTool, length: float, r: RandomNumberGenerator) -> void: P._cylinder(st, Vector3(0, -0.1, 0), Vector3(0, length, 0), 0.022, 0.02, 6, HANDLE, r, -1.0, HANDLE) static func _axe() -> ArrayMesh: var st := P._begin() var r := _rng(13) _handle(st, 0.55, r) _box(st, Vector3(0, 0.47, 0), Vector3(0.06, 0.07, 0.07), BINDING) var n := FastNoiseLite.new() n.seed = 3 n.frequency = 2.0 # Flint head: a flattened wedge pointing along +X. P._blob(st, Vector3(0.075, 0.47, 0), Vector3(0.085, 0.055, 0.025), n, 0.2, FLINT, r, 1) return P._commit(st) static func _pickaxe() -> ArrayMesh: var st := P._begin() var r := _rng(17) _handle(st, 0.6, r) _box(st, Vector3(0, 0.55, 0), Vector3(0.06, 0.07, 0.07), BINDING) P._cylinder(st, Vector3(-0.02, 0.56, 0), Vector3(0.22, 0.5, 0), 0.04, 0.008, 5, STONE, r, -1.0, STONE) P._cylinder(st, Vector3(0.02, 0.56, 0), Vector3(-0.2, 0.51, 0), 0.04, 0.008, 5, STONE, r, -1.0, STONE) return P._commit(st) static func _club() -> ArrayMesh: var st := P._begin() var r := _rng(21) P._cylinder(st, Vector3(0, -0.1, 0), Vector3(0, 0.62, 0), 0.025, 0.07, 7, PLANK_DARK, r, -1.0, PLANK) return P._commit(st) static func _campfire() -> ArrayMesh: var st := P._begin() var r := _rng(31) var n := FastNoiseLite.new() n.seed = 31 n.frequency = 1.6 for i in range(9): var a := TAU * i / 9.0 var c := Vector3(cos(a) * 0.55, 0.06, sin(a) * 0.55) P._blob(st, c, Vector3(0.15, 0.11, 0.13) * r.randf_range(0.85, 1.15), n, 0.3, P._mul(STONE, r.randf_range(0.8, 1.05)), r, 1) # Teepee of logs leaning into the middle. for i in range(5): var a := TAU * i / 5.0 + 0.3 var base := Vector3(cos(a) * 0.36, 0.0, sin(a) * 0.36) P._cylinder(st, base, Vector3(0, 0.55, 0), 0.05, 0.035, 5, P.BARK, r, -1.0, PLANK) P._blob(st, Vector3(0, 0.03, 0), Vector3(0.3, 0.04, 0.3), n, 0.2, ASH, r, 1) return P._commit(st) static func _workbench() -> ArrayMesh: var st := P._begin() _box(st, Vector3(0, 0.82, 0), Vector3(1.8, 0.1, 0.9), PLANK) _box(st, Vector3(0, 0.79, 0.47), Vector3(1.82, 0.06, 0.04), PLANK_DARK) for sx in [-0.8, 0.8]: for sz in [-0.36, 0.36]: _box(st, Vector3(sx, 0.38, sz), Vector3(0.1, 0.8, 0.1), PLANK_DARK) _box(st, Vector3(0, 0.25, 0), Vector3(1.6, 0.06, 0.7), PLANK_DARK) # A few tools lying on top. _box(st, Vector3(0.45, 0.89, 0.1), Vector3(0.35, 0.03, 0.05), HANDLE, Basis(Vector3.UP, 0.4)) _box(st, Vector3(0.6, 0.9, 0.18), Vector3(0.09, 0.06, 0.09), FLINT) _box(st, Vector3(-0.5, 0.92, -0.15), Vector3(0.4, 0.1, 0.22), P.BARK) return P._commit(st) static func _chest() -> ArrayMesh: var st := P._begin() _box(st, Vector3(0, 0.3, 0), Vector3(1.1, 0.6, 0.7), PLANK) _box(st, Vector3(0, 0.66, 0), Vector3(1.14, 0.14, 0.74), PLANK_DARK) for sx in [-0.4, 0.4]: _box(st, Vector3(sx, 0.36, 0), Vector3(0.07, 0.74, 0.76), IRON) _box(st, Vector3(0, 0.55, 0.37), Vector3(0.12, 0.14, 0.03), IRON) return P._commit(st) static func _fiber() -> ArrayMesh: var st := P._begin() var r := _rng(41) for i in range(7): var a := r.randf_range(-0.25, 0.25) var b := Vector3(r.randf_range(-0.03, 0.03), 0.02, r.randf_range(-0.03, 0.03)) P._cylinder(st, b + Vector3(-0.22, 0, 0), b + Vector3(0.22, 0.03 + a * 0.1, a), 0.008, 0.005, 3, P._mul(FIBER, r.randf_range(0.8, 1.15)), r) _box(st, Vector3(0, 0.03, 0), Vector3(0.05, 0.05, 0.08), CLOTH) return P._commit(st) ## A wild flax clump you pick for fibre: tall pale stalks fanning out of the ## grass, each with a seed head, so it stands out above the meadow. static func _fiber_plant() -> ArrayMesh: var st := P._begin() var r := _rng(53) for i in range(20): var a := TAU * i / 20.0 + r.randf_range(-0.2, 0.2) var lean := r.randf_range(0.12, 0.4) var h := r.randf_range(1.0, 1.45) var base := Vector3(cos(a), 0, sin(a)) * r.randf_range(0.03, 0.12) var top := base + Vector3(cos(a) * lean, h, sin(a) * lean) var col := P._mul(FIBER_PLANT, r.randf_range(0.85, 1.15)) P._cylinder(st, base, top, 0.022, 0.009, 4, col, r) P._cylinder(st, top, top + (top - base).normalized() * 0.14, 0.032, 0.006, 4, P._mul(FIBER_SEED, r.randf_range(0.9, 1.1)), r) return P._commit(st) static func _berries() -> ArrayMesh: var st := P._begin() var r := _rng(43) var n := FastNoiseLite.new() n.seed = 43 for i in range(9): var c := Vector3(r.randf_range(-0.07, 0.07), 0.03 + r.randf_range(0.0, 0.05), r.randf_range(-0.07, 0.07)) P._blob(st, c, Vector3.ONE * r.randf_range(0.025, 0.035), n, 0.1, P._mul(BERRY, r.randf_range(0.8, 1.2)), r, 0) P._blob(st, Vector3(0, 0.08, 0), Vector3(0.05, 0.012, 0.03), n, 0.2, P.BUSH, r, 0) return P._commit(st) static func _mushroom(cap: Color) -> ArrayMesh: var st := P._begin() var r := _rng(47) var n := FastNoiseLite.new() n.seed = 47 P._cylinder(st, Vector3(0, -0.02, 0), Vector3(0, 0.1, 0), 0.025, 0.02, 6, MUSH_STEM, r, -1.0, MUSH_STEM) P._blob(st, Vector3(0, 0.11, 0), Vector3(0.075, 0.04, 0.075), n, 0.15, cap, r, 1) return P._commit(st) static func _torch() -> ArrayMesh: var st := P._begin() var r := _rng(53) _handle(st, 0.5, r) P._cylinder(st, Vector3(0, 0.42, 0), Vector3(0, 0.58, 0), 0.045, 0.05, 6, CLOTH, r, -1.0, ASH) return P._commit(st) static func _grave() -> ArrayMesh: var st := P._begin() var r := _rng(59) var n := FastNoiseLite.new() n.seed = 59 P._blob(st, Vector3(0, 0.0, 0), Vector3(0.5, 0.22, 0.8), n, 0.15, SOIL, r, 1) _box(st, Vector3(0, 0.55, -0.62), Vector3(0.08, 1.0, 0.06), PLANK_DARK) _box(st, Vector3(0, 0.8, -0.62), Vector3(0.5, 0.08, 0.06), PLANK_DARK) for i in range(4): var c := Vector3(r.randf_range(-0.35, 0.35), 0.15, r.randf_range(-0.6, 0.6)) P._blob(st, c, Vector3.ONE * r.randf_range(0.07, 0.12), n, 0.3, STONE, r, 0) return P._commit(st) ## World berry bush (not an item). `ripe` adds the berries. static func _berry_bush(ripe: bool) -> ArrayMesh: var st := P._begin() var r := _rng(61) var n := FastNoiseLite.new() n.seed = 61 n.frequency = 2.0 var leaf := P._mul(P.BUSH, 1.1) var blobs: Array[Vector3] = [] for i in range(4): var c := Vector3(r.randf_range(-0.4, 0.4), r.randf_range(0.3, 0.55), r.randf_range(-0.4, 0.4)) var rad := r.randf_range(0.35, 0.5) P._blob(st, c, Vector3(rad, rad * 0.8, rad), n, 0.3, P._mul(leaf, r.randf_range(0.85, 1.1)), r, 1) blobs.append(c + Vector3(0, rad * 0.4, 0)) if ripe: for c in blobs: for k in range(6): var d := Vector3(r.randf_range(-1, 1), r.randf_range(0.2, 1), r.randf_range(-1, 1)).normalized() * 0.4 P._blob(st, c + d, Vector3.ONE * 0.045, n, 0.1, BERRY, r, 0) return P._commit(st) # --- building pieces ---------------------------------------------------------- # Dimensions must match the colliders in scripts/building/build_piece.gd. const BEAM := Color(0.40, 0.27, 0.15) const SHINGLE := Color(0.46, 0.33, 0.20) const THATCH := Color(0.62, 0.52, 0.30) const FUR := Color(0.45, 0.30, 0.20) const LINEN := Color(0.80, 0.76, 0.64) ## Per-board tint so planks don't read as one flat sheet. static func _board(r: RandomNumberGenerator, base: Color = PLANK) -> Color: return P._mul(base, r.randf_range(0.82, 1.08)) ## 2 x 2 m, top surface at y = 0, 0.2 m deep. static func _floor() -> ArrayMesh: var st := P._begin() var r := _rng(71) for i in range(5): _box(st, Vector3(-0.8 + 0.4 * i, -0.05, 0), Vector3(0.385, 0.1, 2.0), _board(r)) for z in [-0.85, 0.0, 0.85]: _box(st, Vector3(0, -0.15, z), Vector3(2.0, 0.1, 0.22), BEAM) return P._commit(st) ## Horizontal boards between two posts, 2 x 2 m, 0.2 m thick, bottom centre at origin. static func _wall_body(st: SurfaceTool, r: RandomNumberGenerator, x0: float, x1: float, y0: float, y1: float) -> void: var rows := maxi(roundi((y1 - y0) / 0.31), 1) var h := (y1 - y0) / rows for i in range(rows): var y := y0 + h * (i + 0.5) _box(st, Vector3((x0 + x1) * 0.5, y, 0), Vector3(x1 - x0, h - 0.015, 0.12), _board(r)) static func _wall() -> ArrayMesh: var st := P._begin() var r := _rng(73) _wall_body(st, r, -0.84, 0.84, 0.1, 1.9) for x in [-0.92, 0.92]: _box(st, Vector3(x, 1.0, 0), Vector3(0.16, 2.0, 0.2), BEAM) for y in [0.06, 1.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: var st := P._begin() var r := _rng(79) for side in [-1.0, 1.0]: _wall_body(st, r, side * 0.55 if side > 0 else -0.84, 0.84 if side > 0 else -0.55, 0.1, 1.9) _box(st, Vector3(side * 0.92, 1.0, 0), Vector3(0.16, 2.0, 0.2), BEAM) _box(st, Vector3(side * 0.6, 0.95, 0), Vector3(0.1, 1.9, 0.2), BEAM) # jamb _box(st, Vector3(0, 1.95, 0), Vector3(1.68, 0.1, 0.2), BEAM) _box(st, Vector3(-0.7, 0.06, 0), Vector3(0.28, 0.12, 0.2), BEAM) _box(st, Vector3(0.7, 0.06, 0), Vector3(0.28, 0.12, 0.2), BEAM) if with_panel: _door_panel_into(st, Vector3(-0.54, 0, 0)) return P._commit(st) ## The door leaf, hinge at the origin, spanning +X. static func _door_panel() -> ArrayMesh: var st := P._begin() _door_panel_into(st, Vector3.ZERO) return P._commit(st) static func _door_panel_into(st: SurfaceTool, o: Vector3) -> void: var r := _rng(83) for i in range(4): _box(st, o + Vector3(0.135 + 0.27 * i, 0.95, 0), Vector3(0.26, 1.86, 0.07), _board(r)) for y in [0.35, 1.55]: _box(st, o + Vector3(0.54, y, 0.05), Vector3(0.96, 0.12, 0.04), BEAM) _box(st, o + Vector3(0.54, 0.95, 0.05), Vector3(0.1, 1.2, 0.04), BEAM, Basis(Vector3.BACK, 0.62)) _box(st, o + Vector3(0.95, 1.0, 0.07), Vector3(0.05, 0.14, 0.05), IRON) _box(st, o + Vector3(0.95, 1.0, -0.07), Vector3(0.05, 0.14, 0.05), IRON) ## Sloped roof: low edge centre at the origin, rising 1 m over 2 m toward -Z, ## with a short eave hanging past the low edge. static func _roof() -> ArrayMesh: var st := P._begin() var r := _rng(89) var a := 0.4636476 var b := Basis(Vector3.RIGHT, a) var n := Vector3(0, cos(a), sin(a)) var along := Vector3(0, sin(a), -cos(a)) # up the slope var length := sqrt(5.0) # Boarded deck, then overlapping rows of shingles on top. _box(st, along * (length * 0.5 - 0.15) + n * 0.03, Vector3(2.0, 0.06, length + 0.3), _board(r, BEAM), b) var rows := 6 for i in range(rows): var s := -0.3 + (length + 0.3) * (i + 0.5) / rows var tilt := Basis(Vector3.RIGHT, a + 0.06) _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) ## Triangular gable end: 0 m tall at -X, 1 m tall at +X. static func _gable() -> ArrayMesh: var st := P._begin() var r := _rng(97) for i in range(3): var y0 := i / 3.0 var x0 := 2.0 * y0 - 1.0 + 0.1 _box(st, Vector3((x0 + 0.92) * 0.5, y0 + 1.0 / 6.0, 0), Vector3(0.92 - x0, 1.0 / 3.0 - 0.015, 0.12), _board(r)) _box(st, Vector3(0.92, 0.5, 0), Vector3(0.16, 1.0, 0.2), BEAM) _box(st, Vector3(0, 0.06, 0), Vector3(2.0, 0.12, 0.2), BEAM) return P._commit(st) ## Bed: 1 x 2.1 m frame with a straw mattress, pillow and fur blanket. static func _bed() -> ArrayMesh: var st := P._begin() var r := _rng(101) for sx in [-0.45, 0.45]: for sz in [-0.98, 0.98]: _box(st, Vector3(sx, 0.22, sz), Vector3(0.1, 0.44, 0.1), BEAM) _box(st, Vector3(sx, 0.28, 0), Vector3(0.08, 0.14, 1.9), _board(r)) for sz in [-0.98, 0.98]: _box(st, Vector3(0, 0.28, sz), Vector3(0.84, 0.14, 0.08), _board(r)) _box(st, Vector3(0, 0.62, -1.0), Vector3(0.96, 0.4, 0.08), _board(r)) # headboard _box(st, Vector3(0, 0.38, 0), Vector3(0.86, 0.14, 1.9), THATCH) _box(st, Vector3(0, 0.5, -0.72), Vector3(0.6, 0.1, 0.32), LINEN) _box(st, Vector3(0, 0.47, 0.25), Vector3(0.9, 0.08, 1.3), FUR) return P._commit(st) # --- hunting gear (M1d) ------------------------------------------------------- const MEAT_RAW := Color(0.72, 0.24, 0.24) const MEAT_COOKED := Color(0.48, 0.26, 0.12) const BONE := Color(0.90, 0.87, 0.78) const HIDE := Color(0.58, 0.42, 0.28) const FEATHER := Color(0.88, 0.86, 0.80) const FLETCH := Color(0.70, 0.22, 0.18) static func _meat(col: Color) -> ArrayMesh: var st := P._begin() var r := _rng(107) var n := FastNoiseLite.new() n.seed = 107 n.frequency = 2.2 P._blob(st, Vector3(0.03, 0.07, 0), Vector3(0.13, 0.07, 0.1), n, 0.25, col, r, 1) P._cylinder(st, Vector3(-0.08, 0.07, 0), Vector3(-0.2, 0.08, 0.01), 0.022, 0.018, 5, BONE, r, -1.0, BONE) P._blob(st, Vector3(-0.21, 0.08, 0.01), Vector3(0.03, 0.03, 0.035), n, 0.1, BONE, r, 0) return P._commit(st) static func _hide() -> ArrayMesh: var st := P._begin() var r := _rng(109) # A rough, folded pelt: a few overlapping flattened slabs. _box(st, Vector3(0, 0.02, 0), Vector3(0.36, 0.03, 0.26), HIDE, Basis(Vector3.UP, 0.2)) _box(st, Vector3(0.05, 0.05, 0.02), Vector3(0.28, 0.03, 0.2), P._mul(HIDE, 0.85), Basis(Vector3.UP, -0.3)) _box(st, Vector3(-0.04, 0.075, -0.02), Vector3(0.2, 0.025, 0.16), _board(r, HIDE), Basis(Vector3.UP, 0.6)) return P._commit(st) static func _feather() -> ArrayMesh: var st := P._begin() var r := _rng(113) P._cylinder(st, Vector3(-0.14, 0.01, 0), Vector3(0.14, 0.012, 0), 0.004, 0.002, 3, BONE, r) for side in [-1.0, 1.0]: var a := Vector3(-0.1, 0.01, 0) var b := Vector3(0.13, 0.012, 0) var c := Vector3(0.0, 0.01, side * 0.035) P._tri(st, a, b, c, Vector3.UP, FEATHER) P._tri(st, a, b, c, Vector3.DOWN, P._mul(FEATHER, 0.85)) return P._commit(st) ## Long shaft along +Y from the grip, fire-hardened point with a flint tip. static func _spear() -> ArrayMesh: var st := P._begin() var r := _rng(127) P._cylinder(st, Vector3(0, -0.55, 0), Vector3(0, 0.75, 0), 0.022, 0.02, 6, HANDLE, r, -1.0, HANDLE) _box(st, Vector3(0, 0.78, 0), Vector3(0.05, 0.06, 0.05), BINDING) P._cylinder(st, Vector3(0, 0.8, 0), Vector3(0, 0.98, 0), 0.035, 0.004, 4, FLINT, r) return P._commit(st) ## Bent stave along +Y with a string on the +Z side (held upright). static func _bow() -> ArrayMesh: var st := P._begin() var r := _rng(131) var pts: Array[Vector3] = [] for i in range(7): var t := -1.0 + 2.0 * i / 6.0 pts.append(Vector3(0, t * 0.55, -0.1 * (1.0 - t * t))) for i in range(6): P._cylinder(st, pts[i], pts[i + 1], 0.02 if absf(i - 2.5) < 1.5 else 0.015, 0.02 if absf(i - 2.5) < 1.5 else 0.013, 5, PLANK_DARK, r, 0.0) P._cylinder(st, pts[0], pts[6], 0.004, 0.004, 3, LINEN, r) _box(st, Vector3(0, 0, -0.1), Vector3(0.05, 0.12, 0.05), BINDING) return P._commit(st) ## Arrow along -Z (its flight direction), tip at the front. static func _arrow(tip: Color = FLINT) -> ArrayMesh: var st := P._begin() var r := _rng(137) P._cylinder(st, Vector3(0, 0, 0.35), Vector3(0, 0, -0.33), 0.008, 0.008, 4, HANDLE, r, 0.0) P._cylinder(st, Vector3(0, 0, -0.32), Vector3(0, 0, -0.4), 0.018, 0.002, 4, tip, r, 0.0) for a in [0.0, TAU / 3.0, TAU * 2.0 / 3.0]: var d := Vector3(cos(a), sin(a), 0) * 0.03 P._tri(st, Vector3(0, 0, 0.33), Vector3(0, 0, 0.2), d + Vector3(0, 0, 0.3), Vector3(-d.y, d.x, 0), FLETCH) P._tri(st, Vector3(0, 0, 0.33), Vector3(0, 0, 0.2), d + Vector3(0, 0, 0.3), Vector3(d.y, -d.x, 0), FLETCH) return P._commit(st) const WOLF_FUR := Color(0.5, 0.5, 0.52) # --- iron (P1) ------------------------------------------------------------------ const RUST := Color(0.5, 0.26, 0.14) const CLAY := Color(0.62, 0.6, 0.55) const CLAY_FIRED := Color(0.66, 0.42, 0.3) const CHARCOAL := Color(0.1, 0.09, 0.09) const IRON_BRIGHT := Color(0.55, 0.56, 0.6) static func _ore() -> ArrayMesh: var st := P._begin() var r := _rng(71) var n := FastNoiseLite.new() n.seed = 71 n.frequency = 1.4 P._blob(st, Vector3(0, 0.07, 0), Vector3(0.13, 0.09, 0.11), n, 0.35, STONE, r, 1) for i in range(4): var a := TAU * i / 4.0 + 0.4 P._blob(st, Vector3(cos(a) * 0.08, 0.1, sin(a) * 0.07), Vector3(0.05, 0.035, 0.04), n, 0.4, RUST, r, 0) return P._commit(st) static func _charcoal() -> ArrayMesh: var st := P._begin() var r := _rng(73) for i in range(4): var c := Vector3(r.randf_range(-0.07, 0.07), 0.03, r.randf_range(-0.06, 0.06)) _box(st, c, Vector3(0.1, 0.05, 0.06) * r.randf_range(0.8, 1.2), P._mul(CHARCOAL, r.randf_range(0.8, 1.6)), Basis(Vector3.UP, r.randf() * TAU)) return P._commit(st) static func _iron_bar() -> ArrayMesh: var st := P._begin() _box(st, Vector3(0, 0.04, 0), Vector3(0.3, 0.07, 0.1), IRON_BRIGHT) _box(st, Vector3(0, 0.085, 0), Vector3(0.26, 0.02, 0.07), P._mul(IRON_BRIGHT, 1.15)) return P._commit(st) static func _nails() -> ArrayMesh: var st := P._begin() var r := _rng(79) for i in range(6): var a := r.randf() * TAU var c := Vector3(r.randf_range(-0.06, 0.06), 0.012, r.randf_range(-0.06, 0.06)) var d := Vector3(cos(a), 0, sin(a)) * 0.07 P._cylinder(st, c - d, c + d, 0.006, 0.002, 4, IRON, r) P._cylinder(st, c - d, c - d * 0.85, 0.013, 0.013, 5, IRON, r) return P._commit(st) static func _iron_axe() -> ArrayMesh: var st := P._begin() var r := _rng(83) _handle(st, 0.58, r) _box(st, Vector3(0.03, 0.5, 0), Vector3(0.1, 0.08, 0.04), IRON) _box(st, Vector3(0.11, 0.5, 0), Vector3(0.07, 0.15, 0.025), IRON_BRIGHT) return P._commit(st) static func _iron_pickaxe() -> ArrayMesh: var st := P._begin() var r := _rng(89) _handle(st, 0.62, r) _box(st, Vector3(0, 0.57, 0), Vector3(0.06, 0.07, 0.06), IRON) P._cylinder(st, Vector3(-0.02, 0.58, 0), Vector3(0.25, 0.51, 0), 0.03, 0.004, 4, IRON_BRIGHT, r, -1.0, IRON) P._cylinder(st, Vector3(0.02, 0.58, 0), Vector3(-0.22, 0.52, 0), 0.03, 0.004, 4, IRON_BRIGHT, r, -1.0, IRON) return P._commit(st) static func _iron_spear() -> ArrayMesh: var st := P._begin() var r := _rng(97) P._cylinder(st, Vector3(0, -0.55, 0), Vector3(0, 0.75, 0), 0.022, 0.02, 6, HANDLE, r, -1.0, HANDLE) _box(st, Vector3(0, 0.77, 0), Vector3(0.04, 0.05, 0.04), IRON) P._cylinder(st, Vector3(0, 0.79, 0), Vector3(0, 1.02, 0), 0.03, 0.002, 4, IRON_BRIGHT, r) return P._commit(st) static func _lantern() -> ArrayMesh: var st := P._begin() var r := _rng(101) # Held by the ring at the top, the light hanging below the hand. _box(st, Vector3(0, 0.05, 0), Vector3(0.14, 0.02, 0.14), IRON) _box(st, Vector3(0, 0.3, 0), Vector3(0.16, 0.03, 0.16), IRON) for sx in [-0.065, 0.065]: for sz in [-0.065, 0.065]: _box(st, Vector3(sx, 0.175, sz), Vector3(0.015, 0.24, 0.015), IRON) _box(st, Vector3(0, 0.17, 0), Vector3(0.11, 0.2, 0.11), Color(1.0, 0.85, 0.5)) P._cylinder(st, Vector3(0, 0.315, 0), Vector3(0, 0.4, 0), 0.04, 0.01, 5, IRON, r) return P._commit(st) ## A low clay dome with a dark mouth at the front (+Z), and a smoke hole. static func _kiln() -> ArrayMesh: var st := P._begin() var r := _rng(103) var n := FastNoiseLite.new() n.seed = 103 n.frequency = 1.2 P._blob(st, Vector3(0, 0.45, 0), Vector3(0.75, 0.55, 0.75), n, 0.08, CLAY_FIRED, r, 1) _box(st, Vector3(0, 0.25, 0.68), Vector3(0.42, 0.34, 0.12), ASH) P._cylinder(st, Vector3(0, 0.95, 0), Vector3(0, 1.12, 0), 0.12, 0.1, 6, P._mul(CLAY_FIRED, 0.85), r, -1.0, ASH) for i in range(8): var a := TAU * i / 8.0 P._blob(st, Vector3(cos(a) * 0.78, 0.08, sin(a) * 0.78), Vector3(0.13, 0.1, 0.12), n, 0.3, STONE, r, 1) return P._commit(st) ## A stone base with a tall clay shaft, the hot mouth low at the front (+Z). static func _smelter() -> ArrayMesh: var st := P._begin() var r := _rng(107) var n := FastNoiseLite.new() n.seed = 107 n.frequency = 1.5 for i in range(10): var a := TAU * i / 10.0 P._blob(st, Vector3(cos(a) * 0.5, 0.12, sin(a) * 0.5), Vector3(0.16, 0.14, 0.15), n, 0.3, P._mul(STONE, r.randf_range(0.8, 1.05)), r, 1) P._cylinder(st, Vector3(0, 0.1, 0), Vector3(0, 1.7, 0), 0.5, 0.26, 8, CLAY_FIRED, r, -1.0, ASH) _box(st, Vector3(0, 0.3, 0.46), Vector3(0.36, 0.28, 0.14), ASH) _box(st, Vector3(0.38, 0.12, 0.42), Vector3(0.28, 0.06, 0.14), STONE, Basis(Vector3.UP, 0.5)) return P._commit(st) ## A stone hearth (left) and an anvil on a stump (right). static func _forge() -> ArrayMesh: var st := P._begin() var r := _rng(109) _box(st, Vector3(-0.55, 0.38, 0), Vector3(0.95, 0.76, 0.95), P._mul(STONE, 0.9)) _box(st, Vector3(-0.55, 0.8, 0), Vector3(0.75, 0.08, 0.75), ASH) _box(st, Vector3(-0.55, 0.86, 0.05), Vector3(0.4, 0.06, 0.4), Color(0.9, 0.35, 0.1)) P._cylinder(st, Vector3(0.55, 0.0, 0), Vector3(0.55, 0.55, 0), 0.24, 0.22, 7, P.BARK, r, -1.0, PLANK) _box(st, Vector3(0.55, 0.62, 0), Vector3(0.5, 0.14, 0.2), IRON) _box(st, Vector3(0.55, 0.72, 0), Vector3(0.62, 0.06, 0.24), IRON_BRIGHT) P._cylinder(st, Vector3(0.84, 0.72, 0), Vector3(1.0, 0.71, 0), 0.07, 0.01, 5, IRON_BRIGHT, r) _box(st, Vector3(0.45, 0.8, 0.0), Vector3(0.24, 0.04, 0.05), HANDLE, Basis(Vector3.UP, 0.6)) return P._commit(st) static func _pelt(col: Color) -> ArrayMesh: var st := P._begin() var r := _rng(149) _box(st, Vector3(0, 0.02, 0), Vector3(0.42, 0.035, 0.24), col, Basis(Vector3.UP, 0.15)) _box(st, Vector3(0.04, 0.05, 0.01), Vector3(0.3, 0.035, 0.18), _board(r, col), Basis(Vector3.UP, -0.25)) _box(st, Vector3(-0.2, 0.03, 0.05), Vector3(0.12, 0.03, 0.05), P._mul(col, 0.8), Basis(Vector3.UP, 0.7)) # tail return P._commit(st) static func _cloak(col: Color = HIDE) -> ArrayMesh: var st := P._begin() var r := _rng(139) # Folded cloak: a stack of fur with a bone clasp. for i in range(3): _box(st, Vector3(0, 0.03 + i * 0.045, 0), Vector3(0.42 - i * 0.05, 0.04, 0.3 - i * 0.03), _board(r, col), Basis(Vector3.UP, (i - 1) * 0.08)) _box(st, Vector3(0, 0.17, 0.0), Vector3(0.32, 0.05, 0.08), P._mul(FUR if col == HIDE else col, 1.1)) _box(st, Vector3(0.1, 0.2, 0.05), Vector3(0.05, 0.03, 0.03), BONE) return P._commit(st) # --- fishing ------------------------------------------------------------------ const PERCH := Color(0.45, 0.52, 0.30) const TROUT := Color(0.50, 0.55, 0.58) const MACKEREL := Color(0.22, 0.42, 0.48) const COD := Color(0.56, 0.52, 0.40) const GRILLED := Color(0.55, 0.36, 0.18) const FLOAT_RED := Color(0.85, 0.15, 0.12) ## Rod along +Y from the grip, a tip ring at the top. Rod tip is at ## (0, ROD_TIP, 0) (hands.gd draws the line from there). const ROD_TIP := 1.15 static func _rod() -> ArrayMesh: var st := P._begin() var r := _rng(151) P._cylinder(st, Vector3(0, -0.15, 0), Vector3(0, ROD_TIP, 0), 0.02, 0.006, 5, HANDLE, r, -1.0, HANDLE) _box(st, Vector3(0, 0.0, 0), Vector3(0.05, 0.16, 0.05), BINDING) # A simple wooden reel with line wound on it. P._cylinder(st, Vector3(-0.05, 0.12, 0.04), Vector3(0.05, 0.12, 0.04), 0.045, 0.045, 7, PLANK_DARK, r, 0.0, LINEN) return P._commit(st) static func _bobber() -> ArrayMesh: var st := P._begin() var r := _rng(157) var n := FastNoiseLite.new() # Big enough to spot 10 m out on the water. P._blob(st, Vector3(0, 0.03, 0), Vector3(0.08, 0.06, 0.08), n, 0.0, Color(0.95, 0.95, 0.92), r, 1) P._blob(st, Vector3(0, 0.09, 0), Vector3(0.07, 0.05, 0.07), n, 0.0, FLOAT_RED, r, 1) P._cylinder(st, Vector3(0, 0.11, 0), Vector3(0, 0.24, 0), 0.01, 0.007, 4, FLOAT_RED, r) return P._commit(st) ## A fish lying on its side along X, head at +X. `len` is half its length. static func _fish(col: Color, len: float, grilled: bool) -> ArrayMesh: var st := P._begin() var r := _rng(163 + int(len * 100.0)) var n := FastNoiseLite.new() n.seed = 163 var body := GRILLED.lerp(col, 0.25) if grilled else col P._blob(st, Vector3(0, len * 0.3, 0), Vector3(len, len * 0.32, len * 0.14), n, 0.05, body, r, 1) P._blob(st, Vector3(len * 0.1, len * 0.22, 0), Vector3(len * 0.75, len * 0.18, len * 0.12), n, 0.05, P._mul(body, 1.25) if not grilled else P._mul(body, 0.8), r, 1) # pale belly / char # Tail fin. var t0 := Vector3(-len * 0.9, len * 0.3, 0) var fin := P._mul(body, 0.8) P._tri(st, t0, Vector3(-len * 1.35, len * 0.65, 0), Vector3(-len * 1.35, -len * 0.05, 0), Vector3(0, 0, 1), fin) P._tri(st, t0, Vector3(-len * 1.35, len * 0.65, 0), Vector3(-len * 1.35, -len * 0.05, 0), Vector3(0, 0, -1), fin) # Dorsal fin and an eye. P._tri(st, Vector3(len * 0.3, len * 0.58, 0), Vector3(-len * 0.3, len * 0.58, 0), Vector3(-len * 0.1, len * 0.85, 0), Vector3(0, 0, 1), fin) P._tri(st, Vector3(len * 0.3, len * 0.58, 0), Vector3(-len * 0.3, len * 0.58, 0), Vector3(-len * 0.1, len * 0.85, 0), Vector3(0, 0, -1), fin) if not grilled: for s in [-1.0, 1.0]: P._blob(st, Vector3(len * 0.75, len * 0.38, s * len * 0.11), Vector3.ONE * len * 0.06, n, 0.0, Color(0.05, 0.05, 0.05), r, 0) return P._commit(st) # --- P2 stone building and wooden upgrades --------------------------------------- # Dimensions match the colliders in build_piece.gd (pillar, stairs, window, # palisade) or reuse the wall/floor/door ones. const MORTAR := Color(0.36, 0.35, 0.33) const STONE_BLOCK := Color(0.62, 0.61, 0.58) static func _block_col(r: RandomNumberGenerator) -> Color: var c := P._mul(STONE_BLOCK, r.randf_range(0.8, 1.08)) return c.lerp(Color(0.6, 0.55, 0.47), r.randf() * 0.25) # some warmer stones ## Courses of staggered blocks filling x0..x1, y0..y1, `depth` thick, with a ## mortar core behind them so the joints read dark. static func _blocks(st: SurfaceTool, r: RandomNumberGenerator, x0: float, x1: float, y0: float, y1: float, depth: float = 0.22, course: float = 0.33) -> void: if x1 - x0 < 0.02 or y1 - y0 < 0.02: return _box(st, Vector3((x0 + x1) * 0.5, (y0 + y1) * 0.5, 0), Vector3(x1 - x0, y1 - y0, depth - 0.04), MORTAR) var rows := maxi(roundi((y1 - y0) / course), 1) var h := (y1 - y0) / rows for i in range(rows): var y := y0 + h * (i + 0.5) var x := x0 # Stagger: every other course starts with a half block (world-aligned # so neighbouring walls line up). var step := 0.5 var next := x0 + (step * 0.5 if i % 2 == 1 else step) while x < x1 - 0.01: var e := minf(next, x1) _box(st, Vector3((x + e) * 0.5, y, 0), Vector3(e - x - 0.025, h - 0.025, depth), _block_col(r)) x = e next = e + step * r.randf_range(0.8, 1.2) static func _cut_stone() -> ArrayMesh: var st := P._begin() var r := _rng(211) _box(st, Vector3(0, 0.08, 0), Vector3(0.3, 0.16, 0.2), _block_col(r)) _box(st, Vector3(0.01, 0.161, 0), Vector3(0.26, 0.004, 0.16), P._mul(STONE_BLOCK, 1.1)) return P._commit(st) ## A heavy bench with a block on it and a frame saw: the stonecutter. static func _stonecutter() -> ArrayMesh: var st := P._begin() var r := _rng(213) _box(st, Vector3(0, 0.72, 0), Vector3(1.6, 0.12, 0.8), PLANK) for sx in [-0.7, 0.7]: for sz in [-0.32, 0.32]: _box(st, Vector3(sx, 0.33, sz), Vector3(0.14, 0.66, 0.14), PLANK_DARK) _box(st, Vector3(0, 0.2, 0), Vector3(1.4, 0.08, 0.64), PLANK_DARK) # Rough stone waiting on the shelf, a block being cut on top. for i in range(3): _box(st, Vector3(-0.45 + 0.42 * i, 0.32, 0.02), Vector3(0.3, 0.16, 0.24), _block_col(r), Basis(Vector3.UP, r.randf_range(-0.3, 0.3))) _box(st, Vector3(0.2, 0.92, 0), Vector3(0.6, 0.28, 0.4), _block_col(r)) # Frame saw: two uprights, a cross bar and the iron blade in the cut. for sx in [-0.2, 0.62]: _box(st, Vector3(sx, 1.12, 0), Vector3(0.05, 0.5, 0.05), HANDLE) _box(st, Vector3(0.21, 1.36, 0), Vector3(0.9, 0.04, 0.04), HANDLE) _box(st, Vector3(0.21, 1.04, 0), Vector3(0.86, 0.06, 0.012), IRON_BRIGHT) # Chips and dust. for i in range(5): _box(st, Vector3(r.randf_range(-0.7, 0.7), 0.79, r.randf_range(-0.3, 0.3)), Vector3(0.05, 0.03, 0.04), STONE) return P._commit(st) ## 2 x 2 m flagstones, top at y = 0, on a 0.2 m bed. static func _stone_floor() -> ArrayMesh: var st := P._begin() var r := _rng(217) _box(st, Vector3(0, -0.11, 0), Vector3(2.0, 0.18, 2.0), MORTAR) for i in range(4): for j in range(4): var c := Vector3(-0.75 + 0.5 * i + r.randf_range(-0.02, 0.02), -0.03, -0.75 + 0.5 * j + r.randf_range(-0.02, 0.02)) _box(st, c, Vector3(0.47, 0.06, 0.47) * Vector3(r.randf_range(0.95, 1.0), 1, r.randf_range(0.95, 1.0)), _block_col(r)) return P._commit(st) static func _stone_wall() -> ArrayMesh: var st := P._begin() _blocks(st, _rng(219), -1.0, 1.0, 0.0, 2.0) return P._commit(st) ## Square column with a base and a capital, 0.4 m. static func _stone_pillar() -> ArrayMesh: var st := P._begin() var r := _rng(223) _box(st, Vector3(0, 1.0, 0), Vector3(0.32, 2.0, 0.32), MORTAR) for i in range(6): var y := 0.2 + (1.6 / 6.0) * (i + 0.5) _box(st, Vector3(0, y, 0), Vector3(0.36, 1.6 / 6.0 - 0.025, 0.36), _block_col(r), Basis(Vector3.UP, r.randf_range(-0.04, 0.04))) _box(st, Vector3(0, 0.1, 0), Vector3(0.44, 0.2, 0.44), _block_col(r)) _box(st, Vector3(0, 1.9, 0), Vector3(0.44, 0.2, 0.44), _block_col(r)) return P._commit(st) static func _wood_pillar() -> ArrayMesh: var st := P._begin() var r := _rng(227) _box(st, Vector3(0, 1.0, 0), Vector3(0.3, 2.0, 0.3), _board(r, BEAM)) _box(st, Vector3(0, 1.94, 0), Vector3(0.4, 0.12, 0.4), BEAM) _box(st, Vector3(0, 0.06, 0), Vector3(0.38, 0.12, 0.38), BEAM) return P._commit(st) ## 8 steps climbing 2 m over 4 m toward -Z (the collider is the smooth ramp ## through their noses). Stone is solid underneath; wood is treads on two ## stringers. static func _stairs(stone: bool) -> ArrayMesh: var st := P._begin() var r := _rng(229 if stone else 231) var a := 0.4636476 for i in range(8): var top := 0.25 * (i + 1) - 0.06 var zc := -0.25 - 0.5 * i if stone: _box(st, Vector3(0, top * 0.5, zc), Vector3(2.0, top, 0.5), MORTAR) _box(st, Vector3(0, top - 0.05, zc + 0.01), Vector3(1.96, 0.1, 0.5), _block_col(r)) else: _box(st, Vector3(0, top - 0.04, zc + 0.03), Vector3(1.7, 0.08, 0.46), _board(r)) if not stone: var length := sqrt(20.0) for x in [-0.9, 0.9]: _box(st, Vector3(x, 0.92, -2.0), Vector3(0.12, 0.26, length), BEAM, Basis(Vector3.RIGHT, a)) return P._commit(st) ## Wall with a 1 x 0.7 m opening 1 m up (x -0.5..0.5, y 1.0..1.7). static func _stone_window() -> ArrayMesh: var st := P._begin() var r := _rng(233) _blocks(st, r, -1.0, 1.0, 0.0, 1.0) _blocks(st, r, -1.0, -0.5, 1.0, 1.7, 0.22, 0.35) _blocks(st, r, 0.5, 1.0, 1.0, 1.7, 0.22, 0.35) _blocks(st, r, -1.0, 1.0, 1.7, 2.0, 0.22, 0.3) _box(st, Vector3(0, 0.97, 0.03), Vector3(1.2, 0.07, 0.3), _block_col(r)) # sill _box(st, Vector3(0, 1.75, 0), Vector3(1.24, 0.12, 0.24), _block_col(r)) # lintel return P._commit(st) static func _wood_window() -> ArrayMesh: var st := P._begin() var r := _rng(237) _wall_body(st, r, -0.84, 0.84, 0.1, 1.0) _wall_body(st, r, -0.84, -0.5, 1.0, 1.7) _wall_body(st, r, 0.5, 0.84, 1.0, 1.7) _wall_body(st, r, -0.84, 0.84, 1.7, 1.9) for x in [-0.92, 0.92]: _box(st, Vector3(x, 1.0, 0), Vector3(0.16, 2.0, 0.2), BEAM) for y in [0.06, 1.94]: _box(st, Vector3(0, y, 0), Vector3(1.68, 0.12, 0.2), BEAM) # Frame, sill and a shutter swung open on each side (outside, +Z). _box(st, Vector3(0, 0.98, 0.02), Vector3(1.16, 0.06, 0.26), BEAM) _box(st, Vector3(0, 1.72, 0), Vector3(1.1, 0.06, 0.2), BEAM) for side in [-1.0, 1.0]: _box(st, Vector3(side * 0.52, 1.35, 0), Vector3(0.05, 0.72, 0.2), BEAM) _box(st, Vector3(side * 0.78, 1.35, 0.14), Vector3(0.48, 0.66, 0.04), _board(r, PLANK_DARK)) _box(st, Vector3(0.0, 1.35, 0), Vector3(0.03, 0.7, 0.03), BEAM) # mullion return P._commit(st) ## Stone arch for a 1.1 x 1.9 m door (the wooden leaf is wood_door_panel). static func _stone_doorway(with_panel: bool) -> ArrayMesh: var st := P._begin() var r := _rng(239) _blocks(st, r, -1.0, -0.55, 0.0, 2.0) _blocks(st, r, 0.55, 1.0, 0.0, 2.0) _box(st, Vector3(0, 1.97, 0), Vector3(1.14, 0.06, 0.2), MORTAR) # Arch stones over the opening: a keystone and its neighbours. for i in range(5): var x := -0.44 + 0.22 * i var h := 0.16 + (0.08 if i == 2 else 0.0) _box(st, Vector3(x, 1.9 + h * 0.5 - 0.02, 0), Vector3(0.2, h, 0.24), _block_col(r)) if with_panel: _door_panel_into(st, Vector3(-0.54, 0, 0)) return P._commit(st) ## Hearth: the fire sits at the origin in a stone firebox (back toward -Z), ## under a hood and chimney. static func _hearth() -> ArrayMesh: var st := P._begin() var r := _rng(241) _box(st, Vector3(0, 0.06, 0), Vector3(1.6, 0.12, 1.2), _block_col(r)) _box(st, Vector3(0, 0.125, 0.05), Vector3(0.9, 0.01, 0.7), ASH) # Back and cheeks. _box(st, Vector3(0, 0.75, -0.48), Vector3(1.6, 1.3, 0.24), MORTAR) for i in range(4): _box(st, Vector3(-0.4 + 0.8 * (i % 2) + r.randf_range(-0.05, 0.05), 0.3 + 0.33 * i, -0.36), Vector3(0.7, 0.3, 0.02), _block_col(r)) for sx in [-0.68, 0.68]: for i in range(4): _box(st, Vector3(sx, 0.28 + 0.3 * i, -0.05), Vector3(0.26, 0.28, 0.7), _block_col(r)) # Mantel, hood and chimney. _box(st, Vector3(0, 1.3, 0.05), Vector3(1.7, 0.12, 0.8), _block_col(r)) _box(st, Vector3(0, 1.55, -0.12), Vector3(1.3, 0.38, 0.6), MORTAR) _blocks(st, r, -0.3, 0.3, 1.74, 2.0, 0.42, 0.26) # A few logs in the fire. for i in range(3): var a := 0.6 * i - 0.6 var d := Vector3(cos(a), 0, sin(a)) * 0.3 P._cylinder(st, Vector3(0, 0.18, 0.05) - d, Vector3(0, 0.18, 0.05) + d, 0.06, 0.06, 5, P.BARK, r, -1.0, PLANK) return P._commit(st) ## 2 m of sharpened logs, 3 m tall. static func _palisade() -> ArrayMesh: var st := P._begin() var r := _rng(243) for i in range(5): _stake(st, r, -0.8 + 0.4 * i, 0.0, 3.0) for y in [0.7, 2.1]: _box(st, Vector3(0, y, -0.2), Vector3(2.0, 0.14, 0.08), BEAM) return P._commit(st) static func _stake(st: SurfaceTool, r: RandomNumberGenerator, x: float, y0: float, top: float) -> void: var h := top - r.randf_range(0.0, 0.18) var rad := r.randf_range(0.16, 0.19) P._cylinder(st, Vector3(x, y0, 0), Vector3(x, h - 0.35, 0), rad, rad * 0.95, 7, P.BARK, r) P._cylinder(st, Vector3(x, h - 0.35, 0), Vector3(x, h, 0), rad * 0.95, 0.02, 7, P.WOOD, r) ## Palisade gate: stakes either side of a 1.1 x 1.9 m opening, more above it. static func _palisade_gate(with_panel: bool) -> ArrayMesh: var st := P._begin() var r := _rng(247) for x in [-0.8, 0.8]: _stake(st, r, x, 0.0, 3.0) for x in [-0.36, 0.0, 0.36]: _stake(st, r, x, 1.95, 3.0) for x in [-0.62, 0.62]: _box(st, Vector3(x, 0.98, 0), Vector3(0.12, 1.96, 0.2), BEAM) _box(st, Vector3(0, 1.98, 0), Vector3(1.4, 0.12, 0.24), BEAM) if with_panel: _door_panel_into(st, Vector3(-0.54, 0, 0)) return P._commit(st) # --- P3 the alpha ------------------------------------------------------------------ const ALPHA_FUR := Color(0.17, 0.16, 0.17) ## Iron-banded door: the wooden leaf with three iron straps and studs. ## `in_frame` = the closed door in a wood frame (icon and ghost); otherwise ## just the leaf, hinge at the origin. static func _iron_door(in_frame: bool) -> ArrayMesh: var st := P._begin() var o := Vector3.ZERO if in_frame: var r := _rng(79) for side in [-1.0, 1.0]: _wall_body(st, r, side * 0.55 if side > 0 else -0.84, 0.84 if side > 0 else -0.55, 0.1, 1.9) _box(st, Vector3(side * 0.92, 1.0, 0), Vector3(0.16, 2.0, 0.2), BEAM) _box(st, Vector3(side * 0.6, 0.95, 0), Vector3(0.1, 1.9, 0.2), BEAM) _box(st, Vector3(0, 1.95, 0), Vector3(1.68, 0.1, 0.2), BEAM) o = Vector3(-0.54, 0, 0) var rr := _rng(251) for i in range(4): _box(st, o + Vector3(0.135 + 0.27 * i, 0.95, 0), Vector3(0.26, 1.86, 0.08), _board(rr, PLANK_DARK)) for y in [0.3, 0.95, 1.6]: for z in [0.05, -0.05]: _box(st, o + Vector3(0.54, y, z), Vector3(1.04, 0.1, 0.02), IRON) for i in range(4): _box(st, o + Vector3(0.14 + 0.27 * i, y, z * 1.4), Vector3(0.03, 0.03, 0.02), IRON_BRIGHT) _box(st, o + Vector3(0.95, 1.0, 0.08), Vector3(0.06, 0.16, 0.05), IRON_BRIGHT) _box(st, o + Vector3(0.95, 1.0, -0.08), Vector3(0.06, 0.16, 0.05), IRON_BRIGHT) return P._commit(st) static func _fang() -> ArrayMesh: var st := P._begin() var r := _rng(253) P._cylinder(st, Vector3(0, 0.02, 0), Vector3(0.02, 0.02, -0.12), 0.022, 0.003, 6, Color(0.88, 0.82, 0.62), r, -1.0, Color(0.6, 0.45, 0.35)) return P._commit(st) # --- P3 the bear --------------------------------------------------------------------- const BEAR_FUR := Color(0.36, 0.24, 0.15) ## A bearskin laid flat: body, four splayed legs and the head toward -Z. ## A skin rug with its head at -Z: the bear's (fur_rug), or wolves' a bit smaller. static func _rug(fur: Color = BEAR_FUR, k: float = 1.0) -> ArrayMesh: var st := P._begin() var r := _rng(273) _box(st, Vector3(0, 0.015, 0.1) * k, Vector3(1.0, 0.03 / k, 1.5) * k, fur) _box(st, Vector3(0, 0.02, 0.1) * k, Vector3(0.8, 0.03 / k, 1.3) * k, P._mul(fur, 1.12)) for sx in [-1.0, 1.0]: for sz in [-0.45, 0.6]: _box(st, Vector3(sx * 0.62, 0.012, sz) * k, Vector3(0.42, 0.024 / k, 0.24) * k, _board(r, fur), Basis(Vector3.UP, sx * (0.5 if sz < 0 else -0.5))) _box(st, Vector3(0, 0.05, -0.85) * k, Vector3(0.38, 0.1, 0.34) * k, P._mul(fur, 0.95)) _box(st, Vector3(0, 0.045, -1.06) * k, Vector3(0.18, 0.08, 0.14) * k, Color(0.58, 0.45, 0.32)) for sx in [-0.14, 0.14]: _box(st, Vector3(sx, 0.11, -0.8) * k, Vector3(0.08, 0.05, 0.05) * k, P._mul(fur, 0.8)) _box(st, Vector3(0, 0.02, 0.92) * k, Vector3(0.12, 0.03, 0.12) * k, fur) return P._commit(st) ## A fired clay jar with a wooden lid, about knee high. static func _jar() -> ArrayMesh: var st := P._begin() var r := _rng(337) P._cylinder(st, Vector3(0, 0.0, 0), Vector3(0, 0.12, 0), 0.16, 0.26, 10, P._mul(CLAY_FIRED, 0.92), r, -1.0, CLAY_FIRED) P._cylinder(st, Vector3(0, 0.12, 0), Vector3(0, 0.42, 0), 0.26, 0.24, 10, CLAY_FIRED, r) P._cylinder(st, Vector3(0, 0.42, 0), Vector3(0, 0.52, 0), 0.24, 0.16, 10, P._mul(CLAY_FIRED, 1.06), r) P._cylinder(st, Vector3(0, 0.52, 0), Vector3(0, 0.56, 0), 0.17, 0.17, 10, P._mul(CLAY_FIRED, 0.85), r) P._cylinder(st, Vector3(0, 0.56, 0), Vector3(0, 0.6, 0), 0.19, 0.17, 10, PLANK_DARK, r, -1.0, PLANK) # lid _box(st, Vector3(0, 0.63, 0), Vector3(0.08, 0.04, 0.05), PLANK_DARK) return P._commit(st) ## A squat tallow candle on a clay dish; the wick's top is at y = 0.2 ## (lamp.gd puts the flame there). static func _candle() -> ArrayMesh: var st := P._begin() var r := _rng(359) P._cylinder(st, Vector3(0, 0.0, 0), Vector3(0, 0.02, 0), 0.08, 0.09, 8, CLAY_FIRED, r, -1.0, P._mul(CLAY_FIRED, 1.1)) P._cylinder(st, Vector3(0, 0.02, 0), Vector3(0, 0.16, 0), 0.04, 0.036, 8, Color(0.93, 0.89, 0.74), r, -1.0, Color(0.96, 0.93, 0.8)) P._cylinder(st, Vector3(0, 0.16, 0), Vector3(0, 0.19, 0), 0.004, 0.003, 4, ASH, r) return P._commit(st) ## A torch in a wooden stand: three splayed feet, a post, the head at 1.4 m ## (lamp.gd's flame sits just above it). static func _torch_stand() -> ArrayMesh: var st := P._begin() var r := _rng(367) for i in range(3): var a := TAU * i / 3.0 P._cylinder(st, Vector3(cos(a) * 0.28, 0.0, sin(a) * 0.28), Vector3(0, 0.45, 0), 0.03, 0.035, 5, _board(r, PLANK_DARK), r) P._cylinder(st, Vector3(0, 0.0, 0), Vector3(0, 1.3, 0), 0.04, 0.035, 6, _board(r), r) _box(st, Vector3(0, 0.5, 0), Vector3(0.12, 0.05, 0.12), BINDING) P._cylinder(st, Vector3(0, 1.28, 0), Vector3(0, 1.42, 0), 0.05, 0.06, 6, CLOTH, r, -1.0, ASH) return P._commit(st) ## A hide bedroll: a flat mat with its rolled-up end and a feather pillow. static func _bedroll() -> ArrayMesh: var st := P._begin() var r := _rng(347) _box(st, Vector3(0, 0.03, 0.15), Vector3(0.8, 0.06, 1.6), HIDE) _box(st, Vector3(0, 0.065, 0.2), Vector3(0.66, 0.02, 1.35), P._mul(HIDE, 1.12)) P._cylinder(st, Vector3(-0.4, 0.13, 0.95), Vector3(0.4, 0.13, 0.95), 0.12, 0.12, 8, _board(r, HIDE), r, -1.0, P._mul(HIDE, 0.8)) _box(st, Vector3(0, 0.1, -0.5), Vector3(0.5, 0.09, 0.28), FEATHER) for sx in [-0.25, 0.25]: _box(st, Vector3(sx, 0.18, 0.95), Vector3(0.04, 0.26, 0.04), BINDING) return P._commit(st) ## A cord with five fangs hanging from a dark scrap of pelt. static func _necklace() -> ArrayMesh: var st := P._begin() var r := _rng(353) for i in range(12): var a := TAU * i / 12.0 var b := TAU * (i + 1) / 12.0 P._cylinder(st, Vector3(cos(a) * 0.12, 0.01, sin(a) * 0.1), Vector3(cos(b) * 0.12, 0.01, sin(b) * 0.1), 0.006, 0.006, 4, BINDING, r) _box(st, Vector3(0, 0.015, 0.1), Vector3(0.09, 0.02, 0.05), ALPHA_FUR) for i in range(5): var x := (i - 2) * 0.035 P._cylinder(st, Vector3(x, 0.012, 0.12), Vector3(x * 1.2, 0.012, 0.19 - absf(i - 2) * 0.012), 0.012, 0.002, 5, Color(0.88, 0.82, 0.62), r) return P._commit(st) # --- P3 the barrow -------------------------------------------------------------------- ## A third of a carved stone disc, with a groove and a pale inlay. static func _barrow_key() -> ArrayMesh: var st := P._begin() var r := _rng(311) var segs := 5 for i in range(segs): var a0 := -0.55 + 1.1 * i / segs var a1 := -0.55 + 1.1 * (i + 1) / segs var c := Vector3(sin((a0 + a1) * 0.5), 0, cos((a0 + a1) * 0.5)) * 0.09 var w := 2.0 * 0.09 * sin((a1 - a0) * 0.5) + 0.006 _box(st, c + Vector3(0, 0.02, 0), Vector3(w, 0.04, 0.12), P._mul(Color(0.45, 0.46, 0.44), r.randf_range(0.9, 1.05)), Basis(Vector3.UP, (a0 + a1) * 0.5)) _box(st, Vector3(0, 0.042, 0.1), Vector3(0.1, 0.006, 0.02), Color(0.82, 0.8, 0.7)) return P._commit(st) # --- P4 food keeping --------------------------------------------------------------------- ## A crust of white salt crystals. static func _salt() -> ArrayMesh: var st := P._begin() var r := _rng(331) for i in range(7): var c := Vector3(r.randf_range(-0.07, 0.07), 0.02, r.randf_range(-0.06, 0.06)) _box(st, c, Vector3.ONE * r.randf_range(0.025, 0.05), P._mul(Color(0.93, 0.93, 0.9), r.randf_range(0.9, 1.05)), Basis(Vector3(r.randf(), r.randf(), r.randf()).normalized(), r.randf() * TAU)) return P._commit(st) ## Two forked posts with a crossbar, strips of meat hanging from it, embers ## below. The fire sits at the origin. static func _smoking_rack() -> ArrayMesh: var st := P._begin() var r := _rng(333) var n := FastNoiseLite.new() n.seed = 333 for sx in [-0.7, 0.7]: P._cylinder(st, Vector3(sx, 0.0, 0), Vector3(sx, 1.35, 0), 0.05, 0.04, 6, P.BARK, r, -1.0, PLANK) P._cylinder(st, Vector3(sx, 1.25, 0), Vector3(sx + 0.08 * signf(sx), 1.45, 0), 0.03, 0.02, 4, P.BARK, r) P._cylinder(st, Vector3(-0.8, 1.32, 0), Vector3(0.8, 1.32, 0), 0.035, 0.035, 6, P.BARK, r, -1.0, PLANK) for i in range(5): var x := -0.48 + 0.24 * i _box(st, Vector3(x, 1.12, 0), Vector3(0.12, 0.32 + 0.06 * sin(i * 2.1), 0.03), P._mul(Color(0.42, 0.18, 0.12), r.randf_range(0.8, 1.1))) for i in range(6): var a := TAU * i / 6.0 P._blob(st, Vector3(cos(a) * 0.32, 0.05, sin(a) * 0.32), Vector3(0.1, 0.07, 0.09), n, 0.3, STONE, r, 1) P._blob(st, Vector3(0, 0.03, 0), Vector3(0.24, 0.035, 0.24), n, 0.2, ASH, r, 1) for i in range(3): var a := 0.9 * i var d := Vector3(cos(a), 0, sin(a)) * 0.18 P._cylinder(st, Vector3(0, 0.06, 0) - d, Vector3(0, 0.06, 0) + d, 0.035, 0.035, 5, P.BARK, r, -1.0, CHARCOAL) return P._commit(st) # --- P5 the Old Stag --------------------------------------------------------------------- const ANTLER := Color(0.9, 0.87, 0.76) ## One branching antler lying on its side. static func _antler_into(st: SurfaceTool, r: RandomNumberGenerator, o: Vector3, k: float, up: Vector3 = Vector3.UP) -> void: var side := up.cross(Vector3.FORWARD).normalized() if absf(up.dot(Vector3.FORWARD)) < 0.9 else Vector3.RIGHT var a := o var b := o + up * 0.35 * k + side * 0.12 * k var c := b + up * 0.3 * k + side * 0.18 * k P._cylinder(st, a, b, 0.035 * k, 0.028 * k, 5, ANTLER, r) P._cylinder(st, b, c, 0.028 * k, 0.01 * k, 5, ANTLER, r) for t in [[a.lerp(b, 0.4), Vector3(0, 0.15, -0.12)], [b, Vector3(0, 0.2, -0.15)], [b.lerp(c, 0.6), Vector3(0, 0.12, 0.14)]]: P._cylinder(st, t[0], (t[0] as Vector3) + (up * (t[1] as Vector3).y + Vector3(0, 0, (t[1] as Vector3).z)) * k * 1.4, 0.018 * k, 0.005 * k, 4, ANTLER, r) static func _antler() -> ArrayMesh: var st := P._begin() _antler_into(st, _rng(361), Vector3(-0.25, 0.04, 0), 1.0, Vector3(1, 0.12, 0).normalized()) return P._commit(st) ## The skull and antlers on a wooden stand. static func _stag_trophy() -> ArrayMesh: var st := P._begin() var r := _rng(363) var n := FastNoiseLite.new() n.seed = 363 _box(st, Vector3(0, 0.08, 0), Vector3(1.2, 0.16, 0.7), P._mul(BEAM, 0.9)) _box(st, Vector3(0, 0.6, -0.15), Vector3(0.16, 0.9, 0.16), BEAM) P._blob(st, Vector3(0, 1.12, -0.1), Vector3(0.16, 0.16, 0.26), n, 0.1, Color(0.88, 0.85, 0.75), r, 1) P._blob(st, Vector3(0, 1.08, 0.12), Vector3(0.07, 0.06, 0.08), n, 0.0, Color(0.2, 0.2, 0.2), r, 0) for s in [-1.0, 1.0]: _antler_into(st, r, Vector3(s * 0.08, 1.25, -0.12), 1.6, Vector3(s * 0.7, 1.0, 0).normalized()) P._blob(st, Vector3(s * 0.07, 1.16, 0.08), Vector3.ONE * 0.025, n, 0.0, Color(0.55, 0.9, 1.0), r, 0) return P._commit(st) static func _antler_pickaxe() -> ArrayMesh: var st := P._begin() var r := _rng(365) _handle(st, 0.62, r) _box(st, Vector3(0, 0.57, 0), Vector3(0.06, 0.08, 0.06), IRON) P._cylinder(st, Vector3(-0.02, 0.58, 0), Vector3(0.27, 0.5, 0), 0.032, 0.004, 5, ANTLER, r, -1.0, ANTLER) P._cylinder(st, Vector3(0.02, 0.58, 0), Vector3(-0.24, 0.53, 0), 0.032, 0.004, 5, ANTLER, r, -1.0, ANTLER) P._cylinder(st, Vector3(0.1, 0.56, 0), Vector3(0.14, 0.66, 0), 0.012, 0.003, 4, ANTLER, r) return P._commit(st)