14constexpr float kEpsilon = 1e-5f;
17 float x = 0.f,
y = 0.f,
z = 0.f;
23float dot(Vec3
a, Vec3
b) {
return a.x *
b.x +
a.y *
b.y +
a.z *
b.z; }
25 return {
a.y *
b.z -
a.z *
b.y,
a.z *
b.x -
a.x *
b.z,
a.x *
b.y -
a.y *
b.x};
35 float u = 0.f,
v = 0.f;
37 Vertex interpolated(
const Vertex& other,
float t)
const {
40 result.normal = normalized(
normal + (other.normal -
normal) *
t);
41 result.u =
u + (other.u -
u) *
t;
42 result.v =
v + (other.v -
v) *
t;
57 void recalculatePlane() {
64 for (
auto& vertex :
vertices) vertex.flip();
70enum PolygonType { Coplanar = 0,
Front = 1,
Back = 2, Spanning = 3 };
72void splitPolygon(
const Vec3&
normal,
float w,
const Polygon& polygon, std::vector<Polygon>& coplanarFront,
73 std::vector<Polygon>& coplanarBack, std::vector<Polygon>& front, std::vector<Polygon>& back) {
74 int polygonType = Coplanar;
75 std::vector<int> types;
76 types.reserve(polygon.vertices.size());
77 for (
const auto& vertex : polygon.
vertices) {
81 types.push_back(
type);
83 if (polygonType == Coplanar) {
84 (
dot(
normal, polygon.planeNormal) > 0.f ? coplanarFront : coplanarBack).push_back(polygon);
87 if (polygonType == Front) {
88 front.push_back(polygon);
91 if (polygonType == Back) {
92 back.push_back(polygon);
98 frontPolygon.vertices.clear();
99 backPolygon.vertices.clear();
100 for (std::size_t i = 0; i < polygon.vertices.size(); ++i) {
101 const std::size_t j = (i + 1u) % polygon.vertices.size();
102 const int ti = types[i];
103 const int tj = types[j];
104 const Vertex& vi = polygon.vertices[i];
105 const Vertex& vj = polygon.vertices[j];
106 if (ti != Back) frontPolygon.vertices.push_back(vi);
107 if (ti != Front) backPolygon.vertices.push_back(vi);
108 if ((ti | tj) == Spanning) {
109 const Vec3 edge = vj.position - vi.position;
111 const float t = std::clamp((
w -
dot(
normal, vi.position)) / denominator, 0.f, 1.f);
113 frontPolygon.vertices.push_back(
split);
114 backPolygon.vertices.push_back(
split);
117 if (frontPolygon.vertices.size() >= 3u) {
118 frontPolygon.recalculatePlane();
119 front.push_back(std::move(frontPolygon));
121 if (backPolygon.vertices.size() >= 3u) {
122 backPolygon.recalculatePlane();
123 back.push_back(std::move(backPolygon));
130 explicit BspNode(
const std::vector<Polygon>& polygons) { build(polygons); }
133 for (
auto& polygon : polygons_) polygon.flip();
134 planeNormal_ = planeNormal_ * -1.f;
136 if (front_) front_->invert();
137 if (back_) back_->invert();
138 std::swap(front_, back_);
141 std::vector<Polygon> clipPolygons(
const std::vector<Polygon>& polygons)
const {
142 if (!hasPlane_)
return polygons;
143 std::vector<Polygon> front;
144 std::vector<Polygon> back;
145 for (
const auto& polygon : polygons)
146 splitPolygon(planeNormal_, planeW_, polygon, front, back, front, back);
147 if (front_) front = front_->clipPolygons(front);
149 back = back_->clipPolygons(back);
152 front.insert(front.end(), std::make_move_iterator(back.begin()), std::make_move_iterator(back.end()));
156 void clipTo(
const BspNode& other) {
157 polygons_ = other.clipPolygons(polygons_);
158 if (front_) front_->clipTo(other);
159 if (back_) back_->clipTo(other);
162 std::vector<Polygon> allPolygons()
const {
163 std::vector<Polygon> result = polygons_;
165 auto values = front_->allPolygons();
166 result.insert(result.end(), std::make_move_iterator(
values.begin()), std::make_move_iterator(
values.end()));
169 auto values = back_->allPolygons();
170 result.insert(result.end(), std::make_move_iterator(
values.begin()), std::make_move_iterator(
values.end()));
175 void build(
const std::vector<Polygon>& polygons) {
176 if (polygons.empty())
return;
178 planeNormal_ = polygons.front().planeNormal;
179 planeW_ = polygons.front().planeW;
182 std::vector<Polygon> front;
183 std::vector<Polygon> back;
184 for (
const auto& polygon : polygons)
185 splitPolygon(planeNormal_, planeW_, polygon, polygons_, polygons_, front, back);
186 if (!front.empty()) {
187 if (!front_) front_ = std::make_unique<BspNode>();
188 front_->build(front);
191 if (!back_) back_ = std::make_unique<BspNode>();
197 bool hasPlane_ =
false;
200 std::vector<Polygon> polygons_;
201 std::unique_ptr<BspNode> front_;
202 std::unique_ptr<BspNode> back_;
205Result<std::vector<Polygon>> polygonsFromMesh(
const MeshBuild&
mesh,
bool fromRight) {
206 if (
mesh.empty() ||
mesh.getIndexCount() % 3 != 0)
209 {},
"procgen.meshBoolean"));
210 if (
mesh.getIndexCount() / 3 > 50000)
213 {},
"procgen.meshBoolean"));
214 std::vector<Polygon> result;
215 result.reserve(
static_cast<std::size_t
>(
mesh.getIndexCount() / 3));
220 for (
int corner = 0; corner < 3; ++corner) {
222 if (index < 0 || index >=
mesh.getVertexCount())
225 {},
"procgen.meshBoolean"));
229 if (!std::isfinite(vertex.position.x) || !std::isfinite(vertex.position.y) ||
230 !std::isfinite(vertex.position.z))
233 "mesh.positions", {},
"procgen.meshBoolean"));
234 polygon.vertices.push_back(vertex);
236 polygon.recalculatePlane();
237 if (
dot(polygon.planeNormal, polygon.planeNormal) <= kEpsilon * kEpsilon)
240 {},
"procgen.meshBoolean"));
241 result.push_back(std::move(polygon));
243 return Result<std::vector<Polygon>>::success(std::move(result));
246std::vector<Polygon> booleanPolygons(std::vector<Polygon>
left, std::vector<Polygon>
right,
256 a.build(
b.allPolygons());
257 return a.allPolygons();
266 a.build(
b.allPolygons());
268 return a.allPolygons();
275 a.build(
b.allPolygons());
277 return a.allPolygons();
280MeshBuild meshFromPolygons(
const std::vector<Polygon>& polygons,
const MeshBuild&
left,
const MeshBuild&
right,
283 std::vector<int> leftGroups;
284 std::vector<int> rightGroups;
285 for (
int i = 0; i <
left.getGroupCount(); ++i) leftGroups.push_back(
output.setActiveGroup(
left.getGroupName(i)));
286 for (
int i = 0; i <
right.getGroupCount(); ++i)
287 rightGroups.push_back(
output.setActiveGroup(
"cutter." +
right.getGroupName(i)));
288 const int leftDefault =
output.setActiveGroup(
"boolean.left");
289 const int rightDefault =
output.setActiveGroup(
"boolean.cutter");
290 for (
const auto& polygon : polygons) {
291 if (polygon.vertices.size() < 3u)
continue;
292 const auto&
groups = polygon.fromRight ? rightGroups : leftGroups;
293 const int fallback = polygon.fromRight ? rightDefault : leftDefault;
294 const int group = polygon.group >= 0 &&
static_cast<std::size_t
>(polygon.group) <
groups.size()
295 ?
groups[
static_cast<std::size_t
>(polygon.group)]
298 const auto base =
static_cast<std::uint32_t
>(
output.getVertexCount());
299 for (
const auto& vertex : polygon.
vertices)
302 for (std::uint32_t i = 1; i + 1 < polygon.vertices.size(); ++i)
303 output.addTriangle(base, base + i, base + i + 1u);
305 output.setMeta(
"generator",
"mesh.boolean");
316 "operation", {},
"procgen.meshBoolean"));
317 auto leftPolygons = polygonsFromMesh(
left,
false);
319 auto rightPolygons = polygonsFromMesh(
right,
true);
321 auto polygons = booleanPolygons(std::move(leftPolygons).takeValue(), std::move(rightPolygons).takeValue(),
operation);
ActionParameterOperation operation
building::EdgeCurveGroup group
std::map< std::string, Var > values
std::array< float, 3 > position
std::vector< Point > vertices
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Vec3 operator-(Vec3 lhs, Vec3 rhs)
Operator -.
Vec3 operator+(Vec3 lhs, Vec3 rhs)
Operator +.
Vec3 operator*(Vec3 value, float scale)
Operator *.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
std::vector< Vec2 > Polygon
Closed polygon ring, stored CCW, without repeating the first point.
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Result< MeshBuild > meshBooleanResult(const MeshBuild &left, const MeshBuild &right, std::string_view operation)
Evaluate a closed-triangle solid boolean using a BSP polygon split.