#pragma once #include "line_evaluation.hpp" namespace internal { // ============================================================= // structures // ============================================================= // Eigen has a type Isometry which supports similar operations as this // but we use AffineCompact to get lower storage cost // so we have to implement a helper function to apply the operation of Isometry to AffineCompact static inline auto inversed_affine_transform(const Eigen::Transform& trs) { Eigen::Transform result; auto linear_part = result.matrix().template topLeftCorner<3, 3>(); linear_part = trs.linear().transpose(); result.matrix().template topRightCorner<3, 1>() = -linear_part * trs.translation(); result.makeAffine(); return result; } // ============================================================= // derived functions // ============================================================= // CAUTION: take care of relations between line's local TBN and plane's local TBN // CAUTION: all internal calculations of polyline should assume that p is in axis/plane's local TBN space static inline auto get_local_TBN(const polyline& line, const Eigen::Ref& p, const Eigen::Ref& q, double t) { Eigen::Vector2d local_tangent, local_normal; if (t >= 0 && t <= line.start_indices.size() && std::abs(t - std::round(t)) <= EPSILON) { // if between two segments, use local vec{pq} on the plane as normal local_normal = (q.topRows<2>() - p.topRows<2>()).normalized(); if (local_normal.dot(calculate_normal(line, t)) < 0) local_normal *= -1; local_tangent = {local_normal[1], -local_normal[0]}; } else { local_tangent = calculate_tangent(line, t); local_normal = calculate_normal(line, t); } Eigen::Transform TBN{}; auto& handle = TBN.matrix(); handle.col(0)[2] = 1; handle.col(1).topRows<2>() = std::move(local_normal); handle.col(2).topRows<2>() = std::move(local_tangent); handle.col(3).topRows<3>() = std::move(q.topRows<3>()); return TBN; } static inline auto get_local_TBN(const helixline& line, const Eigen::Ref& q, double t) { Eigen::Transform TBN{}; auto& handle = TBN.matrix(); const auto T = calculate_tangent(line, t); const auto N = calculate_normal(line, t); const auto B = N.cross(T); handle.col(0).topRows<3>() = std::move(B); handle.col(1).topRows<3>() = std::move(N); handle.col(2).topRows<3>() = std::move(T); handle.col(3).topRows<3>() = std::move(q.topRows<3>()); return TBN; } [[nodiscard]] static inline auto calculate_closest_param(const extrude_polyline& solid, const Eigen::Vector3d& p) -> Eigen::Vector4d { const auto world_to_axis = inversed_affine_transform(solid.axis_to_world); const auto local_p = world_to_axis * Eigen::Vector4d{p[0], p[1], p[2], 1}; const auto axis_closest_param = calculate_closest_param(solid.axis, local_p.topRows<3>()); const auto TBN = get_local_TBN(solid.axis, local_p, axis_closest_param.point, axis_closest_param.t); const auto inv_TBN = inversed_affine_transform(TBN); // TODO: add support for non-parallel (ray vs. profile plane) case // for now just assume that profile plane is parallel to axis const auto profile_closest_param = calculate_closest_param(solid.profile, (inv_TBN * local_p).topRows<3>()); // transform closest point to world space // the distance should be unchanged during transformation, since no scaling is involved auto world_closest_point = solid.axis_to_world * TBN * profile_closest_param.point; world_closest_point[3] = profile_closest_param.distance; return world_closest_point; } [[nodiscard]] static inline auto calculate_closest_param(const extrude_helixline& solid, const Eigen::Vector3d& p) -> Eigen::Vector4d { const auto world_to_axis = inversed_affine_transform(solid.axis_to_world); const auto local_p = world_to_axis * Eigen::Vector4d{p[0], p[1], p[2], 1}; const auto axis_closest_param = calculate_closest_param(solid.axis, local_p.topRows<3>()); const auto TBN = get_local_TBN(solid.axis, axis_closest_param.point, axis_closest_param.t); const auto inv_TBN = inversed_affine_transform(TBN); // TODO: add support for non-parallel (ray vs. profile plane) case // for now just assume that profile plane is parallel to axis const auto profile_closest_param = calculate_closest_param(solid.profile, (inv_TBN * local_p).topRows<3>()); // transform closest point to world space // the distance should be unchanged during transformation, since no scaling is involved auto world_closest_point = solid.axis_to_world * TBN * profile_closest_param.point; world_closest_point[3] = profile_closest_param.distance; return world_closest_point; } } // namespace internal