Compare commits

..

No commits in common. "f4d990546b474e1b3b3fc7a44214e356d3e93eae" and "911bc97b69704e90697848e097753339c436aa82" have entirely different histories.

10 changed files with 743 additions and 835 deletions

896
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -32,5 +32,5 @@ members = [
"sgf",
"timezone-testing",
"tree",
"visions/server", "gm-dash/server"
"visions/server", "gm-dash/server", "halloween-leds"
]

View File

@ -1,121 +0,0 @@
use fixed::types::U16F0;
use crate::{
calculate_frames, Animation, BodyPattern, DashboardPattern, Fade, FadeDirection, Instant, BLINKER_FRAMES, LEFT_BLINKER_BODY, LEFT_BLINKER_DASHBOARD, OFF_BODY, OFF_DASHBOARD, RIGHT_BLINKER_BODY, RIGHT_BLINKER_DASHBOARD
};
pub enum BlinkerDirection {
Left,
Right,
}
pub struct Blinker {
transition: Fade,
fade_in: Fade,
fade_out: Fade,
direction: FadeDirection,
start_time: Instant,
frames: U16F0,
}
impl Blinker {
pub fn new(
starting_dashboard: DashboardPattern,
starting_body: BodyPattern,
direction: BlinkerDirection,
time: Instant,
) -> Self {
let mut ending_dashboard = OFF_DASHBOARD.clone();
match direction {
BlinkerDirection::Left => {
ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r;
ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g;
ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b;
}
BlinkerDirection::Right => {
ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r;
ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g;
ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b;
}
}
let mut ending_body = OFF_BODY.clone();
match direction {
BlinkerDirection::Left => {
for i in 0..30 {
ending_body[i].r = LEFT_BLINKER_BODY[i].r;
ending_body[i].g = LEFT_BLINKER_BODY[i].g;
ending_body[i].b = LEFT_BLINKER_BODY[i].b;
}
}
BlinkerDirection::Right => {
for i in 30..60 {
ending_body[i].r = RIGHT_BLINKER_BODY[i].r;
ending_body[i].g = RIGHT_BLINKER_BODY[i].g;
ending_body[i].b = RIGHT_BLINKER_BODY[i].b;
}
}
}
Blinker {
transition: Fade::new(
starting_dashboard.clone(),
starting_body.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_in: Fade::new(
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_out: Fade::new(
ending_dashboard.clone(),
ending_body.clone(),
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
BLINKER_FRAMES,
time,
),
direction: FadeDirection::Transition,
start_time: time,
frames: BLINKER_FRAMES,
}
}
}
impl Animation for Blinker {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
match self.direction {
FadeDirection::Transition => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeIn => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeOut => {
self.direction = FadeDirection::FadeIn;
self.fade_in.start_time = time;
}
}
self.start_time = time;
}
match self.direction {
FadeDirection::Transition => self.transition.tick(time),
FadeDirection::FadeIn => self.fade_in.tick(time),
FadeDirection::FadeOut => self.fade_out.tick(time),
}
}
}

View File

