Skip to content

Commit 8e8db4e

Browse files
committed
Initial commit
1 parent be49092 commit 8e8db4e

File tree

96 files changed

+16237
-9
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+16237
-9
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# korlibs-library-template
1+
# korlibs-math
File renamed without changes.

korlibs-simple/module.yaml korlibs-math/module.yaml

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ product:
44

55
apply: [ ../common.module-template.yaml ]
66

7-
aliases:
8-
- jvmAndAndroid: [jvm, android]
9-
107
dependencies:
8+
- com.soywiz:korlibs-math-core:6.0.0: exported
9+
- com.soywiz:korlibs-math-vector:6.0.0: exported
10+
- com.soywiz:korlibs-platform:6.0.0: exported
11+
- com.soywiz:korlibs-datastructure:6.0.0: exported
12+
- com.soywiz:korlibs-number:6.0.0: exported
1113

1214
test-dependencies:
13-
15+
- com.soywiz:korlibs-platform:6.0.0
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package korlibs.math
2+
3+
import kotlin.math.max
4+
import kotlin.math.min
5+
6+
fun DoubleArray.minOrElse(nil: Double): Double {
7+
if (isEmpty()) return nil
8+
var out = Double.POSITIVE_INFINITY
9+
for (i in 0..lastIndex) out = min(out, this[i])
10+
return out
11+
}
12+
13+
fun DoubleArray.maxOrElse(nil: Double): Double {
14+
if (isEmpty()) return nil
15+
var out = Double.NEGATIVE_INFINITY
16+
for (i in 0..lastIndex) out = max(out, this[i])
17+
return out
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
@file:Suppress("PackageDirectoryMismatch")
2+
3+
package korlibs.math.random
4+
5+
import korlibs.math.geom.*
6+
import korlibs.math.interpolation.*
7+
import kotlin.math.*
8+
import kotlin.random.*
9+
10+
fun Random.ints(): Sequence<Int> = sequence { while (true) yield(nextInt()) }
11+
fun Random.ints(from: Int, until: Int): Sequence<Int> = sequence { while (true) yield(nextInt(from, until)) }
12+
fun Random.ints(range: IntRange): Sequence<Int> = ints(range.first, range.last + 1)
13+
14+
fun Random.doubles(): Sequence<Double> = sequence { while (true) yield(nextDouble()) }
15+
fun Random.floats(): Sequence<Float> = sequence { while (true) yield(nextFloat()) }
16+
17+
fun <T> List<T>.random(random: Random = Random): T {
18+
if (this.isEmpty()) throw IllegalArgumentException("Empty list")
19+
return this[random.nextInt(this.size)]
20+
}
21+
22+
fun <T> List<T>.randomWithWeights(weights: List<Double>, random: Random = Random): T = random.weighted(this.zip(weights).toMap())
23+
24+
fun Random.nextDoubleInclusive() = (this.nextInt(0x1000001).toDouble() / 0x1000000.toDouble())
25+
fun Random.nextRatio(): Ratio = nextDouble().toRatio()
26+
fun Random.nextRatioInclusive(): Ratio = nextDoubleInclusive().toRatio()
27+
28+
operator fun Random.get(min: Ratio, max: Ratio): Ratio = Ratio(get(min.value, max.value))
29+
operator fun Random.get(min: Double, max: Double): Double = min + nextDouble() * (max - min)
30+
operator fun Random.get(min: Float, max: Float): Float = min + nextFloat() * (max - min)
31+
operator fun Random.get(min: Int, max: Int): Int = min + nextInt(max - min)
32+
operator fun Random.get(range: IntRange): Int = range.first + this.nextInt(range.last - range.first + 1)
33+
operator fun Random.get(range: LongRange): Long = range.first + this.nextLong() % (range.last - range.first + 1)
34+
operator fun <T : Interpolable<T>> Random.get(l: T, r: T): T = (this.nextDoubleInclusive()).toRatio().interpolate(l, r)
35+
operator fun Random.get(l: Angle, r: Angle): Angle = this.nextRatioInclusive().interpolateAngleDenormalized(l, r)
36+
operator fun <T> Random.get(list: List<T>): T = list[this[list.indices]]
37+
operator fun Random.get(rectangle: Rectangle): Point = Point(this[rectangle.left, rectangle.right], this[rectangle.top, rectangle.bottom])
38+
fun <T : MutableInterpolable<T>> T.setToRandom(min: T, max: T, random: Random = Random) { this.setToInterpolated(random.nextDouble().toRatio(), min, max) }
39+
40+
fun <T> Random.weighted(weights: Map<T, Double>): T = shuffledWeighted(weights).first()
41+
fun <T> Random.weighted(weights: RandomWeights<T>): T = shuffledWeighted(weights).first()
42+
43+
fun <T> Random.shuffledWeighted(weights: Map<T, Double>): List<T> = shuffledWeighted(RandomWeights(weights))
44+
fun <T> Random.shuffledWeighted(values: List<T>, weights: List<Double>): List<T> = shuffledWeighted(RandomWeights(values, weights))
45+
fun <T> Random.shuffledWeighted(weights: RandomWeights<T>): List<T> {
46+
val randoms = (0 until weights.items.size).map { -(nextDouble().pow(1.0 / weights.normalizedWeights[it])) }
47+
val sortedIndices = (0 until weights.items.size).sortedWith { a, b -> randoms[a].compareTo(randoms[b]) }
48+
return sortedIndices.map { weights.items[it] }
49+
}
50+
51+
data class RandomWeights<T>(val weightsMap: Map<T, Double>) {
52+
constructor(vararg pairs: Pair<T, Double>) : this(mapOf(*pairs))
53+
constructor(values: List<T>, weights: List<Double>) : this(values.zip(weights).toMap())
54+
55+
val items = weightsMap.keys.toList()
56+
val weights = weightsMap.values.toList()
57+
val normalizedWeights = normalizeWeights(weights)
58+
59+
companion object {
60+
private fun normalizeWeights(weights: List<Double>): List<Double> {
61+
val min = weights.minOrNull() ?: 0.0
62+
return weights.map { (it + min) + 1 }
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
@file:Suppress("PackageDirectoryMismatch")
2+
3+
package korlibs.math.segment
4+
5+
import korlibs.datastructure.BSearchResult
6+
import korlibs.datastructure.IntArrayList
7+
import korlibs.datastructure.genericBinarySearch
8+
import korlibs.math.annotations.*
9+
import kotlin.math.max
10+
import kotlin.math.min
11+
12+
/**
13+
* Non-overlapping SegmentSet
14+
*/
15+
@KormaExperimental
16+
class IntSegmentSet {
17+
@PublishedApi
18+
internal val min = IntArrayList(16)
19+
@PublishedApi
20+
internal val max = IntArrayList(16)
21+
val size get() = min.size
22+
fun isEmpty() = size == 0
23+
fun isNotEmpty() = size > 0
24+
25+
fun clear() = this.apply {
26+
min.clear()
27+
max.clear()
28+
}
29+
30+
fun copyFrom(other: IntSegmentSet) = this.apply {
31+
this.clear()
32+
addUnsafe(other)
33+
}
34+
35+
fun clone() = IntSegmentSet().copyFrom(this)
36+
37+
val minMin get() = if (isNotEmpty()) min.getAt(0) else 0
38+
val maxMax get() = if (isNotEmpty()) max.getAt(max.size - 1) else 0
39+
40+
fun findNearIndex(x: Int): BSearchResult = BSearchResult(genericBinarySearch(0, size) { v ->
41+
val min = this.min.getAt(v)
42+
val max = this.max.getAt(v)
43+
when {
44+
x < min -> +1
45+
x > max -> -1
46+
else -> 0
47+
}
48+
})
49+
50+
inline fun fastForEach(block: (n: Int, min: Int, max: Int) -> Unit) {
51+
for (n in 0 until size) block(n, min.getAt(n), max.getAt(n))
52+
}
53+
54+
fun findLeftBound(x: Int): Int {
55+
//if (size < 8) return 0 // Do not invest time on binary search on small sets
56+
return (genericBinarySearchLeft(0, size) { this.min.getAt(it).compareTo(x) }).coerceIn(0, size - 1)
57+
}
58+
fun findRightBound(x: Int): Int {
59+
//if (size < 8) return size - 1 // Do not invest time on binary search on small sets
60+
return (genericBinarySearchRight(0, size) { this.max.getAt(it).compareTo(x) }).coerceIn(0, size - 1)
61+
}
62+
63+
inline fun fastForEachInterestingRange(min: Int, max: Int, block: (n: Int, x1: Int, x2: Int) -> Unit) {
64+
if (isEmpty()) return
65+
val nmin = findLeftBound(min)
66+
val nmax = findRightBound(max)
67+
for (n in nmin..nmax) block(n, this.min.getAt(n), this.max.getAt(n))
68+
}
69+
70+
internal fun addUnsafe(min: Int, max: Int) = this.apply {
71+
check(min <= max)
72+
insertAt(size, min, max)
73+
}
74+
75+
internal fun addUnsafe(other: IntSegmentSet) = this.apply {
76+
this.min.add(other.min)
77+
this.max.add(other.max)
78+
}
79+
80+
fun add(other: IntSegmentSet) = this.apply {
81+
other.fastForEach { n, min, max ->
82+
add(min, max)
83+
}
84+
}
85+
86+
fun add(min: Int, max: Int) = this.apply {
87+
check(min <= max)
88+
when {
89+
isEmpty() -> insertAt(size, min, max)
90+
min == maxMax -> this.max[this.max.size - 1] = max
91+
max == minMin -> this.min[0] = min
92+
else -> {
93+
var removeStart = -1
94+
var removeCount = -1
95+
96+
fastForEachInterestingRange(min, max) { n, x1, x2 ->
97+
if (intersects(x1, x2, min, max)) {
98+
if (removeStart == -1) removeStart = n
99+
this.min[removeStart] = min(this.min.getAt(removeStart), min(x1, min))
100+
this.max[removeStart] = max(this.max.getAt(removeStart), max(x2, max))
101+
removeCount++
102+
}
103+
}
104+
105+
when {
106+
// Combined
107+
removeCount == 0 -> Unit
108+
removeCount > 0 -> removeAt(removeStart + 1, removeCount)
109+
// Insert at the beginning
110+
max < minMin -> insertAt(0, min, max)
111+
// Insert at the end
112+
min > maxMax -> insertAt(size, min, max)
113+
// Insert at a place
114+
else -> {
115+
for (m in findLeftBound(min).coerceAtLeast(1)..findRightBound(max)) {
116+
//for (m in 1..findRightBound(max)) {
117+
val prevMax = this.max.getAt(m - 1)
118+
val currMin = this.min.getAt(m)
119+
if (min > prevMax && max < currMin) {
120+
insertAt(m, min, max)
121+
return@apply
122+
}
123+
}
124+
125+
error("Unexpected")
126+
}
127+
}
128+
}
129+
}
130+
}
131+
132+
private fun insertAt(n: Int, min: Int, max: Int) {
133+
this.min.insertAt(n, min)
134+
this.max.insertAt(n, max)
135+
}
136+
137+
private fun removeAt(n: Int, count: Int) {
138+
this.min.removeAt(n, count)
139+
this.max.removeAt(n, count)
140+
}
141+
142+
//fun remove(min: Int, max: Int) = this.apply { TODO() }
143+
//fun intersect(min: Int, max: Int) = this.apply { TODO() }
144+
145+
inline fun intersection(min: Int, max: Int, out: (min: Int, max: Int) -> Unit): Boolean {
146+
var count = 0
147+
fastForEachInterestingRange(min, max) { n, x1, x2 ->
148+
if (intersects(x1, x2, min, max)) {
149+
out(max(x1, min), min(x2, max))
150+
count++
151+
}
152+
}
153+
154+
return count > 0
155+
}
156+
157+
// Use for testing
158+
// O(n * log(n))
159+
internal fun intersectionSlow(min: Int, max: Int): Pair<Int, Int>? {
160+
var out: Pair<Int, Int>? = null
161+
intersectionSlow(min, max) { x1, x2 -> out = x1 to x2 }
162+
return out
163+
}
164+
165+
// Use for testing
166+
// O(n^2)
167+
internal inline fun intersectionSlow(min: Int, max: Int, out: (min: Int, max: Int) -> Unit): Boolean {
168+
var count = 0
169+
fastForEach { n, x1, x2 ->
170+
if (intersects(x1, x2, min, max)) {
171+
out(max(x1, min), min(x2, max))
172+
count++
173+
}
174+
}
175+
return count > 0
176+
}
177+
178+
operator fun contains(v: Int): Boolean = findNearIndex(v).found
179+
180+
fun setToIntersect(a: IntSegmentSet, b: IntSegmentSet) = this.apply {
181+
val aSmaller = a.size < b.size
182+
val av = if (aSmaller) a else b
183+
val bv = if (aSmaller) b else a
184+
clear().also { av.fastForEach { n, x1, x2 -> bv.intersection(x1, x2) { min, max -> add(min, max) } } }
185+
}
186+
187+
// Use for testing
188+
internal fun setToIntersectSlow(a: IntSegmentSet, b: IntSegmentSet) = this.apply {
189+
clear().also { a.fastForEach { n, x1, x2 -> b.intersectionSlow(x1, x2) { min, max -> add(min, max) } } }
190+
}
191+
192+
@PublishedApi
193+
internal fun intersects(x1: Int, x2: Int, y1: Int, y2: Int): Boolean = x2 >= y1 && y2 >= x1
194+
@PublishedApi
195+
internal fun intersects(x1: Int, x2: Int, index: Int): Boolean = intersects(x1, x2, min.getAt(index), max.getAt(index))
196+
197+
@PublishedApi
198+
internal fun contains(v: Int, x1: Int, x2: Int): Boolean = v in x1 until x2
199+
@PublishedApi
200+
internal fun contains(v: Int, index: Int): Boolean = contains(v, min.getAt(index), max.getAt(index))
201+
202+
override fun toString(): String = buildString {
203+
append("[")
204+
fastForEach { n, min, max ->
205+
val first = (n == 0)
206+
if (!first) append(", ")
207+
append("$min-$max")
208+
}
209+
append("]")
210+
}
211+
}
212+
213+
// @TODO: In KDS latest versions
214+
@PublishedApi
215+
internal inline fun genericBinarySearchLeft(fromIndex: Int, toIndex: Int, check: (value: Int) -> Int): Int =
216+
genericBinarySearch(fromIndex, toIndex, invalid = { from, to, low, high -> min(low, high).coerceIn(from, to - 1) }, check = check)
217+
@PublishedApi
218+
internal inline fun genericBinarySearchRight(fromIndex: Int, toIndex: Int, check: (value: Int) -> Int): Int =
219+
genericBinarySearch(fromIndex, toIndex, invalid = { from, to, low, high -> max(low, high).coerceIn(from, to - 1) }, check = check)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package korlibs.math.geom
2+
3+
import korlibs.number.*
4+
5+
val Margin.topFixed: FixedShort get() = top.toFixedShort()
6+
val Margin.rightFixed: FixedShort get() = right.toFixedShort()
7+
val Margin.bottomFixed: FixedShort get() = bottom.toFixedShort()
8+
val Margin.leftFixed: FixedShort get() = left.toFixedShort()
9+
10+
val Margin.leftPlusRightFixed: FixedShort get() = leftFixed + rightFixed
11+
val Margin.topPlusBottomFixed: FixedShort get() = topFixed + bottomFixed
12+
val Margin.horizontalFixed: FixedShort get() = (leftFixed + rightFixed) / 2.toFixedShort()
13+
val Margin.verticalFixed: FixedShort get() = (topFixed + bottomFixed) / 2.toFixedShort()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package korlibs.math.geom
2+
3+
import korlibs.math.geom.shape.*
4+
import korlibs.math.geom.vector.*
5+
6+
fun RoundRectangle.toVectorPath(): VectorPath = buildVectorPath { roundRect(this@toVectorPath) }
7+
fun Polygon.toVectorPath(): VectorPath = buildVectorPath { polygon(points, close = true) }
8+
fun Polyline.toVectorPath(): VectorPath = buildVectorPath { polygon(points, close = false) }
9+
fun Rectangle.toVectorPath(): VectorPath = buildVectorPath { rect(this@toVectorPath) }
10+
fun Line.toVectorPath(): VectorPath = buildVectorPath { moveTo(a); lineTo(b) }
11+
fun Circle.toVectorPath(): VectorPath = buildVectorPath { circle(this@toVectorPath.center, this@toVectorPath.radius) }
12+
fun Ellipse.toVectorPath(): VectorPath = buildVectorPath { ellipse(this@toVectorPath.center, this@toVectorPath.radius) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package korlibs.math.geom
2+
3+
import korlibs.number.*
4+
5+
data class PointFixed(val x: Fixed, val y: Fixed) {
6+
operator fun unaryMinus(): PointFixed = PointFixed(-this.x, -this.y)
7+
operator fun unaryPlus(): PointFixed = this
8+
9+
operator fun plus(that: PointFixed): PointFixed = PointFixed(this.x + that.x, this.y + that.y)
10+
operator fun minus(that: PointFixed): PointFixed = PointFixed(this.x - that.x, this.y - that.y)
11+
operator fun times(that: PointFixed): PointFixed = PointFixed(this.x * that.x, this.y * that.y)
12+
operator fun times(that: Fixed): PointFixed = PointFixed(this.x * that, this.y * that)
13+
operator fun div(that: PointFixed): PointFixed = PointFixed(this.x / that.x, this.y / that.y)
14+
operator fun rem(that: PointFixed): PointFixed = PointFixed(this.x % that.x, this.y % that.y)
15+
16+
override fun toString(): String = "($x, $y)"
17+
}

0 commit comments

Comments
 (0)