commonMain.korlibs.math.geom.VectorsDouble.kt Maven / Gradle / Ivy
package korlibs.math.geom
import korlibs.math.*
import korlibs.number.*
import kotlin.math.*
typealias Point = Vector2D
typealias Point2 = Vector2D
typealias Point3 = Vector3D
data class Vector3D(val x: Double, val y: Double, val z: Double)
data class Vector4D(val x: Double, val y: Double, val z: Double, val w: Double)
fun Vector3F.toDouble(): Vector3D = Vector3D(x.toDouble(), y.toDouble(), z.toDouble())
fun Vector3D.toFloat(): Vector3F = Vector3F(x, y, z)
data class Vector2D(val x: Double, val y: Double) : IsAlmostEquals {
//constructor(x: Float, y: Float) : this(float2PackOf(x, y))
constructor(x: Float, y: Float) : this(x.toDouble(), y.toDouble())
constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
constructor(x: Double, y: Int) : this(x.toDouble(), y.toDouble())
constructor(x: Int, y: Double) : this(x.toDouble(), y.toDouble())
constructor(x: Float, y: Int) : this(x.toDouble(), y.toDouble())
constructor(x: Int, y: Float) : this(x.toDouble(), y.toDouble())
//constructor(p: Vector2) : this(p.raw)
constructor() : this(0.0, 0.0)
//constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
//constructor(x: Float, y: Float) : this(x.toDouble(), y.toDouble())
fun copy(x: Float = this.x.toFloat(), y: Float = this.y.toFloat()): Vector2D = Vector2D(x, y)
inline operator fun unaryMinus(): Vector2D = Vector2D(-x, -y)
inline operator fun unaryPlus(): Vector2D = this
inline operator fun plus(that: Vector2D): Vector2D = Vector2D(x + that.x, y + that.y)
inline operator fun minus(that: Vector2D): Vector2D = Vector2D(x - that.x, y - that.y)
inline operator fun times(that: Vector2D): Vector2D = Vector2D(x * that.x, y * that.y)
inline operator fun div(that: Vector2D): Vector2D = Vector2D(x / that.x, y / that.y)
inline operator fun rem(that: Vector2D): Vector2D = Vector2D(x % that.x, y % that.y)
inline operator fun times(scale: Double): Vector2D = Vector2D(x * scale, y * scale)
inline operator fun times(scale: Float): Vector2D = this * scale.toDouble()
inline operator fun times(scale: Int): Vector2D = this * scale.toDouble()
inline operator fun div(scale: Double): Vector2D = Vector2D(x / scale, y / scale)
inline operator fun div(scale: Float): Vector2D = this / scale.toDouble()
inline operator fun div(scale: Int): Vector2D = this / scale.toDouble()
inline operator fun rem(scale: Double): Vector2D = Vector2D(x % scale, y % scale)
inline operator fun rem(scale: Float): Vector2D = this % scale.toDouble()
inline operator fun rem(scale: Int): Vector2D = this % scale.toDouble()
fun avgComponent(): Double = x * 0.5 + y * 0.5
fun minComponent(): Double = min(x, y)
fun maxComponent(): Double = max(x, y)
fun distanceTo(x: Double, y: Double): Double = hypot(x - this.x, y - this.y)
fun distanceTo(x: Float, y: Float): Double = distanceTo(x.toDouble(), y.toDouble())
fun distanceTo(x: Int, y: Int): Double = this.distanceTo(x.toDouble(), y.toDouble())
fun distanceTo(that: Vector2D): Double = distanceTo(that.x, that.y)
infix fun cross(that: Vector2D): Double = crossProduct(this, that)
infix fun dot(that: Vector2D): Double = ((this.x * that.x) + (this.y * that.y))
fun angleTo(other: Vector2D, up: Vector2D = UP): Angle = Angle.between(this.x, this.y, other.x, other.y, up)
val angle: Angle get() = angle()
fun angle(up: Vector2D = UP): Angle = Angle.between(0.0, 0.0, this.x, this.y, up)
operator fun get(component: Int): Double = when (component) {
0 -> x; 1 -> y
else -> throw IndexOutOfBoundsException("Point doesn't have $component component")
}
val length: Double get() = hypot(x, y)
val lengthSquared: Double get() {
val x = x
val y = y
return x*x + y*y
}
val magnitude: Double get() = hypot(x, y)
val normalized: Vector2D get() = this * (1f / magnitude)
val unit: Vector2D get() = this / length
/** Normal vector. Rotates the vector/point -90 degrees (not normalizing it) */
fun toNormal(): Vector2D = Vector2D(-this.y, this.x)
val int: Vector2I get() = Vector2I(x.toInt(), y.toInt())
val intRound: Vector2I get() = Vector2I(x.roundToInt(), y.roundToInt())
fun roundDecimalPlaces(places: Int): Vector2D = Vector2D(x.roundDecimalPlaces(places), y.roundDecimalPlaces(places))
fun round(): Vector2D = Vector2D(round(x), round(y))
fun ceil(): Vector2D = Vector2D(ceil(x), ceil(y))
fun floor(): Vector2D = Vector2D(floor(x), floor(y))
//fun copy(x: Double = this.x, y: Double = this.y): Vector2 = Vector2D(x, y)
override fun isAlmostEquals(other: Vector2D, epsilon: Double): Boolean =
this.x.isAlmostEquals(other.x, epsilon) && this.y.isAlmostEquals(other.y, epsilon)
val niceStr: String get() = "(${x.niceStr}, ${y.niceStr})"
fun niceStr(decimalPlaces: Int): String = "(${x.niceStr(decimalPlaces)}, ${y.niceStr(decimalPlaces)})"
override fun toString(): String = niceStr
fun reflected(normal: Vector2D): Vector2D {
val d = this
val n = normal
return d - 2.0 * (d dot n) * n
}
/** Vector2 with inverted (1f / v) components to this */
fun inv(): Vector2D = Vector2D(1.0 / x, 1.0 / y)
fun isNaN(): Boolean = this.x.isNaN() && this.y.isNaN()
val absoluteValue: Vector2D get() = Vector2D(abs(x), abs(y))
companion object {
val ZERO = Vector2D(0.0, 0.0)
val NaN = Vector2D(Double.NaN, Double.NaN)
/** Mathematically typical LEFT, matching screen coordinates (-1, 0) */
val LEFT = Vector2D(-1.0, 0.0)
/** Mathematically typical RIGHT, matching screen coordinates (+1, 0) */
val RIGHT = Vector2D(+1.0, 0.0)
/** Mathematically typical UP (0, +1) */
val UP = Vector2D(0.0, +1.0)
/** UP using screen coordinates as reference (0, -1) */
val UP_SCREEN = Vector2D(0.0, -1.0)
/** Mathematically typical DOWN (0, -1) */
val DOWN = Vector2D(0.0, -1.0)
/** DOWN using screen coordinates as reference (0, +1) */
val DOWN_SCREEN = Vector2D(0.0, +1.0)
inline operator fun invoke(x: Number, y: Number): Vector2D = Vector2D(x.toDouble(), y.toDouble())
//inline operator fun invoke(x: Float, y: Float): Vector2D = Vector2D(x.toDouble(), y.toDouble())
//fun fromRaw(raw: Float2Pack) = Vector2D(raw)
/** Constructs a point from polar coordinates determined by an [angle] and a [length]. Angle 0 is pointing to the right, and the direction is counter-clock-wise for up=UP and clock-wise for up=UP_SCREEN */
inline fun polar(x: Float, y: Float, angle: Angle, length: Float = 1f, up: Vector2D = UP): Vector2D = Vector2D(x + angle.cosine(up) * length, y + angle.sine(up) * length)
inline fun polar(x: Double, y: Double, angle: Angle, length: Double = 1.0, up: Vector2D = UP): Vector2D = Vector2D(x + angle.cosine(up) * length, y + angle.sine(up) * length)
inline fun polar(base: Vector2D, angle: Angle, length: Double = 1.0, up: Vector2D = UP): Vector2D = polar(base.x, base.y, angle, length, up)
inline fun polar(angle: Angle, length: Double = 1.0, up: Vector2D = UP): Vector2D = polar(0.0, 0.0, angle, length, up)
inline fun middle(a: Vector2D, b: Vector2D): Vector2D = (a + b) * 0.5
fun angle(ax: Double, ay: Double, bx: Double, by: Double, up: Vector2D = UP): Angle = Angle.between(ax, ay, bx, by, up)
fun angle(x1: Double, y1: Double, x2: Double, y2: Double, x3: Double, y3: Double, up: Vector2D = UP): Angle = Angle.between(x1 - x2, y1 - y2, x1 - x3, y1 - y3, up)
fun angle(a: Vector2D, b: Vector2D, up: Vector2D = UP): Angle = Angle.between(a, b, up)
fun angle(p1: Vector2D, p2: Vector2D, p3: Vector2D, up: Vector2D = UP): Angle = Angle.between(p1 - p2, p1 - p3, up)
fun angleArc(a: Vector2D, b: Vector2D, up: Vector2D = UP): Angle = Angle.fromRadians(acos((a dot b) / (a.length * b.length))).adjustFromUp(up)
fun angleFull(a: Vector2D, b: Vector2D, up: Vector2D = UP): Angle = Angle.between(a, b, up)
fun distance(a: Double, b: Double): Double = abs(a - b)
fun distance(x1: Double, y1: Double, x2: Double, y2: Double): Double = hypot(x1 - x2, y1 - y2)
fun distance(x1: Float, y1: Float, x2: Float, y2: Float): Double = hypot(x1 - x2, y1 - y2).toDouble()
fun distance(x1: Int, y1: Int, x2: Int, y2: Int): Double = hypot(x1.toDouble() - x2.toDouble(), y1.toDouble() - y2.toDouble())
fun distance(a: Vector2D, b: Vector2D): Double = distance(a.x, a.y, b.x, b.y)
fun distance(a: Vector2I, b: Vector2I): Double = distance(a.x, a.y, b.x, b.y)
fun distanceSquared(a: Vector2D, b: Vector2D): Double = distanceSquared(a.x, a.y, b.x, b.y)
fun distanceSquared(a: Vector2I, b: Vector2I): Int = distanceSquared(a.x, a.y, b.x, b.y)
fun distanceSquared(x1: Double, y1: Double, x2: Double, y2: Double): Double = square(x1 - x2) + square(y1 - y2)
fun distanceSquared(x1: Float, y1: Float, x2: Float, y2: Float): Float = square(x1 - x2) + square(y1 - y2)
fun distanceSquared(x1: Int, y1: Int, x2: Int, y2: Int): Int = square(x1 - x2) + square(y1 - y2)
@Deprecated("Likely searching for orientation")
inline fun direction(a: Vector2D, b: Vector2D): Vector2D = b - a
fun compare(l: Vector2D, r: Vector2D): Int = compare(l.x, l.y, r.x, r.y)
fun compare(lx: Float, ly: Float, rx: Float, ry: Float): Int = ly.compareTo(ry).let { ret -> if (ret == 0) lx.compareTo(rx) else ret }
fun compare(lx: Double, ly: Double, rx: Double, ry: Double): Int = ly.compareTo(ry).let { ret -> if (ret == 0) lx.compareTo(rx) else ret }
private fun square(x: Double): Double = x * x
private fun square(x: Float): Float = x * x
private fun square(x: Int): Int = x * x
fun dot(aX: Double, aY: Double, bX: Double, bY: Double): Double = (aX * bX) + (aY * bY)
fun dot(aX: Float, aY: Float, bX: Float, bY: Float): Float = (aX * bX) + (aY * bY)
fun dot(a: Vector2D, b: Vector2D): Double = dot(a.x, a.y, b.x, b.y)
fun isCollinear(p1: Point, p2: Point, p3: Point): Boolean =
isCollinear(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y)
fun isCollinear(p1x: Double, p1y: Double, p2x: Double, p2y: Double, p3x: Double, p3y: Double): Boolean {
val area2 = (p1x * (p2y - p3y) + p2x * (p3y - p1y) + p3x * (p1y - p2y)) // 2x triangle area
//println("($p1x, $p1y), ($p2x, $p2y), ($p3x, $p3y) :: area=$area2")
return area2.isAlmostZero()
//val div1 = (p2x - p1x) / (p2y - p1y)
//val div2 = (p1x - p3x) / (p1y - p3y)
//val result = (div1 - div2).absoluteValue
//println("result=$result, div1=$div1, div2=$div2, xa=$p1x, ya=$p1y, x=$p2x, y=$p2y, xb=$p3x, yb=$p3y")
//if (div1.isInfinite() != div2.isInfinite()) return false
//return result.isAlmostZero() || result.isInfinite()
}
fun isCollinear(xa: Float, ya: Float, x: Float, y: Float, xb: Float, yb: Float): Boolean = isCollinear(
xa.toDouble(), ya.toDouble(),
x.toDouble(), y.toDouble(),
xb.toDouble(), yb.toDouble(),
)
fun isCollinear(xa: Int, ya: Int, x: Int, y: Int, xb: Int, yb: Int): Boolean = isCollinear(
xa.toDouble(), ya.toDouble(),
x.toDouble(), y.toDouble(),
xb.toDouble(), yb.toDouble(),
)
// https://algorithmtutor.com/Computational-Geometry/Determining-if-two-consecutive-segments-turn-left-or-right/
/** < 0 left, > 0 right, 0 collinear */
fun orientation(p1: Vector2D, p2: Vector2D, p3: Vector2D, up: Vector2D = UP): Double = orientation(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, up)
fun orientation(ax: Float, ay: Float, bx: Float, by: Float, cx: Float, cy: Float, up: Vector2D = UP): Float {
Orientation.checkValidUpVector(up)
val res = crossProduct(cx - ax, cy - ay, bx - ax, by - ay)
return if (up.y > 0f) res else -res
}
fun orientation(ax: Double, ay: Double, bx: Double, by: Double, cx: Double, cy: Double, up: Vector2D = UP): Double {
Orientation.checkValidUpVector(up)
val res = crossProduct(cx - ax, cy - ay, bx - ax, by - ay)
return if (up.y > 0f) res else -res
}
fun crossProduct(ax: Float, ay: Float, bx: Float, by: Float): Float = (ax * by) - (bx * ay)
fun crossProduct(ax: Double, ay: Double, bx: Double, by: Double): Double = (ax * by) - (bx * ay)
fun crossProduct(p1: Vector2D, p2: Vector2D): Double = crossProduct(p1.x, p1.y, p2.x, p2.y)
fun minComponents(p1: Vector2D, p2: Vector2D): Vector2D = Vector2D(min(p1.x, p2.x), min(p1.y, p2.y))
fun minComponents(p1: Vector2D, p2: Vector2D, p3: Vector2D): Vector2D = Vector2D(
minOf(p1.x, p2.x, p3.x),
minOf(p1.y, p2.y, p3.y)
)
fun minComponents(p1: Vector2D, p2: Vector2D, p3: Vector2D, p4: Vector2D): Vector2D = Vector2D(
minOf(
p1.x,
p2.x,
p3.x,
p4.x
), minOf(p1.y, p2.y, p3.y, p4.y)
)
fun maxComponents(p1: Vector2D, p2: Vector2D): Vector2D = Vector2D(max(p1.x, p2.x), max(p1.y, p2.y))
fun maxComponents(p1: Vector2D, p2: Vector2D, p3: Vector2D): Vector2D = Vector2D(
maxOf(p1.x, p2.x, p3.x),
maxOf(p1.y, p2.y, p3.y)
)
fun maxComponents(p1: Vector2D, p2: Vector2D, p3: Vector2D, p4: Vector2D): Vector2D = Vector2D(
maxOf(
p1.x,
p2.x,
p3.x,
p4.x
), maxOf(p1.y, p2.y, p3.y, p4.y)
)
}
}
operator fun Int.times(v: Vector2D): Vector2D = v * this
operator fun Float.times(v: Vector2D): Vector2D = v * this
operator fun Double.times(v: Vector2D): Vector2D = v * this
fun Vector2D.toFloat(): Vector2F = Vector2F(x, y)
fun Vector2F.toDouble(): Vector2D = Vector2D(x, y)
fun abs(a: Vector2D): Vector2D = a.absoluteValue
fun min(a: Vector2D, b: Vector2D): Vector2D = Vector2D(min(a.x, b.x), min(a.y, b.y))
fun max(a: Vector2D, b: Vector2D): Vector2D = Vector2D(max(a.x, b.x), max(a.y, b.y))
fun Vector2D.clamp(min: Float, max: Float): Vector2D = clamp(min.toDouble(), max.toDouble())
fun Vector2D.clamp(min: Double, max: Double): Vector2D = Vector2D(x.clamp(min, max), y.clamp(min, max))
fun Vector2D.clamp(min: Vector2D, max: Vector2D): Vector2D = Vector2D(x.clamp(min.x, max.x), y.clamp(min.y, max.y))
fun Vector2D.toInt(): Vector2I = Vector2I(x.toInt(), y.toInt())
fun Vector2D.toIntCeil(): Vector2I = Vector2I(x.toIntCeil(), y.toIntCeil())
fun Vector2D.toIntRound(): Vector2I = Vector2I(x.toIntRound(), y.toIntRound())
fun Vector2D.toIntFloor(): Vector2I = Vector2I(x.toIntFloor(), y.toIntFloor())
fun Vector3D.toCylindrical(): CylindricalVector = CylindricalVector.fromCartesian(this)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy