use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
use glam::Vec3;
use crate::error::{PolyscopeError, Result};
use crate::gizmo::GizmoConfig;
use crate::group::Group;
use crate::options::Options;
use crate::quantity::Quantity;
use crate::registry::Registry;
use crate::slice_plane::SlicePlane;
pub type FileDropCallback = Box<dyn FnMut(&[std::path::PathBuf]) + Send + Sync>;
#[derive(Debug, Clone)]
pub enum MaterialLoadRequest {
Static { name: String, path: String },
Blendable {
name: String,
filenames: [String; 4],
},
}
static CONTEXT: OnceLock<RwLock<Context>> = OnceLock::new();
pub struct Context {
pub initialized: bool,
pub registry: Registry,
pub groups: HashMap<String, Group>,
pub slice_planes: HashMap<String, SlicePlane>,
pub gizmo_config: GizmoConfig,
pub selected_structure: Option<(String, String)>,
pub selected_slice_plane: Option<String>,
pub options: Options,
pub length_scale: f32,
pub bounding_box: (Vec3, Vec3),
pub floating_quantities: Vec<Box<dyn Quantity>>,
pub file_drop_callback: Option<FileDropCallback>,
pub material_load_queue: Vec<MaterialLoadRequest>,
}
impl Default for Context {
fn default() -> Self {
Self {
initialized: false,
registry: Registry::new(),
groups: HashMap::new(),
slice_planes: HashMap::new(),
gizmo_config: GizmoConfig::default(),
selected_structure: None,
selected_slice_plane: None,
options: Options::default(),
length_scale: 1.0,
bounding_box: (Vec3::ZERO, Vec3::ONE),
floating_quantities: Vec::new(),
file_drop_callback: None,
material_load_queue: Vec::new(),
}
}
}
impl Context {
#[must_use]
pub fn center(&self) -> Vec3 {
(self.bounding_box.0 + self.bounding_box.1) * 0.5
}
pub fn update_extents(&mut self) {
if !self.options.auto_compute_scene_extents {
return;
}
self.recompute_extents();
}
pub fn recompute_extents(&mut self) {
let mut min = Vec3::splat(f32::MAX);
let mut max = Vec3::splat(f32::MIN);
let mut has_extent = false;
for structure in self.registry.iter() {
if let Some((bb_min, bb_max)) = structure.bounding_box() {
min = min.min(bb_min);
max = max.max(bb_max);
has_extent = true;
}
}
if has_extent {
self.bounding_box = (min, max);
self.length_scale = (max - min).length();
if min == max {
let offset_scale = if self.length_scale == 0.0 {
1e-3
} else {
self.length_scale * 1e-3
};
let offset = Vec3::splat(offset_scale / 2.0);
self.bounding_box = (min - offset, max + offset);
}
} else {
self.bounding_box = (Vec3::ZERO, Vec3::ONE);
self.length_scale = 1.0;
}
}
pub fn create_group(&mut self, name: &str) -> &mut Group {
self.groups
.entry(name.to_string())
.or_insert_with(|| Group::new(name))
}
#[must_use]
pub fn get_group(&self, name: &str) -> Option<&Group> {
self.groups.get(name)
}
pub fn get_group_mut(&mut self, name: &str) -> Option<&mut Group> {
self.groups.get_mut(name)
}
pub fn remove_group(&mut self, name: &str) -> Option<Group> {
self.groups.remove(name)
}
#[must_use]
pub fn has_group(&self, name: &str) -> bool {
self.groups.contains_key(name)
}
#[must_use]
pub fn group_names(&self) -> Vec<&str> {
self.groups
.keys()
.map(std::string::String::as_str)
.collect()
}
#[must_use]
pub fn is_structure_visible(&self, structure: &dyn crate::Structure) -> bool {
structure.is_enabled()
&& self.is_structure_visible_in_groups(structure.type_name(), structure.name())
}
#[must_use]
pub fn is_structure_visible_in_groups(&self, type_name: &str, name: &str) -> bool {
for group in self.groups.values() {
if group.contains_structure(type_name, name) {
if !self.is_group_and_ancestors_enabled(group.name()) {
return false;
}
}
}
true
}
fn is_group_and_ancestors_enabled(&self, group_name: &str) -> bool {
let mut current = group_name;
while let Some(group) = self.groups.get(current) {
if !group.is_enabled() {
return false;
}
if let Some(parent) = group.parent_group() {
current = parent;
} else {
break;
}
}
true
}
pub fn add_slice_plane(&mut self, name: &str) -> &mut SlicePlane {
self.slice_planes
.entry(name.to_string())
.or_insert_with(|| SlicePlane::new(name))
}
#[must_use]
pub fn get_slice_plane(&self, name: &str) -> Option<&SlicePlane> {
self.slice_planes.get(name)
}
pub fn get_slice_plane_mut(&mut self, name: &str) -> Option<&mut SlicePlane> {
self.slice_planes.get_mut(name)
}
pub fn remove_slice_plane(&mut self, name: &str) -> Option<SlicePlane> {
self.slice_planes.remove(name)
}
#[must_use]
pub fn has_slice_plane(&self, name: &str) -> bool {
self.slice_planes.contains_key(name)
}
#[must_use]
pub fn slice_plane_names(&self) -> Vec<&str> {
self.slice_planes
.keys()
.map(std::string::String::as_str)
.collect()
}
#[must_use]
pub fn num_slice_planes(&self) -> usize {
self.slice_planes.len()
}
pub fn slice_planes(&self) -> impl Iterator<Item = &SlicePlane> {
self.slice_planes.values()
}
pub fn enabled_slice_planes(&self) -> impl Iterator<Item = &SlicePlane> {
self.slice_planes.values().filter(|sp| sp.is_enabled())
}
pub fn select_structure(&mut self, type_name: &str, name: &str) {
self.selected_slice_plane = None; self.selected_structure = Some((type_name.to_string(), name.to_string()));
}
pub fn deselect_structure(&mut self) {
self.selected_structure = None;
}
#[must_use]
pub fn selected_structure(&self) -> Option<(&str, &str)> {
self.selected_structure
.as_ref()
.map(|(t, n)| (t.as_str(), n.as_str()))
}
#[must_use]
pub fn has_selection(&self) -> bool {
self.selected_structure.is_some()
}
pub fn select_slice_plane(&mut self, name: &str) {
self.selected_structure = None; self.selected_slice_plane = Some(name.to_string());
}
pub fn deselect_slice_plane(&mut self) {
self.selected_slice_plane = None;
}
#[must_use]
pub fn selected_slice_plane(&self) -> Option<&str> {
self.selected_slice_plane.as_deref()
}
#[must_use]
pub fn has_slice_plane_selection(&self) -> bool {
self.selected_slice_plane.is_some()
}
#[must_use]
pub fn gizmo(&self) -> &GizmoConfig {
&self.gizmo_config
}
pub fn gizmo_mut(&mut self) -> &mut GizmoConfig {
&mut self.gizmo_config
}
}
pub fn init_context() -> Result<()> {
let context = RwLock::new(Context::default());
CONTEXT
.set(context)
.map_err(|_| PolyscopeError::AlreadyInitialized)?;
with_context_mut(|ctx| {
ctx.initialized = true;
});
Ok(())
}
pub fn is_initialized() -> bool {
CONTEXT
.get()
.and_then(|lock| lock.read().ok())
.is_some_and(|ctx| ctx.initialized)
}
pub fn with_context<F, R>(f: F) -> R
where
F: FnOnce(&Context) -> R,
{
let lock = CONTEXT.get().expect("polyscope not initialized");
let guard = lock.read().expect("context lock poisoned");
f(&guard)
}
pub fn with_context_mut<F, R>(f: F) -> R
where
F: FnOnce(&mut Context) -> R,
{
let lock = CONTEXT.get().expect("polyscope not initialized");
let mut guard = lock.write().expect("context lock poisoned");
f(&mut guard)
}
pub fn try_with_context<F, R>(f: F) -> Option<R>
where
F: FnOnce(&Context) -> R,
{
let lock = CONTEXT.get()?;
let guard = lock.read().ok()?;
Some(f(&guard))
}
pub fn try_with_context_mut<F, R>(f: F) -> Option<R>
where
F: FnOnce(&mut Context) -> R,
{
let lock = CONTEXT.get()?;
let mut guard = lock.write().ok()?;
Some(f(&mut guard))
}
pub fn shutdown_context() {
if let Some(lock) = CONTEXT.get() {
if let Ok(mut ctx) = lock.write() {
ctx.initialized = false;
ctx.registry.clear();
ctx.groups.clear();
ctx.slice_planes.clear();
ctx.selected_structure = None;
ctx.selected_slice_plane = None;
ctx.floating_quantities.clear();
ctx.material_load_queue.clear();
}
}
}