feat(packages): ludic.aim - what the crosshair is on: a ray through the middle of the screen against upright cylinders widened by a fixed angular forgiveness, the nearest one the ground does not hide, reach floored at three metres, and which thing the use key means (that, nothing and say why, or the nearest); the probe of the ground's own answer against the aim; the camera, the ground and the body through a port

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 09:07:51 +03:00
parent 24a35f1174
commit dc71fc986c
10 changed files with 399 additions and 0 deletions

View file

@ -0,0 +1,67 @@
# ray.ludic - the ray through the crosshair against an upright cylinder, the forgiveness around a
# body, and the ground 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 of the ground? Without it the crosshair reads through a
# rise; within arm's length nothing is hidden, or a prop on a slope hides behind its own slope.
export function aim_clear_to(x: float, y: float, z: float) -> bool {
let cx = AimView.x()
let cy = AimView.y()
let cz = AimView.z()
if (x - cx) * (x - cx) + (z - cz) * (z - cz) < 9.0 { return true }
for s in 1 .. 10 {
let f = float(s) / 10.0
if AimView.ground(Math.lerp(cx, x, f), Math.lerp(cz, z, f)) - 0.35 > Math.lerp(cy, y, f) { return false }
}
return true
}
# 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)
}