|
| 1 | +package io.monstarlab.mosaic.slider |
| 2 | + |
| 3 | +public class SensitivityDistribution private constructor( |
| 4 | + equationMap: Map<Float, LinearEquation>, |
| 5 | +) { |
| 6 | + private var _equationList: MutableList<RangedLinearEquation> = |
| 7 | + mutableListOf<RangedLinearEquation>() |
| 8 | + public val equationList: List<RangedLinearEquation> get() = _equationList.toList() |
| 9 | + |
| 10 | + init { |
| 11 | + var previous: Pair<Float, LinearEquation>? = null |
| 12 | + // get the max range start value to help us identify the last equation |
| 13 | + val maxRangeStart = equationMap.maxOf { it.key } |
| 14 | + equationMap.forEach { pair -> |
| 15 | + previous?.let { |
| 16 | + addEquation(it.second, it.first, pair.key) |
| 17 | + } |
| 18 | + // add the last equation in the range and make sure the range for it ends at 1f |
| 19 | + if (pair.key == maxRangeStart) { |
| 20 | + addEquation(pair.value, pair.key, 1f) |
| 21 | + } |
| 22 | + previous = pair.toPair() |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + private fun addEquation(equation: LinearEquation, rangeStart: Float, rangeEnd: Float) { |
| 27 | + val offsetRange = rangeStart..rangeEnd |
| 28 | + val valueRange = |
| 29 | + equation.valueFromOffset(rangeStart)..equation.valueFromOffset(rangeEnd) |
| 30 | + _equationList.add( |
| 31 | + RangedLinearEquation( |
| 32 | + equation = equation, |
| 33 | + offsetRange = offsetRange, |
| 34 | + valueRange = valueRange, |
| 35 | + ), |
| 36 | + ) |
| 37 | + } |
| 38 | + |
| 39 | + public fun valueFromOffset(offset: Float): Float? = |
| 40 | + _equationList.firstOrNull { offset in it.offsetRange }?.equation?.valueFromOffset(offset) |
| 41 | + |
| 42 | + public fun offsetFromValue(value: Float): Float? = |
| 43 | + _equationList.firstOrNull { value in it.valueRange }?.equation?.offsetFromValue(value) |
| 44 | + |
| 45 | + public class Builder { |
| 46 | + private var equationRangeStartMap = mutableMapOf<Float, LinearEquation>() |
| 47 | + private var maxRangeValue: Float = -1f |
| 48 | + private var lastEquation: LinearEquation? = null |
| 49 | + |
| 50 | + public fun add(sensitivity: Float, rangeStart: Float): Builder = apply { |
| 51 | + if (equationRangeStartMap.isEmpty() && rangeStart != 0f) { |
| 52 | + throw FirstValueNotZeroException() |
| 53 | + } |
| 54 | + if (rangeStart !in 0.0f..1f) throw OutOfRangeException() |
| 55 | + if (rangeStart < maxRangeValue) throw OverlappingRangeException() |
| 56 | + |
| 57 | + val equationRangeStartPair = calcNextEquation(sensitivity, rangeStart, lastEquation) |
| 58 | + equationRangeStartMap[equationRangeStartPair.first] = equationRangeStartPair.second |
| 59 | + lastEquation = equationRangeStartPair.second |
| 60 | + maxRangeValue = rangeStart |
| 61 | + } |
| 62 | + |
| 63 | + private fun calcNextEquation( |
| 64 | + slope: Float, |
| 65 | + rangeStart: Float, |
| 66 | + previousEquation: LinearEquation?, |
| 67 | + ): Pair<Float, LinearEquation> { |
| 68 | + val y0 = previousEquation?.valueFromOffset(rangeStart) ?: 0f |
| 69 | + val c = y0 - slope * rangeStart |
| 70 | + return Pair(rangeStart, LinearEquation(slope, c)) |
| 71 | + } |
| 72 | + |
| 73 | + public fun build(): SensitivityDistribution { |
| 74 | + return SensitivityDistribution(equationRangeStartMap) |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + public class FirstValueNotZeroException : |
| 79 | + Exception("The range of the first value added should start from zero") |
| 80 | + |
| 81 | + public class OutOfRangeException : Exception("The range value should be between 0 and 1") |
| 82 | + |
| 83 | + public class OverlappingRangeException : |
| 84 | + Exception("can't add overlapping ranges with different sensitivity") |
| 85 | +} |
0 commit comments