Switch energy counter chart guiding line to clustering too

This commit is contained in:
DBotThePony 2024-11-15 14:38:14 +07:00
parent 7664b0b4de
commit ac2c387601
Signed by: DBot
GPG Key ID: DCC23B5715498507
5 changed files with 84 additions and 29 deletions

View File

@ -14,13 +14,19 @@ import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.client.render.*
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.kommons.math.RGBAColor 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.Decimal
import ru.dbotthepony.mc.otm.core.math.asAngle 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.util.formatPower
import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.core.math.times
import java.util.Random
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.absoluteValue
class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<EnergyCounterBlockEntity> { class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<EnergyCounterBlockEntity> {
private val random = Random()
override fun render( override fun render(
tile: EnergyCounterBlockEntity, tile: EnergyCounterBlockEntity,
p_112308_: Float, p_112308_: Float,
@ -80,7 +86,7 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con
if (maximum.isZero || maximum.isInfinite) { if (maximum.isZero || maximum.isInfinite) {
normalized = FloatArray(chart.width) { normalized = FloatArray(chart.width) {
if (chart[it].isInfinite) 0.8f else 0.0f if (chart[it].isInfinite) 1f else 0.0f
} }
levelLabels = ChartLevelLabels( levelLabels = ChartLevelLabels(
@ -97,9 +103,25 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con
val map = Float2ObjectArrayMap<Component>() val map = Float2ObjectArrayMap<Component>()
map[1f] = maximum.formatPower() map[1f] = maximum.formatPower()
map[0.75f] = (maximum * 0.75).formatPower()
map[0.5f] = (maximum * 0.5).formatPower() for (cluster in chart.asIterable().clusterize(random)) {
map[0.25f] = (maximum * 0.25).formatPower() 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( levelLabels = ChartLevelLabels(
labels = map, labels = map,

View File

@ -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.MGUIGraphics
import ru.dbotthepony.mc.otm.client.render.renderChart import ru.dbotthepony.mc.otm.client.render.renderChart
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen 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.chart.DecimalHistoryChart
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal 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 ru.dbotthepony.mc.otm.core.util.formatTickDuration
import kotlin.math.absoluteValue
open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>( open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>(
screen: S, screen: S,
@ -31,25 +35,41 @@ open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>(
if (maximum.isZero || maximum.isInfinite) { if (maximum.isZero || maximum.isInfinite) {
normalized = FloatArray(chart.width) { normalized = FloatArray(chart.width) {
if (chart[it].isInfinite) 0.8f else 0.0f if (chart[it].isInfinite) 1f else 0.0f
} }
levelLabels = ChartLevelLabels( levelLabels = ChartLevelLabels(
labels = mapOf( labels = mapOf(
0.8f to TextComponent(""), 1f to TextComponent(""),
// 0f to formatText(Decimal.ZERO), // 0f to formatText(Decimal.ZERO),
), ),
font = font font = font
) )
} else { } else {
normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() * 0.9f } normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() }
val map = Float2ObjectArrayMap<Component>() val map = Float2ObjectArrayMap<Component>()
map[0.9f] = formatText(maximum) map[1f] = formatText(maximum)
map[0.9f * 0.75f] = formatText(maximum * 0.75)
map[0.9f * 0.5f] = formatText(maximum * 0.5) for (cluster in chart.asIterable().clusterize(randomGenerator)) {
map[0.9f * 0.25f] = formatText(maximum * 0.25) 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( levelLabels = ChartLevelLabels(
labels = map, labels = map,
@ -63,7 +83,8 @@ open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>(
graphics.pose, graphics.pose,
listOf(normalized to RGBAColor.WHITE), listOf(normalized to RGBAColor.WHITE),
width, width,
height, height - font.lineHeight - 4f,
y = font.lineHeight.toFloat() + 4f,
labels = ChartMouseLabels( labels = ChartMouseLabels(
mouseX - absoluteX, mouseX - absoluteX,
mouseY - absoluteY, mouseY - absoluteY,

View File

@ -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.render.pushScissorRect
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.input.QueryUserPanel 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.concatIterators
import ru.dbotthepony.mc.otm.core.collect.flatMap import ru.dbotthepony.mc.otm.core.collect.flatMap
import java.util.* import java.util.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.Predicate import java.util.function.Predicate
import java.util.random.RandomGenerator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -144,6 +146,10 @@ open class EditablePanel<out S : Screen>(
} }
} }
val randomGenerator: RandomGenerator by lazy {
RandomSource2Generator(random)
}
/** /**
* Bigger values means lesser priority while docking, rendering and processing inputs. * Bigger values means lesser priority while docking, rendering and processing inputs.
*/ */

View File

@ -525,14 +525,14 @@ class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_
if (isPreview && !layoutInvalidated) { if (isPreview && !layoutInvalidated) {
if (firstTick) { 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) { if (!status) {
scroller = PreviewScrollers.entries.let { it[random.nextInt(it.size)] } scroller = PreviewScrollers.entries.let { it[random.nextInt(it.size)] }
scroller.init.invoke(this, RandomSource2Generator(random)) scroller.init.invoke(this, randomGenerator)
} }
} }
} }

View File

@ -1,10 +1,9 @@
package ru.dbotthepony.mc.otm.core.math package ru.dbotthepony.mc.otm.core.math
import ru.dbotthepony.mc.otm.core.collect.filter import it.unimi.dsi.fastutil.objects.ObjectArrayList
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.toList
import ru.dbotthepony.mc.otm.core.random import ru.dbotthepony.mc.otm.core.random
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
import kotlin.math.min
interface Cluster<V : Any> { interface Cluster<V : Any> {
val values: List<V> val values: List<V>
@ -35,8 +34,8 @@ private class ClusterValue<V : Comparable<V>>(val value: V, var cluster: Mutable
} }
} }
private class MutableCluster<V : Comparable<V>>(var center: V) { private class MutableCluster<V : Comparable<V>>(var center: V, expectedSize: Int) {
val values = ArrayList<ClusterValue<V>>() val values = ObjectArrayList<ClusterValue<V>>(min(100, expectedSize))
inline fun calculateCenter(identity: V, plus: (V, V) -> V, minus: (V, V) -> V, divInt: (V, Int) -> V, abs: (V) -> V): Boolean { 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 if (values.isEmpty()) return false
@ -69,6 +68,7 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
if (!itr.hasNext()) if (!itr.hasNext())
return listOf() return listOf()
var expectedSize = 1
var min = itr.next() var min = itr.next()
var max = min var max = min
@ -77,6 +77,7 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
min = minOf(min, value) min = minOf(min, value)
max = maxOf(max, value) max = maxOf(max, value)
expectedSize++
} }
if (min == max) { if (min == max) {
@ -84,15 +85,18 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
} }
var targetClusters = initialClusters var targetClusters = initialClusters
val clusters = ObjectArrayList<MutableCluster<V>>(initialClusters)
val values = ObjectArrayList<ClusterValue<V>>(expectedSize)
while (true) { while (true) {
val clusters = ArrayList<MutableCluster<V>>() clusters.clear()
val values = ArrayList<ClusterValue<V>>() values.clear()
var converged = false var converged = false
var overSaturated = false var oversaturation = false
for (i in 0 until targetClusters) { for (i in 0 until targetClusters) {
clusters.add(MutableCluster(identity)) clusters.add(MutableCluster(identity, expectedSize))
} }
for (value in this) { for (value in this) {
@ -112,7 +116,7 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
} }
if (!converged) { if (!converged) {
val emptyClusters = ArrayList<MutableCluster<V>>() val emptyClusters = ObjectArrayList<MutableCluster<V>>()
val citr = clusters.iterator() val citr = clusters.iterator()
for (cluster in citr) { for (cluster in citr) {
@ -143,7 +147,7 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
candidate.error = identity candidate.error = identity
clusters.add(cluster) clusters.add(cluster)
} else { } else {
overSaturated = true oversaturation = true
break break
} }
} }
@ -152,18 +156,20 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
val maxError = values.maxOf { it.error } 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++ targetClusters++
} else { } else {
return clusters.iterator() return clusters.stream()
.filter { it.values.isNotEmpty() } .filter { it.values.isNotEmpty() }
.map { Cluster.Impl(it.values.map { it.value }, it.center) } .map { Cluster.Impl(it.values.map { it.value }, it.center) }
.sorted { o1, o2 -> o2.values.size.compareTo(o1.values.size) } // большие кластеры должны идти первыми
.toList() .toList()
} }
} }
} }
private val DECIMAL_ERROR_TOLERANCE = Decimal("0.04") // TODO: could use some tweaking
private val DECIMAL_ERROR_TOLERANCE = Decimal("0.02")
fun Iterable<Decimal>.clusterize(random: RandomGenerator, clusters: Int? = null): List<Cluster<Decimal>> { fun Iterable<Decimal>.clusterize(random: RandomGenerator, clusters: Int? = null): List<Cluster<Decimal>> {
return clusterize(random, clusters ?: 1, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error -> return clusterize(random, clusters ?: 1, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error ->