diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt index face0b3bc..900e3a550 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt @@ -14,13 +14,19 @@ import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.core.RandomSource2Generator import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.asAngle +import ru.dbotthepony.mc.otm.core.math.clusterize import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.core.math.times +import java.util.Random import kotlin.math.PI +import kotlin.math.absoluteValue class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { + private val random = Random() + override fun render( tile: EnergyCounterBlockEntity, p_112308_: Float, @@ -80,7 +86,7 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con if (maximum.isZero || maximum.isInfinite) { normalized = FloatArray(chart.width) { - if (chart[it].isInfinite) 0.8f else 0.0f + if (chart[it].isInfinite) 1f else 0.0f } levelLabels = ChartLevelLabels( @@ -97,9 +103,25 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con val map = Float2ObjectArrayMap() map[1f] = maximum.formatPower() - map[0.75f] = (maximum * 0.75).formatPower() - map[0.5f] = (maximum * 0.5).formatPower() - map[0.25f] = (maximum * 0.25).formatPower() + + for (cluster in chart.asIterable().clusterize(random)) { + val perc = (cluster.center / maximum).toFloat() + + if (map.keys.none { (it - perc).absoluteValue < 0.08f }) { + map[perc] = cluster.center.formatPower() + } + } + + /* + if (map.keys.none { (it - 0.75f).absoluteValue < 0.1f }) + map[0.75f] = (maximum * 0.75).formatPower() + + if (map.keys.none { (it - 0.5f).absoluteValue < 0.1f }) + map[0.5f] = (maximum * 0.5).formatPower() + + if (map.keys.none { (it - 0.25f).absoluteValue < 0.1f }) + map[0.25f] = (maximum * 0.25).formatPower() + */ levelLabels = ChartLevelLabels( labels = map, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt index 9c187fe74..1b262ce32 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt @@ -8,11 +8,15 @@ import ru.dbotthepony.mc.otm.client.render.ChartMouseLabels import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.renderChart import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.RandomSource2Generator import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.clusterize +import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.core.util.formatTickDuration +import kotlin.math.absoluteValue open class DecimalHistoryChartPanel>( screen: S, @@ -31,25 +35,41 @@ open class DecimalHistoryChartPanel>( if (maximum.isZero || maximum.isInfinite) { normalized = FloatArray(chart.width) { - if (chart[it].isInfinite) 0.8f else 0.0f + if (chart[it].isInfinite) 1f else 0.0f } levelLabels = ChartLevelLabels( labels = mapOf( - 0.8f to TextComponent("∞"), + 1f to TextComponent("∞"), // 0f to formatText(Decimal.ZERO), ), font = font ) } else { - normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() * 0.9f } + normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() } val map = Float2ObjectArrayMap() - map[0.9f] = formatText(maximum) - map[0.9f * 0.75f] = formatText(maximum * 0.75) - map[0.9f * 0.5f] = formatText(maximum * 0.5) - map[0.9f * 0.25f] = formatText(maximum * 0.25) + map[1f] = formatText(maximum) + + for (cluster in chart.asIterable().clusterize(randomGenerator)) { + val perc = (cluster.center / maximum).toFloat() + + if (map.keys.none { (it - perc).absoluteValue < 0.08f }) { + map[perc] = cluster.center.formatPower() + } + } + + /* + if (map.keys.none { (it - 0.75f).absoluteValue < 0.1f }) + map[0.75f] = formatText(maximum * 0.75) + + if (map.keys.none { (it - 0.5f).absoluteValue < 0.1f }) + map[0.5f] = formatText(maximum * 0.5) + + if (map.keys.none { (it - 0.25f).absoluteValue < 0.1f }) + map[0.25f] = formatText(maximum * 0.25) + */ levelLabels = ChartLevelLabels( labels = map, @@ -63,7 +83,8 @@ open class DecimalHistoryChartPanel>( graphics.pose, listOf(normalized to RGBAColor.WHITE), width, - height, + height - font.lineHeight - 4f, + y = font.lineHeight.toFloat() + 4f, labels = ChartMouseLabels( mouseX - absoluteX, mouseY - absoluteY, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt index f12b165fe..b6c8dc559 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt @@ -26,11 +26,13 @@ import ru.dbotthepony.mc.otm.client.render.popScissorRect import ru.dbotthepony.mc.otm.client.render.pushScissorRect import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.input.QueryUserPanel +import ru.dbotthepony.mc.otm.core.RandomSource2Generator import ru.dbotthepony.mc.otm.core.collect.concatIterators import ru.dbotthepony.mc.otm.core.collect.flatMap import java.util.* import java.util.concurrent.CopyOnWriteArrayList import java.util.function.Predicate +import java.util.random.RandomGenerator import kotlin.collections.ArrayList import kotlin.math.roundToInt @@ -144,6 +146,10 @@ open class EditablePanel( } } + val randomGenerator: RandomGenerator by lazy { + RandomSource2Generator(random) + } + /** * Bigger values means lesser priority while docking, rendering and processing inputs. */ diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt index 48ddcdd96..00c0c85d8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt @@ -525,14 +525,14 @@ class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_ if (isPreview && !layoutInvalidated) { if (firstTick) { - scroller.init.invoke(this, RandomSource2Generator(random)) + scroller.init.invoke(this, randomGenerator) } - val status = scroller.scroll.invoke(this, RandomSource2Generator(random)) + val status = scroller.scroll.invoke(this, randomGenerator) if (!status) { scroller = PreviewScrollers.entries.let { it[random.nextInt(it.size)] } - scroller.init.invoke(this, RandomSource2Generator(random)) + scroller.init.invoke(this, randomGenerator) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt index 69b07bae9..22b244067 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt @@ -1,10 +1,9 @@ package ru.dbotthepony.mc.otm.core.math -import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.map -import ru.dbotthepony.mc.otm.core.collect.toList +import it.unimi.dsi.fastutil.objects.ObjectArrayList import ru.dbotthepony.mc.otm.core.random import java.util.random.RandomGenerator +import kotlin.math.min interface Cluster { val values: List @@ -35,8 +34,8 @@ private class ClusterValue>(val value: V, var cluster: Mutable } } -private class MutableCluster>(var center: V) { - val values = ArrayList>() +private class MutableCluster>(var center: V, expectedSize: Int) { + val values = ObjectArrayList>(min(100, expectedSize)) inline fun calculateCenter(identity: V, plus: (V, V) -> V, minus: (V, V) -> V, divInt: (V, Int) -> V, abs: (V) -> V): Boolean { if (values.isEmpty()) return false @@ -69,6 +68,7 @@ private inline fun > Iterable.clusterize( if (!itr.hasNext()) return listOf() + var expectedSize = 1 var min = itr.next() var max = min @@ -77,6 +77,7 @@ private inline fun > Iterable.clusterize( min = minOf(min, value) max = maxOf(max, value) + expectedSize++ } if (min == max) { @@ -84,15 +85,18 @@ private inline fun > Iterable.clusterize( } var targetClusters = initialClusters + val clusters = ObjectArrayList>(initialClusters) + val values = ObjectArrayList>(expectedSize) while (true) { - val clusters = ArrayList>() - val values = ArrayList>() + clusters.clear() + values.clear() + var converged = false - var overSaturated = false + var oversaturation = false for (i in 0 until targetClusters) { - clusters.add(MutableCluster(identity)) + clusters.add(MutableCluster(identity, expectedSize)) } for (value in this) { @@ -112,7 +116,7 @@ private inline fun > Iterable.clusterize( } if (!converged) { - val emptyClusters = ArrayList>() + val emptyClusters = ObjectArrayList>() val citr = clusters.iterator() for (cluster in citr) { @@ -143,7 +147,7 @@ private inline fun > Iterable.clusterize( candidate.error = identity clusters.add(cluster) } else { - overSaturated = true + oversaturation = true break } } @@ -152,18 +156,20 @@ private inline fun > Iterable.clusterize( val maxError = values.maxOf { it.error } - if (!overSaturated && targetClusters < values.size && heuristics(min, max, maxError)) { + if (!oversaturation && targetClusters < values.size && heuristics(min, max, maxError)) { targetClusters++ } else { - return clusters.iterator() + return clusters.stream() .filter { it.values.isNotEmpty() } .map { Cluster.Impl(it.values.map { it.value }, it.center) } + .sorted { o1, o2 -> o2.values.size.compareTo(o1.values.size) } // большие кластеры должны идти первыми .toList() } } } -private val DECIMAL_ERROR_TOLERANCE = Decimal("0.04") +// TODO: could use some tweaking +private val DECIMAL_ERROR_TOLERANCE = Decimal("0.02") fun Iterable.clusterize(random: RandomGenerator, clusters: Int? = null): List> { return clusterize(random, clusters ?: 1, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error ->