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;
pub fn derive_placement_transform(
model: &Model,
units: &UnitScale,
placement: EntityId,
expression: &PointByDistance,
evaluator: &dyn CurveEvaluator,
) -> GeometryResult<Transform> {
let curve = basis_curve3(model, units, placement, expression.basis_curve)?;
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,
) -> 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",
}
})
}
other => Err(GeometryError::Unsupported {
entity: placement,
type_name: other.to_owned(),
detail: "deriving a placement frame needs an alignment centreline 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 { .. } => {
"the evaluator cannot measure distance on this curve family"
}
GeomError::Degenerate { .. } => {
"roll is undefined here: the curve tangent is parallel to the up reference"
}
_ => "the evaluator refused this placement",
}
}