315 lines
8.9 KiB
Kotlin
315 lines
8.9 KiB
Kotlin
|
|
@file:Suppress("unused", "LiftReturnOrAssignment", "MemberVisibilityCanBePrivate")
|
|
|
|
package ru.dbotthepony.kstarbound.math
|
|
|
|
import ru.dbotthepony.kommons.guava.immutableList
|
|
import ru.dbotthepony.kommons.math.intersectRectangles
|
|
import ru.dbotthepony.kommons.math.rectangleContainsRectangle
|
|
import ru.dbotthepony.kommons.util.IStruct2d
|
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
|
import kotlin.math.absoluteValue
|
|
import kotlin.math.max
|
|
import kotlin.math.min
|
|
|
|
/**
|
|
* Axis Aligned Bounding Box, represented by two points, [mins] as lowermost corner of BB,
|
|
* and [maxs] as uppermost corner of BB
|
|
*/
|
|
data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|
constructor(line: Line2d) : this(
|
|
Vector2d(min(line.p0.x, line.p1.x), min(line.p0.y, line.p1.y)),
|
|
Vector2d(max(line.p0.x, line.p1.x), max(line.p0.y, line.p1.y)),
|
|
)
|
|
|
|
init {
|
|
// require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
|
|
// require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
|
|
}
|
|
|
|
val isEmpty: Boolean
|
|
get() = mins.x > maxs.x || mins.y > maxs.y
|
|
|
|
val isZero: Boolean
|
|
get() = mins == maxs
|
|
|
|
val isEmptyOrZero: Boolean
|
|
get() = isEmpty || isZero
|
|
|
|
operator fun plus(other: AABB) = AABB(mins + other.mins, maxs + other.maxs)
|
|
operator fun minus(other: AABB) = AABB(mins - other.mins, maxs - other.maxs)
|
|
operator fun times(other: AABB) = AABB(mins * other.mins, maxs * other.maxs)
|
|
operator fun div(other: AABB) = AABB(mins / other.mins, maxs / other.maxs)
|
|
|
|
operator fun plus(other: Vector2d) = AABB(mins + other, maxs + other)
|
|
operator fun minus(other: Vector2d) = AABB(mins - other, maxs - other)
|
|
operator fun times(other: Vector2d) = AABB(mins * other, maxs * other)
|
|
operator fun div(other: Vector2d) = AABB(mins / other, maxs / other)
|
|
|
|
operator fun times(other: Double) = AABB(mins * other, maxs * other)
|
|
operator fun div(other: Double) = AABB(mins / other, maxs / other)
|
|
|
|
val xSpan get() = maxs.x - mins.x
|
|
val ySpan get() = maxs.y - mins.y
|
|
val centre get() = (mins + maxs) * 0.5
|
|
|
|
val A get() = mins
|
|
val B get() = Vector2d(mins.x, maxs.y)
|
|
val C get() = maxs
|
|
val D get() = Vector2d(maxs.x, mins.y)
|
|
|
|
val bottomLeft get() = A
|
|
val topLeft get() = B
|
|
val topRight get() = C
|
|
val bottomRight get() = D
|
|
|
|
val width get() = maxs.x - mins.x
|
|
val height get() = maxs.y - mins.y
|
|
|
|
val volume get() = max(width * height, 0.0)
|
|
|
|
val extents get() = Vector2d(width * 0.5, height * 0.5)
|
|
|
|
val diameter get() = mins.distance(maxs)
|
|
val radius get() = diameter / 2.0
|
|
|
|
val perimeter get() = (xSpan + ySpan) * 2.0
|
|
|
|
val edges: List<Line2d> by lazy {
|
|
immutableList {
|
|
accept(Line2d(Vector2d(mins.x, mins.y), Vector2d(maxs.x, mins.y)))
|
|
accept(Line2d(Vector2d(maxs.x, mins.y), Vector2d(maxs.x, maxs.y)))
|
|
accept(Line2d(Vector2d(maxs.x, maxs.y), Vector2d(mins.x, maxs.y)))
|
|
accept(Line2d(Vector2d(mins.x, maxs.y), Vector2d(mins.x, mins.y)))
|
|
}
|
|
}
|
|
|
|
fun isInside(point: IStruct2d): Boolean {
|
|
return point.component1() in mins.x .. maxs.x && point.component2() in mins.y .. maxs.y
|
|
}
|
|
|
|
fun isInside(x: Double, y: Double): Boolean {
|
|
return x in mins.x .. maxs.x && y in mins.y .. maxs.y
|
|
}
|
|
|
|
operator fun contains(point: IStruct2d) = isInside(point)
|
|
|
|
/**
|
|
* Checks whenever is this AABB intersect with [other]
|
|
*
|
|
* This method consider they intersect even if only one of axis are equal
|
|
*/
|
|
fun intersect(other: AABB): Boolean {
|
|
return intersectRectangles(mins, maxs, other.mins, other.maxs)
|
|
}
|
|
|
|
fun intersect(line: Line2d): Boolean {
|
|
return line.p0 in this || line.p1 in this || edges.any { it.intersect(line).intersects }
|
|
}
|
|
|
|
/**
|
|
* Returns whenever [other] is contained (encased) inside this AABB
|
|
*/
|
|
operator fun contains(other: AABB): Boolean {
|
|
return rectangleContainsRectangle(mins, maxs, other.mins, other.maxs)
|
|
}
|
|
|
|
/**
|
|
* Checks whenever is this AABB intersect with [other]
|
|
*
|
|
* This method DOES NOT consider they intersect if only one of axis are equal
|
|
*/
|
|
fun intersectWeak(other: AABB): Boolean {
|
|
if (maxs.x == other.mins.x || mins.x == other.maxs.x || maxs.y == other.mins.y || mins.y == other.maxs.y)
|
|
return false
|
|
|
|
return intersectRectangles(mins, maxs, other.mins, other.maxs)
|
|
}
|
|
|
|
fun intersectionDepth(other: AABB): Vector2d {
|
|
val xDepth: Double
|
|
val yDepth: Double
|
|
|
|
val thisCentre = centre
|
|
val otherCentre = other.centre
|
|
|
|
if (thisCentre.x > otherCentre.x) {
|
|
// считаем, что мы вошли справа
|
|
xDepth = mins.x - other.maxs.x
|
|
} else {
|
|
// считаем, что мы вошли слева
|
|
xDepth = maxs.x - other.mins.x
|
|
}
|
|
|
|
if (thisCentre.y > otherCentre.y) {
|
|
// считаем, что мы вошли сверху
|
|
yDepth = mins.y - other.maxs.y
|
|
} else {
|
|
// считаем, что мы вошли снизу
|
|
yDepth = maxs.x - other.mins.x
|
|
}
|
|
|
|
return Vector2d(xDepth, yDepth)
|
|
}
|
|
|
|
fun distance(other: AABB): Double {
|
|
val intersectX: Boolean
|
|
val intersectY: Boolean
|
|
|
|
if (ySpan <= other.ySpan)
|
|
intersectY = mins.y in other.mins.y .. other.maxs.y || maxs.y in other.mins.y .. other.maxs.y
|
|
else
|
|
intersectY = other.mins.y in mins.y .. maxs.y || other.maxs.y in mins.y .. maxs.y
|
|
|
|
if (xSpan <= other.xSpan)
|
|
intersectX = mins.x in other.mins.x .. other.maxs.x || maxs.x in other.mins.x .. other.maxs.x
|
|
else
|
|
intersectX = other.mins.x in mins.x .. maxs.x || other.maxs.x in mins.x .. maxs.x
|
|
|
|
if (intersectY && intersectX) {
|
|
return 0.0
|
|
}
|
|
|
|
if (intersectX) {
|
|
return (mins.y - other.maxs.y).absoluteValue.coerceAtMost((maxs.y - other.mins.y).absoluteValue)
|
|
} else {
|
|
return (mins.x - other.maxs.x).absoluteValue.coerceAtMost((maxs.x - other.mins.x).absoluteValue)
|
|
}
|
|
}
|
|
|
|
fun pushOutFrom(other: AABB): Vector2d {
|
|
if (!intersect(other))
|
|
return Vector2d.ZERO
|
|
|
|
val depth = intersectionDepth(other)
|
|
|
|
if (depth.x.absoluteValue < depth.y.absoluteValue) {
|
|
return Vector2d(x = depth.x)
|
|
} else {
|
|
return Vector2d(y = depth.y)
|
|
}
|
|
}
|
|
|
|
fun overlap(other: AABB): AABB {
|
|
return AABB(
|
|
Vector2d(max(mins.x, other.mins.x), max(mins.y, other.mins.y)),
|
|
Vector2d(min(maxs.x, other.maxs.x), min(maxs.y, other.maxs.y)),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Returns AABB which contains both AABBs
|
|
*/
|
|
fun combine(other: AABB): AABB {
|
|
if (contains(other)) return this
|
|
|
|
val minX = mins.x.coerceAtMost(other.mins.x)
|
|
val minY = mins.y.coerceAtMost(other.mins.y)
|
|
val maxX = maxs.x.coerceAtLeast(other.maxs.x)
|
|
val maxY = maxs.y.coerceAtLeast(other.maxs.y)
|
|
|
|
return AABB(Vector2d(minX, minY), Vector2d(maxX, maxY))
|
|
}
|
|
|
|
/**
|
|
* Returns AABB which contains this AABB and specified point
|
|
*/
|
|
fun expand(x: Double, y: Double): AABB {
|
|
if (isInside(x, y))
|
|
return this
|
|
|
|
return AABB(
|
|
mins.coerceAtMost(x, y),
|
|
maxs.coerceAtLeast(x, y)
|
|
)
|
|
}
|
|
|
|
fun expand(value: IStruct2d) = expand(value.component1(), value.component2())
|
|
fun padded(value: IStruct2d) = expand(value.component1(), value.component2())
|
|
fun padded(x: Double, y: Double) = expand(x, y)
|
|
|
|
/**
|
|
* Returns AABB which edges are expanded by [x] and [y] along their normals
|
|
*/
|
|
fun enlarge(x: Double, y: Double): AABB {
|
|
if (x == 0.0 && y == 0.0) return this
|
|
|
|
return AABB(
|
|
Vector2d(mins.x - x, mins.y - y),
|
|
Vector2d(maxs.x + x, maxs.y + y),
|
|
)
|
|
}
|
|
|
|
fun encasingIntAABB(): AABBi {
|
|
return AABBi(
|
|
Vector2i(roundByAbsoluteValue(mins.x), roundByAbsoluteValue(mins.y)),
|
|
Vector2i(roundByAbsoluteValue(maxs.x), roundByAbsoluteValue(maxs.y)),
|
|
)
|
|
}
|
|
|
|
companion object {
|
|
/**
|
|
* Rectangle with given [width] and [height]
|
|
*/
|
|
fun rectangle(pos: IStruct2d, width: Double, height: Double = width): AABB {
|
|
val (x, y) = pos
|
|
|
|
return AABB(
|
|
Vector2d(x - width / 2.0, y - height / 2.0),
|
|
Vector2d(x + width / 2.0, y + height / 2.0),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Rectangle with given [width] * 2 and [height] * 2
|
|
*/
|
|
fun withSide(pos: IStruct2d, width: Double, height: Double = width): AABB {
|
|
val (x, y) = pos
|
|
|
|
return AABB(
|
|
Vector2d(x - width, y - height),
|
|
Vector2d(x + width, y + height),
|
|
)
|
|
}
|
|
|
|
fun leftCorner(pos: Vector2d, width: Double, height: Double): AABB {
|
|
return AABB(pos, pos + Vector2d(width, height))
|
|
}
|
|
|
|
fun ofPoints(points: Collection<Vector2d>): AABB {
|
|
if (points.isEmpty())
|
|
return NEVER
|
|
|
|
val minX = points.minOf { it.x }
|
|
val maxX = points.maxOf { it.x }
|
|
val minY = points.minOf { it.y }
|
|
val maxY = points.maxOf { it.y }
|
|
|
|
return AABB(
|
|
Vector2d(minX, minY),
|
|
Vector2d(maxX, maxY),
|
|
)
|
|
}
|
|
|
|
@JvmName("ofPointsI")
|
|
fun ofPoints(points: Collection<Vector2i>): AABB {
|
|
if (points.isEmpty())
|
|
return NEVER
|
|
|
|
val minX = points.minOf { it.x }.toDouble()
|
|
val maxX = points.maxOf { it.x }.toDouble()
|
|
val minY = points.minOf { it.y }.toDouble()
|
|
val maxY = points.maxOf { it.y }.toDouble()
|
|
|
|
return AABB(
|
|
Vector2d(minX, minY),
|
|
Vector2d(maxX, maxY),
|
|
)
|
|
}
|
|
|
|
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
|
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
|
}
|
|
}
|