use std::collections::HashSet;
use crate::interaction::selection::NodeId;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum SubObjectRef {
Face(u32),
Vertex(u32),
Edge(u32),
Point(u32),
Voxel(u32),
Cell(u32),
Splat(u32),
Instance(u32),
Segment(u32),
Strip(u32),
}
impl SubObjectRef {
pub fn is_face(&self) -> bool {
matches!(self, Self::Face(_))
}
pub fn is_point(&self) -> bool {
matches!(self, Self::Point(_))
}
pub fn is_vertex(&self) -> bool {
matches!(self, Self::Vertex(_))
}
pub fn is_edge(&self) -> bool {
matches!(self, Self::Edge(_))
}
pub fn is_voxel(&self) -> bool {
matches!(self, Self::Voxel(_))
}
pub fn is_cell(&self) -> bool {
matches!(self, Self::Cell(_))
}
pub fn index(&self) -> u32 {
match *self {
Self::Face(i)
| Self::Vertex(i)
| Self::Edge(i)
| Self::Point(i)
| Self::Voxel(i)
| Self::Cell(i)
| Self::Splat(i)
| Self::Instance(i)
| Self::Segment(i)
| Self::Strip(i) => i,
}
}
pub fn from_feature_id(f: parry3d::shape::FeatureId) -> Option<Self> {
match f {
parry3d::shape::FeatureId::Face(i) => Some(Self::Face(i)),
parry3d::shape::FeatureId::Vertex(i) => Some(Self::Vertex(i)),
parry3d::shape::FeatureId::Edge(i) => Some(Self::Edge(i)),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SubSelection {
selected: HashSet<(NodeId, SubObjectRef)>,
primary: Option<(NodeId, SubObjectRef)>,
version: u64,
}
impl SubSelection {
pub fn new() -> Self {
Self::default()
}
pub fn version(&self) -> u64 {
self.version
}
pub fn select_one(&mut self, object_id: NodeId, sub: SubObjectRef) {
self.selected.clear();
self.selected.insert((object_id, sub));
self.primary = Some((object_id, sub));
self.version = self.version.wrapping_add(1);
}
pub fn toggle(&mut self, object_id: NodeId, sub: SubObjectRef) {
let key = (object_id, sub);
if self.selected.contains(&key) {
self.selected.remove(&key);
if self.primary == Some(key) {
self.primary = self.selected.iter().next().copied();
}
} else {
self.selected.insert(key);
self.primary = Some(key);
}
self.version = self.version.wrapping_add(1);
}
pub fn add(&mut self, object_id: NodeId, sub: SubObjectRef) {
self.selected.insert((object_id, sub));
self.primary = Some((object_id, sub));
self.version = self.version.wrapping_add(1);
}
pub fn remove(&mut self, object_id: NodeId, sub: SubObjectRef) {
let key = (object_id, sub);
self.selected.remove(&key);
if self.primary == Some(key) {
self.primary = self.selected.iter().next().copied();
}
self.version = self.version.wrapping_add(1);
}
pub fn clear(&mut self) {
self.selected.clear();
self.primary = None;
self.version = self.version.wrapping_add(1);
}
pub fn extend(&mut self, items: impl IntoIterator<Item = (NodeId, SubObjectRef)>) {
let mut last = None;
for item in items {
self.selected.insert(item);
last = Some(item);
}
if let Some(item) = last {
self.primary = Some(item);
}
self.version = self.version.wrapping_add(1);
}
pub fn extend_from_rect_pick(&mut self, result: &crate::interaction::picking::RectPickResult) {
for (&object_id, subs) in &result.hits {
for &sub in subs {
self.selected.insert((object_id, sub));
self.primary = Some((object_id, sub));
}
}
self.version = self.version.wrapping_add(1);
}
pub fn contains(&self, object_id: NodeId, sub: SubObjectRef) -> bool {
self.selected.contains(&(object_id, sub))
}
pub fn primary(&self) -> Option<(NodeId, SubObjectRef)> {
self.primary
}
pub fn iter(&self) -> impl Iterator<Item = &(NodeId, SubObjectRef)> {
self.selected.iter()
}
pub fn for_object(&self, object_id: NodeId) -> impl Iterator<Item = SubObjectRef> + '_ {
self.selected
.iter()
.filter(move |(id, _)| *id == object_id)
.map(|(_, sub)| *sub)
}
pub fn len(&self) -> usize {
self.selected.len()
}
pub fn is_empty(&self) -> bool {
self.selected.is_empty()
}
pub fn face_count(&self) -> usize {
self.selected.iter().filter(|(_, s)| s.is_face()).count()
}
pub fn point_count(&self) -> usize {
self.selected.iter().filter(|(_, s)| s.is_point()).count()
}
pub fn vertex_count(&self) -> usize {
self.selected.iter().filter(|(_, s)| s.is_vertex()).count()
}
pub fn voxel_count(&self) -> usize {
self.selected.iter().filter(|(_, s)| s.is_voxel()).count()
}
pub fn cell_count(&self) -> usize {
self.selected.iter().filter(|(_, s)| s.is_cell()).count()
}
}
pub struct VolumeSelectionInfo {
pub dims: [u32; 3],
pub bbox_min: [f32; 3],
pub bbox_max: [f32; 3],
pub model: [[f32; 4]; 4],
}
pub struct PolylineSelectionInfo {
pub positions: Vec<[f32; 3]>,
pub strip_lengths: Vec<u32>,
}
pub struct CellSelectionInfo {
pub positions: Vec<[f32; 3]>,
pub cells: Vec<[u32; 8]>,
}
pub struct SubSelectionRef {
pub(crate) items: Vec<(NodeId, SubObjectRef)>,
pub(crate) mesh_lookup: std::collections::HashMap<u64, (Vec<[f32; 3]>, Vec<u32>)>,
pub(crate) model_matrices: std::collections::HashMap<u64, glam::Mat4>,
pub(crate) point_positions: std::collections::HashMap<u64, Vec<[f32; 3]>>,
pub(crate) voxel_lookup: std::collections::HashMap<u64, VolumeSelectionInfo>,
pub(crate) cell_lookup: std::collections::HashMap<u64, CellSelectionInfo>,
pub(crate) polyline_lookup: std::collections::HashMap<u64, PolylineSelectionInfo>,
pub(crate) curve_family_lookup: std::collections::HashMap<u64, PolylineSelectionInfo>,
pub version: u64,
}
impl SubSelectionRef {
pub fn new(
sub_selection: &SubSelection,
mesh_lookup: std::collections::HashMap<u64, (Vec<[f32; 3]>, Vec<u32>)>,
model_matrices: std::collections::HashMap<u64, glam::Mat4>,
point_positions: std::collections::HashMap<u64, Vec<[f32; 3]>>,
) -> Self {
Self {
items: sub_selection.iter().map(|(n, s)| (*n, *s)).collect(),
mesh_lookup,
model_matrices,
point_positions,
voxel_lookup: std::collections::HashMap::new(),
cell_lookup: std::collections::HashMap::new(),
polyline_lookup: std::collections::HashMap::new(),
curve_family_lookup: std::collections::HashMap::new(),
version: sub_selection.version(),
}
}
pub fn with_voxels(
mut self,
lookup: std::collections::HashMap<u64, VolumeSelectionInfo>,
) -> Self {
self.voxel_lookup = lookup;
self
}
pub fn with_cells(mut self, lookup: std::collections::HashMap<u64, CellSelectionInfo>) -> Self {
self.cell_lookup = lookup;
self
}
pub fn with_polylines(
mut self,
lookup: std::collections::HashMap<u64, PolylineSelectionInfo>,
) -> Self {
self.polyline_lookup = lookup;
self
}
pub fn with_curve_families(
mut self,
lookup: std::collections::HashMap<u64, PolylineSelectionInfo>,
) -> Self {
self.curve_family_lookup = lookup;
self
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interaction::picking::RectPickResult;
#[test]
fn sub_object_ref_kind_checks() {
assert!(SubObjectRef::Face(0).is_face());
assert!(!SubObjectRef::Face(0).is_point());
assert!(!SubObjectRef::Face(0).is_vertex());
assert!(!SubObjectRef::Face(0).is_edge());
assert!(SubObjectRef::Point(1).is_point());
assert!(SubObjectRef::Vertex(2).is_vertex());
assert!(SubObjectRef::Edge(3).is_edge());
}
#[test]
fn sub_object_ref_index() {
assert_eq!(SubObjectRef::Face(7).index(), 7);
assert_eq!(SubObjectRef::Vertex(42).index(), 42);
assert_eq!(SubObjectRef::Edge(0).index(), 0);
assert_eq!(SubObjectRef::Point(99).index(), 99);
}
#[test]
fn sub_object_ref_from_feature_id() {
use parry3d::shape::FeatureId;
assert_eq!(
SubObjectRef::from_feature_id(FeatureId::Face(3)),
Some(SubObjectRef::Face(3))
);
assert_eq!(
SubObjectRef::from_feature_id(FeatureId::Vertex(1)),
Some(SubObjectRef::Vertex(1))
);
assert_eq!(
SubObjectRef::from_feature_id(FeatureId::Edge(2)),
Some(SubObjectRef::Edge(2))
);
assert_eq!(SubObjectRef::from_feature_id(FeatureId::Unknown), None);
}
#[test]
fn sub_object_ref_hashable() {
let mut set = std::collections::HashSet::new();
set.insert(SubObjectRef::Face(0));
set.insert(SubObjectRef::Face(0)); set.insert(SubObjectRef::Face(1));
set.insert(SubObjectRef::Point(0)); assert_eq!(set.len(), 3);
}
#[test]
fn sub_selection_select_one_clears_others() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
sel.add(1, SubObjectRef::Face(1));
sel.select_one(1, SubObjectRef::Face(5));
assert_eq!(sel.len(), 1);
assert!(sel.contains(1, SubObjectRef::Face(5)));
assert!(!sel.contains(1, SubObjectRef::Face(0)));
}
#[test]
fn sub_selection_toggle() {
let mut sel = SubSelection::new();
sel.toggle(1, SubObjectRef::Face(0));
assert!(sel.contains(1, SubObjectRef::Face(0)));
sel.toggle(1, SubObjectRef::Face(0));
assert!(!sel.contains(1, SubObjectRef::Face(0)));
assert!(sel.is_empty());
}
#[test]
fn sub_selection_add_preserves_others() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
sel.add(1, SubObjectRef::Face(1));
assert_eq!(sel.len(), 2);
assert!(sel.contains(1, SubObjectRef::Face(0)));
assert!(sel.contains(1, SubObjectRef::Face(1)));
}
#[test]
fn sub_selection_remove() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
sel.add(1, SubObjectRef::Face(1));
sel.remove(1, SubObjectRef::Face(0));
assert!(!sel.contains(1, SubObjectRef::Face(0)));
assert_eq!(sel.len(), 1);
}
#[test]
fn sub_selection_clear() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
sel.add(2, SubObjectRef::Point(3));
sel.clear();
assert!(sel.is_empty());
assert_eq!(sel.primary(), None);
}
#[test]
fn sub_selection_primary_tracks_last() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
assert_eq!(sel.primary(), Some((1, SubObjectRef::Face(0))));
sel.add(2, SubObjectRef::Point(5));
assert_eq!(sel.primary(), Some((2, SubObjectRef::Point(5))));
}
#[test]
fn sub_selection_contains() {
let mut sel = SubSelection::new();
sel.add(10, SubObjectRef::Face(3));
assert!(sel.contains(10, SubObjectRef::Face(3)));
assert!(!sel.contains(10, SubObjectRef::Face(4)));
assert!(!sel.contains(99, SubObjectRef::Face(3)));
}
#[test]
fn sub_selection_for_object() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
sel.add(1, SubObjectRef::Face(1));
sel.add(2, SubObjectRef::Face(0));
let obj1: Vec<SubObjectRef> = {
let mut v: Vec<_> = sel.for_object(1).collect();
v.sort_by_key(|s| s.index());
v
};
assert_eq!(obj1, vec![SubObjectRef::Face(0), SubObjectRef::Face(1)]);
let obj2: Vec<SubObjectRef> = sel.for_object(2).collect();
assert_eq!(obj2, vec![SubObjectRef::Face(0)]);
assert_eq!(sel.for_object(99).count(), 0);
}
#[test]
fn sub_selection_version_increments() {
let mut sel = SubSelection::new();
let v0 = sel.version();
sel.add(1, SubObjectRef::Face(0));
assert!(sel.version() > v0);
let v1 = sel.version();
sel.clear();
assert!(sel.version() > v1);
}
#[test]
fn sub_selection_kind_counts() {
let mut sel = SubSelection::new();
sel.add(1, SubObjectRef::Face(0));
sel.add(1, SubObjectRef::Face(1));
sel.add(2, SubObjectRef::Point(0));
sel.add(3, SubObjectRef::Vertex(0));
assert_eq!(sel.face_count(), 2);
assert_eq!(sel.point_count(), 1);
assert_eq!(sel.vertex_count(), 1);
}
#[test]
fn sub_selection_extend() {
let mut sel = SubSelection::new();
sel.extend([
(1, SubObjectRef::Face(0)),
(1, SubObjectRef::Face(1)),
(2, SubObjectRef::Point(3)),
]);
assert_eq!(sel.len(), 3);
assert_eq!(sel.primary(), Some((2, SubObjectRef::Point(3))));
}
#[test]
fn sub_selection_extend_from_rect_pick() {
let mut result = RectPickResult::default();
result
.hits
.insert(10, vec![SubObjectRef::Face(0), SubObjectRef::Face(1)]);
result.hits.insert(20, vec![SubObjectRef::Point(5)]);
let mut sel = SubSelection::new();
sel.extend_from_rect_pick(&result);
assert_eq!(sel.len(), 3);
assert!(sel.contains(10, SubObjectRef::Face(0)));
assert!(sel.contains(10, SubObjectRef::Face(1)));
assert!(sel.contains(20, SubObjectRef::Point(5)));
assert_eq!(sel.face_count(), 2);
assert_eq!(sel.point_count(), 1);
}
}