extends RefCounted ## Procedural low-poly animals, built from the same helpers as the trees and ## items (flat-shaded, vertex colours, no assets). ## ## Each kind gives a static "body" mesh (torso, head, ears, tail) facing -Z, ## one "leg" mesh hanging from the origin down -Y, and the four hip points ## the legs pivot on. creature.gd animates the legs. const P = preload("res://scripts/world/proc_mesh.gd") static var _cache := {} static func build(kind: String) -> Dictionary: if _cache.has(kind): return _cache[kind] var d: Dictionary match kind: "rabbit": d = _rabbit() "deer": d = _deer() "wolf": d = _wolf() "alpha": d = _wolf(true) "bear": d = _bear() _: d = _boar() _cache[kind] = d return d static func _rng(seed: int) -> RandomNumberGenerator: var r := RandomNumberGenerator.new() r.seed = seed return r static func _noise(seed: int, f: float = 1.6) -> FastNoiseLite: var n := FastNoiseLite.new() n.seed = seed n.frequency = f return n static func _leg(len: float, r0: float, r1: float, col: Color, hoof: Color, seed: int) -> ArrayMesh: var st := P._begin() var r := _rng(seed) P._cylinder(st, Vector3(0, 0, 0), Vector3(0, -len * 0.55, 0.02), r0, r0 * 0.75, 5, col, r, 0.0) P._cylinder(st, Vector3(0, -len * 0.55, 0.02), Vector3(0, -len, 0), r0 * 0.75, r1, 5, col, r, 0.0, hoof) return P._commit(st) static func _rabbit() -> Dictionary: var fur := Color(0.55, 0.46, 0.36) var st := P._begin() var r := _rng(201) var n := _noise(201) P._blob(st, Vector3(0, 0.2, 0.03), Vector3(0.13, 0.13, 0.2), n, 0.15, fur, r, 1) P._blob(st, Vector3(0, 0.31, -0.17), Vector3(0.085, 0.085, 0.1), n, 0.12, fur, r, 1) for s in [-1.0, 1.0]: P._cylinder(st, Vector3(s * 0.035, 0.38, -0.15), Vector3(s * 0.06, 0.56, -0.1), 0.025, 0.018, 4, P._mul(fur, 1.1), r, -1.0, Color(0.85, 0.65, 0.6)) P._blob(st, Vector3(s * 0.05, 0.33, -0.25), Vector3.ONE * 0.012, n, 0.0, Color(0.05, 0.04, 0.04), r, 0) P._blob(st, Vector3(0, 0.25, 0.23), Vector3.ONE * 0.05, n, 0.2, Color(0.93, 0.92, 0.88), r, 0) return {"body": P._commit(st), "leg": _leg(0.12, 0.03, 0.025, fur, P._mul(fur, 0.8), 202), "hips": [Vector3(-0.07, 0.12, -0.1), Vector3(0.07, 0.12, -0.1), Vector3(-0.08, 0.14, 0.12), Vector3(0.08, 0.14, 0.12)], "radius": 0.18, "height": 0.4} static func _deer() -> Dictionary: var coat := Color(0.62, 0.42, 0.25) var belly := Color(0.85, 0.76, 0.62) var dark := Color(0.2, 0.15, 0.12) var st := P._begin() var r := _rng(211) var n := _noise(211, 1.2) P._blob(st, Vector3(0, 1.0, 0), Vector3(0.26, 0.3, 0.62), n, 0.12, coat, r, 1) P._blob(st, Vector3(0, 0.86, 0.05), Vector3(0.2, 0.14, 0.45), n, 0.1, belly, r, 1) P._cylinder(st, Vector3(0, 1.1, -0.45), Vector3(0, 1.55, -0.68), 0.13, 0.09, 6, coat, r) P._blob(st, Vector3(0, 1.6, -0.78), Vector3(0.11, 0.12, 0.2), n, 0.1, coat, r, 1) P._blob(st, Vector3(0, 1.55, -0.96), Vector3(0.05, 0.05, 0.05), n, 0.0, dark, r, 0) for s in [-1.0, 1.0]: P._cylinder(st, Vector3(s * 0.07, 1.7, -0.72), Vector3(s * 0.2, 1.82, -0.66), 0.04, 0.015, 4, coat, r) P._blob(st, Vector3(s * 0.08, 1.65, -0.86), Vector3.ONE * 0.022, n, 0.0, dark, r, 0) # Small antlers. P._cylinder(st, Vector3(s * 0.04, 1.72, -0.76), Vector3(s * 0.12, 2.0, -0.7), 0.018, 0.01, 4, Color(0.8, 0.74, 0.6), r) P._cylinder(st, Vector3(s * 0.09, 1.88, -0.72), Vector3(s * 0.08, 2.0, -0.86), 0.012, 0.006, 3, Color(0.8, 0.74, 0.6), r) P._blob(st, Vector3(0, 1.12, 0.62), Vector3(0.06, 0.09, 0.05), n, 0.1, belly, r, 0) return {"body": P._commit(st), "leg": _leg(0.88, 0.055, 0.035, coat, dark, 212), "hips": [Vector3(-0.14, 0.9, -0.42), Vector3(0.14, 0.9, -0.42), Vector3(-0.14, 0.92, 0.42), Vector3(0.14, 0.92, 0.42)], "radius": 0.4, "height": 1.7} static func _boar() -> Dictionary: var bristle := Color(0.33, 0.25, 0.2) var snout := Color(0.55, 0.4, 0.36) var tusk := Color(0.92, 0.9, 0.82) var st := P._begin() var r := _rng(221) var n := _noise(221, 1.4) P._blob(st, Vector3(0, 0.62, 0.02), Vector3(0.32, 0.34, 0.6), n, 0.18, bristle, r, 1) P._blob(st, Vector3(0, 0.8, -0.2), Vector3(0.18, 0.15, 0.3), n, 0.3, P._mul(bristle, 0.75), r, 1) # mane P._blob(st, Vector3(0, 0.6, -0.62), Vector3(0.2, 0.2, 0.26), n, 0.15, bristle, r, 1) P._cylinder(st, Vector3(0, 0.55, -0.8), Vector3(0, 0.5, -0.98), 0.1, 0.08, 6, snout, r, -1.0, P._mul(snout, 0.8)) for s in [-1.0, 1.0]: P._cylinder(st, Vector3(s * 0.08, 0.48, -0.88), Vector3(s * 0.12, 0.62, -0.96), 0.022, 0.006, 4, tusk, r) P._cylinder(st, Vector3(s * 0.1, 0.76, -0.62), Vector3(s * 0.17, 0.9, -0.55), 0.05, 0.015, 4, bristle, r) P._blob(st, Vector3(s * 0.11, 0.68, -0.8), Vector3.ONE * 0.02, n, 0.0, Color(0.05, 0.03, 0.03), r, 0) P._cylinder(st, Vector3(0, 0.7, 0.58), Vector3(0, 0.55, 0.72), 0.02, 0.012, 3, bristle, r) return {"body": P._commit(st), "leg": _leg(0.36, 0.07, 0.05, bristle, Color(0.15, 0.12, 0.1), 222), "hips": [Vector3(-0.17, 0.38, -0.36), Vector3(0.17, 0.38, -0.36), Vector3(-0.17, 0.4, 0.38), Vector3(0.17, 0.4, 0.38)], "radius": 0.42, "height": 0.95} ## Grey wolf: lean body, long snout, pointed ears, bushy tail. "eyes" is a ## separate mesh so it can glow (creature.gd gives it an emissive material). ## `alpha`: a third bigger, near-black with a grizzled ruff and a scarred ## muzzle (wolf moon nights). static func _wolf(alpha: bool = false) -> Dictionary: if alpha: return _scaled(_wolf_parts(Color(0.17, 0.16, 0.17), Color(0.5, 0.48, 0.45), 241), 1.32) return _wolf_parts(Color(0.42, 0.42, 0.44), Color(0.72, 0.71, 0.68), 231) ## The same parts scaled up `k` times (body, eyes and leg meshes, hips, radius, height). static func _scaled(d: Dictionary, k: float) -> Dictionary: var out := d.duplicate() for key in ["body", "eyes", "leg"]: if d.has(key): out[key] = _scale_mesh(d[key], k) var hips: Array = [] for h: Vector3 in d.hips: hips.append(h * k) out.hips = hips out.radius = float(d.radius) * k out.height = float(d.height) * k return out static func _scale_mesh(m: ArrayMesh, k: float) -> ArrayMesh: var arrays := m.surface_get_arrays(0) var v: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] for i in range(v.size()): v[i] *= k arrays[Mesh.ARRAY_VERTEX] = v var out := ArrayMesh.new() out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) return out static func _wolf_parts(coat: Color, pale: Color, seed: int) -> Dictionary: var dark := Color(0.16, 0.15, 0.15) var st := P._begin() var r := _rng(seed) var n := _noise(seed, 1.5) P._blob(st, Vector3(0, 0.7, 0.05), Vector3(0.22, 0.24, 0.55), n, 0.15, coat, r, 1) P._blob(st, Vector3(0, 0.8, -0.32), Vector3(0.24, 0.26, 0.3), n, 0.25, P._mul(coat, 1.08), r, 1) # ruff P._blob(st, Vector3(0, 0.6, -0.1), Vector3(0.16, 0.12, 0.35), n, 0.1, pale, r, 1) # belly P._blob(st, Vector3(0, 0.92, -0.62), Vector3(0.15, 0.14, 0.17), n, 0.12, coat, r, 1) # head P._cylinder(st, Vector3(0, 0.88, -0.72), Vector3(0, 0.83, -0.95), 0.09, 0.05, 6, pale, r, -1.0, dark) # snout for s in [-1.0, 1.0]: P._cylinder(st, Vector3(s * 0.08, 1.02, -0.6), Vector3(s * 0.1, 1.2, -0.58), 0.05, 0.008, 4, P._mul(coat, 0.85), r) # Bushy tail, hanging down and back. P._cylinder(st, Vector3(0, 0.78, 0.55), Vector3(0, 0.5, 0.85), 0.07, 0.05, 5, coat, r) P._blob(st, Vector3(0, 0.45, 0.9), Vector3(0.06, 0.08, 0.06), n, 0.2, pale, r, 0) var est := P._begin() for s in [-1.0, 1.0]: P._blob(est, Vector3(s * 0.065, 0.97, -0.75), Vector3.ONE * 0.022, n, 0.0, Color(1.0, 0.8, 0.3), r, 0) return {"body": P._commit(st), "eyes": P._commit(est), "leg": _leg(0.52, 0.045, 0.03, coat, dark, seed + 1), "hips": [Vector3(-0.11, 0.52, -0.38), Vector3(0.11, 0.52, -0.38), Vector3(-0.11, 0.54, 0.38), Vector3(0.11, 0.54, 0.38)], "radius": 0.32, "height": 1.0} ## Brown bear: a heavy humped body, a broad head with a pale muzzle and ## round ears, thick short legs. static func _bear() -> Dictionary: var coat := Color(0.36, 0.24, 0.15) var hump := Color(0.42, 0.29, 0.18) var muzzle := Color(0.58, 0.45, 0.32) var dark := Color(0.1, 0.08, 0.07) var st := P._begin() var r := _rng(261) var n := _noise(261, 1.3) P._blob(st, Vector3(0, 0.95, 0.15), Vector3(0.5, 0.5, 0.85), n, 0.14, coat, r, 1) # body P._blob(st, Vector3(0, 1.22, -0.35), Vector3(0.44, 0.38, 0.42), n, 0.18, hump, r, 1) # shoulder hump P._blob(st, Vector3(0, 1.05, -0.95), Vector3(0.3, 0.27, 0.3), n, 0.12, coat, r, 1) # head P._cylinder(st, Vector3(0, 0.98, -1.12), Vector3(0, 0.92, -1.36), 0.15, 0.1, 6, muzzle, r, -1.0, dark) for s in [-1.0, 1.0]: P._blob(st, Vector3(s * 0.2, 1.3, -0.9), Vector3(0.08, 0.08, 0.05), n, 0.1, P._mul(coat, 0.85), r, 0) # ears P._blob(st, Vector3(0, 0.98, 0.98), Vector3(0.08, 0.08, 0.07), n, 0.2, coat, r, 0) # stub tail var est := P._begin() for s in [-1.0, 1.0]: P._blob(est, Vector3(s * 0.13, 1.12, -1.18), Vector3.ONE * 0.025, n, 0.0, Color(0.9, 0.6, 0.25), r, 0) return {"body": P._commit(st), "eyes": P._commit(est), "leg": _leg(0.72, 0.13, 0.11, coat, dark, 262), "hips": [Vector3(-0.28, 0.72, -0.5), Vector3(0.28, 0.72, -0.5), Vector3(-0.28, 0.74, 0.6), Vector3(0.28, 0.74, 0.6)], "radius": 0.62, "height": 1.6}