diff --git a/README.html b/README.html index 6c13281..c163d77 100644 --- a/README.html +++ b/README.html @@ -343,7 +343,7 @@

Constant function approximation

Linear function approximation

-

If we make make a slightly more appropriate assuming that in the neighborhood +

If we make make a slightly more appropriate assumption that in the neighborhood of the \(P_Y(\z₀)\) the surface \(Y\) is a plane, then we can improve this approximation while keeping \(\f\) linear in \(\z\):

diff --git a/README.md b/README.md index bce5dc2..ce58753 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ derived our gradients geometrically. ### Linear function approximation -If we make make a slightly more appropriate assuming that in the neighborhood +If we make make a slightly more appropriate assumption that in the neighborhood of the $P_Y(\z₀)$ the surface $Y$ is a plane, then we can improve this approximation while keeping $\f$ linear in $\z$: diff --git a/include/closest_rotation.h b/include/closest_rotation.h index cc67fd0..93a7621 100644 --- a/include/closest_rotation.h +++ b/include/closest_rotation.h @@ -1,6 +1,9 @@ #ifndef CLOSEST_ROTATION_H #define CLOSEST_ROTATION_H #include +#include +#include + // Given a 3×3 matrix `M`, find the closest rotation matrix `R`. // // Inputs: diff --git a/main.cpp b/main.cpp index 3115cc9..69a6887 100644 --- a/main.cpp +++ b/main.cpp @@ -85,7 +85,7 @@ int main(int argc, char *argv[]) Eigen::RowVector3d t; icp_single_iteration(VX,FX,VY,FY,num_samples,method,R,t); // Apply transformation to source mesh - VX = ((VX*R).rowwise() + t).eval(); + VX = ((VX*R.transpose()).rowwise() + t).eval(); set_meshes(); if(show_samples) { diff --git a/src/closest_rotation.cpp b/src/closest_rotation.cpp index 54403c1..462fd1b 100644 --- a/src/closest_rotation.cpp +++ b/src/closest_rotation.cpp @@ -1,9 +1,14 @@ #include "closest_rotation.h" void closest_rotation( - const Eigen::Matrix3d & M, - Eigen::Matrix3d & R) + const Eigen::Matrix3d & M, + Eigen::Matrix3d & R) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); + Eigen::JacobiSVD svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV); + Eigen::MatrixXd U = svd.matrixU(); + Eigen::MatrixXd V = svd.matrixV().transpose(); + Eigen::MatrixXd UV = U * V; + Eigen::Vector3d omega(1, 1, UV.determinant()); + Eigen::Matrix3d Omega = omega.asDiagonal(); + R = U * Omega * V; } diff --git a/src/hausdorff_lower_bound.cpp b/src/hausdorff_lower_bound.cpp index 8608964..2fd18c8 100644 --- a/src/hausdorff_lower_bound.cpp +++ b/src/hausdorff_lower_bound.cpp @@ -1,12 +1,17 @@ #include "hausdorff_lower_bound.h" +#include "point_mesh_distance.h" +#include "random_points_on_mesh.h" double hausdorff_lower_bound( - const Eigen::MatrixXd & VX, - const Eigen::MatrixXi & FX, - const Eigen::MatrixXd & VY, - const Eigen::MatrixXi & FY, - const int n) + const Eigen::MatrixXd & VX, + const Eigen::MatrixXi & FX, + const Eigen::MatrixXd & VY, + const Eigen::MatrixXi & FY, + const int n) { - // Replace with your code - return 0; + Eigen::VectorXd D; + Eigen::MatrixXd X, P, N; + random_points_on_mesh(n, VX, FX, X); + point_mesh_distance(X, VY, FY, D, P, N); + return D.maxCoeff(); } diff --git a/src/icp_single_iteration.cpp b/src/icp_single_iteration.cpp index 1e8fda9..5856b51 100644 --- a/src/icp_single_iteration.cpp +++ b/src/icp_single_iteration.cpp @@ -1,16 +1,30 @@ #include "icp_single_iteration.h" +#include "point_mesh_distance.h" +#include "point_to_plane_rigid_matching.h" +#include "point_to_point_rigid_matching.h" +#include "random_points_on_mesh.h" void icp_single_iteration( - const Eigen::MatrixXd & VX, - const Eigen::MatrixXi & FX, - const Eigen::MatrixXd & VY, - const Eigen::MatrixXi & FY, - const int num_samples, - const ICPMethod method, - Eigen::Matrix3d & R, - Eigen::RowVector3d & t) + const Eigen::MatrixXd & VX, + const Eigen::MatrixXi & FX, + const Eigen::MatrixXd & VY, + const Eigen::MatrixXi & FY, + const int num_samples, + const ICPMethod method, + Eigen::Matrix3d & R, + Eigen::RowVector3d & t) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); - t = Eigen::RowVector3d::Zero(); + Eigen::MatrixXd X, P, N; + Eigen::VectorXd D; + random_points_on_mesh(num_samples, VX, FX, X); + point_mesh_distance(X, VY, FY, D, P, N); + + if (method == ICP_METHOD_POINT_TO_POINT) + { + point_to_point_rigid_matching(X, P, R, t); + } + else + { + point_to_plane_rigid_matching(X, P, N, R, t); + } } diff --git a/src/point_mesh_distance.cpp b/src/point_mesh_distance.cpp index 2e6a070..962c74b 100644 --- a/src/point_mesh_distance.cpp +++ b/src/point_mesh_distance.cpp @@ -1,16 +1,101 @@ #include "point_mesh_distance.h" +#include "point_triangle_distance.h" +#include +#include +#include + +struct Triangle +{ + // each row is one vertex on this triangle + Eigen::Matrix3d vertices; + + // row in FY that defines this triangle + int frow; +}; + +Eigen::AlignedBox3d bounding_box(Triangle t) +{ + Eigen::Vector3d corner1 = t.vertices.colwise().minCoeff(); + Eigen::Vector3d corner2 = t.vertices.colwise().maxCoeff(); + return Eigen::AlignedBox3d(corner1, corner2); +} + +struct PointTriangleMinimizer +{ + typedef double Scalar; + Eigen::RowVector3d query_point; + + double shortest_dist = -1; + Eigen::RowVector3d closest_point; + + // the row in FY that defines the triangle on which closest_point lies + int frow; + + PointTriangleMinimizer(Eigen::RowVector3d x) + { + query_point = x; + } + + // Shortest distance from query_point to bounding box b + double minimumOnVolume(const Eigen::AlignedBox3d & b) + { + Eigen::Matrix3d D; + D << b.corner(b.BottomLeftFloor) - query_point.transpose(), + Eigen::Vector3d::Zero(), + query_point.transpose() - b.corner(b.TopRightCeil); + return D.rowwise().maxCoeff().norm(); + } + + // Shortest distance from query_point to triangle t + double minimumOnObject(const Triangle & t) + { + double d; + Eigen::RowVector3d p; + point_triangle_distance(query_point, t.vertices.row(0), t.vertices.row(1), t.vertices.row(2), d, p); + + if (d < shortest_dist || shortest_dist == -1) + { + shortest_dist = d; + closest_point = p; + frow = t.frow; + } + + return d; + } +}; + void point_mesh_distance( - const Eigen::MatrixXd & X, - const Eigen::MatrixXd & VY, - const Eigen::MatrixXi & FY, - Eigen::VectorXd & D, - Eigen::MatrixXd & P, - Eigen::MatrixXd & N) + const Eigen::MatrixXd & X, + const Eigen::MatrixXd & VY, + const Eigen::MatrixXi & FY, + Eigen::VectorXd & D, + Eigen::MatrixXd & P, + Eigen::MatrixXd & N) { - // Replace with your code - P.resizeLike(X); - N = Eigen::MatrixXd::Zero(X.rows(),X.cols()); - for(int i = 0;i triangles; + for (int j = 0; j < FY.rows(); j++) { + Eigen::RowVector3i fy = FY.row(j); + Triangle t; + t.vertices << VY.row(fy[0]), VY.row(fy[1]), VY.row(fy[2]); + t.frow = j; + triangles.push_back(t); + } + + Eigen::KdBVH triangle_tree(triangles.begin(), triangles.end()); + + for (int i = 0; i < X.rows(); i++) + { + PointTriangleMinimizer minimizer(X.row(i)); + D[i] = Eigen::BVMinimize(triangle_tree, minimizer); + P.row(i) = minimizer.closest_point; + N.row(i) = all_normals.row(minimizer.frow); + } } diff --git a/src/point_to_plane_rigid_matching.cpp b/src/point_to_plane_rigid_matching.cpp index 1978510..34d93ad 100644 --- a/src/point_to_plane_rigid_matching.cpp +++ b/src/point_to_plane_rigid_matching.cpp @@ -1,13 +1,42 @@ +#include "closest_rotation.h" #include "point_to_plane_rigid_matching.h" +#include void point_to_plane_rigid_matching( - const Eigen::MatrixXd & X, - const Eigen::MatrixXd & P, - const Eigen::MatrixXd & N, - Eigen::Matrix3d & R, - Eigen::RowVector3d & t) + const Eigen::MatrixXd & X, + const Eigen::MatrixXd & P, + const Eigen::MatrixXd & N, + Eigen::Matrix3d & R, + Eigen::RowVector3d & t) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); - t = Eigen::RowVector3d::Zero(); + Eigen::MatrixXd XN(X.rows(), 3); + for (int i = 0; i < X.rows(); i++) + { + Eigen::RowVector3d x = X.row(i); + Eigen::RowVector3d n = N.row(i); + XN.row(i) = x.cross(n); + } + + Eigen::MatrixXd A(P.rows(), 6); + A << XN, N; + + Eigen::VectorXd b(X.rows()); + for (int i = 0; i < X.rows(); i++) { + b[i] = -(X.row(i) - P.row(i)).dot(N.row(i)); + } + + Eigen::VectorXd u = A.colPivHouseholderQr().solve(b); + Eigen::Matrix3d U; + + // first three elements of u define the angles of rotation + // about the x, y, and z axis, in that order + U << Eigen::RowVector3d(0, -u[2], u[1]), + Eigen::RowVector3d(u[2], 0, -u[0]), + Eigen::RowVector3d(-u[1], u[0], 0); + + Eigen::MatrixXd M = Eigen::Matrix3d::Identity() + U; + closest_rotation(M, R); + + // last three elements of u define the translation + t << u[3], u[4], u[5]; } diff --git a/src/point_to_point_rigid_matching.cpp b/src/point_to_point_rigid_matching.cpp index 079151f..241f11b 100644 --- a/src/point_to_point_rigid_matching.cpp +++ b/src/point_to_point_rigid_matching.cpp @@ -1,14 +1,20 @@ +#include "closest_rotation.h" #include "point_to_point_rigid_matching.h" #include void point_to_point_rigid_matching( - const Eigen::MatrixXd & X, - const Eigen::MatrixXd & P, - Eigen::Matrix3d & R, - Eigen::RowVector3d & t) + const Eigen::MatrixXd & X, + const Eigen::MatrixXd & P, + Eigen::Matrix3d & R, + Eigen::RowVector3d & t) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); - t = Eigen::RowVector3d::Zero(); + Eigen::Vector3d x_centroid = X.colwise().mean(); + Eigen::Vector3d p_centroid = P.colwise().mean(); + + Eigen::MatrixXd X_bar = (X.rowwise() - x_centroid.transpose()).eval(); + Eigen::MatrixXd P_bar = (P.rowwise() - p_centroid.transpose()).eval(); + + closest_rotation(P_bar.transpose() * X_bar, R); + t = p_centroid - R * x_centroid; } diff --git a/src/point_triangle_distance.cpp b/src/point_triangle_distance.cpp index 6405100..cf59ed8 100644 --- a/src/point_triangle_distance.cpp +++ b/src/point_triangle_distance.cpp @@ -1,14 +1,104 @@ #include "point_triangle_distance.h" +#include + +// Returns area of a triangle with vertices v1, v2, and v3. +double triangle_area( + const Eigen::Vector3d & v1, + const Eigen::Vector3d & v2, + const Eigen::Vector3d & v3) +{ + return ((v1 - v2).cross(v1 - v3)).norm() / 2; +} + +// Returns true if the projection of x onto the plane +// defined by a triangle with vertices a, b, and c +// is inside the triangle, false otherwise. Outputs +// the coordinates of the projection proj regardless. +bool projection_in_triangle( + const Eigen::Vector3d & x, + const Eigen::Vector3d & a, + const Eigen::Vector3d & b, + const Eigen::Vector3d & c, + Eigen::Vector3d & proj) +{ + Eigen::Vector3d n = (c - a).cross(b - a); // normal to plane + Eigen::Vector3d u = n / n.norm(); // unit normal + double d = u.dot(x - a); + + proj = x - d * u; + + return triangle_area(a, b, c) >= + triangle_area(proj, b, c) + + triangle_area(a, proj, c) + + triangle_area(a, b, proj); +} + +// Outputs the shortest distance d from x to the line +// segment defined by vectors v1 and v2, and the coordinates +// of the closest point p. +void point_line_distance( + const Eigen::RowVector3d x, + const Eigen::RowVector3d v1, + const Eigen::RowVector3d v2, + double & d, + Eigen::RowVector3d & p) +{ + Eigen::RowVector3d u = (v1 - v2) / (v1 - v2).norm(); + p = (v1 - x).dot(u) * u; + double dist = (p - v1).dot(u); + + if (dist < 0 || dist > (v1 - v2).norm()) + { + // p is not on the line segment, so we set it to the the nearest vertex + p = (x - v1).norm() < (x - v2).norm() ? v1 : v2; + } + + d = (x - p).norm(); +} void point_triangle_distance( - const Eigen::RowVector3d & x, - const Eigen::RowVector3d & a, - const Eigen::RowVector3d & b, - const Eigen::RowVector3d & c, - double & d, - Eigen::RowVector3d & p) -{ - // Replace with your code - d = 0; - p = a; + const Eigen::RowVector3d & x, + const Eigen::RowVector3d & a, + const Eigen::RowVector3d & b, + const Eigen::RowVector3d & c, + double & d, + Eigen::RowVector3d & p) +{ + double da = (x - a).norm(); + + // If x is more than 5 triangle side lengths away + // from the triangle, approximate distances will suffice + // so we choose an arbitrary point. + if (da > 5 * (a - b).norm()) + { + d = da; + p = a; + return; + } + + Eigen::Vector3d proj; + if (projection_in_triangle(x.transpose(), a.transpose(), b.transpose(), c.transpose(), proj)) + { + p = proj.transpose(); + d = (x - p).norm(); + } + else + { + double dists[3]; + Eigen::RowVector3d points[3]; + + point_line_distance(x, a, b, dists[0], points[0]); + point_line_distance(x, a, c, dists[1], points[1]); + point_line_distance(x, b, c, dists[2], points[2]); + + d = -1; + for (int i = 0; i < 3; i++) + { + if (d == -1 || d > dists[i]) + { + d = dists[i]; + p = points[i]; + } + } + } } diff --git a/src/random_points_on_mesh.cpp b/src/random_points_on_mesh.cpp index 6e85d75..9be1d0b 100644 --- a/src/random_points_on_mesh.cpp +++ b/src/random_points_on_mesh.cpp @@ -1,13 +1,62 @@ #include "random_points_on_mesh.h" +#include +#include -void random_points_on_mesh( - const int n, - const Eigen::MatrixXd & V, - const Eigen::MatrixXi & F, - Eigen::MatrixXd & X) +int random_triangle(Eigen::VectorXd cumsum) +{ + double gamma = (double)rand() / RAND_MAX; + int L = 0; + int R = cumsum.size() - 1; + while (L != R) + { + int m = (L + R) / 2; + if (cumsum(m) <= gamma) + { + L = m + 1; + } + else + { + R = m; + } + } + + return L; +} + +Eigen::Vector3d random_point_in_triangle( + Eigen::Vector3d v1, + Eigen::Vector3d v2, + Eigen::Vector3d v3) { - // REPLACE WITH YOUR CODE: - X.resize(n,3); - for(int i = 0;i 1) + { + alpha = 1 - alpha; + beta = 1 - beta; + } + + return v1 + alpha * (v2 - v1) + beta * (v3 - v1); } +void random_points_on_mesh( + const int n, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + Eigen::MatrixXd & X) +{ + Eigen::VectorXd dblA, cumsum; + igl::doublearea(V, F, dblA); + igl::cumsum(dblA, 1, cumsum); + double totalA = dblA.sum(); + cumsum = cumsum / totalA; + + X.resize(n, 3); + + for (int i = 0; i < n; i++) + { + Eigen::Vector3i t = F.row(random_triangle(cumsum)); + X.row(i) = random_point_in_triangle(V.row(t[0]), V.row(t[1]), V.row(t[2])); + } +}