Merge newboolean branch into master.

This is for design task T67744, Boolean Redesign.
It adds a choice of solver to the Boolean modifier and the
Intersect (Boolean) and Intersect (Knife) tools.
The 'Fast' choice is the current Bmesh boolean.
The new 'Exact' choice is a more advanced algorithm that supports
overlapping geometry and uses more robust calculations, but is
slower than the Fast choice.
The default with this commit is set to 'Exact'. We can decide before
the 2.91 release whether or not this is the right choice, but this
choice now will get us more testing and feedback on the new code.
This commit is contained in:
Howard Trickey 2020-08-28 10:56:44 -04:00
parent 4a17508c6d
commit 9e09b5c418
Notes: blender-bot 2023-02-14 11:21:40 +01:00
Referenced by commit fdf77341cd, CMake: Remove duplicate WITH_GMP options.
Referenced by commit f1fee433be, GMP/CMake: Remove duplicate GMP search logic.
Referenced by issue #103942, ASAN error/crash invalid memory access
Referenced by issue #68144, Boolean Difference works like Boolean Union - modifier bug
Referenced by issue #67744, Boolean Redesign
Referenced by issue #61845, BMesh booleans fail with holes
Referenced by issue #58814, BMesh boolean is just too much unreliable
Referenced by issue #55466, Boolean Modifier set to difference not substracting the whole volume
Referenced by issue #54024, BMesh boolean fails when edge is co-planar to face (under some circumstances)
Referenced by issue #47129, new Bmesh boolean is deleting some of edges data, (Creases and Bweigh)
Referenced by issue #47108, Bmesh boolean fails with overlapping faces
Referenced by issue #47087, Bmesh booleans fails when A mesh have coincident edge with B
Referenced by issue #46988, BMesh boolean fails with zero area faces
52 changed files with 17616 additions and 6435 deletions

View File

@ -188,6 +188,7 @@ if(APPLE)
else()
option(WITH_XR_OPENXR "Enable VR features through the OpenXR specification" ON)
endif()
option(WITH_GMP "Enable features depending on GMP (Exact Boolean)" ON)
# Compositor
option(WITH_COMPOSITOR "Enable the tile based nodal compositor" ON)
@ -1739,6 +1740,7 @@ if(FIRST_RUN)
info_cfg_option(WITH_QUADRIFLOW)
info_cfg_option(WITH_USD)
info_cfg_option(WITH_TBB)
info_cfg_option(WITH_GMP)
info_cfg_text("Compiler Options:")
info_cfg_option(WITH_BUILDINFO)

View File

@ -120,6 +120,7 @@ endif()
if(NOT WIN32 OR ENABLE_MINGW64)
include(cmake/gmp.cmake)
include(cmake/openjpeg.cmake)
include(cmake/gmp.cmake)
if(NOT WIN32 OR BUILD_MODE STREQUAL Release)
if(WIN32)
include(cmake/zlib_mingw.cmake)

View File

@ -70,6 +70,9 @@ macro(BLENDER_SRC_GTEST_EX)
if(WITH_TBB)
target_link_libraries(${TARGET_NAME} ${TBB_LIBRARIES})
endif()
if(WITH_GMP)
target_link_libraries(${TARGET_NAME} ${GMP_LIBRARIES})
endif()
get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(GENERATOR_IS_MULTI_CONFIG)

View File

@ -20,6 +20,7 @@ set(WITH_LIBMV ON CACHE BOOL "" FORCE)
set(WITH_LIBMV_SCHUR_SPECIALIZATIONS ON CACHE BOOL "" FORCE)
set(WITH_COMPOSITOR ON CACHE BOOL "" FORCE)
set(WITH_FREESTYLE ON CACHE BOOL "" FORCE)
set(WITH_GMP ON CACHE BOOL "" FORCE)
set(WITH_IK_SOLVER ON CACHE BOOL "" FORCE)
set(WITH_IK_ITASC ON CACHE BOOL "" FORCE)
set(WITH_IMAGE_CINEON ON CACHE BOOL "" FORCE)

View File

@ -25,6 +25,7 @@ set(WITH_LIBMV OFF CACHE BOOL "" FORCE)
set(WITH_LLVM OFF CACHE BOOL "" FORCE)
set(WITH_COMPOSITOR OFF CACHE BOOL "" FORCE)
set(WITH_FREESTYLE OFF CACHE BOOL "" FORCE)
set(WITH_GMP OFF CACHE BOOL "" FORCE)
set(WITH_IK_SOLVER OFF CACHE BOOL "" FORCE)
set(WITH_IK_ITASC OFF CACHE BOOL "" FORCE)
set(WITH_IMAGE_CINEON OFF CACHE BOOL "" FORCE)

View File

@ -21,6 +21,7 @@ set(WITH_LIBMV ON CACHE BOOL "" FORCE)
set(WITH_LIBMV_SCHUR_SPECIALIZATIONS ON CACHE BOOL "" FORCE)
set(WITH_COMPOSITOR ON CACHE BOOL "" FORCE)
set(WITH_FREESTYLE ON CACHE BOOL "" FORCE)
set(WITH_GMP ON CACHE BOOL "" FORCE)
set(WITH_IK_SOLVER ON CACHE BOOL "" FORCE)
set(WITH_IK_ITASC ON CACHE BOOL "" FORCE)
set(WITH_IMAGE_CINEON ON CACHE BOOL "" FORCE)

View File

