reimplements piecewise_function using PIMPL idiom

PIMPL idiom allows implementaiton of piecewise_function to evolve without envoking systemwide recompile (unless the class definition changes)
This commit is contained in:
Richard Brice
2024-08-01 12:34:49 -07:00
parent b22ecba179
commit c31c34b7d2
8 changed files with 243 additions and 127 deletions
+1 -1
View File
@@ -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<taxonomy::piecewise_function>(0.0, spans,&settings_,inst);
return pwf;
+1 -1
View File
@@ -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<taxonomy::piecewise_function>(start, spans, &settings_, inst);
return pwf;
@@ -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<taxonomy::piecewise_function>(start,spans,&settings_,inst);
return pwf;
@@ -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<taxonomy::piecewise_function>(start, spans, &settings_, inst);
return pwf;
+106
View File
@@ -0,0 +1,106 @@
#include "piecewise_function_impl.h"
#include "profile_helper.h"
namespace ifcopenshell {
namespace geometry {
namespace taxonomy {
std::vector<double> ifcopenshell::geometry::taxonomy::piecewise_function_impl::evaluation_points() const {
if (!eval_points_.has_value()) {
double curve_length = length();
auto param_type = settings_ ? settings_->get<ifcopenshell::geometry::settings::PiecewiseStepType>().get() : ifcopenshell::geometry::settings::PiecewiseStepMethod::MAXSTEPSIZE;
auto param = settings_ ? settings_->get<ifcopenshell::geometry::settings::PiecewiseStepParam>().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<double> 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<double> 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<double>& dist) const {
std::vector<taxonomy::point3::ptr> polygon;
polygon.reserve(dist.size());
for (auto& u : dist) {
Eigen::Matrix4d m = evaluate(u);
polygon.push_back(taxonomy::make<taxonomy::point3>(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<double, double, const std::function<Eigen::Matrix4d(double u)>*> 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<ifcopenshell::geometry::settings::Precision>().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
+93
View File
@@ -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<std::pair<double, std::function<Eigen::Matrix4d(double u)>>>;
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<piecewise_function::ptr>& 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<double> 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<double> 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<double>& dist) const;
std::tuple<double, double, const std::function<Eigen::Matrix4d(double u)>*> 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<Eigen::Matrix4d(double u)>* current_span_fn_ = nullptr;
mutable boost::optional<double> length_;
mutable boost::optional<std::vector<double>> eval_points_;
};
} // namespace taxonomy
} // namespace geometry
} // namespace ifcopenshell
#endif PIECEWISE_FUNCTION_IMPL
+21 -83
View File
@@ -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<double> ifcopenshell::geometry::taxonomy::piecewise_function::evaluation_points() const {
if (!eval_points_.has_value()) {
double curve_length = length();
auto param_type = settings_ ? settings_->get<ifcopenshell::geometry::settings::PiecewiseStepType>().get() : ifcopenshell::geometry::settings::PiecewiseStepMethod::MAXSTEPSIZE;
auto param = settings_ ? settings_->get<ifcopenshell::geometry::settings::PiecewiseStepParam>().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<double> 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<piecewise_function::ptr>& 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<double> 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<double>& dist) const {
std::vector<taxonomy::point3::ptr> polygon;
polygon.reserve(dist.size());
for (auto& u : dist) {
Eigen::Matrix4d m = evaluate(u);
polygon.push_back(taxonomy::make<taxonomy::point3>(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<double, double, const std::function<Eigen::Matrix4d(double u)>*> 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<ifcopenshell::geometry::settings::Precision>().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<double> piecewise_function::evaluation_points() const { return impl_->evaluation_points(); }
std::vector<double> 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<taxonomy::collection>();
+18 -39
View File
@@ -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<std::pair<double, std::function<Eigen::Matrix4d(double u)>>>;
using spans_t = std::vector<std::pair<double, std::function<Eigen::Matrix4d(double u)>>>;
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<piecewise_function::ptr>& 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<piecewise_function::ptr>& 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<size_t>(PIECEWISE_FUNCTION), 0);
return boost::hash<decltype(v)>{}(v);
}
@@ -423,15 +407,10 @@ typedef item const* ptr;
Eigen::Matrix4d evaluate(double u) const;
private:
item::ptr evaluate(const std::vector<double>& dist) const;
std::tuple<double, double, const std::function<Eigen::Matrix4d(double u)>*> 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<Eigen::Matrix4d(double u)>* current_span_fn_ = nullptr;
mutable boost::optional<double> length_;
mutable boost::optional<std::vector<double>> 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<taxonomy::piecewise_function::ptr> pwf_;
public:
loop_to_piecewise_function_upgrade(taxonomy::ptr item) {
loop_to_piecewise_function_upgrade(taxonomy::ptr item) {
if constexpr (std::is_same_v<T, piecewise_function>) {
auto loop = taxonomy::dcast<taxonomy::loop>(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