Skip to content

ArenaGeometry.kt

src/jvmMain/kotlin/afield/math/linalg/arena/ArenaGeometry.kt · 80 lines

package afield.math.linalg.arena

Arena-vector versions of a few afield.math.linalg geometry tests, for use inside frameScratch.

Covers the closest point on a segment and on an axis-aligned box, point-in-box, and ray-box intersection. Each one reads and writes arena vectors, so it allocates nothing on the heap.

import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

fun FrameScope.closestPointOnSegment

Allocation-free arena form of afield.math.linalg.closestPointOnSegment.

fun FrameScope.closestPointOnSegment(
    point: ArenaVec3f,
    a: ArenaVec3f,
    b: ArenaVec3f,
    dest: ArenaVec3f,
): ArenaVec3f {
    val abx = b.x - a.x; val aby = b.y - a.y; val abz = b.z - a.z
    val denominator = abx * abx + aby * aby + abz * abz
    val t = if (denominator == 0f) 0f else
        ((point.x - a.x) * abx + (point.y - a.y) * aby + (point.z - a.z) * abz) / denominator
    val clamped = t.coerceIn(0f, 1f)
    return dest.set(a.x + abx * clamped, a.y + aby * clamped, a.z + abz * clamped)
}

fun FrameScope.closestPointOnAabb

Allocation-free arena form of afield.math.linalg.closestPointOnAabb.

fun FrameScope.closestPointOnAabb(
    point: ArenaVec3f,
    minimum: ArenaVec3f,
    maximum: ArenaVec3f,
    dest: ArenaVec3f,
): ArenaVec3f = dest.set(
    point.x.coerceIn(minimum.x, maximum.x),
    point.y.coerceIn(minimum.y, maximum.y),
    point.z.coerceIn(minimum.z, maximum.z),
)

fun FrameScope.containsPointAabb

fun FrameScope.containsPointAabb(
    point: ArenaVec3f,
    minimum: ArenaVec3f,
    maximum: ArenaVec3f,
): Boolean = point.x in minimum.x..maximum.x &&
    point.y in minimum.y..maximum.y &&
    point.z in minimum.z..maximum.z

fun FrameScope.intersectRayAabb

Writes forward (near, far) ray distances without allocating heap vectors.

fun FrameScope.intersectRayAabb(
    origin: ArenaVec3f,
    direction: ArenaVec3f,
    minimum: ArenaVec3f,
    maximum: ArenaVec3f,
    nearFar: ArenaVec2f,
    epsilon: Float = 1e-12f,
): Boolean {
    nearFar.set(0f, Float.POSITIVE_INFINITY)
    return clipSlab(origin.x, direction.x, minimum.x, maximum.x, nearFar, epsilon) &&
        clipSlab(origin.y, direction.y, minimum.y, maximum.y, nearFar, epsilon) &&
        clipSlab(origin.z, direction.z, minimum.z, maximum.z, nearFar, epsilon)
}

private fun FrameScope.clipSlab(
    origin: Float,
    direction: Float,
    low: Float,
    high: Float,
    nearFar: ArenaVec2f,
    epsilon: Float,
): Boolean {
    if (abs(direction) <= epsilon) return origin in low..high
    var a = (low - origin) / direction
    var b = (high - origin) / direction
    if (a > b) { val swap = a; a = b; b = swap }
    nearFar.x = max(nearFar.x, a)
    nearFar.y = min(nearFar.y, b)
    return nearFar.x <= nearFar.y
}