commonMain.korlibs.math.geom.MRectangle.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of korma-jvm Show documentation
Show all versions of korma-jvm Show documentation
Mathematic library for Multiplatform Kotlin 1.3
The newest version!
package korlibs.math.geom
import korlibs.datastructure.*
import korlibs.math.annotations.*
import korlibs.math.internal.*
import korlibs.math.interpolation.*
import korlibs.math.isAlmostEquals
import korlibs.math.roundDecimalPlaces
@KormaMutableApi
@Deprecated("Use Rectangle")
data class MRectangle(
var x: Double, var y: Double,
var width: Double, var height: Double
) : MutableInterpolable, Interpolable, Sizeable, MSizeable {
operator fun contains(that: Point) = contains(that.x, that.y)
operator fun contains(that: MPoint) = contains(that.x, that.y)
operator fun contains(that: MPointInt) = contains(that.x, that.y)
fun contains(x: Double, y: Double) = (x >= left && x < right) && (y >= top && y < bottom)
fun contains(x: Float, y: Float) = contains(x.toDouble(), y.toDouble())
fun contains(x: Int, y: Int) = contains(x.toDouble(), y.toDouble())
val area: Double get() = width * height
val isEmpty: Boolean get() = width == 0.0 && height == 0.0
val isNotEmpty: Boolean get() = width != 0.0 || height != 0.0
val mutable: MRectangle get() = MRectangle(x, y, width, height)
val topLeft: Point get() = Point(left, top)
val topRight: Point get() = Point(right, top)
val bottomLeft: Point get() = Point(left, bottom)
val bottomRight: Point get() = Point(right, bottom)
val center: Point get() = Point((right + left) * 0.5, (bottom + top) * 0.5)
fun isAlmostEquals(other: MRectangle, epsilon: Double = 0.001): Boolean =
this.x.isAlmostEquals(other.x, epsilon) &&
this.y.isAlmostEquals(other.y, epsilon) &&
this.width.isAlmostEquals(other.width, epsilon) &&
this.height.isAlmostEquals(other.height, epsilon)
/**
* Circle that touches or contains all the corners ([topLeft], [topRight], [bottomLeft], [bottomRight]) of the rectangle.
*/
fun outerCircle(): Circle {
return Circle(center, Point.distance(center, topRight))
}
fun without(padding: Margin): MRectangle = MRectangle.fromBounds(
left + padding.left,
top + padding.top,
right - padding.right,
bottom - padding.bottom
)
fun with(margin: Margin): MRectangle = MRectangle.fromBounds(
left - margin.left,
top - margin.top,
right + margin.right,
bottom + margin.bottom
)
infix fun intersects(that: MRectangle): Boolean = intersectsX(that) && intersectsY(that)
infix fun intersectsX(that: MRectangle): Boolean =
that.left <= this.right && that.right >= this.left
infix fun intersectsY(that: MRectangle): Boolean =
that.top <= this.bottom && that.bottom >= this.top
fun intersection(that: MRectangle, target: MRectangle = MRectangle()) =
if (this intersects that) target.setBounds(
kotlin.math.max(this.left, that.left), kotlin.math.max(this.top, that.top),
kotlin.math.min(this.right, that.right), kotlin.math.min(this.bottom, that.bottom)
) else null
companion object {
val POOL: ConcurrentPool = ConcurrentPool({ it.clear() }) { MRectangle() }
// Creates a rectangle from 2 points where the (x,y) is the top left point
// with the same width and height as the point. The 2 points provided can be
// in any arbitrary order, the rectangle will be created from the projected
// rectangle of the 2 points.
//
// Here is one example
// Rect XY point1
// │ │
// ▼ ▼
// ┌────────┐
// │ │
// │ │
// └────────┘
// ▲
// │
// point2
//
// Here is another example
// point1 (Rect XY)
// │
// ▼
// ┌────────┐
// │ │
// │ │
// └────────┘
// ▲
// │
// point2
operator fun invoke(point1: MPoint, point2: MPoint): MRectangle {
val left = minOf(point1.x, point2.x)
val top = minOf(point1.y, point2.y)
val right = maxOf(point1.x, point2.x)
val bottom = maxOf(point1.y, point2.y)
return MRectangle(left, top, right - left, bottom - top)
}
operator fun invoke(): MRectangle = MRectangle(0.0, 0.0, 0.0, 0.0)
operator fun invoke(x: Int, y: Int, width: Int, height: Int): MRectangle = MRectangle(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble())
operator fun invoke(x: Float, y: Float, width: Float, height: Float): MRectangle = MRectangle(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble())
operator fun invoke(topLeft: MPoint, size: MSize): MRectangle = MRectangle(topLeft.x, topLeft.y, size.width, size.height)
operator fun invoke(topLeft: Point, size: Size): MRectangle = MRectangle(topLeft.x, topLeft.y, size.width, size.height)
fun fromBounds(left: Double, top: Double, right: Double, bottom: Double): MRectangle = MRectangle().setBounds(left, top, right, bottom)
fun fromBounds(left: Int, top: Int, right: Int, bottom: Int): MRectangle = MRectangle().setBounds(left, top, right, bottom)
fun fromBounds(left: Float, top: Float, right: Float, bottom: Float): MRectangle = MRectangle().setBounds(left, top, right, bottom)
fun fromBounds(point1: MPoint, point2: MPoint): MRectangle = MRectangle(point1, point2)
fun isContainedIn(a: MRectangle, b: MRectangle): Boolean = a.x >= b.x && a.y >= b.y && a.x + a.width <= b.x + b.width && a.y + a.height <= b.y + b.height
}
fun setXY(x: Double, y: Double) {
this.x = x
this.y = y
}
var left: Double ; get() = x; set(value) { width += (x - value); x = value }
var top: Double ; get() = y; set(value) { height += (y - value); y = value }
var right: Double ; get() = x + width ; set(value) { width = value - x }
var bottom: Double ; get() = y + height ; set(value) { height = value - y }
val pos: Point get() = Point(x, y)
val mPosition: MPoint get() = MPoint(x, y)
override val size: Size get() = Size(width, height)
override val mSize: MSize get() = MSize(width, height)
fun setToBounds(left: Double, top: Double, right: Double, bottom: Double): MRectangle = setTo(left, top, right - left, bottom - top)
fun setTo(x: Double, y: Double, width: Double, height: Double): MRectangle {
this.x = x
this.y = y
this.width = width
this.height = height
return this
}
fun setTo(x: Int, y: Int, width: Int, height: Int): MRectangle = setTo(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble())
fun setTo(x: Float, y: Float, width: Float, height: Float): MRectangle = setTo(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble())
fun copyFrom(that: Rectangle): MRectangle = setTo(that.x, that.y, that.width, that.height)
fun copyFrom(that: MRectangle): MRectangle = setTo(that.x, that.y, that.width, that.height)
fun setBounds(left: Double, top: Double, right: Double, bottom: Double): MRectangle = setTo(left, top, right - left, bottom - top)
fun setBounds(left: Int, top: Int, right: Int, bottom: Int): MRectangle = setBounds(left.toDouble(), top.toDouble(), right.toDouble(), bottom.toDouble())
fun setBounds(left: Float, top: Float, right: Float, bottom: Float): MRectangle = setBounds(left.toDouble(), top.toDouble(), right.toDouble(), bottom.toDouble())
operator fun times(scale: Double): MRectangle = MRectangle(x * scale, y * scale, width * scale, height * scale)
operator fun times(scale: Float): MRectangle = this * scale.toDouble()
operator fun times(scale: Int): MRectangle = this * scale.toDouble()
operator fun div(scale: Double): MRectangle = MRectangle(x / scale, y / scale, width / scale, height / scale)
operator fun div(scale: Float): MRectangle = this / scale.toDouble()
operator fun div(scale: Int): MRectangle = this / scale.toDouble()
operator fun contains(that: MRectangle) = isContainedIn(that, this)
fun setToIntersection(a: MRectangle, b: MRectangle): MRectangle? =
if (a.intersection(b, this) != null) this else null
fun setToUnion(a: MRectangle, b: MRectangle): MRectangle = setToBounds(
kotlin.math.min(a.left, b.left),
kotlin.math.min(a.top, b.top),
kotlin.math.max(a.right, b.right),
kotlin.math.max(a.bottom, b.bottom)
)
infix fun intersection(that: MRectangle) = intersection(that, MRectangle())
fun displaced(dx: Double, dy: Double) = MRectangle(this.x + dx, this.y + dy, width, height)
fun displaced(dx: Float, dy: Float) = displaced(dx.toDouble(), dy.toDouble())
fun displaced(dx: Int, dy: Int) = displaced(dx.toDouble(), dy.toDouble())
fun displace(dx: Double, dy: Double) = setTo(this.x + dx, this.y + dy, this.width, this.height)
fun displace(dx: Float, dy: Float) = displace(dx.toDouble(), dy.toDouble())
fun displace(dx: Int, dy: Int) = displace(dx.toDouble(), dy.toDouble())
fun place(item: MSize, anchor: Anchor, scale: ScaleMode, out: MRectangle = MRectangle()): MRectangle =
place(item.width, item.height, anchor, scale, out)
fun place(
width: Double,
height: Double,
anchor: Anchor,
scale: ScaleMode,
out: MRectangle = MRectangle()
): MRectangle {
val (ow, oh) = scale.transform(Size(width, height), Size(this.width, this.height))
val x = (this.width - ow) * anchor.doubleX
val y = (this.height - oh) * anchor.doubleY
return out.setTo(x, y, ow.toDouble(), oh.toDouble())
}
fun inflate(
left: Double,
top: Double = left,
right: Double = left,
bottom: Double = top
): MRectangle = setBounds(this.left - left, this.top - top, this.right + right, this.bottom + bottom)
fun clear(): MRectangle = setTo(0.0, 0.0, 0.0, 0.0)
fun clone(): MRectangle = MRectangle(x, y, width, height)
fun setToAnchoredRectangle(item: MRectangle, anchor: Anchor, container: MRectangle): MRectangle =
setToAnchoredRectangle(item.mSize, anchor, container)
fun setToAnchoredRectangle(item: MSize, anchor: Anchor, container: MRectangle): MRectangle =
setToAnchoredRectangle(item.immutable, anchor, container)
fun setToAnchoredRectangle(item: Size, anchor: Anchor, container: MRectangle): MRectangle = setTo(
(container.x + anchor.doubleX * (container.width - item.width)).toFloat(),
(container.y + anchor.doubleY * (container.height - item.height)).toFloat(),
item.width,
item.height
)
fun applyTransform(m: MMatrix): MRectangle {
val tl = m.transform(left, top)
val tr = m.transform(right, top)
val bl = m.transform(left, bottom)
val br = m.transform(right, bottom)
val minX = korlibs.math.min(tl.x, tr.x, bl.x, br.x)
val minY = korlibs.math.min(tl.y, tr.y, bl.y, br.y)
val maxX = korlibs.math.max(tl.x, tr.x, bl.x, br.x)
val maxY = korlibs.math.max(tl.y, tr.y, bl.y, br.y)
//val l = m.transformX(left, top)
//val t = m.transformY(left, top)
//val r = m.transformX(right, bottom)
//val b = m.transformY(right, bottom)
return setBounds(minX, minY, maxX, maxY)
}
//override fun toString(): String = "Rectangle([${left.niceStr}, ${top.niceStr}]-[${right.niceStr}, ${bottom.niceStr}])"
override fun toString(): String = "Rectangle(x=${x.niceStr}, y=${y.niceStr}, width=${width.niceStr}, height=${height.niceStr})"
fun toStringBounds(): String = "Rectangle([${left.niceStr},${top.niceStr}]-[${right.niceStr},${bottom.niceStr}])"
fun toStringSize(): String = "Rectangle([${left.niceStr},${top.niceStr}],[${width.niceStr},${height.niceStr}])"
fun toStringCompat(): String = "Rectangle(x=${left.niceStr}, y=${top.niceStr}, w=${width.niceStr}, h=${height.niceStr})"
override fun equals(other: Any?): Boolean = other is MRectangle
&& x.isAlmostEquals(other.x)
&& y.isAlmostEquals(other.y)
&& width.isAlmostEquals(other.width)
&& height.isAlmostEquals(other.height)
override fun interpolateWith(ratio: Ratio, other: MRectangle): MRectangle =
MRectangle().setToInterpolated(ratio, this, other)
override fun setToInterpolated(ratio: Ratio, l: MRectangle, r: MRectangle): MRectangle =
this.setTo(
ratio.interpolate(l.x, r.x),
ratio.interpolate(l.y, r.y),
ratio.interpolate(l.width, r.width),
ratio.interpolate(l.height, r.height)
)
val immutable: Rectangle get() = Rectangle(x, y, width, height)
fun toInt(): MRectangleInt = MRectangleInt(x.toInt(), y.toInt(), width.toInt(), height.toInt())
fun floor(): MRectangle = setTo(
kotlin.math.floor(x),
kotlin.math.floor(y),
kotlin.math.floor(width),
kotlin.math.floor(height)
)
fun round(): MRectangle = setTo(
kotlin.math.round(x),
kotlin.math.round(y),
kotlin.math.round(width),
kotlin.math.round(height)
)
fun roundDecimalPlaces(places: Int): MRectangle = setTo(
x.roundDecimalPlaces(places),
y.roundDecimalPlaces(places),
width.roundDecimalPlaces(places),
height.roundDecimalPlaces(places)
)
fun ceil(): MRectangle = setTo(
kotlin.math.ceil(x),
kotlin.math.ceil(y),
kotlin.math.ceil(width),
kotlin.math.ceil(height)
)
fun normalize() {
if (width < 0.0) {
x += width
width = -width
}
if (height < 0.0) {
y += height
height = -height
}
}
fun expand(left: Float, top: Float, right: Float, bottom: Float): MRectangle =
this.setToBounds(this.left - left, this.top - top, this.right + right, this.bottom + bottom)
fun expand(left: Int, top: Int, right: Int, bottom: Int): MRectangle =
expand(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
fun expand(margin: Margin): MRectangle =
expand(margin.left, margin.top, margin.right, margin.bottom)
fun expand(margin: MarginInt): MRectangle =
expand(margin.left, margin.top, margin.right, margin.bottom)
fun toRectangle(): Rectangle = Rectangle(x, y, width, height)
@KormaMutableApi fun asInt(): MRectangleInt = MRectangleInt(this)
@KormaMutableApi val int: MRectangleInt get() = MRectangleInt(x, y, width, height)
val value: Rectangle get() = Rectangle(x, y, width, height)
}
fun Rectangle.copyTo(out: MRectangle = MRectangle()): MRectangle = out.copyFrom(this)
@KormaMutableApi
fun Iterable.bounds(target: MRectangle = MRectangle()): MRectangle {
var first = true
var left = 0.0
var right = 0.0
var top = 0.0
var bottom = 0.0
for (r in this) {
if (first) {
left = r.left
right = r.right
top = r.top
bottom = r.bottom
first = false
} else {
left = kotlin.math.min(left, r.left)
right = kotlin.math.max(right, r.right)
top = kotlin.math.min(top, r.top)
bottom = kotlin.math.max(bottom, r.bottom)
}
}
return target.setBounds(left, top, right, bottom)
}