use crate::engine_state::EngineState;
use crate::scene::{RenderScene, SolidDisplay};
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityKind {
Face,
Edge,
Vertex,
}
impl EntityKind {
pub fn parse(kind: &str) -> Option<Self> {
match kind.to_ascii_lowercase().as_str() {
"face" => Some(Self::Face),
"edge" => Some(Self::Edge),
"vertex" => Some(Self::Vertex),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Face => "face",
Self::Edge => "edge",
Self::Vertex => "vertex",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupState {
All,
Partial,
None,
}
impl GroupState {
pub fn as_str(self) -> &'static str {
match self {
Self::All => "all",
Self::Partial => "partial",
Self::None => "none",
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct EntityVisibility {
hidden_faces: HashSet<usize>,
hidden_edges: HashSet<usize>,
hidden_vertices: HashSet<usize>,
}
impl EntityVisibility {
fn set_of(&self, kind: EntityKind) -> &HashSet<usize> {
match kind {
EntityKind::Face => &self.hidden_faces,
EntityKind::Edge => &self.hidden_edges,
EntityKind::Vertex => &self.hidden_vertices,
}
}
fn set_mut(&mut self, kind: EntityKind) -> &mut HashSet<usize> {
match kind {
EntityKind::Face => &mut self.hidden_faces,
EntityKind::Edge => &mut self.hidden_edges,
EntityKind::Vertex => &mut self.hidden_vertices,
}
}
pub fn is_visible(&self, kind: EntityKind, index: usize) -> bool {
!self.set_of(kind).contains(&index)
}
pub fn any_hidden(&self, kind: EntityKind) -> bool {
!self.set_of(kind).is_empty()
}
pub fn set_visible(&mut self, kind: EntityKind, index: usize, visible: bool) {
if visible {
self.set_mut(kind).remove(&index);
} else {
self.set_mut(kind).insert(index);
}
}
pub fn set_group_visible(&mut self, kind: EntityKind, count: usize, visible: bool) {
let set = self.set_mut(kind);
set.clear();
if !visible {
set.extend(0..count);
}
}
pub fn group_state(&self, kind: EntityKind, count: usize) -> GroupState {
if count == 0 {
return GroupState::All;
}
let hidden = self.set_of(kind).len();
if hidden == 0 {
GroupState::All
} else if hidden >= count {
GroupState::None
} else {
GroupState::Partial
}
}
pub fn is_face_visible(&self, index: usize) -> bool {
self.is_visible(EntityKind::Face, index)
}
pub fn is_edge_visible(&self, index: usize) -> bool {
self.is_visible(EntityKind::Edge, index)
}
pub fn is_vertex_visible(&self, index: usize) -> bool {
self.is_visible(EntityKind::Vertex, index)
}
pub fn any_face_hidden(&self) -> bool {
self.any_hidden(EntityKind::Face)
}
pub fn any_edge_hidden(&self) -> bool {
self.any_hidden(EntityKind::Edge)
}
pub fn any_vertex_hidden(&self) -> bool {
self.any_hidden(EntityKind::Vertex)
}
pub fn all_hidden(&self, kind: EntityKind, count: usize) -> bool {
count > 0 && self.set_of(kind).len() >= count
}
pub fn all_vertices_hidden(&self, count: usize) -> bool {
self.all_hidden(EntityKind::Vertex, count)
}
}
fn entity_count(solid: &SolidDisplay, kind: EntityKind) -> usize {
match kind {
EntityKind::Face => solid.faces.len(),
EntityKind::Edge => solid.edges.len(),
EntityKind::Vertex => solid.vertices.len(),
}
}
impl RenderScene {
pub fn set_entity_visible(
&mut self,
solid: &str,
kind: EntityKind,
index: usize,
visible: bool,
) -> bool {
match self.solid_mut(solid) {
Some(s) => {
s.visibility.set_visible(kind, index, visible);
true
}
None => false,
}
}
pub fn set_group_visible(&mut self, solid: &str, kind: EntityKind, visible: bool) -> bool {
let Some(count) = self.solid(solid).map(|s| entity_count(s, kind)) else {
return false;
};
if let Some(s) = self.solid_mut(solid) {
s.visibility.set_group_visible(kind, count, visible);
}
true
}
pub fn entity_visible(&self, solid: &str, kind: EntityKind, index: usize) -> Option<bool> {
self.solid(solid).map(|s| s.visibility.is_visible(kind, index))
}
pub fn group_visibility(&self, solid: &str, kind: EntityKind) -> Option<GroupState> {
self.solid(solid)
.map(|s| s.visibility.group_state(kind, entity_count(s, kind)))
}
}
impl EngineState {
pub fn set_entity_visible(
&mut self,
solid: &str,
kind: EntityKind,
index: usize,
visible: bool,
) -> bool {
let ok = self.scene.set_entity_visible(solid, kind, index, visible);
if ok {
self.dirty = true;
}
ok
}
pub fn set_group_visible(&mut self, solid: &str, kind: EntityKind, visible: bool) -> bool {
let ok = self.scene.set_group_visible(solid, kind, visible);
if ok {
self.dirty = true;
}
ok
}
pub fn entity_visible(&self, solid: &str, kind: EntityKind, index: usize) -> Option<bool> {
self.scene.entity_visible(solid, kind, index)
}
pub fn group_visibility(&self, solid: &str, kind: EntityKind) -> Option<GroupState> {
self.scene.group_visibility(solid, kind)
}
pub fn scene_visibility_json(&self) -> String {
let kinds = [
("faces", EntityKind::Face),
("edges", EntityKind::Edge),
("vertices", EntityKind::Vertex),
];
let solids: Vec<serde_json::Value> = self
.scene
.solids()
.iter()
.map(|solid| {
let mut obj = serde_json::Map::new();
obj.insert("name".into(), serde_json::json!(solid.name));
obj.insert("visible".into(), serde_json::json!(solid.visible));
for (key, kind) in kinds {
let count = entity_count(solid, kind);
let states: Vec<bool> =
(0..count).map(|i| solid.visibility.is_visible(kind, i)).collect();
obj.insert(
key.into(),
serde_json::json!({
"group": solid.visibility.group_state(kind, count).as_str(),
"states": states,
}),
);
}
serde_json::Value::Object(obj)
})
.collect();
serde_json::Value::Array(solids).to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cube_history(name: &str) -> String {
serde_json::json!({
"expressions": "",
"configurator": {},
"features": [{
"type": "P.CU",
"inputParams": {
"id": name,
"sizeX": 10.0, "sizeY": 10.0, "sizeZ": 10.0,
"transform": {
"position": [0.0, 0.0, 0.0],
"rotationEuler": [0.0, 0.0, 0.0],
"scale": [1.0, 1.0, 1.0]
},
"boolean": { "targets": [], "operation": "NONE" }
},
"persistentData": {}
}]
})
.to_string()
}
#[test]
fn entity_visibility_hide_show_and_group_tristate() {
let mut vis = EntityVisibility::default();
assert!(vis.is_visible(EntityKind::Face, 0));
assert!(!vis.any_hidden(EntityKind::Face));
assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::All);
vis.set_visible(EntityKind::Face, 2, false);
assert!(!vis.is_visible(EntityKind::Face, 2));
assert!(vis.is_visible(EntityKind::Face, 3));
assert!(vis.any_hidden(EntityKind::Face));
assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::Partial);
vis.set_visible(EntityKind::Face, 2, true);
assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::All);
vis.set_group_visible(EntityKind::Face, 6, false);
assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::None);
assert!(!vis.is_visible(EntityKind::Face, 5));
vis.set_visible(EntityKind::Face, 5, true);
assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::Partial);
assert_eq!(vis.group_state(EntityKind::Edge, 12), GroupState::All);
}
#[test]
fn engine_per_entity_and_group_visibility_through_scene() {
let mut engine = EngineState::new();
engine.set_history_json(&cube_history("Box")).unwrap();
assert!(engine.set_entity_visible("Box", EntityKind::Face, 0, false));
assert_eq!(engine.entity_visible("Box", EntityKind::Face, 0), Some(false));
assert_eq!(engine.entity_visible("Box", EntityKind::Face, 1), Some(true));
assert_eq!(
engine.group_visibility("Box", EntityKind::Face),
Some(GroupState::Partial)
);
assert!(engine.set_group_visible("Box", EntityKind::Face, false));
assert_eq!(
engine.group_visibility("Box", EntityKind::Face),
Some(GroupState::None)
);
assert_eq!(
engine.group_visibility("Box", EntityKind::Edge),
Some(GroupState::All)
);
assert!(engine.set_group_visible("Box", EntityKind::Face, true));
assert_eq!(
engine.group_visibility("Box", EntityKind::Face),
Some(GroupState::All)
);
assert!(!engine.set_entity_visible("Nope", EntityKind::Edge, 0, false));
assert_eq!(engine.entity_visible("Nope", EntityKind::Edge, 0), None);
assert_eq!(engine.group_visibility("Nope", EntityKind::Edge), None);
}
#[test]
fn scene_visibility_json_reports_states_and_group() {
let mut engine = EngineState::new();
engine.set_history_json(&cube_history("Box")).unwrap();
engine.set_entity_visible("Box", EntityKind::Edge, 3, false);
let json: serde_json::Value =
serde_json::from_str(&engine.scene_visibility_json()).unwrap();
let solid = &json.as_array().unwrap()[0];
assert_eq!(solid["name"], "Box");
assert_eq!(solid["visible"], true);
assert_eq!(solid["faces"]["group"], "all");
assert_eq!(solid["faces"]["states"].as_array().unwrap().len(), 6);
assert_eq!(solid["edges"]["group"], "partial");
assert_eq!(solid["edges"]["states"][3], false);
assert_eq!(solid["edges"]["states"][2], true);
}
#[test]
fn entity_kind_token_roundtrips() {
for k in [EntityKind::Face, EntityKind::Edge, EntityKind::Vertex] {
assert_eq!(EntityKind::parse(k.as_str()), Some(k));
}
assert_eq!(EntityKind::parse("FACE"), Some(EntityKind::Face));
assert_eq!(EntityKind::parse("solid"), None);
}
}