From e6727084578081ff19341b7169c5f1a632e95540 Mon Sep 17 00:00:00 2001 From: tsteffek Date: Wed, 11 Dec 2019 15:25:49 +0100 Subject: [PATCH 1/6] refactor(Line): lines are now cartesian lines since working with lines in polar equations is cumbersome, we take polar equations out of the equations and do everything in cartesian. To this end we also introduced Points (which are cartesian points) and PolarPoints (our former points) BREAKING CHANGE: former Points are now PolarPoints --- src/main/kotlin/maths/geometry.kt | 33 +++++++++++ src/main/kotlin/model/NeighborhoodGraph.kt | 23 ++++---- src/main/kotlin/model/Scan2D.kt | 7 ++- .../kotlin/model/geometry/GeometricObject.kt | 4 +- src/main/kotlin/model/geometry/Line.kt | 52 +++++++---------- src/main/kotlin/model/geometry/Point.kt | 56 ++++++------------ src/main/kotlin/model/geometry/PolarPoint.kt | 30 ++++++++++ src/test/kotlin/model/LineTest.kt | 58 +++++++++---------- .../kotlin/model/NeighborhoodGraphTest.kt | 14 ++--- src/test/kotlin/model/PointTest.kt | 47 ++++++++------- src/test/kotlin/model/Scan2DTest.kt | 13 +++-- 11 files changed, 184 insertions(+), 153 deletions(-) create mode 100644 src/main/kotlin/maths/geometry.kt create mode 100644 src/main/kotlin/model/geometry/PolarPoint.kt diff --git a/src/main/kotlin/maths/geometry.kt b/src/main/kotlin/maths/geometry.kt new file mode 100644 index 0000000..3bb96da --- /dev/null +++ b/src/main/kotlin/maths/geometry.kt @@ -0,0 +1,33 @@ +package maths + +import model.geometry.* +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.sqrt + +fun lengthOf(x: Double, y: Double): Double = + sqrt(x * x + y * y) + +fun distancePointToPoint(a: Point, b: Point) = + lengthOf(a.x - b.x, a.y - b.y) + +fun distanceOriginLineToPoint(angle: Double, p: PolarPoint): Double { + val angleBetween = angle - p.angle + return if (abs(angleBetween) < piHalf) + p.distance * cos(angleBetween) + else + p.distance +} + +fun intersectTwoLines(a: Line, b: Line): Point { + val x = (b.intercept - a.intercept) / (a.slope - b.slope) + val y = a.slope * x + a.intercept + return Point(x, y) +} + +fun distanceLineToPoint(l: Line, p: Point): Double { + val inverseSlope = - 1 / l.slope + val perpendicularLine = Line.fromSlopeAndPoint(inverseSlope, p) + val intersection = intersectTwoLines(l, perpendicularLine) + return distancePointToPoint(p, intersection) +} diff --git a/src/main/kotlin/model/NeighborhoodGraph.kt b/src/main/kotlin/model/NeighborhoodGraph.kt index fded2ef..bd2df59 100644 --- a/src/main/kotlin/model/NeighborhoodGraph.kt +++ b/src/main/kotlin/model/NeighborhoodGraph.kt @@ -1,11 +1,10 @@ package model -import model.geometry.GeometricObject -import model.geometry.Point -import model.geometry.distanceOriginLineToPoint -import model.geometry.distancePointToPoint +import maths.distanceOriginLineToPoint +import maths.distancePointToPoint +import model.geometry.* -class NeighborhoodGraph>( +class NeighborhoodGraph( private val map: Map> ) { fun getObjects(): Set = map.keys @@ -13,7 +12,7 @@ class NeighborhoodGraph>( fun getNeighbors(t: T): Set = map.getValue(t) companion object { - fun > usingBruteForce( + fun usingBruteForce( objects: List, closestNeighbors: Int = 2 ): NeighborhoodGraph { @@ -34,7 +33,7 @@ class NeighborhoodGraph>( neighbors.all { neighbor -> map.getValue(neighbor).contains(point) } } - fun fromPolarPoints(points: List): NeighborhoodGraph { + fun fromPolarPoints(points: List): NeighborhoodGraph { val sortedPoints = points.sortedBy { it.angle } val pointToList = @@ -47,7 +46,7 @@ class NeighborhoodGraph>( return NeighborhoodGraph(filterOutlier(pointToList)) } - private fun computeClosest(index: Int, points: List): Set { + private fun computeClosest(index: Int, points: List): Set { val it = points[index % points.size] val halfSize = (points.size * 0.5).toInt() val forwardIterator = points.asCyclicSequence(index + 1, index + halfSize).iterator() @@ -64,11 +63,11 @@ class NeighborhoodGraph>( } private tailrec fun computeNextClosestRec( - it: Point, - closestPoint: Point?, + it: PolarPoint, + closestPoint: PolarPoint?, distanceToClosest: Double, - iterator: Iterator - ): Point? { + iterator: Iterator + ): PolarPoint? { if (!iterator.hasNext()) return closestPoint val nextPoint = iterator.next() diff --git a/src/main/kotlin/model/Scan2D.kt b/src/main/kotlin/model/Scan2D.kt index a37ad30..2ee4631 100644 --- a/src/main/kotlin/model/Scan2D.kt +++ b/src/main/kotlin/model/Scan2D.kt @@ -5,6 +5,7 @@ package model import com.github.doyaaaaaken.kotlincsv.dsl.csvReader import model.geometry.Point +import model.geometry.PolarPoint import mu.KotlinLogging import java.io.File import java.util.concurrent.atomic.AtomicInteger @@ -24,7 +25,7 @@ fun readFromTSV(tsv: File): List> = fun readFromTSV(tsv: String): List> = getTSVReader().readAllWithHeader(tsv) -class Scan2D(val pointCloud: List, private val scanner: Scanner) { +class Scan2D(val pointCloud: List, private val scanner: Scanner) { fun rotateBy(angle: Double): List = pointCloud.map { it.rotateBy(angle) } @@ -79,7 +80,7 @@ class Scan2D(val pointCloud: List, private val scanner: Scanner) { private fun calculatePoints(data: List, scanner: Scanner) - : List { + : List { val countNull = AtomicInteger(0) val countQuality = AtomicInteger(0) @@ -90,7 +91,7 @@ class Scan2D(val pointCloud: List, private val scanner: Scanner) { .map { val clockwise = if (scanner.clockwise) -1 else 1 val angleInRad = Math.toRadians(it.amountOfSteps * scanner.stepAngle * clockwise) - Point(angleInRad, it.distance!!, it.quality!!) + PolarPoint(angleInRad, it.distance!!, it.quality!!) }.toList() logger.info { diff --git a/src/main/kotlin/model/geometry/GeometricObject.kt b/src/main/kotlin/model/geometry/GeometricObject.kt index fea0c17..b8f7e19 100644 --- a/src/main/kotlin/model/geometry/GeometricObject.kt +++ b/src/main/kotlin/model/geometry/GeometricObject.kt @@ -2,7 +2,7 @@ package model.geometry const val PRECISION = 1e-8 -interface GeometricObject { - fun distanceTo(other: T): Double +interface GeometricObject { + fun distanceTo(other: GeometricObject): Double fun toTSVString(): String } diff --git a/src/main/kotlin/model/geometry/Line.kt b/src/main/kotlin/model/geometry/Line.kt index 96afafb..a8bd4be 100644 --- a/src/main/kotlin/model/geometry/Line.kt +++ b/src/main/kotlin/model/geometry/Line.kt @@ -1,31 +1,25 @@ package model.geometry +import maths.distanceLineToPoint import kotlin.math.* const val piHalf = PI / 2 -fun distanceOriginLineToPoint(angle: Double, p: Point): Double { - val angleBetween = angle - p.angle - return if (abs(angleBetween) < piHalf) - p.distance * cos(angleBetween) - else - p.distance -} - -fun distanceLineToPoint(l: Line, p: Point): Double { - val beta = PI - l.angle - val c = distancePointToPoint(p, l.basePoint()) - val b = c * sin(beta) - return sqrt(c.pow(2) - b.pow(2)) -} +data class Line(val slope: Double, val intercept: Double) : GeometricObject { -data class Line(val angle: Double, val distance: Double) { + constructor(slope: Int, intercept: Double) : this(slope.toDouble(), intercept) + constructor(slope: Double, intercept: Int) : this(slope, intercept.toDouble()) + constructor(slope: Int, intercept: Int) : this(slope.toDouble(), intercept.toDouble()) - constructor(angle: Int, distance: Double) : this(angle.toDouble(), distance) - constructor(angle: Double, distance: Int) : this(angle, distance.toDouble()) - constructor(angle: Int, distance: Int) : this(angle.toDouble(), distance.toDouble()) + override fun distanceTo(other: GeometricObject): Double = + when (other) { + is Point -> distanceLineToPoint(this, other) + is Line -> 0.0 + else -> throw NotImplementedError() + } - fun basePoint(): Point = Point(angle, distance, Int.MAX_VALUE) + override fun toTSVString(): String = + "$slope\t$intercept" override fun equals(other: Any?): Boolean { if (this === other) return true @@ -33,25 +27,23 @@ data class Line(val angle: Double, val distance: Double) { other as Line - if (other.angle - PRECISION > angle || angle > other.angle + PRECISION) return false - if (other.distance - PRECISION > distance || distance > other.distance + PRECISION) return false + if (other.slope - PRECISION > slope || slope > other.slope + PRECISION) return false + if (other.intercept - PRECISION > intercept || intercept > other.intercept + PRECISION) return false return true } override fun hashCode(): Int { - var result = angle.hashCode() - result = 31 * result + distance.hashCode() + var result = slope.hashCode() + result = 31 * result + intercept.hashCode() return result } companion object { - fun fromTwoPoints(a: Point, b: Point): Line { - val alpha = if (a.x == b.x) piHalf else atan((b.y - a.y) / (b.x - a.x)) - val theta = piHalf - alpha // in range of 0 to pi - val distance = a.distance * sin(a.angle - alpha) - return if (distance >= 0) Line(theta, distance) - else Line(2 * PI - theta, -distance) - } + fun fromTwoPoints(a: Point, b: Point): Line = + fromSlopeAndPoint((a.y - b.y) / (a.x - b.x), a) + + fun fromSlopeAndPoint(slope: Double, a: Point): Line = + Line(slope, a.y - slope * a.x) } } diff --git a/src/main/kotlin/model/geometry/Point.kt b/src/main/kotlin/model/geometry/Point.kt index e7520ae..d4c4c7c 100644 --- a/src/main/kotlin/model/geometry/Point.kt +++ b/src/main/kotlin/model/geometry/Point.kt @@ -1,40 +1,29 @@ package model.geometry +import maths.distanceLineToPoint +import maths.distancePointToPoint import kotlin.math.cos import kotlin.math.sin import kotlin.math.sqrt -fun distancePointToPoint(a: Point, b: Point) = - lengthOf(a.x - b.x, a.y - b.y) +open class Point( + val x: Double, + val y: Double +) : GeometricObject { -fun lengthOf(x: Double, y: Double): Double = - sqrt(x * x + y * y) + constructor(x: Int, y: Double) : this(x.toDouble(), y) + constructor(x: Double, y: Int) : this(x, y.toDouble()) + constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble()) -class Point( - val angle: Double, - val distance: Double, - val quality: Int -) : GeometricObject { - val x: Double = distance * cos(angle) - val y: Double = distance * sin(angle) - - constructor(angle: Int, distance: Double, quality: Int): this(angle.toDouble(), distance, quality) - constructor(angle: Double, distance: Int, quality: Int): this(angle, distance.toDouble(), quality) - constructor(angle: Int, distance: Int, quality: Int): this(angle.toDouble(), distance.toDouble(), quality) - - override fun distanceTo(other: Point): Double = - distancePointToPoint(this, other) - - fun normalizedDirection(): Pair { - val length = distance - return Pair(x / length, y / length) - } - - fun rotateBy(angle: Double): Point = - Point(this.angle + angle, distance, quality) + override fun distanceTo(other: GeometricObject): Double = + when (other) { + is Point -> distancePointToPoint(other, this) + is Line -> distanceLineToPoint(other, this) + else -> throw NotImplementedError() + } override fun toTSVString(): String = - "$distance\t$angle\t$quality" + "$x\t$y" override fun equals(other: Any?): Boolean { if (this === other) return true @@ -42,23 +31,12 @@ class Point( other as Point - if (quality != other.quality) return false if (other.x - PRECISION > x || x > other.x + PRECISION) return false if (other.y - PRECISION > y || y > other.y + PRECISION) return false return true } - - override fun hashCode(): Int { - var result = distance.hashCode() - result = 31 * result + angle.hashCode() - result = 31 * result + quality - result = 31 * result + x.hashCode() - result = 31 * result + y.hashCode() - return result - } - override fun toString(): String = - "Point(distance=$distance, angle=$angle, quality=$quality)" + "Point(x=$x, y=$y)" } diff --git a/src/main/kotlin/model/geometry/PolarPoint.kt b/src/main/kotlin/model/geometry/PolarPoint.kt new file mode 100644 index 0000000..9d5fd44 --- /dev/null +++ b/src/main/kotlin/model/geometry/PolarPoint.kt @@ -0,0 +1,30 @@ +package model.geometry + +import maths.distancePointToPoint +import kotlin.math.cos +import kotlin.math.sin + +class PolarPoint( + val angle: Double, + val distance: Double, + val quality: Int +) : Point(distance * cos(angle), distance * sin(angle)) { + + constructor(angle: Int, distance: Double, quality: Int): this(angle.toDouble(), distance, quality) + constructor(angle: Double, distance: Int, quality: Int): this(angle, distance.toDouble(), quality) + constructor(angle: Int, distance: Int, quality: Int): this(angle.toDouble(), distance.toDouble(), quality) + + fun normalizedDirection(): Pair { + val length = distance + return Pair(x / length, y / length) + } + + fun rotateBy(angle: Double): PolarPoint = + PolarPoint(this.angle + angle, distance, quality) + + override fun toTSVString(): String = + "$distance\t$angle\t$quality" + + override fun toString(): String = + "Point(distance=$distance, angle=$angle, quality=$quality)" +} diff --git a/src/test/kotlin/model/LineTest.kt b/src/test/kotlin/model/LineTest.kt index 69e3cb0..e796bb8 100644 --- a/src/test/kotlin/model/LineTest.kt +++ b/src/test/kotlin/model/LineTest.kt @@ -5,6 +5,8 @@ import io.kotlintest.matchers.doubles.plusOrMinus import io.kotlintest.shouldBe import io.kotlintest.specs.FreeSpec import io.kotlintest.tables.row +import maths.distanceLineToPoint +import maths.distanceOriginLineToPoint import model.geometry.* import kotlin.math.PI import kotlin.math.sqrt @@ -16,9 +18,9 @@ internal class LineTest : FreeSpec({ "distanceOriginLineToPoint" { forall( - row(Math.toRadians(90.0), Point(Math.toRadians(45.0), sqrt(2.0), 0), 1.0), - row(Math.toRadians(180.0), Point(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0)), - row(Math.toRadians(270.0), Point(Math.toRadians(225.0), sqrt(2.0), 0), 1.0) + row(Math.toRadians(90.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), 1.0), + row(Math.toRadians(180.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0)), + row(Math.toRadians(270.0), PolarPoint(Math.toRadians(225.0), sqrt(2.0), 0), 1.0) ) { angle, point, distance -> distanceOriginLineToPoint(angle, point) shouldBe (distance plusOrMinus 1e-8) } @@ -27,21 +29,21 @@ internal class LineTest : FreeSpec({ "distanceLineToPoint" { forall( row( -// Line(0, 0), -// Point(PI * 0.25, sqrt(2.0), 0), -// 1.0 -// ), row( -// Line(PI, 1), -// Point(PI * 0.25, sqrt(2.0), 0), -// 0.0 -// ), row( - Line(PI * 1.75, sqrt(2.0)), - Point(PI * 1.25, sqrt(2.0), 0), - sqrt(2.0) + Line(1, 3), + Point(0, 0), + 3.0 / sqrt(2.0) + ), row( + Line(1, 3), + Point(-1, -2), + 2.0 * sqrt(2.0) + ), row( + Line(-5, -2), + Point(0.5, -4.5), + 0.0 ), row( - Line(PI * 0.25, sqrt(2.0)), - Point(PI * 0.5, 1, 0), - sqrt(2.0)/2 + Line(-1, 0), + Point(1, 1), + sqrt(2.0) ) ) { line, point, distance -> distanceLineToPoint(line, point) shouldBe (distance plusOrMinus 1e-8) @@ -52,21 +54,17 @@ internal class LineTest : FreeSpec({ "fromTwoPoints" { forall( row( - Point(piHalf, 1, 0), - Point(piHalf / 2, sqrt(2.0), 1), - Line(piHalf, 1) - ), row( - Point(PI * 0.75, 50, 0), - Point(PI * 1.75, sqrt(2.0), 1), - Line(PI * 0.75, 0) + Point(0, 0), + Point(1, 1), + Line(1, 0) ), row( - Point(PI * 1.25, sqrt(2.0) / 2, 0), - Point(PI * 1.75, sqrt(2.0) / 2, 1), - Line(PI * 1.5, 0.5) + Point(1, 1), + Point(0, 0), + Line(1, 0) ), row( - Point(PI * 1.75, sqrt(2.0), 0), - Point(PI * 1.5, 2, 1), - Line(PI * 1.75, sqrt(2.0)) + Point(-2, -3), + Point(-1, -3), + Line(0, -3) ) ) { pointA, pointB, targetLine -> Line.fromTwoPoints(pointA, pointB) shouldBe targetLine diff --git a/src/test/kotlin/model/NeighborhoodGraphTest.kt b/src/test/kotlin/model/NeighborhoodGraphTest.kt index ec8de5f..2d5d10a 100644 --- a/src/test/kotlin/model/NeighborhoodGraphTest.kt +++ b/src/test/kotlin/model/NeighborhoodGraphTest.kt @@ -6,7 +6,7 @@ import io.kotlintest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotlintest.shouldThrow import io.kotlintest.specs.FreeSpec import io.kotlintest.tables.row -import model.geometry.Point +import model.geometry.PolarPoint import mu.KotlinLogging private val logger by lazy { KotlinLogging.logger {} } @@ -14,12 +14,12 @@ private val logger by lazy { KotlinLogging.logger {} } class NeighborhoodGraphTest : FreeSpec({ "companion object" - { - val a = Point(0.0, 1.0, 0) - val b = Point(Math.toRadians(-90.0), 1.0, 4) - val c = Point(Math.toRadians(90.0), 1.0, 2) - val d = Point(Math.toRadians(170.0), 0.9, 3) - val outlier = Point(Math.toRadians(45.0), 50.0, 1) - val unknownPoint = Point(Math.toRadians(90.0), 10.0, 500) + val a = PolarPoint(0.0, 1.0, 0) + val b = PolarPoint(Math.toRadians(-90.0), 1.0, 4) + val c = PolarPoint(Math.toRadians(90.0), 1.0, 2) + val d = PolarPoint(Math.toRadians(170.0), 0.9, 3) + val outlier = PolarPoint(Math.toRadians(45.0), 50.0, 1) + val unknownPoint = PolarPoint(Math.toRadians(90.0), 10.0, 500) val points = listOf(a, outlier, b, c, d) val inlier = listOf(a, b, c, d) diff --git a/src/test/kotlin/model/PointTest.kt b/src/test/kotlin/model/PointTest.kt index 704abe8..8ef8632 100644 --- a/src/test/kotlin/model/PointTest.kt +++ b/src/test/kotlin/model/PointTest.kt @@ -9,19 +9,19 @@ import io.kotlintest.tables.forAll import io.kotlintest.tables.headers import io.kotlintest.tables.row import io.kotlintest.tables.table -import model.geometry.Point +import model.geometry.PolarPoint import kotlin.math.pow import kotlin.math.sqrt -internal class PointTest : FreeSpec({ +internal class PolarPointTest : FreeSpec({ "distancePointToPoint" { - val pointA = Point(Math.toRadians(90.0), 0.0, 0) - val pointB = Point(Math.toRadians(225.0), 2 * sqrt(2.0), 0) + val pointA = PolarPoint(Math.toRadians(90.0), 0.0, 0) + val pointB = PolarPoint(Math.toRadians(225.0), 2 * sqrt(2.0), 0) forall( - row(Point(Math.toRadians(90.0), 2.0, 0), 2.0, 4.4721), - row(Point(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0), 3 * sqrt(2.0)), - row(Point(Math.toRadians(135.0), sqrt(2.0), 0), sqrt(2.0), 3.1623) + row(PolarPoint(Math.toRadians(90.0), 2.0, 0), 2.0, 4.4721), + row(PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0), 3 * sqrt(2.0)), + row(PolarPoint(Math.toRadians(135.0), sqrt(2.0), 0), sqrt(2.0), 3.1623) ) { otherPoint, distanceToA, distanceToB -> pointA.distanceTo(otherPoint) shouldBe (distanceToA plusOrMinus 1e-4) pointB.distanceTo(otherPoint) shouldBe (distanceToB plusOrMinus 1e-4) @@ -32,15 +32,15 @@ internal class PointTest : FreeSpec({ val pointsAndLengths = table( headers("point", "length"), row( - Point(Math.toRadians(45.0), 1.0, 0), + PolarPoint(Math.toRadians(45.0), 1.0, 0), 1.0 ), row( - Point(Math.toRadians(45.0), sqrt(2.0), 0), + PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0) ), row( - Point(Math.toRadians(45.0), 2 * sqrt(2.0), 0), + PolarPoint(Math.toRadians(45.0), 2 * sqrt(2.0), 0), 2 * sqrt(2.0) ) ) @@ -57,19 +57,19 @@ internal class PointTest : FreeSpec({ val pointsAndRotations = table( headers("point", "rotation", "point after rotation"), row( - Point(0.0, 1.0, 0), + PolarPoint(0.0, 1.0, 0), Math.toRadians(45.0), - Point(Math.toRadians(45.0), 1.0, 0) + PolarPoint(Math.toRadians(45.0), 1.0, 0) ), row( - Point(Math.toRadians(45.0), sqrt(2.0), 0), + PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), Math.toRadians(-45.0), - Point(0.0, sqrt(2.0), 0) + PolarPoint(0.0, sqrt(2.0), 0) ), row( - Point(Math.toRadians(45.0), 2 * sqrt(2.0), 0), + PolarPoint(Math.toRadians(45.0), 2 * sqrt(2.0), 0), Math.toRadians(180.0), - Point(Math.toRadians(225.0), 2 * sqrt(2.0), 0) + PolarPoint(Math.toRadians(225.0), 2 * sqrt(2.0), 0) ) ) @@ -83,7 +83,7 @@ internal class PointTest : FreeSpec({ } "toTSV" { - val point = Point(2.0, 3.0, 1) + val point = PolarPoint(2.0, 3.0, 1) val targetString = "3.0\t2.0\t1" point.toTSVString() shouldBe targetString } @@ -92,19 +92,18 @@ internal class PointTest : FreeSpec({ val distance = 0.5 val angle = 2.3 val quality = 50 - val point = Point(angle, distance, quality) + val point = PolarPoint(angle, distance, quality) forall( row(point), - row(Point(angle, distance + 1e-8, quality)), - row(Point(angle, distance, quality)) + row(PolarPoint(angle, distance + 1e-8, quality)), + row(PolarPoint(angle, distance, quality)) ) { targetPoint -> point shouldBe targetPoint } forall( - row(Point(angle, distance, quality + 1)), - row(Point(angle, distance + 0.01, quality)), - row(Point(angle + 0.01, distance, quality)), + row(PolarPoint(angle, distance + 0.01, quality)), + row(PolarPoint(angle + 0.01, distance, quality)), row(2.0) ) { targetPoint -> point shouldNotBe targetPoint @@ -122,7 +121,7 @@ internal class PointTest : FreeSpec({ "computes x and y correctly" { forAll(pointsAndLengths) { distance, angle, x, y, quality -> - val point = Point(angle, distance, quality) + val point = PolarPoint(angle, distance, quality) point.x shouldBe (x plusOrMinus 1e-5) point.y shouldBe (y plusOrMinus 1e-5) point.quality shouldBe quality diff --git a/src/test/kotlin/model/Scan2DTest.kt b/src/test/kotlin/model/Scan2DTest.kt index 9a0d974..62519c9 100644 --- a/src/test/kotlin/model/Scan2DTest.kt +++ b/src/test/kotlin/model/Scan2DTest.kt @@ -7,6 +7,7 @@ import io.kotlintest.shouldBe import io.kotlintest.specs.FreeSpec import io.kotlintest.tables.* import model.geometry.Point +import model.geometry.PolarPoint import mu.KotlinLogging import kotlin.math.sqrt @@ -32,9 +33,9 @@ class Scan2DTest : FreeSpec({ val targetPointCloud = table( headers("Point"), - row(Point(0.0, 1.0, 1)), - row(Point(Math.toRadians(45.0), sqrt(2.0), 2)), - row(Point(Math.toRadians(90.0), 1.0, 3)) + row(PolarPoint(0.0, 1.0, 1)), + row(PolarPoint(Math.toRadians(45.0), sqrt(2.0), 2)), + row(PolarPoint(Math.toRadians(90.0), 1.0, 3)) ) listOf( @@ -74,9 +75,9 @@ class Scan2DTest : FreeSpec({ val targetPointCloud = table( headers("Point"), - row(Point(0.0, 1.0, 1)), - row(Point(Math.toRadians(45.0), sqrt(2.0), 2)), - row(Point(Math.toRadians(90.0), 1.0, 3)) + row(PolarPoint(0.0, 1.0, 1)), + row(PolarPoint(Math.toRadians(45.0), sqrt(2.0), 2)), + row(PolarPoint(Math.toRadians(90.0), 1.0, 3)) ) listOf( From 75c19d6d34c4e2ac6ae7affa0f35c824e6c198a9 Mon Sep 17 00:00:00 2001 From: tsteffek Date: Wed, 11 Dec 2019 18:15:03 +0100 Subject: [PATCH 2/6] test(Geometry, Point, Line): add missing tests Geometry and the new Point class lacked some tests, also refactored existing ones into GeometryTest if applicable --- build.gradle.kts | 1 + src/main/kotlin/{maths => math}/geometry.kt | 12 +- src/main/kotlin/model/NeighborhoodGraph.kt | 7 +- .../kotlin/model/geometry/GeometricObject.kt | 2 - src/main/kotlin/model/geometry/Line.kt | 10 +- src/main/kotlin/model/geometry/Point.kt | 19 +-- src/main/kotlin/model/geometry/PolarPoint.kt | 1 - src/test/kotlin/math/geometryTest.kt | 77 +++++++++ src/test/kotlin/model/LineTest.kt | 62 +++----- src/test/kotlin/model/PointTest.kt | 150 +++++------------- src/test/kotlin/model/PolarPointTest.kt | 91 +++++++++++ 11 files changed, 262 insertions(+), 170 deletions(-) rename src/main/kotlin/{maths => math}/geometry.kt (79%) create mode 100644 src/test/kotlin/math/geometryTest.kt create mode 100644 src/test/kotlin/model/PolarPointTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index a268017..41d2c74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { /* Testing */ testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2") + testImplementation("io.mockk:mockk:1.9.3") } application { diff --git a/src/main/kotlin/maths/geometry.kt b/src/main/kotlin/math/geometry.kt similarity index 79% rename from src/main/kotlin/maths/geometry.kt rename to src/main/kotlin/math/geometry.kt index 3bb96da..9a39287 100644 --- a/src/main/kotlin/maths/geometry.kt +++ b/src/main/kotlin/math/geometry.kt @@ -1,10 +1,16 @@ -package maths +package math -import model.geometry.* +import model.geometry.Line +import model.geometry.Point +import model.geometry.PolarPoint +import kotlin.math.PI import kotlin.math.abs import kotlin.math.cos import kotlin.math.sqrt +const val PIHALF = PI / 2 +const val PRECISION = 1e-8 + fun lengthOf(x: Double, y: Double): Double = sqrt(x * x + y * y) @@ -13,7 +19,7 @@ fun distancePointToPoint(a: Point, b: Point) = fun distanceOriginLineToPoint(angle: Double, p: PolarPoint): Double { val angleBetween = angle - p.angle - return if (abs(angleBetween) < piHalf) + return if (abs(angleBetween) < PIHALF) p.distance * cos(angleBetween) else p.distance diff --git a/src/main/kotlin/model/NeighborhoodGraph.kt b/src/main/kotlin/model/NeighborhoodGraph.kt index bd2df59..3e1f622 100644 --- a/src/main/kotlin/model/NeighborhoodGraph.kt +++ b/src/main/kotlin/model/NeighborhoodGraph.kt @@ -1,8 +1,9 @@ package model -import maths.distanceOriginLineToPoint -import maths.distancePointToPoint -import model.geometry.* +import math.distanceOriginLineToPoint +import math.distancePointToPoint +import model.geometry.GeometricObject +import model.geometry.PolarPoint class NeighborhoodGraph( private val map: Map> diff --git a/src/main/kotlin/model/geometry/GeometricObject.kt b/src/main/kotlin/model/geometry/GeometricObject.kt index b8f7e19..9e6042e 100644 --- a/src/main/kotlin/model/geometry/GeometricObject.kt +++ b/src/main/kotlin/model/geometry/GeometricObject.kt @@ -1,7 +1,5 @@ package model.geometry -const val PRECISION = 1e-8 - interface GeometricObject { fun distanceTo(other: GeometricObject): Double fun toTSVString(): String diff --git a/src/main/kotlin/model/geometry/Line.kt b/src/main/kotlin/model/geometry/Line.kt index a8bd4be..40488b6 100644 --- a/src/main/kotlin/model/geometry/Line.kt +++ b/src/main/kotlin/model/geometry/Line.kt @@ -1,9 +1,7 @@ package model.geometry -import maths.distanceLineToPoint -import kotlin.math.* - -const val piHalf = PI / 2 +import math.PRECISION +import math.distanceLineToPoint data class Line(val slope: Double, val intercept: Double) : GeometricObject { @@ -14,7 +12,9 @@ data class Line(val slope: Double, val intercept: Double) : GeometricObject { override fun distanceTo(other: GeometricObject): Double = when (other) { is Point -> distanceLineToPoint(this, other) - is Line -> 0.0 + is Line -> + if (other.slope != slope) 0.0 + else throw NotImplementedError() else -> throw NotImplementedError() } diff --git a/src/main/kotlin/model/geometry/Point.kt b/src/main/kotlin/model/geometry/Point.kt index d4c4c7c..4816bad 100644 --- a/src/main/kotlin/model/geometry/Point.kt +++ b/src/main/kotlin/model/geometry/Point.kt @@ -1,15 +1,10 @@ package model.geometry -import maths.distanceLineToPoint -import maths.distancePointToPoint -import kotlin.math.cos -import kotlin.math.sin -import kotlin.math.sqrt +import math.PRECISION +import math.distanceLineToPoint +import math.distancePointToPoint -open class Point( - val x: Double, - val y: Double -) : GeometricObject { +open class Point(val x: Double, val y: Double) : GeometricObject { constructor(x: Int, y: Double) : this(x.toDouble(), y) constructor(x: Double, y: Int) : this(x, y.toDouble()) @@ -37,6 +32,12 @@ open class Point( return true } + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + return result + } + override fun toString(): String = "Point(x=$x, y=$y)" } diff --git a/src/main/kotlin/model/geometry/PolarPoint.kt b/src/main/kotlin/model/geometry/PolarPoint.kt index 9d5fd44..797c74a 100644 --- a/src/main/kotlin/model/geometry/PolarPoint.kt +++ b/src/main/kotlin/model/geometry/PolarPoint.kt @@ -1,6 +1,5 @@ package model.geometry -import maths.distancePointToPoint import kotlin.math.cos import kotlin.math.sin diff --git a/src/test/kotlin/math/geometryTest.kt b/src/test/kotlin/math/geometryTest.kt new file mode 100644 index 0000000..e3af978 --- /dev/null +++ b/src/test/kotlin/math/geometryTest.kt @@ -0,0 +1,77 @@ +package math + +import io.kotlintest.data.suspend.forall +import io.kotlintest.matchers.doubles.plusOrMinus +import io.kotlintest.shouldBe +import io.kotlintest.specs.FreeSpec +import io.kotlintest.tables.row +import model.geometry.Line +import model.geometry.Point +import model.geometry.PolarPoint +import kotlin.math.PI +import kotlin.math.sqrt + +internal class GeometryTest : FreeSpec({ + + "piHalf" { + PIHALF shouldBe PI / 2 + } + + "lengthOf" { + forall( + row(1.0, 0.0, 1.0), + row(0.0, 1.0, 1.0), + row(1.0, 1.0, sqrt(2.0)), + row(-1.0, -1.0, sqrt(2.0)) + ) { x, y, length -> + lengthOf(x, y) shouldBe (length plusOrMinus 1e-8) + } + } + + "distancePointToPoint" { + val pointA = PolarPoint(Math.toRadians(90.0), 0.0, 0) + val pointB = PolarPoint(Math.toRadians(225.0), 2 * sqrt(2.0), 0) + forall( + row(PolarPoint(Math.toRadians(90.0), 2.0, 0), 2.0, 4.4721), + row(PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0), 3 * sqrt(2.0)), + row(PolarPoint(Math.toRadians(135.0), sqrt(2.0), 0), sqrt(2.0), 3.1623) + ) { otherPoint, distanceToA, distanceToB -> + pointA.distanceTo(otherPoint) shouldBe (distanceToA plusOrMinus 1e-4) + pointB.distanceTo(otherPoint) shouldBe (distanceToB plusOrMinus 1e-4) + } + } + + "distanceOriginLineToPoint" { + forall( + row(Math.toRadians(90.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), 1.0), + row(Math.toRadians(180.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0)), + row(Math.toRadians(270.0), PolarPoint(Math.toRadians(225.0), sqrt(2.0), 0), 1.0) + ) { angle, point, distance -> + distanceOriginLineToPoint(angle, point) shouldBe (distance plusOrMinus 1e-8) + } + } + + "distanceLineToPoint" { + forall( + row( + Line(1, 3), + Point(0, 0), + 3.0 / sqrt(2.0) + ), row( + Line(1, 3), + Point(-1, -2), + 2.0 * sqrt(2.0) + ), row( + Line(-5, -2), + Point(0.5, -4.5), + 0.0 + ), row( + Line(-1, 0), + Point(1, 1), + sqrt(2.0) + ) + ) { line, point, distance -> + distanceLineToPoint(line, point) shouldBe (distance plusOrMinus 1e-8) + } + } +}) diff --git a/src/test/kotlin/model/LineTest.kt b/src/test/kotlin/model/LineTest.kt index e796bb8..70f528b 100644 --- a/src/test/kotlin/model/LineTest.kt +++ b/src/test/kotlin/model/LineTest.kt @@ -1,52 +1,38 @@ package model import io.kotlintest.data.suspend.forall -import io.kotlintest.matchers.doubles.plusOrMinus import io.kotlintest.shouldBe +import io.kotlintest.shouldThrow import io.kotlintest.specs.FreeSpec import io.kotlintest.tables.row -import maths.distanceLineToPoint -import maths.distanceOriginLineToPoint -import model.geometry.* -import kotlin.math.PI -import kotlin.math.sqrt +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.verify +import math.distanceLineToPoint +import model.geometry.Line +import model.geometry.Point internal class LineTest : FreeSpec({ - "piHalf" { - piHalf shouldBe PI / 2 - } - "distanceOriginLineToPoint" { - forall( - row(Math.toRadians(90.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), 1.0), - row(Math.toRadians(180.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0)), - row(Math.toRadians(270.0), PolarPoint(Math.toRadians(225.0), sqrt(2.0), 0), 1.0) - ) { angle, point, distance -> - distanceOriginLineToPoint(angle, point) shouldBe (distance plusOrMinus 1e-8) + "distanceTo" - { + val l = Line(1, 2) + val point = Point(3, 4) + val line = Line(1.5, 1) + val parallelLine = Line(1, 1) + + "distanceTo Point calls distanceLineToPoint" { + mockkStatic("math.GeometryKt") + every { distanceLineToPoint(l, point) } returns 13.0 + l.distanceTo(point) shouldBe 13.0 + verify { distanceLineToPoint(l, point) } } - } - "distanceLineToPoint" { - forall( - row( - Line(1, 3), - Point(0, 0), - 3.0 / sqrt(2.0) - ), row( - Line(1, 3), - Point(-1, -2), - 2.0 * sqrt(2.0) - ), row( - Line(-5, -2), - Point(0.5, -4.5), - 0.0 - ), row( - Line(-1, 0), - Point(1, 1), - sqrt(2.0) - ) - ) { line, point, distance -> - distanceLineToPoint(line, point) shouldBe (distance plusOrMinus 1e-8) + "distanceTo Line is 0.0 or not implemented if parallel" { + l.distanceTo(line) shouldBe 0.0 + + shouldThrow { + l.distanceTo(parallelLine) + } } } diff --git a/src/test/kotlin/model/PointTest.kt b/src/test/kotlin/model/PointTest.kt index 8ef8632..5dcba7a 100644 --- a/src/test/kotlin/model/PointTest.kt +++ b/src/test/kotlin/model/PointTest.kt @@ -1,130 +1,62 @@ package model import io.kotlintest.data.suspend.forall -import io.kotlintest.matchers.doubles.plusOrMinus import io.kotlintest.shouldBe import io.kotlintest.shouldNotBe import io.kotlintest.specs.FreeSpec -import io.kotlintest.tables.forAll -import io.kotlintest.tables.headers import io.kotlintest.tables.row -import io.kotlintest.tables.table -import model.geometry.PolarPoint -import kotlin.math.pow -import kotlin.math.sqrt +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.verify +import math.distanceLineToPoint +import math.distancePointToPoint +import model.geometry.Line +import model.geometry.Point -internal class PolarPointTest : FreeSpec({ +internal class PointTest : FreeSpec({ - "distancePointToPoint" { - val pointA = PolarPoint(Math.toRadians(90.0), 0.0, 0) - val pointB = PolarPoint(Math.toRadians(225.0), 2 * sqrt(2.0), 0) - forall( - row(PolarPoint(Math.toRadians(90.0), 2.0, 0), 2.0, 4.4721), - row(PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0), 3 * sqrt(2.0)), - row(PolarPoint(Math.toRadians(135.0), sqrt(2.0), 0), sqrt(2.0), 3.1623) - ) { otherPoint, distanceToA, distanceToB -> - pointA.distanceTo(otherPoint) shouldBe (distanceToA plusOrMinus 1e-4) - pointB.distanceTo(otherPoint) shouldBe (distanceToB plusOrMinus 1e-4) - } - } - "Point" - { - val pointsAndLengths = table( - headers("point", "length"), - row( - PolarPoint(Math.toRadians(45.0), 1.0, 0), - 1.0 - ), - row( - PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), - sqrt(2.0) - ), - row( - PolarPoint(Math.toRadians(45.0), 2 * sqrt(2.0), 0), - 2 * sqrt(2.0) - ) - ) + "distanceTo" { + val p = Point(1, 2) + val otherP = Point(3, 4) + val line = Line(1, 1) - "normalizedDirection" { - forAll( - pointsAndLengths - ) { point, _ -> - val vec = point.normalizedDirection() - sqrt(vec.first.pow(2) + vec.second.pow(2)) shouldBe (1.0 plusOrMinus 1e-5) - } - } + mockkStatic("math.GeometryKt") + every { distancePointToPoint(otherP, p) } returns 13.0 + every { distanceLineToPoint(line, p) } returns 23.0 - val pointsAndRotations = table( - headers("point", "rotation", "point after rotation"), - row( - PolarPoint(0.0, 1.0, 0), - Math.toRadians(45.0), - PolarPoint(Math.toRadians(45.0), 1.0, 0) - ), - row( - PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), - Math.toRadians(-45.0), - PolarPoint(0.0, sqrt(2.0), 0) - ), - row( - PolarPoint(Math.toRadians(45.0), 2 * sqrt(2.0), 0), - Math.toRadians(180.0), - PolarPoint(Math.toRadians(225.0), 2 * sqrt(2.0), 0) - ) - ) + p.distanceTo(otherP) shouldBe 13.0 + p.distanceTo(line) shouldBe 23.0 - "rotateBy" { - forAll( - pointsAndRotations - ) { point, rotation, targetPoint -> - val rotatedPoint = point.rotateBy(rotation) - rotatedPoint shouldBe targetPoint - } - } + verify { distanceLineToPoint(line, p) } + verify { distancePointToPoint(otherP, p) } + } - "toTSV" { - val point = PolarPoint(2.0, 3.0, 1) - val targetString = "3.0\t2.0\t1" - point.toTSVString() shouldBe targetString - } + "toTSV" { + val point = Point(2.0, 3.0) + val targetString = "2.0\t3.0" + point.toTSVString() shouldBe targetString + } - "equals" { - val distance = 0.5 - val angle = 2.3 - val quality = 50 - val point = PolarPoint(angle, distance, quality) - forall( - row(point), - row(PolarPoint(angle, distance + 1e-8, quality)), - row(PolarPoint(angle, distance, quality)) - ) { targetPoint -> - point shouldBe targetPoint - } + "equals" { + val distance = 0.5 + val angle = 2.3 + val point = Point(angle, distance) - forall( - row(PolarPoint(angle, distance + 0.01, quality)), - row(PolarPoint(angle + 0.01, distance, quality)), - row(2.0) - ) { targetPoint -> - point shouldNotBe targetPoint - } + forall( + row(point), + row(Point(angle, distance + 1e-8)), + row(Point(angle, distance)) + ) { targetPoint -> + point shouldBe targetPoint } - } - - val pointsAndLengths = table( - headers("polar distance", "polar angle", "cartesian x", "cartesian y", "quality"), - row(1.0, Math.toRadians(90.0), 0.0, 1.0, 0), - row(sqrt(2.0), Math.toRadians(45.0), 1.0, 1.0, 0), - row(2 * sqrt(2.0), Math.toRadians(45.0), 2.0, 2.0, 0), - row(2 * sqrt(2.0), Math.toRadians(-135.0), -2.0, -2.0, 0) - ) - "computes x and y correctly" { - forAll(pointsAndLengths) { distance, angle, x, y, quality -> - val point = PolarPoint(angle, distance, quality) - point.x shouldBe (x plusOrMinus 1e-5) - point.y shouldBe (y plusOrMinus 1e-5) - point.quality shouldBe quality + forall( + row(Point(angle, distance + 0.01)), + row(Point(angle + 0.01, distance)), + row(2.0) + ) { targetPoint -> + point shouldNotBe targetPoint } } }) diff --git a/src/test/kotlin/model/PolarPointTest.kt b/src/test/kotlin/model/PolarPointTest.kt new file mode 100644 index 0000000..cd52919 --- /dev/null +++ b/src/test/kotlin/model/PolarPointTest.kt @@ -0,0 +1,91 @@ +package model + +import io.kotlintest.matchers.doubles.plusOrMinus +import io.kotlintest.shouldBe +import io.kotlintest.specs.FreeSpec +import io.kotlintest.tables.forAll +import io.kotlintest.tables.headers +import io.kotlintest.tables.row +import io.kotlintest.tables.table +import model.geometry.PolarPoint +import kotlin.math.pow +import kotlin.math.sqrt + +internal class PolarPointTest : FreeSpec({ + + "normalizedDirection" { + val pointsAndLengths = table( + headers("point", "length"), + row( + PolarPoint(Math.toRadians(45.0), 1.0, 0), + 1.0 + ), + row( + PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), + sqrt(2.0) + ), + row( + PolarPoint(Math.toRadians(45.0), 2 * sqrt(2.0), 0), + 2 * sqrt(2.0) + ) + ) + + forAll( + pointsAndLengths + ) { point, _ -> + val vec = point.normalizedDirection() + sqrt(vec.first.pow(2) + vec.second.pow(2)) shouldBe (1.0 plusOrMinus 1e-5) + } + } + + "rotateBy" { + val pointsAndRotations = table( + headers("point", "rotation", "point after rotation"), + row( + PolarPoint(0.0, 1.0, 0), + Math.toRadians(45.0), + PolarPoint(Math.toRadians(45.0), 1.0, 0) + ), + row( + PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), + Math.toRadians(-45.0), + PolarPoint(0.0, sqrt(2.0), 0) + ), + row( + PolarPoint(Math.toRadians(45.0), 2 * sqrt(2.0), 0), + Math.toRadians(180.0), + PolarPoint(Math.toRadians(225.0), 2 * sqrt(2.0), 0) + ) + ) + + forAll( + pointsAndRotations + ) { point, rotation, targetPoint -> + val rotatedPoint = point.rotateBy(rotation) + rotatedPoint shouldBe targetPoint + } + } + + "toTSV" { + val point = PolarPoint(2.0, 3.0, 1) + val targetString = "3.0\t2.0\t1" + point.toTSVString() shouldBe targetString + } + + "computes x and y correctly" { + val pointsAndLengths = table( + headers("polar distance", "polar angle", "cartesian x", "cartesian y", "quality"), + row(1.0, Math.toRadians(90.0), 0.0, 1.0, 0), + row(sqrt(2.0), Math.toRadians(45.0), 1.0, 1.0, 0), + row(2 * sqrt(2.0), Math.toRadians(45.0), 2.0, 2.0, 0), + row(2 * sqrt(2.0), Math.toRadians(-135.0), -2.0, -2.0, 0) + ) + + forAll(pointsAndLengths) { distance, angle, x, y, quality -> + val point = PolarPoint(angle, distance, quality) + point.x shouldBe (x plusOrMinus 1e-5) + point.y shouldBe (y plusOrMinus 1e-5) + point.quality shouldBe quality + } + } +}) From 670dd5ec943ec1a8305636a003f55fdec36c49bf Mon Sep 17 00:00:00 2001 From: tsteffek Date: Wed, 11 Dec 2019 18:39:49 +0100 Subject: [PATCH 3/6] more tests --- src/test/kotlin/model/LineTest.kt | 17 +++++++++++++++++ src/test/kotlin/model/PointTest.kt | 18 +++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/model/LineTest.kt b/src/test/kotlin/model/LineTest.kt index 70f528b..2ef17b9 100644 --- a/src/test/kotlin/model/LineTest.kt +++ b/src/test/kotlin/model/LineTest.kt @@ -14,6 +14,17 @@ import model.geometry.Point internal class LineTest : FreeSpec({ + "constructors are all equal" { + val line = Line(1.0, 2.0) + forall( + row(Line(1, 2.0)), + row(Line(1.0, 2)), + row(Line(1, 2)) + ) { + line shouldBe it + } + } + "distanceTo" - { val l = Line(1, 2) val point = Point(3, 4) @@ -36,6 +47,12 @@ internal class LineTest : FreeSpec({ } } + "toTSV" { + val line = Line(2.0, 3.0) + val targetString = "2.0\t3.0" + line.toTSVString() shouldBe targetString + } + "companion object" - { "fromTwoPoints" { forall( diff --git a/src/test/kotlin/model/PointTest.kt b/src/test/kotlin/model/PointTest.kt index 5dcba7a..8f37171 100644 --- a/src/test/kotlin/model/PointTest.kt +++ b/src/test/kotlin/model/PointTest.kt @@ -7,7 +7,7 @@ import io.kotlintest.specs.FreeSpec import io.kotlintest.tables.row import io.mockk.every import io.mockk.mockkStatic -import io.mockk.verify +import io.mockk.verifyAll import math.distanceLineToPoint import math.distancePointToPoint import model.geometry.Line @@ -15,6 +15,16 @@ import model.geometry.Point internal class PointTest : FreeSpec({ + "constructors are all equal" { + val point = Point(1.0, 2.0) + forall( + row(Point(1, 2.0)), + row(Point(1.0, 2)), + row(Point(1, 2)) + ) { + point shouldBe it + } + } "distanceTo" { val p = Point(1, 2) @@ -28,8 +38,10 @@ internal class PointTest : FreeSpec({ p.distanceTo(otherP) shouldBe 13.0 p.distanceTo(line) shouldBe 23.0 - verify { distanceLineToPoint(line, p) } - verify { distancePointToPoint(otherP, p) } + verifyAll { + distanceLineToPoint(line, p) + distancePointToPoint(otherP, p) + } } "toTSV" { From 778cab1fcfe3e333c2ce5eebbff744fb9beec4d8 Mon Sep 17 00:00:00 2001 From: tsteffek Date: Thu, 12 Dec 2019 13:05:29 +0100 Subject: [PATCH 4/6] fix(mockk): mockk can't run parallelized mockk seems to have trouble running parallelized tests in gradle, so parallelism is now disabled for tests --- src/test/kotlin/io/kotlintest/provided/ProjectConfig.kt | 2 +- src/test/kotlin/model/PointTest.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/kotlin/io/kotlintest/provided/ProjectConfig.kt b/src/test/kotlin/io/kotlintest/provided/ProjectConfig.kt index 08fe361..65c1041 100644 --- a/src/test/kotlin/io/kotlintest/provided/ProjectConfig.kt +++ b/src/test/kotlin/io/kotlintest/provided/ProjectConfig.kt @@ -3,5 +3,5 @@ package io.kotlintest.provided import io.kotlintest.AbstractProjectConfig object ProjectConfig : AbstractProjectConfig() { - override fun parallelism(): Int = 8 + override fun parallelism(): Int = 1 } diff --git a/src/test/kotlin/model/PointTest.kt b/src/test/kotlin/model/PointTest.kt index 8f37171..b18e3d5 100644 --- a/src/test/kotlin/model/PointTest.kt +++ b/src/test/kotlin/model/PointTest.kt @@ -30,7 +30,6 @@ internal class PointTest : FreeSpec({ val p = Point(1, 2) val otherP = Point(3, 4) val line = Line(1, 1) - mockkStatic("math.GeometryKt") every { distancePointToPoint(otherP, p) } returns 13.0 every { distanceLineToPoint(line, p) } returns 23.0 From b0efe84e94e47cb94c7d43ed15aa115be4c3a819 Mon Sep 17 00:00:00 2001 From: tsteffek Date: Thu, 12 Dec 2019 13:24:25 +0100 Subject: [PATCH 5/6] use PRECISION in tests --- src/main/kotlin/math/geometry.kt | 2 ++ src/test/kotlin/math/{geometryTest.kt => GeometryTest.kt} | 6 +++--- src/test/kotlin/model/PointTest.kt | 3 ++- src/test/kotlin/model/PolarPointTest.kt | 7 ++++--- 4 files changed, 11 insertions(+), 7 deletions(-) rename src/test/kotlin/math/{geometryTest.kt => GeometryTest.kt} (95%) diff --git a/src/main/kotlin/math/geometry.kt b/src/main/kotlin/math/geometry.kt index 9a39287..a599e6a 100644 --- a/src/main/kotlin/math/geometry.kt +++ b/src/main/kotlin/math/geometry.kt @@ -8,7 +8,9 @@ import kotlin.math.abs import kotlin.math.cos import kotlin.math.sqrt +/** Precomputed value of PI / 2 for performance reasons */ const val PIHALF = PI / 2 +/** Precision used for various geometric similarity checks */ const val PRECISION = 1e-8 fun lengthOf(x: Double, y: Double): Double = diff --git a/src/test/kotlin/math/geometryTest.kt b/src/test/kotlin/math/GeometryTest.kt similarity index 95% rename from src/test/kotlin/math/geometryTest.kt rename to src/test/kotlin/math/GeometryTest.kt index e3af978..bb20bf5 100644 --- a/src/test/kotlin/math/geometryTest.kt +++ b/src/test/kotlin/math/GeometryTest.kt @@ -24,7 +24,7 @@ internal class GeometryTest : FreeSpec({ row(1.0, 1.0, sqrt(2.0)), row(-1.0, -1.0, sqrt(2.0)) ) { x, y, length -> - lengthOf(x, y) shouldBe (length plusOrMinus 1e-8) + lengthOf(x, y) shouldBe (length plusOrMinus PRECISION) } } @@ -47,7 +47,7 @@ internal class GeometryTest : FreeSpec({ row(Math.toRadians(180.0), PolarPoint(Math.toRadians(45.0), sqrt(2.0), 0), sqrt(2.0)), row(Math.toRadians(270.0), PolarPoint(Math.toRadians(225.0), sqrt(2.0), 0), 1.0) ) { angle, point, distance -> - distanceOriginLineToPoint(angle, point) shouldBe (distance plusOrMinus 1e-8) + distanceOriginLineToPoint(angle, point) shouldBe (distance plusOrMinus PRECISION) } } @@ -71,7 +71,7 @@ internal class GeometryTest : FreeSpec({ sqrt(2.0) ) ) { line, point, distance -> - distanceLineToPoint(line, point) shouldBe (distance plusOrMinus 1e-8) + distanceLineToPoint(line, point) shouldBe (distance plusOrMinus PRECISION) } } }) diff --git a/src/test/kotlin/model/PointTest.kt b/src/test/kotlin/model/PointTest.kt index b18e3d5..625c168 100644 --- a/src/test/kotlin/model/PointTest.kt +++ b/src/test/kotlin/model/PointTest.kt @@ -8,6 +8,7 @@ import io.kotlintest.tables.row import io.mockk.every import io.mockk.mockkStatic import io.mockk.verifyAll +import math.PRECISION import math.distanceLineToPoint import math.distancePointToPoint import model.geometry.Line @@ -56,7 +57,7 @@ internal class PointTest : FreeSpec({ forall( row(point), - row(Point(angle, distance + 1e-8)), + row(Point(angle, distance + PRECISION)), row(Point(angle, distance)) ) { targetPoint -> point shouldBe targetPoint diff --git a/src/test/kotlin/model/PolarPointTest.kt b/src/test/kotlin/model/PolarPointTest.kt index cd52919..fc7a72d 100644 --- a/src/test/kotlin/model/PolarPointTest.kt +++ b/src/test/kotlin/model/PolarPointTest.kt @@ -7,6 +7,7 @@ import io.kotlintest.tables.forAll import io.kotlintest.tables.headers import io.kotlintest.tables.row import io.kotlintest.tables.table +import math.PRECISION import model.geometry.PolarPoint import kotlin.math.pow import kotlin.math.sqrt @@ -34,7 +35,7 @@ internal class PolarPointTest : FreeSpec({ pointsAndLengths ) { point, _ -> val vec = point.normalizedDirection() - sqrt(vec.first.pow(2) + vec.second.pow(2)) shouldBe (1.0 plusOrMinus 1e-5) + sqrt(vec.first.pow(2) + vec.second.pow(2)) shouldBe (1.0 plusOrMinus PRECISION) } } @@ -83,8 +84,8 @@ internal class PolarPointTest : FreeSpec({ forAll(pointsAndLengths) { distance, angle, x, y, quality -> val point = PolarPoint(angle, distance, quality) - point.x shouldBe (x plusOrMinus 1e-5) - point.y shouldBe (y plusOrMinus 1e-5) + point.x shouldBe (x plusOrMinus PRECISION) + point.y shouldBe (y plusOrMinus PRECISION) point.quality shouldBe quality } } From 30b25af5e3c6349274ac314d74b3b541d24301b2 Mon Sep 17 00:00:00 2001 From: tsteffek Date: Thu, 12 Dec 2019 13:27:45 +0100 Subject: [PATCH 6/6] codacy fix --- src/main/kotlin/math/geometry.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/math/geometry.kt b/src/main/kotlin/math/geometry.kt index a599e6a..7ad121e 100644 --- a/src/main/kotlin/math/geometry.kt +++ b/src/main/kotlin/math/geometry.kt @@ -8,9 +8,9 @@ import kotlin.math.abs import kotlin.math.cos import kotlin.math.sqrt -/** Precomputed value of PI / 2 for performance reasons */ +/** Precomputed value of PI / 2 for performance reasons. */ const val PIHALF = PI / 2 -/** Precision used for various geometric similarity checks */ +/** Precision used for various geometric similarity checks. */ const val PRECISION = 1e-8 fun lengthOf(x: Double, y: Double): Double =