You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
8.6 KiB
188 lines
8.6 KiB
6 months ago
|
#include <algorithm/glue_algorithm.hpp>
|
||
|
|
||
|
#include "internal_primitive_desc.hpp"
|
||
|
|
||
|
namespace internal
|
||
|
{
|
||
|
void polyline::build_as_axis(const polyline_descriptor_t &desc,
|
||
|
const raw_vector3d_t &profile_ref_normal,
|
||
|
Eigen::Ref<Eigen::Matrix<double, 3, 4>> axis_to_world,
|
||
|
aabb_t<> &aabb)
|
||
|
{
|
||
|
Eigen::Vector3d origin, topper;
|
||
|
vec3d_conversion(desc.axis_start, origin);
|
||
|
vec3d_conversion(desc.axis_end, topper);
|
||
|
this->radius = desc.radius;
|
||
|
this->height = (topper - origin).norm();
|
||
|
this->total_theta = this->height / desc.advance_per_round * TWO_PI;
|
||
|
|
||
|
auto &matrix_handle = axis_to_world.matrix();
|
||
|
vec3d_conversion(desc.start_direction, matrix_handle.col(0));
|
||
|
matrix_handle.col(0).normalized();
|
||
|
matrix_handle.col(2) = (topper - origin) / this->height;
|
||
|
matrix_handle.col(3) = origin;
|
||
|
matrix_handle.col(1) = matrix_handle.col(2).cross(matrix_handle.col(0));
|
||
|
|
||
|
aabb = {
|
||
|
Eigen::Vector3d{-this->radius, -this->radius, 0 },
|
||
|
Eigen::Vector3d{this->radius, this->radius, this->height}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
helixline::helixline(helixline_descriptor_t &&desc, Eigen::Ref<Eigen::Matrix<double, 3, 4>> &axis_to_world, aabb_t<> &aabb)
|
||
|
{
|
||
|
Eigen::Vector3d origin, topper;
|
||
|
vec3d_conversion(std::move(desc.axis_start), origin);
|
||
|
vec3d_conversion(std::move(desc.axis_end), topper);
|
||
|
this->radius = std::move(desc.radius);
|
||
|
this->height = (topper - origin).norm();
|
||
|
this->total_theta = this->height / std::move(desc.advance_per_round) * TWO_PI;
|
||
|
|
||
|
auto &matrix_handle = axis_to_world.matrix();
|
||
|
vec3d_conversion(std::move(desc.start_direction), matrix_handle.col(0));
|
||
|
matrix_handle.col(0).normalized();
|
||
|
matrix_handle.col(2) = (topper - origin) / this->height;
|
||
|
matrix_handle.col(3) = origin;
|
||
|
matrix_handle.col(1) = matrix_handle.col(2).cross(matrix_handle.col(0));
|
||
|
|
||
|
aabb = {
|
||
|
Eigen::Vector3d{-this->radius, -this->radius, 0 },
|
||
|
Eigen::Vector3d{this->radius, this->radius, this->height}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
[[nodiscard]] Eigen::Vector3d helixline::calculate_tangent(double t) const
|
||
|
{
|
||
|
assert(t >= 0 && t <= 1);
|
||
|
|
||
|
return Eigen::Vector3d{-this->total_theta * this->radius * std::sin(t * this->total_theta),
|
||
|
this->total_theta * this->radius * std::cos(t * this->total_theta),
|
||
|
this->height}
|
||
|
.normalized();
|
||
|
}
|
||
|
|
||
|
[[nodiscard]] Eigen::Vector3d helixline::calculate_normal(double t) const
|
||
|
{
|
||
|
assert(t >= 0 && t <= 1);
|
||
|
|
||
|
return {-std::cos(t * this->total_theta), -std::sin(t * this->total_theta), 0.};
|
||
|
}
|
||
|
|
||
|
[[nodiscard]] line_closest_param_t helixline::calculate_closest_param(const Eigen::Ref<const Eigen::Vector3d> &p) const
|
||
|
{
|
||
|
const auto h_p = p.z();
|
||
|
const auto theta_p = std::atan2(p.y(), p.x());
|
||
|
const auto r_p = p.topRows<2>().norm();
|
||
|
const auto theta_intersect = this->total_theta * h_p / this->height;
|
||
|
const auto k = (theta_intersect - theta_p) * INV_TWO_PI;
|
||
|
const auto rounded_k = std::round(k);
|
||
|
|
||
|
// early exit case: point p is on the helix
|
||
|
if (std::abs(k - rounded_k) <= EPSILON) {
|
||
|
const auto theta = theta_p + rounded_k * TWO_PI;
|
||
|
if (theta >= 0 && theta <= this->total_theta)
|
||
|
return {
|
||
|
{p.x(), p.y(), p.z(), 1},
|
||
|
theta / this->total_theta,
|
||
|
true
|
||
|
};
|
||
|
else {
|
||
|
// for points two sides away from the helix, this may cause slight numerical error
|
||
|
// but it should be fine since this kind of points are rare
|
||
|
const auto clipped_theta = std::clamp(theta, 0., this->total_theta);
|
||
|
const auto t = clipped_theta / this->total_theta;
|
||
|
return {
|
||
|
{this->radius * std::cos(t * this->total_theta),
|
||
|
this->radius * std::sin(t * this->total_theta),
|
||
|
t * this->height,
|
||
|
1},
|
||
|
std::move(t),
|
||
|
false
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const auto times_p = -theta_p * (1 + this->total_theta) * INV_PI;
|
||
|
const auto times_line = this->total_theta * this->total_theta * INV_PI;
|
||
|
const auto rounded_times_start = std::ceil(times_p - 0.5);
|
||
|
const auto rounded_times_end = std::floor(times_line + times_p - 0.5);
|
||
|
|
||
|
const auto line_theta_r = this->total_theta * this->radius;
|
||
|
const auto inv_line_theta = 1. / this->total_theta;
|
||
|
const auto t_intersect = std::sqrt((theta_intersect * theta_intersect * this->radius * this->radius + h_p * h_p)
|
||
|
/ (line_theta_r * line_theta_r + this->height * this->height));
|
||
|
|
||
|
struct line_simple_distance_param_t {
|
||
|
double t{};
|
||
|
bool need_refine{false};
|
||
|
bool is_peak_value{true};
|
||
|
} peak_value_param{};
|
||
|
|
||
|
// collect all peak values of f(t) in domain [0, 1], and then find the interval which:
|
||
|
// 1. has one root as extreme minimum point
|
||
|
// 2. the root has smallest distance to t_intersect
|
||
|
{
|
||
|
const auto alpha = this->total_theta * this->radius * r_p;
|
||
|
std::vector<std::pair<double, double>> peak_values(rounded_times_end - rounded_times_start + 1 + 2);
|
||
|
peak_values.front() = {.0, -line_theta_r * r_p * std::sin(theta_p) - h_p};
|
||
|
peak_values.back() = {1., line_theta_r * r_p * std::sin(this->total_theta - theta_p) + this->height - h_p};
|
||
|
algorithm::transform(counting_iterator<size_t>(rounded_times_start),
|
||
|
counting_iterator<size_t>(rounded_times_end + 1),
|
||
|
peak_values.begin() + 1,
|
||
|
[&](size_t k_) -> std::pair<double, double> {
|
||
|
const auto t = (theta_p + PI2 + k_ * PI) * inv_line_theta;
|
||
|
return {std::move(t),
|
||
|
alpha * std::sin(t * this->total_theta - theta_p) + t * this->height - h_p};
|
||
|
});
|
||
|
peak_value_param = algorithm::transform_reduce(
|
||
|
counting_iterator<size_t>{},
|
||
|
counting_iterator<size_t>{peak_values.size() - 1},
|
||
|
line_simple_distance_param_t{},
|
||
|
[&](const line_simple_distance_param_t &lhs, const line_simple_distance_param_t &rhs) {
|
||
|
if (std::abs(lhs.t - t_intersect) < std::abs(rhs.t - t_intersect))
|
||
|
return lhs;
|
||
|
else
|
||
|
return rhs;
|
||
|
},
|
||
|
[&](size_t index) -> line_simple_distance_param_t {
|
||
|
const auto &[t0, f0] = peak_values[index];
|
||
|
const auto &[t1, f1] = peak_values[index + 1];
|
||
|
bool increasing_flag = (f0 > 0 && f1 > 0);
|
||
|
bool decreasing_flag = (f0 < 0 && f1 < 0);
|
||
|
// i.e. has a root t=t0, or distance is always increasing as t increases
|
||
|
if ((f0 >= -EPSILON && f0 <= EPSILON) || increasing_flag) return {t0, false, !increasing_flag};
|
||
|
// i.e. has a root t=t1, or distance is always decreasing as t increases
|
||
|
if ((f1 >= -EPSILON && f1 <= EPSILON) || decreasing_flag) return {t1, false, !decreasing_flag};
|
||
|
// i.e. has a root t in (0, 1)
|
||
|
// in this case, we use linear interpolation as a guess
|
||
|
return {f0 / (f0 - f1), true, true};
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// if we need to refine the guess, just do it!!!
|
||
|
if (peak_value_param.need_refine) {
|
||
|
auto &t = peak_value_param.t;
|
||
|
double delta_t{std::numeric_limits<double>::max()};
|
||
|
const auto alpha = this->total_theta * this->radius * r_p;
|
||
|
const auto alpha_deriv = alpha * this->total_theta;
|
||
|
while (std::abs(delta_t) >= EPSILON) {
|
||
|
// origin equation: f(t)=theta_all*r_all*r_p*sin(t*theta_all-theta_p)+t*height_all-h_p=0
|
||
|
// derivative: f'(t)=theta_all^2*r_all*r_p*cos(t*theta_all-theta_p)+height_all=0
|
||
|
const auto f = alpha * std::sin(t * this->total_theta - theta_p) + t * this->height - h_p;
|
||
|
const auto f_deriv = alpha_deriv * std::cos(t * this->total_theta - theta_p) + this->height;
|
||
|
delta_t = f / f_deriv;
|
||
|
t -= delta_t;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get the final result
|
||
|
return {
|
||
|
{this->radius * std::cos(peak_value_param.t * this->total_theta),
|
||
|
this->radius * std::sin(peak_value_param.t * this->total_theta),
|
||
|
peak_value_param.t * this->height,
|
||
|
1},
|
||
|
peak_value_param.t,
|
||
|
peak_value_param.is_peak_value
|
||
|
};
|
||
|
}
|
||
|
} // namespace internal
|