All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.ru.casperix.math.intersection.float32.Intersection2Float.kt Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
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 - 2024 Weber Informatics LLC | Privacy Policy