ludic/packages/ludic.aim/ray.ludic
Orkuncakilkaya 56e4b6d688 feat(senses): walkers slide round still things, and lines of sight are the world's to answer
- ludic.wildlife: WildlifeWorld.push / pushed_x / pushed_z (defaults change nothing); a ground
  walker's step is slid clear of a still thing with a body from its species' size and scale, and
  a step that gets nowhere turns away as the water does. Birds are not pushed; no dice added.
- ludic.npc: NpcWorld.push / pushed_x / pushed_z (a person 0.35 m round) in npc_walk, and
  NpcWorld.clear (default true), which npc_line_ok asks at 1 m so a detour does not cut a trunk.
- ludic.aim: AimView.clear replaces AimView.ground and the ground march; the pick asks the line
  a body's width short of where the ray enters the thing (aim_clear_at(t, back)), so a solid
  thing does not hide itself. probe.ludic (the ground-agreement probe) and AimView.dry are gone.
- ludic.photo: PhotoLens.clear replaces PhotoLens.ground, PhotoLens.trunk and the march.

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

64 lines
2.8 KiB
Text

# ray.ludic - the ray through the crosshair against an upright cylinder, the forgiveness around a
# body, and whether anything stands in front of a point
# t along the ray where it enters the cylinder at (cx, cz), radius r, from y0 up to y1; or -1
export function aim_ray_cyl(cx: float, y0: float, y1: float, cz: float, r: float) -> float {
let ox = AimView.x() - cx
let oz = AimView.z() - cz
let dx = AimView.fx()
let dy = AimView.fy()
let dz = AimView.fz()
let a = dx * dx + dz * dz
var t0 = -10000.0
var t1 = 10000.0
if a < 0.000001 {
if ox * ox + oz * oz > r * r { return -1.0 } # straight up or down: inside or nothing
} else {
let b = 2.0 * (ox * dx + oz * dz)
let disc = b * b - 4.0 * (a * (ox * ox + oz * oz - r * r))
if disc < 0.0 { return -1.0 }
t0 = (-b - Math.sqrt(disc)) / (2.0 * a)
t1 = (-b + Math.sqrt(disc)) / (2.0 * a)
}
let cy = AimView.y()
if Math.abs(dy) > 0.000001 {
let ta = (y0 - cy) / dy
let tb = (y1 - cy) / dy
t0 = Math.max(t0, Math.min(ta, tb))
t1 = Math.min(t1, Math.max(ta, tb))
} else if cy < y0 or cy > y1 { return -1.0 }
if t0 > t1 or t1 < 0.0 { return -1.0 }
return Math.max(t0, 0.0)
}
# How much wider than itself a thing is treated as at `dist` metres: a fixed angle (twelve pixels of
# a 1080-tall screen), so a branch twenty metres off can be aimed at and the near ones are not easy
# to hit by accident
export function aim_slack_m(dist: float) -> float { return dist * (12.0 / 540.0) * Math.tan(AimView.fov() * 0.5) }
# metres from the body to (x, z)
export function aim_range(x: float, z: float) -> float {
let dx = x - AimView.body_x()
let dz = z - AimView.body_z()
return Math.sqrt(dx * dx + dz * dz)
}
# is the line from the camera to a point clear? (the ground and every still thing: the port's)
export function aim_clear_to(x: float, y: float, z: float) -> bool { return AimView.clear(AimView.x(), AimView.y(), AimView.z(), x, y, z) }
# is the ray clear up to `back` short of t, where it enters a thing? Asked a body's width short, so
# a solid thing (a trunk, a tent) whose collider is wider than its aim does not hide itself
export function aim_clear_at(t: float, back: float) -> bool {
let u = t - Math.max(back, 0.05)
if u < 0.1 { return true }
return aim_clear_to(AimView.x() + AimView.fx() * u, AimView.y() + AimView.fy() * u, AimView.z() + AimView.fz() * u)
}
# how far the ray passes from a body's axis as a share of its width: 0 dead centre, 1 at the edge
export function aim_miss(cx: float, cz: float, r: float) -> float {
let ox = AimView.x() - cx
let oz = AimView.z() - cz
let a = Math.sqrt(AimView.fx() * AimView.fx() + AimView.fz() * AimView.fz())
if a < 0.0001 { return 0.0 }
let proj = (ox * AimView.fx() + oz * AimView.fz()) / a
return Math.sqrt(Math.max(ox * ox + oz * oz - proj * proj, 0.0)) / Math.max(r, 0.01)
}