KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt

369 lines
9.9 KiB
Kotlin

package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.Vector2dTypeAdapter
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.math.linearInterpolation
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.builder.EnumAdapter
enum class JsonFunctionInterpolation {
LINEAR {
override fun interpolate(t: Double, a: Double, b: Double): Double {
return linearInterpolation(t, a, b)
}
};
abstract fun interpolate(t: Double, a: Double, b: Double): Double
}
enum class JsonFunctionConstraint {
CLAMP {
override fun process(x: Double, lower: Double, upper: Double): Double {
return x.coerceIn(lower, upper)
}
};
abstract fun process(x: Double, lower: Double, upper: Double): Double
}
data class DoubleRange(val start: Double, val end: Double) : Comparable<Double> {
init {
require(start <= end) { "start <= end: $start <= $end" }
}
val length = end - start
operator fun contains(value: Double): Boolean {
return value in start .. end
}
fun determine(value: Double): Double {
if (value < start) {
return 0.0
} else if (value > end) {
return 1.0
} else {
return (value - start) / length
}
}
override fun compareTo(other: Double): Int {
if (other < start) {
return 1
} else if (other > end) {
return -1
} else {
return 0
}
}
}
class JsonFunction(
val interpolation: JsonFunctionInterpolation,
val constraint: JsonFunctionConstraint,
ranges: Collection<Vector2d>
) {
val ranges: ImmutableList<Vector2d> = ranges.stream().sorted { o1, o2 -> o1.x.compareTo(o2.x) }.collect(ImmutableList.toImmutableList())
val lowerBound = this.ranges.first().x
val upperBound = this.ranges.last().x
fun evaluate(input: Double): Double {
val x = constraint.process(input, lowerBound, upperBound)
when (interpolation) {
JsonFunctionInterpolation.LINEAR -> {
if (x <= lowerBound) {
return ranges[0].y
} else if (x >= upperBound) {
return ranges.last().y
}
for (i in 0 until ranges.size - 1) {
val a = ranges[i]
val b = ranges[i + 1]
if (x in a.x .. b.x) {
return linearInterpolation((x - a.x) / (b.x - a.x), a.y, b.y)
}
}
return ranges.last().y
}
}
}
companion object : TypeAdapter<JsonFunction>() {
val CONSTRAINT_ADAPTER = EnumAdapter(JsonFunctionConstraint::class)
val INTERPOLATION_ADAPTER = EnumAdapter(JsonFunctionInterpolation::class)
override fun write(out: JsonWriter, value: JsonFunction?) {
TODO("Not yet implemented")
}
override fun read(reader: JsonReader): JsonFunction? {
if (reader.consumeNull())
return null
reader.beginArray()
val interpolation = INTERPOLATION_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing interpolation")
val constraint = CONSTRAINT_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing constraint")
val ranges = ArrayList<Vector2d>()
while (reader.peek() != JsonToken.END_ARRAY) {
ranges.add(Vector2dTypeAdapter.read(reader))
}
reader.endArray()
return JsonFunction(interpolation, constraint, ImmutableList.copyOf(ranges))
}
}
}
class Json2Function(
val interpolation: JsonFunctionInterpolation,
val constraint: JsonFunctionConstraint,
xRanges: List<Ranges>, // значения [a, b, c, d]
yRanges: List<Ranges>, // значения [a, [b, c, d]]
) {
init {
require(xRanges.isNotEmpty()) { "X ranges are empty" }
require(yRanges.isNotEmpty()) { "Y ranges are empty" }
}
val xRanges: ImmutableList<Ranges> = xRanges.stream().sorted { o1, o2 -> o1.edge.compareTo(o2.edge) }.collect(ImmutableList.toImmutableList())
val yRanges: ImmutableList<Ranges> = yRanges.stream().sorted { o1, o2 -> o1.edge.compareTo(o2.edge) }.collect(ImmutableList.toImmutableList())
data class Ranges(val interpolation: JsonFunctionInterpolation, val edge: Double, val ranges: ImmutableList<DoubleRange>) {
val step = 1.0 / ranges.size.toDouble()
fun map(value: Double): Double {
if (value <= ranges.first().start) {
return 0.0
} else if (value >= ranges.last().end) {
return 1.0
}
var i = 0.0
for (range in ranges) {
if (value in range) {
return i + range.determine(value) * step
} else {
i += step
}
}
return i
}
fun unmap(t: Double): Double {
if (t <= 0.0) {
return ranges.first().start
} else if (t >= 1.0) {
return ranges.last().end
}
val unmap = t / step
val range = ranges[unmap.toInt()]
return interpolation.interpolate(unmap % 1.0, range.start, range.end)
}
}
fun evaluate(x: Double, y: Double): Double {
var xT = 0.0
if (xRanges.size == 1) {
xT = xRanges[0].map(x)
} else {
var hit = false
for (i in 1 until xRanges.size) {
val a = xRanges[i - 1]
val b = xRanges[i]
if (x in a.edge .. b.edge) {
xT = interpolation.interpolate((x - a.edge) / (b.edge - a.edge), a.map(x), b.map(x))
hit = true
break
}
}
if (!hit) {
xT = xRanges.last().map(x)
}
}
if (yRanges.size == 1) {
return yRanges[0].unmap(xT)
} else {
for (i in 1 until yRanges.size) {
val a = yRanges[i - 1]
val b = yRanges[i]
if (y in a.edge .. b.edge) {
return interpolation.interpolate((y - a.edge) / (b.edge - a.edge), a.unmap(xT), b.unmap(xT))
}
}
return yRanges.last().unmap(xT)
}
}
companion object : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == Json2Function::class.java) {
return object : TypeAdapter<Json2Function>() {
override fun write(out: JsonWriter, value: Json2Function?) {
TODO("Not yet implemented")
}
override fun read(reader: JsonReader): Json2Function? {
if (reader.consumeNull())
return null
reader.beginArray()
val interpolation = JsonFunction.INTERPOLATION_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing interpolation")
val constraint = JsonFunction.CONSTRAINT_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing constraint")
val xRanges = ArrayList<Ranges>()
val yRanges = ArrayList<Ranges>()
val elements = Starbound.ELEMENTS_ADAPTER.arrays.read(reader)
for ((i, row) in elements.withIndex()) {
if (row !is JsonArray) {
throw JsonSyntaxException("Invalid function ranges at row $i")
}
if (row.size() < 2) {
throw JsonSyntaxException("Row at $i should have at least 2 elements")
}
val edge = row[0].asDouble
if (row[1] is JsonArray) {
val ySource = row[1].asJsonArray
if (ySource.size() < 2) {
throw JsonSyntaxException("Y ranges at row $i should have at least 2 elements")
}
val ranges = ImmutableList.Builder<DoubleRange>()
var prev = ySource[0].asDouble
for (elem in 1 until ySource.size()) {
val v = ySource[elem].asDouble
ranges.add(DoubleRange(prev, v))
prev = v
}
yRanges.add(Ranges(interpolation, edge, ranges.build()))
} else {
if (row.size() < 3) {
throw JsonSyntaxException("Row at $i should have at least 3 elements")
}
val ranges = ImmutableList.Builder<DoubleRange>()
var prev = row[1].asDouble
for (elem in 2 until row.size()) {
val v = row[elem].asDouble
ranges.add(DoubleRange(prev, v))
prev = v
}
xRanges.add(Ranges(interpolation, edge, ranges.build()))
}
}
reader.endArray()
if (xRanges.isEmpty()) {
throw JsonSyntaxException("No X ranges")
}
if (yRanges.isEmpty()) {
throw JsonSyntaxException("No Y ranges")
}
val size = xRanges.first().ranges.size
var broken = xRanges.firstOrNull { it.ranges.size != size }
if (broken != null) {
throw JsonSyntaxException("One or more of X ranges are having different size (found ${broken.ranges.size}, expected $size)")
}
broken = yRanges.firstOrNull { it.ranges.size != size }
if (broken != null) {
throw JsonSyntaxException("One or more of Y ranges are having different size (found ${broken.ranges.size}, expected $size)")
}
return Json2Function(interpolation, constraint, xRanges, yRanges)
}
} as TypeAdapter<T>
}
return null
}
}
}
class JsonConfigFunction(val data: ImmutableList<Pair<Double, JsonElement>>) {
fun evaluate(point: Double): JsonElement? {
return data.lastOrNull { it.first <= point }?.second
}
fun <T> evaluate(point: Double, adapter: TypeAdapter<T>): KOptional<T> {
val eval = data.lastOrNull { it.first <= point }?.second ?: return KOptional()
return KOptional(adapter.fromJsonTree(eval))
}
class Adapter(gson: Gson) : TypeAdapter<JsonConfigFunction>() {
val parent = gson.getAdapter(
TypeToken.getParameterized(
ImmutableList::class.java,
TypeToken.getParameterized(Pair::class.java,
java.lang.Double::class.java,
JsonElement::class.java
).type
)
) as TypeAdapter<ImmutableList<Pair<Double, JsonElement>>>
override fun write(out: JsonWriter, value: JsonConfigFunction?) {
if (value == null)
out.nullValue()
else
parent.write(out, value.data)
}
override fun read(`in`: JsonReader): JsonConfigFunction? {
if (`in`.consumeNull())
return null
return JsonConfigFunction(
parent.read(`in`)
.stream()
.sorted { o1, o2 -> o1.first.compareTo(o2.first) }
.collect(ImmutableList.toImmutableList()))
}
}
}