@ -1,100 +0,0 @@
use fixed::types::{I8F8, U16F0};
use crate::{calculate_frames, calculate_slope, linear_ease, Animation, BodyPattern, DashboardPattern, Instant, OFF_BODY, OFF_DASHBOARD, RGB};
pub struct Fade {
starting_dashboard: DashboardPattern,
starting_lights: BodyPattern,
pub start_time: Instant,
dashboard_slope: [RGB<I8F8>; 3],
body_slope: [RGB<I8F8>; 60],
frames: U16F0,
}
impl Fade {
pub fn new(
dashboard: DashboardPattern,
lights: BodyPattern,
ending_dashboard: DashboardPattern,
ending_lights: BodyPattern,
frames: U16F0,
time: Instant,
) -> Self {
let mut dashboard_slope = [Default::default(); 3];
let mut body_slope = [Default::default(); 60];
for i in 0..3 {
let slope = RGB {
r: calculate_slope(dashboard[i].r, ending_dashboard[i].r, frames),
g: calculate_slope(dashboard[i].g, ending_dashboard[i].g, frames),
b: calculate_slope(dashboard[i].b, ending_dashboard[i].b, frames),
};
dashboard_slope[i] = slope;
}
for i in 0..60 {
let slope = RGB {
r: calculate_slope(lights[i].r, ending_lights[i].r, frames),
g: calculate_slope(lights[i].g, ending_lights[i].g, frames),
b: calculate_slope(lights[i].b, ending_lights[i].b, frames),
};
body_slope[i] = slope;
}
Self {
starting_dashboard: dashboard,
starting_lights: lights,
start_time: time,
dashboard_slope,
body_slope,
frames,
}
}
}
impl Animation for Fade {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let mut frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
frames = self.frames
}
let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD;
let mut body_pattern: BodyPattern = OFF_BODY;
for i in 0..3 {
dashboard_pattern[i].r = linear_ease(
self.starting_dashboard[i].r,
frames,
self.dashboard_slope[i].r,
);
dashboard_pattern[i].g = linear_ease(
self.starting_dashboard[i].g,
frames,
self.dashboard_slope[i].g,
);
dashboard_pattern[i].b = linear_ease(
self.starting_dashboard[i].b,
frames,
self.dashboard_slope[i].b,
);
}
for i in 0..60 {
body_pattern[i].r =
linear_ease(self.starting_lights[i].r, frames, self.body_slope[i].r);
body_pattern[i].g =
linear_ease(self.starting_lights[i].g, frames, self.body_slope[i].g);
body_pattern[i].b =
linear_ease(self.starting_lights[i].b, frames, self.body_slope[i].b);
}
(dashboard_pattern, body_pattern)
}
}
#[derive(Debug)]
pub enum FadeDirection {
Transition,
FadeIn,
FadeOut,
}

View File

@ -1,43 +0,0 @@
use crate::{Animation, BodyPattern, DashboardPattern, Instant};
pub struct FlagRipple {
dashboard: DashboardPattern,
body: BodyPattern,
centers: [usize; 12],
}
impl FlagRipple {
fn new(dashboard: DashboardPattern, body: BodyPattern) -> Self {
Self {
dashboard,
body,
centers: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60],
}
}
}
impl Animation for FlagRipple {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern, Option<Animation>) {
// How does a flag ripple work? I think have some center points that are darker and have a
// bit of a darker pattern around them.
let mut body_pattern = self.body.clone();
for i in 0..60 {
if self.centers.contains(&i) {
body_pattern[i].r = self.body[i].r / 2;
body_pattern[i].g = self.body[i].g / 2;
body_pattern[i].b = self.body[i].b / 2;
} else {
body_pattern[i].r = self.body[i].r;
body_pattern[i].g = self.body[i].g;
body_pattern[i].b = self.body[i].b;
}
}
(self.dashboard, body_pattern, None)
}
}

View File

@ -1,31 +0,0 @@
use fixed::types::{I48F16, I8F8, U128F0, U16F0};
use az::*;
use crate::{BodyPattern, DashboardPattern, Instant, FPS};
mod blinker;
pub use blinker::*;
mod fade;
pub use fade::*;
pub trait Animation {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern);
}
pub fn linear_ease(value: I8F8, frames: U16F0, slope: I8F8) -> I8F8 {
let value_i16f16 = I48F16::from(value) + I48F16::from(frames) * I48F16::from(slope);
value_i16f16.saturating_as()
}
pub fn calculate_frames(starting_time: U128F0, now: U128F0) -> U16F0 {
let frames_128 = (now - starting_time) / U128F0::from(FPS);
(frames_128 % U128F0::from(U16F0::MAX)).cast()
}
pub fn calculate_slope(start: I8F8, end: I8F8, frames: U16F0) -> I8F8 {
let slope_i16f16 = (I48F16::from(end) - I48F16::from(start)) / I48F16::from(frames);
slope_i16f16.saturating_as()
}

View File

