载入中...
搜索中...
未找到
MeshBoolean.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <memory>
7#include <string>
8#include <utility>
9#include <vector>
10
11namespace eve::procgen {
12namespace {
13
14constexpr float kEpsilon = 1e-5f;
15
16struct Vec3 {
17 float x = 0.f, y = 0.f, z = 0.f;
18};
19
20Vec3 operator+(Vec3 a, Vec3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
21Vec3 operator-(Vec3 a, Vec3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
22Vec3 operator*(Vec3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
23float dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
24Vec3 cross(Vec3 a, Vec3 b) {
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};
26}
27Vec3 normalized(Vec3 value) {
28 const float length = std::sqrt(dot(value, value));
29 return length > kEpsilon ? value * (1.f / length) : Vec3{};
30}
31
32struct Vertex {
34 Vec3 normal;
35 float u = 0.f, v = 0.f;
36
37 Vertex interpolated(const Vertex& other, float t) const {
38 Vertex result;
39 result.position = position + (other.position - position) * t;
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;
43 return result;
44 }
45 void flip() { normal = normal * -1.f; }
46};
47
48struct Plane;
49
50struct Polygon {
51 std::vector<Vertex> vertices;
53 float planeW = 0.f;
54 int group = -1;
55 bool fromRight = false;
56
57 void recalculatePlane() {
58 planeNormal = normalized(cross(vertices[1].position - vertices[0].position,
61 }
62 void flip() {
63 std::reverse(vertices.begin(), vertices.end());
64 for (auto& vertex : vertices) vertex.flip();
65 planeNormal = planeNormal * -1.f;
66 planeW = -planeW;
67 }
68};
69
70enum PolygonType { Coplanar = 0, Front = 1, Back = 2, Spanning = 3 };
71
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) {
78 const float distance = dot(normal, vertex.position) - w;
79 const int type = distance < -kEpsilon ? Back : (distance > kEpsilon ? Front : Coplanar);
80 polygonType |= type;
81 types.push_back(type);
82 }
83 if (polygonType == Coplanar) {
84 (dot(normal, polygon.planeNormal) > 0.f ? coplanarFront : coplanarBack).push_back(polygon);
85 return;
86 }
87 if (polygonType == Front) {
88 front.push_back(polygon);
89 return;
90 }
91 if (polygonType == Back) {
92 back.push_back(polygon);
93 return;
94 }
95
96 Polygon frontPolygon = polygon;
97 Polygon backPolygon = 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;
110 const float denominator = dot(normal, edge);
111 const float t = std::clamp((w - dot(normal, vi.position)) / denominator, 0.f, 1.f);
112 const Vertex split = vi.interpolated(vj, t);
113 frontPolygon.vertices.push_back(split);
114 backPolygon.vertices.push_back(split);
115 }
116 }
117 if (frontPolygon.vertices.size() >= 3u) {
118 frontPolygon.recalculatePlane();
119 front.push_back(std::move(frontPolygon));
120 }
121 if (backPolygon.vertices.size() >= 3u) {
122 backPolygon.recalculatePlane();
123 back.push_back(std::move(backPolygon));
124 }
125}
126
127class BspNode {
128public:
129 BspNode() = default;
130 explicit BspNode(const std::vector<Polygon>& polygons) { build(polygons); }
131
132 void invert() {
133 for (auto& polygon : polygons_) polygon.flip();
134 planeNormal_ = planeNormal_ * -1.f;
135 planeW_ = -planeW_;
136 if (front_) front_->invert();
137 if (back_) back_->invert();
138 std::swap(front_, back_);
139 }
140
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);
148 if (back_)
149 back = back_->clipPolygons(back);
150 else
151 back.clear();
152 front.insert(front.end(), std::make_move_iterator(back.begin()), std::make_move_iterator(back.end()));
153 return front;
154 }
155
156 void clipTo(const BspNode& other) {
157 polygons_ = other.clipPolygons(polygons_);
158 if (front_) front_->clipTo(other);
159 if (back_) back_->clipTo(other);
160 }
161
162 std::vector<Polygon> allPolygons() const {
163 std::vector<Polygon> result = polygons_;
164 if (front_) {
165 auto values = front_->allPolygons();
166 result.insert(result.end(), std::make_move_iterator(values.begin()), std::make_move_iterator(values.end()));
167 }
168 if (back_) {
169 auto values = back_->allPolygons();
170 result.insert(result.end(), std::make_move_iterator(values.begin()), std::make_move_iterator(values.end()));
171 }
172 return result;
173 }
174
175 void build(const std::vector<Polygon>& polygons) {
176 if (polygons.empty()) return;
177 if (!hasPlane_) {
178 planeNormal_ = polygons.front().planeNormal;
179 planeW_ = polygons.front().planeW;
180 hasPlane_ = true;
181 }
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);
189 }
190 if (!back.empty()) {
191 if (!back_) back_ = std::make_unique<BspNode>();
192 back_->build(back);
193 }
194 }
195
196private:
197 bool hasPlane_ = false;
198 Vec3 planeNormal_;
199 float planeW_ = 0.f;
200 std::vector<Polygon> polygons_;
201 std::unique_ptr<BspNode> front_;
202 std::unique_ptr<BspNode> back_;
203};
204
205Result<std::vector<Polygon>> polygonsFromMesh(const MeshBuild& mesh, bool fromRight) {
206 if (mesh.empty() || mesh.getIndexCount() % 3 != 0)
207 return Result<std::vector<Polygon>>::failure(Diagnostic::error(
208 DiagnosticCode::InvalidArgument, "mesh.boolean requires non-empty triangle meshes", "mesh",
209 {}, "procgen.meshBoolean"));
210 if (mesh.getIndexCount() / 3 > 50000)
211 return Result<std::vector<Polygon>>::failure(Diagnostic::error(
212 DiagnosticCode::InvalidArgument, "mesh.boolean input exceeds the 50000-triangle budget", "mesh",
213 {}, "procgen.meshBoolean"));
214 std::vector<Polygon> result;
215 result.reserve(static_cast<std::size_t>(mesh.getIndexCount() / 3));
216 for (int triangle = 0; triangle < mesh.getIndexCount() / 3; ++triangle) {
217 Polygon polygon;
218 polygon.group = mesh.getTriangleGroup(triangle);
219 polygon.fromRight = fromRight;
220 for (int corner = 0; corner < 3; ++corner) {
221 const int index = mesh.getIndex(triangle * 3 + corner);
222 if (index < 0 || index >= mesh.getVertexCount())
223 return Result<std::vector<Polygon>>::failure(Diagnostic::error(
224 DiagnosticCode::InvalidArgument, "mesh.boolean found an out-of-range index", "mesh.indices",
225 {}, "procgen.meshBoolean"));
226 Vertex vertex{{mesh.getPositionX(index), mesh.getPositionY(index), mesh.getPositionZ(index)},
227 {mesh.getNormalX(index), mesh.getNormalY(index), mesh.getNormalZ(index)},
228 mesh.getUvU(index), mesh.getUvV(index)};
229 if (!std::isfinite(vertex.position.x) || !std::isfinite(vertex.position.y) ||
230 !std::isfinite(vertex.position.z))
231 return Result<std::vector<Polygon>>::failure(Diagnostic::error(
232 DiagnosticCode::InvalidArgument, "mesh.boolean requires finite vertex positions",
233 "mesh.positions", {}, "procgen.meshBoolean"));
234 polygon.vertices.push_back(vertex);
235 }
236 polygon.recalculatePlane();
237 if (dot(polygon.planeNormal, polygon.planeNormal) <= kEpsilon * kEpsilon)
238 return Result<std::vector<Polygon>>::failure(Diagnostic::error(
239 DiagnosticCode::InvalidArgument, "mesh.boolean rejects degenerate triangles", "mesh.indices",
240 {}, "procgen.meshBoolean"));
241 result.push_back(std::move(polygon));
242 }
243 return Result<std::vector<Polygon>>::success(std::move(result));
244}
245
246std::vector<Polygon> booleanPolygons(std::vector<Polygon> left, std::vector<Polygon> right,
247 std::string_view operation) {
248 BspNode a(left);
249 BspNode b(right);
250 if (operation == "union") {
251 a.clipTo(b);
252 b.clipTo(a);
253 b.invert();
254 b.clipTo(a);
255 b.invert();
256 a.build(b.allPolygons());
257 return a.allPolygons();
258 }
259 if (operation == "difference") {
260 a.invert();
261 a.clipTo(b);
262 b.clipTo(a);
263 b.invert();
264 b.clipTo(a);
265 b.invert();
266 a.build(b.allPolygons());
267 a.invert();
268 return a.allPolygons();
269 }
270 a.invert();
271 b.clipTo(a);
272 b.invert();
273 a.clipTo(b);
274 b.clipTo(a);
275 a.build(b.allPolygons());
276 a.invert();
277 return a.allPolygons();
278}
279
280MeshBuild meshFromPolygons(const std::vector<Polygon>& polygons, const MeshBuild& left, const MeshBuild& right,
281 std::string_view operation) {
282 MeshBuild output;
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)]
296 : fallback;
297 output.setActiveGroup(output.getGroupName(group));
298 const auto base = static_cast<std::uint32_t>(output.getVertexCount());
299 for (const auto& vertex : polygon.vertices)
300 output.addVertex(vertex.position.x, vertex.position.y, vertex.position.z, vertex.normal.x,
301 vertex.normal.y, vertex.normal.z, vertex.u, vertex.v);
302 for (std::uint32_t i = 1; i + 1 < polygon.vertices.size(); ++i)
303 output.addTriangle(base, base + i, base + i + 1u);
304 }
305 output.setMeta("generator", "mesh.boolean");
306 output.setMeta("boolean.operation", std::string(operation));
307 return output;
308}
309
310} // namespace
311
313 if (operation != "union" && operation != "difference" && operation != "intersection")
315 DiagnosticCode::InvalidArgument, "mesh.boolean operation must be union, difference, or intersection",
316 "operation", {}, "procgen.meshBoolean"));
317 auto leftPolygons = polygonsFromMesh(left, false);
318 if (!leftPolygons.ok()) return Result<MeshBuild>::failure(leftPolygons.status());
319 auto rightPolygons = polygonsFromMesh(right, true);
320 if (!rightPolygons.ok()) return Result<MeshBuild>::failure(rightPolygons.status());
321 auto polygons = booleanPolygons(std::move(leftPolygons).takeValue(), std::move(rightPolygons).takeValue(), operation);
322 return Result<MeshBuild>::success(meshFromPolygons(polygons, left, right, operation));
323}
324
325} // namespace eve::procgen
ActionParameterOperation operation
double value
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
const std::string & s
building::EdgeCurveGroup group
float length
Definition CaveMesh.cpp:94
bool split
Definition CaveMesh.cpp:123
std::map< std::string, Var > values
int triangle
float u
Definition Grass.cpp:233
float v
HexVec3 left
HexVec3 right
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float planeW
Vec3 planeNormal
bool fromRight
float distance
Texture * normal
uint32_t groups
Definition OnnxGpgpu.cpp:39
std::vector< Point > vertices
float t
Mesh * mesh
const RoadEdge * edge
uint32_t index
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
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.
Definition UrbanTypes.h:38
std::vector< Vec2 > Polygon
Closed polygon ring, stored CCW, without repeating the first point.
Definition UrbanTypes.h:55
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
Result< MeshBuild > meshBooleanResult(const MeshBuild &left, const MeshBuild &right, std::string_view operation)
Evaluate a closed-triangle solid boolean using a BSP polygon split.