- 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>
64 lines
2.8 KiB
Text
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)
|
|
}
|