use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::GeometryError;
use super::{invalid, require_finite};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SpiralKind {
Clothoid {
constant: f64,
},
Cosine {
cosine: f64,
constant: Option<f64>,
},
Sine {
sine: f64,
linear: Option<f64>,
constant: Option<f64>,
},
SecondOrder {
quadratic: f64,
linear: Option<f64>,
constant: Option<f64>,
},
ThirdOrder {
cubic: f64,
quadratic: Option<f64>,
linear: Option<f64>,
constant: Option<f64>,
},
SeventhOrder {
septic: f64,
lower: [Option<f64>; 7],
},
}
impl SpiralKind {
fn entity_name(self) -> &'static str {
match self {
Self::Clothoid { .. } => "IFCCLOTHOID",
Self::Cosine { .. } => "IFCCOSINESPIRAL",
Self::Sine { .. } => "IFCSINESPIRAL",
Self::SecondOrder { .. } => "IFCSECONDORDERPOLYNOMIALSPIRAL",
Self::ThirdOrder { .. } => "IFCTHIRDORDERPOLYNOMIALSPIRAL",
Self::SeventhOrder { .. } => "IFCSEVENTHORDERPOLYNOMIALSPIRAL",
}
}
fn leading_attribute(self) -> &'static str {
match self {
Self::Clothoid { .. } => "ClothoidConstant",
Self::Cosine { .. } => "CosineTerm",
Self::Sine { .. } => "SineTerm",
Self::SecondOrder { .. } => "QuadraticTerm",
Self::ThirdOrder { .. } => "CubicTerm",
Self::SeventhOrder { .. } => "SepticTerm",
}
}
fn terms(self) -> (f64, Vec<Option<f64>>) {
match self {
Self::Clothoid { constant } => (constant, Vec::new()),
Self::Cosine { cosine, constant } => (cosine, vec![constant]),
Self::Sine {
sine,
linear,
constant,
} => (sine, vec![linear, constant]),
Self::SecondOrder {
quadratic,
linear,
constant,
} => (quadratic, vec![linear, constant]),
Self::ThirdOrder {
cubic,
quadratic,
linear,
constant,
} => (cubic, vec![quadratic, linear, constant]),
Self::SeventhOrder { septic, lower } => (septic, lower.to_vec()),
}
}
}
pub fn spiral(
tx: &mut Transaction,
position: EntityId,
kind: SpiralKind,
) -> Result<EntityId, GeometryError> {
let entity = kind.entity_name();
let (leading, lower) = kind.terms();
require_finite(entity, kind.leading_attribute(), &[leading])?;
if leading == 0.0 {
return Err(invalid(
entity,
kind.leading_attribute(),
"expected a non-zero leading term",
));
}
let present: Vec<f64> = lower.iter().flatten().copied().collect();
require_finite(entity, "term", &present)?;
let mut attrs = vec![Value::Ref(position), Value::Real(leading)];
attrs.extend(
lower
.into_iter()
.map(|t| t.map_or(Value::Null, Value::Real)),
);
Ok(tx.create(Entity::new(entity, attrs)))
}