use crate::asset_id::AssetId;
use crate::patching::definition::FixtureDefinition;
use emath::Pos2;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
)]
#[serde(transparent)]
pub struct FixtureId(pub Uuid);
impl FixtureId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl std::fmt::Display for FixtureId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub const UNITS_PER_METRE: f32 = 100.0;
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub struct LayoutPosition {
pub pos: Pos2,
pub angle: f32,
pub scale: [f32; 2],
}
impl Default for LayoutPosition {
fn default() -> Self {
Self {
pos: Pos2::new(100.0, 100.0),
angle: 0.0,
scale: [1.0, 1.0],
}
}
}
impl LayoutPosition {
pub fn new(pos: Pos2, angle: f32, scale: [f32; 2]) -> Self {
Self { pos, angle, scale }
}
pub fn at(pos: Pos2) -> Self {
Self {
pos,
..Default::default()
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Fixture {
pub id: FixtureId,
pub name: String,
pub definition: Option<AssetId<FixtureDefinition>>,
}
impl Fixture {
pub fn new(name: impl Into<String>) -> Self {
Self {
id: FixtureId::new(),
name: name.into(),
definition: None,
}
}
pub fn with_definition(
name: impl Into<String>,
definition: AssetId<FixtureDefinition>,
) -> Self {
Self {
definition: Some(definition),
..Self::new(name)
}
}
}
impl Default for Fixture {
fn default() -> Self {
Self::new("Fixture")
}
}
pub const CHANNELS_PER_UNIVERSE: u16 = 512;
pub const CHANNELS_PER_LAMP: u16 = 3;
pub const LAMPS_PER_UNIVERSE: u16 = CHANNELS_PER_UNIVERSE / CHANNELS_PER_LAMP;
pub const MAX_UNIVERSE: u16 = 0x7fff;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ArtnetDMXAddress {
universe: u16,
channel: u16,
}
impl ArtnetDMXAddress {
pub fn into_pair(self) -> (u16, u16) {
(self.universe, self.channel)
}
pub fn from_pair((universe, channel): (u16, u16)) -> Self {
Self { universe, channel }
}
pub fn universe(self) -> u16 {
self.universe
}
pub fn channel(self) -> u16 {
self.channel
}
pub fn lamp_index(self, channel_offset: u16) -> usize {
(usize::from(self.channel) + usize::from(channel_offset)) / usize::from(CHANNELS_PER_LAMP)
}
}
impl std::fmt::Display for ArtnetDMXAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (universe, channel) = self.into_pair();
write!(f, "{universe}.{channel}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn address_pair_round_trip() {
for pair in [
(0u16, 0u16),
(1, 5),
(7, 511),
(127, 0),
(128, 256),
(1000, 3),
] {
assert_eq!(ArtnetDMXAddress::from_pair(pair).into_pair(), pair);
}
}
#[test]
fn a_pixels_lamp_index_counts_rgb_triples_from_the_address() {
let address = ArtnetDMXAddress::from_pair((2, 6));
assert_eq!(address.lamp_index(0), 2);
assert_eq!(address.lamp_index(3), 3);
assert_eq!(address.lamp_index(5), 3);
assert_eq!(
ArtnetDMXAddress::from_pair((0, 511)).lamp_index(u16::MAX),
(511 + 65_535) / 3
);
}
#[test]
fn the_highest_art_net_universe_round_trips() {
let address = ArtnetDMXAddress::from_pair((MAX_UNIVERSE, 511));
assert_eq!(address.into_pair(), (32767, 511));
}
#[test]
fn address_keeps_universe_and_channel_apart() {
let address = ArtnetDMXAddress::from_pair((3, 17));
assert_eq!(address.universe(), 3);
assert_eq!(address.channel(), 17);
}
}