diff --git a/CMakeLists.txt b/CMakeLists.txt index 99f2e24..35e837f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(BUILD_TESTS "Build tests" ON) option(BUILD_EXAMPLES "Build examples" ON) option(ENABLE_SANITIZERS "Enable ASan" OFF) +option(VDE_USE_GMP "GMP exact arithmetic" OFF) option(VDE_BUILD_PYTHON "Python bindings" OFF) add_library(vde_compile_options INTERFACE) diff --git a/cmake/FindGMP.cmake b/cmake/FindGMP.cmake new file mode 100644 index 0000000..8c43a93 --- /dev/null +++ b/cmake/FindGMP.cmake @@ -0,0 +1,16 @@ +find_path(GMP_INCLUDE_DIR gmp.h + PATHS /usr/include /usr/include/x86_64-linux-gnu +) +find_library(GMP_LIBRARY NAMES gmp libgmp + PATHS /usr/lib /usr/lib/x86_64-linux-gnu +) +find_library(GMPXX_LIBRARY NAMES gmpxx libgmpxx + PATHS /usr/lib /usr/lib/x86_64-linux-gnu +) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARY GMP_INCLUDE_DIR) +if(GMP_FOUND) + set(GMP_LIBRARIES ${GMPXX_LIBRARY} ${GMP_LIBRARY}) + set(GMP_INCLUDE_DIRS ${GMP_INCLUDE_DIR}) + mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARY GMPXX_LIBRARY) +endif() diff --git a/include/vde/foundation/exact_predicates.h b/include/vde/foundation/exact_predicates.h index 2219568..fa9f34e 100644 --- a/include/vde/foundation/exact_predicates.h +++ b/include/vde/foundation/exact_predicates.h @@ -18,3 +18,20 @@ CircleTest in_circle(const Point2D& a, const Point2D& b, const Point2D& c, const Point2D& d); } // namespace vde::foundation::exact + +/// Adaptive double-precision predicates (default backend) +namespace vde::foundation { +struct AdaptiveDouble { + static exact::Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) { + return exact::orient_2d(a, b, c); + } + static exact::Orientation orient_3d(const Point3D& a, const Point3D& b, + const Point3D& c, const Point3D& d) { + return exact::orient_3d(a, b, c, d); + } + static exact::CircleTest in_circle(const Point2D& a, const Point2D& b, + const Point2D& c, const Point2D& d) { + return exact::in_circle(a, b, c, d); + } +}; +} // namespace vde::foundation diff --git a/include/vde/foundation/exact_predicates_gmp.h b/include/vde/foundation/exact_predicates_gmp.h new file mode 100644 index 0000000..90c907c --- /dev/null +++ b/include/vde/foundation/exact_predicates_gmp.h @@ -0,0 +1,73 @@ +#pragma once +#include "vde/foundation/exact_predicates.h" + +#ifdef VDE_USE_GMP +#include + +namespace vde::foundation::exact { + +/// GMP-based exact predicates — zero rounding error, arbitrary precision +struct GmpExact { + using Rational = mpq_class; + + /// orient_2d with exact rational arithmetic + static Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) { + Rational ax(a.x()), ay(a.y()); + Rational bx(b.x()), by(b.y()); + Rational cx(c.x()), cy(c.y()); + Rational det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax); + int s = mpq_sgn(det.get_mpq_t()); + return s > 0 ? Orientation::CounterClockwise : + s < 0 ? Orientation::Clockwise : Orientation::Collinear; + } + + /// orient_3d with exact rational arithmetic + static Orientation orient_3d(const Point3D& a, const Point3D& b, + const Point3D& c, const Point3D& d) { + Rational ax(a.x()), ay(a.y()), az(a.z()); + Rational bx(b.x()), by(b.y()), bz(b.z()); + Rational cx(c.x()), cy(c.y()), cz(c.z()); + Rational dx(d.x()), dy(d.y()), dz(d.z()); + + Rational adx = ax - dx, ady = ay - dy, adz = az - dz; + Rational bdx = bx - dx, bdy = by - dy, bdz = bz - dz; + Rational cdx = cx - dx, cdy = cy - dy, cdz = cz - dz; + + Rational det = adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); + + int s = mpq_sgn(det.get_mpq_t()); + return s > 0 ? Orientation::CounterClockwise : + s < 0 ? Orientation::Clockwise : Orientation::Collinear; + } + + /// in_circle with exact rational arithmetic + static CircleTest in_circle(const Point2D& a, const Point2D& b, + const Point2D& c, const Point2D& d) { + Rational ax(a.x()), ay(a.y()); + Rational bx(b.x()), by(b.y()); + Rational cx(c.x()), cy(c.y()); + Rational dx(d.x()), dy(d.y()); + + Rational adx = ax - dx, ady = ay - dy; + Rational bdx = bx - dx, bdy = by - dy; + Rational cdx = cx - dx, cdy = cy - dy; + + Rational ab = adx * bdy - ady * bdx; + Rational bc = bdx * cdy - bdy * cdx; + Rational ca = cdx * ady - cdy * adx; + Rational al = adx * adx + ady * ady; + Rational bl = bdx * bdx + bdy * bdy; + Rational cl = cdx * cdx + cdy * cdy; + + Rational det = al * bc + bl * ca + cl * ab; + int s = mpq_sgn(det.get_mpq_t()); + return s > 0 ? CircleTest::Inside : + s < 0 ? CircleTest::Outside : CircleTest::On; + } +}; + +} // namespace vde::foundation::exact + +#endif // VDE_USE_GMP diff --git a/include/vde/foundation/predicates.h b/include/vde/foundation/predicates.h new file mode 100644 index 0000000..4d56791 --- /dev/null +++ b/include/vde/foundation/predicates.h @@ -0,0 +1,41 @@ +#pragma once +#include "vde/foundation/exact_predicates.h" +#ifdef VDE_USE_GMP +#include "vde/foundation/exact_predicates_gmp.h" +#endif + +namespace vde::foundation { + +/// Unified predicate interface — selects GMP or adaptive double at compile time +struct Predicates { +#ifdef VDE_USE_GMP + using Backend = exact::GmpExact; +#else + using Backend = exact::AdaptiveDouble; +#endif + + static exact::Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) { + return Backend::orient_2d(a, b, c); + } + static exact::Orientation orient_3d(const Point3D& a, const Point3D& b, + const Point3D& c, const Point3D& d) { + return Backend::orient_3d(a, b, c, d); + } + static exact::CircleTest in_circle(const Point2D& a, const Point2D& b, + const Point2D& c, const Point2D& d) { + return Backend::in_circle(a, b, c, d); + } +}; + +// ── Convenience wrappers (preferred API) ── +inline exact::Orientation orient(const Point2D& a, const Point2D& b, const Point2D& c) { + return Predicates::orient_2d(a, b, c); +} +inline exact::Orientation orient(const Point3D& a, const Point3D& b, const Point3D& c, const Point3D& d) { + return Predicates::orient_3d(a, b, c, d); +} +inline exact::CircleTest in_circle(const Point2D& a, const Point2D& b, const Point2D& c, const Point2D& d) { + return Predicates::in_circle(a, b, c, d); +} + +} // namespace vde::foundation diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 78d6c2c..8e94200 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -174,3 +174,15 @@ target_link_libraries(vde_capi PUBLIC vde_brep vde_sketch vde_collision vde_boolean vde_spatial PUBLIC vde_mesh vde_curves vde_core vde_foundation vde_compile_options ) + +# ── GMP exact predicates (optional) ── +if(VDE_USE_GMP) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + find_package(GMP REQUIRED) + target_compile_definitions(vde_foundation PUBLIC VDE_USE_GMP) + target_include_directories(vde_foundation PUBLIC ${GMP_INCLUDE_DIRS}) + target_link_libraries(vde_foundation PUBLIC ${GMP_LIBRARIES}) + message(STATUS "GMP exact arithmetic: ${GMP_INCLUDE_DIRS}") +else() + message(STATUS "GMP disabled (-DVDE_USE_GMP=ON to enable)") +endif() diff --git a/src/foundation/exact_predicates_gmp.cpp b/src/foundation/exact_predicates_gmp.cpp new file mode 100644 index 0000000..e5b3c88 --- /dev/null +++ b/src/foundation/exact_predicates_gmp.cpp @@ -0,0 +1,7 @@ +#include "vde/foundation/exact_predicates_gmp.h" +// GMP exact predicates — header-only with mpq_class +// This .cpp exists purely for CMake build verification +namespace vde::foundation::exact { + // Explicit instantiation marker + void __vde_gmp_marker() {} +}