#include "util.h" #include "precise_geometry.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { struct Crossing { int face = -1; Edge edge_a; Edge edge_b; double t = 0.0; double u = 0.0; }; struct EdgeFaceIntersection { Edge edge; int face = -1; double t = 0.0; bool on_boundary = false; }; struct PlanarityResidual { int face = -1; double max_abs = 0.0; double rms = 0.0; }; struct Verification { std::string obj_path; double eps = 1e-12; int topology = 4; size_t vertices = 0; size_t faces = 0; size_t edges = 0; int euler = 0; int genus = 0; std::vector face_sizes; std::vector unique_edges; std::vector bad_edge_incidence; std::vector> bad_face_pairs; std::vector crossings; std::vector intersections; std::vector planarity; int original_crossings = 0; int original_intersections = 0; bool finite = false; double geometry_scale = 0.0; double min_edge_length = 0.0; double max_planarity_residual = 0.0; }; Edge canonical_edge(int a, int b) { return a < b ? Edge(a, b) : Edge(b, a); } std::string edge_obj_string(const Edge& edge) { std::ostringstream out; out << "[" << edge.first + 1 << "," << edge.second + 1 << "]"; return out.str(); } bool segment_intersection_2d( const Vector2d& a, const Vector2d& b, const Vector2d& c, const Vector2d& d, double eps, double& t, double& u ) { return precise_segment_intersection_2d(a, b, c, d, eps, &t, &u); } std::map> make_edge_to_faces(const Faces& faces) { std::map> result; for (size_t face_ix = 0; face_ix < faces.size(); ++face_ix) { const Face& face = faces[face_ix]; for (size_t i = 0; i < face.size(); ++i) { result[canonical_edge(face[i], face[(i + 1) % face.size()])].push_back((int)face_ix); } } return result; } std::vector find_self_crossings(const Verts3D& verts, const Faces& faces, const Planes& planes, double eps) { std::vector result; for (size_t face_ix = 0; face_ix < faces.size(); ++face_ix) { const Face& face = faces[face_ix]; Verts2D projected; make_2d_projection(verts, face, planes[face_ix], projected); const size_t n = face.size(); for (size_t i = 0; i < n; ++i) { const size_t i_next = (i + 1) % n; for (size_t j = i + 1; j < n; ++j) { const size_t j_next = (j + 1) % n; if (i_next == j || j_next == i) { continue; } std::set vertices = {face[i], face[i_next], face[j], face[j_next]}; if (vertices.size() < 4) { continue; } double t = 0.0; double u = 0.0; if (segment_intersection_2d(projected[i], projected[i_next], projected[j], projected[j_next], eps, t, u)) { Crossing crossing; crossing.face = (int)face_ix; crossing.edge_a = Edge(face[i], face[i_next]); crossing.edge_b = Edge(face[j], face[j_next]); crossing.t = t; crossing.u = u; result.push_back(crossing); } } } } return result; } std::vector find_edge_face_intersections( const Verts3D& verts, const Faces& faces, const Planes& planes, const std::vector& edges, double eps ) { std::vector result; std::vector> face_vertices; face_vertices.reserve(faces.size()); for (const Face& face : faces) { face_vertices.emplace_back(face.begin(), face.end()); } for (const Edge& edge : edges) { const int a_ix = edge.first; const int b_ix = edge.second; const Vector3d a = verts[a_ix]; const Vector3d b = verts[b_ix]; for (size_t face_ix = 0; face_ix < faces.size(); ++face_ix) { if (face_vertices[face_ix].count(a_ix) || face_vertices[face_ix].count(b_ix)) { continue; } const Plane& plane = planes[face_ix]; WideReal precise_t; if (!precise_edge_plane_parameter(a, b, plane, eps, precise_t)) { continue; } const double t = precise_t.to_double(); Matrix3d basis; Vector3d origin; Verts2D projected; make_2d_projection(verts, faces[face_ix], plane, projected, basis, origin); const WidePoint2 hit2 = precise_projected_edge_hit(a, b, precise_t, basis, origin); bool on_boundary = false; if (precise_point_in_polygon_2d(hit2, projected, eps, &on_boundary)) { EdgeFaceIntersection hit; hit.edge = edge; hit.face = (int)face_ix; hit.t = t; hit.on_boundary = on_boundary; result.push_back(hit); } } } return result; } std::vector measure_planarity(const Verts3D& verts, const Faces& faces, const Planes& planes) { std::vector result; for (size_t face_ix = 0; face_ix < faces.size(); ++face_ix) { const Face& face = faces[face_ix]; double max_abs = 0.0; double sum_sq = 0.0; for (int vertex_ix : face) { const double distance = planes[face_ix].signed_distance(verts[vertex_ix]); max_abs = std::max(max_abs, std::abs(distance)); sum_sq += double(distance) * double(distance); } PlanarityResidual item; item.face = (int)face_ix; item.max_abs = max_abs; item.rms = (double)std::sqrt(sum_sq / double(face.size())); result.push_back(item); } std::sort(result.begin(), result.end(), [](const PlanarityResidual& a, const PlanarityResidual& b) { return a.max_abs > b.max_abs; }); return result; } Verification verify_obj(const std::string& obj_path, int topology, double eps) { Verification verification; verification.obj_path = obj_path; verification.topology = topology; verification.eps = eps; g_topology = topology; Verts3D verts; Planes planes; import_obj(obj_path.c_str(), verts, g_polys); verification.finite = is_finite(verts); if (verts.empty() || g_polys.empty()) { verification.finite = false; return verification; } make_edges(g_polys, g_edges); v3ds_to_planes(verts, g_polys, planes); const std::map> edge_to_faces = make_edge_to_faces(g_polys); for (const auto& item : edge_to_faces) { verification.unique_edges.push_back(item.first); if (item.second.size() != 2) { verification.bad_edge_incidence.push_back(item.first); } } std::vector> face_edge_sets; for (const Face& face : g_polys) { std::set face_edges; for (size_t i = 0; i < face.size(); ++i) { face_edges.insert(canonical_edge(face[i], face[(i + 1) % face.size()])); } face_edge_sets.push_back(face_edges); } for (size_t i = 0; i < face_edge_sets.size(); ++i) { for (size_t j = i + 1; j < face_edge_sets.size(); ++j) { std::vector shared; std::set_intersection( face_edge_sets[i].begin(), face_edge_sets[i].end(), face_edge_sets[j].begin(), face_edge_sets[j].end(), std::back_inserter(shared) ); if (shared.size() != 1) { verification.bad_face_pairs.emplace_back((int)i, (int)j); } } } verification.vertices = verts.size(); verification.faces = g_polys.size(); verification.edges = verification.unique_edges.size(); verification.euler = (int)verification.vertices - (int)verification.edges + (int)verification.faces; verification.genus = 1 - verification.euler / 2; for (const Face& face : g_polys) { verification.face_sizes.push_back(face.size()); } verification.crossings = find_self_crossings(verts, g_polys, planes, eps); verification.intersections = find_edge_face_intersections(verts, g_polys, planes, verification.unique_edges, eps); verification.planarity = measure_planarity(verts, g_polys, planes); std::vector edge_lengths; edge_lengths.reserve(verification.unique_edges.size()); for (const Edge& edge : verification.unique_edges) { edge_lengths.push_back((verts[edge.first] - verts[edge.second]).norm()); } if (!edge_lengths.empty()) { std::sort(edge_lengths.begin(), edge_lengths.end()); verification.min_edge_length = edge_lengths.front(); verification.geometry_scale = edge_lengths[edge_lengths.size() / 2]; } if (!verification.planarity.empty()) { verification.max_planarity_residual = verification.planarity.front().max_abs; } verification.original_crossings = count_crossings(verts, planes); verification.original_intersections = count_intersections(verts, planes); return verification; } bool has_all_11_gons(const Verification& verification) { return std::all_of(verification.face_sizes.begin(), verification.face_sizes.end(), [](size_t size) { return size == 11; }); } bool is_ok(const Verification& verification) { return verification.vertices == 44 && verification.faces == 12 && verification.edges == 66 && has_all_11_gons(verification) && verification.bad_face_pairs.empty() && verification.bad_edge_incidence.empty() && verification.genus == 6 && verification.finite && verification.geometry_scale > 0.0 && verification.min_edge_length > verification.geometry_scale * 1e-8 && verification.max_planarity_residual <= verification.geometry_scale * 1e-4 && verification.crossings.empty() && verification.intersections.empty(); } std::string render_markdown(const Verification& verification) { std::ostringstream out; out << "# C++ high-precision verification\n\n"; out << "- Source: `" << verification.obj_path << "`\n"; out << "- Topology: `" << verification.topology << "`\n"; out << "- Epsilon: `" << std::scientific << std::setprecision(17) << verification.eps << std::defaultfloat << "`\n"; out << "- Predicate arithmetic: double-double (`~" << WideReal::decimal_digits << " decimal digits`)\n"; out << "- Overall: **" << (is_ok(verification) ? "OK" : "FAIL") << "**\n\n"; out << "## Combinatorics\n\n"; out << "- V/E/F: `" << verification.vertices << "/" << verification.edges << "/" << verification.faces << "`\n"; out << "- Euler characteristic: `" << verification.euler << "`\n"; out << "- Genus: `" << verification.genus << "`\n"; out << "- All faces are 11-gons: `" << (has_all_11_gons(verification) ? "yes" : "no") << "`\n"; out << "- Face-pair failures: `" << verification.bad_face_pairs.size() << "`\n"; out << "- Edge incidence failures: `" << verification.bad_edge_incidence.size() << "`\n\n"; out << "## Geometry\n\n"; out << "- Finite coordinates: `" << (verification.finite ? "yes" : "no") << "`\n"; out << "- Median/min edge length: `" << verification.geometry_scale << "/" << verification.min_edge_length << "`\n"; out << "- Max planarity residual: `" << verification.max_planarity_residual << "`\n"; out << "- Strict C++ self-crossings: `" << verification.crossings.size() << "`\n"; out << "- Strict C++ edge-face intersections: `" << verification.intersections.size() << "`\n"; out << "- Original count_crossings: `" << verification.original_crossings << "`\n"; out << "- Original count_intersections: `" << verification.original_intersections << "`\n\n"; out << "## Self-crossings\n\n"; if (verification.crossings.empty()) { out << "- None\n"; } else { for (const Crossing& crossing : verification.crossings) { out << "- Face " << crossing.face << " (OBJ face " << crossing.face + 1 << "): edge " << edge_obj_string(crossing.edge_a) << " crosses edge " << edge_obj_string(crossing.edge_b) << " (t=" << std::setprecision(17) << crossing.t << ", u=" << crossing.u << ")\n"; } } out << "\n## Edge-face intersections\n\n"; if (verification.intersections.empty()) { out << "- None\n"; } else { for (const EdgeFaceIntersection& hit : verification.intersections) { out << "- Edge " << edge_obj_string(hit.edge) << " intersects OBJ face " << hit.face + 1 << " (t=" << std::setprecision(17) << hit.t << ", " << (hit.on_boundary ? "boundary" : "interior") << ")\n"; } } out << "\n## Worst face planarity residuals\n\n"; const size_t residual_count = std::min(5, verification.planarity.size()); for (size_t i = 0; i < residual_count; ++i) { const PlanarityResidual& residual = verification.planarity[i]; out << "- Face " << residual.face << " (OBJ face " << residual.face + 1 << "): max `" << std::setprecision(17) << residual.max_abs << "`, rms `" << residual.rms << "`\n"; } return out.str(); } std::string json_escape(const std::string& value) { std::ostringstream out; for (const char ch : value) { switch (ch) { case '\\': out << "\\\\"; break; case '"': out << "\\\""; break; case '\n': out << "\\n"; break; case '\r': out << "\\r"; break; case '\t': out << "\\t"; break; default: out << ch; break; } } return out.str(); } std::string render_json(const Verification& verification) { std::ostringstream out; out << "{\n"; out << " \"source\": \"" << json_escape(verification.obj_path) << "\",\n"; out << " \"topology\": " << verification.topology << ",\n"; out << " \"eps\": " << std::setprecision(17) << verification.eps << ",\n"; out << " \"predicate_decimal_digits\": " << WideReal::decimal_digits << ",\n"; out << " \"vertices\": " << verification.vertices << ",\n"; out << " \"edges\": " << verification.edges << ",\n"; out << " \"faces\": " << verification.faces << ",\n"; out << " \"euler\": " << verification.euler << ",\n"; out << " \"genus\": " << verification.genus << ",\n"; out << " \"finite\": " << (verification.finite ? "true" : "false") << ",\n"; out << " \"geometry_scale\": " << verification.geometry_scale << ",\n"; out << " \"min_edge_length\": " << verification.min_edge_length << ",\n"; out << " \"max_planarity_residual\": " << verification.max_planarity_residual << ",\n"; out << " \"strict_self_crossings\": " << verification.crossings.size() << ",\n"; out << " \"strict_edge_face_intersections\": " << verification.intersections.size() << ",\n"; out << " \"original_count_crossings\": " << verification.original_crossings << ",\n"; out << " \"original_count_intersections\": " << verification.original_intersections << ",\n"; out << " \"ok\": " << (is_ok(verification) ? "true" : "false") << ",\n"; out << " \"self_crossings\": [\n"; for (size_t i = 0; i < verification.crossings.size(); ++i) { const Crossing& crossing = verification.crossings[i]; out << " {\"face0\": " << crossing.face << ", \"face1\": " << crossing.face + 1 << ", \"edge_a_obj\": [" << crossing.edge_a.first + 1 << ", " << crossing.edge_a.second + 1 << "]" << ", \"edge_b_obj\": [" << crossing.edge_b.first + 1 << ", " << crossing.edge_b.second + 1 << "]" << ", \"t\": " << crossing.t << ", \"u\": " << crossing.u << "}"; out << (i + 1 == verification.crossings.size() ? "\n" : ",\n"); } out << " ],\n"; out << " \"edge_face_intersections\": [\n"; for (size_t i = 0; i < verification.intersections.size(); ++i) { const EdgeFaceIntersection& hit = verification.intersections[i]; out << " {\"edge_obj\": [" << hit.edge.first + 1 << ", " << hit.edge.second + 1 << "]" << ", \"face0\": " << hit.face << ", \"face1\": " << hit.face + 1 << ", \"t\": " << hit.t << ", \"on_boundary\": " << (hit.on_boundary ? "true" : "false") << "}"; out << (i + 1 == verification.intersections.size() ? "\n" : ",\n"); } out << " ]\n"; out << "}\n"; return out.str(); } void write_text_file(const std::string& path, const std::string& contents) { if (path.empty()) { return; } const std::filesystem::path output_path(path); if (output_path.has_parent_path()) { std::filesystem::create_directories(output_path.parent_path()); } std::ofstream out(path); out << contents; } } // namespace int main(int argc, char** argv) { if (!wide_real_self_test()) { std::cerr << "WideReal self-test failed.\n"; return 3; } std::string obj_path = "data/shape_c2_i0_0.obj"; std::string report_path = "runtime/reports/00_baseline.md"; std::string json_path = "runtime/reports/baseline.json"; int topology = 4; double eps = 1e-12; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if ((arg == "--obj" || arg == "-o") && i + 1 < argc) { obj_path = argv[++i]; } else if ((arg == "--topology" || arg == "-t") && i + 1 < argc) { topology = std::atoi(argv[++i]); } else if (arg == "--eps" && i + 1 < argc) { eps = (double)std::atof(argv[++i]); } else if (arg == "--report" && i + 1 < argc) { report_path = argv[++i]; } else if (arg == "--json" && i + 1 < argc) { json_path = argv[++i]; } else { std::cerr << "Usage: verify_cpp [--obj path] [--topology n] [--eps value] [--report path] [--json path]\n"; return 2; } } const Verification verification = verify_obj(obj_path, topology, eps); const std::string markdown = render_markdown(verification); const std::string json = render_json(verification); write_text_file(report_path, markdown); write_text_file(json_path, json); std::cout << markdown; return is_ok(verification) ? 0 : 1; }