@ -1,95 +0,0 @@
use fixed::types::{I16F0, I8F0, I8F8, U16F0, U8F0};
use az::*;
use crate::{
calculate_frames, calculate_slope, linear_ease, sin, Animation, BodyPattern, DashboardPattern, Instant, OFF_BODY, OFF_DASHBOARD, RGB, WATER_1, WATER_BODY
};
const DROPLET_DIMMING: u8 = 30;
pub struct Ripple {
dashboard: DashboardPattern,
base: BodyPattern,
focii: [usize; 2],
dimming: I8F8,
slope: [RGB<I8F8>; 2],
pub start_time: Instant,
}
impl Ripple {
pub fn new(dashboard: DashboardPattern, base: BodyPattern, time: Instant) -> Self {
let mut slope = [Default::default(); 2];
let dimming = I8F8::lit("0.5");
let focii = [15, 45];
for (idx, focus) in focii.iter().enumerate() {
slope[idx] = RGB {
r: calculate_slope(
base[*focus].r,
base[*focus].r * dimming,
U16F0::from(DROPLET_DIMMING),
),
g: calculate_slope(
base[*focus].g,
base[*focus].g * dimming,
U16F0::from(DROPLET_DIMMING),
),
b: calculate_slope(
base[*focus].b,
base[*focus].b * dimming,
U16F0::from(DROPLET_DIMMING),
),
};
}
Self {
dashboard,
base,
focii,
dimming,
slope,
start_time: time,
}
}
fn pixel(&self, pos: U8F0, frames: U16F0) -> RGB<I8F8> {
let focus: I16F0 = if pos <= 30 { I16F0::from(15 as i16) } else { I16F0::from(45 as i16) };
let distance_from_focus: I8F0 = (focus - I16F0::from(pos)).saturating_as();
let pos: usize = pos.into();
/*
let target = RGB {
r: WATER_BODY[pos].r * self.dimming,
g: WATER_BODY[pos].g * self.dimming,
b: WATER_BODY[pos].b * self.dimming,
};
let slope = RGB{
r: calculate_slope(WATER_BODY[pos].r, target.r, U16F0::from(DROPLET_DIMMING)),
g: calculate_slope(WATER_BODY[pos].g, target.g, U16F0::from(DROPLET_DIMMING)),
b: calculate_slope(WATER_BODY[pos].b, target.b, U16F0::from(DROPLET_DIMMING)),
};
*/
RGB {
r: WATER_BODY[pos].r + sin(frames * 5) * I8F8::from_num(0.5),
g: WATER_BODY[pos].g + sin(frames * 6) * I8F8::from_num(0.5),
b: WATER_BODY[pos].b + sin(frames * 7) * I8F8::from_num(0.5),
}
}
}
impl Animation for Ripple {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let frames = calculate_frames(self.start_time.0, time.0);
let mut body_pattern: BodyPattern = OFF_BODY;
for i in 0..60 {
body_pattern[i] = self.pixel(U8F0::from(i as u8), frames);
}
(self.dashboard.clone(), body_pattern)
}
}

View File

