ludic/packages/ludic.wildlife/steer.ludic
Orkuncakilkaya 81b6782c9d ludic.wildlife (18.2): a walker a crowd moves
wildlife_set_crowd(a, true) hands a walker to a crowd. Its step no longer moves it, and its want is two fields on it: goal_x / goal_z (the point it heads for or faces) and goal_speed (0 when it stands). A route reads those and hands back where the crowd put it through wildlife_moved(a, x, z, vx, vz). That sets its place, its ground, its heading from its velocity and its prints, and updates its row's columns so the spatial index finds it there. Nothing allocates per animal per frame: the want is the animal's own fields, and moved writes in place. The prints and the turn counters moved to walked.ludic (steer.ludic was at the 100-line limit). crowd_test holds it; with crowd off every old test is unchanged. wildlife 26/26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:27:12 +03:00

93 lines
3.2 KiB
Text

# steer.ludic - turning toward a point at a rate, a step along the facing that turns away from water
# and a climb; a walker leaves a print every 2.8 m (the way there and a target about home: way.ludic)
function wl_wrap(a: float) -> float {
var r = a
while r > 3.1415927 { r = r - WL_TAU }
while r < -3.1415927 { r = r + WL_TAU }
return r
}
function wl_face(a: WildAnimal, tx: float, tz: float, rate: float, dt: float) -> void {
let want = Math.atan2(-(tx - a.x), -(tz - a.z))
let r = rate * dt
a.yaw = a.yaw + Math.clamp(wl_wrap(want - a.yaw), -r, r)
a.goal_x = tx
a.goal_z = tz
}
# true when it moved; a walker is slid round a still thing (a trunk, a tent), a bird is not
# a climb is judged over half a metre ahead, not one frame's step: a few centimetres of the drawn
# ground rise steeply over a pebble a walker steps across, and a frame's step shrinks with the rate
const WL_LOOK: float = 0.5
function wl_climb(a: WildAnimal, nx: float, nz: float) -> float {
let dx = nx - a.x
let dz = nz - a.z
let l = Math.sqrt(dx * dx + dz * dz)
return WildlifeWorld.ground(a.x + dx / l * WL_LOOK, a.z + dz / l * WL_LOOK) - a.y
}
function wl_step(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bool {
if a.crowd { return wl_crowd_step(a) }
let d = a.speed * dt
if d < 0.0001 { return false }
var nx = a.x - Math.sin(a.yaw) * d
var nz = a.z - Math.cos(a.yaw) * d
let s = wl_sp(wildlife_st, a.sp)
if not s.flies and wl_push(wildlife_st, a, s, nx, nz) {
nx = wildlife_st.wl_out_x
nz = wildlife_st.wl_out_z
}
let nh = WildlifeWorld.ground(nx, nz)
let moved = Math.abs(nx - a.x) + Math.abs(nz - a.z)
let wet = nh < WildlifeWorld.water(nx, nz) + 0.6
let steep = moved > 0.0001 and wl_climb(a, nx, nz) > WL_LOOK * 1.2
if wet or steep or moved < d * 0.2 {
if wet { wildlife_st.wl_turned_wet += 1 } else if steep { wildlife_st.wl_turned_steep += 1 }
a.yaw = a.yaw + Math.deg_to_rad(75.0)
a.timer = 0.0
wildlife_st.wl_turned += 1
return false
}
a.x = nx
a.z = nz
a.y = nh
wl_walked(wildlife_st, a, s, d)
return true
}
# (x, z) pushed clear of every still thing for a body its size; wl_out_x / _z say where
function wl_push(wildlife_st: mut WildlifeState, a: WildAnimal, s: WildSpecies, x: float, z: float) -> bool {
wildlife_st.wl_ask_x = x
wildlife_st.wl_ask_z = z
let k = Math.clamp(float(s.size), 0.0, 2.0)
let r = (0.2 + 0.2 * k) * s.scale * a.scale
let hit = WildlifeWorld.push(x, z, r, a.y + 0.1, a.y + (0.5 + 0.7 * k) * s.scale * a.scale)
wildlife_st.wl_out_x = x
wildlife_st.wl_out_z = z
if hit {
wildlife_st.wl_out_x = WildlifeWorld.pushed_x()
wildlife_st.wl_out_z = WildlifeWorld.pushed_z()
}
return hit
}
function wl_at_target(a: WildAnimal, r: float) -> bool {
let dx = a.tx - a.x
let dz = a.tz - a.z
return dx * dx + dz * dz < r * r
}
# back to wandering about home after `t` seconds of whatever it was doing
function wl_wander(wildlife_st: WildlifeState, a: WildAnimal, t: float) -> void {
a.state = WILD_WANDER
a.timer = t
wl_pick_target(wildlife_st, a, wl_sp(wildlife_st, a.sp).home)
}
# home where it stands now, kept inside its range
function wl_rehome(wildlife_st: WildlifeState, a: WildAnimal) -> void {
a.home_x = a.x
a.home_z = a.z
wl_clamp(wildlife_st, a)
}