diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+Animation.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+Animation.swift index ba888c07f1..73f8f0e83e 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+Animation.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+Animation.swift @@ -48,7 +48,7 @@ extension PairingView { case .reinforcer: let reinforcers: [Robot.Reinforcer] = [.fire, .rainbow, .sprinkles] return [ - (0.0, { Robot.shared.run(reinforcers.randomElement()!) }), + (5.0, { Robot.shared.run(reinforcers.randomElement()!) }), ] case .light: let color: [Robot.Color] = [.blue, .green, .orange, .pink, .purple, .red, .yellow] diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+RobotManager.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+RobotManager.swift index f9f8ac26e2..e3c9832df0 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+RobotManager.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Pairing/PairingView+RobotManager.swift @@ -76,8 +76,7 @@ extension PairingView { self.updateLightIntensity() DispatchQueue.main.asyncAfter(deadline: .now() + self.lightIntensityChangeDuration) { - let shadeOfColor: Robot.Color = .init(fromGradient: (.black, .lightBlue), at: self.lightIntensity) - self.robot.shine(.all(in: shadeOfColor)) + self.robot.shine(.all(in: Robot.Gradient(fromColors: .black, .white).color(at: self.lightIntensity))) self.breathe() } } diff --git a/Modules/RobotKit/Sources/Extensions/UIColor+toHSV.swift b/Modules/RobotKit/Sources/Extensions/UIColor+toHSV.swift new file mode 100644 index 0000000000..9cae6172d3 --- /dev/null +++ b/Modules/RobotKit/Sources/Extensions/UIColor+toHSV.swift @@ -0,0 +1,22 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import SwiftUI + +// swiftlint:disable identifier_name large_tuple + +extension UIColor { + func toHSV() -> (hue: CGFloat, saturation: CGFloat, value: CGFloat) { + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + + self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + return (hue, saturation, brightness) + } +} + +// swiftlint:enable identifier_name large_tuple diff --git a/Modules/RobotKit/Sources/Robot+Colors.swift b/Modules/RobotKit/Sources/Robot+Colors.swift index 407f16817b..45ac7b951f 100644 --- a/Modules/RobotKit/Sources/Robot+Colors.swift +++ b/Modules/RobotKit/Sources/Robot+Colors.swift @@ -6,7 +6,7 @@ import SwiftUI // MARK: - Robot.Color -// swiftlint:disable nesting identifier_name line_length +// swiftlint:disable nesting public extension Robot { struct Color { @@ -26,22 +26,9 @@ public extension Robot { self = color } - public init(r: UInt8, g: UInt8, b: UInt8) { - self.robotRGB = [r, g, b] - self.screenRGB = [r, g, b] - } - - public init(fromGradient colors: (Color, Color), at position: Float) { - let positionClamped = max(min(position, 1), 0) - let (r1, g1, b1) = (Float(colors.0.robotRGB[0]), Float(colors.0.robotRGB[1]), Float(colors.0.robotRGB[2])) - let (r2, g2, b2) = (Float(colors.1.robotRGB[0]), Float(colors.1.robotRGB[1]), Float(colors.1.robotRGB[2])) - - let r = UInt8(r1 + (r2 - r1) * positionClamped) - let g = UInt8(g1 + (g2 - g1) * positionClamped) - let b = UInt8(b1 + (b2 - b1) * positionClamped) - - self.robotRGB = [r, g, b] - self.screenRGB = [r, g, b] + public init(red: UInt8, green: UInt8, blue: UInt8) { + self.robotRGB = [red, green, blue] + self.screenRGB = [red, green, blue] } // MARK: Public @@ -58,6 +45,24 @@ public extension Robot { ) } + public var robotUiColor: UIColor { + UIColor( + red: Double(self.robotRGB[0]) / 255.0, + green: Double(self.robotRGB[1]) / 255.0, + blue: Double(self.robotRGB[2]) / 255.0, + alpha: 1.0 + ) + } + + public var screenUiColor: UIColor { + UIColor( + red: Double(self.screenRGB[0]) / 255.0, + green: Double(self.screenRGB[1]) / 255.0, + blue: Double(self.screenRGB[2]) / 255.0, + alpha: 1.0 + ) + } + // MARK: Private private enum ColorString: String { @@ -120,4 +125,4 @@ public extension Robot.Color { static let yellow: Robot.Color = .init(robot: 255, 255, 0, screen: 251, 232, 0) } -// swiftlint:enable nesting identifier_name line_length +// swiftlint:enable nesting diff --git a/Modules/RobotKit/Sources/Robot+Gradient.swift b/Modules/RobotKit/Sources/Robot+Gradient.swift new file mode 100644 index 0000000000..22be16002d --- /dev/null +++ b/Modules/RobotKit/Sources/Robot+Gradient.swift @@ -0,0 +1,71 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import SwiftUI + +// MARK: - Robot.Color + +// swiftlint:disable identifier_name + +public extension Robot { + struct Gradient { + // MARK: Lifecycle + + public init(fromColors colors: Color...) { + self.gradientColors = colors + } + + // MARK: Public + + public func color(at position: Float) -> Robot.Color { + let positionClamped = max(min(position, 1), 0) + + let scaledIndex = positionClamped * Float(self.gradientColors.count - 1) + let firstIndex = Int(scaledIndex) + let secondIndex = min(firstIndex + 1, gradientColors.count - 1) + let fraction = CGFloat(scaledIndex - Float(firstIndex)) + + let firstColor = self.gradientColors[firstIndex] + let secondColor = self.gradientColors[secondIndex] + + let (h1, s1, v1) = firstColor.robotUiColor.toHSV() + let (h2, s2, v2) = secondColor.robotUiColor.toHSV() + + let h = self.interpolateHue(from: h1, to: h2, fraction: fraction) + let s = self.interpolate(from: s1, to: s2, fraction: fraction) + let v = self.interpolate(from: v1, to: v2, fraction: fraction) + + let uiColor = UIColor(hue: h, saturation: s, brightness: v, alpha: 1) + + let r = UInt8(max(min(uiColor.cgColor.components![0] * 255.0, 255), 0)) + let g = UInt8(max(min(uiColor.cgColor.components![1] * 255.0, 255), 0)) + let b = UInt8(max(min(uiColor.cgColor.components![2] * 255.0, 255), 0)) + + return Robot.Color(red: r, green: g, blue: b) + } + + // MARK: Private + + private let gradientColors: [Robot.Color] + + private func interpolate(from start: CGFloat, to end: CGFloat, fraction: CGFloat) -> CGFloat { + start + (end - start) * fraction + } + + private func interpolateHue(from start: CGFloat, to end: CGFloat, fraction: CGFloat) -> CGFloat { + let diff = abs(end - start) + if diff > 0.5 { + if start > end { + return (start + ((1.0 + end - start) * fraction)).truncatingRemainder(dividingBy: 1.0) + } else { + return (start - ((1.0 + start - end) * fraction)).truncatingRemainder(dividingBy: 1.0) + } + } else { + return self.interpolate(from: start, to: end, fraction: fraction) + } + } + } +} + +// swiftlint:enable identifier_name