@ -12,15 +12,27 @@ use core::{
};
use fixed::types::{I48F16, I8F8, U128F0, U16F0};
mod animations;
pub use animations::*;
mod patterns;
pub use patterns::*;
mod types;
pub use types::{BodyPattern, DashboardPattern, RGB};
fn calculate_frames(starting_time: U128F0, now: U128F0) -> U16F0 {
let frames_128 = (now - starting_time) / U128F0::from(FPS);
(frames_128 % U128F0::from(U16F0::MAX)).cast()
}
fn calculate_slope(start: I8F8, end: I8F8, frames: U16F0) -> I8F8 {
let slope_i16f16 = (I48F16::from(end) - I48F16::from(start)) / I48F16::from(frames);
slope_i16f16.saturating_as()
}
fn linear_ease(value: I8F8, frames: U16F0, slope: I8F8) -> I8F8 {
let value_i16f16 = I48F16::from(value) + I48F16::from(frames) * I48F16::from(slope);
value_i16f16.saturating_as()
}
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
pub struct Instant(pub U128F0);
@ -53,6 +65,233 @@ pub trait UI {
fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern);
}
pub trait Animation {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern);
}
/*
pub struct DefaultAnimation {}
impl Animation for DefaultAnimation {
fn tick(&mut self, _: Instant) -> (DashboardPattern, BodyPattern) {
(WATER_DASHBOARD, WATER_BODY)
}
}
*/
pub struct Fade {
starting_dashboard: DashboardPattern,
starting_lights: BodyPattern,
start_time: Instant,
dashboard_slope: [RGB<I8F8>; 3],
body_slope: [RGB<I8F8>; 60],
frames: U16F0,
}
impl Fade {
fn new(
dashboard: DashboardPattern,
lights: BodyPattern,
ending_dashboard: DashboardPattern,
ending_lights: BodyPattern,
frames: U16F0,
time: Instant,
) -> Self {
let mut dashboard_slope = [Default::default(); 3];
let mut body_slope = [Default::default(); 60];
for i in 0..3 {
let slope = RGB {
r: calculate_slope(dashboard[i].r, ending_dashboard[i].r, frames),
g: calculate_slope(dashboard[i].g, ending_dashboard[i].g, frames),
b: calculate_slope(dashboard[i].b, ending_dashboard[i].b, frames),
};
dashboard_slope[i] = slope;
}
for i in 0..60 {
let slope = RGB {
r: calculate_slope(lights[i].r, ending_lights[i].r, frames),
g: calculate_slope(lights[i].g, ending_lights[i].g, frames),
b: calculate_slope(lights[i].b, ending_lights[i].b, frames),
};
body_slope[i] = slope;
}
Self {
starting_dashboard: dashboard,
starting_lights: lights,
start_time: time,
dashboard_slope,
body_slope,
frames,
}
}
}
impl Animation for Fade {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let mut frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
frames = self.frames
}
let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD;
let mut body_pattern: BodyPattern = OFF_BODY;
for i in 0..3 {
dashboard_pattern[i].r = linear_ease(
self.starting_dashboard[i].r,
frames,
self.dashboard_slope[i].r,
);
dashboard_pattern[i].g = linear_ease(
self.starting_dashboard[i].g,
frames,
self.dashboard_slope[i].g,
);
dashboard_pattern[i].b = linear_ease(
self.starting_dashboard[i].b,
frames,
self.dashboard_slope[i].b,
);
}
for i in 0..60 {
body_pattern[i].r =
linear_ease(self.starting_lights[i].r, frames, self.body_slope[i].r);
body_pattern[i].g =
linear_ease(self.starting_lights[i].g, frames, self.body_slope[i].g);
body_pattern[i].b =
linear_ease(self.starting_lights[i].b, frames, self.body_slope[i].b);
}
(dashboard_pattern, body_pattern)
}
}
#[derive(Debug)]
pub enum FadeDirection {
Transition,
FadeIn,
FadeOut,
}
pub enum BlinkerDirection {
Left,
Right,
}
pub struct Blinker {
transition: Fade,
fade_in: Fade,
fade_out: Fade,
direction: FadeDirection,
start_time: Instant,
frames: U16F0,
}
impl Blinker {
fn new(
starting_dashboard: DashboardPattern,
starting_body: BodyPattern,
direction: BlinkerDirection,
time: Instant,
) -> Self {
let mut ending_dashboard = OFF_DASHBOARD.clone();
match direction {
BlinkerDirection::Left => {
ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r;
ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g;
ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b;
}
BlinkerDirection::Right => {
ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r;
ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g;
ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b;
}
}
let mut ending_body = OFF_BODY.clone();
match direction {
BlinkerDirection::Left => {
for i in 0..30 {
ending_body[i].r = LEFT_BLINKER_BODY[i].r;
ending_body[i].g = LEFT_BLINKER_BODY[i].g;
ending_body[i].b = LEFT_BLINKER_BODY[i].b;
}
}
BlinkerDirection::Right => {
for i in 30..60 {
ending_body[i].r = RIGHT_BLINKER_BODY[i].r;
ending_body[i].g = RIGHT_BLINKER_BODY[i].g;
ending_body[i].b = RIGHT_BLINKER_BODY[i].b;
}
}
}
Blinker {
transition: Fade::new(
starting_dashboard.clone(),
starting_body.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_in: Fade::new(
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_out: Fade::new(
ending_dashboard.clone(),
ending_body.clone(),
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
BLINKER_FRAMES,
time,
),
direction: FadeDirection::Transition,
start_time: time,
frames: BLINKER_FRAMES,
}
}
}
impl Animation for Blinker {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
match self.direction {
FadeDirection::Transition => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeIn => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeOut => {
self.direction = FadeDirection::FadeIn;
self.fade_in.start_time = time;
}
}
self.start_time = time;
}
match self.direction {
FadeDirection::Transition => self.transition.tick(time),
FadeDirection::FadeIn => self.fade_in.tick(time),
FadeDirection::FadeOut => self.fade_out.tick(time),
}
}
}
#[derive(Clone, Debug)]
pub enum Event {
Brake,

View File

@ -7,7 +7,6 @@ edition = "2021"
[dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] }
async-channel = "2.3.1"
cairo-rs = { version = "0.18" }
fixed = { version = "1" }
gio = { version = "0.18" }

