std::optional<std::tuple<double, double, double, double>> FitPlane(const QList<QVector3D>& points)
{
if (points.size() < 3) {
return std::nullopt;
}
double x0x0 = 0, x0y1 = 0, x0z2 = 0, x0_3 = 0;
double x1y0 = 0, y1y1 = 0, y1z2 = 0, y1_3 = 0;
double x2z0 = 0, y2z1 = 0, z2z2 = 0, z2_3 = 0;
double x3_0 = 0, y3_1 = 0, z3_2 = 0, n_3_3 = 0;
for (auto&& point : points) {
x0x0 += point.x() * point.x();
x0y1 += point.x() * point.y();
x0z2 += point.x() * point.z();
x0_3 += point.x();
x1y0 += point.x() * point.y();
y1y1 += point.y() * point.y();
y1z2 += point.y() * point.z();
y1_3 += point.y();
x2z0 += point.x() * point.z();
y2z1 += point.y() * point.z();
z2z2 += point.z() * point.z();
z2_3 += point.z();
x3_0 += point.x();
y3_1 += point.y();
z3_2 += point.z();
}
n_3_3 = points.size();
Eigen::Matrix4d A1;
A1 << x0x0, x0y1, x0z2, x0_3, x1y0, y1y1, y1z2, y1_3, x2z0, y2z1, z2z2, z2_3, x3_0, y3_1, z3_2, n_3_3;
try {
Eigen::JacobiSVD<Eigen::Matrix4d> svd(A1, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Matrix4d V = svd.matrixV();
Eigen::Vector4d result = V.col(3);
result.normalize();
return std::make_tuple(result(0), result(1), result(2), result(3));
} catch (...) {
return std::nullopt;
}
}
std::optional<QPair<double, QVector3D>> Fit2DCircle(const QList<QVector3D>& points)
{
if (points.size() < 3) {
return { std::nullopt };
}
Eigen::Vector3d center;
double radius = 0.0f;
double sum_x = 0.0f, sum_y = 0.0f;
double sum_x2 = 0.0f, sum_y2 = 0.0f;
double sum_x3 = 0.0f, sum_y3 = 0.0f;
double sum_xy = 0.0f, sum_x1y2 = 0.0f, sum_x2y1 = 0.0f;
int N = points.size();
for (int i = 0; i < N; i++) {
double x = points[i].x();
double y = points[i].y();
double x2 = x * x;
double y2 = y * y;
sum_x += x;
sum_y += y;
sum_x2 += x2;
sum_y2 += y2;
sum_x3 += x2 * x;
sum_y3 += y2 * y;
sum_xy += x * y;
sum_x1y2 += x * y2;
sum_x2y1 += x2 * y;
}
double C, D, E, G, H;
double a, b, c;
C = N * sum_x2 - sum_x * sum_x;
D = N * sum_xy - sum_x * sum_y;
E = N * sum_x3 + N * sum_x1y2 - (sum_x2 + sum_y2) * sum_x;
G = N * sum_y2 - sum_y * sum_y;
H = N * sum_x2y1 + N * sum_y3 - (sum_x2 + sum_y2) * sum_y;
a = (H * D - E * G) / (C * G - D * D);
b = (H * C - E * D) / (D * D - G * C);
c = -(a * sum_x + b * sum_y + sum_x2 + sum_y2) / N;
center[0] = a / (-2);
center[1] = b / (-2);
radius = sqrt(a * a + b * b - 4 * c) / 2;
return std::make_pair(radius, QVector3D(center[0], center[1], 0));
}
std::optional<std::tuple<double, QVector3D, QVector3D>> Fit3DCircle(const QList<QVector3D>& points)
{
if (points.size() < 3) {
return { std::nullopt };
}
QList<QVector3D> cycle_pts = points;
auto&& plane = Utils::Plane::FitPlane(cycle_pts);
if (!plane.has_value()) {
return { std::nullopt };
}
auto&& [A, B, C, D] = plane.value();
QVector3D abcd(DF(A), DF(B), DF(C));
Eigen::Vector3d normal(A, B, C);
Eigen::Vector3d axis = normal.cross(Eigen::Vector3d::UnitZ());
axis.normalize();
double rad = acos(normal.dot(Eigen::Vector3d::UnitZ()) / normal.norm());
Eigen::AngleAxisd rotation(rad, axis);
Eigen::Matrix3d rotation_matrix = rotation.toRotationMatrix();
for (auto&& point : cycle_pts) {
Eigen::Vector3d eigen_point(point.x(), point.y(), point.z());
Eigen::Vector3d rotated_point = rotation_matrix * eigen_point;
point.setX(rotated_point.x());
point.setY(rotated_point.y());
point.setZ(rotated_point.z());
}
double z = -D / C;
Eigen::Vector3d v = normal;
v = rotation_matrix * v;
if (v.z() * normal.z() < 0) {
z = -z;
}
plane = Utils::Plane::FitPlane(cycle_pts);
if (!plane.has_value()) {
return { std::nullopt };
}
auto&& circle = Fit2DCircle(cycle_pts);
if (!circle.has_value()) {
return { std::nullopt };
}
Eigen::Vector3d center(std::get<1>(circle.value()).x(), std::get<1>(circle.value()).y(), z);
Eigen::Vector3d rotated_center = rotation_matrix.inverse() * center;
QVector3D center_point(rotated_center.x(), rotated_center.y(), rotated_center.z());
Eigen::Vector3d p1 = Eigen::Vector3d(cycle_pts[0].x(), cycle_pts[0].y(), cycle_pts[0].z());
Eigen::Vector3d r_p1 = rotation_matrix.inverse() * p1;
double radius = std::get<0>(circle.value());
return { { radius, center_point, abcd } };
}