Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): self-serve ui for pearl orbs #283

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/cone/examples/cone-simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async fn simulation_task(cone: &mut Cone) -> eyre::Result<()> {
}
SimulationState::QrCode => {
for pixel in pixels.iter_mut() {
*pixel = Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER;
*pixel = Argb::DIAMOND_CENTER_SUMMON_USER_AMBER;
}

let cmd =
Expand Down
63 changes: 50 additions & 13 deletions ui/rgb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,37 @@ impl ops::Mul<f64> for Argb {

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn mul(self, rhs: f64) -> Self::Output {
Argb(
self.0,
((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX),
)
// at low brightness (low rhs)
// prefer to turn LED off if the resulting color has only 1 component
// with an initial color that has more than 1 component
if ((self.1 != 0) as u8) + ((self.2 != 0) as u8) + ((self.3 != 0) as u8) > 1 {
let res = Argb(
self.0,
((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX),
);
// if result color has only 1 component, prefer to turn LED off
if ((res.1 != 0) as u8) + ((res.2 != 0) as u8) + ((res.3 != 0) as u8) == 1 {
Argb::OFF
} else {
res
}
} else {
Argb(
self.0,
((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX),
)
}
}
}

impl ops::MulAssign<f64> for Argb {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn mul_assign(&mut self, rhs: f64) {
self.1 = ((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX);
self.2 = ((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX);
self.3 = ((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX);
*self = *self * rhs;
}
}

Expand Down Expand Up @@ -69,6 +85,29 @@ impl Argb {
pub const PEARL_USER_SIGNUP: Argb = Argb(None, 31, 31, 31);
pub const PEARL_USER_FLASH: Argb = Argb(None, 255, 255, 255);

/// ***** Self-serve colors *****
/// We intentionally don't include blue in most of the color scheme
/// because a sine wave with a low blue component doesn't look good:
/// whiter once wave is over, but darker during the wave.
///
/// Outer-ring color during operator QR scans
pub const PEARL_RING_OPERATOR_QR_SCAN: Argb = Argb(None, 20, 6, 0);
pub const PEARL_RING_OPERATOR_QR_SCAN_SPINNER: Argb = Argb(None, 25, 15, 9);
pub const PEARL_RING_OPERATOR_QR_SCAN_SPINNER_OPERATOR_BASED: Argb =
Argb(None, 25, 22, 5);
/// Outer-ring color during user QR scans
pub const PEARL_RING_USER_QR_SCAN: Argb = Argb(None, 30, 20, 0);
pub const PEARL_RING_USER_QR_SCAN_SPINNER: Argb = Argb(None, 28, 25, 10);
/// Shroud color to invite user to scan / reposition in front of the orb
pub const PEARL_CENTER_SUMMON_USER_AMBER: Argb = Argb(None, 30, 20, 0);
/// Shroud color during user scan/capture (in progress)
pub const PEARL_CENTER_USER_CAPTURE: Argb = Argb(None, 30, 20, 0);
/// Outer-ring color during user scan/capture (in progress)
pub const PEARL_RING_USER_CAPTURE: Argb = Argb(None, 30, 20, 0);
/// Error color for outer ring
pub const PEARL_RING_ERROR_SALMON: Argb = Argb(None, 24, 4, 0);

/// ***** Self-serve colors *****
pub const DIAMOND_OPERATOR_AMBER: Argb =
Argb(Some(Self::DIMMING_MAX_VALUE), 20, 16, 0);
// To help quickly distinguish dev vs prod software,
Expand All @@ -87,10 +126,8 @@ impl Argb {
/// Outer-ring color during user QR scans
pub const DIAMOND_RING_USER_QR_SCAN: Argb = Argb(Some(5), 120, 80, 4);
pub const DIAMOND_RING_USER_QR_SCAN_SPINNER: Argb = Argb(Some(10), 100, 90, 35);
/// Shroud color to invite user to scan / reposition in front of the orb
pub const DIAMOND_SHROUD_SUMMON_USER_AMBER: Argb = Argb(Some(3), 95, 40, 3);
/// Shroud color during user scan/capture (in progress)
pub const DIAMOND_SHROUD_USER_CAPTURE: Argb = Argb(Some(3), 118, 51, 3);
/// Shroud color to invite user to scan / reposition in front of the orb and capture
pub const DIAMOND_CENTER_SUMMON_USER_AMBER: Argb = Argb(Some(3), 95, 40, 3);
/// Outer-ring color during user scan/capture (in progress)
pub const DIAMOND_RING_USER_CAPTURE: Argb = Argb(Some(10), 120, 100, 4);
pub const DIAMOND_CONE_AMBER: Argb = Argb(Some(Self::DIMMING_MAX_VALUE), 25, 18, 1);
Expand Down
53 changes: 51 additions & 2 deletions ui/src/engine/animations/progress.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::engine::animations::{render_lines, LIGHT_BLEEDING_OFFSET_RAD};
use crate::engine::{Animation, AnimationState, RingFrame};
use crate::engine::{Animation, AnimationState, RingFrame, Transition};
use eyre::eyre;
use orb_rgb::Argb;
use std::{any::Any, f64::consts::PI};

Expand All @@ -19,6 +20,8 @@ pub struct Progress<const N: usize> {
/// once `progress` reached, maintain progress ring to set `progress` during `progress_duration`
progress_duration: Option<f64>,
pulse_angle: f64,
transition: Option<Transition>,
transition_time: f64,
pub(crate) shape: Shape<N>,
}

Expand All @@ -44,6 +47,8 @@ impl<const N: usize> Progress<N> {
progress: initial_progress,
progress_duration,
pulse_angle: PULSE_ANGLE_RAD,
transition: None,
transition_time: 0.0,
shape: Shape {
progress: 0.0,
phase: 0.0,
Expand Down Expand Up @@ -87,8 +92,35 @@ impl<const N: usize> Animation for Progress<N> {
dt: f64,
idle: bool,
) -> AnimationState {
let scaling_factor = match self.transition {
Some(Transition::ForceStop) => return AnimationState::Finished,
Some(Transition::StartDelay(duration)) => {
self.transition_time += dt;
if self.transition_time >= duration {
self.transition = None;
}
return AnimationState::Running;
}
Some(Transition::FadeOut(duration)) => {
self.transition_time += dt;
if self.transition_time >= duration {
return AnimationState::Finished;
}
(self.transition_time * PI / 2.0 / duration).cos()
}
Some(Transition::FadeIn(duration)) => {
self.transition_time += dt;
if self.transition_time >= duration {
self.transition = None;
}
(self.transition_time * PI / 2.0 / duration).sin()
}
_ => 1.0,
};

tracing::trace!("scaling: {scaling_factor}");
if !idle {
self.shape.render(frame, self.color);
self.shape.render(frame, self.color * scaling_factor);
}

self.shape.progress = self.shape.progress
Expand Down Expand Up @@ -127,6 +159,23 @@ impl<const N: usize> Animation for Progress<N> {
}
}
}

fn stop(&mut self, transition: Transition) -> eyre::Result<()> {
match transition {
Transition::PlayOnce | Transition::Shrink => {
return Err(eyre!(
"Transition {:?} not supported for static animation",
transition
));
}
t => {
self.transition = Some(t);
self.transition_time = 0.0;
}
}

Ok(())
}
}

impl<const N: usize> Shape<N> {
Expand Down
31 changes: 24 additions & 7 deletions ui/src/engine/animations/simple_spinner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::engine::animations::Static;
use crate::engine::{
Animation, AnimationState, RingFrame, Transition, TransitionStatus,
PEARL_RING_LED_COUNT,
};
use eyre::eyre;
use orb_rgb::Argb;
Expand Down Expand Up @@ -92,11 +93,23 @@ impl<const N: usize> Animation for SimpleSpinner<N> {
}

self.phase = (self.phase + dt * self.speed) % (2.0 * PI);
// `N - led-index` with `led-index` increasing in {0,N}, because the LED strip goes anti-clockwise
// `N as f64 * 3.0 / 4.0` because the first LED is at 6 o'clock (3PI/2)
let progress = (N as f64 - (self.phase * N as f64 / (2.0 * PI))
+ N as f64 * 3.0 / 4.0)
% N as f64;
let (progress, led_spinner_count) = if N == PEARL_RING_LED_COUNT {
// Pearl
(
(self.phase * N as f64 / (2.0 * PI) + N as f64 / 4.0) % N as f64,
15,
)
} else {
// Diamond
// `N - led-index` with `led-index` increasing in {0,N}, because the LED strip goes anti-clockwise
// `N as f64 * 3.0 / 4.0` because the first LED is at 6 o'clock (3PI/2)
(
(N as f64 - (self.phase * N as f64 / (2.0 * PI))
+ N as f64 * 3.0 / 4.0)
% N as f64,
3,
)
};
let led_index = progress as usize;
let head_tail_scale = progress - led_index as f64;

Expand Down Expand Up @@ -154,9 +167,13 @@ impl<const N: usize> Animation for SimpleSpinner<N> {
as i32) as u8,
);
*led = c * scaling_factor;
} else if i == (led_index + 1) % N || i == (led_index + 2) % N {
} else if (((led_index + led_spinner_count) % N) < led_index
&& (i < ((led_index + led_spinner_count) % N) || i > led_index))
|| (((led_index + led_spinner_count) % N) > led_index
&& (i < ((led_index + led_spinner_count) % N) && i > led_index))
{
*led = self.color * scaling_factor;
} else if i == (led_index + 3) % N {
} else if i == (led_index + led_spinner_count) % N {
let c = Argb(
self.color.0,
(background.1 as i32
Expand Down
3 changes: 2 additions & 1 deletion ui/src/engine/animations/wave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ impl<const N: usize> Animation for Wave<N> {

intensity *= scaling_factor;

// specific case for pearl center
if N == PEARL_CENTER_LED_COUNT {
let r = f64::from(self.color.1) * intensity;
let g = f64::from(self.color.2) * intensity;
Expand Down Expand Up @@ -167,7 +168,7 @@ impl<const N: usize> Animation for Wave<N> {
*led = Argb(None, r, g, b);
}
} else {
// diamond
// pearl's ring or diamond
for led in frame.iter_mut() {
*led = self.color * intensity;
}
Expand Down
20 changes: 12 additions & 8 deletions ui/src/engine/diamond.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ use tokio_stream::wrappers::{IntervalStream, UnboundedReceiverStream};
use crate::engine::animations::alert::BlinkDurations;
use crate::engine::{
animations, operator, Animation, AnimationsStack, CenterFrame, ConeFrame, Event,
EventHandler, OperatorFrame, OrbType, QrScanSchema, QrScanUnexpectedReason,
RingFrame, Runner, RunningAnimation, SignupFailReason, Transition,
DIAMOND_CENTER_LED_COUNT, DIAMOND_CONE_LED_COUNT, DIAMOND_RING_LED_COUNT,
LED_ENGINE_FPS, LEVEL_BACKGROUND, LEVEL_FOREGROUND, LEVEL_NOTICE,
EventHandler, OperatingMode, OperatorFrame, OrbType, QrScanSchema,
QrScanUnexpectedReason, RingFrame, Runner, RunningAnimation, SignupFailReason,
Transition, DIAMOND_CENTER_LED_COUNT, DIAMOND_CONE_LED_COUNT,
DIAMOND_RING_LED_COUNT, LED_ENGINE_FPS, LEVEL_BACKGROUND, LEVEL_FOREGROUND,
LEVEL_NOTICE,
};
use crate::sound;
use crate::sound::Player;
Expand Down Expand Up @@ -181,6 +182,7 @@ impl Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
is_api_mode: false,
paused: false,
gimbal: None,
operating_mode: OperatingMode::default(),
}
}

Expand Down Expand Up @@ -546,7 +548,7 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
self.set_center(
LEVEL_FOREGROUND,
animations::Wave::<DIAMOND_CENTER_LED_COUNT>::new(
Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER,
Argb::DIAMOND_CENTER_SUMMON_USER_AMBER,
3.0,
0.0,
true,
Expand Down Expand Up @@ -642,7 +644,7 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
self.set_center(
LEVEL_FOREGROUND,
animations::Static::<DIAMOND_CENTER_LED_COUNT>::new(
Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER,
Argb::DIAMOND_CENTER_SUMMON_USER_AMBER,
None,
)
.fade_in(1.5),
Expand Down Expand Up @@ -802,7 +804,6 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {

self.operator_signup_phase.signup_successful();

// ring, run error animation at NOTICE level, off for the rest.
self.set_ring(
LEVEL_BACKGROUND,
animations::Static::<DIAMOND_RING_LED_COUNT>::new(Argb::OFF, None),
Expand Down Expand Up @@ -891,7 +892,7 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
self.set_ring(
LEVEL_NOTICE,
animations::Spinner::<DIAMOND_RING_LED_COUNT>::triple(
Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER,
Argb::DIAMOND_RING_ERROR_SALMON,
None,
),
);
Expand Down Expand Up @@ -936,6 +937,9 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
Event::Gimbal { x, y } => {
self.gimbal = Some((*x, *y));
}
Event::Flow { mode } => {
self.operating_mode = *mode;
}
}
Ok(())
}
Expand Down
Loading
Loading