use crate::patching::definition::{FixtureDefinition, Mode, Pixel, group_ids};
use std::collections::BTreeSet;
use std::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum DefinitionError {
#[error("the new order is not a permutation of the {expected} pixels")]
NotAPermutation { expected: usize },
#[error("there is no mode {0}")]
NoSuchMode(usize),
#[error("the first mode, \"individual\", gives every pixel its own group and cannot change")]
IndividualMode,
#[error("there is no model {0}")]
NoSuchModel(usize),
#[error("\"{name}\" still draws {pixels} pixels; give them another model first")]
ModelInUse { name: String, pixels: usize },
}
impl Mode {
pub(crate) fn normalize(&mut self) {
self.group_of = group_ids(self.group_of.iter().copied());
}
pub fn is_partition_of(&self, pixel_count: usize) -> bool {
let ids: BTreeSet<usize> = self.group_of.iter().copied().collect();
self.group_of.len() == pixel_count && ids.iter().copied().eq(0..ids.len())
}
fn fit(&mut self, pixel_count: usize) {
self.group_of.truncate(pixel_count);
self.extend_individual(pixel_count - self.group_of.len());
self.normalize();
}
fn extend_individual(&mut self, added: usize) {
let next = self.group_of.iter().max().map_or(0, |max| max + 1);
self.group_of.extend(next..next + added);
}
fn remap(&mut self, order: &[usize]) {
self.group_of = order.iter().map(|old| self.group_of[*old]).collect();
self.normalize();
}
}
impl FixtureDefinition {
pub fn insert_pixels(&mut self, pixels: Vec<Pixel>) -> Range<usize> {
self.fit_modes();
let start = self.pixels.len();
let added = pixels.len();
self.pixels.extend(pixels);
for mode in &mut self.modes {
mode.extend_individual(added);
}
self.debug_check();
start..start + added
}
pub fn remove_pixels(&mut self, indices: &BTreeSet<usize>) {
let keep: Vec<usize> = (0..self.pixels.len())
.filter(|index| !indices.contains(index))
.collect();
self.apply_order(&keep);
}
pub fn reorder(&mut self, order: &[usize]) -> Result<(), DefinitionError> {
let unique: BTreeSet<usize> = order.iter().copied().collect();
if order.len() != self.pixels.len() || !unique.iter().copied().eq(0..self.pixels.len()) {
return Err(DefinitionError::NotAPermutation {
expected: self.pixels.len(),
});
}
self.apply_order(order);
Ok(())
}
pub fn reverse_pixels(&mut self, indices: &BTreeSet<usize>) {
let slots: Vec<usize> = self.existing(indices).collect();
let mut order: Vec<usize> = (0..self.pixels.len()).collect();
for (slot, source) in slots.iter().zip(slots.iter().rev()) {
order[*slot] = *source;
}
self.apply_order(&order);
}
pub fn move_to_front(&mut self, indices: &BTreeSet<usize>) {
let selected: Vec<usize> = self.existing(indices).collect();
let order: Vec<usize> = selected
.iter()
.copied()
.chain((0..self.pixels.len()).filter(|index| !indices.contains(index)))
.collect();
self.apply_order(&order);
}
pub fn duplicate_pixels(
&mut self,
indices: &BTreeSet<usize>,
offset_m: [f32; 3],
) -> Range<usize> {
let copies = self
.existing(indices)
.map(|index| {
let mut copy = self.pixels[index].clone();
copy.name = format!("{} copy", copy.name);
for (axis, offset) in copy.position_m.iter_mut().zip(offset_m) {
*axis += offset;
}
copy
})
.collect();
self.insert_pixels(copies)
}
pub fn set_model_of(
&mut self,
indices: &BTreeSet<usize>,
model: usize,
) -> Result<(), DefinitionError> {
if model >= self.models.len() {
return Err(DefinitionError::NoSuchModel(model));
}
for index in self.existing(indices).collect::<Vec<_>>() {
self.pixels[index].model = model;
}
Ok(())
}
pub fn remove_model(&mut self, model: usize) -> Result<(), DefinitionError> {
if model >= self.models.len() {
return Err(DefinitionError::NoSuchModel(model));
}
let users = self
.pixels
.iter()
.filter(|pixel| pixel.model == model)
.count();
if users > 0 {
return Err(DefinitionError::ModelInUse {
name: self.models[model].name.clone(),
pixels: users,
});
}
self.models.remove(model);
for pixel in &mut self.pixels {
if pixel.model > model {
pixel.model -= 1;
}
}
self.outline = match self.outline {
Some(outline) if outline == model => None,
Some(outline) if outline > model => Some(outline - 1),
outline => outline,
};
Ok(())
}
pub fn pixel_bounds_m(&self) -> Option<([f32; 2], [f32; 2])> {
let mut bounds: Option<([f32; 2], [f32; 2])> = None;
for pixel in &self.pixels {
let half = self.model(pixel.model).map_or([0.0; 2], |model| {
[model.size_m[0] / 2.0, model.size_m[1] / 2.0]
});
let (sin, cos) = pixel.rotation.sin_cos();
let extent = [
(half[0] * cos).abs() + (half[1] * sin).abs(),
(half[0] * sin).abs() + (half[1] * cos).abs(),
];
let [x, y, _] = pixel.position_m;
let low = [x - extent[0], y - extent[1]];
let high = [x + extent[0], y + extent[1]];
bounds = Some(match bounds {
None => (low, high),
Some((min, max)) => (
[min[0].min(low[0]), min[1].min(low[1])],
[max[0].max(high[0]), max[1].max(high[1])],
),
});
}
bounds
}
pub fn fit_size(&mut self) {
let pixels = self.pixel_bounds_m().map_or([0.0; 2], |(min, max)| {
[
min[0].abs().max(max[0].abs()),
min[1].abs().max(max[1].abs()),
]
});
let outline = self
.outline
.and_then(|outline| self.model(outline))
.map_or([0.0; 2], |outline| outline.size_m.map(|size| size / 2.0));
self.size_m[0] = 2.0 * pixels[0].max(outline[0]);
self.size_m[1] = 2.0 * pixels[1].max(outline[1]);
}
pub fn fit_size_to_photo(&mut self) {
self.fit_size();
if let Some(photo) = &self.photo {
for (size, reach) in self.size_m.iter_mut().zip(photo.reach_m()) {
*size = size.max(2.0 * reach);
}
}
}
pub fn centre_pixels(&mut self) {
let Some((min, max)) = self.pixel_bounds_m() else {
return;
};
let centre = [(min[0] + max[0]) / 2.0, (min[1] + max[1]) / 2.0];
for pixel in &mut self.pixels {
pixel.position_m[0] -= centre[0];
pixel.position_m[1] -= centre[1];
}
if let Some(photo) = &mut self.photo {
photo.offset_m[0] -= centre[0];
photo.offset_m[1] -= centre[1];
}
}
pub fn add_mode(&mut self, name: impl Into<String>) -> usize {
let mut mode = Mode::individual(self.pixels.len());
mode.name = name.into();
self.modes.push(mode);
self.modes.len() - 1
}
pub fn remove_mode(&mut self, index: usize) -> Result<(), DefinitionError> {
self.editable_mode(index)?;
self.modes.remove(index);
Ok(())
}
pub fn group_selection(
&mut self,
mode: usize,
pixels: &BTreeSet<usize>,
) -> Result<(), DefinitionError> {
let selected: Vec<usize> = self.existing(pixels).collect();
let mode = self.editable_mode(mode)?;
let joined = mode.group_of.iter().max().map_or(0, |max| max + 1);
for pixel in selected {
mode.group_of[pixel] = joined;
}
mode.normalize();
Ok(())
}
pub fn ungroup_selection(
&mut self,
mode: usize,
pixels: &BTreeSet<usize>,
) -> Result<(), DefinitionError> {
let selected: Vec<usize> = self.existing(pixels).collect();
let mode = self.editable_mode(mode)?;
let next = mode.group_of.iter().max().map_or(0, |max| max + 1);
for (offset, pixel) in selected.into_iter().enumerate() {
mode.group_of[pixel] = next + offset;
}
mode.normalize();
Ok(())
}
pub fn each_own_group(&mut self, mode: usize) -> Result<(), DefinitionError> {
let pixel_count = self.pixels.len();
let mode = self.editable_mode(mode)?;
mode.group_of = (0..pixel_count).collect();
Ok(())
}
pub fn next_pixel_number(&self) -> usize {
self.pixels.len()
}
fn editable_mode(&mut self, index: usize) -> Result<&mut Mode, DefinitionError> {
if index == self.default_mode() {
return Err(DefinitionError::IndividualMode);
}
self.fit_modes();
self.modes
.get_mut(index)
.ok_or(DefinitionError::NoSuchMode(index))
}
fn existing<'a>(&self, indices: &'a BTreeSet<usize>) -> impl Iterator<Item = usize> + 'a {
let pixel_count = self.pixels.len();
indices
.iter()
.copied()
.filter(move |index| *index < pixel_count)
}
fn apply_order(&mut self, order: &[usize]) {
self.fit_modes();
self.pixels = order.iter().map(|old| self.pixels[*old].clone()).collect();
for mode in &mut self.modes {
mode.remap(order);
}
self.debug_check();
}
fn fit_modes(&mut self) {
let pixel_count = self.pixels.len();
for mode in &mut self.modes {
mode.fit(pixel_count);
}
}
fn debug_check(&self) {
debug_assert!(
self.modes
.iter()
.all(|mode| mode.is_partition_of(self.pixels.len())),
"every mode must stay a partition of the pixels"
);
}
}
#[cfg(test)]
mod tests;