@ -496,6 +496,10 @@ function(SETUP_LIBDIRS)
link_directories(${ALEMBIC_LIBPATH})
endif()
if(WITH_GMP)
link_directories(${GMP_LIBPATH})
endif()
if(WITH_GHOST_WAYLAND)
link_directories(
${wayland-client_LIBRARY_DIRS}

View File

@ -407,6 +407,15 @@ if(WITH_TBB)
find_package(TBB)
endif()
if(WITH_GMP)
find_package(GMP)
if(NOT GMP_FOUND)
set(WITH_GMP OFF)
message(STATUS "GMP not found")
endif()
endif()
# CMake FindOpenMP doesn't know about AppleClang before 3.12, so provide custom flags.
if(WITH_OPENMP)
if(CMAKE_C_COMPILER_ID MATCHES "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "7.0")

View File

@ -427,6 +427,15 @@ if(WITH_TBB)
find_package_wrapper(TBB)
endif()
if(WITH_GMP)
find_package(GMP)
if(NOT GMP_FOUND)
set(WITH_GMP OFF)
message(STATUS "GMP not found")
endif()
endif()
if(WITH_XR_OPENXR)
find_package(XR_OpenXR_SDK)
if(NOT XR_OPENXR_SDK_FOUND)

View File

@ -18,6 +18,9 @@
/** \file
* \ingroup bli
*
* This header file contains both a C interface and a C++ interface
* to the 2D Constrained Delaunay Triangulation library routine.
*/
#ifdef __cplusplus
@ -107,11 +110,6 @@ extern "C" {
* If zero is supplied for epsilon, an internal value of 1e-8 used
* instead, since this code will not work correctly if it is not allowed
* to merge "too near" vertices.
*
* Normally, if epsilon is non-zero, there is an "input modify" pass which
* checks to see if some vertices are within epsilon of other edges, and
* snapping them to those edges if so. You can skip this pass by setting
* skip_input_modify to true. (This is also useful in some unit tests.)
*/
typedef struct CDT_input {
int verts_len;
@ -123,7 +121,6 @@ typedef struct CDT_input {
int *faces_start_table;
int *faces_len_table;
float epsilon;
bool skip_input_modify;
} CDT_input;
/**
@ -150,12 +147,9 @@ typedef struct CDT_input {
* - faces_orig, faces_orig_start_table, faces_orig_len_table
*
* For edges, the edges_orig triple can also say which original face
* edge is part of a given output edge. If an index in edges_orig
* is greater than the input's edges_len, then subtract input's edges_len
* from it to some number i: then the face edge that starts from the
* input vertex at input's faces[i] is the corresponding face edge.
* for convenience, face_edge_offset in the result will be the input's
* edges_len, so that this conversion can be easily done by the caller.
* edge is part of a given output edge. See the comment below
* on the C++ interface for how to decode the entries in the edges_orig
* table.
*/
typedef struct CDT_result {
int verts_len;
@ -207,4 +201,69 @@ void BLI_delaunay_2d_cdt_free(CDT_result *result);
#ifdef __cplusplus
}
#endif
/* C++ Interface. */
# include "BLI_array.hh"
# include "BLI_double2.hh"
# include "BLI_math_mpq.hh"
# include "BLI_mpq2.hh"
# include "BLI_vector.hh"
namespace blender::meshintersect {
/* vec2<Arith_t> is a 2d vector with Arith_t as the type for coordinates. */
template<typename Arith_t> struct vec2_impl;
template<> struct vec2_impl<double> {
typedef double2 type;
};
# ifdef WITH_GMP
template<> struct vec2_impl<mpq_class> {
typedef mpq2 type;
};
# endif
template<typename Arith_t> using vec2 = typename vec2_impl<Arith_t>::type;
template<typename Arith_t> class CDT_input {
public:
Array<vec2<Arith_t>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
Arith_t epsilon{0};
};
template<typename Arith_t> class CDT_result {
public:
Array<vec2<Arith_t>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
/** For each output vert, which input verts correspond to it? */
Array<Vector<int>> vert_orig;
/**
* For each output edge, which input edges does it overlap?
* The input edge ids are encoded as follows:
* if the value is less than face_edge_offset, then it is
* an index into the input edge[] array.
* else let (a, b) = the quotient and remainder of dividing
* the edge index by face_edge_offset; "a" will be the input face + 1,
* and "b" will be a position within that face.
*/
Array<Vector<int>> edge_orig;
/** For each output face, which original faces does it overlap? */
Array<Vector<int>> face_orig;
/** Used to encode edge_orig (see above). */
int face_edge_offset;
};
CDT_result<double> delaunay_2d_calc(const CDT_input<double> &input, CDT_output_type output_type);
# ifdef WITH_GMP
CDT_result<mpq_class> delaunay_2d_calc(const CDT_input<mpq_class> &input,
CDT_output_type output_type);
# endif
} /* namespace blender::meshintersect */
#endif /* __cplusplus */

View File

@ -0,0 +1,143 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_double3.hh"
namespace blender {
struct double2 {
double x, y;
double2() = default;
double2(const double *ptr) : x{ptr[0]}, y{ptr[1]}
{
}
double2(double x, double y) : x(x), y(y)
{
}
double2(const double3 &other) : x(other.x), y(other.y)
{
}
operator double *()
{
return &x;
}
operator const double *() const
{
return &x;
}
float length() const
{
return len_v2_db(*this);
}
friend double2 operator+(const double2 &a, const double2 &b)
{
return {a.x + b.x, a.y + b.y};
}
friend double2 operator-(const double2 &a, const double2 &b)
{
return {a.x - b.x, a.y - b.y};
}
friend double2 operator*(const double2 &a, double b)
{
return {a.x * b, a.y * b};
}
friend double2 operator/(const double2 &a, double b)
{
BLI_assert(b != 0.0);
return {a.x / b, a.y / b};
}
friend double2 operator*(double a, const double2 &b)
{
return b * a;
}
friend bool operator==(const double2 &a, const double2 &b)
{
return a.x == b.x && a.y == b.y;
}
friend bool operator!=(const double2 &a, const double2 &b)
{
return a.x != b.x || a.y != b.y;
}
friend std::ostream &operator<<(std::ostream &stream, const double2 &v)
{
stream << "(" << v.x << ", " << v.y << ")";
return stream;
}
static double dot(const double2 &a, const double2 &b)
{
return a.x * b.x + a.y * b.y;
}
static double2 interpolate(const double2 &a, const double2 &b, double t)
{
return a * (1 - t) + b * t;
}
static double2 abs(const double2 &a)
{
return double2(fabs(a.x), fabs(a.y));
}
static double distance(const double2 &a, const double2 &b)
{
return (a - b).length();
}
static double distance_squared(const double2 &a, const double2 &b)
{
return double2::dot(a, b);
}
struct isect_result {
enum {
LINE_LINE_COLINEAR = -1,
LINE_LINE_NONE = 0,
LINE_LINE_EXACT = 1,
LINE_LINE_CROSS = 2,
} kind;
double lambda;
double mu;
};
static isect_result isect_seg_seg(const double2 &v1,
const double2 &v2,
const double2 &v3,
const double2 &v4);
};
} // namespace blender

View File

@ -0,0 +1,245 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*/
#include <iostream>
#include "BLI_math_vector.h"
#include "BLI_span.hh"
namespace blender {
struct double3 {
double x, y, z;
double3() = default;
double3(const double *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}
{
}
double3(const double (*ptr)[3]) : double3((const double *)ptr)
{
}
explicit double3(double value) : x(value), y(value), z(value)
{
}
explicit double3(int value) : x(value), y(value), z(value)
{
}
double3(double x, double y, double z) : x{x}, y{y}, z{z}
{
}
operator const double *() const
{
return &x;
}
operator double *()
{
return &x;
}
double normalize_and_get_length()
{
return normalize_v3_db(*this);
}
double3 normalized() const
{
double3 result;
normalize_v3_v3_db(result, *this);
return result;
}
double length() const
{
return len_v3_db(*this);
}
double length_squared() const
{
return len_squared_v3_db(*this);
}
void reflect(const double3 &normal)
{
*this = this->reflected(normal);
}
double3 reflected(const double3 &normal) const
{
double3 result;
reflect_v3_v3v3_db(result, *this, normal);
return result;
}
static double3 safe_divide(const double3 &a, const double3 &b)
{
double3 result;
result.x = (b.x == 0.0) ? 0.0 : a.x / b.x;
result.y = (b.y == 0.0) ? 0.0 : a.y / b.y;
result.z = (b.z == 0.0) ? 0.0 : a.z / b.z;
return result;
}
void invert()
{
x = -x;
y = -y;
z = -z;
}
friend double3 operator+(const double3 &a, const double3 &b)
{
return {a.x + b.x, a.y + b.y, a.z + b.z};
}
void operator+=(const double3 &b)
{
this->x += b.x;
this->y += b.y;
this->z += b.z;
}
friend double3 operator-(const double3 &a, const double3 &b)
{
return {a.x - b.x, a.y - b.y, a.z - b.z};
}
friend double3 operator-(const double3 &a)
{
return {-a.x, -a.y, -a.z};
}
void operator-=(const double3 &b)
{
this->x -= b.x;
this->y -= b.y;
this->z -= b.z;
}
void operator*=(const double &scalar)
{
this->x *= scalar;
this->y *= scalar;
this->z *= scalar;
}
void operator*=(const double3 &other)
{
this->x *= other.x;
this->y *= other.y;
this->z *= other.z;
}
friend double3 operator*(const double3 &a, const double3 &b)
{
return {a.x * b.x, a.y * b.y, a.z * b.z};
}
friend double3 operator*(const double3 &a, const double &b)
{
return {a.x * b, a.y * b, a.z * b};
}
friend double3 operator*(const double &a, const double3 &b)
{
return b * a;
}
friend double3 operator/(const double3 &a, const double &b)
{
BLI_assert(b != 0.0);
return {a.x / b, a.y / b, a.z / b};
}
friend bool operator==(const double3 &a, const double3 &b)
{
return a.x == b.x && a.y == b.y && a.z == b.z;
}
friend bool operator!=(const double3 &a, const double3 &b)
{
return a.x != b.x || a.y != b.y || a.z != b.z;
}
friend std::ostream &operator<<(std::ostream &stream, const double3 &v)
{
stream << "(" << v.x << ", " << v.y << ", " << v.z << ")";
return stream;
}
static double dot(const double3 &a, const double3 &b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static double3 cross_high_precision(const double3 &a, const double3 &b)
{
double3 result;
cross_v3_v3v3_db(result, a, b);
return result;
}
static double3 project(const double3 &a, const double3 &b)
{
double3 result;
project_v3_v3v3_db(result, a, b);
return result;
}
static double distance(const double3 &a, const double3 &b)
{
return (a - b).length();
}
static double distance_squared(const double3 &a, const double3 &b)
{
return double3::dot(a, b);
}
static double3 interpolate(const double3 &a, const double3 &b, double t)
{
return a * (1 - t) + b * t;
}
static double3 abs(const double3 &a)
{
return double3(fabs(a.x), fabs(a.y), fabs(a.z));
}
static int dominant_axis(const double3 &a)
{
double x = (a.x >= 0) ? a.x : -a.x;
double y = (a.y >= 0) ? a.y : -a.y;
double z = (a.z >= 0) ? a.z : -a.z;
return ((x > y) ? ((x > z) ? 0 : 2) : ((y > z) ? 1 : 2));
}
static double3 cross_poly(Span<double3> poly);
};
} // namespace blender

View File

@ -47,6 +47,11 @@ struct float2 {
return &x;
}
float length() const
{
return len_v2(*this);
}
float2 &operator+=(const float2 &other)
{
x += other.x;
@ -107,6 +112,47 @@ struct float2 {
return stream;
}
static float dot(const float2 &a, const float2 &b)
{
return a.x * b.x + a.y * b.y;
}
static float2 interpolate(const float2 &a, const float2 &b, float t)
{
return a * (1 - t) + b * t;
}
static float2 abs(const float2 &a)
{
return float2(fabsf(a.x), fabsf(a.y));
}
static float distance(const float2 &a, const float2 &b)
{
return (a - b).length();
}
static float distance_squared(const float2 &a, const float2 &b)
{
return float2::dot(a, b);
}
struct isect_result {
enum {
LINE_LINE_COLINEAR = -1,
LINE_LINE_NONE = 0,
LINE_LINE_EXACT = 1,
LINE_LINE_CROSS = 2,
} kind;
float lambda;
float mu;
};
static isect_result isect_seg_seg(const float2 &v1,
const float2 &v2,
const float2 &v3,
const float2 &v4);
friend bool operator==(const float2 &a, const float2 &b)
{
return a.x == b.x && a.y == b.y;

View File

@ -243,6 +243,11 @@ struct float3 {
{
return a * (1 - t) + b * t;
}
static float3 abs(const float3 &a)
{
return float3(fabsf(a.x), fabsf(a.y), fabsf(a.z));
}
};
} // namespace blender

View File

@ -242,6 +242,14 @@ double double_round(double x, int ndigits);
} \
(void)0
# define BLI_ASSERT_UNIT_V3_DB(v) \
{ \
const double _test_unit = len_squared_v3_db(v); \
BLI_assert(!(fabs(_test_unit - 1.0) >= BLI_ASSERT_UNIT_EPSILON) || \
!(fabs(_test_unit) >= BLI_ASSERT_UNIT_EPSILON)); \
} \
(void)0
# define BLI_ASSERT_UNIT_V2(v) \
{ \
const float _test_unit = len_squared_v2(v); \

View File

@ -0,0 +1,61 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
* \brief Math vector functions needed specifically for mesh intersect and boolean.
*/
#include "BLI_double2.hh"
#include "BLI_double3.hh"
#ifdef WITH_GMP
#include "BLI_math_mpq.hh"
#include "BLI_mpq2.hh"
#include "BLI_mpq3.hh"
#endif
namespace blender {
/* #orient2d gives the exact result, using multi-precision arithmetic when result
* is close to zero. orient3d_fast just uses double arithmetic, so may be
* wrong if the answer is very close to zero.
* Similarly, for #incircle and #incircle_fast. */
int orient2d(const double2 &a, const double2 &b, const double2 &c);
int orient2d_fast(const double2 &a, const double2 &b, const double2 &c);
int incircle(const double2 &a, const double2 &b, const double2 &c, const double2 &d);
int incircle_fast(const double2 &a, const double2 &b, const double2 &c, const double2 &d);
/* #orient3d gives the exact result, using multi-precision arithmetic when result
* is close to zero. orient3d_fast just uses double arithmetic, so may be
* wrong if the answer is very close to zero.
* Similarly, for #insphere and #insphere_fast. */
int orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d);
int orient3d_fast(const double3 &a, const double3 &b, const double3 &c, const double3 &d);
int insphere(const double3 &a, const double3 &b, const double3 &c, const double3 &d, const double3 &e);
int insphere_fast(const double3 &a, const double3 &b, const double3 &c, const double3 &d, const double3 &e);
#ifdef WITH_GMP
int orient2d(const mpq2 &a, const mpq2 &b, const mpq2 &c);
int incircle(const mpq2 &a, const mpq2 &b, const mpq2 &c, const mpq2 &d);
int orient3d(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &d);
#endif
} // namespace blender

View File

@ -0,0 +1,36 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*/
#ifdef WITH_GMP
/* This file uses an external file header to define the multi-precision
* rational type, mpq_class.
* This class keeps separate multi-precision integer numerator and
* denominator, reduced to lowest terms after each arithmetic operation.
* It can be used where it is important to have exact arithmetic results.
*
* See gmplib.org for full documentation. In particular:
* https://gmplib.org/manual/C_002b_002b-Interface-Rationals
*/
# include "gmpxx.h"
#endif /* WITH_GMP */

View File

@ -140,6 +140,7 @@ MINLINE void mul_v2_v2fl(float r[2], const float a[2], float f);
MINLINE void mul_v3_fl(float r[3], float f);
MINLINE void mul_v3db_db(double r[3], double f);
MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f);
MINLINE void mul_v3_v3db_db(double r[3], const double a[3], double f);
MINLINE void mul_v2_v2(float r[2], const float a[2]);
MINLINE void mul_v2_v2v2(float r[2], const float a[2], const float b[2]);
MINLINE void mul_v3_v3(float r[3], const float a[3]);
@ -236,17 +237,21 @@ MINLINE float len_manhattan_v3v3(const float a[3], const float b[3]) ATTR_WARN_U
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT;
MINLINE float len_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT;
MINLINE double len_v3_db(const double a[3]) ATTR_WARN_UNUSED_RESULT;
MINLINE double len_squared_v3_db(const double v[3]) ATTR_WARN_UNUSED_RESULT;
MINLINE float normalize_v2_length(float r[2], const float unit_scale);
MINLINE float normalize_v2_v2_length(float r[2], const float a[2], const float unit_scale);
MINLINE float normalize_v3_length(float r[3], const float unit_scale);
MINLINE float normalize_v3_v3_length(float r[3], const float a[3], const float unit_scale);
MINLINE double normalize_v3_length_d(double n[3], const double unit_scale);
MINLINE double normalize_v3_length_db(double n[3], const double unit_scale);
MINLINE double normalize_v3_v3_length_db(double r[3], const double a[3], const double unit_scale);
MINLINE float normalize_v2(float r[2]);
MINLINE float normalize_v2_v2(float r[2], const float a[2]);
MINLINE float normalize_v3(float r[3]);
MINLINE float normalize_v3_v3(float r[3], const float a[3]);
MINLINE double normalize_v3_d(double n[3]);
MINLINE double normalize_v3_v3_db(double r[3], const double a[3]);
MINLINE double normalize_v3_db(double n[3]);
/******************************* Interpolation *******************************/
@ -402,6 +407,7 @@ void angle_poly_v3(float *angles, const float *verts[3], int len);
void project_v2_v2v2(float out[2], const float p[2], const float v_proj[2]);
void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3]);
void project_v3_v3v3_db(double out[3], const double p[3], const double v_proj[3]);
void project_v2_v2v2_normalized(float out[2], const float p[2], const float v_proj[2]);
void project_v3_v3v3_normalized(float out[3], const float p[3], const float v_proj[3]);
void project_plane_v3_v3v3(float out[3], const float p[3], const float v_plane[3]);
@ -410,6 +416,7 @@ void project_plane_normalized_v3_v3v3(float out[3], const float p[3], const floa
void project_plane_normalized_v2_v2v2(float out[2], const float p[2], const float v_plane[2]);
void project_v3_plane(float out[3], const float plane_no[3], const float plane_co[3]);
void reflect_v3_v3v3(float out[3], const float vec[3], const float normal[3]);
void reflect_v3_v3v3_db(double out[3], const double vec[3], const double normal[3]);
void ortho_basis_v3v3_v3(float r_n1[3], float r_n2[3], const float n[3]);
void ortho_v3_v3(float out[3], const float v[3]);
void ortho_v2_v2(float out[2], const float v[2]);

View File

@ -0,0 +1,79 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*/
/* The boolean functions in Blenlib use exact arithmetic, so require GMP. */
#ifdef WITH_GMP
# include "BLI_mesh_intersect.hh"
# include <functional>
namespace blender::meshintersect {
/**
* Enum values after BOOLEAN_NONE need to match BMESH_ISECT_BOOLEAN_... values in
* editmesh_intersect.c. */
enum class BoolOpType {
None = -1,
/* Aligned with #BooleanModifierOp. */
Intersect = 0,
Union = 1,
Difference = 2,
};
/**
* Do the boolean operation op on the mesh pm_in.
* The boolean operation has nshapes input shapes. Each is a disjoint subset of the input mesh.
* The shape_fn argument, when applied to an input face argument, says which shape it is in
* (should be a value from -1 to nshapes - 1: if -1, it is not part of any shape).
* The use_self arg says whether or not the function should assume that faces in the
* same shape intersect - if the argument is true, such self-intersections will be found.
* Sometimes the caller has already done a triangulation of the faces,
* and if so, *pm_triangulated contains a triangulation: if non-null, it contains a mesh
* of triangles, each of whose orig_field says which face in pm that triangle belongs to.
* pm arg isn't const because we may populate its verts (for debugging).
* Same goes for the pm_triangulated arg.
* The output IMesh will have faces whose orig fields map back to faces and edges in
* the input mesh.
*/
IMesh boolean_mesh(IMesh &imesh,
BoolOpType op,
int nshapes,
std::function<int(int)> shape_fn,
bool use_self,
IMesh *pm_triangulated,
IMeshArena *arena);
/**
* This is like boolean, but operates on IMesh's whose faces are all triangles.
* It is exposed mainly for unit testing, at the moment: boolean_mesh() uses
* it to do most of its work.
*/
IMesh boolean_trimesh(IMesh &trimesh,
BoolOpType op,
int nshapes,
std::function<int(int)> shape_fn,
bool use_self,
IMeshArena *arena);
} // namespace blender::meshintersect
#endif /* WITH_GMP */

View File

@ -0,0 +1,359 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*
* Mesh intersection library functions.
* Uses exact arithmetic, so need GMP.
*/
#ifdef WITH_GMP
# include <iostream>
# include "BLI_array.hh"
# include "BLI_double3.hh"
# include "BLI_index_range.hh"
# include "BLI_map.hh"
# include "BLI_math_mpq.hh"
# include "BLI_mpq3.hh"
# include "BLI_span.hh"
# include "BLI_utility_mixins.hh"
# include "BLI_vector.hh"
namespace blender::meshintersect {
constexpr int NO_INDEX = -1;
/**
* Vertex coordinates are stored both as #double3 and #mpq3, which should agree.
* Most calculations are done in exact arithmetic, using the mpq3 version,
* but some predicates can be sped up by operating on doubles and using error analysis
* to find the cases where that is good enough.
* Vertices also carry along an id, created on allocation. The id
* is useful for making algorithms that don't depend on pointers.
* Also, they are easier to read while debugging.
* They also carry an orig index, which can be used to tie them back to
* vertices that the caller may have in a different way (e.g., #BMVert).
* An orig index can be #NO_INDEX, indicating the Vert was created by
* the algorithm and doesn't match an original Vert.
* Vertices can be reliably compared for equality,
* and hashed (on their co_exact field).
*/
struct Vert {
mpq3 co_exact;
double3 co;
int id = NO_INDEX;
int orig = NO_INDEX;
Vert() = default;
Vert(const mpq3 &mco, const double3 &dco, int id, int orig);
~Vert() = default;
/** Test equality on the co_exact field. */
bool operator==(const Vert &other) const;
/** Hash on the co_exact field. */
uint64_t hash() const;
};
std::ostream &operator<<(std::ostream &os, const Vert *v);
/**
* A Plane whose equation is `dot(norm, p) + d = 0`.
* The norm and d fields are always present, but the norm_exact
* and d_exact fields may be lazily populated. Since we don't
* store degenerate planes, we can tell if a the exact versions
* are not populated yet by having `norm_exact == 0`.
*/
struct Plane {
mpq3 norm_exact;
mpq_class d_exact;
double3 norm;
double d;
Plane() = default;
Plane(const mpq3 &norm_exact, const mpq_class &d_exact);
Plane(const double3 &norm, const double d);
/* Test equality on the exact fields. */
bool operator==(const Plane &other) const;
/* Hash onthe exact fields. */
uint64_t hash() const;
void make_canonical();
bool exact_populated() const;
void populate_exact();
};
std::ostream &operator<<(std::ostream &os, const Plane *plane);
/**
* A #Face has a sequence of Verts that for a CCW ordering around them.
* Faces carry an index, created at allocation time, useful for making
* pointer-independent algorithms, and for debugging.
* They also carry an original index, meaningful to the caller.
* And they carry original edge indices too: each is a number meaningful
* to the caller for the edge starting from the corresponding face position.
* A "face position" is the index of a vertex around a face.
* Faces don't own the memory pointed at by the vert array.
* Also indexed by face position, the is_intersect array says
* for each edge whether or not it is the result of intersecting
* with another face in the intersect algorithm.
* Since the intersect algorithm needs the plane for each face,
* a #Face also stores the Plane of the face, but this is only
* populate later because not all faces will be intersected.
*/
struct Face : NonCopyable {
Array<const Vert *> vert;
Array<int> edge_orig;
Array<bool> is_intersect;
Plane *plane = nullptr;
int id = NO_INDEX;
int orig = NO_INDEX;
using FacePos = int;
Face() = default;
Face(Span<const Vert *> verts, int id, int orig, Span<int> edge_origs, Span<bool> is_intersect);
Face(Span<const Vert *> verts, int id, int orig);
~Face();
bool is_tri() const
{
return vert.size() == 3;
}
/* Test equality of verts, in same positions. */
bool operator==(const Face &other) const;
/* Test equaliy faces allowing cyclic shifts. */
bool cyclic_equal(const Face &other) const;
FacePos next_pos(FacePos p) const
{
return (p + 1) % vert.size();
}
FacePos prev_pos(FacePos p) const
{
return (p + vert.size() - 1) % vert.size();
}
const Vert *const &operator[](int index) const
{
return vert[index];
}
int size() const
{
return vert.size();
}
const Vert *const *begin() const
{
return vert.begin();
}
const Vert *const *end() const
{
return vert.end();
}
IndexRange index_range() const
{
return IndexRange(vert.size());
}
void populate_plane(bool need_exact);
bool plane_populated() const
{
return plane != nullptr;
}
};
std::ostream &operator<<(std::ostream &os, const Face *f);
/**
* #IMeshArena is the owner of the Vert and Face resources used
* during a run of one of the mesh-intersect main functions.
* It also keeps has a hash table of all Verts created so that it can
* ensure that only one instance of a Vert with a given co_exact will
* exist. I.e., it de-duplicates the vertices.
*/
class IMeshArena : NonCopyable, NonMovable {
class IMeshArenaImpl;
std::unique_ptr<IMeshArenaImpl> pimpl_;
public:
IMeshArena();
~IMeshArena();
/**
* Provide hints to number of expected Verts and Faces expected
* to be allocated.
*/
void reserve(int vert_num_hint, int face_num_hint);
int tot_allocated_verts() const;
int tot_allocated_faces() const;
/**
* These add routines find and return an existing Vert with the same
* co_exact, if it exists (the orig argument is ignored in this case),
* or else allocates and returns a new one. The index field of a
* newly allocated Vert will be the index in creation order.
*/
const Vert *add_or_find_vert(const mpq3 &co, int orig);
const Vert *add_or_find_vert(const double3 &co, int orig);
Face *add_face(Span<const Vert *> verts,
int orig,
Span<int> edge_origs,
Span<bool> is_intersect);
Face *add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs);
Face *add_face(Span<const Vert *> verts, int orig);
/** The following return #nullptr if not found. */
const Vert *find_vert(const mpq3 &co) const;
const Face *find_face(Span<const Vert *> verts) const;
};
/**
* A #blender::meshintersect::IMesh is a self-contained mesh structure
* that can be used in `blenlib` without depending on the rest of Blender.
* The Vert and #Face resources used in the #IMesh should be owned by
* some #IMeshArena.
* The Verts used by a #IMesh can be recovered from the Faces, so
* are usually not stored, but on request, the #IMesh can populate
* internal structures for indexing exactly the set of needed Verts,
* and also going from a Vert pointer to the index in that system.
*/
class IMesh {
Array<Face *> face_; /* Not `const` so can lazily populate planes. */
Array<const Vert *> vert_; /* Only valid if vert_populated_. */
Map<const Vert *, int> vert_to_index_; /* Only valid if vert_populated_. */
bool vert_populated_ = false;
public:
IMesh() = default;
IMesh(Span<Face *> faces) : face_(faces)
{
}
void set_faces(Span<Face *> faces);
Face *face(int index) const
{
return face_[index];
}
int face_size() const
{
return face_.size();
}
int vert_size() const
{
return vert_.size();
}
bool has_verts() const
{
return vert_populated_;
}
void set_dirty_verts()
{
vert_populated_ = false;
vert_to_index_.clear();
vert_ = Array<const Vert *>();
}
/* Pass `max_verts` if there is a good bound estimate on the maximum number of verts. */
void populate_vert();
void populate_vert(int max_verts);
const Vert *vert(int index) const
{
BLI_assert(vert_populated_);
return vert_[index];
}
/** Returns index in vert_ where v is, or #NO_INDEX. */
int lookup_vert(const Vert *v) const;
IndexRange vert_index_range() const
{
BLI_assert(vert_populated_);
return IndexRange(vert_.size());
}
IndexRange face_index_range() const
{
return IndexRange(face_.size());
}
Span<const Vert *> vertices() const
{
BLI_assert(vert_populated_);
return Span<const Vert *>(vert_);
}
Span<Face *> faces() const
{
return Span<Face *>(face_);
}
/**
* Replace face at given index with one that elides the
* vertices at the positions in face_pos_erase that are true.
* Use arena to allocate the new face in.
*/
void erase_face_positions(int f_index, Span<bool> face_pos_erase, IMeshArena *arena);
};
std::ostream &operator<<(std::ostream &os, const IMesh &mesh);
/**
* The output will have duplicate vertices merged and degenerate triangles ignored.
* If the input has overlapping co-planar triangles, then there will be
* as many duplicates as there are overlaps in each overlapping triangular region.
* The orig field of each #IndexedTriangle will give the orig index in the input #IMesh
* that the output triangle was a part of (input can have -1 for that field and then
* the index in `tri[]` will be used as the original index).
* The orig structure of the output #IMesh gives the originals for vertices and edges.
* Note: if the input tm_in has a non-empty orig structure, then it is ignored.
*/
IMesh trimesh_self_intersect(const IMesh &tm_in, IMeshArena *arena);
IMesh trimesh_nary_intersect(const IMesh &tm_in,
int nshapes,
std::function<int(int)> shape_fn,
bool use_self,
IMeshArena *arena);
/** This has the side effect of populating verts in the #IMesh. */
void write_obj_mesh(IMesh &m, const std::string &objname);
} /* namespace blender::meshintersect */
#endif /* WITH_GMP */

View File

@ -0,0 +1,184 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*/
#ifdef WITH_GMP
# include "BLI_math_mpq.hh"
# include "BLI_mpq3.hh"
namespace blender {
struct mpq2 {
mpq_class x, y;
mpq2() = default;
mpq2(const mpq_class *ptr) : x{ptr[0]}, y{ptr[1]}
{
}
mpq2(mpq_class x, mpq_class y) : x(x), y(y)
{
}
mpq2(const mpq2 &other) : x(other.x), y(other.y)
{
}
mpq2(mpq2 &&other) noexcept : x(std::move(other.x)), y(std::move(other.y))
{
}
~mpq2() = default;
mpq2 &operator=(const mpq2 &other)
{
if (this != &other) {
x = other.x;
y = other.y;
}
return *this;
}
mpq2 &operator=(mpq2 &&other) noexcept
{
x = std::move(other.x);
y = std::move(other.y);
return *this;
}
mpq2(const mpq3 &other) : x(other.x), y(other.y)
{
}
operator mpq_class *()
{
return &x;
}
operator const mpq_class *() const
{
return &x;
}
/**
* Cannot do this exactly in rational arithmetic!
* Approximate by going in and out of doubles.
*/
mpq_class length() const
{
mpq_class lsquared = dot(*this, *this);
return mpq_class(sqrt(lsquared.get_d()));
}
friend mpq2 operator+(const mpq2 &a, const mpq2 &b)
{
return {a.x + b.x, a.y + b.y};
}
friend mpq2 operator-(const mpq2 &a, const mpq2 &b)
{
return {a.x - b.x, a.y - b.y};
}
friend mpq2 operator*(const mpq2 &a, mpq_class b)
{
return {a.x * b, a.y * b};
}
friend mpq2 operator/(const mpq2 &a, mpq_class b)
{
BLI_assert(b != 0);
return {a.x / b, a.y / b};
}
friend mpq2 operator*(mpq_class a, const mpq2 &b)
{
return b * a;
}
friend bool operator==(const mpq2 &a, const mpq2 &b)
{
return a.x == b.x && a.y == b.y;
}
friend bool operator!=(const mpq2 &a, const mpq2 &b)
{
return a.x != b.x || a.y != b.y;
}
friend std::ostream &operator<<(std::ostream &stream, const mpq2 &v)
{
stream << "(" << v.x << ", " << v.y << ")";
return stream;
}
static mpq_class dot(const mpq2 &a, const mpq2 &b)
{
return a.x * b.x + a.y * b.y;
}
static mpq2 interpolate(const mpq2 &a, const mpq2 &b, mpq_class t)
{
return a * (1 - t) + b * t;
}
static mpq2 abs(const mpq2 &a)
{
mpq_class abs_x = (a.x >= 0) ? a.x : -a.x;
mpq_class abs_y = (a.y >= 0) ? a.y : -a.y;
return mpq2(abs_x, abs_y);
}
static mpq_class distance(const mpq2 &a, const mpq2 &b)
{
return (a - b).length();
}
static mpq_class distance_squared(const mpq2 &a, const mpq2 &b)
{
return dot(a, b);
}
struct isect_result {
enum {
LINE_LINE_COLINEAR = -1,
LINE_LINE_NONE = 0,
LINE_LINE_EXACT = 1,
LINE_LINE_CROSS = 2,
} kind;
mpq_class lambda;
mpq_class mu;
};
static isect_result isect_seg_seg(const mpq2 &v1,
const mpq2 &v2,
const mpq2 &v3,
const mpq2 &v4);
/** There is a sensible use for hashing on exact arithmetic types. */
uint64_t hash() const;
};
} // namespace blender
#endif /* WITH_GMP */

View File

@ -0,0 +1,281 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*/
#ifdef WITH_GMP
# include <iostream>
# include "BLI_math.h"
# include "BLI_math_mpq.hh"
# include "BLI_span.hh"
namespace blender {
struct mpq3 {
mpq_class x, y, z;
mpq3() = default;
mpq3(const mpq_class *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}
{
}
mpq3(const mpq_class (*ptr)[3]) : mpq3((const mpq_class *)ptr)
{
}
explicit mpq3(mpq_class value) : x(value), y(value), z(value)
{
}
explicit mpq3(int value) : x(value), y(value), z(value)
{
}
mpq3(mpq_class x, mpq_class y, mpq_class z) : x{x}, y{y}, z{z}
{
}
operator const mpq_class *() const
{
return &x;
}
operator mpq_class *()
{
return &x;
}
/* Cannot do this exactly in rational arithmetic!
* Approximate by going in and out of doubles.
*/
mpq_class normalize_and_get_length()
{
double dv[3] = {x.get_d(), y.get_d(), z.get_d()};
double len = normalize_v3_db(dv);
this->x = mpq_class(dv[0]);
this->y = mpq_class(dv[1]);
this->z = mpq_class(dv[2]);
return len;
}
mpq3 normalized() const
{
double dv[3] = {x.get_d(), y.get_d(), z.get_d()};
double dr[3];
normalize_v3_v3_db(dr, dv);
return mpq3(mpq_class(dr[0]), mpq_class(dr[1]), mpq_class(dr[2]));
}
/* Cannot do this exactly in rational arithmetic!
* Approximate by going in and out of double.
*/
mpq_class length() const
{
mpq_class lsquared = this->length_squared();
double dsquared = lsquared.get_d();
double d = sqrt(dsquared);
return mpq_class(d);
}
mpq_class length_squared() const
{
return x * x + y * y + z * z;
}
void reflect(const mpq3 &normal)
{
*this = this->reflected(normal);
}
mpq3 reflected(const mpq3 &normal) const
{
mpq3 result;
const mpq_class dot2 = 2 * dot(*this, normal);
result.x = this->x - (dot2 * normal.x);
result.y = this->y - (dot2 * normal.y);
result.z = this->z - (dot2 * normal.z);
return result;
}
static mpq3 safe_divide(const mpq3 &a, const mpq3 &b)
{
mpq3 result;
result.x = (b.x == 0) ? mpq_class(0) : a.x / b.x;
result.y = (b.y == 0) ? mpq_class(0) : a.y / b.y;
result.z = (b.z == 0) ? mpq_class(0) : a.z / b.z;
return result;
}
void invert()
{
x = -x;
y = -y;
z = -z;
}
friend mpq3 operator+(const mpq3 &a, const mpq3 &b)
{
return mpq3(a.x + b.x, a.y + b.y, a.z + b.z);
}
void operator+=(const mpq3 &b)
{
this->x += b.x;
this->y += b.y;
this->z += b.z;
}
friend mpq3 operator-(const mpq3 &a, const mpq3 &b)
{
return mpq3(a.x - b.x, a.y - b.y, a.z - b.z);
}
friend mpq3 operator-(const mpq3 &a)
{
return mpq3(-a.x, -a.y, -a.z);
}
void operator-=(const mpq3 &b)
{
this->x -= b.x;
this->y -= b.y;
this->z -= b.z;
}
void operator*=(mpq_class scalar)
{
this->x *= scalar;
this->y *= scalar;
this->z *= scalar;
}
void operator*=(const mpq3 &other)
{
this->x *= other.x;
this->y *= other.y;
this->z *= other.z;
}
friend mpq3 operator*(const mpq3 &a, const mpq3 &b)
{
return {a.x * b.x, a.y * b.y, a.z * b.z};
}
friend mpq3 operator*(const mpq3 &a, const mpq_class &b)
{
return mpq3(a.x * b, a.y * b, a.z * b);
}
friend mpq3 operator*(const mpq_class &a, const mpq3 &b)
{
return mpq3(a * b.x, a * b.y, a * b.z);
}
friend mpq3 operator/(const mpq3 &a, const mpq_class &b)
{
BLI_assert(b != 0);
return mpq3(a.x / b, a.y / b, a.z / b);
}
friend bool operator==(const mpq3 &a, const mpq3 &b)
{
return a.x == b.x && a.y == b.y && a.z == b.z;
}
friend bool operator!=(const mpq3 &a, const mpq3 &b)
{
return a.x != b.x || a.y != b.y || a.z != b.z;
}
friend std::ostream &operator<<(std::ostream &stream, const mpq3 &v)
{
stream << "(" << v.x << ", " << v.y << ", " << v.z << ")";
return stream;
}
static mpq_class dot(const mpq3 &a, const mpq3 &b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static mpq3 cross(const mpq3 &a, const mpq3 &b)
{
return mpq3(a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]);
}
static mpq3 cross_high_precision(const mpq3 &a, const mpq3 &b)
{
return cross(a, b);
}
static mpq3 project(const mpq3 &a, const mpq3 &b)
{
const mpq_class mul = mpq3::dot(a, b) / mpq3::dot(b, b);
return mpq3(mul * b[0], mul * b[1], mul * b[2]);
}
static mpq_class distance(const mpq3 &a, const mpq3 &b)
{
mpq3 diff(a.x - b.x, a.y - b.y, a.z - b.z);
return diff.length();
}
static mpq_class distance_squared(const mpq3 &a, const mpq3 &b)
{
mpq3 diff(a.x - b.x, a.y - b.y, a.z - b.z);
return mpq3::dot(diff, diff);
}
static mpq3 interpolate(const mpq3 &a, const mpq3 &b, mpq_class t)
{
mpq_class s = 1 - t;
return mpq3(a.x * s + b.x * t, a.y * s + b.y * t, a.z * s + b.z * t);
}
static mpq3 abs(const mpq3 &a)
{
mpq_class abs_x = (a.x >= 0) ? a.x : -a.x;
mpq_class abs_y = (a.y >= 0) ? a.y : -a.y;
mpq_class abs_z = (a.z >= 0) ? a.z : -a.z;
return mpq3(abs_x, abs_y, abs_z);
}
static int dominant_axis(const mpq3 &a)
{
mpq_class x = (a.x >= 0) ? a.x : -a.x;
mpq_class y = (a.y >= 0) ? a.y : -a.y;
mpq_class z = (a.z >= 0) ? a.z : -a.z;
return ((x > y) ? ((x > z) ? 0 : 2) : ((y > z) ? 1 : 2));
}
static mpq3 cross_poly(Span<mpq3> poly);
/** There is a sensible use for hashing on exact arithmetic types. */
uint64_t hash() const;
};
uint64_t hash_mpq_class(const mpq_class &value);
} // namespace blender
#endif /* WITH_GMP */

View File

@ -32,6 +32,7 @@ set(INC
set(INC_SYS
${ZLIB_INCLUDE_DIRS}
${FREETYPE_INCLUDE_DIRS}
${GMP_INCLUDE_DIRS}
)
set(SRC
@ -64,7 +65,7 @@ set(SRC
intern/boxpack_2d.c
intern/buffer.c
intern/convexhull_2d.c
intern/delaunay_2d.c
intern/delaunay_2d.cc
intern/dot_export.cc
intern/dynlib.c
intern/easing.c
@ -89,6 +90,7 @@ set(SRC
intern/math_base_inline.c
intern/math_base_safe_inline.c
intern/math_bits_inline.c
intern/math_boolean.cc
intern/math_color.c
intern/math_color_blend_inline.c
intern/math_color_inline.c
@ -99,9 +101,12 @@ set(SRC
intern/math_rotation.c
intern/math_solvers.c
intern/math_statistics.c
intern/math_vec.cc
intern/math_vector.c
intern/math_vector_inline.c
intern/memory_utils.c
intern/mesh_boolean.cc
intern/mesh_intersect.cc
intern/noise.c
intern/path_util.c
intern/polyfill_2d.c
@ -170,6 +175,8 @@ set(SRC
BLI_dlrbTree.h
BLI_dot_export.hh
BLI_dot_export_attribute_enums.hh
BLI_double2.hh
BLI_double3.hh
BLI_dynlib.h
BLI_dynstr.h
BLI_easing.h
@ -214,11 +221,13 @@ set(SRC
BLI_math_base.h
BLI_math_base_safe.h
BLI_math_bits.h
BLI_math_boolean.hh
BLI_math_color.h
BLI_math_color_blend.h
BLI_math_geom.h
BLI_math_inline.h
BLI_math_interp.h
BLI_math_mpq.hh
BLI_math_matrix.h
BLI_math_rotation.h
BLI_math_solvers.h
@ -230,6 +239,10 @@ set(SRC
BLI_memory_utils.h
BLI_memory_utils.hh
BLI_mempool.h
BLI_mesh_boolean.hh
BLI_mesh_intersect.hh
BLI_mpq2.hh
BLI_mpq3.hh
BLI_noise.h
BLI_path_util.h
BLI_polyfill_2d.h
@ -306,6 +319,18 @@ if(WITH_TBB)
)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
list(APPEND INC_SYS
${GMP_INCLUDE_DIRS}
)
list(APPEND LIB
${GMP_LIBRARIES}
)
endif()
if(WIN32)
list(APPEND INC
../../../intern/utfconv
@ -374,6 +399,8 @@ if(WITH_GTESTS)
tests/BLI_math_vector_test.cc
tests/BLI_memiter_test.cc
tests/BLI_memory_utils_test.cc
tests/BLI_mesh_boolean_test.cc
tests/BLI_mesh_intersect_test.cc
tests/BLI_multi_value_map_test.cc
tests/BLI_path_util_test.cc
tests/BLI_polyfill_2d_test.cc

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,193 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bli
*/
#include "BLI_double2.hh"
#include "BLI_double3.hh"
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_hash.hh"
#include "BLI_math_mpq.hh"
#include "BLI_mpq2.hh"
#include "BLI_mpq3.hh"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
namespace blender {
float2::isect_result float2::isect_seg_seg(const float2 &v1,
const float2 &v2,
const float2 &v3,
const float2 &v4)
{
float2::isect_result ans;
float div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]);
if (div == 0.0f) {
ans.lambda = 0.0f;
ans.mu = 0.0f;
ans.kind = float2::isect_result::LINE_LINE_COLINEAR;
}
else {
ans.lambda = ((v1[1] - v3[1]) * (v4[0] - v3[0]) - (v1[0] - v3[0]) * (v4[1] - v3[1])) / div;
ans.mu = ((v1[1] - v3[1]) * (v2[0] - v1[0]) - (v1[0] - v3[0]) * (v2[1] - v1[1])) / div;
if (ans.lambda >= 0.0f && ans.lambda <= 1.0f && ans.mu >= 0.0f && ans.mu <= 1.0f) {
if (ans.lambda == 0.0f || ans.lambda == 1.0f || ans.mu == 0.0f || ans.mu == 1.0f) {
ans.kind = float2::isect_result::LINE_LINE_EXACT;
}
else {
ans.kind = float2::isect_result::LINE_LINE_CROSS;
}
}
else {
ans.kind = float2::isect_result::LINE_LINE_NONE;
}
}
return ans;
}
double2::isect_result double2::isect_seg_seg(const double2 &v1,
const double2 &v2,
const double2 &v3,
const double2 &v4)
{
double2::isect_result ans;
double div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]);
if (div == 0.0) {
ans.lambda = 0.0;
ans.mu = 0.0;
ans.kind = double2::isect_result::LINE_LINE_COLINEAR;
}
else {
ans.lambda = ((v1[1] - v3[1]) * (v4[0] - v3[0]) - (v1[0] - v3[0]) * (v4[1] - v3[1])) / div;
ans.mu = ((v1[1] - v3[1]) * (v2[0] - v1[0]) - (v1[0] - v3[0]) * (v2[1] - v1[1])) / div;
if (ans.lambda >= 0.0 && ans.lambda <= 1.0 && ans.mu >= 0.0 && ans.mu <= 1.0) {
if (ans.lambda == 0.0 || ans.lambda == 1.0 || ans.mu == 0.0 || ans.mu == 1.0) {
ans.kind = double2::isect_result::LINE_LINE_EXACT;
}
else {
ans.kind = double2::isect_result::LINE_LINE_CROSS;
}
}
else {
ans.kind = double2::isect_result::LINE_LINE_NONE;
}
}
return ans;
}
#ifdef WITH_GMP
mpq2::isect_result mpq2::isect_seg_seg(const mpq2 &v1,
const mpq2 &v2,
const mpq2 &v3,
const mpq2 &v4)
{
mpq2::isect_result ans;
mpq_class div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]);
if (div == 0.0) {
ans.lambda = 0.0;
ans.mu = 0.0;
ans.kind = mpq2::isect_result::LINE_LINE_COLINEAR;
}
else {
ans.lambda = ((v1[1] - v3[1]) * (v4[0] - v3[0]) - (v1[0] - v3[0]) * (v4[1] - v3[1])) / div;
ans.mu = ((v1[1] - v3[1]) * (v2[0] - v1[0]) - (v1[0] - v3[0]) * (v2[1] - v1[1])) / div;
if (ans.lambda >= 0 && ans.lambda <= 1 && ans.mu >= 0 && ans.mu <= 1) {
if (ans.lambda == 0 || ans.lambda == 1 || ans.mu == 0 || ans.mu == 1) {
ans.kind = mpq2::isect_result::LINE_LINE_EXACT;
}
else {
ans.kind = mpq2::isect_result::LINE_LINE_CROSS;
}
}
else {
ans.kind = mpq2::isect_result::LINE_LINE_NONE;
}
}
return ans;
}
#endif
double3 double3::cross_poly(Span<double3> poly)
{
/* Newell's Method. */
int nv = static_cast<int>(poly.size());
if (nv < 3) {
return double3(0, 0, 0);
}
const double3 *v_prev = &poly[nv - 1];
const double3 *v_curr = &poly[0];
double3 n(0, 0, 0);
for (int i = 0; i < nv;) {
n[0] = n[0] + ((*v_prev)[1] - (*v_curr)[1]) * ((*v_prev)[2] + (*v_curr)[2]);
n[1] = n[1] + ((*v_prev)[2] - (*v_curr)[2]) * ((*v_prev)[0] + (*v_curr)[0]);
n[2] = n[2] + ((*v_prev)[0] - (*v_curr)[0]) * ((*v_prev)[1] + (*v_curr)[1]);
v_prev = v_curr;
++i;
if (i < nv) {
v_curr = &poly[i];
}
}
return n;
}
mpq3 mpq3::cross_poly(Span<mpq3> poly)
{
/* Newell's Method. */
int nv = static_cast<int>(poly.size());
if (nv < 3) {
return mpq3(0);
}
const mpq3 *v_prev = &poly[nv - 1];
const mpq3 *v_curr = &poly[0];
mpq3 n(0);
for (int i = 0; i < nv;) {
n[0] = n[0] + ((*v_prev)[1] - (*v_curr)[1]) * ((*v_prev)[2] + (*v_curr)[2]);
n[1] = n[1] + ((*v_prev)[2] - (*v_curr)[2]) * ((*v_prev)[0] + (*v_curr)[0]);
n[2] = n[2] + ((*v_prev)[0] - (*v_curr)[0]) * ((*v_prev)[1] + (*v_curr)[1]);
v_prev = v_curr;
++i;
if (i < nv) {
v_curr = &poly[i];
}
}
return n;
}
uint64_t hash_mpq_class(const mpq_class &value)
{
/* TODO: better/faster implementation of this. */
return DefaultHash<float>{}(static_cast<float>(value.get_d()));
}
uint64_t mpq2::hash() const
{
uint64_t hashx = hash_mpq_class(this->x);
uint64_t hashy = hash_mpq_class(this->y);
return hashx ^ (hashy * 33);
}
uint64_t mpq3::hash() const
{
uint64_t hashx = hash_mpq_class(this->x);
uint64_t hashy = hash_mpq_class(this->y);
uint64_t hashz = hash_mpq_class(this->z);
return hashx ^ (hashy * 33) ^ (hashz * 33 * 37);
}
} // namespace blender

View File

@ -669,6 +669,15 @@ void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3])
out[2] = mul * v_proj[2];
}
void project_v3_v3v3_db(double out[3], const double p[3], const double v_proj[3])
{
const double mul = dot_v3v3_db(p, v_proj) / dot_v3v3_db(v_proj, v_proj);
out[0] = mul * v_proj[0];
out[1] = mul * v_proj[1];
out[2] = mul * v_proj[2];
}
/**
* Project \a p onto a unit length \a v_proj
*/
@ -796,6 +805,17 @@ void reflect_v3_v3v3(float out[3], const float v[3], const float normal[3])
out[2] = v[2] - (dot2 * normal[2]);
}
void reflect_v3_v3v3_db(double out[3], const double v[3], const double normal[3])
{
const double dot2 = 2.0 * dot_v3v3_db(v, normal);
/* BLI_ASSERT_UNIT_V3_DB(normal); this assert is not known? */
out[0] = v[0] - (dot2 * normal[0]);
out[1] = v[1] - (dot2 * normal[1]);
out[2] = v[2] - (dot2 * normal[2]);
}
/**
* Takes a vector and computes 2 orthogonal directions.
*

View File

@ -571,6 +571,13 @@ MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f)
r[2] = a[2] * f;
}
MINLINE void mul_v3_v3db_db(double r[3], const double a[3], double f)
{
r[0] = a[0] * f;
r[1] = a[1] * f;
r[2] = a[2] * f;
}
MINLINE void mul_v2_v2(float r[2], const float a[2])
{
r[0] *= a[0];
@ -988,6 +995,11 @@ MINLINE float len_squared_v3(const float v[3])
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
MINLINE double len_squared_v3_db(const double v[3])
{
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
MINLINE float len_manhattan_v2(const float v[2])
{
return fabsf(v[0]) + fabsf(v[1]);
@ -1045,6 +1057,11 @@ MINLINE float len_v3(const float a[3])
return sqrtf(dot_v3v3(a, a));
}
MINLINE double len_v3_db(const double a[3])
{
return sqrt(dot_v3v3_db(a, a));
}
MINLINE float len_squared_v2v2(const float a[2], const float b[2])
{
float d[2];
@ -1161,7 +1178,29 @@ MINLINE float normalize_v3_v3(float r[3], const float a[3])
return normalize_v3_v3_length(r, a, 1.0f);
}
MINLINE double normalize_v3_length_d(double n[3], const double unit_length)
MINLINE double normalize_v3_v3_length_db(double r[3], const double a[3], double unit_length)
{
double d = dot_v3v3_db(a, a);
/* a larger value causes normalize errors in a
* scaled down models with camera extreme close */
if (d > 1.0e-70) {
d = sqrt(d);
mul_v3_v3db_db(r, a, unit_length / d);
}
else {
zero_v3_db(r);
d = 0.0;
}
return d;
}
MINLINE double normalize_v3_v3_db(double r[3], const double a[3])
{
return normalize_v3_v3_length_db(r, a, 1.0);
}
MINLINE double normalize_v3_length_db(double n[3], const double unit_length)
{
double d = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
@ -1184,9 +1223,9 @@ MINLINE double normalize_v3_length_d(double n[3], const double unit_length)
return d;
}
MINLINE double normalize_v3_d(double n[3])
MINLINE double normalize_v3_db(double n[3])
{
return normalize_v3_length_d(n, 1.0);
return normalize_v3_length_db(n, 1.0);
}
MINLINE float normalize_v3_length(float n[3], const float unit_length)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,908 @@
/* Apache License, Version 2.0 */
#include "testing/testing.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_map.hh"
#include "BLI_math_mpq.hh"
#include "BLI_mesh_boolean.hh"
#include "BLI_mpq3.hh"
#include "BLI_vector.hh"
namespace blender::meshintersect::tests {
constexpr bool DO_OBJ = false;
/* Build and hold an IMesh from a string spec. Also hold and own resources used by IMesh. */
class IMeshBuilder {
public:
IMesh imesh;
IMeshArena arena;
/* "Edge orig" indices are an encoding of <input face#, position in face of start of edge>. */
static constexpr int MAX_FACE_LEN = 1000; /* Used for forming "orig edge" indices only. */
static int edge_index(int face_index, int facepos)
{
return face_index * MAX_FACE_LEN + facepos;
}
static std::pair<int, int> face_and_pos_for_edge_index(int e_index)
{
return std::pair<int, int>(e_index / MAX_FACE_LEN, e_index % MAX_FACE_LEN);
}
/*
* Spec should have form:
* #verts #faces
* mpq_class mpq_class mpq_clas [#verts lines]
* int int int ... [#faces lines; indices into verts for given face]
*/
IMeshBuilder(const char *spec)
{
std::istringstream ss(spec);
std::string line;
getline(ss, line);
std::istringstream hdrss(line);
int nv, nf;
hdrss >> nv >> nf;
if (nv == 0 || nf == 0) {
return;
}
arena.reserve(nv, nf);
Vector<const Vert *> verts;
Vector<Face *> faces;
bool spec_ok = true;
int v_index = 0;
while (v_index < nv && spec_ok && getline(ss, line)) {
std::istringstream iss(line);
mpq_class p0;
mpq_class p1;
mpq_class p2;
iss >> p0 >> p1 >> p2;
spec_ok = !iss.fail();
verts.append(arena.add_or_find_vert(mpq3(p0, p1, p2), v_index));
++v_index;
}
if (v_index != nv) {
spec_ok = false;
}
int f_index = 0;
while (f_index < nf && spec_ok && getline(ss, line)) {
std::istringstream fss(line);
Vector<const Vert *> face_verts;
Vector<int> edge_orig;
int fpos = 0;
while (spec_ok && fss >> v_index) {
if (v_index < 0 || v_index >= nv) {
spec_ok = false;
continue;
}
face_verts.append(verts[v_index]);
edge_orig.append(edge_index(f_index, fpos));
++fpos;
}
Face *facep = arena.add_face(face_verts, f_index, edge_orig);
faces.append(facep);
++f_index;
}
if (f_index != nf) {
spec_ok = false;
}
if (!spec_ok) {
std::cout << "Bad spec: " << spec;
return;
}
imesh = IMesh(faces);
}
};
static int all_shape_zero(int UNUSED(t))
{
return 0;
}
TEST(boolean_trimesh, Empty)
{
IMeshArena arena;
IMesh in;
IMesh out = boolean_trimesh(in, BoolOpType::None, 1, all_shape_zero, true, &arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 0);
EXPECT_EQ(out.face_size(), 0);
}
TEST(boolean_trimesh, TetTetTrimesh)
{
const char *spec = R"(8 8
0 0 0
2 0 0
1 2 0
1 1 2
0 0 1
2 0 1
1 2 1
1 1 3
0 2 1
0 1 3
1 2 3
2 0 3
4 6 5
4 5 7
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(mb.imesh, BoolOpType::None, 1, all_shape_zero, true, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 11);
EXPECT_EQ(out.face_size(), 20);
if (DO_OBJ) {
write_obj_mesh(out, "tettet_tm");
}
IMeshBuilder mb2(spec);
IMesh out2 = boolean_trimesh(mb2.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb2.arena);
out2.populate_vert();
EXPECT_EQ(out2.vert_size(), 10);
EXPECT_EQ(out2.face_size(), 16);
if (DO_OBJ) {
write_obj_mesh(out2, "tettet_union_tm");
}
IMeshBuilder mb3(spec);
IMesh out3 = boolean_trimesh(
mb3.imesh, BoolOpType::Union, 2, [](int t) { return t < 4 ? 0 : 1; }, false, &mb3.arena);
out3.populate_vert();
EXPECT_EQ(out3.vert_size(), 10);
EXPECT_EQ(out3.face_size(), 16);
if (DO_OBJ) {
write_obj_mesh(out3, "tettet_union_binary_tm");
}
IMeshBuilder mb4(spec);
IMesh out4 = boolean_trimesh(
mb4.imesh, BoolOpType::Union, 2, [](int t) { return t < 4 ? 0 : 1; }, true, &mb4.arena);
out4.populate_vert();
EXPECT_EQ(out4.vert_size(), 10);
EXPECT_EQ(out4.face_size(), 16);
if (DO_OBJ) {
write_obj_mesh(out4, "tettet_union_binary_self_tm");
}
IMeshBuilder mb5(spec);
IMesh out5 = boolean_trimesh(
mb5.imesh, BoolOpType::Intersect, 2, [](int t) { return t < 4 ? 0 : 1; }, false, &mb5.arena);
out5.populate_vert();
EXPECT_EQ(out5.vert_size(), 4);
EXPECT_EQ(out5.face_size(), 4);
if (DO_OBJ) {
write_obj_mesh(out5, "tettet_intersect_binary_tm");
}
IMeshBuilder mb6(spec);
IMesh out6 = boolean_trimesh(
mb6.imesh,
BoolOpType::Difference,
2,
[](int t) { return t < 4 ? 0 : 1; },
false,
&mb6.arena);
out6.populate_vert();
EXPECT_EQ(out6.vert_size(), 6);
EXPECT_EQ(out6.face_size(), 8);
if (DO_OBJ) {
write_obj_mesh(out6, "tettet_difference_binary_tm");
}
IMeshBuilder mb7(spec);
IMesh out7 = boolean_trimesh(
mb7.imesh,
BoolOpType::Difference,
2,
[](int t) { return t < 4 ? 1 : 0; },
false,
&mb7.arena);
out7.populate_vert();
EXPECT_EQ(out7.vert_size(), 8);
EXPECT_EQ(out7.face_size(), 12);
if (DO_OBJ) {
write_obj_mesh(out7, "tettet_difference_rev_binary_tm");
}
}
TEST(boolean_trimesh, TetTet2Trimesh)
{
const char *spec = R"(8 8
0 1 -1
7/8 -1/2 -1
-7/8 -1/2 -1
0 0 1
0 1 0
7/8 -1/2 0
-7/8 -1/2 0
0 0 2
0 3 1
0 1 2
1 3 2
2 3 0
4 7 5
4 5 6
5 7 6
6 7 4
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 10);
EXPECT_EQ(out.face_size(), 16);
if (DO_OBJ) {
write_obj_mesh(out, "tettet2_union_tm");
}
}
TEST(boolean_trimesh, CubeTetTrimesh)
{
const char *spec = R"(12 16
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
0 1/2 1/2
1/2 -1/4 1/2
-1/2 -1/4 1/2
0 0 3/2
0 1 3
0 3 2
2 3 7
2 7 6
6 7 5
6 5 4
4 5 1
4 1 0
2 6 4
2 4 0
7 3 1
7 1 5
8 11 9
8 9 10
9 11 10
10 11 8
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 14);
EXPECT_EQ(out.face_size(), 24);
if (DO_OBJ) {
write_obj_mesh(out, "cubetet_union_tm");
}
}
TEST(boolean_trimesh, BinaryTetTetTrimesh)
{
const char *spec = R"(8 8
0 0 0
2 0 0
1 2 0
1 1 2
0 0 1
2 0 1
1 2 1
1 1 3
0 2 1
0 1 3
1 2 3
2 0 3
4 6 5
4 5 7
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(
mb.imesh, BoolOpType::Intersect, 2, [](int t) { return t < 4 ? 0 : 1; }, false, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 4);
EXPECT_EQ(out.face_size(), 4);
if (DO_OBJ) {
write_obj_mesh(out, "binary_tettet_isect_tm");
}
}
TEST(boolean_trimesh, TetTetCoplanarTrimesh)
{
const char *spec = R"(8 8
0 1 0
7/8 -1/2 0
-7/8 -1/2 0
0 0 1
0 1 0
7/8 -1/2 0
-7/8 -1/2 0
0 0 -1
0 3 1
0 1 2
1 3 2
2 3 0
4 5 7
4 6 5
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 5);
EXPECT_EQ(out.face_size(), 6);
if (DO_OBJ) {
write_obj_mesh(out, "tettet_coplanar_tm");
}
}
TEST(boolean_trimesh, TetInsideTetTrimesh)
{
const char *spec = R"(8 8
0 0 0
2 0 0
1 2 0
1 1 2
-1 -3/4 -1/2
3 -3/4 -1/2
1 13/4 -1/2
1 5/4 7/2
0 2 1
0 1 3
1 2 3
2 0 3
4 6 5
4 5 7
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 4);
EXPECT_EQ(out.face_size(), 4);
if (DO_OBJ) {
write_obj_mesh(out, "tetinsidetet_tm");
}
}
TEST(boolean_trimesh, TetBesideTetTrimesh)
{
const char *spec = R"(8 8
0 0 0
2 0 0
1 2 0
1 1 2
3 0 0
5 0 0
4 2 0
4 1 2
0 2 1
0 1 3
1 2 3
2 0 3
4 6 5
4 5 7
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 8);
EXPECT_EQ(out.face_size(), 8);
if (DO_OBJ) {
write_obj_mesh(out, "tetbesidetet_tm");
}
}
TEST(boolean_trimesh, DegenerateTris)
{
const char *spec = R"(10 10
0 0 0
2 0 0
1 2 0
1 1 2
0 0 1
2 0 1
1 2 1
1 1 3
0 0 0
1 0 0
0 2 1
0 8 1
0 1 3
1 2 3
2 0 3
4 6 5
4 5 7
5 6 7
6 4 7
0 1 9
)";
IMeshBuilder mb(spec);
IMesh out = boolean_trimesh(
mb.imesh, BoolOpType::Intersect, 2, [](int t) { return t < 5 ? 0 : 1; }, false, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 4);
EXPECT_EQ(out.face_size(), 4);
if (DO_OBJ) {
write_obj_mesh(out, "degenerate_tris_tm");
}
}
TEST(boolean_polymesh, TetTet)
{
const char *spec = R"(8 8
0 0 0
2 0 0
1 2 0
1 1 2
0 0 1
2 0 1
1 2 1
1 1 3
0 2 1
0 1 3
1 2 3
2 0 3
4 6 5
4 5 7
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh, BoolOpType::None, 1, all_shape_zero, true, nullptr, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 11);
EXPECT_EQ(out.face_size(), 13);
if (DO_OBJ) {
write_obj_mesh(out, "tettet");
}
IMeshBuilder mb2(spec);
IMesh out2 = boolean_mesh(
mb2.imesh,
BoolOpType::None,
2,
[](int t) { return t < 4 ? 0 : 1; },
false,
nullptr,
&mb2.arena);
out2.populate_vert();
EXPECT_EQ(out2.vert_size(), 11);
EXPECT_EQ(out2.face_size(), 13);
if (DO_OBJ) {
write_obj_mesh(out, "tettet2");
}
}
TEST(boolean_polymesh, CubeCube)
{
const char *spec = R"(16 12
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
1/2 1/2 1/2
1/2 1/2 5/2
1/2 5/2 1/2
1/2 5/2 5/2
5/2 1/2 1/2
5/2 1/2 5/2
5/2 5/2 1/2
5/2 5/2 5/2
0 1 3 2
6 2 3 7
4 6 7 5
0 4 5 1
0 2 6 4
3 1 5 7
8 9 11 10
14 10 11 15
12 14 15 13
8 12 13 9
8 10 14 12
11 9 13 15
)";
IMeshBuilder mb(spec);
if (DO_OBJ) {
write_obj_mesh(mb.imesh, "cube_cube_in");
}
IMesh out = boolean_mesh(
mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, nullptr, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 20);
EXPECT_EQ(out.face_size(), 12);
if (DO_OBJ) {
write_obj_mesh(out, "cubecube_union");
}
IMeshBuilder mb2(spec);
IMesh out2 = boolean_mesh(
mb2.imesh,
BoolOpType::None,
2,
[](int t) { return t < 6 ? 0 : 1; },
false,
nullptr,
&mb2.arena);
out2.populate_vert();
EXPECT_EQ(out2.vert_size(), 22);
EXPECT_EQ(out2.face_size(), 18);
if (DO_OBJ) {
write_obj_mesh(out2, "cubecube_none");
}
}
TEST(boolean_polymesh, CubeCone)
{
const char *spec = R"(14 12
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
0 1/2 3/4
119/250 31/200 3/4
147/500 -81/200 3/4
0 0 7/4
-147/500 -81/200 3/4
-119/250 31/200 3/4
0 1 3 2
2 3 7 6
6 7 5 4
4 5 1 0
2 6 4 0
7 3 1 5
8 11 9
9 11 10
10 11 12
12 11 13
13 11 8
8 9 10 12 13)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, nullptr, &mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 14);
EXPECT_EQ(out.face_size(), 12);
if (DO_OBJ) {
write_obj_mesh(out, "cubeccone");
}
}
TEST(boolean_polymesh, CubeCubeCoplanar)
{
const char *spec = R"(16 12
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
-1/2 -1/2 1
-1/2 -1/2 2
-1/2 1/2 1
-1/2 1/2 2
1/2 -1/2 1
1/2 -1/2 2
1/2 1/2 1
1/2 1/2 2
0 1 3 2
2 3 7 6
6 7 5 4
4 5 1 0
2 6 4 0
7 3 1 5
8 9 11 10
10 11 15 14
14 15 13 12
12 13 9 8
10 14 12 8
15 11 9 13
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh,
BoolOpType::Union,
2,
[](int t) { return t < 6 ? 0 : 1; },
false,
nullptr,
&mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 16);
EXPECT_EQ(out.face_size(), 12);
if (DO_OBJ) {
write_obj_mesh(out, "cubecube_coplanar");
}
}
TEST(boolean_polymesh, TetTeTCoplanarDiff)
{
const char *spec = R"(8 8
0 1 0
7/8 -1/2 0
-7/8 -1/2 0
0 0 1
0 1 0
7/8 -1/2 0
-7/8 -1/2 0
0 0 -1
0 3 1
0 1 2
1 3 2
2 3 0
4 5 7
4 6 5
5 6 7
6 4 7
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh,
BoolOpType::Difference,
2,
[](int t) { return t < 4 ? 0 : 1; },
false,
nullptr,
&mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 4);
EXPECT_EQ(out.face_size(), 4);
if (DO_OBJ) {
write_obj_mesh(out, "tettet_coplanar_diff");
}
}
TEST(boolean_polymesh, CubeCubeStep)
{
const char *spec = R"(16 12
0 -1 0
0 -1 2
0 1 0
0 1 2
2 -1 0
2 -1 2
2 1 0
2 1 2
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
0 1 3 2
2 3 7 6
6 7 5 4
4 5 1 0
2 6 4 0
7 3 1 5
8 9 11 10
10 11 15 14
14 15 13 12
12 13 9 8
10 14 12 8
15 11 9 13
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh,
BoolOpType::Difference,
2,
[](int t) { return t < 6 ? 0 : 1; },
false,
nullptr,
&mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 12);
EXPECT_EQ(out.face_size(), 8);
if (DO_OBJ) {
write_obj_mesh(out, "cubecubestep");
}
}
TEST(boolean_polymesh, CubeCyl4)
{
const char *spec = R"(16 12
0 1 -1
0 1 1
1 0 -1
1 0 1
0 -1 -1
0 -1 1
-1 0 -1
-1 0 1
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
0 1 3 2
2 3 5 4
3 1 7 5
4 5 7 6
6 7 1 0
0 2 4 6
8 9 11 10
10 11 15 14
14 15 13 12
12 13 9 8
10 14 12 8
15 11 9 13
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh,
BoolOpType::Difference,
2,
[](int t) { return t < 6 ? 1 : 0; },
false,
nullptr,
&mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 16);
EXPECT_EQ(out.face_size(), 20);
if (DO_OBJ) {
write_obj_mesh(out, "cubecyl4");
}
}
TEST(boolean_polymesh, CubeCubesubdivDiff)
{
/* A cube intersected by a subdivided cube that intersects first cubes edges exactly. */
const char *spec = R"(26 22
2 1/3 2
2 -1/3 2
2 -1/3 0
2 1/3 0
0 -1/3 2
0 1/3 2
0 1/3 0
0 -1/3 0
1 1/3 2
1 -1/3 2
1 1/3 0
1 -1/3 0
0 -1/3 1
0 1/3 1
2 1/3 1
2 -1/3 1
1 1/3 1
1 -1/3 1
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
17 9 4 12
13 6 7 12
15 2 3 14
11 7 6 10
16 13 5 8
9 1 0 8
4 9 8 5
14 16 8 0
2 11 10 3
15 1 9 17
2 15 17 11
3 10 16 14
10 6 13 16
1 15 14 0
5 13 12 4
11 17 12 7
19 21 20 18
21 25 24 20
25 23 22 24
23 19 18 22
18 20 24 22
23 25 21 19
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh,
BoolOpType::Difference,
2,
[](int t) { return t < 16 ? 1 : 0; },
false,
nullptr,
&mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 16);
EXPECT_EQ(out.face_size(), 10);
if (DO_OBJ) {
write_obj_mesh(out, "cubecubesubdivdiff");
}
}
TEST(boolean_polymesh, CubePlane)
{
const char *spec = R"(12 7
-2 -2 0
2 -2 0
-2 2 0
2 2 0
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
0 1 3 2
4 5 7 6
6 7 11 10
10 11 9 8
8 9 5 4
6 10 8 4
11 7 5 9
)";
IMeshBuilder mb(spec);
IMesh out = boolean_mesh(
mb.imesh,
BoolOpType::Difference,
2,
[](int t) { return t >= 1 ? 0 : 1; },
false,
nullptr,
&mb.arena);
out.populate_vert();
EXPECT_EQ(out.vert_size(), 8);
EXPECT_EQ(out.face_size(), 6);
if (DO_OBJ) {
write_obj_mesh(out, "cubeplane");
}
}
} // namespace blender::meshintersect::tests

