![JAR search and dependency download from the Maven repository](/logo.png)
commonMain.ru.casperix.math.intersection.float32.Intersection2Float.kt Maven / Gradle / Ivy
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
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy