use crate::geometry::GeometryId;
use crate::transform::{Transform2D, Transform3D};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Placement<S> {
pub geometry_id: GeometryId,
pub instance: usize,
pub position: Vec<S>,
pub rotation: Vec<S>,
pub boundary_index: usize,
pub mirrored: bool,
pub rotation_index: Option<usize>,
}
impl<S: Copy + Default> Placement<S> {
pub fn new_2d(geometry_id: GeometryId, instance: usize, x: S, y: S, angle: S) -> Self {
Self {
geometry_id,
instance,
position: vec![x, y],
rotation: vec![angle],
boundary_index: 0,
mirrored: false,
rotation_index: None,
}
}
pub fn new_3d(
geometry_id: GeometryId,
instance: usize,
x: S,
y: S,
z: S,
rx: S,
ry: S,
rz: S,
) -> Self {
Self {
geometry_id,
instance,
position: vec![x, y, z],
rotation: vec![rx, ry, rz],
boundary_index: 0,
mirrored: false,
rotation_index: None,
}
}
pub fn with_boundary(mut self, index: usize) -> Self {
self.boundary_index = index;
self
}
pub fn with_mirrored(mut self, mirrored: bool) -> Self {
self.mirrored = mirrored;
self
}
pub fn with_rotation_index(mut self, index: usize) -> Self {
self.rotation_index = Some(index);
self
}
pub fn is_2d(&self) -> bool {
self.position.len() == 2
}
pub fn is_3d(&self) -> bool {
self.position.len() == 3
}
pub fn x(&self) -> S {
self.position.first().copied().unwrap_or_default()
}
pub fn y(&self) -> S {
self.position.get(1).copied().unwrap_or_default()
}
pub fn z(&self) -> Option<S> {
self.position.get(2).copied()
}
pub fn angle(&self) -> S {
self.rotation.first().copied().unwrap_or_default()
}
}
impl<S: u_geometry::nalgebra_types::RealField + Copy + Default> Placement<S> {
pub fn to_transform_2d(&self) -> Transform2D<S> {
Transform2D::new(self.x(), self.y(), self.angle())
}
pub fn from_transform_2d(
geometry_id: GeometryId,
instance: usize,
transform: &Transform2D<S>,
) -> Self {
Self::new_2d(
geometry_id,
instance,
transform.tx,
transform.ty,
transform.angle,
)
}
pub fn to_transform_3d(&self) -> Transform3D<S> {
let z = self.position.get(2).copied().unwrap_or(S::zero());
let rx = self.rotation.first().copied().unwrap_or(S::zero());
let ry = self.rotation.get(1).copied().unwrap_or(S::zero());
let rz = self.rotation.get(2).copied().unwrap_or(S::zero());
Transform3D::new(self.x(), self.y(), z, rx, ry, rz)
}
pub fn from_transform_3d(
geometry_id: GeometryId,
instance: usize,
transform: &Transform3D<S>,
) -> Self {
Self::new_3d(
geometry_id,
instance,
transform.tx,
transform.ty,
transform.tz,
transform.rx,
transform.ry,
transform.rz,
)
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PlacementStats {
pub count: usize,
pub mirrored_count: usize,
pub rotation_distribution: std::collections::HashMap<usize, usize>,
pub boundary_distribution: std::collections::HashMap<usize, usize>,
}
impl PlacementStats {
pub fn from_placements<S>(placements: &[Placement<S>]) -> Self {
let mut stats = Self {
count: placements.len(),
..Default::default()
};
for p in placements {
if p.mirrored {
stats.mirrored_count += 1;
}
if let Some(rot_idx) = p.rotation_index {
*stats.rotation_distribution.entry(rot_idx).or_insert(0) += 1;
}
*stats
.boundary_distribution
.entry(p.boundary_index)
.or_insert(0) += 1;
}
stats
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_placement_2d() {
let p = Placement::new_2d("test".to_string(), 0, 10.0, 20.0, 0.5);
assert!(p.is_2d());
assert!(!p.is_3d());
assert_eq!(p.x(), 10.0);
assert_eq!(p.y(), 20.0);
assert_eq!(p.angle(), 0.5);
}
#[test]
fn test_placement_3d() {
let p = Placement::new_3d("test".to_string(), 0, 10.0, 20.0, 30.0, 0.1, 0.2, 0.3);
assert!(p.is_3d());
assert!(!p.is_2d());
assert_eq!(p.z(), Some(30.0));
}
#[test]
fn test_transform_conversion() {
let p = Placement::new_2d("test".to_string(), 0, 10.0_f64, 20.0, 0.5);
let t = p.to_transform_2d();
assert_eq!(t.tx, 10.0);
assert_eq!(t.ty, 20.0);
assert_eq!(t.angle, 0.5);
}
#[test]
fn test_placement_stats() {
let placements = vec![
Placement::new_2d("a".to_string(), 0, 0.0, 0.0, 0.0).with_rotation_index(0),
Placement::new_2d("b".to_string(), 0, 0.0, 0.0, 0.0)
.with_rotation_index(1)
.with_mirrored(true),
Placement::new_2d("c".to_string(), 0, 0.0, 0.0, 0.0)
.with_rotation_index(0)
.with_boundary(1),
];
let stats = PlacementStats::from_placements(&placements);
assert_eq!(stats.count, 3);
assert_eq!(stats.mirrored_count, 1);
assert_eq!(stats.rotation_distribution.get(&0), Some(&2));
assert_eq!(stats.rotation_distribution.get(&1), Some(&1));
}
}