From 4ffa010f99a38ebd6d7ac763355403e98454fc09 Mon Sep 17 00:00:00 2001 From: Makogan Date: Wed, 17 Jul 2024 13:22:14 -0400 Subject: [PATCH] Simplified mesh intersection logic (#221) --- CHANGELOG.md | 8 + assets/tests/bar.obj | 195 +++ assets/tests/center_cylinder.obj | 191 +++ assets/tests/low_poly_bunny.obj | 1401 +++++++++++++++++ assets/tests/offset_cylinder.obj | 191 +++ assets/tests/poly_cylinder.obj | 141 ++ assets/tests/stairs.obj | 71 + crates/parry3d-f64/Cargo.toml | 4 + crates/parry3d/Cargo.toml | 1 + src/query/point/point_query.rs | 5 + src/shape/shape.rs | 4 +- src/shape/triangle.rs | 49 + src/shape/trimesh.rs | 15 - .../mesh_intersection/mesh_intersection.rs | 1079 ++++++++----- .../mesh_intersection_error.rs | 8 +- .../triangle_triangle_intersection.rs | 105 +- src/transformation/mod.rs | 3 + src/transformation/polygon_intersection.rs | 6 +- src/transformation/wavefront.rs | 38 + src/utils/mod.rs | 2 +- src/utils/spade.rs | 7 +- 21 files changed, 2981 insertions(+), 543 deletions(-) create mode 100644 assets/tests/bar.obj create mode 100644 assets/tests/center_cylinder.obj create mode 100644 assets/tests/low_poly_bunny.obj create mode 100644 assets/tests/offset_cylinder.obj create mode 100644 assets/tests/poly_cylinder.obj create mode 100644 assets/tests/stairs.obj create mode 100644 src/transformation/wavefront.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bbfb5362..e01125f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,17 @@ ## Unreleased +### Added + +- Add `Triangle::robust_scaled_normal` and `Triangle::robust_normal` as a more robust way to compute the triangles + normal for thin triangles that generally cause numerical instabilities. +- Add `Triangle::angle_closest_to_90` to find the triangle’s vertex with an angle closest to 90 degree. +- Add the `wavefront` feature that enables `TriMesh::to_obj_file` for exporting a mesh as an obj file. + ### Modified - `TypedShape::Custom(u32)` is now `TypedShape::Custom(&dyn Shape)`. +- Significantly improved the general stability of mesh/mesh intersection calculation. ## v0.16.1 diff --git a/assets/tests/bar.obj b/assets/tests/bar.obj new file mode 100644 index 00000000..a6748221 --- /dev/null +++ b/assets/tests/bar.obj @@ -0,0 +1,195 @@ +# Author: Camilo Talero +# License: CC0 +v 0.467654 13.305400 -6.892637 +v -0.550473 -3.651697 6.193181 +v 0.451122 13.300038 -6.900873 +v -0.567006 -3.657059 6.184945 +v 0.436815 13.292733 -6.911451 +v -0.581312 -3.664363 6.174367 +v 0.425285 13.283769 -6.923967 +v -0.592842 -3.673329 6.161853 +v 0.416975 13.273486 -6.937937 +v -0.601152 -3.683610 6.147883 +v 0.412204 13.262284 -6.952824 +v -0.605924 -3.694813 6.132995 +v 0.411154 13.250590 -6.968058 +v -0.606973 -3.706506 6.117761 +v 0.413868 13.238854 -6.983053 +v -0.604259 -3.718241 6.102765 +v 0.420240 13.227530 -6.997232 +v -0.597888 -3.729566 6.088586 +v 0.430025 13.217050 -7.010054 +v -0.588102 -3.740046 6.075767 +v 0.442847 13.207818 -7.021019 +v -0.575280 -3.749279 6.064799 +v 0.458215 13.200187 -7.029711 +v -0.559913 -3.756910 6.056108 +v 0.475536 13.194451 -7.035795 +v -0.542591 -3.762644 6.050025 +v 0.494146 13.190835 -7.039036 +v -0.523982 -3.766263 6.046782 +v 0.513329 13.189469 -7.039310 +v -0.504798 -3.767626 6.046510 +v 0.532348 13.190415 -7.036608 +v -0.485779 -3.766682 6.049212 +v 0.550472 13.193629 -7.031031 +v -0.467655 -3.763467 6.054788 +v 0.567005 13.198991 -7.022795 +v -0.451122 -3.758105 6.063024 +v 0.581311 13.206296 -7.012217 +v -0.436816 -3.750801 6.073602 +v 0.592841 13.215261 -6.999702 +v -0.425286 -3.741835 6.086116 +v 0.601151 13.225543 -6.985735 +v -0.416976 -3.731554 6.100085 +v 0.605923 13.236745 -6.970846 +v -0.412204 -3.720351 6.114974 +v 0.606972 13.248439 -6.955610 +v -0.411155 -3.708658 6.130208 +v 0.604259 13.260175 -6.940615 +v -0.413868 -3.696923 6.145204 +v 0.597887 13.271499 -6.926435 +v -0.420240 -3.685598 6.159383 +v 0.588102 13.281980 -6.913616 +v -0.430025 -3.675117 6.172202 +v 0.575279 13.291211 -6.902651 +v -0.442848 -3.665884 6.183169 +v 0.559912 13.298842 -6.893957 +v -0.458215 -3.658254 6.191861 +v 0.542591 13.304578 -6.887875 +v -0.475536 -3.652520 6.197946 +v 0.523981 13.308194 -6.884634 +v -0.494146 -3.648901 6.201186 +v 0.504798 13.309560 -6.884359 +v -0.513329 -3.647538 6.201461 +v 0.485779 13.308614 -6.887062 +v -0.532348 -3.648482 6.198758 +# 64 vertices, 0 vertices normals + +f 3 2 1 +f 6 3 5 +f 8 5 7 +f 10 7 9 +f 11 10 9 +f 13 12 11 +f 15 14 13 +f 17 16 15 +f 19 18 17 +f 21 20 19 +f 24 21 23 +f 26 23 25 +f 28 25 27 +f 29 28 27 +f 31 30 29 +f 33 32 31 +f 35 34 33 +f 37 36 35 +f 40 37 39 +f 42 39 41 +f 44 41 43 +f 45 44 43 +f 48 45 47 +f 50 47 49 +f 52 49 51 +f 54 51 53 +f 56 53 55 +f 57 56 55 +f 60 57 59 +f 62 59 61 +f 14 22 6 +f 64 61 63 +f 2 63 1 +f 63 55 31 +f 4 2 3 +f 4 3 6 +f 6 5 8 +f 8 7 10 +f 12 10 11 +f 14 12 13 +f 16 14 15 +f 18 16 17 +f 20 18 19 +f 22 20 21 +f 22 21 24 +f 24 23 26 +f 26 25 28 +f 30 28 29 +f 32 30 31 +f 34 32 33 +f 36 34 35 +f 38 36 37 +f 38 37 40 +f 40 39 42 +f 42 41 44 +f 46 44 45 +f 46 45 48 +f 48 47 50 +f 50 49 52 +f 52 51 54 +f 54 53 56 +f 58 56 57 +f 58 57 60 +f 60 59 62 +f 4 6 62 +f 2 4 62 +f 64 2 62 +f 60 62 58 +f 56 58 62 +f 54 56 62 +f 52 54 50 +f 48 50 46 +f 44 46 38 +f 42 44 38 +f 40 42 38 +f 36 38 30 +f 34 36 30 +f 32 34 30 +f 28 30 26 +f 24 26 22 +f 20 22 14 +f 18 20 14 +f 16 18 14 +f 12 14 6 +f 10 12 6 +f 8 10 6 +f 50 54 38 +f 46 50 38 +f 26 30 22 +f 62 6 38 +f 54 62 38 +f 30 38 6 +f 22 30 6 +f 62 61 64 +f 64 63 2 +f 1 63 3 +f 5 3 63 +f 7 5 63 +f 9 7 15 +f 11 9 15 +f 13 11 15 +f 17 15 19 +f 21 19 15 +f 23 21 15 +f 25 23 27 +f 29 27 31 +f 33 31 35 +f 37 35 31 +f 39 37 31 +f 41 39 43 +f 45 43 47 +f 49 47 51 +f 53 51 47 +f 55 53 47 +f 57 55 59 +f 61 59 63 +f 27 23 15 +f 31 27 15 +f 43 39 31 +f 47 43 31 +f 59 55 63 +f 7 63 31 +f 15 7 31 +f 55 47 31 +# 124 faces, 0 coords texture + +# End of File diff --git a/assets/tests/center_cylinder.obj b/assets/tests/center_cylinder.obj new file mode 100644 index 00000000..eaeecf53 --- /dev/null +++ b/assets/tests/center_cylinder.obj @@ -0,0 +1,191 @@ +# Author: Camilo Talero +# License: CC0 +v 0.000000 -1.000000 -1.000000 +v 0.000000 1.000000 -1.000000 +v 0.195090 -1.000000 -0.980785 +v 0.195090 1.000000 -0.980785 +v 0.382683 -1.000000 -0.923880 +v 0.382683 1.000000 -0.923880 +v 0.555570 -1.000000 -0.831470 +v 0.555570 1.000000 -0.831470 +v 0.707107 -1.000000 -0.707107 +v 0.707107 1.000000 -0.707107 +v 0.831470 -1.000000 -0.555570 +v 0.831470 1.000000 -0.555570 +v 0.923880 -1.000000 -0.382683 +v 0.923880 1.000000 -0.382683 +v 0.980785 -1.000000 -0.195090 +v 0.980785 1.000000 -0.195090 +v 1.000000 -1.000000 0.000000 +v 1.000000 1.000000 0.000000 +v 0.980785 -1.000000 0.195090 +v 0.980785 1.000000 0.195090 +v 0.923880 -1.000000 0.382683 +v 0.923880 1.000000 0.382683 +v 0.831470 -1.000000 0.555570 +v 0.831470 1.000000 0.555570 +v 0.707107 -1.000000 0.707107 +v 0.707107 1.000000 0.707107 +v 0.555570 -1.000000 0.831470 +v 0.555570 1.000000 0.831470 +v 0.382683 -1.000000 0.923880 +v 0.382683 1.000000 0.923880 +v 0.195090 -1.000000 0.980785 +v 0.195090 1.000000 0.980785 +v -0.000000 -1.000000 1.000000 +v -0.000000 1.000000 1.000000 +v -0.195090 -1.000000 0.980785 +v -0.195090 1.000000 0.980785 +v -0.382683 -1.000000 0.923880 +v -0.382683 1.000000 0.923880 +v -0.555570 -1.000000 0.831470 +v -0.555570 1.000000 0.831470 +v -0.707107 -1.000000 0.707107 +v -0.707107 1.000000 0.707107 +v -0.831469 -1.000000 0.555570 +v -0.831469 1.000000 0.555570 +v -0.923880 -1.000000 0.382684 +v -0.923880 1.000000 0.382684 +v -0.980785 -1.000000 0.195090 +v -0.980785 1.000000 0.195090 +v -1.000000 -1.000000 -0.000000 +v -1.000000 1.000000 -0.000000 +v -0.980785 -1.000000 -0.195090 +v -0.980785 1.000000 -0.195090 +v -0.923879 -1.000000 -0.382684 +v -0.923879 1.000000 -0.382684 +v -0.831470 -1.000000 -0.555570 +v -0.831470 1.000000 -0.555570 +v -0.707107 -1.000000 -0.707107 +v -0.707107 1.000000 -0.707107 +v -0.555570 -1.000000 -0.831470 +v -0.555570 1.000000 -0.831470 +v -0.382683 -1.000000 -0.923880 +v -0.382683 1.000000 -0.923880 +v -0.195090 -1.000000 -0.980785 +v -0.195090 1.000000 -0.980785 +s off +f 2 3 1 +f 4 5 3 +f 6 7 5 +f 8 9 7 +f 10 11 9 +f 12 13 11 +f 14 15 13 +f 16 17 15 +f 18 19 17 +f 20 21 19 +f 22 23 21 +f 24 25 23 +f 26 27 25 +f 28 29 27 +f 30 31 29 +f 32 33 31 +f 34 35 33 +f 36 37 35 +f 38 39 37 +f 40 41 39 +f 42 43 41 +f 44 45 43 +f 46 47 45 +f 48 49 47 +f 50 51 49 +f 52 53 51 +f 54 55 53 +f 56 57 55 +f 58 59 57 +f 60 61 59 +f 54 38 22 +f 62 63 61 +f 64 1 63 +f 15 31 47 +f 2 4 3 +f 4 6 5 +f 6 8 7 +f 8 10 9 +f 10 12 11 +f 12 14 13 +f 14 16 15 +f 16 18 17 +f 18 20 19 +f 20 22 21 +f 22 24 23 +f 24 26 25 +f 26 28 27 +f 28 30 29 +f 30 32 31 +f 32 34 33 +f 34 36 35 +f 36 38 37 +f 38 40 39 +f 40 42 41 +f 42 44 43 +f 44 46 45 +f 46 48 47 +f 48 50 49 +f 50 52 51 +f 52 54 53 +f 54 56 55 +f 56 58 57 +f 58 60 59 +f 60 62 61 +f 6 4 62 +f 4 2 62 +f 2 64 62 +f 62 60 58 +f 58 56 62 +f 56 54 62 +f 54 52 50 +f 50 48 46 +f 46 44 42 +f 42 40 38 +f 38 36 34 +f 34 32 30 +f 30 28 26 +f 26 24 22 +f 22 20 18 +f 18 16 14 +f 14 12 10 +f 10 8 6 +f 54 50 38 +f 50 46 38 +f 46 42 38 +f 38 34 30 +f 30 26 38 +f 26 22 38 +f 22 18 6 +f 18 14 6 +f 14 10 6 +f 6 62 54 +f 6 54 22 +f 62 64 63 +f 64 2 1 +f 63 1 3 +f 3 5 7 +f 7 9 11 +f 11 13 15 +f 15 17 19 +f 19 21 23 +f 23 25 27 +f 27 29 31 +f 31 33 35 +f 35 37 39 +f 39 41 43 +f 43 45 47 +f 47 49 51 +f 51 53 47 +f 53 55 47 +f 55 57 63 +f 57 59 63 +f 59 61 63 +f 63 3 15 +f 3 7 15 +f 7 11 15 +f 15 19 23 +f 23 27 15 +f 27 31 15 +f 31 35 47 +f 35 39 47 +f 39 43 47 +f 47 55 63 +f 63 15 47 diff --git a/assets/tests/low_poly_bunny.obj b/assets/tests/low_poly_bunny.obj new file mode 100644 index 00000000..6422f4df --- /dev/null +++ b/assets/tests/low_poly_bunny.obj @@ -0,0 +1,1401 @@ +# Author: Camilo Talero +# License: CC0 + +# Decimated from https://graphics.stanford.edu/~mdfisher/Data/Meshes/bunny.obj + +v 0.088770 1.286703 -0.402328 +v -0.237358 1.595642 0.123569 +v -1.125000 1.521824 0.796579 +v -1.675977 1.201319 0.040754 +v 0.637293 -0.006644 -0.160961 +v 0.245401 1.587681 0.486845 +v 0.306515 -0.284975 -0.426354 +v -0.776760 -0.116857 0.826870 +v -0.027808 1.027334 0.883640 +v 0.015771 0.886230 -0.670367 +v -0.779321 -0.300516 0.858120 +v -0.345437 1.465395 -0.130014 +v 0.484914 -0.299720 0.238641 +v -0.094634 -0.230486 0.200918 +v -0.690734 -0.322259 -0.419404 +v -1.002323 0.279923 0.693748 +v -1.691673 0.992154 -0.063988 +v -1.187880 -0.206945 0.398413 +v -0.700454 0.446739 0.836718 +v -1.688780 2.021224 0.632541 +v -0.179516 1.468788 0.700518 +v -1.163733 -0.016451 0.133963 +v 0.614266 1.331982 0.573045 +v -1.292738 2.067869 -0.002609 +v -0.697589 0.903487 0.895772 +v -0.553115 0.265960 0.743723 +v -0.907673 2.381575 0.025717 +v 1.141912 0.273601 0.516107 +v 0.437403 -0.303291 0.447249 +v 0.211614 -0.304826 0.541249 +v -0.302450 1.004593 0.881693 +v -0.361474 0.918233 1.043957 +v 0.777422 0.297245 -0.219951 +v -1.125659 0.244187 -0.128440 +v 0.771131 0.726767 -0.311357 +v -1.077368 0.171055 0.477747 +v -1.731113 1.183966 0.317156 +v -1.018205 0.710677 0.919940 +v -1.124609 -0.228734 0.958223 +v -0.211456 -0.217407 0.751920 +v -1.472710 2.505241 -0.837040 +v -1.306590 2.631176 -1.173387 +v -1.122428 -0.058767 -0.095476 +v -0.754680 -0.231501 -0.193115 +v -1.398670 2.112914 0.038787 +v 0.234889 1.104241 -0.401347 +v 0.097765 0.096534 -0.616585 +v -1.500235 1.146758 0.718896 +v -0.670191 2.478831 -0.020642 +v -0.930667 0.534924 -0.369422 +v 0.534702 0.396707 0.864832 +v -1.790483 0.963998 0.196709 +v -0.979360 1.620475 0.595260 +v -0.224198 2.292585 -0.273762 +v 0.318656 -0.053112 0.879959 +v -0.657217 -0.149747 -0.604688 +v -0.850177 2.437413 -0.172501 +v -1.285508 0.245704 -0.055830 +v -1.479020 0.435043 0.653007 +v 0.968271 0.211250 0.628987 +v 0.863557 0.332947 0.540158 +v -0.919580 -0.328048 0.107806 +v -0.718748 -0.317132 0.206641 +v -0.644636 -0.326972 0.400391 +v -1.342952 -0.223018 -0.128304 +v -0.720823 0.576003 -0.404734 +v -0.337128 0.710368 -0.791895 +v -1.270818 2.026028 0.739459 +v -0.978303 0.925187 0.884035 +v -0.470121 -0.037386 1.032238 +v 0.986357 -0.077431 0.492021 +v -1.611931 1.761430 -0.064956 +v -0.710258 -0.305473 0.775053 +v -1.425370 1.234800 0.977332 +v 0.919860 0.806306 0.057630 +v 0.844152 1.087791 0.126640 +v -1.152274 0.152310 0.109967 +v -1.816931 1.730446 0.332613 +v -0.522118 1.086486 0.851781 +v -0.410911 1.302278 -0.331441 +v -0.651317 1.297510 -0.323072 +v -0.507949 -0.241648 0.236043 +v -0.966024 1.413036 -0.267413 +v -1.253759 -0.300415 0.896741 +v 0.434215 -0.085258 -0.413636 +v 0.201754 0.746022 1.119187 +v -1.345331 2.044793 -0.961809 +v -0.982281 1.879877 0.151544 +v 0.169619 1.644939 0.280768 +v -0.365136 -0.297437 1.042007 +v -0.243028 2.514544 -0.440292 +v 0.504131 -0.154731 0.757366 +v -1.103126 2.070624 0.559535 +v -1.249353 -0.313633 0.394476 +v -1.280850 -0.163914 0.539172 +v -1.824687 1.996111 0.277494 +v -0.452083 0.862621 -0.687707 +v -1.011868 -0.072330 0.344236 +v -1.210809 2.417327 -1.241960 +v 0.888214 -0.015508 -0.118325 +v -1.317414 1.211810 0.747668 +v -0.250178 -0.046735 0.968372 +v -1.371637 2.358822 -0.455232 +v -0.357283 0.635818 1.149879 +v -0.162430 1.178153 0.874590 +v 0.307308 0.555817 -0.592592 +v -1.439766 1.703510 1.007824 +v 0.940102 0.470482 0.314923 +v -0.869262 0.824713 -0.444856 +v -0.000708 -0.277414 0.938915 +v 0.405405 0.312149 0.968318 +v 0.053858 -0.319867 0.086221 +v -1.697227 0.824448 -0.077251 +v -1.374595 -0.094946 0.107979 +v -1.345137 -0.294970 0.613325 +v -1.046990 -0.019141 0.507989 +v -0.413104 2.562906 -0.313807 +v -1.210733 1.437931 0.859106 +v -1.318737 1.431845 1.040827 +v -1.288047 1.929522 -0.425011 +v -0.318706 1.189421 -0.413767 +v -0.312010 2.730358 -0.517358 +v 0.031319 -0.314576 0.340269 +v -1.172122 0.851391 0.911670 +v -0.876298 1.513891 0.426197 +v -0.123102 1.508613 -0.178811 +v 0.601453 0.574139 0.888465 +v -1.224890 2.104145 0.070637 +v -1.663550 0.635510 0.589829 +v -0.633995 0.530108 -0.640556 +v -0.869803 -0.041610 -0.212523 +v 1.161832 0.100970 0.462145 +v -1.607313 1.392937 1.003375 +v -1.501493 1.992898 -0.532615 +v -1.339678 2.460943 -0.891817 +v 0.338816 -0.307084 0.047956 +v 0.708091 0.794717 0.800908 +v -1.265469 2.162605 -0.889537 +v -1.250326 0.443967 -0.331626 +v 0.899016 0.612787 -0.015984 +v -0.556916 0.696451 -0.727762 +v -1.366082 0.643331 0.860208 +v -1.703072 1.876421 0.128359 +v 0.250180 0.115686 1.025581 +v -1.256550 -0.077663 0.741935 +v -1.183592 -0.331282 -0.208945 +v 0.204146 1.421923 -0.272873 +v -0.955854 1.143830 -0.392172 +v -1.716935 1.490338 -0.074950 +v -0.908799 0.429790 0.834956 +v -1.538323 2.078191 -0.001137 +v -1.049203 0.010663 -0.139785 +v 0.704164 0.029651 0.639615 +v -1.458649 2.333704 -0.873450 +v 0.004969 0.372179 1.129627 +v -1.596361 2.397274 -0.800259 +v -1.341676 2.560165 -1.199939 +v 0.419052 0.131983 0.957949 +v -0.202714 -0.310556 0.887632 +v -0.115134 -0.319651 -0.478370 +v -1.438816 2.126505 0.550931 +v -0.783046 -0.082089 -0.454203 +v -0.544058 1.502474 0.347908 +v -0.081829 0.661604 -0.753178 +v 0.430927 0.243353 -0.527954 +v 0.342511 0.329771 -0.591951 +v -0.628960 0.667973 0.841685 +v -1.765286 1.762439 0.830310 +v -1.593169 0.428926 0.368551 +v -0.205053 -0.314581 -0.368238 +v -0.975391 1.573702 -0.067315 +v -1.080629 1.779159 0.582179 +v -0.286128 0.227649 -0.731269 +v -1.308441 2.218786 -1.174929 +v 0.165365 0.561680 1.120613 +v 0.937182 0.428210 0.078141 +v -1.476833 1.282447 -0.142186 +v -0.433806 2.714358 -0.250615 +v -0.012886 1.014192 -0.456335 +v -0.565310 1.352252 0.629078 +v 1.234315 0.254007 0.176538 +v -0.373302 -0.098943 -0.562156 +v -1.040106 -0.320691 0.505978 +v 1.099452 -0.069639 0.291576 +v -1.193130 1.964145 0.734747 +v -1.645526 0.502254 0.046539 +v -0.540201 0.908352 -0.491425 +v 0.570428 0.829597 -0.420398 +v -0.576187 0.086198 0.723778 +v 0.231792 -0.311216 -0.263775 +v 0.732278 0.083778 -0.132954 +v 1.141719 0.423271 0.086702 +v 0.643705 1.365763 0.110519 +v -0.776583 2.232682 0.011187 +v -1.419341 2.034858 -0.926695 +v -0.640916 0.032052 -0.209933 +v -0.608278 -0.226524 0.718841 +v -0.865838 0.052745 0.790804 +v -1.564612 2.166453 -0.413847 +v -0.905788 -0.262625 -0.475934 +v -0.301342 2.214073 -0.248339 +v 0.005170 0.251326 1.138458 +v -0.573455 1.514294 0.097736 +v -1.020513 2.030979 0.251655 +v 0.137022 -0.214321 0.616751 +v 0.711103 0.237502 0.768200 +v 0.127122 -0.214120 -0.225272 +v -1.806110 1.916116 0.569366 +v -1.277569 0.937860 -0.357568 +v 0.176886 0.443417 -0.667591 +v -1.113210 -0.108873 0.906066 +v -1.011517 0.021087 0.698274 +v -1.677539 0.679915 -0.076252 +v -1.528158 0.911803 -0.264668 +v 1.087176 -0.001257 0.029786 +v -1.506097 0.355873 0.058008 +v -0.470996 2.196668 -0.034051 +v 0.358495 -0.274806 0.859045 +v -0.458061 0.429134 -0.766367 +v -0.511038 0.890224 0.898911 +v -0.668958 0.184383 -0.231929 +v -1.381548 1.578558 -0.185131 +v -0.439620 0.025928 0.886406 +v 0.398810 0.591481 1.038245 +v -0.494721 0.660059 1.094944 +v -0.838277 0.707370 0.848427 +v 0.742359 -0.206825 -0.021296 +v -0.141354 1.596759 0.500398 +v -1.091671 0.943650 -0.446658 +v -1.764174 1.241225 0.871106 +v -1.210576 0.307507 0.664774 +v -0.074613 -0.145208 -0.508665 +v 0.342399 0.906800 0.957341 +v 0.155755 0.982028 0.982627 +v -0.022443 0.245175 -0.693814 +v -0.238162 0.501004 -0.767078 +v -1.040084 1.855343 0.020213 +v 0.550221 -0.014567 -0.376243 +v -1.413349 0.920041 0.836655 +v -1.865590 1.292859 0.256558 +v 1.131363 0.447779 0.402172 +v -0.426494 2.265286 -0.326361 +v -0.124100 0.698862 1.151555 +v -0.698915 -0.324743 -0.621120 +v -1.573628 0.825916 0.736794 +v -0.967770 1.370468 0.610515 +v -0.991614 0.085067 0.318619 +v 0.268490 -0.042224 -0.521637 +v -1.428416 1.826239 -0.158769 +v 0.574640 0.188610 -0.399206 +v -1.777570 1.746828 0.528811 +v 0.123443 1.610684 -0.003350 +v -1.492246 1.359446 1.073136 +v 0.681170 -0.289601 0.228171 +v -1.405828 0.224329 0.285109 +v 0.493389 0.901867 0.927212 +v 0.019799 0.960872 1.059991 +v -1.617425 1.577763 1.071003 +v -0.800488 2.131342 0.115520 +v -0.542822 -0.227411 -0.359371 +v -0.615801 0.255370 -0.410325 +v -1.254180 2.352787 -0.934974 +v 0.814158 0.980996 -0.083952 +v -1.464121 -0.279284 0.155621 +v 0.369606 1.022056 -0.451923 +v 0.367000 0.888857 -0.480172 +v -0.810730 1.496291 -0.188235 +v -1.548075 2.587439 -1.020768 +v 1.017473 0.202480 -0.094421 +v 0.608082 0.484621 -0.418798 +v 0.250226 1.215063 0.788707 +v 0.494691 1.230701 -0.257862 +v -1.228326 1.399494 -0.187916 +v -0.183017 2.542578 -0.614849 +v 0.341084 1.428278 0.677115 +v -1.811080 1.649729 0.105226 +v -1.563434 2.122153 0.411995 +v -1.632931 1.191555 0.573243 +v 0.248308 0.323535 1.074198 +v 0.886995 0.829980 0.479410 +v -1.105279 1.280555 0.705321 +v -0.672986 -0.155706 0.986040 +v -0.565846 2.088861 -0.077108 +v -1.223974 2.175265 -0.454547 +v -1.864966 1.422518 0.849826 +v 0.474841 1.468868 -0.016535 +v 0.406291 -0.293454 -0.046927 +v -0.173724 2.371577 -0.487548 +v -1.543734 0.457225 -0.109284 +v 0.691319 1.116551 -0.214524 +v -0.262840 0.992519 -0.489889 +v -0.536299 1.112677 -0.457167 +v -1.777837 0.993397 0.440722 +v -1.486874 2.096864 -0.439157 +v -0.798384 0.990198 0.831084 +v -0.741115 2.246469 -0.276917 +v -1.524776 2.305617 -0.417891 +v -0.563827 -0.018832 -0.486544 +v -0.263735 0.858539 1.123588 +v -1.267050 1.841575 -0.136076 +v -0.954582 1.133881 0.803952 +v 0.490460 1.125336 0.795452 +v -0.126344 0.362811 -0.708438 +v -0.891146 1.598640 0.220774 +v -0.070018 0.479193 1.187786 +v -1.199467 0.496492 0.838375 +v -0.330814 0.231838 1.053564 +v -1.242676 1.743577 0.713482 +v 0.051788 1.279042 0.835333 +v 0.845702 -0.038298 0.654686 +v -1.794907 0.805548 0.393436 +v -1.162147 1.151608 -0.375568 +v -1.118677 2.115250 0.232827 +v -1.905789 1.514014 0.420251 +v 0.540473 0.218553 0.876277 +v 0.428260 0.093019 -0.543882 +v -1.548273 1.898256 0.895887 +v -0.711679 1.104614 -0.412796 +v 0.440645 1.512562 0.443111 +v 0.018526 0.028101 1.082396 +v -0.752117 1.416690 0.597296 +v -1.738766 0.651866 0.192682 +v -1.479460 0.663107 -0.305621 +v -1.543330 2.001675 -0.088456 +v -0.667335 0.730400 -0.503039 +v -0.243881 0.388424 1.100894 +v -1.084792 2.018689 -0.051406 +v -1.240990 1.939813 -0.059667 +v -0.475402 2.527752 -0.418690 +v -1.180211 0.612930 -0.416339 +v 0.746305 1.060499 0.609414 +v -1.725012 1.542070 0.984246 +v -0.542900 0.218017 -0.587722 +v -1.865483 1.396082 0.595172 +v -0.095870 0.157725 1.102226 +v -0.075278 -0.027518 -0.617613 +v -0.651977 1.446782 -0.145062 +v -1.779600 0.752411 0.135209 +v 0.624036 -0.241836 0.499252 +v -1.138333 1.017194 0.857497 +v -0.756902 0.644740 0.876448 +v -1.727128 0.608488 0.348094 +v -0.095799 1.219323 -0.430231 +v -1.142637 1.746613 -0.105149 +v -1.446628 -0.324673 -0.014624 +v 0.154657 -0.313672 0.815652 +v 0.207536 0.828623 -0.626683 +v -0.911998 0.294079 -0.290498 +v -0.516087 -0.228438 1.105902 +v -0.965888 -0.328775 -0.264877 +vn 0.3426 -0.8427 0.4152 +vn 0.7137 -0.6992 0.0409 +vn 0.5350 -0.7232 0.4367 +vn 0.7303 0.6516 0.2052 +vn 0.5477 0.6918 -0.4706 +vn 0.6836 0.4825 -0.5477 +vn -0.4756 0.1252 -0.8707 +vn -0.6597 -0.0478 -0.7500 +vn -0.0488 0.0570 -0.9972 +vn -0.6065 -0.5049 0.6142 +vn -0.7001 0.6814 0.2135 +vn -0.8887 -0.4572 -0.0357 +vn 0.1656 0.7894 -0.5912 +vn -0.0596 0.3574 -0.9320 +vn -0.0401 0.5918 -0.8051 +vn -0.7305 -0.6504 0.2082 +vn -0.8793 0.4170 -0.2301 +vn -0.6142 0.5555 0.5605 +vn -0.8180 -0.3774 -0.4341 +vn -0.8045 0.5373 -0.2530 +vn -0.4600 0.1647 -0.8725 +vn -0.2088 -0.2977 0.9316 +vn 0.3507 0.3619 0.8637 +vn -0.3733 -0.2847 0.8829 +vn 0.1900 -0.9807 -0.0454 +vn 0.1934 -0.9717 -0.1355 +vn 0.5358 -0.8219 0.1935 +vn 0.8484 0.3150 -0.4253 +vn 0.8942 0.4415 -0.0734 +vn 0.9704 0.1656 -0.1755 +vn 0.3394 -0.7741 -0.5345 +vn 0.3296 -0.9135 -0.2386 +vn 0.5452 -0.4688 -0.6950 +vn 0.7484 -0.4452 -0.4916 +vn 0.5837 -0.5469 -0.6002 +vn 0.4636 -0.1905 -0.8653 +vn -0.0436 0.3244 0.9449 +vn 0.0903 0.4742 0.8757 +vn 0.1590 0.2098 0.9647 +vn -0.9872 0.0597 0.1479 +vn -0.9746 0.2092 -0.0803 +vn -0.9894 0.1271 0.0708 +vn -0.2404 -0.5754 -0.7818 +vn -0.6521 -0.5784 -0.4901 +vn -0.5611 -0.1786 -0.8082 +vn -0.8618 0.4550 -0.2243 +vn -0.9283 0.2885 0.2345 +vn -0.4987 0.7761 0.3861 +vn -0.1982 0.4065 -0.8919 +vn -0.8221 -0.1058 -0.5594 +vn -0.5135 0.7182 -0.4696 +vn -0.0158 -0.9993 0.0328 +vn 0.0865 -0.9960 -0.0243 +vn 0.0820 0.9955 0.0470 +vn -0.6447 0.6741 0.3604 +vn -0.6107 0.7883 -0.0754 +vn 0.4471 -0.6123 0.6521 +vn 0.1743 -0.5517 0.8156 +vn -0.0442 -0.9981 0.0435 +vn 0.6526 0.0258 -0.7572 +vn -0.0156 -0.5983 -0.8011 +vn 0.3386 -0.9403 0.0341 +vn 0.7793 0.3531 0.5176 +vn 0.5718 0.1304 -0.8099 +vn 0.1365 0.8806 -0.4539 +vn 0.7225 -0.1222 0.6805 +vn 0.3230 0.1616 0.9325 +vn 0.5580 0.2046 0.8042 +vn 0.6760 0.6274 0.3866 +vn 0.0809 0.9344 0.3468 +vn -0.0609 0.9632 0.2619 +vn -0.4330 0.5375 0.7236 +vn -0.0034 -0.0647 0.9979 +vn -0.5024 -0.2688 0.8218 +vn 0.5920 0.2534 0.7651 +vn 0.1527 0.8770 0.4556 +vn 0.3349 0.4961 0.8011 +vn -0.8285 -0.4312 0.3573 +vn -0.9550 -0.2778 0.1040 +vn -0.9589 -0.2702 0.0861 +vn -0.8741 -0.4553 -0.1691 +vn -0.8419 -0.0508 -0.5372 +vn -0.9364 -0.2849 0.2046 +vn -0.8414 0.5178 0.1546 +vn -0.2568 0.9657 0.0396 +vn -0.2355 0.9010 -0.3644 +vn -0.4672 -0.8829 -0.0463 +vn -0.0120 -0.9515 -0.3075 +vn 0.4191 -0.7146 -0.5601 +vn -0.6720 0.7359 0.0828 +vn -0.5670 0.5585 -0.6055 +vn -0.6226 0.2899 -0.7269 +vn 0.4303 0.3229 0.8430 +vn 0.4516 0.0463 0.8910 +vn 0.6007 -0.0644 0.7969 +vn 0.3807 0.2200 -0.8981 +vn -0.7021 -0.7022 0.1183 +vn -0.7981 -0.5187 -0.3066 +vn -0.4663 -0.8499 0.2456 +vn -0.3695 -0.9141 -0.1672 +vn 0.0482 -0.9988 -0.0116 +vn 0.6007 -0.1522 -0.7848 +vn -0.3162 -0.0712 -0.9460 +vn -0.3613 -0.2318 -0.9032 +vn -0.4531 -0.4804 0.7509 +vn 0.2551 -0.2389 0.9369 +vn 0.2301 -0.7603 0.6074 +vn 0.5181 -0.0518 -0.8537 +vn 0.4267 -0.1009 -0.8987 +vn 0.6424 -0.0366 -0.7655 +vn -0.0187 -0.0736 0.9971 +vn -0.2920 -0.1236 0.9484 +vn -0.1841 0.1166 0.9760 +vn 0.7665 -0.4567 -0.4516 +vn 0.8583 -0.0856 0.5059 +vn -0.1776 0.8686 -0.4626 +vn -0.0644 0.6608 -0.7478 +vn 0.0033 0.5807 -0.8141 +vn -0.8960 -0.4405 -0.0560 +vn -0.8055 -0.3476 0.4800 +vn -0.9589 -0.1808 0.2187 +vn -0.3672 0.3360 0.8673 +vn -0.6780 0.0461 0.7336 +vn 0.1242 0.5300 -0.8389 +vn 0.2084 0.4047 -0.8904 +vn 0.2847 0.4773 -0.8314 +vn -0.7638 0.5383 -0.3561 +vn -0.7104 0.7034 -0.0237 +vn -0.0837 0.7561 0.6491 +vn -0.1505 0.7227 0.6745 +vn -0.0646 0.9641 0.2575 +vn 0.9679 -0.1526 0.1999 +vn 0.7733 0.2808 0.5684 +vn -0.1433 0.4000 0.9052 +vn 0.0362 0.7660 0.6419 +vn -0.5553 -0.4357 0.7084 +vn -0.2047 -0.4671 0.8602 +vn -0.3092 -0.7768 0.5486 +vn -0.2712 -0.6027 -0.7505 +vn 0.2025 0.8502 -0.4859 +vn -0.3543 0.1125 -0.9284 +vn -0.2344 0.1964 -0.9521 +vn 0.3349 0.8659 0.3716 +vn 0.6122 -0.0741 0.7873 +vn 0.4813 -0.8660 0.1358 +vn -0.1167 -0.2780 0.9535 +vn 0.5229 0.7700 0.3656 +vn 0.9820 0.0297 -0.1867 +vn -0.3797 -0.8331 -0.4022 +vn -0.6427 -0.7398 -0.1989 +vn 0.5701 -0.7730 -0.2783 +vn 0.6006 -0.2295 -0.7659 +vn -0.3601 -0.3949 -0.8452 +vn -0.8063 0.3939 0.4413 +vn 0.1591 -0.3946 -0.9050 +vn 0.1931 -0.5181 -0.8332 +vn 0.2447 -0.3310 -0.9113 +vn -0.0649 0.3823 0.9218 +vn 0.2160 -0.1580 -0.9635 +vn 0.1196 -0.1383 -0.9831 +vn -0.4369 -0.7529 -0.4922 +vn 0.2158 -0.1663 0.9622 +vn 0.7199 0.2118 0.6609 +vn 0.8415 -0.3006 0.4489 +vn 0.0297 -0.9858 -0.1651 +vn -0.4272 -0.8742 0.2306 +vn -0.0457 -0.9989 0.0084 +vn -0.9275 0.1671 0.3343 +vn -0.5768 -0.2795 -0.7676 +vn -0.0662 -0.3587 -0.9311 +vn -0.0862 -0.0453 -0.9952 +vn -0.2455 -0.9656 0.0858 +vn -0.1435 0.4377 0.8876 +vn -0.2133 -0.0325 0.9764 +vn 0.0171 0.1288 0.9915 +vn 0.7556 0.3396 0.5601 +vn -0.1675 -0.2187 -0.9613 +vn -0.1971 0.6597 0.7252 +vn 0.2758 0.6566 0.7020 +vn 0.7606 0.0756 -0.6448 +vn 0.6788 0.2354 0.6956 +vn 0.5872 0.0624 0.8070 +vn -0.5787 -0.6946 0.4274 +vn 0.4096 0.2410 0.8798 +vn -0.0968 -0.6580 -0.7468 +vn 0.4860 -0.8727 -0.0471 +vn -0.2362 -0.9717 -0.0074 +vn -0.4839 -0.5874 -0.6487 +vn -0.7779 -0.6278 -0.0267 +vn -0.8490 -0.4669 -0.2473 +vn -0.6610 0.6323 -0.4040 +vn -0.3863 0.7952 0.4673 +vn 0.1676 0.9857 0.0176 +vn -0.1522 0.9327 0.3269 +vn 0.2260 0.8994 0.3742 +vn -0.3571 -0.2155 0.9089 +vn -0.3032 0.2664 0.9149 +vn 0.7269 -0.4996 -0.4712 +vn -0.7586 -0.2943 -0.5814 +vn -0.8366 -0.1244 -0.5335 +vn -0.3514 -0.2035 -0.9138 +vn 0.0814 0.7005 0.7090 +vn -0.0177 0.6348 0.7724 +vn -0.3925 0.6990 0.5978 +vn 0.1502 -0.9820 0.1147 +vn -0.6315 -0.6765 -0.3789 +vn -0.6901 -0.0133 0.7236 +vn -0.3943 0.1783 0.9015 +vn 0.9095 -0.0128 -0.4156 +vn 0.8159 -0.1909 -0.5457 +vn 0.2008 -0.3908 0.8983 +vn 0.1394 -0.9672 0.2124 +vn 0.0355 -0.8495 0.5263 +vn 0.3063 -0.9519 -0.0003 +vn 0.4003 0.2449 -0.8830 +vn -0.4490 0.3074 -0.8390 +vn -0.9892 0.0242 -0.1444 +vn 0.6429 -0.4686 0.6059 +vn 0.1501 0.4162 -0.8968 +vn -0.0388 0.2941 0.9550 +vn 0.1367 0.3194 0.9377 +vn 0.3290 0.8786 -0.3461 +vn 0.3878 -0.1484 0.9097 +vn 0.3687 -0.3298 0.8691 +vn 0.5691 -0.1234 0.8130 +vn 0.0517 -0.9987 -0.0025 +vn 0.4554 0.0788 -0.8868 +vn 0.5274 0.2360 -0.8162 +vn -0.9325 0.0351 -0.3594 +vn -0.1049 0.3355 -0.9362 +vn 0.3955 0.5216 0.7559 +vn 0.2253 0.4448 0.8668 +vn 0.1537 0.6513 0.7431 +vn -0.9405 -0.2408 -0.2398 +vn -0.9691 -0.2378 -0.0648 +vn 0.3931 -0.3091 0.8660 +vn 0.0945 -0.4971 0.8625 +vn 0.3898 0.4327 0.8129 +vn -0.8203 -0.5367 -0.1973 +vn -0.0537 -0.1847 -0.9813 +vn 0.2318 -0.4039 -0.8849 +vn -0.5737 0.3267 -0.7511 +vn 0.7911 0.3964 0.4659 +vn 0.7264 0.1545 0.6697 +vn 0.9357 0.1886 0.2983 +vn 0.1855 -0.3253 0.9273 +vn 0.2335 0.2371 0.9430 +vn 0.5402 0.3444 0.7678 +vn 0.1300 -0.8009 -0.5845 +vn -0.2580 -0.0229 0.9659 +vn -0.0201 -0.1369 0.9904 +vn 0.3384 -0.9107 -0.2367 +vn 0.1301 -0.9909 -0.0350 +vn -0.2088 -0.5878 -0.7816 +vn -0.1166 0.1144 0.9866 +vn 0.4329 -0.0985 -0.8961 +vn 0.7391 -0.1754 -0.6504 +vn 0.9453 -0.2994 -0.1298 +vn 0.3819 -0.8117 -0.4420 +vn -0.3257 0.4494 -0.8319 +vn -0.3433 0.6871 -0.6403 +vn -0.6563 0.1388 -0.7416 +vn -0.0447 0.4931 -0.8688 +vn -0.1363 0.4069 -0.9032 +vn 0.3530 0.6798 -0.6428 +vn 0.4251 0.5568 -0.7136 +vn 0.5038 0.7953 -0.3372 +vn 0.6884 0.4254 -0.5875 +vn -0.8354 -0.2527 -0.4881 +vn -0.9552 -0.2164 -0.2021 +vn -0.8470 -0.0251 -0.5310 +vn -0.9223 -0.2065 0.3268 +vn 0.9513 0.2560 0.1715 +vn 0.6482 0.6769 0.3487 +vn -0.6243 -0.0827 -0.7768 +vn -0.1759 -0.7897 0.5877 +vn 0.0879 -0.1576 0.9836 +vn 0.2887 -0.0042 0.9574 +vn 0.7151 0.6878 -0.1242 +vn 0.5129 0.8340 0.2036 +vn 0.6570 0.6009 0.4552 +vn -0.6043 0.1230 -0.7872 +vn 0.0999 -0.0844 0.9914 +vn -0.0088 0.0029 1.0000 +vn -0.6472 0.2352 -0.7251 +vn -0.8818 0.2468 -0.4020 +vn -0.8340 0.1349 -0.5351 +vn 0.4974 0.7572 0.4235 +vn 0.6940 0.3859 -0.6078 +vn 0.1152 0.5752 -0.8099 +vn -0.0726 -0.8899 0.4504 +vn -0.1857 -0.2350 0.9541 +vn -0.6501 0.3010 0.6977 +vn -0.1643 0.6143 0.7718 +vn 0.3429 -0.3485 -0.8723 +vn -0.1503 0.0283 0.9882 +vn -0.7577 -0.6347 0.1519 +vn -0.2384 0.4328 0.8694 +vn -0.2574 -0.3028 -0.9176 +vn -0.4710 -0.2972 -0.8306 +vn -0.1915 0.9708 -0.1442 +vn 0.1182 0.9396 -0.3212 +vn -0.1266 0.8217 -0.5557 +vn -0.0632 0.8997 0.4320 +vn 0.5512 -0.1910 0.8122 +vn 0.7059 -0.2009 0.6792 +vn 0.3346 0.3881 -0.8587 +vn 0.3166 -0.0885 -0.9444 +vn 0.2364 0.5971 -0.7666 +vn -0.5864 0.1391 -0.7980 +vn -0.2323 0.2642 -0.9361 +vn -0.7547 0.5948 0.2768 +vn 0.5743 0.0921 0.8134 +vn -0.1443 0.5360 0.8318 +vn -0.1355 0.3853 -0.9128 +vn -0.0497 0.2330 -0.9712 +vn 0.2686 0.2618 0.9270 +vn -0.9220 -0.0919 0.3762 +vn -0.8305 0.1980 -0.5206 +vn -0.0735 0.6562 0.7510 +vn 0.2398 0.6940 -0.6789 +vn -0.9776 0.1451 -0.1528 +vn 0.6672 0.7439 0.0397 +vn 0.8073 0.3331 0.4872 +vn 0.0364 0.9885 -0.1469 +vn -0.6598 0.0449 0.7501 +vn -0.5067 -0.8582 0.0826 +vn -0.1436 -0.2482 0.9580 +vn -0.5641 -0.6250 0.5396 +vn 0.4818 0.1195 0.8681 +vn -0.0321 0.6014 -0.7983 +vn -0.0241 0.4170 -0.9086 +vn -0.1583 -0.1923 0.9685 +vn -0.0529 -0.1448 0.9880 +vn -0.5747 0.3958 -0.7163 +vn -0.9176 -0.2231 -0.3288 +vn -0.3776 -0.2547 0.8903 +vn -0.6125 0.2527 -0.7490 +vn 0.5612 -0.1604 0.8120 +vn 0.3301 0.6045 0.7250 +vn 0.9687 0.2229 0.1092 +vn -0.9776 -0.1581 0.1393 +vn 0.5660 -0.3290 0.7559 +vn -0.0994 -0.0641 0.9930 +vn 0.2604 0.0657 -0.9633 +vn 0.0081 -0.2936 -0.9559 +vn 0.1141 -0.7767 0.6195 +vn 0.1131 0.4315 -0.8950 +vn 0.1528 -0.1302 -0.9796 +vn 0.6308 -0.1048 0.7689 +f 339//1 184//2 71//3 +f 108//4 192//5 176//6 +f 177//7 149//8 222//9 +f 84//10 145//11 115//12 +f 267//13 148//14 83//15 +f 36//16 116//17 212//18 +f 154//19 268//20 157//21 +f 253//22 107//23 133//24 +f 82//25 63//26 44//27 +f 263//28 76//29 75//30 +f 227//31 287//32 5//33 +f 238//34 85//35 316//36 +f 295//37 301//38 69//39 +f 251//40 78//41 314//42 +f 139//43 289//44 323//45 +f 96//46 208//47 20//48 +f 56//49 200//50 162//51 +f 183//52 62//53 63//26 +f 45//54 297//55 151//56 +f 5//33 287//32 238//34 +f 218//57 110//58 346//59 +f 99//60 174//61 157//21 +f 183//52 63//26 64//62 +f 91//63 274//64 122//65 +f 118//66 281//67 3//68 +f 64//62 63//26 82//25 +f 93//69 313//70 161//71 +f 223//72 102//73 307//74 +f 287//32 85//35 238//34 +f 194//75 178//76 49//77 +f 278//78 334//79 37//80 +f 240//81 149//8 4//82 +f 247//83 98//84 116//17 +f 277//85 128//86 45//54 +f 205//87 346//59 40//88 +f 85//35 287//32 7//89 +f 114//90 43//91 65//92 +f 256//93 224//94 127//95 +f 192//5 269//96 176//6 +f 94//97 345//98 183//52 +f 123//99 112//100 13//101 +f 237//102 327//103 296//104 +f 48//105 101//106 74//107 +f 165//108 166//109 270//110 +f 341//111 167//112 25//113 +f 288//114 274//64 54//115 +f 12//116 80//117 81//118 +f 322//119 129//120 311//121 +f 220//122 167//112 225//123 +f 179//124 265//125 266//126 +f 94//97 95//127 18//128 +f 180//129 21//130 163//131 +f 88//132 204//133 93//69 +f 79//134 321//135 295//37 +f 295//37 321//135 301//38 +f 16//136 306//137 231//138 +f 345//98 65//92 146//139 +f 84//10 115//12 94//97 +f 62//53 345//98 146//139 +f 267//13 337//140 81//118 +f 209//141 148//14 229//142 +f 45//54 103//143 297//55 +f 119//144 107//23 253//22 +f 237//102 283//145 88//132 +f 150//146 306//137 16//136 +f 192//5 241//147 181//148 +f 289//44 58//149 216//150 +f 120//151 300//152 249//153 +f 20//48 208//47 168//154 +f 232//155 336//156 47//157 +f 301//38 340//158 69//39 +f 336//156 303//159 235//160 +f 350//161 62//53 146//139 +f 341//111 150//146 19//162 +f 181//148 28//163 132//164 +f 346//59 159//165 40//88 +f 112//100 207//166 190//167 +f 278//78 37//80 293//168 +f 66//169 50//170 109//171 +f 277//85 45//54 151//56 +f 205//87 30//172 346//59 +f 299//173 104//174 243//175 +f 204//133 313//70 93//69 +f 172//176 88//132 93//69 +f 209//141 330//177 323//45 +f 317//178 68//179 161//71 +f 75//30 35//180 263//28 +f 308//181 185//182 68//179 +f 74//107 133//24 230//183 +f 88//132 283//145 259//184 +f 242//185 201//186 283//145 +f 207//166 112//100 14//187 +f 34//188 139//43 50//170 +f 34//188 77//189 58//149 +f 30//172 123//99 13//101 +f 120//151 249//153 134//190 +f 57//191 27//192 49//77 +f 89//193 228//194 6//195 +f 201//186 242//185 288//114 +f 116//17 36//16 247//83 +f 223//72 26//196 189//197 +f 269//96 181//148 215//198 +f 333//199 130//200 219//201 +f 180//129 79//134 21//130 +f 257//202 9//203 32//204 +f 30//172 13//101 29//205 +f 27//192 57//191 128//86 +f 195//206 154//19 157//21 +f 48//105 245//207 239//208 +f 62//53 44//27 63//26 +f 208//47 78//41 251//40 +f 128//86 277//85 161//71 +f 74//107 118//66 119//144 +f 140//209 33//210 270//110 +f 310//211 71//3 132//164 +f 242//185 237//102 296//104 +f 44//27 15//212 260//213 +f 289//44 139//43 58//149 +f 227//31 184//2 254//214 +f 27//192 204//133 49//77 +f 249//153 344//215 222//9 +f 249//153 300//152 344//215 +f 146//139 131//216 350//161 +f 165//108 316//36 47//157 +f 268//20 154//19 156//217 +f 19//162 167//112 341//111 +f 48//105 239//208 101//106 +f 115//12 95//127 94//97 +f 218//57 29//205 92//218 +f 46//219 265//125 179//124 +f 330//177 50//170 139//43 +f 21//130 105//220 309//221 +f 339//1 92//218 29//205 +f 42//222 99//60 157//21 +f 279//223 144//224 111//225 +f 7//89 287//32 190//167 +f 136//226 13//101 112//100 +f 108//4 176//6 75//30 +f 270//110 106//227 188//228 +f 324//229 96//46 151//56 +f 318//230 229//142 148//14 +f 302//231 271//232 234//233 +f 194//75 204//133 259//184 +f 36//16 77//189 247//83 +f 156//217 294//234 199//235 +f 102//73 55//236 320//237 +f 321//135 246//238 301//38 +f 186//239 289//44 216//150 +f 232//155 182//240 336//156 +f 316//36 85//35 248//241 +f 196//242 162//51 131//216 +f 128//86 313//70 27//192 +f 331//243 137//244 280//245 +f 153//246 60//247 61//248 +f 7//89 160//249 232//155 +f 205//87 40//88 14//187 +f 198//250 19//162 150//146 +f 202//251 144//224 279//223 +f 82//25 197//252 73//253 +f 4//82 37//80 240//81 +f 32//204 225//123 299//173 +f 200//50 244//254 350//161 +f 340//158 124//255 69//39 +f 191//256 5//33 33//210 +f 167//112 26//196 225//123 +f 165//108 270//110 250//257 +f 174//61 138//258 87//259 +f 187//260 97//261 325//262 +f 87//259 120//151 195//206 +f 21//130 79//134 105//220 +f 121//263 292//264 80//117 +f 171//265 267//13 83//15 +f 7//89 190//167 160//249 +f 228//194 21//130 6//195 +f 272//266 286//267 290//268 +f 213//269 338//270 113//271 +f 101//106 118//66 74//107 +f 229//142 330//177 209//141 +f 55//236 110//58 218//57 +f 114//90 345//98 264//272 +f 42//222 262//273 99//60 +f 42//222 135//274 262//273 +f 77//189 152//275 43//91 +f 73//253 197//252 159//165 +f 84//10 183//52 11//276 +f 344//215 171//265 222//9 +f 155//277 279//223 175//278 +f 82//25 14//187 40//88 +f 193//279 319//280 23//281 +f 122//65 274//64 329//282 +f 38//283 150//146 226//284 +f 188//228 265//125 290//268 +f 72//285 276//286 143//287 +f 246//238 321//135 125//288 +f 328//289 24//290 128//86 +f 60//247 153//246 310//211 +f 57//191 178//76 329//282 +f 277//85 20//48 161//71 +f 274//64 242//185 329//282 +f 64//62 73//253 183//52 +f 170//291 15//212 160//249 +f 235//160 166//109 47//157 +f 18//128 95//127 116//17 +f 81//118 80//117 318//230 +f 349//292 282//293 11//276 +f 105//220 9//203 271//232 +f 211//294 212//18 145//11 +f 269//96 100//295 191//256 +f 146//139 65//92 131//216 +f 317//178 168//154 258//296 +f 169//297 186//239 216//150 +f 198//250 8//298 189//197 +f 278//78 230//183 334//79 +f 40//88 159//165 197//252 +f 50//170 348//299 34//188 +f 221//300 348//299 66//169 +f 163//131 321//135 180//129 +f 114//90 65//92 345//98 +f 2//301 252//302 126//303 +f 183//52 345//98 62//53 +f 297//55 41//304 156//217 +f 51//305 206//306 127//95 +f 104//174 225//123 307//74 +f 290//268 193//279 76//29 +f 179//124 266//126 347//307 +f 106//227 210//308 10//309 +f 209//141 214//310 312//311 +f 18//128 98//84 22//312 +f 294//234 156//217 154//19 +f 183//52 73//253 11//276 +f 339//1 71//3 310//211 +f 217//313 194//75 259//184 +f 23//281 331//243 76//29 +f 105//220 79//134 31//314 +f 316//36 248//241 47//157 +f 29//205 254//214 339//1 +f 273//315 171//265 83//15 +f 318//230 292//264 325//262 +f 54//115 201//186 288//114 +f 302//231 137//244 331//243 +f 62//53 15//212 44//27 +f 185//182 308//181 93//69 +f 184//2 132//164 71//3 +f 206//306 153//246 61//248 +f 200//50 56//49 244//254 +f 108//4 241//147 192//5 +f 166//109 210//308 106//227 +f 160//249 244//254 232//155 +f 10//309 67//316 97//261 +f 201//186 217//313 283//145 +f 201//186 54//115 217//313 +f 233//317 256//93 234//233 +f 195//206 294//234 154//19 +f 318//230 109//171 229//142 +f 26//196 198//250 189//197 +f 224//94 279//223 111//225 +f 94//97 18//128 114//90 +f 48//105 230//183 278//78 +f 285//318 334//79 230//183 +f 43//91 114//90 22//312 +f 213//269 113//271 214//310 +f 177//7 17//319 4//82 +f 105//220 31//314 9//203 +f 70//320 102//73 223//72 +f 64//62 82//25 73//253 +f 314//42 276//286 240//81 +f 176//6 191//256 33//210 +f 176//6 269//96 191//256 +f 126//303 252//302 147//321 +f 270//110 33//210 250//257 +f 15//212 350//161 244//254 +f 227//31 215//198 184//2 +f 273//315 83//15 312//311 +f 4//82 52//322 37//80 +f 120//151 134//190 195//206 +f 174//61 87//259 195//206 +f 318//230 80//117 292//264 +f 61//248 241//147 108//4 +f 304//323 53//324 125//288 +f 66//169 325//262 130//200 +f 346//59 110//58 159//165 +f 61//248 60//247 241//147 +f 125//288 321//135 163//131 +f 288//114 242//185 274//64 +f 2//301 12//116 203//325 +f 38//283 69//39 124//255 +f 330//177 109//171 50//170 +f 99//60 138//258 174//61 +f 332//326 285//318 230//183 +f 53//324 88//132 172//176 +f 169//297 216//150 255//327 +f 13//101 287//32 254//214 +f 287//32 227//31 254//214 +f 335//328 102//73 320//237 +f 304//323 88//132 53//324 +f 15//212 62//53 350//161 +f 231//138 306//137 59//329 +f 234//233 86//330 233//317 +f 163//131 203//325 304//323 +f 240//81 334//79 314//42 +f 208//47 96//46 78//41 +f 290//268 263//28 188//228 +f 193//279 23//281 76//29 +f 157//21 268//20 42//222 +f 13//101 136//226 190//167 +f 291//331 121//263 343//332 +f 324//229 143//287 96//46 +f 134//190 294//234 195//206 +f 326//333 155//277 305//334 +f 55//236 144//224 320//237 +f 333//199 182//240 298//335 +f 188//228 106//227 347//307 +f 281//67 340//158 301//38 +f 102//73 335//328 307//74 +f 196//242 261//336 298//335 +f 8//298 198//250 212//18 +f 129//120 142//337 245//207 +f 271//232 9//203 234//233 +f 168//154 317//178 20//48 +f 36//16 59//329 255//327 +f 153//246 206//306 339//1 +f 82//25 40//88 197//252 +f 116//17 145//11 212//18 +f 235//160 47//157 336//156 +f 67//316 219//201 141//338 +f 299//173 243//175 257//202 +f 315//339 206//306 51//305 +f 244//254 160//249 15//212 +f 121//263 291//331 292//264 +f 78//41 276//286 314//42 +f 55//236 92//218 206//306 +f 167//112 19//162 26//196 +f 148//14 81//118 318//230 +f 111//225 51//305 127//95 +f 31//314 32//204 9//203 +f 225//123 104//174 299//173 +f 139//43 34//188 58//149 +f 3//68 53//324 172//176 +f 309//221 271//232 275//340 +f 262//273 103//143 284//341 +f 271//232 23//281 275//340 +f 95//127 145//11 116//17 +f 145//11 95//127 115//12 +f 313//70 204//133 27//192 +f 101//106 340//158 281//67 +f 342//342 129//120 322//119 +f 260//213 82//25 44//27 +f 249//153 72//285 324//229 +f 45//54 128//86 24//290 +f 203//325 267//13 171//265 +f 261//336 130//200 333//199 +f 158//343 144//224 55//236 +f 252//302 2//301 89//193 +f 113//271 17//319 214//310 +f 74//107 119//144 253//22 +f 93//69 308//181 172//176 +f 219//201 182//240 333//199 +f 190//167 207//166 170//291 +f 322//119 338//270 213//269 +f 168//154 251//40 334//79 +f 317//178 107//23 68//179 +f 258//296 332//326 133//24 +f 316//36 250//257 238//34 +f 133//24 332//326 230//183 +f 4//82 149//8 177//7 +f 29//205 218//57 346//59 +f 163//131 228//194 2//301 +f 307//74 26//196 223//72 +f 328//289 344//215 300//152 +f 243//175 305//334 175//278 +f 306//137 124//255 142//337 +f 293//168 311//121 129//120 +f 206//306 315//339 55//236 +f 72//285 249//153 149//8 +f 327//103 237//102 328//289 +f 276//286 78//41 143//287 +f 322//119 169//297 342//342 +f 324//229 294//234 134//190 +f 324//229 199//235 294//234 +f 220//122 225//123 32//204 +f 289//44 213//269 323//45 +f 341//111 25//113 226//284 +f 296//104 327//103 57//191 +f 329//282 242//185 296//104 +f 113//271 338//270 52//322 +f 80//117 126//303 121//263 +f 39//344 84//10 11//276 +f 170//291 160//249 190//167 +f 45//54 284//341 103//143 +f 156//217 199//235 297//55 +f 10//309 210//308 164//345 +f 129//120 245//207 293//168 +f 296//104 57//191 329//282 +f 304//323 171//265 88//132 +f 243//175 104//174 326//333 +f 337//140 12//116 81//118 +f 277//85 96//46 20//48 +f 100//295 215//198 227//31 +f 207//166 260//213 170//291 +f 54//115 194//75 217//313 +f 113//271 52//322 17//319 +f 106//227 10//309 347//307 +f 25//113 79//134 295//37 +f 226//284 25//113 295//37 +f 173//346 303//159 336//156 +f 75//30 140//209 35//180 +f 220//122 32//204 31//314 +f 138//258 99//60 262//273 +f 3//68 308//181 118//66 +f 268//20 156//217 41//304 +f 179//124 10//309 97//261 +f 174//61 195//206 157//21 +f 189//197 70//320 223//72 +f 189//197 282//293 70//320 +f 315//339 158//343 55//236 +f 215//198 181//148 184//2 +f 18//128 116//17 98//84 +f 233//317 86//330 175//278 +f 127//95 224//94 111//225 +f 324//229 134//190 249//153 +f 175//278 224//94 233//317 +f 257//202 234//233 9//203 +f 91//63 54//115 274//64 +f 335//328 320//237 144//224 +f 131//216 348//299 221//300 +f 118//66 101//106 281//67 +f 305//334 155//277 175//278 +f 228//194 89//193 2//301 +f 214//310 17//319 177//7 +f 76//29 280//245 75//30 +f 159//165 90//347 73//253 +f 276//286 149//8 240//81 +f 127//95 137//244 256//93 +f 155//277 202//251 279//223 +f 97//261 187//260 291//331 +f 8//298 212//18 211//294 +f 263//28 290//268 76//29 +f 4//82 17//319 52//322 +f 110//58 349//292 90//347 +f 330//177 229//142 109//171 +f 109//171 325//262 66//169 +f 178//76 57//191 49//77 +f 226//284 150//146 341//111 +f 151//56 96//46 277//85 +f 245//207 278//78 293//168 +f 13//101 190//167 287//32 +f 86//330 257//202 243//175 +f 198//250 26//196 19//162 +f 94//97 114//90 264//272 +f 65//92 43//91 131//216 +f 173//346 182//240 219//201 +f 254//214 184//2 339//1 +f 234//233 257//202 86//330 +f 302//231 23//281 271//232 +f 18//128 22//312 114//90 +f 72//285 149//8 276//286 +f 250//257 33//210 5//33 +f 182//240 56//49 298//335 +f 248//241 232//155 47//157 +f 228//194 163//131 21//130 +f 298//335 162//51 196//242 +f 108//4 280//245 61//248 +f 207//166 14//187 260//213 +f 46//219 179//124 343//332 +f 176//6 33//210 140//209 +f 147//321 272//266 1//348 +f 327//103 128//86 57//191 +f 328//289 300//152 284//341 +f 102//73 70//320 110//58 +f 103//143 41//304 297//55 +f 286//267 89//193 319//280 +f 70//320 349//292 110//58 +f 317//178 161//71 20//48 +f 325//262 97//261 141//338 +f 284//341 300//152 120//151 +f 316//36 165//108 250//257 +f 272//266 265//125 46//219 +f 164//345 67//316 10//309 +f 236//349 303//159 173//346 +f 220//122 25//113 167//112 +f 66//169 261//336 221//300 +f 42//222 41//304 135//274 +f 295//37 69//39 226//284 +f 254//214 29//205 13//101 +f 215//198 100//295 269//96 +f 346//59 30//172 29//205 +f 222//9 171//265 273//315 +f 347//307 266//126 188//228 +f 118//66 308//181 119//144 +f 308//181 107//23 119//144 +f 77//189 255//327 58//149 +f 11//276 8//298 211//294 +f 192//5 181//148 269//96 +f 303//159 236//349 164//345 +f 74//107 230//183 48//105 +f 221//300 261//336 196//242 +f 206//306 137//244 127//95 +f 166//109 106//227 270//110 +f 47//157 166//109 165//108 +f 216//150 58//149 255//327 +f 283//145 237//102 242//185 +f 343//332 179//124 291//331 +f 37//80 52//322 293//168 +f 323//45 330//177 139//43 +f 169//297 59//329 342//342 +f 322//119 186//239 169//297 +f 92//218 339//1 206//306 +f 163//131 2//301 203//325 +f 84//10 39//344 211//294 +f 293//168 52//322 311//121 +f 82//25 260//213 14//187 +f 282//293 189//197 8//298 +f 152//275 34//188 348//299 +f 43//91 152//275 131//216 +f 336//156 182//240 173//346 +f 282//293 349//292 70//320 +f 343//332 126//303 1//348 +f 155//277 326//333 335//328 +f 246//238 281//67 301//38 +f 112//100 190//167 136//226 +f 85//35 7//89 248//241 +f 194//75 54//115 91//63 +f 265//125 188//228 266//126 +f 55//236 218//57 92//218 +f 141//338 130//200 325//262 +f 348//299 50//170 66//169 +f 335//328 144//224 202//251 +f 298//335 56//49 162//51 +f 2//301 126//303 12//116 +f 289//44 186//239 213//269 +f 322//119 213//269 186//239 +f 321//135 79//134 180//129 +f 319//280 275//340 23//281 +f 231//138 59//329 36//16 +f 79//134 25//113 220//122 +f 332//326 258//296 168//154 +f 307//74 225//123 26//196 +f 131//216 162//51 200//50 +f 275//340 6//195 21//130 +f 5//33 100//295 227//31 +f 179//124 97//261 291//331 +f 335//328 326//333 307//74 +f 209//141 323//45 214//310 +f 183//52 84//10 94//97 +f 258//296 107//23 317//178 +f 286//267 147//321 252//302 +f 204//133 194//75 49//77 +f 312//311 177//7 273//315 +f 11//276 73//253 90//347 +f 270//110 35//180 140//209 +f 122//65 329//282 178//76 +f 91//63 122//65 178//76 +f 77//189 43//91 22//312 +f 284//341 120//151 138//258 +f 87//259 138//258 120//151 +f 270//110 188//228 35//180 +f 177//7 312//311 214//310 +f 226//284 69//39 38//283 +f 338//270 322//119 311//121 +f 298//335 261//336 333//199 +f 292//264 291//331 187//260 +f 211//294 145//11 84//10 +f 194//75 117//350 178//76 +f 148//14 267//13 81//118 +f 267//13 203//325 337//140 +f 150//146 38//283 306//137 +f 249//153 222//9 149//8 +f 66//169 130//200 261//336 +f 285//318 168//154 334//79 +f 327//103 328//289 128//86 +f 235//160 303//159 166//109 +f 248//241 7//89 232//155 +f 307//74 326//333 104//174 +f 213//269 214//310 323//45 +f 15//212 170//291 260//213 +f 318//230 325//262 109//171 +f 121//263 126//303 343//332 +f 262//273 284//341 138//258 +f 246//238 125//288 53//324 +f 83//15 148//14 312//311 +f 172//176 308//181 3//68 +f 176//6 140//209 75//30 +f 14//187 112//100 123//99 +f 152//275 348//299 131//216 +f 36//16 255//327 77//189 +f 143//287 324//229 72//285 +f 135//274 41//304 103//143 +f 175//278 279//223 224//94 +f 24//290 328//289 45//54 +f 97//261 67//316 141//338 +f 217//313 259//184 283//145 +f 16//136 231//138 36//16 +f 210//308 166//109 303//159 +f 164//345 236//349 67//316 +f 80//117 12//116 126//303 +f 131//216 200//50 350//161 +f 143//287 78//41 96//46 +f 171//265 344//215 237//102 +f 141//338 219//201 130//200 +f 272//266 147//321 286//267 +f 340//158 101//106 239//208 +f 196//242 131//216 221//300 +f 234//233 256//93 302//231 +f 144//224 158//343 111//225 +f 30//172 205//87 123//99 +f 252//302 89//193 286//267 +f 45//54 328//289 284//341 +f 126//303 147//321 1//348 +f 3//68 246//238 53//324 +f 3//68 281//67 246//238 +f 110//58 55//236 102//73 +f 326//333 305//334 243//175 +f 331//243 23//281 302//231 +f 237//102 88//132 171//265 +f 233//317 224//94 256//93 +f 268//20 41//304 42//222 +f 5//33 238//34 250//257 +f 6//195 319//280 89//193 +f 32//204 299//173 257//202 +f 128//86 161//71 313//70 +f 152//275 77//189 34//188 +f 239//208 124//255 340//158 +f 159//165 110//58 90//347 +f 203//325 12//116 337//140 +f 331//243 280//245 76//29 +f 91//63 178//76 117//350 +f 11//276 90//347 349//292 +f 222//9 273//315 177//7 +f 204//133 88//132 259//184 +f 107//23 308//181 68//179 +f 280//245 137//244 206//306 +f 93//69 161//71 68//179 +f 38//283 124//255 306//137 +f 343//332 1//348 46//219 +f 339//1 310//211 153//246 +f 309//221 275//340 21//130 +f 117//350 194//75 91//63 +f 163//131 304//323 125//288 +f 314//42 334//79 251//40 +f 188//228 263//28 35//180 +f 193//279 290//268 286//267 +f 37//80 334//79 240//81 +f 206//306 61//248 280//245 +f 332//326 168//154 285//318 +f 94//97 264//272 345//98 +f 132//164 184//2 181//148 +f 39//344 11//276 211//294 +f 56//49 232//155 244//254 +f 56//49 182//240 232//155 +f 14//187 123//99 205//87 +f 11//276 282//293 8//298 +f 46//219 1//348 272//266 +f 309//221 105//220 271//232 +f 129//120 342//342 59//329 +f 191//256 100//295 5//33 +f 93//69 68//179 185//182 +f 325//262 292//264 187//260 +f 304//323 203//325 171//265 +f 148//14 209//141 312//311 +f 175//278 86//330 243//175 +f 262//273 135//274 103//143 +f 36//16 212//18 16//136 +f 198//250 150//146 16//136 +f 236//349 173//346 219//201 +f 60//247 28//163 241//147 +f 303//159 164//345 210//308 +f 98//84 77//189 22//312 +f 107//23 258//296 133//24 +f 67//316 236//349 219//201 +f 347//307 10//309 179//124 +f 324//229 297//55 199//235 +f 6//195 275//340 319//280 +f 256//93 137//244 302//231 +f 132//164 60//247 310//211 +f 278//78 245//207 48//105 +f 77//189 98//84 247//83 +f 272//266 290//268 265//125 +f 108//4 75//30 280//245 +f 111//225 158//343 51//305 +f 133//24 74//107 253//22 +f 212//18 198//250 16//136 +f 306//137 142//337 59//329 +f 208//47 251//40 168//154 +f 124//255 239//208 142//337 +f 245//207 142//337 239//208 +f 142//337 129//120 59//329 +f 59//329 169//297 255//327 +f 193//279 286//267 319//280 +f 51//305 158//343 315//339 +f 60//247 132//164 28//163 +f 181//148 241//147 28//163 +f 220//122 31//314 79//134 +f 335//328 202//251 155//277 +f 324//229 151//56 297//55 +f 338//270 311//121 52//322 +f 344//215 328//289 237//102 diff --git a/assets/tests/offset_cylinder.obj b/assets/tests/offset_cylinder.obj new file mode 100644 index 00000000..88d7831e --- /dev/null +++ b/assets/tests/offset_cylinder.obj @@ -0,0 +1,191 @@ +# Author: Camilo Talero +# License: CC0 +v -0.010000 -1.010000 -1.780000 +v -0.010000 0.990000 -1.780000 +v 0.185090 -1.010000 -1.760785 +v 0.185090 0.990000 -1.760785 +v 0.372683 -1.010000 -1.703879 +v 0.372683 0.990000 -1.703879 +v 0.545570 -1.010000 -1.611470 +v 0.545570 0.990000 -1.611470 +v 0.697107 -1.010000 -1.487107 +v 0.697107 0.990000 -1.487107 +v 0.821470 -1.010000 -1.335570 +v 0.821470 0.990000 -1.335570 +v 0.913880 -1.010000 -1.162683 +v 0.913880 0.990000 -1.162683 +v 0.970785 -1.010000 -0.975090 +v 0.970785 0.990000 -0.975090 +v 0.990000 -1.010000 -0.780000 +v 0.990000 0.990000 -0.780000 +v 0.970785 -1.010000 -0.584910 +v 0.970785 0.990000 -0.584910 +v 0.913880 -1.010000 -0.397317 +v 0.913880 0.990000 -0.397317 +v 0.821470 -1.010000 -0.224430 +v 0.821470 0.990000 -0.224430 +v 0.697107 -1.010000 -0.072893 +v 0.697107 0.990000 -0.072893 +v 0.545570 -1.010000 0.051470 +v 0.545570 0.990000 0.051470 +v 0.372683 -1.010000 0.143880 +v 0.372683 0.990000 0.143880 +v 0.185090 -1.010000 0.200785 +v 0.185090 0.990000 0.200785 +v -0.010000 -1.010000 0.220000 +v -0.010000 0.990000 0.220000 +v -0.205090 -1.010000 0.200785 +v -0.205090 0.990000 0.200785 +v -0.392683 -1.010000 0.143880 +v -0.392683 0.990000 0.143880 +v -0.565570 -1.010000 0.051470 +v -0.565570 0.990000 0.051470 +v -0.717107 -1.010000 -0.072893 +v -0.717107 0.990000 -0.072893 +v -0.841469 -1.010000 -0.224430 +v -0.841469 0.990000 -0.224430 +v -0.933879 -1.010000 -0.397316 +v -0.933879 0.990000 -0.397316 +v -0.990785 -1.010000 -0.584910 +v -0.990785 0.990000 -0.584910 +v -1.010000 -1.010000 -0.780000 +v -1.010000 0.990000 -0.780000 +v -0.990785 -1.010000 -0.975090 +v -0.990785 0.990000 -0.975090 +v -0.933879 -1.010000 -1.162684 +v -0.933879 0.990000 -1.162684 +v -0.841470 -1.010000 -1.335570 +v -0.841470 0.990000 -1.335570 +v -0.717107 -1.010000 -1.487107 +v -0.717107 0.990000 -1.487107 +v -0.565570 -1.010000 -1.611470 +v -0.565570 0.990000 -1.611470 +v -0.392683 -1.010000 -1.703880 +v -0.392683 0.990000 -1.703880 +v -0.205090 -1.010000 -1.760785 +v -0.205090 0.990000 -1.760785 +s off +f 2 3 1 +f 4 5 3 +f 6 7 5 +f 8 9 7 +f 10 11 9 +f 12 13 11 +f 14 15 13 +f 16 17 15 +f 18 19 17 +f 20 21 19 +f 22 23 21 +f 24 25 23 +f 26 27 25 +f 28 29 27 +f 30 31 29 +f 32 33 31 +f 34 35 33 +f 36 37 35 +f 38 39 37 +f 40 41 39 +f 42 43 41 +f 44 45 43 +f 46 47 45 +f 48 49 47 +f 50 51 49 +f 52 53 51 +f 54 55 53 +f 56 57 55 +f 58 59 57 +f 60 61 59 +f 54 38 22 +f 62 63 61 +f 64 1 63 +f 15 31 47 +f 2 4 3 +f 4 6 5 +f 6 8 7 +f 8 10 9 +f 10 12 11 +f 12 14 13 +f 14 16 15 +f 16 18 17 +f 18 20 19 +f 20 22 21 +f 22 24 23 +f 24 26 25 +f 26 28 27 +f 28 30 29 +f 30 32 31 +f 32 34 33 +f 34 36 35 +f 36 38 37 +f 38 40 39 +f 40 42 41 +f 42 44 43 +f 44 46 45 +f 46 48 47 +f 48 50 49 +f 50 52 51 +f 52 54 53 +f 54 56 55 +f 56 58 57 +f 58 60 59 +f 60 62 61 +f 6 4 62 +f 4 2 62 +f 2 64 62 +f 62 60 58 +f 58 56 62 +f 56 54 62 +f 54 52 50 +f 50 48 46 +f 46 44 42 +f 42 40 38 +f 38 36 34 +f 34 32 30 +f 30 28 26 +f 26 24 22 +f 22 20 18 +f 18 16 14 +f 14 12 10 +f 10 8 6 +f 54 50 38 +f 50 46 38 +f 46 42 38 +f 38 34 30 +f 30 26 38 +f 26 22 38 +f 22 18 6 +f 18 14 6 +f 14 10 6 +f 6 62 54 +f 6 54 22 +f 62 64 63 +f 64 2 1 +f 63 1 3 +f 3 5 7 +f 7 9 11 +f 11 13 15 +f 15 17 19 +f 19 21 23 +f 23 25 27 +f 27 29 31 +f 31 33 35 +f 35 37 39 +f 39 41 43 +f 43 45 47 +f 47 49 51 +f 51 53 47 +f 53 55 47 +f 55 57 63 +f 57 59 63 +f 59 61 63 +f 63 3 15 +f 3 7 15 +f 7 11 15 +f 15 19 23 +f 23 27 15 +f 27 31 15 +f 31 35 47 +f 35 39 47 +f 39 43 47 +f 47 55 63 +f 63 15 47 diff --git a/assets/tests/poly_cylinder.obj b/assets/tests/poly_cylinder.obj new file mode 100644 index 00000000..4937a5c7 --- /dev/null +++ b/assets/tests/poly_cylinder.obj @@ -0,0 +1,141 @@ +# Author: Camilo Talero +# License: CC0 +v -2.274960 1.634560 -0.654264 +v -2.230466 1.592349 -0.700534 +v -2.173552 1.441081 -0.682305 +v -2.182022 1.422727 -0.652495 +v -2.246091 1.550951 -0.639542 +v -2.230519 1.501727 -0.628318 +v -2.234529 1.544985 -0.655547 +v -2.201307 1.484995 -0.667436 +v -2.227519 1.484150 -0.619714 +v -2.262122 1.436412 -0.519611 +v -2.217010 1.449139 -0.610715 +v -2.274961 1.468401 -0.522026 +v -2.309084 1.548492 -0.524516 +v -2.268742 1.480160 -0.542545 +v -2.248044 1.503023 -0.597894 +v -2.283536 1.531561 -0.556898 +v -2.265529 1.557428 -0.609807 +v -2.301338 1.565738 -0.552144 +v -2.310203 1.613862 -0.574532 +v -2.296426 1.575467 -0.568704 +v -2.295412 1.616367 -0.603075 +v -2.270176 1.600751 -0.635943 +v -2.270445 1.576701 -0.616320 +v -2.286738 1.585906 -0.594400 +v -2.284624 1.597595 -0.607498 +v -2.291954 1.595247 -0.592472 +v -2.286412 1.570707 -0.582891 +v -2.256446 1.568496 -0.634918 +v 0.196502 2.730363 0.722639 +v 0.240997 2.688151 0.676368 +v 0.225372 2.646754 0.737360 +v 0.240944 2.597529 0.748584 +v 0.236933 2.640788 0.721355 +v 0.270155 2.580798 0.709466 +v 0.243943 2.579952 0.757189 +v 0.297910 2.536883 0.694597 +v 0.289440 2.518529 0.724407 +v 0.209340 2.532215 0.857292 +v 0.254453 2.544942 0.766188 +v 0.196502 2.564204 0.854877 +v 0.162379 2.644295 0.852386 +v 0.202720 2.575963 0.834357 +v 0.223419 2.598826 0.779009 +v 0.187926 2.627364 0.820004 +v 0.205934 2.653231 0.767095 +v 0.170124 2.661540 0.824758 +v 0.161259 2.709665 0.802371 +v 0.175036 2.671269 0.808199 +v 0.176051 2.712170 0.773827 +v 0.201286 2.696553 0.740959 +v 0.201017 2.672503 0.760582 +v 0.184724 2.681708 0.782502 +v 0.186838 2.693398 0.769405 +v 0.179508 2.691049 0.784430 +v 0.185050 2.666510 0.794011 +v 0.215016 2.664299 0.741984 +s off +f 4 38 10 +f 45 56 55 +f 23 52 24 +f 10 39 11 +f 24 53 25 +f 11 40 12 +f 25 54 26 +f 12 41 13 +f 1 30 2 +f 26 55 27 +f 13 42 14 +f 27 56 28 +f 14 43 15 +f 28 29 1 +f 15 44 16 +f 2 31 5 +f 16 45 17 +f 5 32 6 +f 17 46 18 +f 6 33 7 +f 18 47 19 +f 7 34 8 +f 19 48 20 +f 8 35 9 +f 20 49 21 +f 9 36 3 +f 21 50 22 +f 3 37 4 +f 22 51 23 +f 4 37 38 +f 31 30 56 +f 30 29 56 +f 34 33 32 +f 32 31 43 +f 31 56 45 +f 35 34 32 +f 37 36 35 +f 39 38 37 +f 42 41 40 +f 45 44 43 +f 43 42 39 +f 42 40 39 +f 39 37 35 +f 45 43 31 +f 43 39 35 +f 35 32 43 +f 48 47 46 +f 51 50 53 +f 50 49 53 +f 53 52 51 +f 48 46 55 +f 46 45 55 +f 54 53 49 +f 54 49 48 +f 55 54 48 +f 23 51 52 +f 10 38 39 +f 24 52 53 +f 11 39 40 +f 25 53 54 +f 12 40 41 +f 1 29 30 +f 26 54 55 +f 13 41 42 +f 27 55 56 +f 14 42 43 +f 28 56 29 +f 15 43 44 +f 2 30 31 +f 16 44 45 +f 5 31 32 +f 17 45 46 +f 6 32 33 +f 18 46 47 +f 7 33 34 +f 19 47 48 +f 8 34 35 +f 20 48 49 +f 9 35 36 +f 21 49 50 +f 3 36 37 +f 22 50 51 diff --git a/assets/tests/stairs.obj b/assets/tests/stairs.obj new file mode 100644 index 00000000..2cdad207 --- /dev/null +++ b/assets/tests/stairs.obj @@ -0,0 +1,71 @@ +# Author: Camilo Talero +# License: CC0 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 4.915915 +v -1.000000 1.000000 4.915915 +v 1.000000 1.000000 4.915915 +v 1.000000 -1.000000 4.915915 +v -1.000000 4.391501 1.000000 +v -1.000000 4.391501 -1.000000 +v 1.000000 4.391501 -1.000000 +v 1.000000 4.391501 1.000000 +v -1.000000 1.000000 -7.119346 +v 1.000000 1.000000 -7.119346 +v -1.000000 4.391501 -7.119346 +v 1.000000 4.391501 -7.119346 +v -1.000000 11.844163 -1.000000 +v 1.000000 11.844163 -1.000000 +v -1.000000 11.844163 -7.119346 +v 1.000000 11.844163 -7.119346 +s off +f 2 3 1 +f 4 7 3 +f 8 5 7 +f 2 11 6 +f 7 1 3 +f 2 14 4 +f 11 9 12 +f 6 12 5 +f 1 10 2 +f 5 9 1 +f 14 16 15 +f 6 13 2 +f 4 18 8 +f 8 16 6 +f 17 20 18 +f 8 20 15 +f 14 17 4 +f 14 23 19 +f 22 23 21 +f 19 24 20 +f 20 22 15 +f 15 21 14 +f 2 4 3 +f 4 8 7 +f 8 6 5 +f 2 10 11 +f 7 5 1 +f 2 13 14 +f 11 10 9 +f 6 11 12 +f 1 9 10 +f 5 12 9 +f 14 13 16 +f 6 16 13 +f 4 17 18 +f 8 15 16 +f 17 19 20 +f 8 18 20 +f 14 19 17 +f 14 21 23 +f 22 24 23 +f 19 23 24 +f 20 24 22 +f 15 22 21 diff --git a/crates/parry3d-f64/Cargo.toml b/crates/parry3d-f64/Cargo.toml index 4ef42957..edb6dd94 100644 --- a/crates/parry3d-f64/Cargo.toml +++ b/crates/parry3d-f64/Cargo.toml @@ -29,6 +29,7 @@ simd-stable = ["simba/wide", "simd-is-enabled"] simd-nightly = ["simba/portable_simd", "simd-is-enabled"] enhanced-determinism = ["simba/libm_force", "indexmap"] parallel = ["rayon"] +wavefront = ["obj"] # Do not enable this feature directly. It is automatically # enabled with the "simd-stable" or "simd-nightly" feature. @@ -59,6 +60,9 @@ cust_core = { version = "0.1", optional = true } spade = { version = "2", optional = true } # Make this optional? rayon = { version = "1", optional = true } bytemuck = { version = "1", features = ["derive"], optional = true } +rstar = "0.12.0" +obj = { version = "0.10.2", optional = true } + log = "0.4" ordered-float = { version = "4", default-features = false } thiserror = { version = "1", optional = true } diff --git a/crates/parry3d/Cargo.toml b/crates/parry3d/Cargo.toml index 54402e1e..5603c710 100644 --- a/crates/parry3d/Cargo.toml +++ b/crates/parry3d/Cargo.toml @@ -63,6 +63,7 @@ bytemuck = { version = "1", features = ["derive"], optional = true } log = "0.4" ordered-float = { version = "4", default-features = false } thiserror = { version = "1", optional = true } +rstar = "0.12.0" [dev-dependencies] oorandom = "11" diff --git a/src/query/point/point_query.rs b/src/query/point/point_query.rs index 57eff479..67428170 100644 --- a/src/query/point/point_query.rs +++ b/src/query/point/point_query.rs @@ -33,6 +33,11 @@ impl PointProjection { point: pos * self.point, } } + + /// Returns `true` if `Self::is_inside` is `true` or if the distance between the projected point and `point` is smaller than `min_dist`. + pub fn is_inside_eps(&self, original_point: &Point, min_dist: Real) -> bool { + self.is_inside || na::distance_squared(original_point, &self.point) < min_dist * min_dist + } } /// Trait of objects that can be tested for point inclusion and projection. diff --git a/src/shape/shape.rs b/src/shape/shape.rs index 5bbcbe70..fe713438 100644 --- a/src/shape/shape.rs +++ b/src/shape/shape.rs @@ -3,6 +3,8 @@ use core::fmt::Debug; use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume}; use crate::mass_properties::MassProperties; use crate::math::{Isometry, Point, Real, Vector}; +#[cfg(not(feature = "std"))] +use crate::num::Float; use crate::query::{PointQuery, RayCast}; #[cfg(feature = "serde-serialize")] use crate::shape::SharedShape; @@ -1317,7 +1319,7 @@ impl Shape for Cone { } fn ccd_angular_thickness(&self) -> Real { - let apex_half_angle = self.radius.atan2(self.half_height); + let apex_half_angle = RealField::atan2(self.radius, self.half_height); assert!(apex_half_angle >= 0.0); let basis_angle = Real::frac_pi_2() - apex_half_angle; basis_angle.min(apex_half_angle * 2.0) diff --git a/src/shape/triangle.rs b/src/shape/triangle.rs index 7dc2e761..af16b55e 100644 --- a/src/shape/triangle.rs +++ b/src/shape/triangle.rs @@ -230,6 +230,9 @@ impl Triangle { /// /// The vector points such that it is collinear to `AB × AC` (where `×` denotes the cross /// product). + /// Note that on thin triangles the calculated normals can suffer from numerical issues. + /// For a more robust (but more computationally expensive) normal calculation, see + /// [`Triangle::robust_scaled_normal`]. #[inline] pub fn scaled_normal(&self) -> Vector { let ab = self.b - self.a; @@ -237,6 +240,29 @@ impl Triangle { ab.cross(&ac) } + /// Find a triangle normal more robustly than with [`Triangle::scaled_normal`]. + /// + /// Thin triangles can cause numerical issues when computing its normal. This method accounts + /// for these numerical issues more robustly than [`Triangle::scaled_normal`], but is more + /// computationally expensive. + #[inline] + #[cfg(feature = "dim3")] + pub fn robust_scaled_normal(&self) -> na::Vector3 { + let pts = self.vertices(); + let best_vertex = self.angle_closest_to_90(); + let d1 = pts[(best_vertex + 2) % 3] - pts[(best_vertex + 1) % 3]; + let d2 = pts[best_vertex] - pts[(best_vertex + 1) % 3]; + + d1.cross(&d2) + } + + /// Similar to [`Triangle::robust_scaled_normal`], but returns the unit length normal. + #[inline] + #[cfg(feature = "dim3")] + pub fn robust_normal(&self) -> na::Vector3 { + self.robust_scaled_normal().normalize() + } + /// Computes the extents of this triangle on the given direction. /// /// This computes the min and max values of the dot products between each @@ -551,6 +577,29 @@ impl Triangle { } } + /// Find the index of a vertex in this triangle, such that the two + /// edges incident in that vertex form the angle closest to 90 + /// degrees in the triangle. + pub fn angle_closest_to_90(&self) -> usize { + let points = self.vertices(); + let mut best_cos = 2.0; + let mut selected_i = 0; + + for i in 0..3 { + let d1 = (points[i] - points[(i + 1) % 3]).normalize(); + let d2 = (points[(i + 2) % 3] - points[(i + 1) % 3]).normalize(); + + let cos_abs = d1.dot(&d2).abs(); + + if cos_abs < best_cos { + best_cos = cos_abs; + selected_i = i; + } + } + + selected_i + } + /// Reverse the orientation of this triangle by swapping b and c. pub fn reverse(&mut self) { mem::swap(&mut self.b, &mut self.c); diff --git a/src/shape/trimesh.rs b/src/shape/trimesh.rs index 476fe493..270ffb53 100644 --- a/src/shape/trimesh.rs +++ b/src/shape/trimesh.rs @@ -172,21 +172,6 @@ pub struct TriMeshTopology { pub half_edges: Vec, } -impl TriMeshTopology { - #[cfg(feature = "dim3")] - pub(crate) fn face_half_edges_ids(&self, fid: u32) -> [u32; 3] { - let first_half_edge = self.faces[fid as usize].half_edge; - - let mut result = [first_half_edge; 3]; - for k in 1..3 { - let half_edge = self.half_edges[result[k - 1] as usize]; - result[k] = half_edge.next; - } - - result - } -} - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( feature = "rkyv", diff --git a/src/transformation/mesh_intersection/mesh_intersection.rs b/src/transformation/mesh_intersection/mesh_intersection.rs index b7f31fb8..fdc84685 100644 --- a/src/transformation/mesh_intersection/mesh_intersection.rs +++ b/src/transformation/mesh_intersection/mesh_intersection.rs @@ -1,11 +1,46 @@ -use super::{MeshIntersectionError, TriangleTriangleIntersection, EPS}; -use crate::math::{Isometry, Point, Real, Vector}; +use super::{MeshIntersectionError, TriangleTriangleIntersection}; +use crate::math::{Isometry, Real}; +use crate::query::point::point_query::PointQueryWithLocation; use crate::query::{visitors::BoundingVolumeIntersectionsSimultaneousVisitor, PointQuery}; -use crate::shape::{FeatureId, TriMesh, Triangle}; -use crate::utils::{self, WBasis}; -use na::{Point2, Vector2}; -use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Triangulation as _}; -use std::collections::{HashMap, HashSet}; +use crate::shape::{TriMesh, Triangle}; +use crate::utils; +use na::{Point3, Vector3}; +#[cfg(feature = "wavefront")] +use obj::{Group, IndexTuple, ObjData, Object, SimplePolygon}; +use rstar::RTree; +use spade::{ConstrainedDelaunayTriangulation, InsertionError, Triangulation as _}; +use std::collections::BTreeMap; +use std::collections::HashSet; +#[cfg(feature = "wavefront")] +use std::path::PathBuf; + +/// Metadata that specifies thresholds to use when making construction choices +/// in mesh intersections. +pub struct MeshIntersectionTolerances { + /// The smallest angle (in radians) that will be tolerated. A triangle with + /// a smaller angle is considered degenerate and will be deleted. + pub angle_epsilon: Real, + /// The maximum distance at which two points are considered to overlap in space. + /// If `||p1 - p2|| < global_insertion_epsilon` then p1 and p2 are considered + /// to be the same point. + pub global_insertion_epsilon: Real, + /// A multiplier coefficient to scale [`Self::global_insertion_epsilon`] when checking for + /// point duplication within a single triangle. + /// + /// Inside of an individual triangle the distance at which two points are considered + /// to be the same is `global_insertion_epsilon * local_insertion_epsilon_mod`. + pub local_insertion_epsilon_scale: Real, +} + +impl Default for MeshIntersectionTolerances { + fn default() -> Self { + Self { + angle_epsilon: (0.005 as Real).to_radians(), // 0.005 degrees + global_insertion_epsilon: Real::EPSILON * 100.0, + local_insertion_epsilon_scale: 10., + } + } +} /// Computes the intersection of two meshes. /// @@ -19,6 +54,35 @@ pub fn intersect_meshes( mesh2: &TriMesh, flip2: bool, ) -> Result, MeshIntersectionError> { + intersect_meshes_with_tolerances( + pos1, + mesh1, + flip1, + pos2, + mesh2, + flip2, + MeshIntersectionTolerances::default(), + ) +} + +/// Similar to `intersect_meshes`. +/// +/// It allows to specify epsilons for how the algorithm will behave. +/// See `MeshIntersectionTolerances` for details. +pub fn intersect_meshes_with_tolerances( + pos1: &Isometry, + mesh1: &TriMesh, + flip1: bool, + pos2: &Isometry, + mesh2: &TriMesh, + flip2: bool, + meta_data: MeshIntersectionTolerances, +) -> Result, MeshIntersectionError> { + if cfg!(debug_assertions) { + mesh1.assert_half_edge_topology_is_valid(); + mesh2.assert_half_edge_topology_is_valid(); + } + if mesh1.topology().is_none() || mesh2.topology().is_none() { return Err(MeshIntersectionError::MissingTopology); } @@ -27,18 +91,14 @@ pub fn intersect_meshes( return Err(MeshIntersectionError::MissingPseudoNormals); } - // NOTE: remove this, used for debugging only. - mesh1.assert_half_edge_topology_is_valid(); - mesh2.assert_half_edge_topology_is_valid(); - let pos12 = pos1.inv_mul(pos2); // 1: collect all the potential triangle-triangle intersections. - let mut intersections = vec![]; + let mut intersection_candidates = vec![]; let mut visitor = BoundingVolumeIntersectionsSimultaneousVisitor::with_relative_pos( pos12, |tri1: &u32, tri2: &u32| { - intersections.push((*tri1, *tri2)); + intersection_candidates.push((*tri1, *tri2)); true }, ); @@ -50,16 +110,20 @@ pub fn intersect_meshes( let mut new_indices1 = vec![]; let mut new_indices2 = vec![]; - for (fid1, fid2) in &intersections { + // 2: Identify all triangles that do actually intersect. + let mut intersections = vec![]; + for (fid1, fid2) in &intersection_candidates { let tri1 = mesh1.triangle(*fid1); let tri2 = mesh2.triangle(*fid2).transformed(&pos12); if super::triangle_triangle_intersection(&tri1, &tri2).is_some() { + intersections.push((*fid1, *fid2)); let _ = deleted_faces1.insert(*fid1); let _ = deleted_faces2.insert(*fid2); } } + // 3: Grab all triangles that are inside the other mesh but do not intersect it. extract_connected_components( &pos12, mesh1, @@ -77,81 +141,108 @@ pub fn intersect_meshes( &mut new_indices2, ); - let mut new_vertices12 = vec![]; - let mut new_indices12 = vec![]; - - cut_and_triangulate_intersections( - &pos12, - mesh1, - flip1, - mesh2, - flip2, - &mut new_vertices12, - &mut new_indices12, - &mut intersections, - ); - - let old_vertices1 = mesh1.vertices(); - let old_vertices2 = mesh2.vertices(); - - // At this point, we know what triangles we want from the first mesh, - // and the ones we want from the second mesh. Now we need to build the - // vertex buffer and adjust the indices accordingly. - let mut new_vertices = vec![]; - - // Maps from unified index to the final vertex index. - let mut index_map = HashMap::new(); - let base_id2 = mesh1.vertices().len() as u32; - - // Grab all the triangles from the connected component extracted from the first mesh. - for idx1 in &mut new_indices1 { - for k in 0..3 { - let new_id = *index_map.entry(idx1[k]).or_insert_with(|| { - let vtx = old_vertices1[idx1[k] as usize]; - new_vertices.push(vtx); - new_vertices.len() - 1 - }); - idx1[k] = new_id as u32; + // 4: Initialize a new mesh by inserting points into a set. Duplicate points should + // hash to the same index. + let mut point_set = RTree::::new(); + let mut topology_indices = Vec::new(); + { + let mut insert_point = |position: Point3| { + insert_into_set(position, &mut point_set, meta_data.global_insertion_epsilon) as u32 + }; + // Add the inside vertices and triangles from mesh1 + for mut face in new_indices1 { + if flip1 { + face.swap(0, 1); + } + topology_indices.push([ + insert_point(mesh1.vertices()[face[0] as usize]), + insert_point(mesh1.vertices()[face[1] as usize]), + insert_point(mesh1.vertices()[face[2] as usize]), + ]); } - } - // Grab all the triangles from the connected component extracted from the second mesh. - for idx2 in &mut new_indices2 { - for k in 0..3 { - let new_id = *index_map.entry(base_id2 + idx2[k]).or_insert_with(|| { - let vtx = pos12 * old_vertices2[idx2[k] as usize]; - new_vertices.push(vtx); - new_vertices.len() - 1 - }); - idx2[k] = new_id as u32; + // Add the inside vertices and triangles from mesh2 + for mut face in new_indices2 { + if flip2 { + face.swap(0, 1); + } + topology_indices.push([ + insert_point(mesh2.vertices()[face[0] as usize]), + insert_point(mesh2.vertices()[face[1] as usize]), + insert_point(mesh2.vertices()[face[2] as usize]), + ]); } } - // Grab all the trinangles from the intersections. - for idx12 in &mut new_indices12 { - for id12 in idx12 { - let new_id = *index_map.entry(*id12).or_insert_with(|| { - let vtx = unified_vertex(mesh1, mesh2, &new_vertices12, &pos12, *id12); - new_vertices.push(vtx); - new_vertices.len() - 1 - }); - *id12 = new_id as u32; - } - } + // 5: Associate constraint edges generated by a tringle-triangle intersection + // to each intersecting triangle where they occur. + let mut constraints1 = BTreeMap::<_, Vec<_>>::new(); + let mut constraints2 = BTreeMap::<_, Vec<_>>::new(); + for (fid1, fid2) in &intersections { + let tri1 = mesh1.triangle(*fid1); + let tri2 = mesh2.triangle(*fid2).transformed(&pos12); - if flip1 { - new_indices1.iter_mut().for_each(|idx| idx.swap(1, 2)); - } + let list1 = constraints1.entry(fid1).or_default(); + let list2 = constraints2.entry(fid2).or_default(); - if flip2 { - new_indices2.iter_mut().for_each(|idx| idx.swap(1, 2)); + let intersection = super::triangle_triangle_intersection(&tri1, &tri2); + if let Some(intersection) = intersection { + match intersection { + TriangleTriangleIntersection::Segment { a, b } => { + // For both triangles, add the points in the intersection + // and their associated edge to the set. + // Note this necessarily introduces duplicate points to the + // set that need to be filtered out. + list1.push([a.p1, b.p1]); + list2.push([a.p1, b.p1]); + } + TriangleTriangleIntersection::Polygon(polygon) => { + for i in 0..polygon.len() { + let a = polygon[i]; + let b = polygon[(i + 1) % polygon.len()]; + + // Triangles overlap in space, so only one constraint is needed. + list1.push([a.p1, b.p1]); + } + } + } + } } - new_indices1.append(&mut new_indices2); - new_indices1.append(&mut new_indices12); + // 6: Collect all triangles that intersect and their associated constraint edges. + // For each such triangle, compute a CDT of its constraints. For each face in this CDT, + // if the face is contained in the opposite mesh, add it to the intersection mesh. + merge_triangle_sets( + mesh1, + mesh2, + &constraints1, + &pos12, + flip1, + flip2, + &meta_data, + &mut point_set, + &mut topology_indices, + )?; - if !new_indices1.is_empty() { - Ok(Some(TriMesh::new(new_vertices, new_indices1))) + merge_triangle_sets( + mesh2, + mesh1, + &constraints2, + &Isometry::identity(), + flip2, + flip1, + &meta_data, + &mut point_set, + &mut topology_indices, + )?; + + // 7: Sort the ouput points by insertion order. + let mut vertices: Vec<_> = point_set.iter().copied().collect(); + vertices.sort_by(|a, b| a.id.cmp(&b.id)); + let vertices: Vec<_> = vertices.iter().map(|p| Point3::from(p.point)).collect(); + + if !topology_indices.is_empty() { + Ok(Some(TriMesh::new(vertices, topology_indices))) } else { Ok(None) } @@ -256,393 +347,529 @@ fn extract_connected_components( } } -#[derive(Copy, Clone, Debug)] -struct SpadeInfo { - handle: FixedVertexHandle, +fn triangulate_constraints_and_merge_duplicates( + tri: &Triangle, + constraints: &[[Point3; 2]], + epsilon: Real, +) -> Result< + ( + ConstrainedDelaunayTriangulation>, + Vec>, + ), + InsertionError, +> { + let mut constraints = constraints.to_vec(); + // Add the triangle points to the triangulation. + let mut point_set = RTree::::new(); + let _ = insert_into_set(tri.a, &mut point_set, epsilon); + let _ = insert_into_set(tri.b, &mut point_set, epsilon); + let _ = insert_into_set(tri.c, &mut point_set, epsilon); + + // Sometimes, points on the edge of a triangle are slightly off, and this makes + // spade think that there is a super thin triangle. Project points close to an edge + // onto the edge to get better results. + let tri_vtx = tri.vertices(); + for point_pair in constraints.iter_mut() { + let p1 = point_pair[0]; + let p2 = point_pair[1]; + + for i in 0..3 { + let q1 = tri_vtx[i]; + let q2 = tri_vtx[(i + 1) % 3]; + + let proj1 = project_point_to_segment(&p1, &[q1, q2]); + if (p1 - proj1).norm() < epsilon { + point_pair[0] = Point3::from(proj1); + } + + let proj2 = project_point_to_segment(&p2, &[q1, q2]); + if (p2 - proj2).norm() < epsilon { + point_pair[1] = Point3::from(proj2); + } + } + } + + // Generate edge, taking care to merge duplicate vertices. + let mut edges = Vec::new(); + for point_pair in constraints { + let p1_id = insert_into_set(point_pair[0], &mut point_set, epsilon); + let p2_id = insert_into_set(point_pair[1], &mut point_set, epsilon); + + edges.push([p1_id, p2_id]); + } + + let mut points: Vec<_> = point_set.iter().cloned().collect(); + points.sort_by(|a, b| a.id.cmp(&b.id)); + + let tri_points = tri.vertices(); + let best_source = tri.angle_closest_to_90(); + let d1 = tri_points[(best_source + 2) % 3] - tri_points[(best_source + 1) % 3]; + let d2 = tri_points[best_source] - tri_points[(best_source + 1) % 3]; + let (e1, e2) = planar_gram_schmidt(d1, d2); + + let project = |p: &Point3| spade::Point2::new(e1.dot(&p.coords), e2.dot(&p.coords)); + + // Project points into 2D and triangulate the resulting set. + let planar_points: Vec<_> = points + .iter() + .copied() + .map(|point| { + let point_proj = project(&point.point); + utils::sanitize_spade_point(point_proj) + }) + .collect(); + let cdt_triangulation = + ConstrainedDelaunayTriangulation::bulk_load_cdt_stable(planar_points, edges)?; + debug_assert!(cdt_triangulation.vertices().len() == points.len()); + + let points = points.into_iter().map(|p| Point3::from(p.point)).collect(); + Ok((cdt_triangulation, points)) } -struct Triangulation { - delaunay: ConstrainedDelaunayTriangulation>, - basis: [Vector; 2], - vtx_handles: [FixedVertexHandle; 3], - ref_pt: Point, - ref_proj: [Point2; 3], - normalization: na::Matrix2, +// We heavily recommend that this is left here in case one needs to debug the above code. +#[cfg(feature = "wavefront")] +fn _points_to_obj(mesh: &[Point3], path: &PathBuf) { + use std::io::Write; + let mut file = std::fs::File::create(path).unwrap(); + + for p in mesh { + writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap(); + } } -impl Triangulation { - fn new(triangle: Triangle) -> Self { - let mut delaunay = ConstrainedDelaunayTriangulation::>::new(); - let normal = triangle.normal().unwrap(); - let basis = normal.orthonormal_basis(); - - let ab = triangle.b - triangle.a; - let ac = triangle.c - triangle.a; - - let mut ref_proj = [ - Point2::origin(), - Point2::new(ab.dot(&basis[0]), ab.dot(&basis[1])), - Point2::new(ac.dot(&basis[0]), ac.dot(&basis[1])), - ]; - - let normalization_inv = - na::Matrix2::from_columns(&[ref_proj[1].coords, ref_proj[2].coords]); - let normalization = normalization_inv - .try_inverse() - .unwrap_or(na::Matrix2::identity()); - - for ref_proj in &mut ref_proj { - *ref_proj = normalization * *ref_proj; - } +// We heavily recommend that this is left here in case one needs to debug the above code. +#[cfg(feature = "wavefront")] +fn _points_and_edges_to_obj(mesh: &[Point3], edges: &[[usize; 2]], path: &PathBuf) { + use std::io::Write; + let mut file = std::fs::File::create(path).unwrap(); - let vtx_handles = [ - delaunay - .insert(utils::sanitize_point(spade::Point2::new( - ref_proj[0].x, - ref_proj[0].y, - ))) - .unwrap(), - delaunay - .insert(utils::sanitize_point(spade::Point2::new( - ref_proj[1].x, - ref_proj[1].y, - ))) - .unwrap(), - delaunay - .insert(utils::sanitize_point(spade::Point2::new( - ref_proj[2].x, - ref_proj[2].y, - ))) - .unwrap(), - ]; + for p in mesh { + writeln!(file, "v {} {} {}", p.x, p.y, p.z).unwrap(); + } - Self { - delaunay, - basis, - vtx_handles, - ref_pt: triangle.a, - ref_proj, - normalization, - } + for e in edges { + writeln!(file, "l {} {}", e[0] + 1, e[1] + 1).unwrap(); } +} + +#[derive(Copy, Clone, PartialEq, Debug, Default)] +struct TreePoint { + point: Point3, + id: usize, +} + +impl rstar::Point for TreePoint { + type Scalar = Real; + const DIMENSIONS: usize = 3; - fn project(&self, pt: Point, orig_fid: FeatureId) -> spade::Point2 { - let dpt = pt - self.ref_pt; - let mut proj = - self.normalization * Point2::new(dpt.dot(&self.basis[0]), dpt.dot(&self.basis[1])); - - if let FeatureId::Edge(i) = orig_fid { - let a = self.ref_proj[i as usize]; - let b = self.ref_proj[(i as usize + 1) % 3]; - let ab = b - a; - let ap = proj - a; - let param = ab.dot(&ap) / ab.norm_squared(); - let shift = Vector2::new(ab.y, -ab.x); - - // NOTE: if we have intersections exactly on the edge, we nudge - // their projection slightly outside of the triangle. That - // way, the triangle’s edge gets split automatically by - // the triangulation (or, rather, it will be split when we - // add the contsraint involving that point). - // NOTE: this is not ideal though, so we should find a way to simply - // delete spurious triangles that are outside of the intersection - // curve. - proj = a + ab * param + shift * EPS * 10.0; + fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { + TreePoint { + point: Point3::new(generator(0), generator(1), generator(2)), + id: usize::MAX, } + } + + fn nth(&self, index: usize) -> Self::Scalar { + self.point[index] + } - spade::Point2::new(proj.x, proj.y) + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + &mut self.point[index] } } -fn cut_and_triangulate_intersections( - pos12: &Isometry, - mesh1: &TriMesh, - flip1: bool, - mesh2: &TriMesh, - flip2: bool, - new_vertices12: &mut Vec>, - new_indices12: &mut Vec<[u32; 3]>, - intersections: &mut Vec<(u32, u32)>, -) { - let mut triangulations1 = HashMap::new(); - let mut triangulations2 = HashMap::new(); - let mut intersection_points = HashMap::new(); - - let mut spade_infos = [HashMap::new(), HashMap::new()]; - let mut spade_handle_to_intersection = [HashMap::new(), HashMap::new()]; - - for (i1, i2) in intersections.drain(..) { - let tris = [mesh1.triangle(i1), mesh2.triangle(i2).transformed(pos12)]; - let vids = [mesh1.indices()[i1 as usize], mesh2.indices()[i2 as usize]]; - - if let Some(intersection) = super::triangle_triangle_intersection(&tris[0], &tris[1]) { - let tri_ids = [i1, i2]; - - let triangulation1 = triangulations1.entry(tri_ids[0]).or_insert_with(|| { - let triangulation = Triangulation::new(tris[0]); - for k in 0..3 { - let _ = spade_handle_to_intersection[0].insert( - (tri_ids[0], triangulation.vtx_handles[k]), - (FeatureId::Vertex(vids[0][k]), FeatureId::Unknown), - ); - } - triangulation - }); - - let triangulation2 = triangulations2.entry(tri_ids[1]).or_insert_with(|| { - let triangulation = Triangulation::new(tris[1]); - for k in 0..3 { - let _ = spade_handle_to_intersection[1].insert( - (tri_ids[1], triangulation.vtx_handles[k]), - (FeatureId::Unknown, FeatureId::Vertex(vids[1][k])), - ); - } - triangulation - }); - - let triangulations = [triangulation1, triangulation2]; - - let mut insert_point = - |pt: [_; 2], key: (FeatureId, FeatureId), orig_fid: [FeatureId; 2], i: usize| { - let spade_key = (tri_ids[i], key); - - spade_infos[i] - .entry(spade_key) - .or_insert_with(|| { - let point2d = triangulations[i].project(pt[i], orig_fid[i]); - let handle = triangulations[i] - .delaunay - .insert(utils::sanitize_point(point2d)) - .unwrap(); - let _ = - spade_handle_to_intersection[i].insert((tri_ids[i], handle), key); - SpadeInfo { handle } - }) - .handle - }; +fn insert_into_set( + position: Point3, + point_set: &mut RTree, + epsilon: Real, +) -> usize { + let point_count = point_set.size(); + let point_to_insert = TreePoint { + point: position, + id: point_count, + }; - match intersection { - TriangleTriangleIntersection::Segment { - a: inter_a, - b: inter_b, - } => { - let fa_1 = convert_fid(mesh1, i1, inter_a.f1); - let fa_2 = convert_fid(mesh2, i2, inter_a.f2); - let fb_1 = convert_fid(mesh1, i1, inter_b.f1); - let fb_2 = convert_fid(mesh2, i2, inter_b.f2); - - let orig_fid_a = [inter_a.f1, inter_a.f2]; - let orig_fid_b = [inter_b.f1, inter_b.f2]; - let key_a = (fa_1, fa_2); - let key_b = (fb_1, fb_2); - - let ins_a = *intersection_points - .entry(key_a) - .or_insert([inter_a.p1, inter_a.p2]); - let ins_b = *intersection_points - .entry(key_b) - .or_insert([inter_b.p1, inter_b.p2]); - - let handles_a = [ - insert_point(ins_a, key_a, orig_fid_a, 0), - insert_point(ins_a, key_a, orig_fid_a, 1), - ]; - - let handles_b = [ - insert_point(ins_b, key_b, orig_fid_b, 0), - insert_point(ins_b, key_b, orig_fid_b, 1), - ]; - - for i in 0..2 { - // NOTE: the naming of the `ConstrainedDelaunayTriangulation::can_add_constraint` method is misleading. - if !triangulations[i] - .delaunay - .can_add_constraint(handles_a[i], handles_b[i]) - { - let _ = triangulations[i] - .delaunay - .add_constraint(handles_a[i], handles_b[i]); - } - } - } - TriangleTriangleIntersection::Polygon(intersections) => { - for inter in intersections { - let f1 = convert_fid(mesh1, i1, inter.f1); - let f2 = convert_fid(mesh2, i2, inter.f2); - let orig_fid = [inter.f1, inter.f2]; - let key = (f1, f2); - let ins = *intersection_points - .entry(key) - .or_insert([inter.p1, inter.p2]); - - let _ = insert_point(ins, key, orig_fid, 0); - let _ = insert_point(ins, key, orig_fid, 1); - } - } + match point_set.nearest_neighbor(&point_to_insert) { + Some(tree_point) => { + if (tree_point.point - position).norm_squared() <= epsilon { + tree_point.id + } else { + point_set.insert(point_to_insert); + debug_assert!(point_set.size() == point_count + 1); + point_count } } + None => { + point_set.insert(point_to_insert); + debug_assert!(point_set.size() == point_count + 1); + point_count + } } - - extract_result( - pos12, - mesh1, - flip1, - mesh2, - flip2, - &spade_handle_to_intersection, - &intersection_points, - &triangulations1, - &triangulations2, - new_vertices12, - new_indices12, - ); } -fn convert_fid(mesh: &TriMesh, tri: u32, fid: FeatureId) -> FeatureId { - match fid { - FeatureId::Edge(eid) => { - let topology = mesh.topology().unwrap(); - let half_edge_id = topology.face_half_edges_ids(tri)[eid as usize]; - let half_edge = &topology.half_edges[half_edge_id as usize]; - // NOTE: if the twin doesn’t exist, it’s equal to u32::MAX. So the `min` will - // automatically filter it out. - FeatureId::Edge(half_edge_id.min(half_edge.twin)) +fn smallest_angle(points: &[Point3]) -> Real { + let n = points.len(); + + let mut worst_cos = -2.0; + for i in 0..points.len() { + let d1 = (points[i] - points[(i + 1) % n]).normalize(); + let d2 = (points[(i + 2) % n] - points[(i + 1) % n]).normalize(); + + let cos = d1.dot(&d2); + if cos > worst_cos { + worst_cos = cos; } - FeatureId::Vertex(vid) => FeatureId::Vertex(mesh.indices()[tri as usize][vid as usize]), - FeatureId::Face(_) => FeatureId::Face(tri), - FeatureId::Unknown => FeatureId::Unknown, } + + worst_cos.acos() } -fn unified_vertex( - mesh1: &TriMesh, - mesh2: &TriMesh, - new_vertices12: &[Point], - pos12: &Isometry, - vid: u32, -) -> Point { - let base_id2 = mesh1.vertices().len() as u32; - let base_id12 = (mesh1.vertices().len() + mesh2.vertices().len()) as u32; - - if vid < base_id2 { - mesh1.vertices()[vid as usize] - } else if vid < base_id12 { - pos12 * mesh2.vertices()[(vid - base_id2) as usize] - } else { - new_vertices12[(vid - base_id12) as usize] +fn planar_gram_schmidt(v1: Vector3, v2: Vector3) -> (Vector3, Vector3) { + let u1 = v1; + let u2 = v2 - (v2.dot(&u1) / u1.norm_squared()) * u1; + + let e1 = u1.normalize(); + let e2 = u2.normalize(); + + (e1, e2) +} + +fn project_point_to_segment(point: &Point3, segment: &[Point3; 2]) -> Point3 { + let dir = segment[1] - segment[0]; + let local = point - segment[0]; + + let norm = dir.norm(); + // Restrict the result to the segment portion of the line. + let coeff = (dir.dot(&local) / norm).clamp(0., norm); + + segment[0] + coeff * dir.normalize() +} + +/// No matter how smart we are about computing intersections. It is always possible +/// to create ultra thin triangles when a point lies on an edge of a tirangle. These +/// are degenerate and need to be removed. +fn is_triangle_degenerate( + triangle: &[Point3; 3], + epsilon_angle: Real, + epsilon_distance: Real, +) -> bool { + if smallest_angle(triangle) < epsilon_angle { + return true; + } + + let mut shortest_side = Real::MAX; + for i in 0..3 { + let p1 = triangle[i]; + let p2 = triangle[(i + 1) % 3]; + + shortest_side = shortest_side.min((p1 - p2).norm()); } + + let mut worse_projection_distance = Real::MAX; + for i in 0..3 { + let dir = triangle[(i + 1) % 3] - triangle[(i + 2) % 3]; + if dir.norm() < epsilon_distance { + return true; + } + + let dir = dir.normalize(); + let proj = triangle[(i + 2) % 3] + (triangle[i] - triangle[(i + 2) % 3]).dot(&dir) * dir; + + worse_projection_distance = worse_projection_distance.min((proj - triangle[i]).norm()); + } + + worse_projection_distance < epsilon_distance } -fn extract_result( - pos12: &Isometry, +fn merge_triangle_sets( mesh1: &TriMesh, - flip1: bool, mesh2: &TriMesh, + triangle_constraints: &BTreeMap<&u32, Vec<[Point3; 2]>>, + pos12: &Isometry, + flip1: bool, flip2: bool, - spade_handle_to_intersection: &[HashMap<(u32, FixedVertexHandle), (FeatureId, FeatureId)>; 2], - intersection_points: &HashMap<(FeatureId, FeatureId), [Point; 2]>, - triangulations1: &HashMap, - triangulations2: &HashMap, - new_vertices12: &mut Vec>, - new_indices12: &mut Vec<[u32; 3]>, -) { - // Base ids for indexing in the first mesh vertices, second mesh vertices, and new vertices, as if they - // are part of a single big array. - let base_id2 = mesh1.vertices().len() as u32; - let base_id12 = (mesh1.vertices().len() + mesh2.vertices().len()) as u32; - - let mut added_vertices = HashMap::new(); - let mut vertex_remaping = HashMap::new(); - - // Generate the new points and setup the mapping between indices from - // the second mesh, to vertices from the first mash (for cases of vertex/vertex intersections). - for (fids, pts) in intersection_points.iter() { - match *fids { - (FeatureId::Vertex(vid1), FeatureId::Vertex(vid2)) => { - let _ = vertex_remaping.insert(vid2, vid1); - } - (FeatureId::Vertex(_), _) | (_, FeatureId::Vertex(_)) => {} - _ => { - let _ = added_vertices.entry(fids).or_insert_with(|| { - new_vertices12.push(pts[0]); - new_vertices12.len() as u32 - 1 - }); + metadata: &MeshIntersectionTolerances, + point_set: &mut RTree, + topology_indices: &mut Vec<[u32; 3]>, +) -> Result<(), MeshIntersectionError> { + // For each triangle, and each constraint edge associated to that triangle, + // make a triangulation of the face and sort whether or not each generated + // sub-triangle is part of the intersection. + // For each sub-triangle that is part of the intersection, add them to the + // output mesh. + for (triangle_id, constraints) in triangle_constraints.iter() { + let tri = mesh1.triangle(**triangle_id); + + let (delaunay, points) = triangulate_constraints_and_merge_duplicates( + &tri, + constraints, + metadata.global_insertion_epsilon * metadata.local_insertion_epsilon_scale, + ) + .or(Err(MeshIntersectionError::TriangulationError))?; + + for face in delaunay.inner_faces() { + let verts = face.vertices(); + let p1 = points[verts[0].index()]; + let p2 = points[verts[1].index()]; + let p3 = points[verts[2].index()]; + + // Sometimes the triangulation is messed up due to numerical errors. If + // a triangle does not survive this test it should be deleted. + if is_triangle_degenerate( + &[p1, p2, p3], + metadata.angle_epsilon, + metadata.global_insertion_epsilon, + ) { + continue; } - } - } - let fids_to_unified_index = |fids| match fids { - (FeatureId::Vertex(vid1), _) => vid1, - (_, FeatureId::Vertex(vid2)) => vertex_remaping - .get(&vid2) - .copied() - .unwrap_or(base_id2 + vid2), - _ => base_id12 + added_vertices[&fids], - }; - - for (tri_id, triangulation) in triangulations1.iter() { - for face in triangulation.delaunay.inner_faces() { - let vtx = face.vertices(); - let mut tri = [Point::origin(); 3]; - let mut idx = [0; 3]; - for k in 0..3 { - let fids = spade_handle_to_intersection[0][&(*tri_id, vtx[k].fix())]; - let vid = fids_to_unified_index(fids); - let vertex = unified_vertex(mesh1, mesh2, new_vertices12, pos12, vid); - - idx[k] = vid; - tri[k] = vertex; + let center = Triangle { + a: p1, + b: p2, + c: p3, } + .center(); + + let epsilon = metadata.global_insertion_epsilon; + let projection = mesh2 + .project_local_point_and_get_location(&pos12.inverse_transform_point(¢er), true) + .0; - let tri = Triangle::from(tri); - let center = tri.center(); - let projection = mesh2.project_point(pos12, &tri.center(), false); + if flip2 ^ (projection.is_inside_eps(¢er, epsilon)) { + topology_indices.push([ + insert_into_set(p1, point_set, epsilon) as u32, + insert_into_set(p2, point_set, epsilon) as u32, + insert_into_set(p3, point_set, epsilon) as u32, + ]); - if !tri.is_affinely_dependent_eps(EPS * 10.0) - && ((flip2 ^ projection.is_inside) - || (projection.point - center).norm() <= EPS * 10.0) - { if flip1 { - idx.swap(1, 2); + topology_indices.last_mut().unwrap().swap(0, 1) } - new_indices12.push(idx); - } - } - } - for (tri_id, triangulation) in triangulations2.iter() { - for face in triangulation.delaunay.inner_faces() { - let vtx = face.vertices(); - let mut tri = [Point::origin(); 3]; - let mut idx = [0; 3]; - for k in 0..3 { - let fids = spade_handle_to_intersection[1][&(*tri_id, vtx[k].fix())]; - let vid = fids_to_unified_index(fids); - let vertex = unified_vertex(mesh1, mesh2, new_vertices12, pos12, vid); - - idx[k] = vid; - tri[k] = vertex; - } + let [id1, id2, id3] = topology_indices.last().unwrap(); - let tri = Triangle::from(tri); - let center = tri.center(); - - // TODO: when two faces are coplanar, they will be present in both `triangulation1` and - // `triangulation2`. So we need to only pick one of them. Here we already picked the - // face from `triangulation1`. So now we need to ignore the duplicate face. Such face - // is detected by looking at the distance from the triangle’s center to the other mesh. - // If the center lies on the other mesh, then that we have a duplicate face that was already - // added in the previous loop. - let projection = mesh1.project_local_point(¢er, false); - if !tri.is_affinely_dependent_eps(EPS * 10.0) - && ((flip1 ^ projection.is_inside) - && (projection.point - center).norm() > EPS * 10.0) - { - if flip2 { - idx.swap(1, 2); + // This should *never* trigger. If it does + // it means the code has created a triangle with duplicate vertices, + // which means we encountered an unaccounted for edge case. + if id1 == id2 || id1 == id3 || id2 == id3 { + return Err(MeshIntersectionError::DuplicateVertices); } - new_indices12.push(idx); } } } + + Ok(()) +} + +#[cfg(feature = "wavefront")] +#[cfg(test)] +mod tests { + use super::*; + use crate::shape::TriMeshFlags; + use crate::transformation::wavefront::*; + use obj::Obj; + + #[test] + fn test_same_mesh_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap(); + + let mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + &mesh, + false, + &Isometry::identity(), + &mesh, + false, + ) + .unwrap() + .unwrap(); + + mesh.to_obj_file(&PathBuf::from("same_test.obj")); + } + + #[test] + fn test_offset_cylinder_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/offset_cylinder.obj").unwrap(); + + let offset_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/center_cylinder.obj").unwrap(); + + let center_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + ¢er_mesh, + false, + &Isometry::identity(), + &offset_mesh, + false, + ) + .unwrap() + .unwrap(); + + res.to_obj_file(&PathBuf::from("offset_test.obj")); + } + + #[test] + fn test_stair_bar_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/stairs.obj").unwrap(); + + let stair_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/bar.obj").unwrap(); + + let bar_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + &stair_mesh, + false, + &Isometry::identity(), + &bar_mesh, + false, + ) + .unwrap() + .unwrap(); + + res.to_obj_file(&PathBuf::from("stair_test.obj")); + } + + #[test] + fn test_complex_intersection() { + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/low_poly_bunny.obj").unwrap(); + + let bunny_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let Obj { + data: ObjData { + position, objects, .. + }, + .. + } = Obj::load("../../assets/tests/poly_cylinder.obj").unwrap(); + + let cylinder_mesh = TriMesh::with_flags( + position + .iter() + .map(|v| Point3::new(v[0] as f64, v[1] as f64, v[2] as f64)) + .collect::>(), + objects[0].groups[0] + .polys + .iter() + .map(|p| [p.0[0].0 as u32, p.0[1].0 as u32, p.0[2].0 as u32]) + .collect::>(), + TriMeshFlags::all(), + ); + + let res = intersect_meshes( + &Isometry::identity(), + &bunny_mesh, + false, + &Isometry::identity(), + &cylinder_mesh, + true, + ) + .unwrap() + .unwrap(); + + res.to_obj_file(&PathBuf::from("complex_test.obj")); + } } diff --git a/src/transformation/mesh_intersection/mesh_intersection_error.rs b/src/transformation/mesh_intersection/mesh_intersection_error.rs index 2dba1a17..9132824e 100644 --- a/src/transformation/mesh_intersection/mesh_intersection_error.rs +++ b/src/transformation/mesh_intersection/mesh_intersection_error.rs @@ -6,18 +6,22 @@ pub enum MeshIntersectionError { MissingTopology, MissingPseudoNormals, TriTriError, + DuplicateVertices, + TriangulationError, } impl fmt::Display for MeshIntersectionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MissingTopology => { - f.pad("at least on of the meshes is missing its topology information. Call `mesh.compute_topology` on the mesh") + f.pad("at least one of the meshes is missing its topology information. Call `mesh.compute_topology` on the mesh") } Self::MissingPseudoNormals => { - f.pad("at least on of the meshes is missing its pseudo-normals. Call `mesh.compute_pseudo_normals` on the mesh") + f.pad("at least one of the meshes is missing its pseudo-normals. Call `mesh.compute_pseudo_normals` on the mesh") } Self::TriTriError => f.pad("internal failure while intersecting two triangles"), + Self::DuplicateVertices => f.pad("internal failure while merging faces resulting from intersections"), + Self::TriangulationError => f.pad("internal failure while triangulating an intersection face"), } } } diff --git a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs index afe0a99c..b787fa7e 100644 --- a/src/transformation/mesh_intersection/triangle_triangle_intersection.rs +++ b/src/transformation/mesh_intersection/triangle_triangle_intersection.rs @@ -8,9 +8,6 @@ use crate::utils::WBasis; #[derive(Copy, Clone, Debug, Default)] pub struct TriangleTriangleIntersectionPoint { pub p1: Point, - pub p2: Point, - pub f1: FeatureId, - pub f2: FeatureId, } #[derive(Clone, Debug)] @@ -35,8 +32,8 @@ pub fn triangle_triangle_intersection( tri1: &Triangle, tri2: &Triangle, ) -> Option { - let normal1 = tri1.normal()?; - let normal2 = tri2.normal()?; + let normal1 = tri1.robust_normal(); + let normal2 = tri2.robust_normal(); if let Some(intersection_dir) = normal1.cross(&normal2).try_normalize(1.0e-6) { let mut range1 = [ @@ -99,82 +96,16 @@ pub fn triangle_triangle_intersection( return None; } - let edge_between = |a, b| match (a, b) { - (0, 1) | (1, 0) => FeatureId::Edge(0), - (1, 2) | (2, 1) => FeatureId::Edge(1), - (2, 0) | (0, 2) => FeatureId::Edge(2), - _ => FeatureId::Edge(a), - }; - - let inter_f1 = match (range1[0].2, range1[1].2) { - (FeatureId::Vertex(a), FeatureId::Vertex(b)) => edge_between(a, b), - (FeatureId::Vertex(v), FeatureId::Edge(e)) - | (FeatureId::Edge(e), FeatureId::Vertex(v)) => { - if e == (v + 1) % 3 { - FeatureId::Face(0) - } else { - FeatureId::Edge(e) - } - } - _ => FeatureId::Face(0), - }; - let inter_f2 = match (range2[0].2, range2[1].2) { - (FeatureId::Vertex(a), FeatureId::Vertex(b)) => edge_between(a, b), - (FeatureId::Vertex(v), FeatureId::Edge(e)) - | (FeatureId::Edge(e), FeatureId::Vertex(v)) => { - if e == (v + 1) % 3 { - FeatureId::Face(0) - } else { - FeatureId::Edge(e) - } - } - _ => FeatureId::Face(0), - }; - let a = if range2[0].0 > range1[0].0 + EPS { - TriangleTriangleIntersectionPoint { - p1: range2[0].1, - p2: range2[0].1, - f1: inter_f1, - f2: range2[0].2, - } - } else if range2[0].0 < range1[0].0 - EPS { - TriangleTriangleIntersectionPoint { - p1: range1[0].1, - p2: range1[0].1, - f1: range1[0].2, - f2: inter_f2, - } + TriangleTriangleIntersectionPoint { p1: range2[0].1 } } else { - TriangleTriangleIntersectionPoint { - p1: range1[0].1, - p2: range2[0].1, - f1: range1[0].2, - f2: range2[0].2, - } + TriangleTriangleIntersectionPoint { p1: range1[0].1 } }; let b = if range2[1].0 < range1[1].0 - EPS { - TriangleTriangleIntersectionPoint { - p1: range2[1].1, - p2: range2[1].1, - f1: inter_f1, - f2: range2[1].2, - } - } else if range2[1].0 > range1[1].0 + EPS { - TriangleTriangleIntersectionPoint { - p1: range1[1].1, - p2: range1[1].1, - f1: range1[1].2, - f2: inter_f2, - } + TriangleTriangleIntersectionPoint { p1: range2[1].1 } } else { - TriangleTriangleIntersectionPoint { - p1: range1[1].1, - p2: range2[1].1, - f1: range1[1].2, - f2: range2[1].2, - } + TriangleTriangleIntersectionPoint { p1: range1[1].1 } }; Some(TriangleTriangleIntersection::Segment { a, b }) @@ -216,27 +147,17 @@ pub fn triangle_triangle_intersection( crate::transformation::convex_polygons_intersection(&poly1, &poly2, |pt1, pt2| { let intersection = match (pt1, pt2) { (Some(loc1), Some(loc2)) => { - let (f1, p1) = convert_loc(loc1, pts1); - let (f2, p2) = convert_loc(loc2, pts2); - TriangleTriangleIntersectionPoint { p1, p2, f1, f2 } + let (_f1, p1) = convert_loc(loc1, pts1); + let (_f2, _p2) = convert_loc(loc2, pts2); + TriangleTriangleIntersectionPoint { p1 } } (Some(loc1), None) => { - let (f1, p1) = convert_loc(loc1, pts1); - TriangleTriangleIntersectionPoint { - p1, - p2: p1, - f1, - f2: FeatureId::Face(0), - } + let (_f1, p1) = convert_loc(loc1, pts1); + TriangleTriangleIntersectionPoint { p1 } } (None, Some(loc2)) => { - let (f2, p2) = convert_loc(loc2, pts2); - TriangleTriangleIntersectionPoint { - p1: p2, - p2, - f1: FeatureId::Face(0), - f2, - } + let (_f2, p2) = convert_loc(loc2, pts2); + TriangleTriangleIntersectionPoint { p1: p2 } } (None, None) => unreachable!(), }; diff --git a/src/transformation/mod.rs b/src/transformation/mod.rs index 05729e03..be7e8348 100644 --- a/src/transformation/mod.rs +++ b/src/transformation/mod.rs @@ -39,3 +39,6 @@ mod to_polyline; #[cfg(feature = "dim3")] mod to_trimesh; pub mod utils; + +#[cfg(feature = "wavefront")] +pub mod wavefront; diff --git a/src/transformation/polygon_intersection.rs b/src/transformation/polygon_intersection.rs index 9f4f30e4..ebdd8376 100644 --- a/src/transformation/polygon_intersection.rs +++ b/src/transformation/polygon_intersection.rs @@ -437,7 +437,7 @@ pub fn polygons_intersection( out(None, Some(location)) }; - if let Some(first_intersection) = edge_inters.get(0) { + if let Some(first_intersection) = edge_inters.first() { // Jump on the first intersection and move on to the other polygon. to_traverse.poly = (to_traverse.poly + 1) % 2; to_traverse.edge = first_intersection.edges[to_traverse.poly]; @@ -455,12 +455,12 @@ pub fn polygons_intersection( // If there are no intersection, check if one polygon is inside the other. if intersections[0].is_empty() { - if utils::point_in_poly2d(&poly1[0], &poly2) { + if utils::point_in_poly2d(&poly1[0], poly2) { for pt_id in 0..poly1.len() { out(Some(PolylinePointLocation::OnVertex(pt_id)), None) } out(None, None); - } else if utils::point_in_poly2d(&poly2[0], &poly1) { + } else if utils::point_in_poly2d(&poly2[0], poly1) { for pt_id in 0..poly2.len() { out(None, Some(PolylinePointLocation::OnVertex(pt_id))) } diff --git a/src/transformation/wavefront.rs b/src/transformation/wavefront.rs new file mode 100644 index 00000000..829d6749 --- /dev/null +++ b/src/transformation/wavefront.rs @@ -0,0 +1,38 @@ +use crate::shape::TriMesh; +use obj::{Group, IndexTuple, ObjData, ObjError, Object, SimplePolygon}; +use std::path::PathBuf; + +impl TriMesh { + pub fn to_obj_file(&self, path: &PathBuf) -> Result<(), ObjError> { + let mut file = std::fs::File::create(path).unwrap(); + + ObjData { + position: self + .vertices() + .into_iter() + .map(|v| [v.x as f32, v.y as f32, v.z as f32]) + .collect(), + objects: vec![Object { + groups: vec![Group { + polys: self + .indices() + .into_iter() + .map(|tri| { + SimplePolygon(vec![ + IndexTuple(tri[0] as usize, None, None), + IndexTuple(tri[1] as usize, None, None), + IndexTuple(tri[2] as usize, None, None), + ]) + }) + .collect(), + name: "".to_string(), + index: 0, + material: None, + }], + name: "".to_string(), + }], + ..Default::default() + } + .write_to_buf(&mut file) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b24de470..85e70f03 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -30,7 +30,7 @@ pub(crate) use self::sort::sort2; pub(crate) use self::sort::sort3; pub use self::sorted_pair::SortedPair; #[cfg(all(feature = "dim3", feature = "std"))] -pub(crate) use self::spade::sanitize_point; +pub(crate) use self::spade::sanitize_spade_point; pub(crate) use self::weighted_value::WeightedValue; pub(crate) use self::wops::{simd_swap, WBasis, WCross, WSign}; diff --git a/src/utils/spade.rs b/src/utils/spade.rs index a6ff93ce..f9efa214 100644 --- a/src/utils/spade.rs +++ b/src/utils/spade.rs @@ -4,7 +4,7 @@ use crate::math::Real; /// /// Returns 0.0 if the coordinate is smaller than `spade::MIN_ALLOWED_VALUE`. /// Returns `spade::MAX_ALLOWED_VALUE` the coordinate is larger than `spade::MAX_ALLOWED_VALUE`. -pub fn sanitize_coord(coord: Real) -> Real { +pub fn sanitize_spade_coord(coord: Real) -> Real { let abs = coord.abs(); #[allow(clippy::unnecessary_cast)] @@ -21,6 +21,7 @@ pub fn sanitize_coord(coord: Real) -> Real { coord } -pub fn sanitize_point(point: spade::Point2) -> spade::Point2 { - spade::Point2::new(sanitize_coord(point.x), sanitize_coord(point.y)) +/// Ensures the coordinates of the given point don’t go out of the bounds of spade’s acceptable values. +pub fn sanitize_spade_point(point: spade::Point2) -> spade::Point2 { + spade::Point2::new(sanitize_spade_coord(point.x), sanitize_spade_coord(point.y)) }