diff --git a/src/ifcgeom/mapping/IfcCurveSegment.cpp b/src/ifcgeom/mapping/IfcCurveSegment.cpp index 4e9cb9e1de..322b2f773b 100644 --- a/src/ifcgeom/mapping/IfcCurveSegment.cpp +++ b/src/ifcgeom/mapping/IfcCurveSegment.cpp @@ -888,7 +888,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCurveSegment* inst) { auto length = cse.length(); - taxonomy::piecewise_function::spans spans; + taxonomy::piecewise_function::spans_t spans; spans.emplace_back(fabs(length), fn); auto pwf = taxonomy::make(0.0, spans,&settings_,inst); return pwf; diff --git a/src/ifcgeom/mapping/IfcGradientCurve.cpp b/src/ifcgeom/mapping/IfcGradientCurve.cpp index 144f68d877..3c5b95635a 100644 --- a/src/ifcgeom/mapping/IfcGradientCurve.cpp +++ b/src/ifcgeom/mapping/IfcGradientCurve.cpp @@ -84,7 +84,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcGradientCurve* inst) { return m; }; - taxonomy::piecewise_function::spans spans; + taxonomy::piecewise_function::spans_t spans; spans.emplace_back(length, composition); auto pwf = taxonomy::make(start, spans, &settings_, inst); return pwf; diff --git a/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp b/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp index ddc5621baa..7af8b42bd8 100644 --- a/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp +++ b/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp @@ -43,7 +43,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst double start = pw_curve->start(); double basis_curve_length = pw_curve->length(); - taxonomy::piecewise_function::spans offset_spans; + taxonomy::piecewise_function::spans_t offset_spans; #if defined SCHEMA_HAS_IfcDistanceExpression double first_distance = first_offset_value->DistanceAlong(); @@ -155,7 +155,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst // current implementation assumes that composition is equal to the full length of basis curve // this may change depending on decisions in the bSI-IF - taxonomy::piecewise_function::spans spans; + taxonomy::piecewise_function::spans_t spans; spans.emplace_back(basis_curve_length, composition); auto pwf = taxonomy::make(start,spans,&settings_,inst); return pwf; diff --git a/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp b/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp index 1ca21e3690..caf8104c65 100644 --- a/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp +++ b/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp @@ -83,7 +83,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSegmentedReferenceCurve* ins return m; }; - taxonomy::piecewise_function::spans spans; + taxonomy::piecewise_function::spans_t spans; spans.emplace_back(length, composition); auto pwf = taxonomy::make(start, spans, &settings_, inst); return pwf; diff --git a/src/ifcgeom/piecewise_function_impl.cpp b/src/ifcgeom/piecewise_function_impl.cpp new file mode 100644 index 0000000000..ecd2762057 --- /dev/null +++ b/src/ifcgeom/piecewise_function_impl.cpp @@ -0,0 +1,106 @@ +#include "piecewise_function_impl.h" +#include "profile_helper.h" + +namespace ifcopenshell { + +namespace geometry { + +namespace taxonomy { + +std::vector ifcopenshell::geometry::taxonomy::piecewise_function_impl::evaluation_points() const { + if (!eval_points_.has_value()) { + double curve_length = length(); + + auto param_type = settings_ ? settings_->get().get() : ifcopenshell::geometry::settings::PiecewiseStepMethod::MAXSTEPSIZE; + auto param = settings_ ? settings_->get().get() : 0.5; + unsigned num_steps = 0; + if (param_type == ifcopenshell::geometry::settings::PiecewiseStepMethod::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(start_, start_ + curve_length, num_steps); + } + return *eval_points_; +} + +std::vector ifcopenshell::geometry::taxonomy::piecewise_function_impl::evaluation_points(double ustart, double uend, unsigned nsteps) const { + double curve_length = length(); + ustart = std::max(start_, ustart); + uend = std::min(uend, 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; +} + +ifcopenshell::geometry::taxonomy::item::ptr ifcopenshell::geometry::taxonomy::piecewise_function_impl::evaluate() const { + return evaluate(evaluation_points()); +} + +item::ptr ifcopenshell::geometry::taxonomy::piecewise_function_impl::evaluate(double ustart, double uend, unsigned nsteps) const { + return evaluate(evaluation_points(ustart, uend, nsteps)); +} + +item::ptr ifcopenshell::geometry::taxonomy::piecewise_function_impl::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.col(3)(0), m.col(3)(1), m.col(3)(2))); + } + + return polygon_from_points(polygon); +} + +Eigen::Matrix4d ifcopenshell::geometry::taxonomy::piecewise_function_impl::evaluate(double u) const { + // 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 + return (*current_span_fn_)(u); +} + +std::tuple*> ifcopenshell::geometry::taxonomy::piecewise_function_impl::get_span(double u) const { + // force u to be within bounds of the curve + double s = start(); + double e = end(); + u = std::max(s, u); + u = std::min(u, e); + + double span_start = s; + for (auto& [length, fn] : spans_) { + double span_end = span_start + length; + auto tolerance = settings_ ? settings_->get().get() : 0.001; + if (span_start <= u && u < span_end + tolerance) { + return {span_start, span_end, &fn}; + } + span_start += length; + } + + Logger::Error("piecewise_function_impl::get_span span not found."); + return {0, 0, nullptr}; +} + +} // namespace taxonomy + +} // namespace geometry + +} // namespace ifcopenshell diff --git a/src/ifcgeom/piecewise_function_impl.h b/src/ifcgeom/piecewise_function_impl.h new file mode 100644 index 0000000000..0c2f306ac5 --- /dev/null +++ b/src/ifcgeom/piecewise_function_impl.h @@ -0,0 +1,93 @@ +#ifndef PIECEWISE_FUNCTION_IMPL +#define PIECEWISE_FUNCTION_IMPL + +#include "taxonomy.h" + +namespace ifcopenshell { + +namespace geometry { + +namespace taxonomy { + +struct piecewise_function_impl { + using spans_t = std::vector>>; + + piecewise_function_impl(double start, const spans_t& s, ifcopenshell::geometry::Settings* settings = nullptr) : start_(start), + settings_(settings), + spans_(s){}; + piecewise_function_impl(double start, const std::vector& pwfs, ifcopenshell::geometry::Settings* settings = nullptr) : start_(start), + settings_(settings) { + for (auto& pwf : pwfs) { + spans_.insert(spans_.end(), pwf->spans().begin(), pwf->spans().end()); + } + }; + piecewise_function_impl(piecewise_function_impl&&) = default; + piecewise_function_impl(const piecewise_function_impl&) = default; + + const ifcopenshell::geometry::Settings* settings_ = nullptr; + + const spans_t& spans() const { return spans_; } + + bool is_empty() const { return spans_.empty(); } + + double start() const { + return start_; + } + + double end() const { + return start_ + length(); + } + + double length() const { + if (!length_.has_value()) { + length_ = std::accumulate(spans_.begin(), spans_.end(), 0.0, [](const auto& v, const auto& s) { return v + s.first; }); + } + return *length_; + } + + piecewise_function_impl* clone_() const { return new piecewise_function_impl(*this); } + + /// @brief returns a vector of "distance along" points where the evaluate function computes loop points + std::vector evaluation_points() const; + + /// @brief returns a vector of "distance along" points between ustart and uend + /// @param ustart starting location + /// @param uend ending location + /// @param nsteps number of steps to evaluate + std::vector evaluation_points(double ustart, double uend, unsigned nsteps) const; + + /// @brief evaluates the piecewise function between start and end + /// evaluation point step size is taken from the settings object + item::ptr evaluate() const; + + /// @brief evaluates the piecewise function between ustart and uend + /// if ustart and uend are out of range, the range of values evaluated + /// are constrained to start_ and start_+length_ + /// @param ustart starting location + /// @param uend ending location + /// @param nsteps number of steps to evaluate + /// @return taxonomy::loop::ptr + item::ptr evaluate(double ustart, double uend, unsigned nsteps) const; + + /// @brief evaluates the piecewise function at u + /// @param u u is constrained to be between start_ and start_+length + /// @return 4x4 placement matrix + Eigen::Matrix4d evaluate(double u) const; + + private: + item::ptr evaluate(const std::vector& dist) const; + std::tuple*> get_span(double u) const; + double start_ = 0.0; // starting value of the pwf + spans_t spans_; + + mutable double current_span_start_ = 0; + mutable double current_span_end_ = 0; + mutable const std::function* current_span_fn_ = nullptr; + mutable boost::optional length_; + mutable boost::optional> eval_points_; +}; + +} // namespace taxonomy +} // namespace geometry +} // namespace ifcopenshell +#endif PIECEWISE_FUNCTION_IMPL \ No newline at end of file diff --git a/src/ifcgeom/taxonomy.cpp b/src/ifcgeom/taxonomy.cpp index 9a80afe7d1..b9fdc78d27 100644 --- a/src/ifcgeom/taxonomy.cpp +++ b/src/ifcgeom/taxonomy.cpp @@ -1,6 +1,7 @@ #include "../ifcparse/IfcLogger.h" #include "taxonomy.h" #include "profile_helper.h" +#include "piecewise_function_impl.h" using namespace ifcopenshell::geometry::taxonomy; @@ -462,97 +463,34 @@ ifcopenshell::geometry::taxonomy::solid::ptr ifcopenshell::geometry::create_box( return solid; } -std::vector ifcopenshell::geometry::taxonomy::piecewise_function::evaluation_points() const { - if (!eval_points_.has_value()) { - double curve_length = length(); - - auto param_type = settings_ ? settings_->get().get() : ifcopenshell::geometry::settings::PiecewiseStepMethod::MAXSTEPSIZE; - auto param = settings_ ? settings_->get().get() : 0.5; - unsigned num_steps = 0; - if (param_type == ifcopenshell::geometry::settings::PiecewiseStepMethod::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(start_, start_ + curve_length, num_steps); - } - return *eval_points_; +/////////////////// +piecewise_function::piecewise_function(double start, const spans_t& s, ifcopenshell::geometry::Settings* settings, const IfcUtil::IfcBaseInterface* instance) : implicit_item(instance) { + impl_ = new piecewise_function_impl(start, s, settings); } -std::vector ifcopenshell::geometry::taxonomy::piecewise_function::evaluation_points(double ustart, double uend, unsigned nsteps) const { - double curve_length = length(); - ustart = std::max(start_, ustart); - uend = std::min(uend, start_ + curve_length); +piecewise_function::piecewise_function(double start, const std::vector& pwfs, ifcopenshell::geometry::Settings* settings, const IfcUtil::IfcBaseInterface* instance) : implicit_item(instance) { + impl_ = new piecewise_function_impl(start, pwfs, settings); +}; - 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; +piecewise_function::piecewise_function(const piecewise_function& other) : implicit_item(other) { + impl_ = other.impl_->clone_(); } -ifcopenshell::geometry::taxonomy::item::ptr ifcopenshell::geometry::taxonomy::piecewise_function::evaluate() const { - return evaluate(evaluation_points()); +piecewise_function::~piecewise_function() { + delete impl_; } -item::ptr ifcopenshell::geometry::taxonomy::piecewise_function::evaluate(double ustart, double uend,unsigned nsteps) const { - return evaluate(evaluation_points(ustart,uend,nsteps)); -} +const piecewise_function::spans_t& piecewise_function::spans() const { return impl_->spans(); } +bool piecewise_function::is_empty() const { return impl_->is_empty(); } +double piecewise_function::start() const { return impl_->start(); } +double piecewise_function::end() const { return impl_->end(); } +double piecewise_function::length() const { return impl_->length(); } -item::ptr ifcopenshell::geometry::taxonomy::piecewise_function::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.col(3)(0), m.col(3)(1), m.col(3)(2))); - } - - return polygon_from_points(polygon); -} - -Eigen::Matrix4d ifcopenshell::geometry::taxonomy::piecewise_function::evaluate(double u) const { - // 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 - return (*current_span_fn_)(u); -} - -std::tuple*> ifcopenshell::geometry::taxonomy::piecewise_function::get_span(double u) const { - // force u to be within bounds of the curve - double s = start(); - double e = end(); - u = std::max(s, u); - u = std::min(u, e); - - double span_start = s; - for (auto& [length, fn] : spans_) { - double span_end = span_start + length; - auto tolerance = settings_ ? settings_->get().get() : 0.001; - if (span_start <= u && u < span_end + tolerance) { - return {span_start, span_end, &fn} ; - } - span_start += length; - } - - Logger::Error("taxonomy::piecewise_function::get_span span not found."); - return {0, 0, nullptr}; -} +std::vector piecewise_function::evaluation_points() const { return impl_->evaluation_points(); } +std::vector piecewise_function::evaluation_points(double ustart, double uend, unsigned nsteps) const { return impl_->evaluation_points(ustart, uend, nsteps); } +item::ptr piecewise_function::evaluate() const { return impl_->evaluate(); } +item::ptr piecewise_function::evaluate(double ustart, double uend, unsigned nsteps) const { return impl_->evaluate(ustart, uend, nsteps); } +Eigen::Matrix4d piecewise_function::evaluate(double u) const { return impl_->evaluate(u); } ifcopenshell::geometry::taxonomy::collection::ptr ifcopenshell::geometry::flatten(const taxonomy::collection::ptr& deep) { auto flat = make(); diff --git a/src/ifcgeom/taxonomy.h b/src/ifcgeom/taxonomy.h index 4fbbf636d2..3b77273240 100644 --- a/src/ifcgeom/taxonomy.h +++ b/src/ifcgeom/taxonomy.h @@ -351,46 +351,30 @@ typedef item const* ptr; virtual item::ptr evaluate() const = 0; }; + struct piecewise_function_impl; // forward declaration struct piecewise_function : public implicit_item { DECLARE_PTR(piecewise_function) - using spans = std::vector>>; + using spans_t = std::vector>>; - piecewise_function(double start,const spans& s, ifcopenshell::geometry::Settings* settings = nullptr, const IfcUtil::IfcBaseInterface* instance = nullptr) : - implicit_item(instance), start_(start), settings_(settings), spans_(s){}; - piecewise_function(double start,const std::vector& pwfs, ifcopenshell::geometry::Settings* settings = nullptr, const IfcUtil::IfcBaseInterface* instance = nullptr) : - implicit_item(instance), start_(start), settings_(settings) - { - for (auto& pwf : pwfs) { - spans_.insert(spans_.end(), pwf->spans_.begin(), pwf->spans_.end()); - } - }; + piecewise_function(double start, const spans_t& s, ifcopenshell::geometry::Settings* settings = nullptr, const IfcUtil::IfcBaseInterface* instance = nullptr); + piecewise_function(double start, const std::vector& pwfs, ifcopenshell::geometry::Settings* settings = nullptr, const IfcUtil::IfcBaseInterface* instance = nullptr); piecewise_function(piecewise_function&&) = default; - piecewise_function(const piecewise_function&) = default; + piecewise_function(const piecewise_function&); + virtual ~piecewise_function(); const ifcopenshell::geometry::Settings* settings_ = nullptr; - bool is_empty() const { return spans_.empty(); } - - double start() const { - return start_; - } - - double end() const { - return start_ + length(); - } - - double length() const { - if (!length_.has_value()) { - length_ = std::accumulate(spans_.begin(), spans_.end(), 0.0, [](const auto& v, const auto& s) { return v + s.first; }); - } - return *length_; - } + const spans_t& spans() const; + bool is_empty() const; + double start() const; + double end() const; + double length() const; virtual piecewise_function* clone_() const { return new piecewise_function(*this); } virtual kinds kind() const { return PIECEWISE_FUNCTION; } - virtual size_t calc_hash() const { + virtual size_t calc_hash() const { auto v = std::make_tuple(static_cast(PIECEWISE_FUNCTION), 0); return boost::hash{}(v); } @@ -423,15 +407,10 @@ typedef item const* ptr; Eigen::Matrix4d evaluate(double u) const; private: - item::ptr evaluate(const std::vector& dist) const; - std::tuple*> get_span(double u) const; - double start_ = 0.0; // starting value of the pwf - spans spans_; - mutable double current_span_start_ = 0; - mutable double current_span_end_ = 0; - mutable const std::function* current_span_fn_ = nullptr; - mutable boost::optional length_; - mutable boost::optional> eval_points_; + // note: it would be better if this were a std::unique_ptr, but that requires having the full definition + // of piecewise_function_impl in this header file, which defeats the purpose of the PIMPL idiom. + // if this is a std::unique_ptr, then the _ifcopenshell_wrapper library doesn't compile + piecewise_function_impl* impl_ = nullptr; }; #ifdef TAXONOMY_USE_SHARED_PTR @@ -1329,14 +1308,14 @@ typedef item const* ptr; boost::optional pwf_; public: - loop_to_piecewise_function_upgrade(taxonomy::ptr item) { + loop_to_piecewise_function_upgrade(taxonomy::ptr item) { if constexpr (std::is_same_v) { auto loop = taxonomy::dcast(item); if (loop) { if (loop->pwf.is_initialized()) { pwf_ = loop->pwf; } else { - taxonomy::piecewise_function::spans spans; + taxonomy::piecewise_function::spans_t spans; spans.reserve(loop->children.size()); for (auto& edge : loop->children) { // the edge could be an arc or trimmed circle in the case of IfcIndexPolyCurve - support for this isn't implemented yet