diff --git a/src/ifcgeom/infra_sweep_helper.cpp b/src/ifcgeom/infra_sweep_helper.cpp index 7559d4c9d6..f3f79d4c18 100644 --- a/src/ifcgeom/infra_sweep_helper.cpp +++ b/src/ifcgeom/infra_sweep_helper.cpp @@ -63,31 +63,37 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_, auto relative_dist_along = (dist_along - *profile_index) / (*(profile_index + 1) - *profile_index); const auto& profile_a = cross_sections[std::distance(longitudes.begin(), profile_index)].section_geometry; const auto& offset_a = cross_sections[std::distance(longitudes.begin(), profile_index)].offset; + const auto& rotation_a = cross_sections[std::distance(longitudes.begin(), profile_index)].rotation; taxonomy::geom_item::ptr interpolated = nullptr; // Only interpolate if: // - there is a profile ahead of us, and - // - we're not exactly at the location of the current profile or whether there is an offset involved. + // - we're not exactly at the location of the current profile or whether there is an offset involved bool should_interpolate = (profile_index + 1 < longitudes.end()) && - (relative_dist_along >= 1.e-9 || offset_a.cwiseAbs().maxCoeff() > 0.); + (relative_dist_along >= 1.e-9 || offset_a.cwiseAbs().maxCoeff() > 0. || rotation_a); + + boost::optional interpolated_rotation; if (should_interpolate) { taxonomy::geom_item::ptr profile_b; Eigen::Vector3d offset_b; + boost::optional rotation_b; if ((profile_index + 1 < longitudes.end())) { profile_b = cross_sections[std::distance(longitudes.begin(), profile_index) + 1].section_geometry; offset_b = cross_sections[std::distance(longitudes.begin(), profile_index) + 1].offset; + rotation_b = cross_sections[std::distance(longitudes.begin(), profile_index) + 1].rotation; } else { profile_b = profile_a; offset_b = offset_a; + rotation_b = rotation_a; } // Only interpolate if the profiles are different or either of the offsets is non-zero bool should_interpolate2 = (profile_a->instance != profile_b->instance) || - (offset_a.cwiseAbs().maxCoeff() > 0. || offset_b.cwiseAbs().maxCoeff() > 0.); + (offset_a.cwiseAbs().maxCoeff() > 0. || offset_b.cwiseAbs().maxCoeff() > 0. || rotation_b); if (should_interpolate2) { @@ -130,6 +136,13 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_, } auto interpolated_offset = lerp(offset_a, offset_b, relative_dist_along); + if (rotation_a && rotation_b) { + // @todo we don't support an overridden rotation on only one of the placements + // in which case we would need to lerp with the rotation component below in m4b. + interpolated_rotation = lerp(*rotation_a, *rotation_b, relative_dist_along); + } else { + Logger::Error("Direction vectors on cross section placements only supported when used consistently"); + } taxonomy::loop::ptr w1, w2; taxonomy::edge::ptr e1, e2; for (auto tmp_ : boost::combine(loops_a, loops_b)) { @@ -149,6 +162,7 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_, auto& p2 = boost::get(e2->start); auto p3 = (lerp(p1->ccomponents(), p2->ccomponents(), relative_dist_along) + interpolated_offset).eval(); + // auto p4 = (interpolated_rotation * p3).eval(); points.push_back(taxonomy::make(p3)); } if (!points.empty()) { @@ -173,9 +187,16 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_, }*/ Eigen::Matrix4d m4b = Eigen::Matrix4d::Identity(); - m4b.col(0).head<3>() = m4.col(1).head<3>().normalized(); - m4b.col(1).head<3>() = m4.col(2).head<3>().normalized(); - m4b.col(2).head<3>() = m4.col(0).head<3>().normalized(); + if (interpolated_rotation) { + // direction vectors on the linear placement overwrite the placement otherwise inferred from the tangent + m4b.col(0).head<3>() = interpolated_rotation->col(1); + m4b.col(1).head<3>() = interpolated_rotation->col(2); + m4b.col(2).head<3>() = interpolated_rotation->col(0); + } else { + m4b.col(0).head<3>() = m4.col(1).head<3>().normalized(); + m4b.col(1).head<3>() = m4.col(2).head<3>().normalized(); + m4b.col(2).head<3>() = m4.col(0).head<3>().normalized(); + } m4b.col(3).head<3>() = m4.col(3).head<3>(); if (interpolated) { diff --git a/src/ifcgeom/infra_sweep_helper.h b/src/ifcgeom/infra_sweep_helper.h index 351206dd03..bacc4bd4f0 100644 --- a/src/ifcgeom/infra_sweep_helper.h +++ b/src/ifcgeom/infra_sweep_helper.h @@ -12,6 +12,7 @@ namespace ifcopenshell { double dist_along; taxonomy::geom_item::ptr section_geometry; Eigen::Vector3d offset; + boost::optional rotation; bool operator <(const cross_section& other) const { return dist_along < other.dist_along; diff --git a/src/ifcgeom/mapping/IfcSectionedSolidHorizontal.cpp b/src/ifcgeom/mapping/IfcSectionedSolidHorizontal.cpp index 640b5a24f0..fe5b31c56f 100644 --- a/src/ifcgeom/mapping/IfcSectionedSolidHorizontal.cpp +++ b/src/ifcgeom/mapping/IfcSectionedSolidHorizontal.cpp @@ -49,6 +49,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in // The longitudes determine the range of the sweep and the offsets are interpolated in between // sweep segments. std::vector profile_offsets; + std::vector> profile_rotations; std::vector longitudes; for (auto& cs : *css) { @@ -69,6 +70,19 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in ); profile_offsets.push_back(po); + + boost::optional rot; + if (csp->Axis() && csp->RefDirection()) { + rot = taxonomy::matrix4( + Eigen::Vector3d(0, 0, 0), + taxonomy::cast(map(csp->Axis()))->ccomponents(), + taxonomy::cast(map(csp->RefDirection()))->ccomponents()).ccomponents().block<3,3>(0,0); + } else if (csp->Axis()) { + rot = taxonomy::matrix4( + Eigen::Vector3d(0, 0, 0), + taxonomy::cast(map(csp->Axis()))->ccomponents()).ccomponents().block<3, 3>(0, 0); + } + profile_rotations.push_back(rot); } if (faces.size() != profile_offsets.size()) { Logger::Warning("Expected CrossSections and CrossSectionPositions to be equal length, but got " + std::to_string(faces.size()) + " and " + std::to_string(profile_offsets.size()) + " respectively", inst); @@ -80,7 +94,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in } for (size_t i = 0; i < faces.size(); ++i) { - cross_sections.push_back({ longitudes[i], faces[i], profile_offsets[i] }); + cross_sections.push_back({ longitudes[i], faces[i], profile_offsets[i], profile_rotations[i]}); } #else return nullptr; diff --git a/src/ifcgeom/mapping/IfcSectionedSurface.cpp b/src/ifcgeom/mapping/IfcSectionedSurface.cpp index 3edcde7946..4948ddd0a0 100644 --- a/src/ifcgeom/mapping/IfcSectionedSurface.cpp +++ b/src/ifcgeom/mapping/IfcSectionedSurface.cpp @@ -49,6 +49,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSurface* inst) { // The longitudes determine the range of the sweep and the offsets are interpolated in between // sweep segments. std::vector profile_offsets; + std::vector> profile_rotations; std::vector longitudes; for (auto& cs : *css) { @@ -69,6 +70,19 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSurface* inst) { ); profile_offsets.push_back(po); + + boost::optional rot; + if (csp->Axis() && csp->RefDirection()) { + rot = taxonomy::matrix4( + Eigen::Vector3d(0, 0, 0), + taxonomy::cast(map(csp->Axis()))->ccomponents(), + taxonomy::cast(map(csp->RefDirection()))->ccomponents()).ccomponents().block<3, 3>(0, 0); + } else if (csp->Axis()) { + rot = taxonomy::matrix4( + Eigen::Vector3d(0, 0, 0), + taxonomy::cast(map(csp->Axis()))->ccomponents()).ccomponents().block<3, 3>(0, 0); + } + profile_rotations.push_back(rot); } #else return nullptr; @@ -83,7 +97,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSurface* inst) { } for (size_t i = 0; i < faces.size(); ++i) { - cross_sections.push_back({ longitudes[i], faces[i], profile_offsets[i] }); + cross_sections.push_back({ longitudes[i], faces[i], profile_offsets[i], profile_rotations[i] }); } }