328 lines
8.7 KiB
Kotlin
328 lines
8.7 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.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.kstarbound.io.json.InternedJsonElementAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.builder.IStringSerializable
|
|
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
|
import ru.dbotthepony.kvector.util.linearInterpolation
|
|
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
|
|
|
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.peek() == JsonToken.NULL)
|
|
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>() {
|
|
val elements = gson.getAdapter(JsonArray::class.java)
|
|
|
|
override fun write(out: JsonWriter, value: Json2Function?) {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun read(reader: JsonReader): Json2Function? {
|
|
if (reader.peek() == JsonToken.NULL)
|
|
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 = elements.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
|
|
}
|
|
}
|
|
}
|