1use crate::asset_id::AssetId;
2use crate::patching::definition::FixtureDefinition;
3use emath::Pos2;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(
8 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
9)]
10#[serde(transparent)]
11pub struct FixtureId(pub Uuid);
12
13impl FixtureId {
14 pub fn new() -> Self {
15 Self(Uuid::new_v4())
16 }
17}
18
19impl std::fmt::Display for FixtureId {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 self.0.fmt(f)
22 }
23}
24
25pub const UNITS_PER_METRE: f32 = 100.0;
27
28#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
34pub struct LayoutPosition {
35 pub pos: Pos2,
36 pub angle: f32,
37 pub scale: [f32; 2],
38}
39
40impl Default for LayoutPosition {
41 fn default() -> Self {
42 Self {
43 pos: Pos2::new(100.0, 100.0),
44 angle: 0.0,
45 scale: [1.0, 1.0],
46 }
47 }
48}
49
50impl LayoutPosition {
51 pub fn new(pos: Pos2, angle: f32, scale: [f32; 2]) -> Self {
52 Self { pos, angle, scale }
53 }
54
55 pub fn at(pos: Pos2) -> Self {
56 Self {
57 pos,
58 ..Default::default()
59 }
60 }
61}
62
63#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
67pub struct Fixture {
68 pub id: FixtureId,
69 pub name: String,
70 pub definition: Option<AssetId<FixtureDefinition>>,
73}
74
75impl Fixture {
76 pub fn new(name: impl Into<String>) -> Self {
77 Self {
78 id: FixtureId::new(),
79 name: name.into(),
80 definition: None,
81 }
82 }
83
84 pub fn with_definition(
86 name: impl Into<String>,
87 definition: AssetId<FixtureDefinition>,
88 ) -> Self {
89 Self {
90 definition: Some(definition),
91 ..Self::new(name)
92 }
93 }
94}
95
96impl Default for Fixture {
97 fn default() -> Self {
98 Self::new("Fixture")
99 }
100}
101
102pub const CHANNELS_PER_UNIVERSE: u16 = 512;
106
107pub const CHANNELS_PER_LAMP: u16 = 3;
109
110pub const LAMPS_PER_UNIVERSE: u16 = CHANNELS_PER_UNIVERSE / CHANNELS_PER_LAMP;
112
113pub const MAX_UNIVERSE: u16 = 0x7fff;
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
121pub struct ArtnetDMXAddress {
122 universe: u16,
123 channel: u16,
124}
125
126impl ArtnetDMXAddress {
127 pub fn into_pair(self) -> (u16, u16) {
128 (self.universe, self.channel)
129 }
130
131 pub fn from_pair((universe, channel): (u16, u16)) -> Self {
132 Self { universe, channel }
133 }
134
135 pub fn universe(self) -> u16 {
136 self.universe
137 }
138
139 pub fn channel(self) -> u16 {
140 self.channel
141 }
142
143 pub fn lamp_index(self, channel_offset: u16) -> usize {
146 (usize::from(self.channel) + usize::from(channel_offset)) / usize::from(CHANNELS_PER_LAMP)
148 }
149}
150
151impl std::fmt::Display for ArtnetDMXAddress {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 let (universe, channel) = self.into_pair();
154 write!(f, "{universe}.{channel}")
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn address_pair_round_trip() {
164 for pair in [
165 (0u16, 0u16),
166 (1, 5),
167 (7, 511),
168 (127, 0),
169 (128, 256),
170 (1000, 3),
171 ] {
172 assert_eq!(ArtnetDMXAddress::from_pair(pair).into_pair(), pair);
173 }
174 }
175
176 #[test]
177 fn a_pixels_lamp_index_counts_rgb_triples_from_the_address() {
178 let address = ArtnetDMXAddress::from_pair((2, 6));
179 assert_eq!(address.lamp_index(0), 2);
180 assert_eq!(address.lamp_index(3), 3);
181 assert_eq!(address.lamp_index(5), 3);
182 assert_eq!(
184 ArtnetDMXAddress::from_pair((0, 511)).lamp_index(u16::MAX),
185 (511 + 65_535) / 3
186 );
187 }
188
189 #[test]
190 fn the_highest_art_net_universe_round_trips() {
191 let address = ArtnetDMXAddress::from_pair((MAX_UNIVERSE, 511));
192 assert_eq!(address.into_pair(), (32767, 511));
193 }
194
195 #[test]
196 fn address_keeps_universe_and_channel_apart() {
197 let address = ArtnetDMXAddress::from_pair((3, 17));
198 assert_eq!(address.universe(), 3);
199 assert_eq!(address.channel(), 17);
200 }
201}