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 { 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 ) { val ranges: ImmutableList = 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() { 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() 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, // значения [a, b, c, d] yRanges: List, // значения [a, [b, c, d]] ) { init { require(xRanges.isNotEmpty()) { "X ranges are empty" } require(yRanges.isNotEmpty()) { "Y ranges are empty" } } val xRanges: ImmutableList = xRanges.stream().sorted { o1, o2 -> o1.edge.compareTo(o2.edge) }.collect(ImmutableList.toImmutableList()) val yRanges: ImmutableList = 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) { 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 create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == Json2Function::class.java) { return object : TypeAdapter() { 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() val yRanges = ArrayList() 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() 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() 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 } return null } } }