use axiolid_contracts::GeomError;
use axiolid_core::Frame3;
use axiolid_curve::Curve3;
use axiolid_curve_evaluate_contract::{CurveEvaluator, CurveMeasure as KernelMeasure};
use ifc_alignment::{AlignmentUnits, CurveMeasure, PointByDistance};
use ifc_model::{EntityId, Model};
use crate::error::{GeometryError, GeometryResult};
use crate::transform::Transform;
use crate::units::UnitScale;
mod polyline;
pub fn derive_placement_transform(
model: &Model,
units: &UnitScale,
placement: EntityId,
expression: &PointByDistance,
evaluator: &dyn CurveEvaluator,
) -> GeometryResult<Transform> {
let parameter_requested = matches!(expression.distance_along, CurveMeasure::Parameter(_));
let curve = basis_curve3(
model,
units,
placement,
expression.basis_curve,
parameter_requested,
)?;
let at = match expression.distance_along {
CurveMeasure::Length(value) => KernelMeasure::Distance(units.length(value)),
CurveMeasure::Parameter(value) => KernelMeasure::Parameter(value),
};
let frame = evaluator
.frame_at(&curve, at)
.map_err(|error| GeometryError::Unsupported {
entity: placement,
type_name: "IFCLINEARPLACEMENT".into(),
detail: refusal_detail(&error),
})?;
Ok(offset_frame(frame, expression, units))
}
fn basis_curve3(
model: &Model,
units: &UnitScale,
placement: EntityId,
basis: EntityId,
parameter_requested: bool,
) -> GeometryResult<Curve3> {
let entity = model.get(basis).ok_or(GeometryError::MissingEntity {
referrer: placement,
missing: basis,
})?;
let alignment_units = AlignmentUnits {
length_to_metres: units.length_to_metres,
angle_to_radians: units.angle_to_radians,
};
match entity.type_name.as_ref() {
"IFCALIGNMENT" | "IFCGRADIENTCURVE" => {
ifc_alignment::gradient_curve3(model, basis, alignment_units).map_err(|_error| {
GeometryError::Unsupported {
entity: placement,
type_name: entity.type_name.to_string(),
detail: "basis curve does not compose an exact centreline",
}
})
}
"IFCPOLYLINE" => polyline::polyline(model, units, basis, entity),
"IFCINDEXEDPOLYCURVE" => {
polyline::indexed_polycurve(model, units, basis, entity, parameter_requested)
}
other => Err(GeometryError::Unsupported {
entity: placement,
type_name: other.to_owned(),
detail: "deriving a placement frame needs an alignment centreline or a \
straight-segment polyline as basis curve",
}),
}
}
fn offset_frame(frame: Frame3, expression: &PointByDistance, units: &UnitScale) -> Transform {
let lateral = units.length(expression.offset_lateral.unwrap_or(0.0));
let vertical = units.length(expression.offset_vertical.unwrap_or(0.0));
let longitudinal = units.length(expression.offset_longitudinal.unwrap_or(0.0));
let origin = [
frame.origin.x + frame.z.x * lateral + frame.y.x * vertical + frame.x.x * longitudinal,
frame.origin.y + frame.z.y * lateral + frame.y.y * vertical + frame.x.y * longitudinal,
frame.origin.z + frame.z.z * lateral + frame.y.z * vertical + frame.x.z * longitudinal,
];
Transform {
basis: [
[frame.x.x, frame.x.y, frame.x.z],
[frame.y.x, frame.y.y, frame.y.z],
[frame.z.x, frame.z.y, frame.z.z],
],
origin,
}
}
fn refusal_detail(error: &GeomError) -> &'static str {
match error {
GeomError::Unsupported { .. } | GeomError::UnsupportedInput { .. } => {
"the evaluator cannot measure distance on this curve family"
}
GeomError::InvalidInput(_) => {
"the evaluator rejected the measure: it lies off the curve, or roll is \
undefined because the curve tangent is parallel to the up reference"
}
GeomError::Degenerate(_) => "the evaluator found the basis curve degenerate there",
_ => "the evaluator refused this placement",
}
}