View File

@ -1,13 +1,16 @@
use std::{cell::RefCell, env, rc::Rc};
use adw::prelude::*;
use async_channel::{unbounded, Receiver, Sender, TryRecvError};
use fixed::types::{I8F8, U128F0};
use glib::Object;
use glib::{Object, Sender};
use gtk::subclass::prelude::*;
use lights_core::{
App, BodyPattern, DashboardPattern, Event, Instant, FPS, OFF_BODY, OFF_DASHBOARD, RGB, UI,
};
use std::{
cell::RefCell,
env,
rc::Rc,
sync::mpsc::{Receiver, TryRecvError},
};
const WIDTH: i32 = 640;
const HEIGHT: i32 = 480;
@ -153,13 +156,13 @@ impl UI for GTKUI {
match self.rx.try_recv() {
Ok(event) => Some(event),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Closed) => None,
Err(TryRecvError::Disconnected) => None,
}
}
fn update_lights(&self, dashboard_lights: DashboardPattern, lights: BodyPattern) {
self.tx
.send_blocking(Update {
.send(Update {
dashboard: dashboard_lights,
lights,
})
@ -173,8 +176,9 @@ fn main() {
.build();
adw_app.connect_activate(move |adw_app| {
let (update_tx, update_rx) = unbounded();
let (event_tx, event_rx) = unbounded();
let (update_tx, update_rx) =
gtk::glib::MainContext::channel::<Update>(gtk::glib::Priority::DEFAULT);
let (event_tx, event_rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let mut bike_app = App::new(Box::new(GTKUI {
@ -214,21 +218,21 @@ fn main() {
left_button.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send_blocking(Event::LeftBlinker);
let _ = event_tx.send(Event::LeftBlinker);
}
});
brake_button.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send_blocking(Event::Brake);
let _ = event_tx.send(Event::Brake);
}
});
right_button.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send_blocking(Event::RightBlinker);
let _ = event_tx.send(Event::RightBlinker);
}
});
@ -247,14 +251,14 @@ fn main() {
previous_pattern.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send_blocking(Event::PreviousPattern);
let _ = event_tx.send(Event::PreviousPattern);
}
});
next_pattern.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send_blocking(Event::NextPattern);
let _ = event_tx.send(Event::NextPattern);
}
});
@ -265,7 +269,6 @@ fn main() {
layout.append(&dashboard_lights);
layout.append(&bike_lights);
/*
update_rx.attach(None, {
let dashboard_lights = dashboard_lights.clone();
let bike_lights = bike_lights.clone();
@ -275,19 +278,6 @@ fn main() {
glib::ControlFlow::Continue
}
});
*/
glib::spawn_future_local({
let dashboard_lights = dashboard_lights.clone();
let bike_lights = bike_lights.clone();
async move {
while let Ok(Update { dashboard, lights }) = update_rx.recv().await {
dashboard_lights.set_lights(dashboard);
bike_lights.set_lights(lights);
}
}
});
window.set_content(Some(&layout));
window.present();