196 lines
5.2 KiB
Kotlin
196 lines
5.2 KiB
Kotlin
package ru.dbotthepony.kstarbound.math
|
|
|
|
import com.google.gson.Gson
|
|
import com.google.gson.TypeAdapter
|
|
import com.google.gson.annotations.JsonAdapter
|
|
import com.google.gson.stream.JsonReader
|
|
import com.google.gson.stream.JsonWriter
|
|
import ru.dbotthepony.kommons.util.IStruct2d
|
|
import ru.dbotthepony.kommons.util.KOptional
|
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|
import ru.dbotthepony.kstarbound.io.readVector2d
|
|
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
|
import ru.dbotthepony.kstarbound.json.getAdapter
|
|
import java.io.DataInputStream
|
|
import java.io.DataOutputStream
|
|
import kotlin.math.absoluteValue
|
|
import kotlin.math.max
|
|
import kotlin.math.min
|
|
|
|
private operator fun Vector2d.compareTo(other: Vector2d): Int {
|
|
var cmp = x.compareTo(other.x)
|
|
if (cmp == 0) cmp = y.compareTo(y)
|
|
return cmp
|
|
}
|
|
|
|
@JsonAdapter(Line2d.Adapter::class)
|
|
data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector2d(isLegacy), stream.readVector2d(isLegacy))
|
|
|
|
val normal: Vector2d
|
|
|
|
init {
|
|
val diff = (p1 - p0).unitVector
|
|
normal = Vector2d(-diff.y, diff.x)
|
|
}
|
|
|
|
val center: Vector2d
|
|
get() = p0 + difference * 0.5
|
|
|
|
val aabb: AABB get() {
|
|
return AABB(
|
|
Vector2d(min(p0.x, p1.x), min(p0.y, p1.y)),
|
|
Vector2d(max(p0.x, p1.x), max(p0.y, p1.y)),
|
|
)
|
|
}
|
|
|
|
operator fun plus(other: IStruct2d): Line2d {
|
|
return Line2d(p0 + other, p1 + other)
|
|
}
|
|
|
|
operator fun minus(other: IStruct2d): Line2d {
|
|
return Line2d(p0 - other, p1 - other)
|
|
}
|
|
|
|
operator fun times(other: IStruct2d): Line2d {
|
|
return Line2d(p0 * other, p1 * other)
|
|
}
|
|
|
|
operator fun times(other: Double): Line2d {
|
|
return Line2d(p0 * other, p1 * other)
|
|
}
|
|
|
|
data class Intersection(val intersects: Boolean, val point: Vector2d?, val t: Double?, val coincides: Boolean, val glances: Boolean) {
|
|
companion object {
|
|
val EMPTY = Intersection(false, null, null, false, false)
|
|
}
|
|
}
|
|
|
|
val difference: Vector2d
|
|
get() = p1 - p0
|
|
|
|
fun reverse(): Line2d {
|
|
return Line2d(p1, p0)
|
|
}
|
|
|
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeStruct2d(p0, isLegacy)
|
|
stream.writeStruct2d(p1, isLegacy)
|
|
}
|
|
|
|
/**
|
|
* * if > 0.0 - left side
|
|
* * if < 0.0 - right side
|
|
* * if = 0.0 - rests upon
|
|
*/
|
|
fun isLeft(p2: IStruct2d): Double {
|
|
return (p1.component1() - p0.component1()) * (p2.component2() - p0.component2()) - (p2.component1() - p0.component1()) * (p1.component2() - p0.component2())
|
|
}
|
|
|
|
// original source of this intersection algorithm:
|
|
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
|
// article "Intersection of two lines in three-space" by Ronald Goldman, published in Graphics Gems, page 304
|
|
fun intersect(other: Line2d, infinite: Boolean = false): Intersection {
|
|
val (c, d) = other
|
|
|
|
val ab = difference
|
|
val cd = other.difference
|
|
|
|
val abCross = p0.cross(p1)
|
|
val cdCross = c.cross(d)
|
|
|
|
val denominator = ab.cross(cd)
|
|
val xNumber = abCross * cd.x - cdCross * ab.x
|
|
val yNumber = abCross * cd.y - cdCross * ab.y
|
|
|
|
if (denominator.absoluteValue <= NEAR_ZERO) {
|
|
if (xNumber.absoluteValue <= NEAR_ZERO && yNumber.absoluteValue <= NEAR_ZERO) {
|
|
val intersects = infinite || (p0 >= c && p0 <= d) || (c >= p0 && c <= p1)
|
|
var point: Vector2d? = null
|
|
var t = 0.0
|
|
|
|
if (intersects) {
|
|
if (infinite) {
|
|
point = Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)
|
|
} else {
|
|
point = if (p0 < c) c else p0
|
|
}
|
|
}
|
|
|
|
if (p0 < c) {
|
|
if (c.x != p0.x) {
|
|
t = (c.x - p0.x) / ab.x
|
|
} else {
|
|
t = (c.y - p0.y) / ab.y
|
|
}
|
|
} else if (p0 > d) {
|
|
if (d.x != p0.x) {
|
|
t = (d.x - p0.x) / ab.x
|
|
} else {
|
|
t = (d.y - p0.y) / ab.y
|
|
}
|
|
}
|
|
|
|
return Intersection(intersects, point, t, true, intersects)
|
|
} else {
|
|
return Intersection.EMPTY
|
|
}
|
|
} else {
|
|
val ta = (c - p0).cross(cd) / denominator
|
|
val tb = (c - p0).cross(ab) / denominator
|
|
|
|
val intersects = infinite || (ta in 0.0 .. 1.0 && tb in 0.0 .. 1.0)
|
|
|
|
return Intersection(
|
|
intersects = intersects,
|
|
t = ta,
|
|
point = (p1 - p0) * ta + p0,
|
|
coincides = false,
|
|
glances = !infinite && intersects && (ta <= NEAR_ZERO || ta >= NEAR_ONE || tb <= NEAR_ZERO || tb >= NEAR_ONE)
|
|
)
|
|
}
|
|
}
|
|
|
|
fun project(axis: IStruct2d): Double {
|
|
val diff = difference
|
|
val (x, y) = axis
|
|
return ((x - p0.x) * diff.x + (y - p0.y) * diff.y) / diff.lengthSquared
|
|
}
|
|
|
|
fun distanceTo(other: IStruct2d, infinite: Boolean = false): Double {
|
|
var proj = project(other)
|
|
|
|
if (!infinite)
|
|
proj = proj.coerceIn(0.0, 1.0)
|
|
|
|
return (Vector2d(other) - p0 + difference * proj).length
|
|
}
|
|
|
|
fun distanceTo(other: Vector2d, infinite: Boolean = false): Double {
|
|
var proj = project(other)
|
|
|
|
if (!infinite)
|
|
proj = proj.coerceIn(0.0, 1.0)
|
|
|
|
return (other - p0 + difference * proj).length
|
|
}
|
|
|
|
class Adapter(gson: Gson) : TypeAdapter<Line2d>() {
|
|
private val pair = gson.getAdapter<Pair<Vector2d, Vector2d>>()
|
|
|
|
override fun write(out: JsonWriter, value: Line2d) {
|
|
pair.write(out, value.p0 to value.p1)
|
|
}
|
|
|
|
override fun read(`in`: JsonReader): Line2d {
|
|
val (a, b) = pair.read(`in`)
|
|
return Line2d(a, b)
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
const val NEAR_ZERO = Double.MIN_VALUE * 2.0
|
|
const val NEAR_ONE = 1.0 - NEAR_ZERO
|
|
}
|
|
}
|