Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Line): lines are now cartesian lines #12

Merged
merged 6 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {

/* Testing */
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2")
testImplementation("io.mockk:mockk:1.9.3")
}

application {
Expand Down
41 changes: 41 additions & 0 deletions src/main/kotlin/math/geometry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package math

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

/** 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 =
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)
}
22 changes: 11 additions & 11 deletions src/main/kotlin/model/NeighborhoodGraph.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package model

import math.distanceOriginLineToPoint
import math.distancePointToPoint
import model.geometry.GeometricObject
import model.geometry.Point
import model.geometry.distanceOriginLineToPoint
import model.geometry.distancePointToPoint
import model.geometry.PolarPoint

class NeighborhoodGraph<T : GeometricObject<T>>(
class NeighborhoodGraph<T : GeometricObject>(
private val map: Map<T, Set<T>>
) {
fun getObjects(): Set<T> = map.keys

fun getNeighbors(t: T): Set<T> = map.getValue(t)

companion object {
fun <T : GeometricObject<T>> usingBruteForce(
fun <T : GeometricObject> usingBruteForce(
objects: List<T>,
closestNeighbors: Int = 2
): NeighborhoodGraph<T> {
Expand All @@ -34,7 +34,7 @@ class NeighborhoodGraph<T : GeometricObject<T>>(
neighbors.all { neighbor -> map.getValue(neighbor).contains(point) }
}

fun fromPolarPoints(points: List<Point>): NeighborhoodGraph<Point> {
fun fromPolarPoints(points: List<PolarPoint>): NeighborhoodGraph<PolarPoint> {
val sortedPoints = points.sortedBy { it.angle }

val pointToList =
Expand All @@ -47,7 +47,7 @@ class NeighborhoodGraph<T : GeometricObject<T>>(
return NeighborhoodGraph(filterOutlier(pointToList))
}

private fun computeClosest(index: Int, points: List<Point>): Set<Point> {
private fun computeClosest(index: Int, points: List<PolarPoint>): Set<PolarPoint> {
val it = points[index % points.size]
val halfSize = (points.size * 0.5).toInt()
val forwardIterator = points.asCyclicSequence(index + 1, index + halfSize).iterator()
Expand All @@ -64,11 +64,11 @@ class NeighborhoodGraph<T : GeometricObject<T>>(
}

private tailrec fun computeNextClosestRec(
it: Point,
closestPoint: Point?,
it: PolarPoint,
closestPoint: PolarPoint?,
distanceToClosest: Double,
iterator: Iterator<Point>
): Point? {
iterator: Iterator<PolarPoint>
): PolarPoint? {
if (!iterator.hasNext()) return closestPoint
val nextPoint = iterator.next()

Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/model/Scan2D.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,7 +25,7 @@ fun readFromTSV(tsv: File): List<Map<String, String>> =
fun readFromTSV(tsv: String): List<Map<String, String>> =
getTSVReader().readAllWithHeader(tsv)

class Scan2D(val pointCloud: List<Point>, private val scanner: Scanner) {
class Scan2D(val pointCloud: List<PolarPoint>, private val scanner: Scanner) {

fun rotateBy(angle: Double): List<Point> =
pointCloud.map { it.rotateBy(angle) }
Expand Down Expand Up @@ -79,7 +80,7 @@ class Scan2D(val pointCloud: List<Point>, private val scanner: Scanner) {


private fun calculatePoints(data: List<ScanData>, scanner: Scanner)
: List<Point> {
: List<PolarPoint> {
val countNull = AtomicInteger(0)
val countQuality = AtomicInteger(0)

Expand All @@ -90,7 +91,7 @@ class Scan2D(val pointCloud: List<Point>, 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 {
Expand Down
6 changes: 2 additions & 4 deletions src/main/kotlin/model/geometry/GeometricObject.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package model.geometry

const val PRECISION = 1e-8

interface GeometricObject<T> {
fun distanceTo(other: T): Double
interface GeometricObject {
fun distanceTo(other: GeometricObject): Double
fun toTSVString(): String
}
64 changes: 28 additions & 36 deletions src/main/kotlin/model/geometry/Line.kt
Original file line number Diff line number Diff line change
@@ -1,57 +1,49 @@
package model.geometry

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 angle: Double, val distance: Double) {

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())
import math.PRECISION
import math.distanceLineToPoint

data class Line(val slope: Double, val intercept: Double) : GeometricObject {

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())

override fun distanceTo(other: GeometricObject): Double =
when (other) {
is Point -> distanceLineToPoint(this, other)
is Line ->
if (other.slope != slope) 0.0
else throw NotImplementedError()
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
if (javaClass != other?.javaClass) return false

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)
}
}
53 changes: 16 additions & 37 deletions src/main/kotlin/model/geometry/Point.kt
Original file line number Diff line number Diff line change
@@ -1,64 +1,43 @@
package model.geometry

import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import math.PRECISION
import math.distanceLineToPoint
import math.distancePointToPoint

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<Point> {
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<Double, Double> {
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
if (javaClass != other?.javaClass) return false

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()
var 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)"
}
29 changes: 29 additions & 0 deletions src/main/kotlin/model/geometry/PolarPoint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package model.geometry

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<Double, Double> {
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)"
}
2 changes: 1 addition & 1 deletion src/test/kotlin/io/kotlintest/provided/ProjectConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading