it.unibo.alchemist.util.Ranges.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of alchemist-implementationbase Show documentation
Show all versions of alchemist-implementationbase Show documentation
Abstract, incarnation independent implementations of the Alchemist's interfaces. Provides support for those who want to write incarnations.
/*
* Copyright (C) 2010-2023, Danilo Pianini and contributors
* listed, for each module, in the respective subproject's build.gradle.kts file.
*
* This file is part of Alchemist, and is distributed under the terms of the
* GNU General Public License, with a linking exception,
* as described in the file LICENSE in the Alchemist distribution's top directory.
*/
package it.unibo.alchemist.util
import it.unibo.alchemist.model.geometry.Vector2D
import it.unibo.alchemist.util.Ranges.minus
import org.apache.commons.lang3.ObjectUtils
/**
* A collection of extension functions that enrich [ClosedRange].
*/
object Ranges {
/**
* Creates a [ClosedRange] from a couple of unordered values.
*/
fun > rangeFromUnordered(bound1: T, bound2: T): ClosedRange =
ObjectUtils.min(bound1, bound2)..ObjectUtils.max(bound1, bound2)
/**
* Checks whether two ranges coincide. It's a different way of checking if they're equals,
* which doesn't depend on their actual implementation. Note that the way you obtain a range
* may influence the actual class used, and you can have two coincident ranges which are not
* equal because of different classes.
*/
fun > ClosedRange.coincidesWith(other: ClosedRange): Boolean =
start == other.start && endInclusive == other.endInclusive
/**
* Checks whether the range contains the [other] one.
* This method is faster than checking if the range contains both of the endpoints of [other].
*/
fun > ClosedRange.contains(other: ClosedRange): Boolean =
start <= other.start && endInclusive >= other.endInclusive
/**
* Checks whether the range intersects the [other] one.
*/
fun > ClosedRange.intersects(other: ClosedRange): Boolean =
start <= other.endInclusive && endInclusive >= other.start
/*
* Finds the intersection between two ranges which are guaranteed to intersect.
*/
private fun > ClosedRange.unsafeIntersect(other: ClosedRange): ClosedRange =
ObjectUtils.max(start, other.start)..ObjectUtils.min(endInclusive, other.endInclusive)
/**
* Finds the intersection between two ranges, the resulting range may feature a single value
* (if the ranges only share an endpoint) or can be null, if they don't intersect at all.
*/
fun > ClosedRange.intersect(other: ClosedRange): ClosedRange? = when {
intersects(other) -> unsafeIntersect(other)
else -> null
}
/**
* Checks whether two ranges intersect, excluding their bounds (i.e., excluding both
* [ClosedRange.start] and [ClosedRange.endInclusive]). This means false is returned in
* case the ranges share a single endpoint.
*/
fun > ClosedRange.intersectsBoundsExcluded(other: ClosedRange): Boolean =
start < other.endInclusive && endInclusive > other.start
/**
* Performs a subtraction between ranges. The operation can produce an empty list (e.g. if
* the current range is contained in the [other] one), a list featuring a single element,
* or a list featuring two elements (e.g. if the current range contains the [other] one).
*/
infix operator fun > ClosedRange.minus(other: ClosedRange): List> = when {
other.contains(this) -> emptyList()
!intersects(other) -> listOf(this)
start >= other.start -> listOf(rangeFromUnordered(endInclusive, other.endInclusive))
endInclusive <= other.endInclusive -> listOf(rangeFromUnordered(start, other.start))
else -> listOf(rangeFromUnordered(start, other.start), rangeFromUnordered(endInclusive, other.endInclusive))
}
/**
* Subtracts all the given ranges from the current one. See [ClosedRange.minus].
*/
fun > ClosedRange.subtractAll(others: List>): List> = when {
others.isEmpty() -> listOf(this)
else -> (this - others.first()).flatMap { it.subtractAll(others.drop(1)) }
}
/**
* Given a non-empty list of points represented as vectors, this method finds the extreme
* coordinates (i.e. min and max coordinates) either on the X-axis or the Y-axis. [getXCoords]
* indicates which coordinates to extract, these are used to create the returned [ClosedRange].
*/
private fun > List.findExtremeCoords(getXCoords: Boolean): ClosedRange {
val selector = { v: V -> v.x.takeIf { getXCoords } ?: v.y }
val min = minByOrNull(selector)?.run(selector)
val max = maxByOrNull(selector)?.run(selector)
require(min != null && max != null) { "no point could be found" }
return min..max
}
/**
* Given a non-empty list of points represented as vectors, this method finds the extreme coordinates
* (i.e. min and max coordinates) on the X-axis, these are used to create the returned [ClosedRange].
*/
fun > List.findExtremeCoordsOnX(): ClosedRange = findExtremeCoords(true)
/**
* Given a non-empty list of points represented as vectors, this method finds the extreme coordinates
* (i.e. min and max coordinates) on the Y-axis, these are used to create the returned [ClosedRange].
*/
fun > List.findExtremeCoordsOnY(): ClosedRange = findExtremeCoords(false)
}