diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Tangent.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Tangent.kt new file mode 100644 index 000000000..889debecb --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Tangent.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.geometry + +import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo +import space.kscience.kmath.linear.DoubleLinearSpace.plus +import space.kscience.kmath.linear.Point +import kotlin.math.absoluteValue +import kotlin.math.atan2 +import kotlin.math.pow +import kotlin.math.sign +import kotlin.math.sin +import kotlin.math.cos +import space.kscience.kmath.geometry.Euclidean2DSpace.vector +import space.kscience.kmath.geometry.Euclidean2DSpace.dot +import space.kscience.kmath.geometry.Euclidean2DSpace.scale +import space.kscience.kmath.geometry.Euclidean2DSpace.add + + +public class Segment( + public val startPoint: DoubleVector2D, + public val terminalPoint: DoubleVector2D, + public val length: Double = startPoint.distanceTo(terminalPoint) +) { + public override operator fun equals(other: Any?): Boolean { + return if (other is Segment) { + startPoint.x.equalFloat(other.startPoint.x) && startPoint.y.equalFloat(other.startPoint.y) && + terminalPoint.x.equalFloat(other.terminalPoint.x) && terminalPoint.y.equalFloat(other.terminalPoint.y) + } else { + false + } + } +} + +public const val maxFloatDelta: Double = 0.000001 +public fun Double.equalFloat(other: Double): Boolean = kotlin.math.abs(this - other) < maxFloatDelta + + + +public fun tangentsToCircles( + startCircle: Circle2D, + terminalCircle: Circle2D +): kotlin.collections.MutableMap { + val R1 = startCircle.radius + val R2 = terminalCircle.radius + val d = Segment(startCircle.center, terminalCircle.center).length + val angle1 = atan2(terminalCircle.center.x - startCircle.center.x, terminalCircle.center.y - startCircle.center.y) + var r: Double + var angle2: Double + val routes = mapOf("RSR" to Pair(R1, R2), + "RSL" to Pair(R1, -R2), + "LSR" to Pair(-R1, R2), + "LSL" to Pair(-R1, -R2)) + val segments = mutableMapOf() + for ((route, r1r2) in routes) { + val r1 = r1r2.first + val r2 = r1r2.second + r = if (r1.sign == r2.sign) { + r1.absoluteValue - r2.absoluteValue + } else { + r1.absoluteValue + r2.absoluteValue + } + val L = (d * d - r * r).pow(0.5) + angle2 = if (r1.absoluteValue > r2.absoluteValue) { + angle1 + r1.sign * atan2(r.absoluteValue, L) + } else { + angle1 - r2.sign * atan2(r.absoluteValue, L) + } + val W = vector(-cos(angle2), sin(angle2)) + segments[route] = Segment(add(startCircle.center, scale(W, r1)), + add(terminalCircle.center, scale(W, r2))) + } + return segments +} \ No newline at end of file diff --git a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/TangentTest.kt b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/TangentTest.kt new file mode 100644 index 000000000..beac8d15f --- /dev/null +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/TangentTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.geometry + +import space.kscience.kmath.geometry.Euclidean2DSpace.vector +import kotlin.test.Test +import kotlin.test.assertEquals + +class TangentTest { + @Test + fun tangent() { + val c1 = Circle2D(vector(0.0, 0.0), 1.0) + val c2 = Circle2D(vector(4.0, 0.0), 1.0) + val routes = arrayListOf("RSR", "RSL", "LSR", "LSL") + val segments = arrayListOf( + Segment(startPoint = vector(0.0, 1.0), + terminalPoint = vector(4.0, 1.0)), + Segment(startPoint = vector(0.5, 0.8660254), + terminalPoint = vector(3.5, -0.8660254)), + Segment(startPoint = vector(0.5, -0.8660254), + terminalPoint = vector(3.5, 0.8660254)), + Segment(startPoint = vector(0.0, -1.0), + terminalPoint = vector(4.0, -1.0)) + ) + + val tangentMap = tangentsToCircles(c1, c2) + val tangentMapKeys = tangentMap.keys.toList() + val tangentMapValues = tangentMap.values.toList() + + assertEquals(routes, tangentMapKeys) + for (i in segments.indices) { + assertEquals(segments[i], tangentMapValues[i]) + } +// assertEquals(segments, tangentMapValues) + } +} \ No newline at end of file