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() { private val pair = gson.getAdapter>() 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 } }