From 2bf1eb5999ab44cca98543fc3906c4fa09159951 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Tue, 19 May 2026 19:06:26 -0500 Subject: [PATCH 1/8] Add NextColorDistanceSensor with ColorProfile HSV matching --- .../nextftc/hardware/sensors/ColorProfile.kt | 58 +++++++++ .../sensors/NextColorDistanceSensor.kt | 116 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt new file mode 100644 index 0000000..bd4e904 --- /dev/null +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 NextFTC Team + * + * Use of this source code is governed by an BSD-3-clause + * license that can be found in the LICENSE.md file at the root of this repository or at + * https://opensource.org/license/bsd-3-clause. + */ + +package dev.nextftc.hardware.sensors + +/** + * Defines a color by its HSV bounds. Used by [NextColorDistanceSensor.isColor] + * to check if a sensor reading matches. + * + * Hue is in degrees (0..360). Saturation and value are in (0..1) and default + * to the full range, so you can specify just hue if you want. + * + * Example: + * ``` + * val green = ColorProfile(hueMin = 150f, hueMax = 185f, saturationMin = 0.2f) + * val purple = ColorProfile(hueMin = 200f, hueMax = 250f, saturationMin = 0.2f) + * ``` + * + * + * @property hueMin Lower hue bound in degrees (0..360). + * @property hueMax Upper hue bound in degrees (0..360). + * @property saturationMin Lower saturation bound (0..1). Defaults to 0. + * @property saturationMax Upper saturation bound (0..1). Defaults to 1. + * @property valueMin Lower value/brightness bound (0..1). Defaults to 0. + * @property valueMax Upper value/brightness bound (0..1). Defaults to 1. + * + * @author 28shettr + */ + +data class ColorProfile @JvmOverloads constructor( + val hueMin: Float, + val hueMax: Float, + val saturationMin: Float = 0f, + val saturationMax: Float = 1f, + val valueMin: Float = 0f, + val valueMax: Float = 1f, +) { + /** True if [hsv] falls inside all three bounds. Hue wraps if [hueMin] > [hueMax]. */ + fun contains(hsv: FloatArray): Boolean { + val h = hsv[0] + val s = hsv[1] + val v = hsv[2] + val hueInRange = if (hueMin > hueMax) { + h >= hueMin || h <= hueMax // wraparound: e.g. 350..10 means 350..360 OR 0..10 + } else { + h in hueMin..hueMax // normal range + } + + return hueInRange + && s in saturationMin..saturationMax + && v in valueMin..valueMax + } +} diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt new file mode 100644 index 0000000..95b6dd4 --- /dev/null +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026 NextFTC Team + * + * Use of this source code is governed by an BSD-3-clause + * license that can be found in the LICENSE.md file at the root of this repository or at + * https://opensource.org/license/bsd-3-clause. + */ + +package dev.nextftc.hardware.sensors + +import android.graphics.Color +import com.qualcomm.robotcore.hardware.DistanceSensor +import com.qualcomm.robotcore.hardware.NormalizedColorSensor +import com.qualcomm.robotcore.hardware.NormalizedRGBA +import dev.nextftc.hardware.LazyHardware +import dev.nextftc.hardware.RobotController +import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit + + +/** + * Combines a color sensor and an optional distance sensor into one class. + * Call [update] each loop to read the hardware. Use [isColor] to check + * against a [ColorProfile]. + * + * Example: + * ``` + * val sensor = NextColorDistanceSensor("colorSensor", hasDistance = true) + * val green = ColorProfile(hueMin = 150f, hueMax = 185f, saturationMin = 0.2f) + * + * override fun periodic() { + * sensor.update() + * if (sensor.isWithinDistance(4.0) && sensor.isColor(green)) { ... } + * } + * ``` + * + * Use [debug] in telemetry to calibrate [ColorProfile]s. + * + * @param colorInitializer Lazily resolves the backing [NormalizedColorSensor]. + * @param distanceInitializer Optional lazy distance sensor. + * + * @author 28shettr + */ +class NextColorDistanceSensor( + colorInitializer: () -> NormalizedColorSensor, + distanceInitializer: (() -> DistanceSensor)? = null, +) { + @JvmOverloads + constructor(sensorName: String, hasDistance: Boolean = false) : this( + { RobotController.hardwareMap[sensorName] as NormalizedColorSensor }, + if (hasDistance) { + { RobotController.hardwareMap[sensorName] as DistanceSensor } + } else null, + ) + + private val colorSensor by LazyHardware(colorInitializer) + private val distanceSensor: DistanceSensor? by lazy { distanceInitializer?.invoke() } + + private var cachedColors: NormalizedRGBA? = null + private var cachedDistanceCm: Double = Double.NaN + private val cachedHsv: FloatArray = FloatArray(3) + + + /** Last cached distance in centimeters, or [Double.NaN] if no distance sensor is attached or [update] has not been called. */ + val distance: Double + get() = cachedDistanceCm + + /** Last cached normalized RGBA reading, or null if [update] has not been called. */ + val rgba: NormalizedRGBA? + get() = cachedColors + + /** Last cached hue in degrees (0..360). */ + val hue: Float get() = cachedHsv[0] + + /** Last cached saturation (0..1). */ + val saturation: Float get() = cachedHsv[1] + + /** Last cached value/brightness (0..1). */ + val value: Float get() = cachedHsv[2] + + /** Gain applied to the color sensor. Higher values amplify readings for better detection at distance or in low light. Typical range is 1..4. */ + var gain: Float + get() = colorSensor.gain + set(gain) { + colorSensor.gain = gain + } + + /** Reads the color sensor (and distance sensor, if present) and refreshes the cache. Call this once per loop, before reading any properties. */ + fun update() { + val colors = colorSensor.normalizedColors + cachedColors = colors + + val r = (colors.red * 255).toInt() + val g = (colors.green * 255).toInt() + val b = (colors.blue * 255).toInt() + + Color.RGBToHSV(r, g, b, cachedHsv) + + cachedDistanceCm = distanceSensor?.getDistance(DistanceUnit.CM) ?: Double.NaN + } + + /** True if a distance sensor is attached and an object is within [threshold] centimeters. */ + fun isWithinDistance(threshold: Double): Boolean { + return !distance.isNaN() && distance <= threshold + } + + /** True if the cached HSV reading falls inside [range]. */ + fun isColor(range: ColorProfile): Boolean { + return range.contains(cachedHsv) + } + + /** Single-line telemetry string showing current HSV and distance. Useful for calibrating [ColorProfile]s. */ + fun debug(): String { + val d = if (cachedDistanceCm.isNaN()) "n/a" else cachedDistanceCm + return "H=$hue S=$saturation V=$value dist=$d" + } +} \ No newline at end of file From bde282f35c6d42a2d7ee08fb65e4bcb56fab49e3 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Tue, 19 May 2026 20:49:52 -0500 Subject: [PATCH 2/8] fixed comment to be more clear --- .../main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt index bd4e904..4bd5e5c 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt @@ -46,7 +46,7 @@ data class ColorProfile @JvmOverloads constructor( val s = hsv[1] val v = hsv[2] val hueInRange = if (hueMin > hueMax) { - h >= hueMin || h <= hueMax // wraparound: e.g. 350..10 means 350..360 OR 0..10 + h >= hueMin || h <= hueMax // wraparound: e.g. 350 to 10 means 350 to 360 OR 0 to 10 } else { h in hueMin..hueMax // normal range } From 8347c9423c25e43cf98d2bd2f9fadbc3fe6fa968 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Tue, 19 May 2026 21:45:26 -0500 Subject: [PATCH 3/8] refactor: enhance NextColorDistanceSensor with unit-aware distance and improved debugging --- .../sensors/NextColorDistanceSensor.kt | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt index 95b6dd4..7fda103 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt @@ -60,10 +60,6 @@ class NextColorDistanceSensor( private val cachedHsv: FloatArray = FloatArray(3) - /** Last cached distance in centimeters, or [Double.NaN] if no distance sensor is attached or [update] has not been called. */ - val distance: Double - get() = cachedDistanceCm - /** Last cached normalized RGBA reading, or null if [update] has not been called. */ val rgba: NormalizedRGBA? get() = cachedColors @@ -98,8 +94,19 @@ class NextColorDistanceSensor( cachedDistanceCm = distanceSensor?.getDistance(DistanceUnit.CM) ?: Double.NaN } + /** Returns the last cached distance converted to the requested [unit]. */ + fun distance(unit: DistanceUnit = DistanceUnit.CM): Double { + return when (unit) { + DistanceUnit.CM -> cachedDistanceCm + DistanceUnit.MM -> cachedDistanceCm * 10 + DistanceUnit.INCH -> cachedDistanceCm / 2.54 + DistanceUnit.METER -> cachedDistanceCm / 100 + } + } + /** True if a distance sensor is attached and an object is within [threshold] centimeters. */ - fun isWithinDistance(threshold: Double): Boolean { + fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { + val distance = distance(unit) return !distance.isNaN() && distance <= threshold } @@ -110,7 +117,22 @@ class NextColorDistanceSensor( /** Single-line telemetry string showing current HSV and distance. Useful for calibrating [ColorProfile]s. */ fun debug(): String { - val d = if (cachedDistanceCm.isNaN()) "n/a" else cachedDistanceCm - return "H=$hue S=$saturation V=$value dist=$d" + val rgba = cachedColors + + val r = "%.2f".format(rgba?.red ?: 0f) + val g = "%.2f".format(rgba?.green ?: 0f) + val b = "%.2f".format(rgba?.blue ?: 0f) + + val h = "%.1f".format(hue) + val s = "%.2f".format(saturation) + val v = "%.2f".format(value) + + val d = if (cachedDistanceCm.isNaN()) { + "n/a" + } else { + "%.2f".format(cachedDistanceCm) + } + + return "RGB=($r,$g,$b) HSV=($h,$s,$v) Dist=$d" } } \ No newline at end of file From fe1ba1ba8658908c9fc2e8ac4b0e9a3f9ca63c52 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Wed, 20 May 2026 17:36:47 -0500 Subject: [PATCH 4/8] Fixed Color Profile so now it accepts either hsv or rgb --- .../sensors/NextColorDistanceSensor.kt | 65 +++++++-------- .../hardware/sensors/colors/ColorProfile.kt | 83 +++++++++++++++++++ .../hardware/sensors/colors/NextColor.kt | 77 +++++++++++++++++ 3 files changed, 192 insertions(+), 33 deletions(-) create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt index 7fda103..ae36407 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt @@ -11,21 +11,25 @@ package dev.nextftc.hardware.sensors import android.graphics.Color import com.qualcomm.robotcore.hardware.DistanceSensor import com.qualcomm.robotcore.hardware.NormalizedColorSensor -import com.qualcomm.robotcore.hardware.NormalizedRGBA import dev.nextftc.hardware.LazyHardware import dev.nextftc.hardware.RobotController +import dev.nextftc.hardware.sensors.colors.ColorProfile +import dev.nextftc.hardware.sensors.colors.NextColor import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit /** * Combines a color sensor and an optional distance sensor into one class. * Call [update] each loop to read the hardware. Use [isColor] to check - * against a [ColorProfile]. + * against a [dev.nextftc.hardware.sensors.colors.ColorProfile]. * * Example: * ``` - * val sensor = NextColorDistanceSensor("colorSensor", hasDistance = true) - * val green = ColorProfile(hueMin = 150f, hueMax = 185f, saturationMin = 0.2f) + * val green = ColorProfile( + * space = ColorSpace.HSV, + * color = NextColor.HSV(160f, 0.8f, 0.7f), + * tolerance = NextColor.HSV(15f, 0.3f, 1f), + * ) * * override fun periodic() { * sensor.update() @@ -33,7 +37,7 @@ import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit * } * ``` * - * Use [debug] in telemetry to calibrate [ColorProfile]s. + * Use [debug] in telemetry to calibrate [dev.nextftc.hardware.sensors.colors.ColorProfile]s. * * @param colorInitializer Lazily resolves the backing [NormalizedColorSensor]. * @param distanceInitializer Optional lazy distance sensor. @@ -55,14 +59,15 @@ class NextColorDistanceSensor( private val colorSensor by LazyHardware(colorInitializer) private val distanceSensor: DistanceSensor? by lazy { distanceInitializer?.invoke() } - private var cachedColors: NormalizedRGBA? = null private var cachedDistanceCm: Double = Double.NaN - private val cachedHsv: FloatArray = FloatArray(3) + private var cachedColor: NextColor = NextColor.RGB(0f, 0f, 0f) + private var cachedHsv: FloatArray = FloatArray(3) - /** Last cached normalized RGBA reading, or null if [update] has not been called. */ - val rgba: NormalizedRGBA? - get() = cachedColors + + /** Last cached reading as a [NextColor]. Black until [update] is called. */ + val color: NextColor + get() = cachedColor /** Last cached hue in degrees (0..360). */ val hue: Float get() = cachedHsv[0] @@ -82,27 +87,23 @@ class NextColorDistanceSensor( /** Reads the color sensor (and distance sensor, if present) and refreshes the cache. Call this once per loop, before reading any properties. */ fun update() { - val colors = colorSensor.normalizedColors - cachedColors = colors - - val r = (colors.red * 255).toInt() - val g = (colors.green * 255).toInt() - val b = (colors.blue * 255).toInt() + val c = colorSensor.normalizedColors + cachedColor = NextColor.RGB(c.red * 255, c.green * 255, c.blue * 255) - Color.RGBToHSV(r, g, b, cachedHsv) + Color.RGBToHSV( + cachedColor.red.toInt(), + cachedColor.green.toInt(), + cachedColor.blue.toInt(), + cachedHsv + ) cachedDistanceCm = distanceSensor?.getDistance(DistanceUnit.CM) ?: Double.NaN } + /** Returns the last cached distance converted to the requested [unit]. */ - fun distance(unit: DistanceUnit = DistanceUnit.CM): Double { - return when (unit) { - DistanceUnit.CM -> cachedDistanceCm - DistanceUnit.MM -> cachedDistanceCm * 10 - DistanceUnit.INCH -> cachedDistanceCm / 2.54 - DistanceUnit.METER -> cachedDistanceCm / 100 - } - } + fun distance(unit: DistanceUnit = DistanceUnit.CM): Double = + unit.fromUnit(DistanceUnit.CM, cachedDistanceCm) /** True if a distance sensor is attached and an object is within [threshold] centimeters. */ fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { @@ -110,18 +111,16 @@ class NextColorDistanceSensor( return !distance.isNaN() && distance <= threshold } - /** True if the cached HSV reading falls inside [range]. */ - fun isColor(range: ColorProfile): Boolean { - return range.contains(cachedHsv) - } + /** True if the cached HSV reading falls inside [profile]. */ + fun isColor(profile: ColorProfile): Boolean = profile.matches(cachedColor) + /** Single-line telemetry string showing current HSV and distance. Useful for calibrating [ColorProfile]s. */ fun debug(): String { - val rgba = cachedColors - val r = "%.2f".format(rgba?.red ?: 0f) - val g = "%.2f".format(rgba?.green ?: 0f) - val b = "%.2f".format(rgba?.blue ?: 0f) + val r = "%.0f".format(cachedColor.red) + val g = "%.0f".format(cachedColor.green) + val b = "%.0f".format(cachedColor.blue) val h = "%.1f".format(hue) val s = "%.2f".format(saturation) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt new file mode 100644 index 0000000..d616c02 --- /dev/null +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026 NextFTC Team + * + * Use of this source code is governed by an BSD-3-clause + * license that can be found in the LICENSE.md file at the root of this repository or at + * https://opensource.org/license/bsd-3-clause. + */ + +package dev.nextftc.hardware.sensors.colors + +import kotlin.math.abs +import dev.nextftc.hardware.sensors.NextColorDistanceSensor + +enum class ColorSpace { RGB, HSV } + +/** + * Describes a target color and the per-channel tolerances used to decide whether a + * sensor reading is a match. + * + * Use [NextColorDistanceSensor.debug] in telemetry to read live HSV values and + * calibrate [color] and [tolerance]. [ColorSpace.HSV] is recommended for most cases + * as it is more stable under changing lighting conditions. + * + * Example: + * ``` + * val green = ColorProfile( + * space = ColorSpace.HSV, + * color = NextColor.HSV(130f, 0.7f, 0.6f), + * tolerance = NextColor.HSV(20f, 0.3f, 1f), + * ) + * + * override fun periodic() { + * sensor.update() + * if (sensor.isColor(green)) { ... } + * } + * ``` + * + * @property space The color space to compare in. + * @property color The target color to match against. + * @property tolerance How far each channel can deviate from [color] and still count as a match. + * + * @author 28shettr + */ +data class ColorProfile ( + val space: ColorSpace, + val color: NextColor, + val tolerance: NextColor +) { + + private val colorHsv = color.hsv + private val toleranceHsv = tolerance.hsv + private val colorRgb = color.rgb + private val toleranceRgb = tolerance.rgb + + /** Returns `true` if [reading] falls within [tolerance] of [color] in [space]. */ + fun matches(reading: NextColor): Boolean = when (space) { + ColorSpace.RGB -> matchesRgb(reading) + ColorSpace.HSV -> matchesHsv(reading) + } + private fun matchesRgb(input: NextColor): Boolean { + val c = colorRgb + val t = toleranceRgb + val o = input.rgb + + return abs(o[0] - c[0]) <= t[0] && + abs(o[1] - c[1]) <= t[1] && + abs(o[2] - c[2]) <= t[2] + } + + private fun matchesHsv(input: NextColor): Boolean { + val c = colorHsv + val t = toleranceHsv + val o = input.hsv + return wraparoundCheck(o[0], c[0]) <= t[0] && + abs(o[1] - c[1]) <= t[1] && + abs(o[2] - c[2]) <= t[2] + } + + private fun wraparoundCheck(a: Float, b: Float): Float { + val diff = abs(a - b) % 360f + return if (diff > 180f) 360f - diff else diff + } +} diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt new file mode 100644 index 0000000..0075fcc --- /dev/null +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026 NextFTC Team + * + * Use of this source code is governed by an BSD-3-clause + * license that can be found in the LICENSE.md file at the root of this repository or at + * https://opensource.org/license/bsd-3-clause. + */ + +package dev.nextftc.hardware.sensors.colors + +import android.graphics.Color +import dev.nextftc.hardware.sensors.NextColorDistanceSensor + +/** + * Stores a color. Use [RGB] if you have red, green, and blue values, + * or [HSV] if you have hue, saturation, and brightness values. + * + * Example: + * ``` + * val lime = NextColor.HSV(100f, 0.9f, 0.85f) + * val sameColor = NextColor.RGB(lime.red, lime.green, lime.blue) + * ``` + * + * + * @author 28shettr + */ + + +data class NextColor( + val red: Float, + val green: Float, + val blue: Float, +) { + /** The color as a `[red, green, blue]` float array (0–255). */ + val rgb: FloatArray + get() = floatArrayOf(red, green, blue) + + /** The color as a `[hue, saturation, value]` float array (hue: 0–360, saturation/value: 0–1). */ + val hsv: FloatArray + get(){ + val out = FloatArray(3) + Color.RGBToHSV(red.toInt(), green.toInt(), blue.toInt(), out) + return out + } + + companion object{ + + /** + * Creates a color from red, green, and blue values. + * You can use the [NextColorDistanceSensor.debug] to find these values. + * + * @param red How much red (0–255). + * @param green How much green (0–255). + * @param blue How much blue (0–255). + */ + fun RGB(red: Float, green: Float, blue: Float): NextColor = + NextColor(red, green, blue) + + + /** + * Creates a color from hue, saturation, and brightness values. + * You can use the [NextColorDistanceSensor.debug] to find these values. + * + * @param hue The color's position on the color wheel, in degrees (0–360). + * @param saturation How vivid the color is (0 = grey, 1 = fully vivid). + * @param value How bright the color is (0 = black, 1 = full brightness). + */ + fun HSV(hue: Float, saturation: Float, value: Float): NextColor { + val rgbInt = Color.HSVToColor(floatArrayOf(hue, saturation, value)) + return NextColor( + Color.red(rgbInt).toFloat(), + Color.green(rgbInt).toFloat(), + Color.blue(rgbInt).toFloat(), + ) + } + } +} \ No newline at end of file From ce478c064f7018bc1992162d6a63f65b0e22dfb8 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Wed, 20 May 2026 17:37:11 -0500 Subject: [PATCH 5/8] Distance Sensor added --- .../hardware/sensors/NextDistanceSensor.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt new file mode 100644 index 0000000..1763660 --- /dev/null +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026 NextFTC Team + * + * Use of this source code is governed by an BSD-3-clause + * license that can be found in the LICENSE.md file at the root of this repository or at + * https://opensource.org/license/bsd-3-clause. + */ + +package dev.nextftc.hardware.sensors + +import com.qualcomm.robotcore.hardware.DistanceSensor +import dev.nextftc.hardware.LazyHardware +import dev.nextftc.hardware.RobotController +import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit +/** + * Lightweight wrapper for a distance sensor that caches the last reading. + * Call [update] in periodic to read the hardware. + * + * Use [isWithinDistance] to check + * if an object is close enough. + * + * Example: + * ``` + * override fun periodic() { + * sensor.update() + * if (sensor.isWithinDistance(6.7)) { ... } + * } + * ``` + * + * @param initializer Lazily resolves the backing [DistanceSensor]. + * + * @author 28shettr + */ +class NextDistanceSensor( + initializer: () -> DistanceSensor +) { + constructor(name: String) : this( + { RobotController.hardwareMap[name] as DistanceSensor } + ) + + private val distanceSensor by LazyHardware(initializer) + + private var cachedDistanceCm: Double = Double.NaN + + /** Reads the distance sensor and refreshes the cache. Call this once per loop, before reading any properties. */ + fun update() { + cachedDistanceCm = distanceSensor.getDistance(DistanceUnit.CM) + } + + /** Returns the last cached distance converted to the requested [unit]. Defaults to centimeters. */ + fun distance(unit: DistanceUnit = DistanceUnit.CM): Double = + unit.fromUnit(DistanceUnit.CM, cachedDistanceCm) + + /** True if an object is within [threshold] of the sensor. Defaults to centimeters. */ + fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { + val distance = distance(unit) + return !distance.isNaN() && distance <= threshold + } + +} \ No newline at end of file From 8f9a9e7e79897f2c593cb414ed43dc1040157464 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Wed, 20 May 2026 18:54:48 -0500 Subject: [PATCH 6/8] Variable name messed up --- .../hardware/sensors/colors/ColorProfile.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt index d616c02..d55e468 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt @@ -60,20 +60,20 @@ data class ColorProfile ( private fun matchesRgb(input: NextColor): Boolean { val c = colorRgb val t = toleranceRgb - val o = input.rgb + val i = input.rgb - return abs(o[0] - c[0]) <= t[0] && - abs(o[1] - c[1]) <= t[1] && - abs(o[2] - c[2]) <= t[2] + return abs(i[0] - c[0]) <= t[0] && + abs(i[1] - c[1]) <= t[1] && + abs(i[2] - c[2]) <= t[2] } private fun matchesHsv(input: NextColor): Boolean { val c = colorHsv val t = toleranceHsv - val o = input.hsv - return wraparoundCheck(o[0], c[0]) <= t[0] && - abs(o[1] - c[1]) <= t[1] && - abs(o[2] - c[2]) <= t[2] + val i = input.hsv + return wraparoundCheck(i[0], c[0]) <= t[0] && + abs(i[1] - c[1]) <= t[1] && + abs(i[2] - c[2]) <= t[2] } private fun wraparoundCheck(a: Float, b: Float): Float { From 1261ec80a59cfcc70c09d98e72ddafdf7c4b97c1 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Wed, 20 May 2026 20:58:50 -0500 Subject: [PATCH 7/8] compile time enforced range checks --- .../nextftc/hardware/sensors/ColorProfile.kt | 58 ------------------- .../hardware/sensors/colors/NextColor.kt | 14 ++++- 2 files changed, 12 insertions(+), 60 deletions(-) delete mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt deleted file mode 100644 index 4bd5e5c..0000000 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/ColorProfile.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2026 NextFTC Team - * - * Use of this source code is governed by an BSD-3-clause - * license that can be found in the LICENSE.md file at the root of this repository or at - * https://opensource.org/license/bsd-3-clause. - */ - -package dev.nextftc.hardware.sensors - -/** - * Defines a color by its HSV bounds. Used by [NextColorDistanceSensor.isColor] - * to check if a sensor reading matches. - * - * Hue is in degrees (0..360). Saturation and value are in (0..1) and default - * to the full range, so you can specify just hue if you want. - * - * Example: - * ``` - * val green = ColorProfile(hueMin = 150f, hueMax = 185f, saturationMin = 0.2f) - * val purple = ColorProfile(hueMin = 200f, hueMax = 250f, saturationMin = 0.2f) - * ``` - * - * - * @property hueMin Lower hue bound in degrees (0..360). - * @property hueMax Upper hue bound in degrees (0..360). - * @property saturationMin Lower saturation bound (0..1). Defaults to 0. - * @property saturationMax Upper saturation bound (0..1). Defaults to 1. - * @property valueMin Lower value/brightness bound (0..1). Defaults to 0. - * @property valueMax Upper value/brightness bound (0..1). Defaults to 1. - * - * @author 28shettr - */ - -data class ColorProfile @JvmOverloads constructor( - val hueMin: Float, - val hueMax: Float, - val saturationMin: Float = 0f, - val saturationMax: Float = 1f, - val valueMin: Float = 0f, - val valueMax: Float = 1f, -) { - /** True if [hsv] falls inside all three bounds. Hue wraps if [hueMin] > [hueMax]. */ - fun contains(hsv: FloatArray): Boolean { - val h = hsv[0] - val s = hsv[1] - val v = hsv[2] - val hueInRange = if (hueMin > hueMax) { - h >= hueMin || h <= hueMax // wraparound: e.g. 350 to 10 means 350 to 360 OR 0 to 10 - } else { - h in hueMin..hueMax // normal range - } - - return hueInRange - && s in saturationMin..saturationMax - && v in valueMin..valueMax - } -} diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt index 0075fcc..19ddd2e 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt @@ -15,6 +15,8 @@ import dev.nextftc.hardware.sensors.NextColorDistanceSensor * Stores a color. Use [RGB] if you have red, green, and blue values, * or [HSV] if you have hue, saturation, and brightness values. * + * You can use the [NextColorDistanceSensor.debug] to find these values. + * Reccomened to use [HSV] for most cases. * Example: * ``` * val lime = NextColor.HSV(100f, 0.9f, 0.85f) @@ -53,9 +55,13 @@ data class NextColor( * @param green How much green (0–255). * @param blue How much blue (0–255). */ - fun RGB(red: Float, green: Float, blue: Float): NextColor = - NextColor(red, green, blue) + fun RGB(red: Float, green: Float, blue: Float): NextColor { + require(red in 0f..255f) {"value must be 0-255 got $red"} + require(green in 0f..255f) {"value must be 0-255 got $green"} + require(blue in 0f..255f) {"value must be 0-255 got $blue"} + return NextColor(red, green, blue) + } /** * Creates a color from hue, saturation, and brightness values. @@ -66,6 +72,10 @@ data class NextColor( * @param value How bright the color is (0 = black, 1 = full brightness). */ fun HSV(hue: Float, saturation: Float, value: Float): NextColor { + require(hue in 0f..360f){"value must be 0-360 got $hue"} + require(saturation in 0f..1f){"value must be 0-1 got $saturation"} + require(value in 0f..1f){"value must be 0-1 got $value"} + val rgbInt = Color.HSVToColor(floatArrayOf(hue, saturation, value)) return NextColor( Color.red(rgbInt).toFloat(), From 0a13860b9a66dee8b3b30fdd5b3bf32b21898915 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Thu, 21 May 2026 18:20:51 -0500 Subject: [PATCH 8/8] spotless apply done --- .../sensors/NextColorDistanceSensor.kt | 155 +++++++++--------- .../hardware/sensors/NextDistanceSensor.kt | 51 +++--- .../hardware/sensors/colors/ColorProfile.kt | 66 ++++---- .../hardware/sensors/colors/NextColor.kt | 103 ++++++------ 4 files changed, 180 insertions(+), 195 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt index ae36407..876b2b4 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextColorDistanceSensor.kt @@ -17,7 +17,6 @@ import dev.nextftc.hardware.sensors.colors.ColorProfile import dev.nextftc.hardware.sensors.colors.NextColor import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit - /** * Combines a color sensor and an optional distance sensor into one class. * Call [update] each loop to read the hardware. Use [isColor] to check @@ -45,93 +44,91 @@ import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit * @author 28shettr */ class NextColorDistanceSensor( - colorInitializer: () -> NormalizedColorSensor, - distanceInitializer: (() -> DistanceSensor)? = null, + colorInitializer: () -> NormalizedColorSensor, + distanceInitializer: (() -> DistanceSensor)? = null, ) { - @JvmOverloads - constructor(sensorName: String, hasDistance: Boolean = false) : this( - { RobotController.hardwareMap[sensorName] as NormalizedColorSensor }, - if (hasDistance) { - { RobotController.hardwareMap[sensorName] as DistanceSensor } - } else null, - ) - - private val colorSensor by LazyHardware(colorInitializer) - private val distanceSensor: DistanceSensor? by lazy { distanceInitializer?.invoke() } - - private var cachedDistanceCm: Double = Double.NaN - - private var cachedColor: NextColor = NextColor.RGB(0f, 0f, 0f) - private var cachedHsv: FloatArray = FloatArray(3) - - - /** Last cached reading as a [NextColor]. Black until [update] is called. */ - val color: NextColor - get() = cachedColor - - /** Last cached hue in degrees (0..360). */ - val hue: Float get() = cachedHsv[0] - - /** Last cached saturation (0..1). */ - val saturation: Float get() = cachedHsv[1] - - /** Last cached value/brightness (0..1). */ - val value: Float get() = cachedHsv[2] - - /** Gain applied to the color sensor. Higher values amplify readings for better detection at distance or in low light. Typical range is 1..4. */ - var gain: Float - get() = colorSensor.gain - set(gain) { - colorSensor.gain = gain - } - - /** Reads the color sensor (and distance sensor, if present) and refreshes the cache. Call this once per loop, before reading any properties. */ - fun update() { - val c = colorSensor.normalizedColors - cachedColor = NextColor.RGB(c.red * 255, c.green * 255, c.blue * 255) - - Color.RGBToHSV( - cachedColor.red.toInt(), - cachedColor.green.toInt(), - cachedColor.blue.toInt(), - cachedHsv - ) - - cachedDistanceCm = distanceSensor?.getDistance(DistanceUnit.CM) ?: Double.NaN + @JvmOverloads + constructor(sensorName: String, hasDistance: Boolean = false) : this( + { RobotController.hardwareMap[sensorName] as NormalizedColorSensor }, + if (hasDistance) { + { RobotController.hardwareMap[sensorName] as DistanceSensor } + } else { + null + }, + ) + + private val colorSensor by LazyHardware(colorInitializer) + private val distanceSensor: DistanceSensor? by lazy { distanceInitializer?.invoke() } + + private var cachedDistanceCm: Double = Double.NaN + + private var cachedColor: NextColor = NextColor.rgb(0f, 0f, 0f) + private var cachedHsv: FloatArray = FloatArray(3) + + /** Last cached reading as a [NextColor]. Black until [update] is called. */ + val color: NextColor + get() = cachedColor + + /** Last cached hue in degrees (0..360). */ + val hue: Float get() = cachedHsv[0] + + /** Last cached saturation (0..1). */ + val saturation: Float get() = cachedHsv[1] + + /** Last cached value/brightness (0..1). */ + val value: Float get() = cachedHsv[2] + + /** Gain applied to the color sensor. Higher values amplify readings for better detection at distance or in low light. Typical range is 1..4. */ + var gain: Float + get() = colorSensor.gain + set(gain) { + colorSensor.gain = gain } + /** Reads the color sensor (and distance sensor, if present) and refreshes the cache. Call this once per loop, before reading any properties. */ + fun update() { + val c = colorSensor.normalizedColors + cachedColor = NextColor.rgb(c.red * 255, c.green * 255, c.blue * 255) - /** Returns the last cached distance converted to the requested [unit]. */ - fun distance(unit: DistanceUnit = DistanceUnit.CM): Double = - unit.fromUnit(DistanceUnit.CM, cachedDistanceCm) - - /** True if a distance sensor is attached and an object is within [threshold] centimeters. */ - fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { - val distance = distance(unit) - return !distance.isNaN() && distance <= threshold - } + Color.RGBToHSV( + cachedColor.red.toInt(), + cachedColor.green.toInt(), + cachedColor.blue.toInt(), + cachedHsv, + ) - /** True if the cached HSV reading falls inside [profile]. */ - fun isColor(profile: ColorProfile): Boolean = profile.matches(cachedColor) + cachedDistanceCm = distanceSensor?.getDistance(DistanceUnit.CM) ?: Double.NaN + } + /** Returns the last cached distance converted to the requested [unit]. */ + fun distance(unit: DistanceUnit = DistanceUnit.CM): Double = + unit.fromUnit(DistanceUnit.CM, cachedDistanceCm) - /** Single-line telemetry string showing current HSV and distance. Useful for calibrating [ColorProfile]s. */ - fun debug(): String { + /** True if a distance sensor is attached and an object is within [threshold] centimeters. */ + fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { + val distance = distance(unit) + return !distance.isNaN() && distance <= threshold + } - val r = "%.0f".format(cachedColor.red) - val g = "%.0f".format(cachedColor.green) - val b = "%.0f".format(cachedColor.blue) + /** True if the cached HSV reading falls inside [profile]. */ + fun isColor(profile: ColorProfile): Boolean = profile.matches(cachedColor) - val h = "%.1f".format(hue) - val s = "%.2f".format(saturation) - val v = "%.2f".format(value) + /** Single-line telemetry string showing current HSV and distance. Useful for calibrating [ColorProfile]s. */ + fun debug(): String { + val r = "%.0f".format(cachedColor.red) + val g = "%.0f".format(cachedColor.green) + val b = "%.0f".format(cachedColor.blue) - val d = if (cachedDistanceCm.isNaN()) { - "n/a" - } else { - "%.2f".format(cachedDistanceCm) - } + val h = "%.1f".format(hue) + val s = "%.2f".format(saturation) + val v = "%.2f".format(value) - return "RGB=($r,$g,$b) HSV=($h,$s,$v) Dist=$d" + val d = if (cachedDistanceCm.isNaN()) { + "n/a" + } else { + "%.2f".format(cachedDistanceCm) } -} \ No newline at end of file + + return "RGB=($r,$g,$b) HSV=($h,$s,$v) Dist=$d" + } +} diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt index 1763660..3cd3fb4 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDistanceSensor.kt @@ -31,30 +31,27 @@ import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit * * @author 28shettr */ -class NextDistanceSensor( - initializer: () -> DistanceSensor -) { - constructor(name: String) : this( - { RobotController.hardwareMap[name] as DistanceSensor } - ) - - private val distanceSensor by LazyHardware(initializer) - - private var cachedDistanceCm: Double = Double.NaN - - /** Reads the distance sensor and refreshes the cache. Call this once per loop, before reading any properties. */ - fun update() { - cachedDistanceCm = distanceSensor.getDistance(DistanceUnit.CM) - } - - /** Returns the last cached distance converted to the requested [unit]. Defaults to centimeters. */ - fun distance(unit: DistanceUnit = DistanceUnit.CM): Double = - unit.fromUnit(DistanceUnit.CM, cachedDistanceCm) - - /** True if an object is within [threshold] of the sensor. Defaults to centimeters. */ - fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { - val distance = distance(unit) - return !distance.isNaN() && distance <= threshold - } - -} \ No newline at end of file +class NextDistanceSensor(initializer: () -> DistanceSensor) { + constructor(name: String) : this( + { RobotController.hardwareMap[name] as DistanceSensor }, + ) + + private val distanceSensor by LazyHardware(initializer) + + private var cachedDistanceCm: Double = Double.NaN + + /** Reads the distance sensor and refreshes the cache. Call this once per loop, before reading any properties. */ + fun update() { + cachedDistanceCm = distanceSensor.getDistance(DistanceUnit.CM) + } + + /** Returns the last cached distance converted to the requested [unit]. Defaults to centimeters. */ + fun distance(unit: DistanceUnit = DistanceUnit.CM): Double = + unit.fromUnit(DistanceUnit.CM, cachedDistanceCm) + + /** True if an object is within [threshold] of the sensor. Defaults to centimeters. */ + fun isWithinDistance(threshold: Double, unit: DistanceUnit = DistanceUnit.CM): Boolean { + val distance = distance(unit) + return !distance.isNaN() && distance <= threshold + } +} diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt index d55e468..c0d434f 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/ColorProfile.kt @@ -8,8 +8,8 @@ package dev.nextftc.hardware.sensors.colors -import kotlin.math.abs import dev.nextftc.hardware.sensors.NextColorDistanceSensor +import kotlin.math.abs enum class ColorSpace { RGB, HSV } @@ -41,43 +41,39 @@ enum class ColorSpace { RGB, HSV } * * @author 28shettr */ -data class ColorProfile ( - val space: ColorSpace, - val color: NextColor, - val tolerance: NextColor -) { +data class ColorProfile(val space: ColorSpace, val color: NextColor, val tolerance: NextColor) { - private val colorHsv = color.hsv - private val toleranceHsv = tolerance.hsv - private val colorRgb = color.rgb - private val toleranceRgb = tolerance.rgb + private val colorHsv = color.hsv + private val toleranceHsv = tolerance.hsv + private val colorRgb = color.rgb + private val toleranceRgb = tolerance.rgb - /** Returns `true` if [reading] falls within [tolerance] of [color] in [space]. */ - fun matches(reading: NextColor): Boolean = when (space) { - ColorSpace.RGB -> matchesRgb(reading) - ColorSpace.HSV -> matchesHsv(reading) - } - private fun matchesRgb(input: NextColor): Boolean { - val c = colorRgb - val t = toleranceRgb - val i = input.rgb + /** Returns `true` if [reading] falls within [tolerance] of [color] in [space]. */ + fun matches(reading: NextColor): Boolean = when (space) { + ColorSpace.RGB -> matchesRgb(reading) + ColorSpace.HSV -> matchesHsv(reading) + } + private fun matchesRgb(input: NextColor): Boolean { + val c = colorRgb + val t = toleranceRgb + val i = input.rgb - return abs(i[0] - c[0]) <= t[0] && - abs(i[1] - c[1]) <= t[1] && - abs(i[2] - c[2]) <= t[2] - } + return abs(i[0] - c[0]) <= t[0] && + abs(i[1] - c[1]) <= t[1] && + abs(i[2] - c[2]) <= t[2] + } - private fun matchesHsv(input: NextColor): Boolean { - val c = colorHsv - val t = toleranceHsv - val i = input.hsv - return wraparoundCheck(i[0], c[0]) <= t[0] && - abs(i[1] - c[1]) <= t[1] && - abs(i[2] - c[2]) <= t[2] - } + private fun matchesHsv(input: NextColor): Boolean { + val c = colorHsv + val t = toleranceHsv + val i = input.hsv + return wraparoundCheck(i[0], c[0]) <= t[0] && + abs(i[1] - c[1]) <= t[1] && + abs(i[2] - c[2]) <= t[2] + } - private fun wraparoundCheck(a: Float, b: Float): Float { - val diff = abs(a - b) % 360f - return if (diff > 180f) 360f - diff else diff - } + private fun wraparoundCheck(a: Float, b: Float): Float { + val diff = abs(a - b) % 360f + return if (diff > 180f) 360f - diff else diff + } } diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt index 19ddd2e..aff04de 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/colors/NextColor.kt @@ -12,11 +12,11 @@ import android.graphics.Color import dev.nextftc.hardware.sensors.NextColorDistanceSensor /** - * Stores a color. Use [RGB] if you have red, green, and blue values, - * or [HSV] if you have hue, saturation, and brightness values. + * Stores a color. Use [rgb] if you have red, green, and blue values, + * or [hsv] if you have hue, saturation, and brightness values. * * You can use the [NextColorDistanceSensor.debug] to find these values. - * Reccomened to use [HSV] for most cases. + * Reccomened to use [hsv] for most cases. * Example: * ``` * val lime = NextColor.HSV(100f, 0.9f, 0.85f) @@ -27,61 +27,56 @@ import dev.nextftc.hardware.sensors.NextColorDistanceSensor * @author 28shettr */ +data class NextColor(val red: Float, val green: Float, val blue: Float) { + /** The color as a `[red, green, blue]` float array (0–255). */ + val rgb: FloatArray + get() = floatArrayOf(red, green, blue) -data class NextColor( - val red: Float, - val green: Float, - val blue: Float, -) { - /** The color as a `[red, green, blue]` float array (0–255). */ - val rgb: FloatArray - get() = floatArrayOf(red, green, blue) - - /** The color as a `[hue, saturation, value]` float array (hue: 0–360, saturation/value: 0–1). */ - val hsv: FloatArray - get(){ - val out = FloatArray(3) - Color.RGBToHSV(red.toInt(), green.toInt(), blue.toInt(), out) - return out - } + /** The color as a `[hue, saturation, value]` float array (hue: 0–360, saturation/value: 0–1). */ + val hsv: FloatArray + get() { + val out = FloatArray(3) + Color.RGBToHSV(red.toInt(), green.toInt(), blue.toInt(), out) + return out + } - companion object{ + companion object { - /** - * Creates a color from red, green, and blue values. - * You can use the [NextColorDistanceSensor.debug] to find these values. - * - * @param red How much red (0–255). - * @param green How much green (0–255). - * @param blue How much blue (0–255). - */ - fun RGB(red: Float, green: Float, blue: Float): NextColor { - require(red in 0f..255f) {"value must be 0-255 got $red"} - require(green in 0f..255f) {"value must be 0-255 got $green"} - require(blue in 0f..255f) {"value must be 0-255 got $blue"} + /** + * Creates a color from red, green, and blue values. + * You can use the [NextColorDistanceSensor.debug] to find these values. + * + * @param red How much red (0–255). + * @param green How much green (0–255). + * @param blue How much blue (0–255). + */ + fun rgb(red: Float, green: Float, blue: Float): NextColor { + require(red in 0f..255f) { "value must be 0-255 got $red" } + require(green in 0f..255f) { "value must be 0-255 got $green" } + require(blue in 0f..255f) { "value must be 0-255 got $blue" } - return NextColor(red, green, blue) - } + return NextColor(red, green, blue) + } - /** - * Creates a color from hue, saturation, and brightness values. - * You can use the [NextColorDistanceSensor.debug] to find these values. - * - * @param hue The color's position on the color wheel, in degrees (0–360). - * @param saturation How vivid the color is (0 = grey, 1 = fully vivid). - * @param value How bright the color is (0 = black, 1 = full brightness). - */ - fun HSV(hue: Float, saturation: Float, value: Float): NextColor { - require(hue in 0f..360f){"value must be 0-360 got $hue"} - require(saturation in 0f..1f){"value must be 0-1 got $saturation"} - require(value in 0f..1f){"value must be 0-1 got $value"} + /** + * Creates a color from hue, saturation, and brightness values. + * You can use the [NextColorDistanceSensor.debug] to find these values. + * + * @param hue The color's position on the color wheel, in degrees (0–360). + * @param saturation How vivid the color is (0 = grey, 1 = fully vivid). + * @param value How bright the color is (0 = black, 1 = full brightness). + */ + fun hsv(hue: Float, saturation: Float, value: Float): NextColor { + require(hue in 0f..360f) { "value must be 0-360 got $hue" } + require(saturation in 0f..1f) { "value must be 0-1 got $saturation" } + require(value in 0f..1f) { "value must be 0-1 got $value" } - val rgbInt = Color.HSVToColor(floatArrayOf(hue, saturation, value)) - return NextColor( - Color.red(rgbInt).toFloat(), - Color.green(rgbInt).toFloat(), - Color.blue(rgbInt).toFloat(), - ) - } + val rgbInt = Color.HSVToColor(floatArrayOf(hue, saturation, value)) + return NextColor( + Color.red(rgbInt).toFloat(), + Color.green(rgbInt).toFloat(), + Color.blue(rgbInt).toFloat(), + ) } -} \ No newline at end of file + } +}