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.consumeNull import ru.dbotthepony.kommons.math.linearInterpolation import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.json.builder.EnumAdapter import ru.dbotthepony.kstarbound.math.Vector2dTypeAdapter 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.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() 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() { 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() val yRanges = ArrayList() 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() 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 } } } class JsonConfigFunction(val data: ImmutableList>) { fun evaluate(point: Double): JsonElement? { return data.lastOrNull { it.first <= point }?.second } fun evaluate(point: Double, adapter: TypeAdapter): KOptional { val eval = data.lastOrNull { it.first <= point }?.second ?: return KOptional() return KOptional(adapter.fromJsonTree(eval)) } class Adapter(gson: Gson) : TypeAdapter() { 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>> 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())) } } }