use std::path::PathBuf;
use crate::coords::CoordSystem;
use crate::error::{Error, Result};
use crate::grid::RectilinearGrid;
use crate::math::{BoundingBox, Vec3};
use crate::param::{ParameterSet, Scalar};
use crate::primitives::{Primitive, Shape};
use crate::properties::{MaterialQuantity, Property, PropertyKind, TypeMask};
#[derive(Debug, Clone, PartialEq)]
pub struct BackgroundMaterial {
pub epsilon: Scalar,
pub mue: Scalar,
pub kappa: Scalar,
pub sigma: Scalar,
}
impl Default for BackgroundMaterial {
fn default() -> Self {
BackgroundMaterial {
epsilon: Scalar::new(1.0),
mue: Scalar::new(1.0),
kappa: Scalar::new(0.0),
sigma: Scalar::new(0.0),
}
}
}
impl BackgroundMaterial {
fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.epsilon.update(p)?;
self.mue.update(p)?;
self.kappa.update(p)?;
self.sigma.update(p)
}
pub fn value(&self, q: MaterialQuantity) -> f64 {
match q {
MaterialQuantity::Epsilon => self.epsilon.value(),
MaterialQuantity::Mue => self.mue.value(),
MaterialQuantity::Kappa => self.kappa.value(),
MaterialQuantity::Sigma => self.sigma.value(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PropertyId(pub u32);
#[derive(Debug, Clone, Copy)]
pub struct Hit<'a> {
pub property: &'a Property,
pub primitive: &'a Primitive,
pub primitive_index: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Medium {
pub epsilon: [f64; 3],
pub mue: [f64; 3],
pub kappa: [f64; 3],
pub sigma: [f64; 3],
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ContinuousStructure {
properties: Vec<Property>,
next_id: u32,
pub grid: RectilinearGrid,
pub parameters: ParameterSet,
pub background: BackgroundMaterial,
pub coord_system: CoordSystem,
pub base_dir: Option<PathBuf>,
pub extra_attributes: Vec<(String, String)>,
}
impl ContinuousStructure {
pub fn new() -> Self {
Self::default()
}
pub fn add_property(&mut self, mut p: Property) -> PropertyId {
self.next_id += 1;
p.id = self.next_id;
self.properties.push(p);
PropertyId(self.next_id)
}
pub fn add_primitive(
&mut self,
id: PropertyId,
prim: impl Into<Primitive>,
) -> Result<&mut Primitive> {
let p = self
.property_mut(id)
.ok_or_else(|| Error::Invalid(format!("no property {id:?}")))?;
Ok(p.add_primitive(prim))
}
pub fn remove_property(&mut self, id: PropertyId) -> Option<Property> {
let i = self.properties.iter().position(|p| p.id == id.0)?;
let mut p = self.properties.remove(i);
p.id = 0;
Some(p)
}
pub fn properties(&self) -> &[Property] {
&self.properties
}
pub fn properties_mut(&mut self) -> impl Iterator<Item = &mut Property> {
self.properties.iter_mut()
}
pub fn properties_of(&self, mask: impl Into<TypeMask>) -> impl Iterator<Item = &Property> {
let mask = mask.into();
self.properties
.iter()
.filter(move |p| mask.contains(p.property_type()))
}
pub fn property(&self, id: PropertyId) -> Option<&Property> {
self.properties.iter().find(|p| p.id == id.0)
}
pub fn property_mut(&mut self, id: PropertyId) -> Option<&mut Property> {
self.properties.iter_mut().find(|p| p.id == id.0)
}
pub fn property_by_name(&self, name: &str) -> Option<&Property> {
self.properties.iter().find(|p| p.name == name)
}
pub fn property_by_name_mut(&mut self, name: &str) -> Option<&mut Property> {
self.properties.iter_mut().find(|p| p.name == name)
}
pub fn property_id(&self, name: &str) -> Option<PropertyId> {
self.property_by_name(name).map(|p| PropertyId(p.id))
}
pub fn primitives(&self) -> impl Iterator<Item = (&Property, &Primitive)> {
self.properties
.iter()
.flat_map(|p| p.primitives.iter().map(move |q| (p, q)))
}
pub fn update(&mut self) -> Result<()> {
let params = &self.parameters;
self.background.update(params)?;
for p in &mut self.properties {
p.update(params)?;
for prim in &mut p.primitives {
if let Shape::PolyhedronReader(r) = &mut prim.shape
&& r.mesh.is_none()
{
r.load(self.base_dir.as_deref())?;
}
}
}
Ok(())
}
pub fn property_at(
&self,
p: Vec3,
cs: CoordSystem,
mask: impl Into<TypeMask>,
) -> Option<Hit<'_>> {
let mask = mask.into();
let mut best: Option<Hit<'_>> = None;
for prop in self
.properties
.iter()
.filter(|q| mask.contains(q.property_type()))
{
for (i, prim) in prop.primitives.iter().enumerate() {
if best.is_some_and(|b| b.primitive.priority > prim.priority) {
continue;
}
if prim.contains_in(p, cs, self.coord_system) {
best = Some(Hit {
property: prop,
primitive: prim,
primitive_index: i,
});
}
}
}
best
}
pub fn properties_at(
&self,
p: Vec3,
cs: CoordSystem,
mask: impl Into<TypeMask>,
) -> Vec<Hit<'_>> {
let mask = mask.into();
let mut hits: Vec<Hit<'_>> = Vec::new();
for prop in self
.properties
.iter()
.filter(|q| mask.contains(q.property_type()))
{
for (i, prim) in prop.primitives.iter().enumerate() {
if prim.contains_in(p, cs, self.coord_system) {
hits.push(Hit {
property: prop,
primitive: prim,
primitive_index: i,
});
}
}
}
hits.reverse();
hits.sort_by_key(|h| core::cmp::Reverse(h.primitive.priority));
hits
}
pub fn medium_at(&self, p: Vec3, cs: CoordSystem) -> Option<Medium> {
let hit = self.property_at(
p,
cs,
TypeMask::MATERIALS | TypeMask::of(crate::PropertyType::Metal),
);
let cart = cs.to_cartesian(p);
match hit.map(|h| &h.property.kind) {
Some(PropertyKind::Metal) => None,
Some(k) => {
let m = k.material().expect("mask only matches materials");
Some(Medium {
epsilon: m.values_at(MaterialQuantity::Epsilon, cart),
mue: m.values_at(MaterialQuantity::Mue, cart),
kappa: m.values_at(MaterialQuantity::Kappa, cart),
sigma: m.values_at(MaterialQuantity::Sigma, cart),
})
}
None => {
let b = &self.background;
Some(Medium {
epsilon: [b.epsilon.value(); 3],
mue: [b.mue.value(); 3],
kappa: [b.kappa.value(); 3],
sigma: [b.sigma.value(); 3],
})
}
}
}
pub fn bounding_box(&self, cs: CoordSystem) -> BoundingBox {
self.primitives()
.map(|(_, q)| q.bounding_box_in(cs, self.coord_system))
.filter(|b| b.min.is_finite() && b.max.is_finite())
.fold(BoundingBox::EMPTY, |a, b| a.union(&b))
}
pub fn primitives_in<'a>(
&'a self,
region: &'a BoundingBox,
cs: CoordSystem,
mask: impl Into<TypeMask>,
) -> impl Iterator<Item = (&'a Property, &'a Primitive)> + 'a {
let mask = mask.into();
let dcs = self.coord_system;
self.primitives()
.filter(move |(p, _)| mask.contains(p.property_type()))
.filter(move |(_, q)| q.bounding_box_in(cs, dcs).intersects(region))
}
pub fn index(&self, cs: CoordSystem, mask: impl Into<TypeMask>) -> SpatialIndex<'_> {
SpatialIndex::new(self, cs, mask.into())
}
pub fn validate(&self) -> Vec<String> {
let mut w = Vec::new();
for (i, p) in self.properties.iter().enumerate() {
if self.properties[..i].iter().any(|q| q.name == p.name) {
w.push(format!("duplicate property name {:?}", p.name));
}
if p.primitives.is_empty() {
w.push(format!("property {:?} has no primitives", p.name));
}
for (k, q) in p.primitives.iter().enumerate() {
let b = q.bounding_box(self.coord_system);
if b.is_empty() {
w.push(format!(
"property {:?}: primitive #{k} ({}) is empty",
p.name,
q.shape.type_name()
));
}
}
}
w
}
}
struct Entry<'a> {
bbox: BoundingBox,
hit: Hit<'a>,
}
pub struct SpatialIndex<'a> {
entries: Vec<Entry<'a>>,
cs: CoordSystem,
default_cs: CoordSystem,
}
impl<'a> SpatialIndex<'a> {
fn new(s: &'a ContinuousStructure, cs: CoordSystem, mask: TypeMask) -> Self {
let mut entries: Vec<Entry<'a>> = Vec::new();
for prop in s
.properties
.iter()
.filter(|q| mask.contains(q.property_type()))
{
for (i, prim) in prop.primitives.iter().enumerate() {
let bbox = prim
.bounding_box(s.coord_system)
.expanded(1e-9 * prim.bounding_box(s.coord_system).size().norm());
entries.push(Entry {
bbox,
hit: Hit {
property: prop,
primitive: prim,
primitive_index: i,
},
});
}
}
entries.reverse();
entries.sort_by_key(|e| core::cmp::Reverse(e.hit.primitive.priority));
SpatialIndex {
entries,
cs,
default_cs: s.coord_system,
}
}
pub fn query(&self, p: Vec3) -> Option<Hit<'a>> {
let c = self.cs.to_cartesian(p);
self.entries
.iter()
.filter(|e| e.bbox.contains(c))
.find(|e| {
e.hit
.primitive
.contains_in(c, CoordSystem::Cartesian, self.default_cs)
})
.map(|e| e.hit)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::{BoxShape, Sphere};
use crate::properties::Material;
fn model() -> ContinuousStructure {
let mut s = ContinuousStructure::new();
let sub = s.add_property(Property::material("sub", Material::new(4.0, 0.0)));
s.add_primitive(
sub,
Primitive::new(BoxShape::new([0.0, 0.0, 0.0], [10.0, 10.0, 1.0])).with_priority(1),
)
.unwrap();
let m = s.add_property(Property::metal("pec"));
s.add_primitive(
m,
Primitive::new(Sphere::new([5.0, 5.0, 1.0], 0.5)).with_priority(5),
)
.unwrap();
s
}
#[test]
fn priority() {
let s = model();
let h = s
.property_at(
Vec3::new(5.0, 5.0, 0.8),
CoordSystem::Cartesian,
TypeMask::ANY,
)
.unwrap();
assert_eq!(h.property.name, "pec");
let h = s
.property_at(
Vec3::new(1.0, 1.0, 0.5),
CoordSystem::Cartesian,
TypeMask::ANY,
)
.unwrap();
assert_eq!(h.property.name, "sub");
assert!(
s.property_at(
Vec3::new(1.0, 1.0, 2.0),
CoordSystem::Cartesian,
TypeMask::ANY
)
.is_none()
);
assert_eq!(
s.properties_at(
Vec3::new(5.0, 5.0, 0.8),
CoordSystem::Cartesian,
TypeMask::ANY
)
.len(),
2
);
let idx = s.index(CoordSystem::Cartesian, TypeMask::ANY);
for p in [
Vec3::new(5.0, 5.0, 0.8),
Vec3::new(1.0, 1.0, 0.5),
Vec3::new(1.0, 1.0, 2.0),
] {
assert_eq!(
idx.query(p).map(|h| &h.property.name),
s.property_at(p, CoordSystem::Cartesian, TypeMask::ANY)
.map(|h| &h.property.name)
);
}
}
#[test]
fn media() {
let s = model();
assert_eq!(
s.medium_at(Vec3::new(1.0, 1.0, 0.5), CoordSystem::Cartesian)
.unwrap()
.epsilon,
[4.0; 3]
);
assert_eq!(
s.medium_at(Vec3::new(1.0, 1.0, 5.0), CoordSystem::Cartesian)
.unwrap()
.epsilon,
[1.0; 3]
);
assert!(
s.medium_at(Vec3::new(5.0, 5.0, 1.0), CoordSystem::Cartesian)
.is_none()
);
let b = s.bounding_box(CoordSystem::Cartesian);
assert_eq!(b.max, Vec3::new(10.0, 10.0, 1.5));
}
#[test]
fn parametric() {
let mut s = ContinuousStructure::new();
s.parameters.set("w", 2.0);
let id = s.add_property(Property::metal("m"));
s.add_primitive(
id,
BoxShape {
start: [0.0, 0.0, 0.0].into(),
stop: crate::ScalarPoint(["w".into(), "w*2".into(), 1.0.into()]),
},
)
.unwrap();
s.update().unwrap();
assert!(
s.property_at(
Vec3::new(1.9, 3.9, 0.5),
CoordSystem::Cartesian,
TypeMask::ANY
)
.is_some()
);
s.parameters.set("w", 1.0);
s.update().unwrap();
assert!(
s.property_at(
Vec3::new(1.9, 3.9, 0.5),
CoordSystem::Cartesian,
TypeMask::ANY
)
.is_none()
);
assert!(s.validate().is_empty());
assert!(s.remove_property(id).is_some());
}
}