File diff suppressed because it is too large Load Diff

View File

@ -504,6 +504,18 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
}
/* Initialize solver for Boolean. */
if (!DNA_struct_elem_find(fd->filesdna, "BooleanModifierData", "enum", "solver")) {
for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) {
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Boolean) {
BooleanModifierData *bmd = (BooleanModifierData *)md;
bmd->solver = eBooleanModifierSolver_Fast;
}
}
}
}
}
/**

View File

@ -138,6 +138,8 @@ set(SRC
tools/bmesh_bevel.h
tools/bmesh_bisect_plane.c
tools/bmesh_bisect_plane.h
tools/bmesh_boolean.cc
tools/bmesh_boolean.h
tools/bmesh_decimate.h
tools/bmesh_decimate_collapse.c
tools/bmesh_decimate_dissolve.c
@ -204,6 +206,18 @@ if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
list(APPEND INC_SYS
${GMP_INCLUDE_DIRS}
)
list(APPEND LIB
${GMP_LIBRARIES}
)
endif()
blender_add_lib(bf_bmesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)

View File

@ -30,6 +30,7 @@ extern "C" {
#include "tools/bmesh_beautify.h"
#include "tools/bmesh_bevel.h"
#include "tools/bmesh_bisect_plane.h"
#include "tools/bmesh_boolean.h"
#include "tools/bmesh_decimate.h"
#include "tools/bmesh_edgenet.h"
#include "tools/bmesh_edgesplit.h"

View File

@ -0,0 +1,479 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bmesh
*
* Main functions for boolean on a #BMesh (used by the tool and modifier)
*/
#include "BLI_array.hh"
#include "BLI_math.h"
#include "BLI_math_mpq.hh"
#include "BLI_mesh_boolean.hh"
#include "BLI_mesh_intersect.hh"
#include "bmesh.h"
#include "bmesh_boolean.h"
#include "bmesh_edgesplit.h"
namespace blender {
namespace meshintersect {
#ifdef WITH_GMP
/** Make a #blender::meshintersect::Mesh from #BMesh bm.
* We are given a triangulation of it from the caller via #looptris,
* which are looptris_tot triples of loops that together tessellate
* the faces of bm.
* Return a second #IMesh in *r_triangulated that has the triangulated
* mesh, with face "orig" fields that connect the triangles back to
* the faces in the returned (polygonal) mesh.
*/
static IMesh mesh_from_bm(BMesh *bm,
struct BMLoop *(*looptris)[3],
const int looptris_tot,
IMesh *r_triangulated,
IMeshArena *arena)
{
BLI_assert(r_triangulated != nullptr);
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE);
BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE);
/* Account for triangulation and intersects. */
const int estimate_num_outv = (3 * bm->totvert) / 2;
const int estimate_num_outf = 4 * bm->totface;
arena->reserve(estimate_num_outv, estimate_num_outf);
Array<const Vert *> vert(bm->totvert);
for (int v = 0; v < bm->totvert; ++v) {
BMVert *bmv = BM_vert_at_index(bm, v);
vert[v] = arena->add_or_find_vert(mpq3(bmv->co[0], bmv->co[1], bmv->co[2]), v);
}
Array<Face *> face(bm->totface);
constexpr int estimated_max_facelen = 100;
Vector<const Vert *, estimated_max_facelen> face_vert;
Vector<int, estimated_max_facelen> face_edge_orig;
for (int f = 0; f < bm->totface; ++f) {
BMFace *bmf = BM_face_at_index(bm, f);
int flen = bmf->len;
face_vert.clear();
face_edge_orig.clear();
BMLoop *l = bmf->l_first;
for (int i = 0; i < flen; ++i) {
const Vert *v = vert[BM_elem_index_get(l->v)];
face_vert.append(v);
int e_index = BM_elem_index_get(l->e);
face_edge_orig.append(e_index);
l = l->next;
}
face[f] = arena->add_face(face_vert, f, face_edge_orig);
}
/* Now do the triangulation mesh.
* The loop_tris have accurate v and f members for the triangles,
* but their next and e pointers are not correct for the loops
* that start added-diagonal edges. */
Array<Face *> tri_face(looptris_tot);
face_vert.resize(3);
face_edge_orig.resize(3);
for (int i = 0; i < looptris_tot; ++i) {
BMFace *bmf = looptris[i][0]->f;
int f = BM_elem_index_get(bmf);
for (int j = 0; j < 3; ++j) {
BMLoop *l = looptris[i][j];
int v_index = BM_elem_index_get(l->v);
int e_index;
if (l->next->v == looptris[i][(j + 1) % 3]->v) {
e_index = BM_elem_index_get(l->e);
}
else {
e_index = NO_INDEX;
}
face_vert[j] = vert[v_index];
face_edge_orig[j] = e_index;
}
tri_face[i] = arena->add_face(face_vert, f, face_edge_orig);
}
r_triangulated->set_faces(tri_face);
return IMesh(face);
}
static bool bmvert_attached_to_wire(const BMVert *bmv)
{
/* This is not quite right. It returns true if the only edges
* Attached to \a bmv are wire edges. TODO: iterate through edges
* attached to \a bmv and check #BM_edge_is_wire. */
return BM_vert_is_wire(bmv);
}
static bool face_has_verts_in_order(BMesh *bm, BMFace *bmf, const BMVert *v1, const BMVert *v2)
{
BMIter liter;
BMLoop *l = static_cast<BMLoop *>(BM_iter_new(&liter, bm, BM_LOOPS_OF_FACE, bmf));
while (l != NULL) {
if (l->v == v1 && l->next->v == v2) {
return true;
}
l = static_cast<BMLoop *>(BM_iter_step(&liter));
}
return false;
}
/** Use the unused _BM_ELEM_TAG_ALT #BMElem.hflag to mark geometry we will keep. */
constexpr uint KEEP_FLAG = (1 << 6);
/**
* Change #BMesh bm to have the mesh match m_out. Return true if there were any changes at all.
* Vertices, faces, and edges in the current bm that are not used in the output are killed,
* except we don't kill wire edges and we don't kill hidden geometry.
* Also, the #BM_ELEM_TAG header flag is set for those #BMEdge's that come from intersections
* resulting from the intersection needed by the Boolean operation.
*/
static bool apply_mesh_output_to_bmesh(BMesh *bm, IMesh &m_out)
{
bool any_change = false;
m_out.populate_vert();
/* Initially mark all existing verts as "don't keep", except hidden verts
* and verts attached to wire edges. */
for (int v = 0; v < bm->totvert; ++v) {
BMVert *bmv = BM_vert_at_index(bm, v);
if (BM_elem_flag_test(bmv, BM_ELEM_HIDDEN) || bmvert_attached_to_wire(bmv)) {
BM_elem_flag_enable(bmv, KEEP_FLAG);
}
else {
BM_elem_flag_disable(bmv, KEEP_FLAG);
}
}
/* Reuse old or make new #BMVert's, depending on if there's an orig or not.
* For those reused, mark them "keep".
* Store needed old #BMVert's in new_bmvs first, as the table may be unusable after
* creating a new #BMVert. */
Array<BMVert *> new_bmvs(m_out.vert_size());
for (int v : m_out.vert_index_range()) {
const Vert *vertp = m_out.vert(v);
int orig = vertp->orig;
if (orig != NO_INDEX) {
BLI_assert(orig >= 0 && orig < bm->totvert);
BMVert *bmv = BM_vert_at_index(bm, orig);
new_bmvs[v] = bmv;
BM_elem_flag_enable(bmv, KEEP_FLAG);
}
else {
new_bmvs[v] = NULL;
}
}
for (int v : m_out.vert_index_range()) {
const Vert *vertp = m_out.vert(v);
if (new_bmvs[v] == NULL) {
float co[3];
const double3 &d_co = vertp->co;
for (int i = 0; i < 3; ++i) {
co[i] = static_cast<float>(d_co[i]);
}
BMVert *bmv = BM_vert_create(bm, co, NULL, BM_CREATE_NOP);
new_bmvs[v] = bmv;
BM_elem_flag_enable(bmv, KEEP_FLAG);
any_change = true;
}
}
/* Initially mark all existing faces as "don't keep", except hidden faces.
* Also, save current #BMFace pointers as creating faces will disturb the table. */
Array<BMFace *> old_bmfs(bm->totface);
for (int f = 0; f < bm->totface; ++f) {
BMFace *bmf = BM_face_at_index(bm, f);
old_bmfs[f] = bmf;
if (BM_elem_flag_test(bmf, BM_ELEM_HIDDEN)) {
BM_elem_flag_enable(bmf, KEEP_FLAG);
}
else {
BM_elem_flag_disable(bmf, KEEP_FLAG);
}
}
/* Save the original #BMEdge's so we can use them as examples. */
Array<BMEdge *> old_edges(bm->totedge);
std::copy(bm->etable, bm->etable + bm->totedge, old_edges.begin());
/* Reuse or make new #BMFace's, as the faces are identical to old ones or not.
* If reusing, mark them as "keep". First find the maximum face length
* so we can declare some arrays outside of the face-creating loop. */
int maxflen = 0;
for (const Face *f : m_out.faces()) {
maxflen = max_ii(maxflen, f->size());
}
Array<BMVert *> face_bmverts(maxflen);
Array<BMEdge *> face_bmedges(maxflen);
for (const Face *f : m_out.faces()) {
const Face &face = *f;
int flen = face.size();
for (int i = 0; i < flen; ++i) {
const Vert *v = face[i];
int v_index = m_out.lookup_vert(v);
BLI_assert(v_index < new_bmvs.size());
face_bmverts[i] = new_bmvs[v_index];
}
BMFace *bmf = BM_face_exists(face_bmverts.data(), flen);
/* #BM_face_exists checks if the face exists with the vertices in either order.
* We can only reuse the face if the orientations are the same. */
if (bmf != NULL && face_has_verts_in_order(bm, bmf, face_bmverts[0], face_bmverts[1])) {
BM_elem_flag_enable(bmf, KEEP_FLAG);
}
else {
int orig = face.orig;
BMFace *orig_face;
/* There should always be an orig face, but just being extra careful here. */
if (orig != NO_INDEX) {
orig_face = old_bmfs[orig];
}
else {
orig_face = NULL;
}
/* Make or find #BMEdge's. */
for (int i = 0; i < flen; ++i) {
BMVert *bmv1 = face_bmverts[i];
BMVert *bmv2 = face_bmverts[(i + 1) % flen];
BMEdge *bme = BM_edge_exists(bmv1, bmv2);
if (bme == NULL) {
BMEdge *orig_edge = NULL;
if (face.edge_orig[i] != NO_INDEX) {
orig_edge = old_edges[face.edge_orig[i]];
}
bme = BM_edge_create(bm, bmv1, bmv2, orig_edge, BM_CREATE_NOP);
if (orig_edge != NULL) {
BM_elem_select_copy(bm, bme, orig_edge);
}
}
face_bmedges[i] = bme;
if (face.is_intersect[i]) {
BM_elem_flag_enable(bme, BM_ELEM_TAG);
}
else {
BM_elem_flag_disable(bme, BM_ELEM_TAG);
}
}
BMFace *bmf = BM_face_create(
bm, face_bmverts.data(), face_bmedges.data(), flen, orig_face, BM_CREATE_NOP);
if (orig_face != NULL) {
BM_elem_select_copy(bm, bmf, orig_face);
}
BM_elem_flag_enable(bmf, KEEP_FLAG);
/* Now do interpolation of loop data (e.g., UV's) using the example face. */
if (orig_face != NULL) {
BMIter liter;
BMLoop *l = static_cast<BMLoop *>(BM_iter_new(&liter, bm, BM_LOOPS_OF_FACE, bmf));
while (l != NULL) {
BM_loop_interp_from_face(bm, l, orig_face, true, true);
l = static_cast<BMLoop *>(BM_iter_step(&liter));
}
}
any_change = true;
}
}
/* Now kill the unused faces and verts, and clear flags for kept ones. */
/* #BM_ITER_MESH_MUTABLE macro needs type casts for C++, so expand here.
* TODO(howard): make some nice C++ iterators for #BMesh. */
BMIter iter;
BMFace *bmf = static_cast<BMFace *>(BM_iter_new(&iter, bm, BM_FACES_OF_MESH, NULL));
while (bmf != NULL) {
# ifdef DEBUG
iter.count = BM_iter_mesh_count(BM_FACES_OF_MESH, bm);
# endif
BMFace *bmf_next = static_cast<BMFace *>(BM_iter_step(&iter));
if (BM_elem_flag_test(bmf, KEEP_FLAG)) {
BM_elem_flag_disable(bmf, KEEP_FLAG);
}
else {
BM_face_kill_loose(bm, bmf);
# if 0
BM_face_kill(bm, bmf);
# endif
any_change = true;
}
bmf = bmf_next;
}
BMVert *bmv = static_cast<BMVert *>(BM_iter_new(&iter, bm, BM_VERTS_OF_MESH, NULL));
while (bmv != NULL) {
# ifdef DEBUG
iter.count = BM_iter_mesh_count(BM_VERTS_OF_MESH, bm);
# endif
BMVert *bmv_next = static_cast<BMVert *>(BM_iter_step(&iter));
if (BM_elem_flag_test(bmv, KEEP_FLAG)) {
BM_elem_flag_disable(bmv, KEEP_FLAG);
}
else {
BM_vert_kill(bm, bmv);
any_change = true;
}
bmv = bmv_next;
}
return any_change;
}
static bool bmesh_boolean(BMesh *bm,
struct BMLoop *(*looptris)[3],
const int looptris_tot,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const bool use_self,
const bool use_separate_all,
const BoolOpType boolean_mode)
{
IMeshArena arena;
IMesh m_triangulated;
IMesh m_in = mesh_from_bm(bm, looptris, looptris_tot, &m_triangulated, &arena);
std::function<int(int)> shape_fn;
int nshapes;
if (use_self) {
/* Unary boolean operation. Want every face where test_fn doesn't return -1. */
nshapes = 1;
shape_fn = [bm, test_fn, user_data](int f) {
BMFace *bmf = BM_face_at_index(bm, f);
if (test_fn(bmf, user_data) != -1) {
return 0;
}
return -1;
};
}
else {
nshapes = 2;
shape_fn = [bm, test_fn, user_data](int f) {
BMFace *bmf = BM_face_at_index(bm, f);
int test_val = test_fn(bmf, user_data);
if (test_val == 0) {
return 0;
}
if (test_val == 1) {
return 1;
}
return -1;
};
}
IMesh m_out = boolean_mesh(
m_in, boolean_mode, nshapes, shape_fn, use_self, &m_triangulated, &arena);
bool any_change = apply_mesh_output_to_bmesh(bm, m_out);
if (use_separate_all) {
/* We are supposed to separate all faces that are incident on intersection edges. */
BM_mesh_edgesplit(bm, false, true, false);
}
return any_change;
}
#endif // WITH_GMP
} // namespace meshintersect
} // namespace blender
extern "C" {
/**
* Perform the boolean operation specified by boolean_mode on the mesh bm.
* The inputs to the boolean operation are either one sub-mesh (if use_self is true),
* or two sub-meshes. The sub-meshes are specified by providing a test_fn which takes
* a face and the supplied user_data and says with 'side' of the boolean operation
* that face is for: 0 for the first side (side A), 1 for the second side (side B),
* and -1 if the face is to be ignored completely in the boolean operation.
*
* If use_self is true, all operations do the same: the sub-mesh is self-intersected
* and all pieces inside that result are removed.
* Otherwise, the operations can be one of #BMESH_ISECT_BOOLEAN_ISECT, #BMESH_ISECT_BOOLEAN_UNION,
* or #BMESH_ISECT_BOOLEAN_DIFFERENCE.
*
* (The actual library function called to do the boolean is internally capable of handling
* n-ary operands, so maybe in the future we can expose that functionality to users.)
*/
#ifdef WITH_GMP
bool BM_mesh_boolean(BMesh *bm,
struct BMLoop *(*looptris)[3],
const int looptris_tot,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const bool use_self,
const int boolean_mode)
{
return blender::meshintersect::bmesh_boolean(
bm,
looptris,
looptris_tot,
test_fn,
user_data,
use_self,
false,
static_cast<blender::meshintersect::BoolOpType>(boolean_mode));
}
/**
* Perform a Knife Intersection operation on the mesh bm.
* There are either one or two operands, the same as described above for BM_mesh_boolean().
* If use_separate_all is true, each edge that is created from the intersection should
* be used to separate all its incident faces. TODO: implement that.
* TODO: need to ensure that "selected/non-selected" flag of original faces gets propagated
* to the intersection result faces.
*/
bool BM_mesh_boolean_knife(BMesh *bm,
struct BMLoop *(*looptris)[3],
const int looptris_tot,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const bool use_self,
const bool use_separate_all)
{
return blender::meshintersect::bmesh_boolean(bm,
looptris,
looptris_tot,
test_fn,
user_data,
use_self,
use_separate_all,
blender::meshintersect::BoolOpType::None);
}
#else
bool BM_mesh_boolean(BMesh *UNUSED(bm),
struct BMLoop *(*looptris)[3],
const int UNUSED(looptris_tot),
int (*test_fn)(BMFace *, void *),
void *UNUSED(user_data),
const bool UNUSED(use_self),
const int UNUSED(boolean_mode))
{
UNUSED_VARS(looptris, test_fn);
return false;
}
/**
* Perform a Knife Intersection operation on the mesh bm.
* There are either one or two operands, the same as described above for #BM_mesh_boolean().
* If use_separate_all is true, each edge that is created from the intersection should
* be used to separate all its incident faces. TODO: implement that.
* TODO: need to ensure that "selected/non-selected" flag of original faces gets propagated
* to the intersection result faces.
*/
bool BM_mesh_boolean_knife(BMesh *UNUSED(bm),
struct BMLoop *(*looptris)[3],
const int UNUSED(looptris_tot),
int (*test_fn)(BMFace *, void *),
void *UNUSED(user_data),
const bool UNUSED(use_self),
const bool UNUSED(use_separate_all))
{
UNUSED_VARS(looptris, test_fn);
return false;
}
#endif
} /* extern "C" */

