#include "function_item_evaluator.h" #include "profile_helper.h" #include using namespace ifcopenshell::geometry; std::vector ifcopenshell::geometry::helmert_curve_point(double A0, double A1, double A2, double s) { auto theta = [A0, A1, A2](double t) -> double { auto a0 = A0 ? t / A0 : 0.0; auto a1 = A1 ? A1 * std::pow(t, 2) / (2 * fabs(std::pow(A1, 3))) : 0.0; auto a2 = A2 ? std::pow(t, 3) / (3 * std::pow(A2, 3)) : 0.0; return a0 + a1 + a2; }; auto fn_x = [theta](double t) -> double { return cos(theta(t)); }; auto fn_y = [theta](double t) -> double { return sin(theta(t)); }; auto x = boost::math::quadrature::trapezoidal(fn_x, 0.0, s); auto y = boost::math::quadrature::trapezoidal(fn_y, 0.0, s); auto angle = theta(x); return {x, y, angle}; } struct functor_fn_evaluator : public fn_evaluator { functor_fn_evaluator(taxonomy::functor_item::const_ptr fn, const ifcopenshell::geometry::Settings& settings) : fn_evaluator(settings), fn_(fn) { } fn_evaluator* clone() const override { return new functor_fn_evaluator(*this); } double start() const override { return fn_->start(); } double end() const override { return fn_->end(); } Eigen::Matrix4d evaluate(double u) const override { return (*fn_)(u); } taxonomy::functor_item::const_ptr fn_; }; struct piecewise_fn_evaluator : public fn_evaluator { piecewise_fn_evaluator(taxonomy::piecewise_function::const_ptr fn, const ifcopenshell::geometry::Settings& settings) : fn_evaluator(settings), fn_(fn) { } fn_evaluator* clone() const override { return new piecewise_fn_evaluator(*this); } double start() const override { return fn_->start(); } double end() const override { return fn_->end(); } Eigen::Matrix4d evaluate(double u) const override { // assume monotonic evaluation and store last evaluated segment if (current_span_fn_ == nullptr || (u < current_span_start_ || current_span_end_ < u)) { // there isn't a current span or u is outside the range of the current span // get a new "current span" std::tie(current_span_start_, current_span_end_, current_span_fn_) = get_span(u); } u -= current_span_start_; // make u relative to start of span function_item_evaluator evaluator(settings_, current_span_fn_); return evaluator.evaluate(u); } std::tuple get_span(double u) const { // force u to be within bounds of the curve double s = fn_->start(); double e = fn_->end(); u = std::max(s, u); u = std::min(u, e); double span_start = s; for (auto& fn : fn_->spans()) { double span_end = span_start + fn->length(); auto tolerance = settings_.get().get(); if (span_start <= u && u < span_end + tolerance) { return {span_start, span_end, fn}; } span_start += fn->length(); } Logger::Error("piecewise span not found."); return {0, 0, nullptr}; } taxonomy::piecewise_function::const_ptr fn_; mutable double current_span_start_ = 0; mutable double current_span_end_ = 0; mutable taxonomy::function_item::const_ptr current_span_fn_ = nullptr; }; struct gradient_fn_evaluator : public fn_evaluator { gradient_fn_evaluator(taxonomy::gradient_function::const_ptr fn, const ifcopenshell::geometry::Settings& settings) : fn_evaluator(settings), fn_(fn), horizontal_evaluator_(settings, fn->get_horizontal()), vertical_evaluator_(settings, fn->get_vertical()) { start_ = fn_->get_vertical()->start(); } fn_evaluator* clone() const override { return new gradient_fn_evaluator(*this); } double start() const override { return fn_->start(); } double end() const override { return fn_->end(); } Eigen::Matrix4d evaluate(double u) const override { auto xy = horizontal_evaluator_.evaluate(u); auto uz = vertical_evaluator_.evaluate(u); // curvature is stored in row 3 - capture it and remove it from the xy and uz matrices // so the matrix operations (ie multiplication) works correct.y auto horizontal_curvature = xy.row(3); xy.row(3) = Eigen::Vector4d(0, 0, 0, 1); auto vertical_curvature = uz.row(3); uz.row(3) = Eigen::Vector4d(0, 0, 0, 1); uz(0, 3) = 0.0; // x is distance along. zero it out so it doesn't add to the x from horizontal uz.col(1).swap(uz.col(2)); // uz is 2D in distance along - y plane, swap y and z so elevations become z uz.row(1).swap(uz.row(2)); Eigen::Matrix4d m; m = xy * uz; // combine horizontal and vertical // Put curvature back into the solution matrix // curvature for vertical is in column 0, need it to be in column 1 // so it doesn't add to curvature for horizontal std::swap(vertical_curvature(0), vertical_curvature(1)); m.row(3) = horizontal_curvature + vertical_curvature; return m; } function_item_evaluator horizontal_evaluator_, vertical_evaluator_; double start_; // start of vertical taxonomy::gradient_function::const_ptr fn_; }; struct cant_fn_evaluator : public fn_evaluator { cant_fn_evaluator(taxonomy::cant_function::const_ptr fn, const ifcopenshell::geometry::Settings& settings) : fn_evaluator(settings), fn_(fn), gradient_evaluator_(settings, fn->get_gradient()), cant_evaluator_(settings, fn->get_cant()) { start_ = fn_->get_cant()->start(); } fn_evaluator* clone() const override { return new cant_fn_evaluator(*this); } double start() const override { return fn_->start(); } double end() const override { return fn_->end(); } Eigen::Matrix4d evaluate(double u) const override { auto g = gradient_evaluator_.evaluate(u); auto c = cant_evaluator_.evaluate(u); // curvature is stored in row 3 - capture it and remove it from the xy and uz matrices // so the matrix operations (ie multiplication) works correctly auto gradient_curvature = g.row(3); g.row(3) = Eigen::Vector4d(0, 0, 0, 1); auto cant_curvature = c.row(3); c.row(3) = Eigen::Vector4d(0, 0, 0, 1); // Need to multiply g and c so the axis vectors // from cant have the correct rotation applied so // they are relative to the gradient curve coordinate system // // However, the coordinate points don't need to have the rotations // of g applied. Save off the x,y,z and cant values auto x = g(0, 3); auto y = g(1, 3); auto z = g(2, 3); auto s = c(1, 3); // superelevation // change column 3 to (0,0,0,1) Eigen::Vector4d p(0, 0, 0, 1); g.col(3) = p; c.col(3) = p; // multiply g and c to get the axes in the correct orientation Eigen::Matrix4d m = g * c; // reinstate the values for x and y. // z is the gradient curve z value plus the superelevation // that comes from the cant. m(0, 3) = x; m(1, 3) = y; m(2, 3) = z + s; // reinstate values for curvature. // cant_curvature is cant alone. this needs to be combined with gradient in column 3 gradient_curvature[3] = gradient_curvature[2] + cant_curvature[3]; m.row(3) = gradient_curvature; return m; } function_item_evaluator gradient_evaluator_, cant_evaluator_; double start_; // start of cant taxonomy::cant_function::const_ptr fn_; }; struct offset_fn_evaluator : public fn_evaluator { offset_fn_evaluator(taxonomy::offset_function::const_ptr fn, const ifcopenshell::geometry::Settings& settings) : fn_evaluator(settings), fn_(fn), basis_evaluator_(settings, fn->get_basis()), offset_evaluator_(settings, fn->get_offset()) { } fn_evaluator* clone() const override { return new offset_fn_evaluator(*this); } double start() const override { return fn_->start(); } double end() const override { return fn_->end(); } Eigen::Matrix4d evaluate(double u) const override { auto p = basis_evaluator_.evaluate(u); auto offset = offset_evaluator_.evaluate(u); Eigen::Matrix4d m = p * offset; return m; } function_item_evaluator basis_evaluator_, offset_evaluator_; taxonomy::offset_function::const_ptr fn_; }; function_item_evaluator::function_item_evaluator(const ifcopenshell::geometry::Settings& settings,taxonomy::function_item::const_ptr fn) { auto kind = fn->kind(); if (kind == taxonomy::FUNCTOR_ITEM) { fn_evaluator_ = new functor_fn_evaluator(std::dynamic_pointer_cast(fn),settings); } else if (kind == taxonomy::PIECEWISE_FUNCTION) { fn_evaluator_ = new piecewise_fn_evaluator(std::dynamic_pointer_cast(fn), settings); } else if (kind == taxonomy::GRADIENT_FUNCTION) { fn_evaluator_ = new gradient_fn_evaluator(std::dynamic_pointer_cast(fn), settings); } else if (kind == taxonomy::CANT_FUNCTION) { fn_evaluator_ = new cant_fn_evaluator(std::dynamic_pointer_cast(fn), settings); } else if (kind == taxonomy::OFFSET_FUNCTION) { fn_evaluator_ = new offset_fn_evaluator(std::dynamic_pointer_cast(fn), settings); } else { Logger::Error("Unexpected function type"); } } function_item_evaluator::function_item_evaluator(const function_item_evaluator& other) { fn_evaluator_ = other.fn_evaluator_->clone(); eval_points_ = other.eval_points_; } function_item_evaluator::~function_item_evaluator() { delete fn_evaluator_; } std::vector function_item_evaluator::evaluation_points() const { if (!eval_points_.has_value()) { double curve_length = fn_evaluator_->length(); auto param_type = fn_evaluator_->settings_.get().get(); auto param = fn_evaluator_->settings_.get().get(); unsigned num_steps = 0; if (param_type == ifcopenshell::geometry::settings::FunctionStepMethod::MAXSTEPSIZE) { // parameter is max step size num_steps = (unsigned)std::ceil(curve_length / param); } else { // parameter is minimum number of steps num_steps = (unsigned)std::ceil(param); } eval_points_ = evaluation_points(fn_evaluator_->start(), fn_evaluator_->end(), num_steps); } return *eval_points_; } std::vector function_item_evaluator::evaluation_points(double ustart, double uend, unsigned nsteps) const { double curve_length = fn_evaluator_->length(); ustart = std::max(fn_evaluator_->start(), ustart); uend = std::min(uend, fn_evaluator_->start() + curve_length); nsteps = std::max(1u, nsteps); // never have fewer than 1 step auto resolution = (uend - ustart) / nsteps; std::vector u_values; u_values.reserve(nsteps); for (unsigned i = 0; i <= nsteps; ++i) { auto u = resolution * i + ustart; u_values.push_back(u); } return u_values; } taxonomy::item::ptr function_item_evaluator::evaluate() const { return evaluate(evaluation_points()); } taxonomy::item::ptr function_item_evaluator::evaluate(double ustart, double uend, unsigned nsteps) const { return evaluate(evaluation_points(ustart, uend, nsteps)); } taxonomy::item::ptr function_item_evaluator::evaluate(const std::vector& dist) const { std::vector polygon; polygon.reserve(dist.size()); for (auto& u : dist) { Eigen::Matrix4d m = evaluate(u); polygon.push_back(taxonomy::make(m(0, 3), m(1, 3), m(2, 3))); } return polygon_from_points(polygon); } Eigen::Matrix4d function_item_evaluator::evaluate(double u) const { Eigen::Matrix4d m = fn_evaluator_->evaluate(u); if (!fn_evaluator_->settings_.get().get()) { m.row(3) = Eigen::Vector4d(0, 0, 0, 1); } return m; }