From bef317677c9a1c3496595dca8999a06ead1c3f85 Mon Sep 17 00:00:00 2001 From: Artyom Degtyarev Date: Wed, 15 Feb 2023 14:36:58 +0300 Subject: [PATCH] tangentsToCircle fixed --- .../space/kscience/kmath/geometry/Circle2D.kt | 39 ++++++- .../space/kscience/kmath/geometry/Line.kt | 10 ++ .../space/kscience/kmath/geometry/Tangent.kt | 108 +++++++++--------- .../kscience/kmath/geometry/TangentTest.kt | 24 ++-- .../kscience/kmath/geometry/testUtils.kt | 3 + 5 files changed, 116 insertions(+), 68 deletions(-) diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Circle2D.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Circle2D.kt index 8beef6fee..a26a592da 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Circle2D.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Circle2D.kt @@ -6,7 +6,8 @@ package space.kscience.kmath.geometry import kotlinx.serialization.Serializable -import kotlin.math.PI +import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo +import kotlin.math.* /** * A circle in 2D space @@ -17,4 +18,40 @@ public data class Circle2D( public val radius: Double ) +public fun Circle2D.tangentsToCircle(other: Circle2D): kotlin.collections.MutableMap> { + val R1 = this.radius + val R2 = other.radius + val line = LineSegment(this.center, other.center) + val d = line.begin.distanceTo(line.end) + val angle1 = atan2(other.center.x - this.center.x, other.center.y - this.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 = Euclidean2DSpace.vector(-cos(angle2), sin(angle2)) + segments[route] = LineSegment( + Euclidean2DSpace.add(this.center, Euclidean2DSpace.scale(W, r1)), + Euclidean2DSpace.add(other.center, Euclidean2DSpace.scale(W, r2)) + ) + } + return segments +} + public val Circle2D.circumference: Double get() = radius * 2 * PI diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt index ab322ddca..bb03b5a93 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt @@ -27,5 +27,15 @@ public fun LineSegment.line(algebra: GeometrySpace): Line Line(begin, end - begin) } +public fun equalLineSegments(line1: LineSegment, line2: LineSegment): Boolean { + val maxFloatDelta = 0.000001 + return line1.begin.x.equalFloat(line2.begin.x) && line1.begin.y.equalFloat(line2.begin.y) && + line1.end.x.equalFloat(line2.end.x) && line1.end.y.equalFloat(line2.end.y) +// return line1.begin == line2.begin && line1.end == line2.end +} + +public fun Double.equalFloat(other: Double, maxFloatDelta: Double = 0.000001): + Boolean = kotlin.math.abs(this - other) < maxFloatDelta + public typealias LineSegment2D = LineSegment public typealias LineSegment3D = LineSegment 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 index 889debecb..cfcb78115 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Tangent.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Tangent.kt @@ -5,9 +5,6 @@ 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 @@ -15,63 +12,64 @@ 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 +import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo -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 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 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 +//public fun tangentsToCircles( +// startCircle: Circle2D, +// terminalCircle: Circle2D +//): kotlin.collections.MutableMap> { +// val R1 = startCircle.radius +// val R2 = terminalCircle.radius +// val line = LineSegment(startCircle.center, terminalCircle.center) +// val d = line.begin.distanceTo(line.end) +// 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] = LineSegment(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 index beac8d15f..fd3002e19 100644 --- a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/TangentTest.kt +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/TangentTest.kt @@ -8,6 +8,7 @@ package space.kscience.kmath.geometry import space.kscience.kmath.geometry.Euclidean2DSpace.vector import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class TangentTest { @Test @@ -15,25 +16,24 @@ class TangentTest { 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 segments = arrayListOf>( + LineSegment(begin = vector(0.0, 1.0), + end = vector(4.0, 1.0)), + LineSegment(begin = vector(0.5, 0.8660254), + end = vector(3.5, -0.8660254)), + LineSegment(begin = vector(0.5, -0.8660254), + end = vector(3.5, 0.8660254)), + LineSegment(begin = vector(0.0, -1.0), + end = vector(4.0, -1.0)) ) - val tangentMap = tangentsToCircles(c1, c2) + val tangentMap = c1.tangentsToCircle(c2) val tangentMapKeys = tangentMap.keys.toList() val tangentMapValues = tangentMap.values.toList() assertEquals(routes, tangentMapKeys) for (i in segments.indices) { - assertEquals(segments[i], tangentMapValues[i]) + assertTrue(equalLineSegments(segments[i], tangentMapValues[i])) } -// assertEquals(segments, tangentMapValues) } } \ No newline at end of file diff --git a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt index 89db22d45..c62af3cd3 100644 --- a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt @@ -44,3 +44,6 @@ fun GeometrySpace.isCollinear(a: V, b: V, absoluteTolerance: Dou fun GeometrySpace.isOrthogonal(a: V, b: V, absoluteTolerance: Double = 1e-6): Boolean = abs(a dot b) < absoluteTolerance + +fun Double.equalFloat(other: Double, maxFloatDelta: Double = 0.000001): + Boolean = kotlin.math.abs(this - other) < maxFloatDelta \ No newline at end of file