View File

@ -0,0 +1,45 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bmesh
*/
#ifdef __cplusplus
extern "C" {
#endif
bool BM_mesh_boolean(BMesh *bm,
struct BMLoop *(*looptris)[3],
const int looptris_tot,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const bool use_self,
const int boolean_mode);
bool BM_mesh_boolean_knife(BMesh *bm,
struct BMLoop *(*looptris)[3],
const int looptris_tot,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const bool use_self,
const bool use_separate_all);
#ifdef __cplusplus
}
#endif

View File

@ -117,7 +117,7 @@ static void bm_decim_build_quadrics(BMesh *bm, Quadric *vquadrics)
cross_v3_v3v3(edge_plane, edge_vector, f->no);
copy_v3db_v3fl(edge_plane_db, edge_plane);
if (normalize_v3_d(edge_plane_db) > (double)FLT_EPSILON) {
if (normalize_v3_db(edge_plane_db) > (double)FLT_EPSILON) {
Quadric q;
float center[3];

View File

@ -20,7 +20,15 @@
* \ingroup bmesh
*/
#ifdef __cplusplus
extern "C" {
#endif
void BM_mesh_edgesplit(BMesh *bm,
const bool use_verts,
const bool tag_only,
const bool copy_select);
#ifdef __cplusplus
}
#endif

View File

@ -93,6 +93,10 @@ if(WITH_BULLET)
add_definitions(-DWITH_BULLET)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
add_definitions(${GL_DEFINITIONS})
blender_add_lib(bf_editor_mesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -39,6 +39,9 @@
#include "WM_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "ED_mesh.h"
#include "ED_screen.h"
@ -46,6 +49,7 @@
#include "mesh_intern.h" /* own include */
#include "tools/bmesh_boolean.h"
#include "tools/bmesh_intersect.h"
#include "tools/bmesh_separate.h"
@ -134,6 +138,11 @@ enum {
ISECT_SEPARATE_NONE = 2,
};
enum {
ISECT_SOLVER_FAST = 0,
ISECT_SOLVER_EXACT = 1,
};
static int edbm_intersect_exec(bContext *C, wmOperator *op)
{
const int mode = RNA_enum_get(op->ptr, "mode");
@ -142,6 +151,11 @@ static int edbm_intersect_exec(bContext *C, wmOperator *op)
bool use_separate_cut = false;
const int separate_mode = RNA_enum_get(op->ptr, "separate_mode");
const float eps = RNA_float_get(op->ptr, "threshold");
#ifdef WITH_GMP
const bool exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
#else
const bool exact = false;
#endif
bool use_self;
bool has_isect;
@ -186,19 +200,25 @@ static int edbm_intersect_exec(bContext *C, wmOperator *op)
continue;
}
has_isect = BM_mesh_intersect(em->bm,
em->looptris,
em->tottri,
test_fn,
NULL,
use_self,
use_separate_all,
true,
true,
true,
true,
-1,
eps);
if (exact) {
has_isect = BM_mesh_boolean_knife(
em->bm, em->looptris, em->tottri, test_fn, NULL, use_self, use_separate_all);
}
else {
has_isect = BM_mesh_intersect(em->bm,
em->looptris,
em->tottri,
test_fn,
NULL,
use_self,
use_separate_all,
true,
true,
true,
true,
-1,
eps);
}
if (use_separate_cut) {
/* detach selected/un-selected faces */
@ -220,6 +240,38 @@ static int edbm_intersect_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static void edbm_intersect_ui(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout;
uiLayout *row;
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
#ifdef WITH_GMP
bool use_exact = RNA_enum_get(&ptr, "solver") == ISECT_SOLVER_EXACT;
#else
bool use_exact = false;
#endif
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
row = uiLayoutRow(layout, false);
uiItemR(row, &ptr, "mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
uiItemS(layout);
row = uiLayoutRow(layout, false);
uiItemR(row, &ptr, "separate_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
uiItemS(layout);
#ifdef WITH_GMP
row = uiLayoutRow(layout, false);
uiItemR(row, &ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
uiItemS(layout);
#endif
if (!use_exact) {
uiItemR(layout, &ptr, "threshold", 0, NULL, ICON_NONE);
}
}
void MESH_OT_intersect(struct wmOperatorType *ot)
{
static const EnumPropertyItem isect_mode_items[] = {
@ -243,6 +295,12 @@ void MESH_OT_intersect(struct wmOperatorType *ot)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem isect_intersect_solver_items[] = {
{ISECT_SOLVER_FAST, "FAST", 0, "Fast", "Faster Solver, some limitations"},
{ISECT_SOLVER_EXACT, "EXACT", 0, "Exact", "Exact Solver, slower, handles more cases"},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Intersect (Knife)";
ot->description = "Cut an intersection into faces";
@ -251,6 +309,7 @@ void MESH_OT_intersect(struct wmOperatorType *ot)
/* api callbacks */
ot->exec = edbm_intersect_exec;
ot->poll = ED_operator_editmesh;
ot->ui = edbm_intersect_ui;
/* props */
RNA_def_enum(ot->srna, "mode", isect_mode_items, ISECT_SEL_UNSEL, "Source", "");
@ -258,6 +317,14 @@ void MESH_OT_intersect(struct wmOperatorType *ot)
ot->srna, "separate_mode", isect_separate_items, ISECT_SEPARATE_CUT, "Separate Mode", "");
RNA_def_float_distance(
ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge threshold", "", 0.0, 0.001);
#ifdef WITH_GMP
RNA_def_enum(ot->srna,
"solver",
isect_intersect_solver_items,
ISECT_SOLVER_EXACT,
"Solver",
"Which Intersect solver to use");
#endif
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -280,6 +347,12 @@ static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op)
{
const int boolean_operation = RNA_enum_get(op->ptr, "operation");
bool use_swap = RNA_boolean_get(op->ptr, "use_swap");
bool use_self = RNA_boolean_get(op->ptr, "use_self");
#ifdef WITH_GMP
bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
#else
bool use_exact = false;
#endif
const float eps = RNA_float_get(op->ptr, "threshold");
int (*test_fn)(BMFace *, void *);
bool has_isect;
@ -298,19 +371,25 @@ static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op)
continue;
}
has_isect = BM_mesh_intersect(em->bm,
em->looptris,
em->tottri,
test_fn,
NULL,
false,
false,
true,
true,
false,
true,
boolean_operation,
eps);
if (use_exact) {
has_isect = BM_mesh_boolean(
em->bm, em->looptris, em->tottri, test_fn, NULL, use_self, boolean_operation);
}
else {
has_isect = BM_mesh_intersect(em->bm,
em->looptris,
em->tottri,
test_fn,
NULL,
false,
false,
true,
true,
false,
true,
boolean_operation,
eps);
}
edbm_intersect_select(em, obedit->data, has_isect);
@ -326,6 +405,38 @@ static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static void edbm_intersect_boolean_ui(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout;
uiLayout *row;
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
#ifdef WITH_GMP
bool use_exact = RNA_enum_get(&ptr, "solver") == ISECT_SOLVER_EXACT;
#else
bool use_exact = false;
#endif
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
row = uiLayoutRow(layout, false);
uiItemR(row, &ptr, "operation", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
uiItemS(layout);
#ifdef WITH_GMP
row = uiLayoutRow(layout, false);
uiItemR(row, &ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
uiItemS(layout);
#endif
uiItemR(layout, &ptr, "use_swap", 0, NULL, ICON_NONE);
uiItemR(layout, &ptr, "use_self", 0, NULL, ICON_NONE);
if (!use_exact) {
uiItemR(layout, &ptr, "threshold", 0, NULL, ICON_NONE);
}
}
void MESH_OT_intersect_boolean(struct wmOperatorType *ot)
{
static const EnumPropertyItem isect_boolean_operation_items[] = {
@ -334,6 +445,11 @@ void MESH_OT_intersect_boolean(struct wmOperatorType *ot)
{BMESH_ISECT_BOOLEAN_DIFFERENCE, "DIFFERENCE", 0, "Difference", ""},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem isect_boolean_solver_items[] = {
{ISECT_SOLVER_FAST, "FAST", 0, "Fast", "Faster Solver, some limitations"},
{ISECT_SOLVER_EXACT, "EXACT", 0, "Exact", "Exact Solver, slower, handles more cases"},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Intersect (Boolean)";
@ -343,21 +459,31 @@ void MESH_OT_intersect_boolean(struct wmOperatorType *ot)
/* api callbacks */
ot->exec = edbm_intersect_boolean_exec;
ot->poll = ED_operator_editmesh;
ot->ui = edbm_intersect_boolean_ui;
/* props */
RNA_def_enum(ot->srna,
"operation",
isect_boolean_operation_items,
BMESH_ISECT_BOOLEAN_DIFFERENCE,
"Boolean",
"");
"Boolean operation",
"Which boolean operation to apply");
RNA_def_boolean(ot->srna,
"use_swap",
false,
"Swap",
"Use with difference intersection to swap which side is kept");
RNA_def_boolean(ot->srna, "use_self", false, "Self", "Do self-union or self-intersection");
RNA_def_float_distance(
ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge threshold", "", 0.0, 0.001);
#ifdef WITH_GMP
RNA_def_enum(ot->srna,
"solver",
isect_boolean_solver_items,
ISECT_SOLVER_EXACT,
"Solver",
"Which Boolean solver to use");
#endif
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@ -862,7 +862,8 @@ typedef struct BooleanModifierData {
struct Object *object;
char operation;
char _pad[2];
char solver;
char _pad[1];
char bm_flag;
float double_threshold;
} BooleanModifierData;
@ -873,7 +874,12 @@ typedef enum {
eBooleanModifierOp_Difference = 2,
} BooleanModifierOp;
/* bm_flag (only used when G_DEBUG) */
typedef enum {
eBooleanModifierSolver_Fast = 0,
eBooleanModifierSolver_Exact = 1,
} BooleanModifierSolver;
/* bm_flag only used when G_DEBUG. */
enum {
eBooleanModifierBMeshFlag_BMesh_Separate = (1 << 0),
eBooleanModifierBMeshFlag_BMesh_NoDissolve = (1 << 1),

View File

@ -352,6 +352,10 @@ if(WITH_XR_OPENXR)
add_definitions(-DWITH_XR_OPENXR)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
# Build makesrna executable
blender_include_dirs(
.

View File

@ -2819,6 +2819,16 @@ static void rna_def_modifier_boolean(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem prop_solver_items[] = {
{eBooleanModifierSolver_Fast,
"FAST",
0,
"Fast",
"Simple solver for the best performance, without support for overlapping geometry"},
{eBooleanModifierSolver_Exact, "EXACT", 0, "Exact", "Advanced solver for the best result"},
{0, NULL, 0, NULL, NULL},
};
srna = RNA_def_struct(brna, "BooleanModifier", "Modifier");
RNA_def_struct_ui_text(srna, "Boolean Modifier", "Boolean operations modifier");
RNA_def_struct_sdna(srna, "BooleanModifierData");
@ -2847,6 +2857,12 @@ static void rna_def_modifier_boolean(BlenderRNA *brna)
prop, "Overlap Threshold", "Threshold for checking overlapping geometry");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "solver", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_solver_items);
RNA_def_property_enum_default(prop, eBooleanModifierSolver_Exact);
RNA_def_property_ui_text(prop, "Solver", "Method for calculating booleans");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
/* BMesh debugging options, only used when G_DEBUG is set */
/* BMesh intersection options */

View File

@ -171,6 +171,10 @@ if(WITH_CYCLES)
add_definitions(-DWITH_CYCLES)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
# So we can have special tricks in modifier system.
add_definitions(${GL_DEFINITIONS})

View File

@ -62,6 +62,7 @@
#include "bmesh.h"
#include "bmesh_tools.h"
#include "tools/bmesh_boolean.h"
#include "tools/bmesh_intersect.h"
#ifdef DEBUG_TIME
@ -75,6 +76,11 @@ static void initData(ModifierData *md)
bmd->double_threshold = 1e-6f;
bmd->operation = eBooleanModifierOp_Difference;
#ifdef WITH_GMP
bmd->solver = eBooleanModifierSolver_Exact;
#else
bmd->solver = eBooleanModifierSolver_Fast;
#endif
}
static bool isDisabled(const struct Scene *UNUSED(scene),
@ -315,19 +321,30 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
0;
}
BM_mesh_intersect(bm,
looptris,
tottri,
bm_face_isect_pair,
NULL,
false,
use_separate,
use_dissolve,
use_island_connect,
false,
false,
bmd->operation,
bmd->double_threshold);
#ifdef WITH_GMP
bool use_exact = bmd->solver == eBooleanModifierSolver_Exact;
#else
bool use_exact = false;
#endif
if (use_exact) {
BM_mesh_boolean(bm, looptris, tottri, bm_face_isect_pair, NULL, false, bmd->operation);
}
else {
BM_mesh_intersect(bm,
looptris,
tottri,
bm_face_isect_pair,
NULL,
false,
use_separate,
use_dissolve,
use_island_connect,
false,
false,
bmd->operation,
bmd->double_threshold);
}
MEM_freeN(looptris);
}
@ -374,7 +391,18 @@ static void panel_draw(const bContext *C, Panel *panel)
uiLayoutSetPropSep(layout, true);
uiItemR(layout, &ptr, "object", 0, NULL, ICON_NONE);
uiItemR(layout, &ptr, "double_threshold", 0, NULL, ICON_NONE);
#ifdef WITH_GMP
bool use_exact = RNA_enum_get(&ptr, "solver") == eBooleanModifierSolver_Exact;
#else
bool use_exact = false;
#endif
if (!use_exact) {
uiItemR(layout, &ptr, "double_threshold", 0, NULL, ICON_NONE);
}
#ifdef WITH_GMP
uiItemR(layout, &ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
#endif
if (G.debug) {
uiLayout *col = uiLayoutColumn(layout, true);
@ -394,7 +422,7 @@ ModifierTypeInfo modifierType_Boolean = {
/* structName */ "BooleanModifierData",
/* structSize */ sizeof(BooleanModifierData),
/* type */ eModifierTypeType_Nonconstructive,
/* flags */ eModifierTypeFlag_AcceptsMesh,
/* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode,
/* copyData */ BKE_modifier_copydata_generic,

View File

@ -64,4 +64,8 @@ if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
blender_add_lib(bf_python_bmesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -1639,7 +1639,6 @@ static PyObject *M_Geometry_delaunay_2d_cdt(PyObject *UNUSED(self), PyObject *ar
in.faces_start_table = in_faces_start_table;
in.faces_len_table = in_faces_len_table;
in.epsilon = epsilon;
in.skip_input_modify = false;
res = BLI_delaunay_2d_cdt_calc(&in, output_type);

View File

@ -115,6 +115,10 @@ if(WITH_XR_OPENXR)
add_definitions(-DWITH_XR_OPENXR)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
# Setup the exe sources and buildinfo
set(SRC
creator.c

View File

@ -154,13 +154,13 @@ add_blender_test(
--run-all-tests
)
add_blender_test(
bmesh_boolean
${TEST_SRC_DIR}/modeling/bool_regression.blend
--python ${TEST_PYTHON_DIR}/boolean_operator.py
--
--run-all-tests
)
# add_blender_test(
# bmesh_boolean
# ${TEST_SRC_DIR}/modeling/bool_regression.blend
# --python ${TEST_PYTHON_DIR}/boolean_operator.py
# --
# --run-all-tests
#)
add_blender_test(
bmesh_split_faces
@ -176,13 +176,13 @@ add_blender_test(
--python-text run_tests.py
)
add_blender_test(
modifiers
${TEST_SRC_DIR}/modeling/modifiers.blend
--python ${TEST_PYTHON_DIR}/modifiers.py
--
--run-all-tests
)
#add_blender_test(
# modifiers
# ${TEST_SRC_DIR}/modeling/modifiers.blend
# --python ${TEST_PYTHON_DIR}/modifiers.py
# --
# --run-all-tests
#)
add_blender_test(
physics_cloth