├── Cutting.h ├── DataStructures.h ├── LICENSE ├── README.md ├── demo.mov └── main.cpp /Cutting.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Yuting Wang. All rights reserved. 3 | // 4 | #ifndef Cutting_h 5 | #define Cutting_h 6 | 7 | #include "DataStructures.h" 8 | 9 | template 10 | class Cutter3D { 11 | typedef std::array I1; 12 | typedef std::array I2; 13 | typedef std::array I3; 14 | typedef std::array I4; 15 | typedef std::array I5; 16 | typedef std::array T2; 17 | typedef std::array T3; 18 | typedef std::array T4; 19 | typedef map Intersections; 20 | typedef map> TetBoundary2TetIds; 21 | 22 | struct CutElement { 23 | int parentElementIndex; 24 | array subElements; // in the same order as the tet nodes 25 | 26 | CutElement(int i, bool fill = true): parentElementIndex(i) { 27 | subElements.fill(fill); 28 | } 29 | 30 | int numPieces() const { 31 | return (int)subElements[0] + (int)subElements[1] + (int)subElements[2] + (int)subElements[3]; 32 | } 33 | }; 34 | 35 | static bool computeIntersection(const array& nodes1, const array& nodes2, array& w1, array& w2) { 36 | T v1 = volume(nodes1[0], nodes2[0], nodes2[1], nodes2[2]); 37 | T v2 = volume(nodes1[1], nodes2[0], nodes2[1], nodes2[2]); 38 | T v3 = volume(nodes1[0], nodes1[1], nodes2[0], nodes2[1]); 39 | T v4 = volume(nodes1[0], nodes1[1], nodes2[1], nodes2[2]); 40 | T v5 = volume(nodes1[0], nodes1[1], nodes2[2], nodes2[0]); 41 | if (v1*v2<0 && (v3>0)==(v4>0) && (v4>0)==(v5>0)) { 42 | w1[0] = fabs(v2) / (fabs(v1) + fabs(v2)); 43 | w1[1] = 1 - w1[0]; 44 | T v = fabs(v3) + fabs(v4) + fabs(v5); 45 | w2[0] = fabs(v4) / v; 46 | w2[1] = fabs(v5) / v; 47 | w2[2] = 1 - w2[0] - w2[1]; 48 | //cout << w1[0] << endl; 49 | return true; 50 | } else { 51 | return false; 52 | } 53 | } 54 | 55 | static bool computeIntersection(const array& nodes1, const array& nodes2, array& w) { 56 | array w1; 57 | return computeIntersection(nodes1, nodes2, w, w1); 58 | } 59 | 60 | static bool computeIntersection(const array& nodes1, const array& nodes2, array& w) { 61 | array w1; 62 | return computeIntersection(nodes2, nodes1, w1, w); 63 | } 64 | 65 | static bool computeIntersection(const array& nodes1, const array& nodes2, array& w) { 66 | T v1 = volume(nodes1[0], nodes1[1], nodes1[2], nodes2[0]); 67 | T v2 = volume(nodes1[0], nodes1[2], nodes1[3], nodes2[0]); 68 | T v3 = volume(nodes1[0], nodes1[3], nodes1[1], nodes2[0]); 69 | T v4 = volume(nodes2[0], nodes1[1], nodes1[2], nodes1[3]); 70 | if (v1 == 0 || v2 == 0 || v3 == 0 || v4 == 0) { 71 | cout << "point tet degenerate case" << endl; 72 | } 73 | // cout << v1 << ", " << v2 << ", " << v3 << ", " << v4 << endl; 74 | if ((v1>0) == (v2>0) && (v2>0) == (v3>0) && (v3>0) == (v4>0)) { 75 | T v = fabs(v1) + fabs(v2) + fabs(v3) + fabs(v4); 76 | w[0] = fabs(v4) / v; 77 | w[1] = fabs(v2) / v; 78 | w[2] = fabs(v3) / v; 79 | w[3] = 1 - w[0] - w[1] - w[2]; 80 | return true; 81 | } else { 82 | return false; 83 | } 84 | return false; 85 | } 86 | 87 | template 88 | static void computeIntersections(const vector& nodes1, const vector& nodes2, const vector>& e1, const vector>& e2, const BoxHierarchy& b1, const BoxHierarchy& b2, map& intersections) { 89 | vector> intersectingBoxes; // intersecting boxes 90 | b1.intersect(b2, intersectingBoxes); 91 | for (size_t i = 0; i < intersectingBoxes.size(); ++i) { 92 | //cout << "e1 " << i << endl; 93 | //print(e1[i]); 94 | for (auto j : intersectingBoxes[i]) { 95 | //cout << j << ", " << endl; 96 | //print(e2[j]); 97 | auto tetNodes = elementNodes(nodes1, e1[i]); 98 | auto triNodes = elementNodes(nodes2, e2[j]); 99 | array w; 100 | if (computeIntersection(tetNodes, triNodes, w)) { 101 | intersections[toI4(e1[i])] = toI4(w,0); 102 | } 103 | } 104 | } 105 | } 106 | 107 | static Intersections computeIntersections(const TetMesh& tetMesh, const TriMesh& triMesh, TetBoundary2TetIds& tetBoundary2TetIds) { 108 | map intersections; 109 | 110 | // build box hierarchies for tetMesh 111 | set tetMeshFaces; 112 | set tetMeshEdges; 113 | for (int i = 0; i < tetMesh.mesh_.size(); ++i) { 114 | auto tet = tetMesh.mesh_[i]; 115 | sort(tet.begin(), tet.end()); 116 | tetBoundary2TetIds[tet].push_back(i); 117 | auto faces = tetFaces(tet); 118 | for (auto& face: faces) { 119 | sort(face.begin(), face.end()); 120 | tetBoundary2TetIds[toI4(face)].push_back(i); 121 | tetMeshFaces.insert(face); 122 | } 123 | auto edges = tetEdges(tet); 124 | for (auto& edge: edges) { 125 | sort(edge.begin(), edge.end()); 126 | tetBoundary2TetIds[toI4(edge)].push_back(i); 127 | tetMeshEdges.insert(edge); 128 | } 129 | } 130 | vector tetMeshNodeVec; 131 | for (int i = 0; i < tetMesh.nodes_.size(); ++i) { 132 | tetMeshNodeVec.push_back(I1{i}); 133 | } 134 | vector tetMeshFaceVec(tetMeshFaces.begin(), tetMeshFaces.end()); 135 | vector tetMeshEdgeVec(tetMeshEdges.begin(), tetMeshEdges.end()); 136 | cout << "buliding tet mesh hierarchy" << endl; 137 | auto tetMeshHierarchy = buildBoxHierarchy(tetMesh.nodes_, tetMesh.mesh_); 138 | auto tetMeshFaceHierarchy = buildBoxHierarchy(tetMesh.nodes_, tetMeshFaceVec); 139 | auto tetMeshEdgeHierarchy = buildBoxHierarchy(tetMesh.nodes_, tetMeshEdgeVec); 140 | auto tetMeshNodeHierarchy = buildBoxHierarchy(tetMesh.nodes_, tetMeshNodeVec); 141 | cout << "tet mesh hierarchy built" << endl; 142 | 143 | // box hierarchy for triMesh 144 | set triMeshEdges; 145 | for (const auto& tri: triMesh.mesh_) { 146 | auto edges = faceEdges(tri); 147 | for (auto& edge: edges) { 148 | sort(edge.begin(), edge.end()); 149 | triMeshEdges.insert(edge); 150 | } 151 | } 152 | vector triMeshEdgeVec(triMeshEdges.begin(), triMeshEdges.end()); 153 | vector triMeshNodeVec; 154 | for (int i = 0; i < triMesh.nodes_.size(); ++i) { 155 | triMeshNodeVec.push_back(I1{i}); 156 | } 157 | // cout << "trimeshhierarchy\n"; 158 | // print(triMesh.nodes_); 159 | // print(triMesh.mesh_); 160 | auto triMeshHierarchy = buildBoxHierarchy(triMesh.nodes_, triMesh.mesh_); 161 | // cout << "trimeshhierarchy\n"; 162 | auto triMeshEdgeHierarchy = buildBoxHierarchy(triMesh.nodes_, triMeshEdgeVec); 163 | auto triMeshNodeHierarchy = buildBoxHierarchy(triMesh.nodes_, triMeshNodeVec); 164 | cout << "tri mesh hierarchy built" << endl; 165 | 166 | // compute intersections 167 | // v-v 168 | // v-e 169 | // v-f 170 | // e-v 171 | // e-e 172 | // e-f 173 | computeIntersections<2,3>(tetMesh.nodes_, triMesh.nodes_, tetMeshEdgeVec, triMesh.mesh_, tetMeshEdgeHierarchy, triMeshHierarchy, intersections); 174 | // f-v 175 | // f-e 176 | computeIntersections<3,2>(tetMesh.nodes_, triMesh.nodes_, tetMeshFaceVec, triMeshEdgeVec, tetMeshFaceHierarchy, triMeshEdgeHierarchy, intersections); 177 | // t-v 178 | computeIntersections<4,1>(tetMesh.nodes_, triMesh.nodes_, tetMesh.mesh_, triMeshNodeVec, tetMeshHierarchy, triMeshNodeHierarchy, intersections); 179 | 180 | return intersections; 181 | } 182 | 183 | static vector split(const TetMesh& tetMesh, const Intersections& intersections, TetBoundary2TetIds& tetBoundary2TetIds, set& cutTets) { 184 | cutTets.clear(); 185 | for (const auto& t: tetBoundary2TetIds) { 186 | if (intersections.count(t.first)) { 187 | for (auto i: t.second) { 188 | cutTets.insert(i); 189 | } 190 | } 191 | } 192 | cout << cutTets.size() << " tets cut\n"; 193 | vector v; 194 | for (int i = 0; i < tetMesh.mesh_.size(); ++i) { 195 | if (cutTets.count(i)) { 196 | array added; 197 | added.fill(false); 198 | auto tet = tetMesh.mesh_[i]; 199 | for (int j = 0; j < 4; ++j) { 200 | if (!added[j]) { 201 | // find all connected pieces 202 | CutElement ce(i, false); 203 | stack s; 204 | s.push(j); 205 | while(s.size()) { 206 | auto top = s.top(); 207 | ce.subElements[top] = true; 208 | added[top] = true; 209 | s.pop(); 210 | // add all the connected pieces that are not added yet 211 | for (int k = 0; k < 4; ++k) { 212 | if (!added[k]) { 213 | if (!intersections.count(toI4(sorted(I2{tet[top],tet[k]})))) { 214 | s.push(k); 215 | } 216 | } 217 | } 218 | } 219 | v.push_back(ce); 220 | } 221 | } 222 | } 223 | } 224 | return v; 225 | } 226 | 227 | void static newTet(int parentId, const I4& tet, const TetMesh& tetMesh, vector& newNodes, vector& newMesh, map& nodeMapping, UnionFind& uf) { 228 | I4 newTet; 229 | //cout << "parent id " << parentId << endl; 230 | for (int i = 0; i < 4; ++i) { // for each node 231 | int newId = uf.find(tet[i]); 232 | //cout << tet[i] << ", " << newId << endl; 233 | const auto& it = nodeMapping.find(newId); 234 | if (it != nodeMapping.end()) { 235 | newTet[i] = it->second; 236 | } else { 237 | newTet[i] = newNodes.size(); 238 | nodeMapping[newId] = newNodes.size(); 239 | newNodes.push_back(tetMesh.nodes_[tetMesh.mesh_[parentId][i]]); 240 | } 241 | } 242 | 243 | newMesh.push_back(newTet); 244 | } 245 | 246 | static void merge(const vector& cutElements, const TetMesh& tetMesh, vector& newNodes, vector& newMesh, const Intersections& intersections) { 247 | newNodes.clear(); 248 | newMesh.clear(); 249 | UnionFind uf(tetMesh.nodes_.size() + 4 * cutElements.size()); 250 | map faceNode2NewNode; // key = {face,materialNode,node} 251 | set cutTets; 252 | int total = tetMesh.nodes_.size(); 253 | for (const auto& ce: cutElements) { // need to do face-face merging even for tets that are touched by the cut but not split, so that if a neighbor splits they are all connected to it. 254 | cutTets.insert(ce.parentElementIndex); 255 | const auto& tet = tetMesh.mesh_[ce.parentElementIndex]; 256 | for (int i = 0; i < 4; ++i) { // for each face 257 | auto face = tetFace(tet, i); 258 | sort(face.begin(), face.end()); 259 | I5 key; 260 | for (int j = 0; j < 3; ++j) { 261 | key[j] = face[j]; 262 | } 263 | for (int j = 0; j < 3; ++j) { // for each node check for material 264 | int fij = FaceIndexes[i][j]; 265 | if (ce.subElements[fij]) { 266 | key[3] = tet[fij]; 267 | uf.merge(total+fij, key[3]); 268 | for (int k = 0; k < 3; ++k) { // for each node, merge 269 | int fik = FaceIndexes[i][k]; 270 | key[4] = tet[fik]; 271 | int newId = total+fik; 272 | //print(key); 273 | const auto& it = faceNode2NewNode.find(key); 274 | if (it != faceNode2NewNode.end()) { 275 | //cout << "merging " << it->second << ", " << newId << endl; 276 | uf.merge(it->second, newId); 277 | } else { 278 | faceNode2NewNode[key] = newId; 279 | } 280 | } 281 | } 282 | } 283 | } 284 | total += 4; 285 | } 286 | total = tetMesh.nodes_.size(); 287 | map nodeMapping; 288 | for (const auto& ce: cutElements) { 289 | newTet(ce.parentElementIndex, I4{total, total+1, total+2, total+3}, tetMesh, newNodes, newMesh, nodeMapping, uf); 290 | total += 4; 291 | } 292 | for (int i = 0; i < tetMesh.mesh_.size(); ++i) { 293 | if (!cutTets.count(i)) { 294 | newTet(i, tetMesh.mesh_[i], tetMesh, newNodes, newMesh, nodeMapping, uf); 295 | } 296 | } 297 | 298 | // cout << "merged mesh \n"; 299 | // print(newNodes); 300 | // print(newMesh); 301 | } 302 | 303 | static TetMesh subdivide(const vector& cutElements, const TetMesh& tetMesh, vector& newNodes, vector& newMesh, Intersections& intersections) { 304 | // add a new node inside the tet, connect with cuts on each face to subdivide the tet 305 | map newNodeMapping; 306 | for (int i = 0; i < cutElements.size(); ++i) { 307 | const auto& ce = cutElements[i]; 308 | const auto& originalTet = tetMesh.mesh_[ce.parentElementIndex]; 309 | const auto sortedOriginalTet = sorted(originalTet); 310 | const auto& tet = newMesh[i]; 311 | 312 | // get all edge cuts and add them as new nodes 313 | const auto originalEdges = tetEdges(originalTet); 314 | const auto edges = tetEdges(tet); 315 | int cutEdges = 0; 316 | T4 averageEdgeWeight{0,0,0,0}; 317 | map originalNodeId2Weight; 318 | for (int k = 0; k < originalEdges.size(); ++k) { 319 | auto sortedOriginalEdge = toI4(sorted(originalEdges[k])); 320 | auto sortedEdge = toI4(sorted(edges[k])); 321 | const auto& it = intersections.find(sortedOriginalEdge); 322 | if (it != intersections.end()) { 323 | ++cutEdges; 324 | for (int j = 0; j < 2; ++j) { 325 | originalNodeId2Weight[sortedOriginalEdge[j]] += it->second[j]; 326 | } 327 | const auto& idIt = newNodeMapping.find(sortedEdge); 328 | if (idIt == newNodeMapping.end()) { 329 | newNodeMapping[sortedEdge] = newNodes.size(); 330 | newNodes.push_back(elementCenter(tetMesh.nodes_, sortedOriginalEdge, it->second)); 331 | // cout << "edge node "; 332 | // print(elementCenter(tetMesh.nodes_, sortedOriginalEdge, it->second)); 333 | } 334 | } 335 | } 336 | for (int j = 0; j < 4; ++j) { 337 | averageEdgeWeight[j] = originalNodeId2Weight[sortedOriginalTet[j]]; 338 | } 339 | //cout << "cutEdges " << cutEdges << endl; 340 | 341 | // face cuts 342 | const auto originalFaces = tetFaces(originalTet); 343 | const auto faces = tetFaces(tet); 344 | for (int k = 0; k < faces.size(); ++k) { 345 | auto sortedOriginalFace = toI4(sorted(originalFaces[k])); 346 | auto sortedFace = toI4(sorted(faces[k])); 347 | const auto& it = intersections.find(sortedOriginalFace); 348 | if (it != intersections.end()) { // face center already computed 349 | const auto& idIt = newNodeMapping.find(sortedFace); 350 | if (idIt == newNodeMapping.end()) { 351 | newNodeMapping[sortedFace] = newNodes.size(); 352 | newNodes.push_back(elementCenter(tetMesh.nodes_, sortedOriginalFace, it->second)); 353 | } 354 | // cout << "face center "; 355 | // print(elementCenter(tetMesh.nodes_, sortedOriginalFace, it->second)); 356 | } else { // use average of edge cuts if not 357 | int numEdges = 0; 358 | T4 faceWeights{0,0,0,0}; 359 | map node2weight; 360 | for (int j = 0; j < 3; ++j) { 361 | auto sortedOriginalEdge = toI4(sorted(array{sortedOriginalFace[j], sortedOriginalFace[(j+1)%3]})); 362 | const auto& edgeIt = intersections.find(sortedOriginalEdge); 363 | if (edgeIt != intersections.end()) { 364 | ++numEdges; 365 | for (int e = 0; e < 2; ++e) { 366 | node2weight[sortedOriginalEdge[e]] += edgeIt->second[e]; 367 | } 368 | } 369 | } 370 | if (numEdges > 1) { // otherwise don't add new face center 371 | newNodeMapping[sortedFace] = newNodes.size(); 372 | for (int j = 0; j < 3; ++j) { 373 | faceWeights[j] = node2weight[sortedOriginalFace[j]] / numEdges; 374 | } 375 | // cout << "face weight "; 376 | // print(faceWeights); 377 | // cout << "face center "; 378 | // print(elementCenter(tetMesh.nodes_, sortedOriginalFace, faceWeights)); 379 | newNodes.push_back(elementCenter(tetMesh.nodes_, sortedOriginalFace, faceWeights)); 380 | intersections[sortedOriginalFace] = faceWeights; 381 | } 382 | } 383 | } 384 | 385 | // tet center 386 | int tetCenterId = newNodes.size(); 387 | const auto& tetCenterIt = intersections.find(sortedOriginalTet); 388 | if (tetCenterIt != intersections.end()) { 389 | newNodes.push_back(elementCenter(tetMesh.nodes_, sortedOriginalTet, tetCenterIt->second)); 390 | // cout << "tet center "; 391 | // print(elementCenter(tetMesh.nodes_, sortedOriginalTet, tetCenterIt->second)); 392 | } else { // if doesn't exist, use average of edge cuts or the center 393 | if (ce.numPieces() == 4) { 394 | averageEdgeWeight.fill(0.25); 395 | } else { 396 | averageEdgeWeight = divide(averageEdgeWeight, cutEdges); 397 | // print(averageEdgeWeight); 398 | } 399 | newNodes.push_back(elementCenter(tetMesh.nodes_, sortedOriginalTet, averageEdgeWeight)); 400 | // cout << "tet center "; 401 | // print(elementCenter(tetMesh.nodes_, sortedOriginalTet, averageEdgeWeight)); 402 | intersections[sortedOriginalTet] = averageEdgeWeight; 403 | } 404 | 405 | // add elements that are created by the new nodes added above 406 | vector newTets; 407 | for (int f = 0; f < faces.size(); ++f) { 408 | const auto& face = faces[f]; 409 | const auto sortedFace = toI4(sorted(face)); 410 | const auto& newFaceCenterIt = newNodeMapping.find(sortedFace); 411 | if (newFaceCenterIt != newNodeMapping.end()) { 412 | for (int j = 0; j < 3; ++j) { 413 | auto sortedEdge = toI4(sorted(array{face[j], face[(j+1)%3]})); 414 | const auto& newEdgeCenterIt = newNodeMapping.find(sortedEdge); 415 | if (newEdgeCenterIt != newNodeMapping.end()) { 416 | if (ce.subElements[FaceIndexes[f][j]]) { 417 | newTets.push_back(I4{tetCenterId, newFaceCenterIt->second, face[j], newEdgeCenterIt->second}); 418 | } 419 | if (ce.subElements[FaceIndexes[f][(j+1)%3]]) { 420 | newTets.push_back(I4{tetCenterId, newFaceCenterIt->second, newEdgeCenterIt->second, face[(j+1)%3]}); 421 | } 422 | } else if (ce.subElements[FaceIndexes[f][j]]) { 423 | newTets.push_back(I4{tetCenterId, newFaceCenterIt->second, face[j], face[(j+1)%3]}); 424 | } 425 | } 426 | } else if (ce.subElements[FaceIndexes[f][0]]) { // no face intersection, might have 0 or 1 edge cut 427 | bool isSplit = false; 428 | for (int j = 0; j < 3; ++j) { 429 | auto sortedEdge = toI4(sorted(array{face[j], face[(j+1)%3]})); 430 | const auto& newEdgeCenterIt = newNodeMapping.find(sortedEdge); 431 | if (newEdgeCenterIt != newNodeMapping.end()) { 432 | newTets.push_back(I4{tetCenterId, face[(j+2)%3], face[j], newEdgeCenterIt->second}); 433 | newTets.push_back(I4{tetCenterId, face[(j+2)%3], newEdgeCenterIt->second, face[(j+1)%3]}); 434 | isSplit = true; 435 | break; 436 | } 437 | } 438 | if (!isSplit) { 439 | newTets.push_back(I4{tetCenterId, face[0], face[1], face[2]}); 440 | } 441 | } 442 | } 443 | newMesh[i] = newTets[0]; 444 | for (int j = 1; j < newTets.size(); ++j) { 445 | newMesh.push_back(newTets[j]); 446 | } 447 | } 448 | return TetMesh(move(newNodes), move(newMesh)); 449 | } 450 | 451 | public: 452 | static TetMesh run(const TetMesh& tetMesh, const TriMesh& triMesh) { 453 | TetBoundary2TetIds tetBoundary2TetIds; 454 | auto intersections = computeIntersections(tetMesh, triMesh, tetBoundary2TetIds); 455 | cout << "finished computing " << intersections.size() << " intersections\n"; 456 | // for (auto& a: intersections) { 457 | // print(a.first); 458 | // print(a.second); 459 | // } 460 | set cutTets; 461 | vector cutElements = split(tetMesh, intersections, tetBoundary2TetIds, cutTets); 462 | // for (auto& ce: cutElements) { 463 | // cout << ce.parentElementIndex << endl; 464 | // print(ce.subElements); 465 | // } 466 | vector newNodes; 467 | vector newMesh; 468 | merge(cutElements, tetMesh, newNodes, newMesh, intersections); 469 | cout << "finished split-merge\n"; 470 | return subdivide(cutElements, tetMesh, newNodes, newMesh, intersections); 471 | } 472 | }; 473 | 474 | #endif /* Cutting_h */ 475 | -------------------------------------------------------------------------------- /DataStructures.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Yuting Wang. All rights reserved. 3 | // 4 | 5 | #ifndef DataStructures_h 6 | #define DataStructures_h 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifdef WIN32 14 | #include 15 | #else 16 | #include 17 | #endif 18 | #include 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | 24 | template 25 | void print(const array& v) { 26 | cout << "{"; 27 | for (int i = 0; i < d; ++i) { 28 | cout << v[i] << ", "; 29 | } 30 | cout << "}\n"; 31 | } 32 | 33 | template 34 | void print(const vector>& v) { 35 | cout << "{\n"; 36 | for (const auto& a: v) { 37 | print(a); 38 | } 39 | cout << "}\n"; 40 | } 41 | 42 | template 43 | void print(const array, d2>& v) { 44 | cout << "{\n"; 45 | for (const auto& a: v) { 46 | print(a); 47 | } 48 | cout << "}\n"; 49 | } 50 | 51 | template 52 | T sorted(const T& v) { 53 | auto sv = v; 54 | sort(sv.begin(), sv.end()); 55 | return sv; 56 | } 57 | 58 | class UnionFind { 59 | int *id, cnt, *sz; 60 | public: 61 | // Create an empty union find data structure with N isolated sets. 62 | UnionFind(int N) { 63 | cnt = N; 64 | id = new int[N]; 65 | sz = new int[N]; 66 | for(int i=0; i 114 | array toI4(const array& Id, T fill = -1) { 115 | array a; 116 | a.fill(fill); 117 | for (int i = 0; i < min(d, 4); ++i) { 118 | a[i] = Id[i]; 119 | } 120 | return a; 121 | } 122 | template 123 | array add(const array& a1, const array& a2) { 124 | array s; 125 | for (int j = 0; j < d; ++j) { 126 | s[j] = a1[j] + a2[j]; 127 | } 128 | return s; 129 | } 130 | 131 | template 132 | array substract(const array& a1, const array& a2) { 133 | array s; 134 | for (int j = 0; j < d; ++j) { 135 | s[j] = a1[j] - a2[j]; 136 | } 137 | return s; 138 | } 139 | 140 | template 141 | array divide(const array& a, T t) { 142 | array s; 143 | for (int j = 0; j < d; ++j) { 144 | s[j] = a[j] / t; 145 | } 146 | return s; 147 | } 148 | 149 | template 150 | array center(const array, n>& nodes, const array weights) { 151 | array c; 152 | for (size_t i = 0; i < d; ++i) { 153 | c[i] = 0; 154 | for (size_t j = 0; j < n; ++j) { 155 | c[i] += nodes[j][i] * weights[j]; 156 | } 157 | } 158 | return c; 159 | } 160 | 161 | template 162 | array elementCenter(const vector>& nodes, const array& element, const array& weights) { 163 | array c; 164 | c.fill(0); 165 | for (size_t i = 0; i < 4; ++i) { 166 | if (element[i] >= 0) { 167 | const auto& node = nodes[element[i]]; 168 | auto w = weights[i]; 169 | for (int j = 0; j < d; ++j) { 170 | c[j] += (node[j] * w); 171 | } 172 | } else { 173 | break; 174 | } 175 | } 176 | return c; 177 | } 178 | 179 | template 180 | T dot(const array& a1, const array& a2) { 181 | T s = 0; 182 | for (int j = 0; j < d; ++j) { 183 | s += a1[j] * a2[j]; 184 | } 185 | return s; 186 | } 187 | 188 | template 189 | T cross(const array& a1, const array& a2) { 190 | return a1[0] * a2[1] - a1[1] * a2[0]; 191 | } 192 | 193 | template 194 | array cross(const array& a1, const array& a2) { 195 | array s; 196 | for (int j = 0; j < 3; ++j) { 197 | s[j] = a1[(j+1)%3] * a2[(j+2)%3] - a2[(j+1)%3] * a1[(j+2)%3]; 198 | } 199 | return s; 200 | } 201 | 202 | template 203 | T volume(const array& node1, const array& node2, const array& node3, const array& node4) { 204 | return dot(cross(substract(node2,node1), substract(node3,node1)), substract(node4,node1)); 205 | } 206 | 207 | template 208 | T norm(const array& a1) { 209 | return sqrt(dot(a1, a1)); 210 | } 211 | 212 | template 213 | T pointEdgeWeight(const array& e1, const array& e2, const array& p) { 214 | array v1 = substract(p, e1); 215 | array v2 = substract(e2, e1); 216 | return dot(v1, v2) / dot(v2, v2); 217 | } 218 | 219 | template 220 | bool pointOnEdge(const array& e1, const array& e2, const array& p, T& w) { 221 | array v1 = substract(p, e1); 222 | array v2 = substract(e1, e2); 223 | if (cross(v1, v2) == 0) { 224 | w = pointEdgeWeight(e1, e2, p); 225 | return true; 226 | } 227 | return false; 228 | } 229 | 230 | template 231 | bool edgeEdgeIntersect(const array& p1, const array& p2, const array& q1, const array& q2, T& w) { 232 | array e1 = substract(p2, p1); 233 | T a1 = cross(substract(q1, p1), e1); 234 | T a2 = cross(substract(q2, p1), e1); 235 | if ((a1 < 0 && a2 > 0) || (a1 > 0 && a2 < 0)) { 236 | array e2 = substract(q2, q1); 237 | T a3 = cross(substract(p1, q1), e2); 238 | T a4 = cross(substract(p2, q1), e2); 239 | if ((a3 < 0 && a4 > 0) || (a3 > 0 && a4 < 0)) { 240 | w = a3 / (a3 - a4); 241 | return true; 242 | } 243 | } 244 | return false; 245 | } 246 | 247 | template 248 | bool pointInTriangle(const array, 3>& triangle, const array& point, array& w) { 249 | array areas; 250 | for (int i = 0; i < 3; ++i) { 251 | areas[(i+2)%3] = cross(substract(point, triangle[i]), substract(triangle[(i+1)%3], triangle[i])); 252 | } 253 | if ((areas[0] < 0 && areas[1] < 0 && areas[2] < 0) || (areas[0] > 0 && areas[1] > 0 && areas[2] > 0)) { 254 | w = divide(areas, areas[0]+areas[1]+areas[2]); 255 | return true; 256 | } 257 | return false; 258 | } 259 | 260 | template 261 | array elementCenter(const vector>& vertices, const array& element) { 262 | array center; 263 | for (auto i : element) { 264 | for (int j = 0; j < d; ++j) { 265 | center[j] += vertices[i][j]; 266 | } 267 | } 268 | for (int i = 0; i < d; ++i) { 269 | center[i] /= (T)element.size(); 270 | } 271 | return center; 272 | } 273 | 274 | template 275 | struct Box { 276 | array lowerLeft_, upperRight_; 277 | 278 | Box() {} 279 | 280 | Box(const Box& b) { 281 | for (int i = 0; i < d; ++i) { 282 | lowerLeft_[i] = b.lowerLeft_[i]; 283 | upperRight_[i] = b.upperRight_[i]; 284 | } 285 | } 286 | 287 | Box(const Box& b1, const Box& b2) { 288 | for (int i = 0; i < d; ++i) { 289 | lowerLeft_[i] = min(b1.lowerLeft_[i], b2.lowerLeft_[i]); 290 | upperRight_[i] = max(b1.upperRight_[i], b2.upperRight_[i]); 291 | } 292 | } 293 | 294 | bool intersects(const Box& b) { 295 | for (int i = 0; i < d; ++i) { 296 | if (lowerLeft_[i] > b.upperRight_[i] || upperRight_[i] < b.lowerLeft_[i]) { 297 | return false; 298 | } 299 | } 300 | return true; 301 | } 302 | }; 303 | 304 | template 305 | Box buildBox(const vector>& vertices, const array& element) { 306 | Box b; 307 | // print(element); 308 | for (int i = 0; i < d; ++i) { 309 | b.lowerLeft_[i] = numeric_limits::max(); 310 | b.upperRight_[i] = numeric_limits::lowest(); 311 | } 312 | // print(b.lowerLeft_); 313 | // print(b.upperRight_); 314 | for (size_t i = 0; i < d1; ++i) { 315 | // print(vertices[element[i]]); 316 | for (int j = 0; j < d; ++j) { 317 | b.lowerLeft_[j] = min(b.lowerLeft_[j], vertices[element[i]][j]); 318 | b.upperRight_[j] = max(b.upperRight_[j], vertices[element[i]][j]); 319 | } 320 | } 321 | // print(b.lowerLeft_); 322 | // print(b.upperRight_); 323 | return b; 324 | } 325 | 326 | template 327 | struct BoxNode { 328 | int n_; 329 | BoxNode *left_, *right_; 330 | Box box_; 331 | 332 | BoxNode(int n, const vector>& boxes) : n_(n), left_(nullptr), right_(nullptr), box_(boxes[n]) {} 333 | 334 | BoxNode(BoxNode* left, BoxNode* right) : n_(-1), left_(left), right_(right), box_(left->box_, right->box_) {} 335 | 336 | ~BoxNode() { 337 | if (left_ != nullptr) { 338 | delete left_; 339 | } 340 | if (right_ != nullptr) { 341 | delete right_; 342 | } 343 | } 344 | }; 345 | 346 | template 347 | BoxNode* buildBoxHierarchy(const vector>& boxes, const vector>& centers, vector& elementIndexes, int begin, int end, int level) { 348 | BoxNode* root = nullptr; 349 | if (elementIndexes.size() == 0) { 350 | return nullptr; 351 | } 352 | if (begin == end) { 353 | root = new BoxNode(elementIndexes[begin], boxes); 354 | } else { 355 | nth_element(elementIndexes.begin()+begin, elementIndexes.begin()+(begin+end)/2, elementIndexes.begin()+end, [&](std::size_t i, std::size_t j){ return centers[i][level%d] < centers[j][level%d]; }); 356 | BoxNode *left = buildBoxHierarchy(boxes, centers, elementIndexes, begin, (begin+end)/2, ++level); 357 | BoxNode *right = buildBoxHierarchy(boxes, centers, elementIndexes, (begin+end)/2+1, end, level); 358 | root = new BoxNode(left, right); 359 | } 360 | return root; 361 | } 362 | 363 | template 364 | class BoxHierarchy { 365 | int n_; //number of boxes 366 | BoxNode* root_; 367 | public: 368 | BoxHierarchy(const vector>& boxes, const vector>& centers) : n_(centers.size()) { 369 | if (boxes.size()) { 370 | vector elementIndexes(boxes.size()); 371 | iota(elementIndexes.begin(), elementIndexes.end(), 0); 372 | root_ = buildBoxHierarchy(boxes, centers, elementIndexes, 0, boxes.size()-1, 0); 373 | } 374 | } 375 | 376 | void intersect(const BoxHierarchy& bh, vector>& intersectingElements) const { 377 | intersectingElements.clear(); 378 | intersectingElements.resize(n_); 379 | if (!root_ || !bh.root_) { 380 | return; 381 | } 382 | stack*, BoxNode*>> s; 383 | s.push(pair*, BoxNode*>(root_, bh.root_)); 384 | while (s.size()) { 385 | pair*, BoxNode*> top = s.top(); 386 | s.pop(); 387 | if (top.first->box_.intersects(top.second->box_)) { 388 | if (top.first->n_ != -1 && top.second->n_ != -1) { 389 | intersectingElements[top.first->n_].push_back(top.second->n_); 390 | } else if (top.second->n_ != -1) { 391 | s.push(pair*, BoxNode*>(top.first->left_, top.second)); 392 | s.push(pair*, BoxNode*>(top.first->right_, top.second)); 393 | } else if (top.first->n_ != -1) { 394 | s.push(pair*, BoxNode*>(top.first, top.second->left_)); 395 | s.push(pair*, BoxNode*>(top.first, top.second->right_)); 396 | } else { 397 | s.push(pair*, BoxNode*>(top.first->left_, top.second->left_)); 398 | s.push(pair*, BoxNode*>(top.first->left_, top.second->right_)); 399 | s.push(pair*, BoxNode*>(top.first->right_, top.second->left_)); 400 | s.push(pair*, BoxNode*>(top.first->right_, top.second->right_)); 401 | } 402 | } 403 | } 404 | } 405 | 406 | ~BoxHierarchy() { 407 | delete root_; 408 | } 409 | }; 410 | 411 | template 412 | BoxHierarchy buildBoxHierarchy(const vector>& nodes, const vector>& elements) { 413 | vector> boxes; 414 | vector> centers; 415 | for (const auto& e: elements) { 416 | boxes.push_back(buildBox(nodes, e)); 417 | centers.push_back(elementCenter(nodes, e)); 418 | } 419 | return BoxHierarchy(boxes, centers); 420 | } 421 | 422 | template 423 | class TriMesh { 424 | typedef std::array I2; 425 | typedef std::array I3; 426 | typedef std::array I4; 427 | typedef std::array TV; 428 | 429 | public: 430 | vector nodes_; 431 | vector mesh_; 432 | 433 | void clear() { 434 | nodes_.clear(); 435 | mesh_.clear(); 436 | } 437 | }; 438 | 439 | const array, 4> FaceIndexes = { 440 | array{0,1,2}, 441 | array{0,2,3}, 442 | array{1,2,3}, 443 | array{0,3,1} 444 | }; 445 | 446 | array,4> tetFaces(const array& tet) { 447 | array,4> faces; 448 | for (int i = 0; i < 4; ++i) { 449 | for (int j = 0; j < 3; ++j) { 450 | faces[i][j] = tet[FaceIndexes[i][j]]; 451 | } 452 | } 453 | return faces; 454 | } 455 | 456 | array tetFace(const array& tet, int i) { 457 | array face; 458 | for (int j = 0; j < 3; ++j) { 459 | face[j] = tet[FaceIndexes[i][j]]; 460 | } 461 | return face; 462 | } 463 | 464 | const array, 6> EdgeIndexes = {array{0,1}, array{0,2}, array{0,3}, array{1,2}, array{1,3}, array{2,3}}; 465 | 466 | array,6> tetEdges(const array& tet) { 467 | array,6> faces; 468 | for (int i = 0; i < 6; ++i) { 469 | for (int j = 0; j < 2; ++j) { 470 | faces[i][j] = tet[EdgeIndexes[i][j]]; 471 | } 472 | } 473 | return faces; 474 | } 475 | 476 | array,3> faceEdges(const array& face) { 477 | array,3> edges; 478 | for (int i = 0; i < 3; ++i) { 479 | edges[i] = array{face[i], face[(i+1)%3]}; 480 | } 481 | return edges; 482 | } 483 | 484 | template 485 | array,d1> elementNodes(const vector>& nodes, const array& element) { 486 | array,d1> ps; 487 | for (int i = 0; i < d1; ++i) { 488 | ps[i] = nodes[element[i]]; 489 | } 490 | return ps; 491 | } 492 | template 493 | class TetMesh { 494 | typedef array I2; 495 | typedef array I3; 496 | typedef array I4; 497 | typedef array TV; 498 | 499 | public: 500 | vector nodes_; 501 | vector mesh_; 502 | vector surfaceMesh_; 503 | vector connectedComponents_; //connected component id of each element 504 | 505 | TetMesh() {} 506 | 507 | TetMesh(vector&& nodes, vector&& mesh): nodes_(nodes), mesh_(mesh) { 508 | initializeSurfaceMesh(); 509 | computeConnectedComponents(); 510 | } 511 | void initializeSurfaceMesh() { 512 | surfaceMesh_.clear(); 513 | map surfaceElements; //sorted to unsorted elements 514 | for (const auto& tet: mesh_) { 515 | for (const auto& fi: FaceIndexes) { 516 | auto face = I3{tet[fi[0]], tet[fi[1]], tet[fi[2]]}; 517 | auto sortedFace = face; 518 | sort(sortedFace.begin(), sortedFace.end()); 519 | if (surfaceElements.count(sortedFace)) { 520 | surfaceElements.erase(sortedFace); 521 | } else { 522 | surfaceElements[sortedFace] = face; 523 | } 524 | } 525 | } 526 | for (const auto& e: surfaceElements) { 527 | surfaceMesh_.push_back(e.second); 528 | } 529 | } 530 | 531 | void computeConnectedComponents() { 532 | connectedComponents_.resize(mesh_.size()); 533 | UnionFind nodeClasses(nodes_.size()); 534 | for (int i = 0; i < mesh_.size(); ++i) { 535 | for (int j = 0; j < 3; ++j) { 536 | nodeClasses.merge(mesh_[i][j], mesh_[i][j+1]); 537 | } 538 | } 539 | map nodeClassToCC; 540 | int c = 1; 541 | for (int i = 0; i < mesh_.size(); ++i) { 542 | if (!nodeClassToCC.count(nodeClasses.find(mesh_[i][0]))) { 543 | connectedComponents_[i] = c; 544 | nodeClassToCC[nodeClasses.find(mesh_[i][0])] = c; 545 | ++c; 546 | } else { 547 | connectedComponents_[i] = nodeClassToCC[nodeClasses.find(mesh_[i][0])]; 548 | } 549 | } 550 | cout << "found " << c-1 << " connected components\n"; 551 | } 552 | }; 553 | 554 | #endif /* DataStructures_hpp */ 555 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Yuting Wang 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3d-cutter 2 | 3 | ## Description 4 | If you want to cut a tetrahedral mesh with an arbitrary surface in 3d, this is the code to use! 5 | 6 | It's a simplified implementation of the paper "An Adaptive Virtual Node Algorithm with Robust Mesh Cutting" http://www.seas.upenn.edu/~cffjiang/research/cut/paper.pdf. The resolution of the cutting details is constrained by the tetrahedral mesh, which is adaptively refined after each cut. For now it doesn't support robust intersection computations or cutting through nodes, edges or faces, so you will need to perturb your meshes to avoid these degenerate cases if they happen. 7 | 8 | ## Usage 9 | Written in C++11, the header only cutter depends on the standard library alone, and it has a simple interface: 10 | 11 | TetMesh Cutter3D::run(const TetMesh& tetMesh, const TriMesh& triMesh). 12 | 13 | You simply feed the cutter a tetrahedral mesh and a cutting surface mesh, and it will return a new tetrahedral mesh that is cut. 14 | 15 | The meshes can be easily constructed from std::vector and std::array, so just convert your meshes into these std data structures and start running the cutter! 16 | 17 | TetMesh(vector>& nodes, vector>& mesh) 18 | 19 | TriMesh(vector>& nodes, vector>& mesh) 20 | 21 | 22 | ## Demo 23 | main.cpp is an interactive cutting interface that depends on OpenGL and GLUT. It supports drawing a curve that will be extended along the z direction into a cutting surface, dragging pieces around, rotation and shifting. For more details, please see the comments in the keyboard callback function "void key(unsigned char key, int x, int y)". 24 | 25 | The video demo.mov is added to showcase the cutting effects. 26 | -------------------------------------------------------------------------------- /demo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopstring/3d-cutter/05d9cf85578e9f94aded2a69519b18b74167bab2/demo.mov -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Yuting Wang. All rights reserved. 3 | // 4 | 5 | #ifdef __APPLE__//Mac OS 6 | //# include 7 | # include 8 | # include 9 | #else 10 | //# include 11 | # include 12 | # include 13 | #endif // __APPLE__ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "Cutting.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | using namespace std; 27 | 28 | #define d 3 29 | #define T float 30 | #define TV array 31 | #define T2 array 32 | #define T4 array 33 | #define Idp1 array 34 | #define Id array 35 | #define I3 array 36 | #define I2 array 37 | #define I4 array 38 | 39 | #ifdef far 40 | #undef far 41 | #endif 42 | 43 | #ifdef near 44 | #undef near 45 | #endif 46 | 47 | TetMesh tetMesh; 48 | TriMesh triMesh; 49 | 50 | void zoom(T scaleSpeed) { 51 | for (auto& v : tetMesh.nodes_) { 52 | v = divide(v, 1/scaleSpeed); 53 | } 54 | for (auto& v : triMesh.nodes_) { 55 | v = divide(v, 1/scaleSpeed); 56 | } 57 | } 58 | 59 | void shift(TV shiftSpeed) { 60 | for (auto& v : tetMesh.nodes_) { 61 | v = add(v, shiftSpeed); 62 | } 63 | for (auto& v : triMesh.nodes_) { 64 | v = add(v, shiftSpeed); 65 | } 66 | } 67 | 68 | void simpleTet1() { 69 | tetMesh.nodes_.clear(); 70 | tetMesh.mesh_.clear(); 71 | tetMesh.nodes_.push_back(TV{0,0,0}); 72 | tetMesh.nodes_.push_back(TV{1,0,0}); 73 | tetMesh.nodes_.push_back(TV{0,1,0}); 74 | tetMesh.nodes_.push_back(TV{0,0,1}); 75 | tetMesh.nodes_.push_back(TV{1,1,1}); 76 | tetMesh.mesh_.push_back(Idp1{0,1,2,3}); 77 | tetMesh.mesh_.push_back(Idp1{4,2,1,3}); 78 | // array w; 79 | // Cutter3D::computeIntersection(array{tetMesh.nodes_[0],tetMesh.nodes_[1],tetMesh.nodes_[2],tetMesh.nodes_[3]}, array{TV{0.1,0.1,0.1}}, w); 80 | // cout << w[0] << ", " << w[1] << ", " << w[2] << ", " << w[3] << endl; 81 | tetMesh.initializeSurfaceMesh(); 82 | tetMesh.computeConnectedComponents(); 83 | 84 | triMesh.nodes_.push_back(TV{0.3f,-0.1f,1.1f}); 85 | triMesh.nodes_.push_back(TV{0.3f,-0.1f,-1}); 86 | triMesh.nodes_.push_back(TV{0.3f,1.1f,1.1f}); 87 | triMesh.nodes_.push_back(TV{0.3f,1.1f,-1}); 88 | triMesh.mesh_.push_back(I3{0,1,2}); 89 | triMesh.mesh_.push_back(I3{2,3,1}); 90 | 91 | Cutter3D cutter; 92 | tetMesh = cutter.run(tetMesh, triMesh); 93 | } 94 | 95 | void loadTriMesh(const string& filename) { 96 | ifstream fs(filename); 97 | triMesh.nodes_.clear(); 98 | triMesh.mesh_.clear(); 99 | string line; 100 | int l = 0; 101 | while(getline(fs, line)) { 102 | if (line.find("end_header") != std::string::npos) { 103 | while(getline(fs, line)) { 104 | if (line.substr(0,2) != "3 ") { 105 | stringstream ss(line); 106 | T t1,t2,t3; 107 | ss >> t1 >> t2 >> t3; 108 | triMesh.nodes_.push_back(divide(TV{t1,t2,t3},13)); 109 | } else { 110 | stringstream ss(line); 111 | int t1,t2,t3; 112 | ss >> t1 >> t1 >> t2 >> t3; 113 | triMesh.mesh_.push_back(I3{t1,t2,t3}); 114 | } 115 | } 116 | break; 117 | } 118 | } 119 | cout << triMesh.nodes_.size() << " " << triMesh.mesh_.size() << endl; 120 | } 121 | 122 | void meshCutGrid() { 123 | tetMesh.nodes_.clear(); 124 | tetMesh.mesh_.clear(); 125 | int width = 23; 126 | int height = 23; 127 | int depth = 23; 128 | T low = -0.5; 129 | T high = 0.5; 130 | T left = -0.5; 131 | T right = 0.5; 132 | T far = 0.6; 133 | T near = -0.5; 134 | T lw = (right-left)/width; 135 | T lh = (high-low)/height; 136 | T ld = (far-near)/depth; 137 | int offset = (width+1)*(depth+1); 138 | 139 | for (int i = 0; i < height+1; i++) { 140 | //cout << "height: " << low+i*lh << endl; 141 | for (int j = 0; j < width+1; j++) { 142 | for (int k = 0; k < depth+1; k++) { 143 | tetMesh.nodes_.push_back(TV{left+j*lw, low+i*lh,near+k*ld}); 144 | } 145 | } 146 | } 147 | 148 | int tetIndex = 0; 149 | for (int k = 0; k < height; k++) { 150 | for (int i = 0; i < width; i++) { 151 | for (int j = 0; j < depth; j++) { 152 | tetMesh.mesh_.push_back(I4{tetIndex+offset+1, tetIndex+1, tetIndex, tetIndex+depth+1}); 153 | tetMesh.mesh_.push_back(I4{tetIndex, tetIndex+offset, tetIndex+offset+1, tetIndex+depth+1}); 154 | tetMesh.mesh_.push_back(I4{tetIndex+depth+1, tetIndex+offset, tetIndex+offset+1, tetIndex+offset+depth+1}); 155 | tetMesh.mesh_.push_back(I4{tetIndex+1, tetIndex+depth+1, tetIndex+offset+1, tetIndex+depth+2}); 156 | tetMesh.mesh_.push_back(I4{tetIndex+depth+1, tetIndex+depth+2, tetIndex+offset+depth+2, tetIndex+offset+1}); 157 | tetMesh.mesh_.push_back(I4{tetIndex+depth+1, tetIndex+depth+2+offset, tetIndex+offset+depth+1, tetIndex+offset+1}); 158 | tetIndex++; 159 | } 160 | tetIndex++; 161 | } 162 | tetIndex += (depth+1); 163 | } 164 | 165 | tetMesh.initializeSurfaceMesh(); 166 | tetMesh.computeConnectedComponents(); 167 | 168 | // triMesh.nodes_.push_back(TV{0.3,-0.1,1.1}); 169 | // triMesh.nodes_.push_back(TV{0.3,-0.1,-1}); 170 | // triMesh.nodes_.push_back(TV{0.3,1.1,1.1}); 171 | // triMesh.nodes_.push_back(TV{0.3,1.1,-1}); 172 | // triMesh.mesh_.push_back(I3{0,1,2}); 173 | // triMesh.mesh_.push_back(I3{2,3,1}); 174 | 175 | // loadTriMesh("/Users/yutingwang/Downloads/beethoven.ply"); 176 | // Cutter3D cutter; 177 | // tetMesh = cutter.run(tetMesh, triMesh); 178 | } 179 | 180 | 181 | T scaleSpeed=1.1; 182 | T shiftSpeed=0.02; 183 | bool cutting = false; 184 | void key(unsigned char key, int x, int y) { 185 | switch( key ) { 186 | case 033: // Escape Key 187 | exit(EXIT_SUCCESS); 188 | break; 189 | case 'c': // enter/exit cutting mode. In cutting mode, drag the mouse to draw a cutting curve. Otherwise, drag the red tet mesh to move it, or drag the mouse in the background to rotate the scene. 190 | cutting = !cutting; 191 | break; 192 | case 'r': // remove cutting triangle mesh 193 | triMesh.nodes_.clear(); 194 | triMesh.mesh_.clear(); 195 | break; 196 | case 'i': // zoom in 197 | zoom(scaleSpeed); 198 | break; 199 | case 'o': // zoom out 200 | zoom(1/scaleSpeed); 201 | break; 202 | case 'a': // shift left 203 | shift(TV{-shiftSpeed,0,0}); 204 | break; 205 | case 'd': // shift right 206 | shift(TV{shiftSpeed,0,0}); 207 | break; 208 | case 'w': // shift up 209 | shift(TV{0,shiftSpeed,0}); 210 | break; 211 | case 's': // shift down 212 | shift(TV{0,-shiftSpeed,0}); 213 | break; 214 | case 'f': // reload initial meshes 215 | meshCutGrid(); 216 | break; 217 | } 218 | glutPostRedisplay(); 219 | } 220 | 221 | int windowWidth = 600; 222 | int windowHeight = 600; 223 | T2 startingPosition; 224 | T transSpeed=0.1; 225 | set draggingParticles; 226 | int triMeshRes = 30; 227 | T triMeshNear = -1.1; 228 | T triMeshFar = 1.1; 229 | T triMeshEdgeLen = (triMeshFar-triMeshNear) / triMeshRes; 230 | void mouse(int button, int state, int x, int y) { 231 | array location{2*x/T(windowWidth)-1,1-2*y/T(windowHeight)}; 232 | if(button==4){ 233 | zoom(scaleSpeed); 234 | glutPostRedisplay(); 235 | return; 236 | } else if(button==3){ 237 | zoom(1/scaleSpeed); 238 | glutPostRedisplay(); 239 | return; 240 | } 241 | if(state==GLUT_DOWN){ 242 | if(button==GLUT_LEFT_BUTTON){ 243 | startingPosition = location; 244 | if (cutting) { 245 | triMesh.clear(); 246 | for (int i = 0; i <= triMeshRes; ++i) { 247 | triMesh.nodes_.push_back(TV{location[0], location[1], (T)-1.1+i*triMeshEdgeLen}); 248 | } 249 | } else { 250 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 251 | glBegin(GL_TRIANGLES); 252 | for(int i = 0; i < tetMesh.mesh_.size(); ++i) { 253 | glColor4d(tetMesh.connectedComponents_[i]/255.,0,0,0); 254 | const auto& tet = tetMesh.mesh_[i]; 255 | for (const auto& fi: FaceIndexes) { 256 | for (int k = 0; k < 3; ++k) { 257 | auto p = tetMesh.nodes_[tet[fi[k]]]; 258 | glVertex3d(p[0], p[1], p[2]); 259 | } 260 | } 261 | } 262 | glEnd(); 263 | unsigned char val; 264 | glReadPixels(x, windowHeight - y, 1, 1, GL_RED, GL_UNSIGNED_BYTE, &val); 265 | draggingParticles.clear(); 266 | for (int i = 0; i < tetMesh.connectedComponents_.size(); ++i) { 267 | if (tetMesh.connectedComponents_[i] == (int)val) { 268 | for (auto j : tetMesh.mesh_[i]) { 269 | draggingParticles.insert(j); 270 | } 271 | } 272 | } 273 | } 274 | } 275 | } 276 | else if(state==GLUT_UP){ 277 | if (cutting) { 278 | if (triMesh.mesh_.size()==0) { 279 | return; 280 | } 281 | tetMesh = Cutter3D::run(tetMesh, triMesh); 282 | glutPostRedisplay(); 283 | } 284 | } 285 | } 286 | 287 | void motion(int x, int y) { 288 | T2 location{2*x/T(windowWidth)-1,1-2*y/T(windowHeight)}; 289 | auto shift = substract(location, startingPosition); 290 | T l = sqrt(pow(shift[0],2)+pow(shift[1],2)); 291 | if (fabs(shift[0]) < 0.002 || fabs(shift[1]) < 0.002) { 292 | return; 293 | } 294 | if (cutting){ 295 | //cout << location[0] << ", " << location[1] << endl; 296 | for (int i = 0; i <= triMeshRes; ++i) { 297 | if (i > 0) { 298 | int n = triMesh.nodes_.size(); 299 | triMesh.mesh_.push_back(I3{n, n-1, n-triMeshRes-1}); 300 | triMesh.mesh_.push_back(I3{n-triMeshRes-1, n-1, n-triMeshRes-2}); 301 | } 302 | triMesh.nodes_.push_back(TV{location[0], location[1], (T)-1.1+i*triMeshEdgeLen}); 303 | } 304 | } else { 305 | if (draggingParticles.size()) { 306 | for (auto i : draggingParticles) { 307 | tetMesh.nodes_[i] = add(tetMesh.nodes_[i], TV{shift[0], shift[1], 0}); 308 | } 309 | } else { 310 | T dx = -shift[1]; 311 | T dy = shift[0]; 312 | T costheta = cos(-l); 313 | T sintheta = sin(-l); 314 | dx /= l; 315 | dy /= l; 316 | array,d> rotationMatrix{ 317 | TV{costheta+dx*dx*(1-costheta), dx*dy*(1-costheta), dy*sintheta}, 318 | TV{dx*dy*(1-costheta), dy*dy*(1-costheta)+costheta, -dx*sintheta}, 319 | TV{-dy*sintheta, dx*sintheta, costheta} 320 | }; 321 | for (auto& p: tetMesh.nodes_) { 322 | p = TV{dot(rotationMatrix[0], p),dot(rotationMatrix[1], p),dot(rotationMatrix[2], p)}; 323 | } 324 | //print(tetMesh.nodes_); 325 | for (auto& p: triMesh.nodes_) { 326 | p = TV{dot(rotationMatrix[0], p),dot(rotationMatrix[1], p),dot(rotationMatrix[2], p)}; 327 | } 328 | } 329 | startingPosition = location; 330 | } 331 | glutPostRedisplay(); 332 | } 333 | 334 | void render(const vector& nodes, const vector& faces, const T4& triColor, const T4& segColor) { 335 | vector tris, segs; 336 | for (const auto& face: faces) { 337 | tris.push_back(nodes[face[0]]); 338 | tris.push_back(nodes[face[1]]); 339 | tris.push_back(nodes[face[2]]); 340 | for (int i = 0; i < 3; ++i) { 341 | segs.push_back(nodes[face[i]]); 342 | segs.push_back(nodes[face[(i+1)%3]]); 343 | } 344 | } 345 | glVertexPointer(3, GL_FLOAT, 0, segs.data()); 346 | glColor4f(segColor[0], segColor[1], segColor[2], segColor[3]); 347 | glDrawArrays(GL_LINES, 0, segs.size()); 348 | 349 | glVertexPointer(3, GL_FLOAT, 0, tris.data()); 350 | glColor4f(triColor[0], triColor[1], triColor[2], triColor[3]); 351 | glDrawArrays(GL_TRIANGLES, 0, tris.size()); 352 | } 353 | 354 | void render() { 355 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 356 | glEnableClientState(GL_VERTEX_ARRAY); 357 | 358 | render(tetMesh.nodes_, tetMesh.surfaceMesh_, T4{1,0,0,1}, T4{0,1,1,1}); 359 | render(triMesh.nodes_, triMesh.mesh_, T4{0,0,1,1}, T4{0,1,1,1}); 360 | 361 | glutSwapBuffers(); 362 | glDisableClientState(GL_VERTEX_ARRAY); 363 | } 364 | 365 | void reshape(GLint newWidth,GLint newHeight) { 366 | glViewport(0, 0, newWidth, newHeight); 367 | windowWidth = newWidth; 368 | windowHeight = newHeight; 369 | glutPostRedisplay(); 370 | } 371 | 372 | int main(int argc, char **argv) { 373 | meshCutGrid(); 374 | 375 | glutInit(&argc, argv); 376 | glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA | GLUT_ALPHA); 377 | glutInitWindowSize(windowWidth, windowHeight); 378 | string window_name="cutting"; 379 | glutCreateWindow(window_name.c_str()); 380 | glClearColor(0.0,0.0,0.0,1.0); 381 | glEnable(GL_DEPTH_TEST); 382 | 383 | //glutSpecialFunc(Special_Key); 384 | glutKeyboardFunc(key); 385 | glutMouseFunc(mouse); 386 | glutMotionFunc(motion); 387 | glutDisplayFunc(render); 388 | glutReshapeFunc(reshape); 389 | 390 | glutMainLoop(); 391 | 392 | return 0; 393 | } 394 | --------------------------------------------------------------------------------