#include <igl/opengl/glfw/Viewer.h>
#include <igl/opengl/glfw/imgui/ImGuiHelpers.h>
#include <igl/opengl/glfw/imgui/ImGuiMenu.h>
#include <igl/png/writePNG.h>
#include <igl/readOBJ.h>
#include <igl/remesh_along_isoline.h>
#include <imgui/imgui.h>
#include <algorithm>
#include <iostream>
#include "arap-mold.h"
#include "mesh.h"
#include "surcut.h"
#include "viridis.h"
using igl::opengl::glfw::Viewer;
using Eigen::Matrix4d;
using Eigen::MatrixXd;
using Eigen::MatrixXi;
using Eigen::Vector3d;
using Eigen::Vector4d;
using Eigen::VectorXd;
using Eigen::VectorXi;
void draw_plane(Viewer &, Mesh &);
void draw_menu(Mesh &);
void update_vectors();
// Toggle running of ARAP
bool run_arap = false;
// Run arap, and when it converges update the rest state.
bool run_with_update = false;
// Show rotations for all *faces*
bool show_list = false;
// Visualize the forces on each vertex when stitching
bool show_forces = false;
// Cut plane and point with `float`, for ImGui.
Eigen::Vector3f fplane_p;
Eigen::Vector3f fplane_n;
// Model size properties
float scale; /* Minimal span for any of the three dimensions of the model */
Eigen::Vector3f mins, maxs;
// The libigl viewer.
Viewer viewer;
MatrixXd forces_endpoints;
MatrixXd forces_colors;
// Set the real `plane` vectors.
void update_vectors() {
plane_p = fplane_p.cast<double>();
plane_n = fplane_n.cast<double>().normalized();
}
void update_viewer(Viewer &viewer, Mesh &mesh) {
forces_colors = MatrixXd::Zero(mesh.vertices.rows(), 3);
for (int v = 0; v < mesh.vertices.rows(); v++) {
auto force = mesh.forces.row(v);
double ff = force.norm() / scale;
if (ff > 1.0)
ff = 1.0;
double *c = plasma(ff);
for (int i = 0; i < 3; i++)
forces_colors(v, i) = c[i];
}
draw_plane(viewer, mesh);
viewer.data().set_mesh(mesh.vertices, mesh.faces);
viewer.data().set_normals(mesh.normals);
viewer.data().set_colors(mesh.face_colors);
if (show_forces) {
viewer.data().add_edges(mesh.vertices, forces_endpoints, forces_colors);
}
}
// Draw the custom menu
void draw_menu(Mesh &mesh) {
bool recompute = false;
bool px = ImGui::SliderFloat("plane_p.x", &fplane_p[0], mins.x(), maxs.x(), "%.4f");
bool py = ImGui::SliderFloat("plane_p.y", &fplane_p[1], mins.y(), maxs.y(), "%.4f");
bool pz = ImGui::SliderFloat("plane_p.z", &fplane_p[2], mins.z(), maxs.z(), "%.4f");
bool nx = ImGui::SliderFloat("plane_n.x", &fplane_n[0], -1.0, 1.0, "%.2f");
bool ny = ImGui::SliderFloat("plane_n.y", &fplane_n[1], -1.0, 1.0, "%.2f");
bool nz = ImGui::SliderFloat("plane_n.z", &fplane_n[2], -1.0, 1.0, "%.2f");
if (px || py || pz || nx || ny || nz) {
plane_p = fplane_p.cast<double>();
plane_n = fplane_n.cast<double>().normalized();
update_vectors();
recompute |= true;
}
if (!run_arap) {
if (ImGui::Button("Run ARAP"))
run_arap = true;
} else {
if (ImGui::Button("Stop [Run ARAP]"))
run_arap = false;
}
if (!run_with_update) {
if (ImGui::Button("Run and Update")) {
run_with_update = true;
}
} else {
if (ImGui::Button("Stop [Run and Update]")) {
run_with_update = false;
}
}
ImGui::Text("iter count: %d", iteration_count);
if (ImGui::Button("Reset")) {
mesh.vertices = mesh.rest_state;
recompute |= true;
}
if (ImGui::Button("Update Rest State")) {
mesh.rest_state = mesh.vertices;
arap::precomputation(mesh, arap_data);
recompute |= true;
}
if (ImGui::Button("Step ARAP")) {
step_arap(mesh);
}
// Attempt at remeshing: Doesn't really work, since we probably need to tell it that we should
// discard very accute triangles, or something.
// if (ImGui::Button("Remesh")) {
// VectorXd field = VectorXd::Zero(mesh.vertices.rows());
//
// MatrixXd new_vertices;
// MatrixXi new_faces;
// VectorXd new_field;
// VectorXi birth_triangles;
// Eigen::SparseMatrix<double> bary_coords;
// VectorXi face_above_field;
// igl::remesh_along_isoline(mesh.vertices, mesh.faces, field, 0.0,
// new_vertices, new_faces, new_field, birth_triangles, bary_coords, face_above_field);
// mesh.vertices = new_vertices;
// mesh.faces = new_faces;
// recompute |= true;
// }
ImGui::Checkbox("Blend rotations", &arap_args.should_blend);
if (arap_args.should_blend) {
ImGui::SliderFloat("blend", &arap_args.blend, 0.0, 1.0, "%.2f");
}
if (ImGui::Checkbox("Stiching", &stitching)) {
if (!stitching)
mesh.forces = MatrixXd::Zero(mesh.vertices.rows(), mesh.vertices.cols());
recompute |= true;
}
if (stitching) {
ImGui::Indent();
ImGui::Checkbox("Show Forces", &show_forces);
if (ImGui::SliderFloat("Force", &force_scale, 0.1, 10.0, "%.2f"))
recompute |= true;
if (ImGui::Checkbox("Force ~ Area", &area_force))
recompute |= true;
ImGui::Checkbox("Distance Cutoff", &force_cutoff);
if (force_cutoff && ImGui::SliderFloat("Distance Cutoff", &force_dist_cutoff, 0.1, 10.0, "%.2f"))
recompute |= true;
ImGui::Unindent();
}
ImGui::Checkbox("Skip Moldability Rotations", ®ular_arap);
ImGui::Checkbox("SLERP 2 rotations", &arap_args.slerp_2_rots);
ImGui::Checkbox("Rotate before mold check", &arap_args.rotate_before);
ImGui::Checkbox("Show Rotations (mold/arap)", &show_list);
if (show_list) {
static bool either = true, both = false;
if (ImGui::BeginCombo("Filter zeroes", either ? "Either >0" : "Both >0")) {
if (ImGui::Selectable("Either >0")) {
either = true;
both = false;
};
if (ImGui::Selectable("Both >0")) {
either = false;
both = true;
};
ImGui::EndCombo();
}
char buffer[80] = {};
ImGui::BeginChild("scrolling");
for (size_t i = 0; i < arap_data.mht_face_mold_angle.size(); i++) {
float ma = arap_data.mht_face_mold_angle[i];
float aa = arap_data.mht_face_arap_angle[i];
int offset = sprintf(buffer, "%zu: ", i);
float limit = 0.01;
if ((either && ((ma > limit) || (aa > limit))) || (both && ((ma > limit) && (aa > limit)))) {
sprintf(buffer + offset, "%3.2f / %3.2f", ma, aa);
}
ImGui::Text("%s", buffer);
}
ImGui::EndChild();
}
if (recompute) {
compute_things(mesh);
}
}
// Draw the cut plane in a very hacky way.
void draw_plane(Viewer &viewer, Mesh &mesh) {
viewer.data().lines.resize(0, 0);
Eigen::Vector3d dir1(plane_n.cross(Eigen::Vector3d(0, 0, 1)));
if (dir1.norm() < 0.5) {
dir1 = Eigen::Vector3d(plane_n.cross(Eigen::Vector3d(0, 1, 0)));
}
Eigen::Vector3d dir2(plane_n.cross(dir1));
dir1 *= scale;
dir2 *= scale;
// This is terrible!
Eigen::MatrixXd planeEA(16, 3);
planeEA.row(0) = plane_p - dir1 + dir2;
planeEA.row(1) = plane_p + dir2;
planeEA.row(2) = plane_p - dir1;
planeEA.row(3) = plane_p - dir1;
planeEA.row(4) = plane_p; // up
planeEA.row(5) = plane_p;
planeEA.row(6) = plane_p + dir1;
planeEA.row(7) = plane_p - dir1; // from left
planeEA.row(8) = plane_p; // left
planeEA.row(9) = plane_p - dir1 - dir2;
planeEA.row(10) = plane_p - dir1 - dir2;
planeEA.row(11) = plane_p - dir2; // from bot
planeEA.row(12) = plane_p - dir2;
planeEA.row(13) = plane_p + dir1 - dir2;
planeEA.row(14) = plane_p - dir1 - dir2;
planeEA.row(15) = plane_p - dir2;
Eigen::MatrixXd planeEB(16, 3);
planeEB.row(0) = plane_p + dir2;
planeEB.row(1) = plane_p + dir1 + dir2;
planeEB.row(2) = plane_p - dir1 + dir2;
planeEB.row(3) = plane_p + dir2;
planeEB.row(4) = plane_p + dir2;
planeEB.row(5) = plane_p + dir1 + dir2;
planeEB.row(6) = plane_p + dir1 + dir2;
planeEB.row(7) = plane_p;
planeEB.row(8) = plane_p + dir1;
planeEB.row(9) = plane_p - dir1;
planeEB.row(10) = plane_p;
planeEB.row(11) = plane_p;
planeEB.row(12) = plane_p + dir1;
planeEB.row(13) = plane_p + dir1;
planeEB.row(14) = plane_p - dir2;
planeEB.row(15) = plane_p + dir1 - dir2;
Eigen::RowVector3d c(1.0, 1.0, 1.0);
viewer.data().add_edges(planeEA, planeEB, c);
}
int main(int argc, char *argv[]) {
int error;
using namespace std;
if (argc != 2) {
cout << "Usage: ./surcut <input mesh>" << endl;
return 1;
}
TetmeshOut tetmesh_out;
{
MatrixXd read_verts;
MatrixXi read_faces;
igl::readOBJ(argv[1], read_verts, read_faces);
TetmeshIn tetmesh_in;
tetmesh_in.vertices = &read_verts;
tetmesh_in.faces = &read_faces;
error = make_tetmesh(tetmesh_in, tetmesh_out);
if (error) {
cerr << "[ERR] tetesh returned " << error << endl;
exit(1);
}
}
Mesh mesh(tetmesh_out);
mesh.boundary = VectorXi(4);
mesh.boundary << 0, 1, 2, 3;
mesh_print_sizes(mesh);
forces_endpoints_ptr = &forces_endpoints; /* this is out of place */
arap_data.faces = &mesh.faces;
arap_data.normals = &mesh.normals;
arap_data.mht_tf2t = mesh.face2tets;
arap_data.mht_plane_p = &plane_p;
arap_data.mht_plane_n = &plane_n;
arap_data.vert_is_above = &is_point_above;
arap_data.moldable = &moldable;
arap_data.orientation = &orientation;
arap_data.U0 = mesh.vertices;
arap_data.with_dynamics = true;
// XXX(todo): what's a reasonable value here?
arap_data.h = 0.01;
arap::precomputation(mesh, arap_data);
maxs = mesh.vertices.colwise().maxCoeff().cast<float>();
mins = mesh.vertices.colwise().minCoeff().cast<float>();
scale = (maxs - mins).minCoeff();
fplane_p = ((maxs + mins) / 2.0);
fplane_n = Eigen::Vector3f(0.0, 0.0, 1.0);
update_vectors();
igl::opengl::glfw::imgui::ImGuiMenu menu;
viewer.plugins.push_back(&menu);
menu.callback_draw_custom_window = [&mesh]() { draw_menu(mesh); };
menu.callback_draw_viewer_menu = [&menu]() { menu.draw_viewer_menu(); };
viewer.callback_pre_draw = [&mesh](Viewer &viewer) {
if (run_with_update) {
if (step_arap(mesh) == StepReturn::Converged) {
mesh.rest_state = mesh.vertices;
arap::precomputation(mesh, arap_data);
compute_things(mesh);
}
} else {
if (run_arap) {
step_arap(mesh);
}
}
update_viewer(viewer, mesh);
return false;
};
viewer.callback_key_down = [&mesh, &tetmesh_out](Viewer &viewer, unsigned char key, int mod) {
if (key == ' ') {
run_arap = !run_arap;
} else if (key == '.') {
step_arap(mesh);
update_viewer(viewer, mesh);
} else if (key == 'R') {
mesh.vertices = mesh.rest_state = tetmesh_out.vertices;
arap::precomputation(mesh, arap_data);
compute_things(mesh);
update_viewer(viewer, mesh);
} else if (key == 'P') {
Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> R(1280, 800);
Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> G(1280, 800);
Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> B(1280, 800);
Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> A(1280, 800);
viewer.core().draw_buffer(viewer.data(), false, R, G, B, A);
igl::png::writePNG(R, G, B, A, "out.png");
}
return false;
};
viewer.data().clear();
// viewer.data().set_face_based(true);
viewer.data().set_mesh(mesh.vertices, mesh.faces);
viewer.core().is_animating = true;
compute_things(mesh);
viewer.launch();
}