commonMain.ru.casperix.math.intersection.float32.Intersection2Float.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of math Show documentation
Show all versions of math Show documentation
Simple set of geometric and other types
package ru.casperix.math.intersection.float32
import ru.casperix.math.angle.float32.RadianFloat
import ru.casperix.math.axis_aligned.float32.Box2f
import ru.casperix.math.curve.float32.Arc2f
import ru.casperix.math.curve.float32.Circle2f
import ru.casperix.math.curve.float32.ParametricCurve2f
import ru.casperix.math.geometry.*
import ru.casperix.math.geometry.builder.Triangulator
import ru.casperix.math.geometry.float32.Geometry2Float
import ru.casperix.math.geometry.float32.Geometry2Float.calculateDeterminant
import ru.casperix.math.geometry.float64.Geometry2Double.distPointToLine
import ru.casperix.math.intersection.ConvexHullIntersection
import ru.casperix.math.intersection.CurveIntersection
import ru.casperix.math.intersection.CurveIntersectionSolver
import ru.casperix.math.intersection.IntersectionApi
import ru.casperix.math.intersection.float64.Intersection2Double
import ru.casperix.math.straight_line.float32.LineSegment2f
import ru.casperix.math.test.FloatCompare
import ru.casperix.math.vector.float32.Vector2f
import ru.casperix.math.vector.rotateCCW
import ru.casperix.math.vector.toQuad
import ru.casperix.misc.toDoubleArray
import kotlin.math.absoluteValue
import kotlin.math.pow
import kotlin.math.sqrt
object Intersection2Float : IntersectionApi {
const val EPSILON = 0.0001f
fun hasPolygonWithCircle(a: Polygon2f, b: Circle2f): Boolean {
if (!hasPolygonWithPolygon(a, Box2f.byRadius(b.center, Vector2f(b.range)).toQuad())) {
return false
}
//TODO: now extremely rough and slow
Triangulator.point(b.center, b.range * 2f, 32).forEach {
if (hasPolygonWithPolygon(a, it)) {
return true
}
}
return false
}
fun hasBoxWithBox(A: Box2f, B: Box2f): Boolean {
if (A.max.y - B.min.y < 0f) return false
if (B.max.y - A.min.y < 0f) return false
if (A.max.x - B.min.x < 0f) return false
if (B.max.x - A.min.x < 0f) return false
return true
}
/**
* Calculate intersection (if exist) for convex polygon
* @return true - if intersection exist
*/
fun hasPolygonWithPolygon(a: Polygon2f, b: Polygon2f): Boolean {
val listA = a.getVertices().flatMap { listOf(it.x, it.y) }.toFloatArray().toDoubleArray()
val listB = b.getVertices().flatMap { listOf(it.x, it.y) }.toFloatArray().toDoubleArray()
return ConvexHullIntersection.hasIntersection(listA, listB)
}
/**
* Calculate intersection (if exist) for convex polygon
* @return minimal-translation-vector if intersection exist / another case: null
*/
fun getPolygonWithPolygon(a: Polygon2f, b: Polygon2f): Vector2f? {
val listA = a.getVertices().flatMap { listOf(it.x, it.y) }.toFloatArray().toDoubleArray()
val listB = b.getVertices().flatMap { listOf(it.x, it.y) }.toFloatArray().toDoubleArray()
return ConvexHullIntersection.getPenetration(listA, listB)?.toVector2f()
}
fun hasPolygonWithSegment(a: Polygon2f, b: LineSegment2f): Boolean {
return hasPolygonWithPolygon(a, CustomPolygon(b.start, b.finish))
}
fun getCurveWithCurve(A: ParametricCurve2f, B: ParametricCurve2f): List {
return CurveIntersectionSolver.intersectionCurveWithCurve(A, B)
}
fun getArcWithArc(A: Arc2f, B: Arc2f): List {
val D = A.center.distTo(B.center)
if (D > A.range + B.range) return emptyList()
val x = (A.range.pow(2) - B.range.pow(2) + D.pow(2)) / (2f * D)
val y = D - x
val z = sqrt(A.range.pow(2) - x.pow(2))
val AB = (B.center - A.center).normalize()
val vZ = AB.rotateCCW()
val candidates = listOf(
A.center + AB * x + vZ * z,
A.center + AB * x - vZ * z
)
return candidates.filter {
val aAngle = RadianFloat.byDirection(it - A.center)
val bAngle = RadianFloat.byDirection(it - B.center)
A.isAngleInside(aAngle) && B.isAngleInside(bAngle)
}
}
override fun hasPointWithTriangle(P: Vector2f, triangle: Triangle2f): Boolean {
// Compute vectors
val v0 = triangle.v2 - triangle.v0
val v1 = triangle.v1 - triangle.v0
val v2 = P - triangle.v0
// Compute dot products
val dot00 = v0.dot(v0)
val dot01 = v0.dot(v1)
val dot02 = v0.dot(v2)
val dot11 = v1.dot(v1)
val dot12 = v1.dot(v2)
// Compute barycentric coordinates
val invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01)
val u = (dot11 * dot02 - dot01 * dot12) * invDenom
val v = (dot00 * dot12 - dot01 * dot02) * invDenom
// Check if point is in triangle
return (u >= 0) && (v >= 0) && (u + v <= 1.0)
}
override fun hasPointWithPolygon(P: Vector2f, polygon: Polygon): Boolean {
val vertices = polygon.getVertices()
if (vertices.isEmpty()) return false
if (vertices.size == 1) return P == vertices[0]
if (vertices.size == 2) return Intersection2Float.hasPointWithLine(P, vertices[0], vertices[1])
repeat(polygon.getTriangleAmount()) { index ->
if (hasPointWithTriangle(P, polygon.getTriangle(index))) return true
}
return false
}
override fun hasTriangleWithTriangle(a: Triangle2f, b: Triangle2f): Boolean {
val listA = floatArrayOf(a.v0.x, a.v0.y, a.v1.x, a.v1.y, a.v2.x, a.v2.y).toDoubleArray()
val listB = floatArrayOf(b.v0.x, b.v0.y, b.v1.x, b.v1.y, b.v2.x, b.v2.y).toDoubleArray()
return ConvexHullIntersection.hasIntersection(listA, listA.size, listB, listB.size)
}
override fun hasQuadWithQuad(a: Quad2f, b: Quad2f): Boolean {
val listA = floatArrayOf(a.v0.x, a.v0.y, a.v1.x, a.v1.y, a.v2.x, a.v2.y, a.v3.x, a.v3.y).toDoubleArray()
val listB = floatArrayOf(b.v0.x, b.v0.y, b.v1.x, b.v1.y, b.v2.x, b.v2.y, b.v3.x, b.v3.y).toDoubleArray()
return ConvexHullIntersection.hasIntersection(listA, listA.size, listB, listB.size)
}
override fun hasQuadWithTriangle(a: Quad2f, b: Triangle2f): Boolean {
val listA = floatArrayOf(b.v0.x, b.v0.y, b.v1.x, b.v1.y, b.v2.x, b.v2.y).toDoubleArray()
val listB = floatArrayOf(a.v0.x, a.v0.y, a.v1.x, a.v1.y, a.v2.x, a.v2.y, a.v3.x, a.v3.y).toDoubleArray()
return ConvexHullIntersection.hasIntersection(listA, listA.size, listB, listB.size)
}
override fun hasPointWithLine(P: Vector2f, T: Line2f): Boolean {
val T2 = T.convert { it.toVector2d() }
val P2 = P.toVector2d()
return distPointToLine(
P2,
T2
) < Intersection2Double.EPSILON && (P2.distTo(T2.v0) + P2.distTo(T2.v1) - T2.v0.distTo(T2.v1)).absoluteValue < Intersection2Double.EPSILON
}
override fun hasPointWithSegment(P: Vector2f, T: Line2f): Boolean {
val dest = Geometry2Float.distPointToSegment(P, T)
return dest < FloatCompare.defaultError
}
fun hasPointWithSegment(P: Vector2f, T: LineSegment2f): Boolean {
return hasPointWithSegment(P, T.toLine())
}
fun getSegmentWithSegmentWithoutEnd(S: Line2f, T: Line2f): Vector2f? {
val intersection = getSegmentWithSegment(S, T) ?: return null
if (intersection == S.v0 || intersection == S.v1 || intersection == T.v0 || intersection == T.v1) return null
return intersection
}
fun hasSegmentWithSegmentWithoutEnd(S: LineSegment2f, T: LineSegment2f): Boolean {
return getSegmentWithSegmentWithoutEnd(S, T) != null
}
fun getSegmentWithSegmentWithoutEnd(S: LineSegment2f, T: LineSegment2f): Vector2f? {
val intersection = getSegmentWithSegment(S, T) ?: return null
if (intersection == S.start || intersection == S.finish || intersection == T.start || intersection == T.finish) return null
return intersection
}
/**
* https://martin-thoma.com/how-to-check-if-two-line-segments-intersect
*/
fun isPointOnLine(segment: LineSegment2f, point: Vector2f): Boolean {
// Move the image, so that a.first is on (0|0)
val aTmp = segment.finish - segment.start
val bTmp = point - segment.start
val r = aTmp.cross(bTmp)
return r.absoluteValue < EPSILON
}
/**
* https://martin-thoma.com/how-to-check-if-two-line-segments-intersect
*/
private fun lineSegmentTouchesOrCrossesLine(a: LineSegment2f, b: LineSegment2f): Boolean {
if (isPointOnLine(a, b.start) || isPointOnLine(a, b.finish)) return true
val isRightBStart = Geometry2Float.getPointAroundRay(a.start, a.finish, b.start, 0f) == PointAroundRay.RIGHT
val isRightBFinish = Geometry2Float.getPointAroundRay(a.start, a.finish, b.finish, 0f) == PointAroundRay.RIGHT
return isRightBStart xor isRightBFinish
}
private fun hasLinesIntersect(a: LineSegment2f, b: LineSegment2f): Boolean {
val box1 = Box2f.byCorners(a.start, a.finish)
val box2 = Box2f.byCorners(b.start, b.finish)
return hasBoxWithBox(box1, box2) && lineSegmentTouchesOrCrossesLine(a, b) && lineSegmentTouchesOrCrossesLine(b, a)
}
fun getSegmentWithSegment(a: LineSegment2f, b: LineSegment2f): Vector2f? {
// The algorithm is very sensitive to precision (calculate in double)
val result = Intersection2Double.intersectSegments(
a.start.x.toDouble(),
a.start.y.toDouble(),
a.finish.x.toDouble(),
a.finish.y.toDouble(),
b.start.x.toDouble(),
b.start.y.toDouble(),
b.finish.x.toDouble(),
b.finish.y.toDouble()
)?.toVector2f()
if (result != null) {
return result
}
// collinear lines can have intersection...
if (hasLinesIntersect(a, b)) {
if (hasPointWithSegment(b.start, a)) return b.start
if (hasPointWithSegment(b.finish, a)) return b.finish
if (hasPointWithSegment(a.start, b)) return a.start
if (hasPointWithSegment(a.finish, b)) return a.finish
//TODO: not trivial case
return a.start
}
return null
}
override fun getSegmentWithSegment(a: Line2f, b: Line2f): Vector2f? {
// The algorithm is very sensitive to precision (calculate in double)
return getSegmentWithSegment(a.toSegment(), b.toSegment())
}
override fun getLineWithLine(S: Line2f, T: Line2f): Vector2f? {
// The algorithm is very sensitive to precision (calculate in double)
return Intersection2Double.intersectInfinityLines(
S.v0.x.toDouble(),
S.v0.y.toDouble(),
S.v1.x.toDouble(),
S.v1.y.toDouble(),
T.v0.x.toDouble(),
T.v0.y.toDouble(),
T.v1.x.toDouble(),
T.v1.y.toDouble()
)?.toVector2f()
}
override fun hasPointWithQuad(point: Vector2f, value: Quad2f): Boolean {
val ccw = calculateDeterminant(value.v0, value.v1, value.v2)
val factor = if (ccw >= 0) 1f else -1f
val e0 = calculateDeterminant(value.v0, value.v1, point)
if (e0 * factor < 0) return false
val e1 = calculateDeterminant(value.v1, value.v2, point)
if (e1 * factor < 0) return false
val e2 = calculateDeterminant(value.v2, value.v3, point)
if (e2 * factor < 0) return false
val e3 = calculateDeterminant(value.v3, value.v0, point)
if (e3 * factor < 0) return false
return true
}
}