mesh/mesh.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/mesh/mesh.h" | ||
| 2 | |||
| 3 | #include "terraingen/util/checks.h" | ||
| 4 | |||
| 5 | #include <cmath> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <map> | ||
| 8 | #include <stdexcept> | ||
| 9 | #include <tuple> | ||
| 10 | #include <utility> | ||
| 11 | |||
| 12 | namespace terraingen { | ||
| 13 | |||
| 14 | 11765 | Box3d Bounds(const TriangleMesh& mesh) { | |
| 15 | 11765 | if (mesh.positions.empty()) { | |
| 16 | 1 | throw std::invalid_argument("An empty mesh has no bounds"); | |
| 17 | } | ||
| 18 | 11764 | Box3d box{mesh.positions.front(), mesh.positions.front()}; | |
| 19 | 4684029 | for (const Vec3d& p : mesh.positions) { | |
| 20 | 4672265 | box.min = {std::fmin(box.min.x, p.x), std::fmin(box.min.y, p.y), std::fmin(box.min.z, p.z)}; | |
| 21 | 4672265 | box.max = {std::fmax(box.max.x, p.x), std::fmax(box.max.y, p.y), std::fmax(box.max.z, p.z)}; | |
| 22 | } | ||
| 23 | 11764 | return box; | |
| 24 | } | ||
| 25 | |||
| 26 | namespace { | ||
| 27 | |||
| 28 | /// Twice the area-weighted normal of triangle `t`. | ||
| 29 | 680417 | Vec3d AreaNormal(const TriangleMesh& mesh, const std::array<std::uint32_t, 3>& t) { | |
| 30 | 680417 | const Vec3d& a = mesh.positions[t[0]]; | |
| 31 | 680417 | return Cross(mesh.positions[t[1]] - a, mesh.positions[t[2]] - a); | |
| 32 | } | ||
| 33 | |||
| 34 | } // namespace | ||
| 35 | |||
| 36 | 1348 | void ValidateMesh(const TriangleMesh& mesh) { | |
| 37 | 1730536 | for (const auto& t : mesh.triangles) { | |
| 38 | 6916767 | for (const std::uint32_t v : t) { | |
| 39 | 5187579 | if (v >= mesh.positions.size()) { | |
| 40 | 5 | throw std::invalid_argument("A triangle indexes past the mesh's vertices"); | |
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | 1343 | } | |
| 45 | |||
| 46 | 184 | double Volume(const TriangleMesh& mesh) { | |
| 47 | 184 | ValidateMesh(mesh); | |
| 48 | 183 | double sum = 0.0; | |
| 49 | 180071 | for (const auto& t : mesh.triangles) { | |
| 50 | 179888 | sum += Dot(mesh.positions[t[0]], Cross(mesh.positions[t[1]], mesh.positions[t[2]])); | |
| 51 | } | ||
| 52 | 183 | return sum / 6.0; | |
| 53 | } | ||
| 54 | |||
| 55 | 4 | double SurfaceArea(const TriangleMesh& mesh) { | |
| 56 | 4 | ValidateMesh(mesh); | |
| 57 | 3 | double sum = 0.0; | |
| 58 | 20507 | for (const auto& t : mesh.triangles) { | |
| 59 | 20504 | sum += Length(AreaNormal(mesh, t)); | |
| 60 | } | ||
| 61 | 3 | return 0.5 * sum; | |
| 62 | } | ||
| 63 | |||
| 64 | 10 | std::vector<Vec3d> VertexNormals(const TriangleMesh& mesh) { | |
| 65 | 10 | ValidateMesh(mesh); | |
| 66 | 9 | std::vector<Vec3d> normals(mesh.positions.size()); | |
| 67 | 186140 | for (const auto& t : mesh.triangles) { | |
| 68 | 186131 | const Vec3d n = AreaNormal(mesh, t); | |
| 69 | 744524 | for (const std::uint32_t v : t) { | |
| 70 | 558393 | normals[v] = normals[v] + n; | |
| 71 | } | ||
| 72 | } | ||
| 73 | 94109 | for (Vec3d& n : normals) { | |
| 74 | 94100 | n = Normalized(n); | |
| 75 | } | ||
| 76 | 9 | return normals; | |
| 77 | } | ||
| 78 | |||
| 79 | 417 | ShadedMesh WithCreases(const TriangleMesh& mesh, double creaseAngle) { | |
| 80 | 417 | RequireNonNegative(creaseAngle, "Crease angle"); | |
| 81 | 416 | ValidateMesh(mesh); | |
| 82 | 415 | const double cosine = std::cos(creaseAngle); | |
| 83 | 415 | std::vector<Vec3d> areaNormals; | |
| 84 | 415 | std::vector<Vec3d> unitNormals; | |
| 85 | 415 | std::vector<std::vector<std::size_t>> around(mesh.positions.size()); | |
| 86 | 474197 | for (std::size_t f = 0; f < mesh.triangles.size(); ++f) { | |
| 87 | 473782 | areaNormals.push_back(AreaNormal(mesh, mesh.triangles[f])); | |
| 88 | 473782 | unitNormals.push_back(Normalized(areaNormals.back())); | |
| 89 | 1895128 | for (const std::uint32_t v : mesh.triangles[f]) { | |
| 90 | 1421346 | around[v].push_back(f); | |
| 91 | } | ||
| 92 | } | ||
| 93 | 415 | ShadedMesh shaded; | |
| 94 | 415 | std::map<std::tuple<std::uint32_t, double, double, double>, std::uint32_t> made; | |
| 95 | 474197 | for (std::size_t f = 0; f < mesh.triangles.size(); ++f) { | |
| 96 | 473782 | std::array<std::uint32_t, 3> triangle{}; | |
| 97 | 1895128 | for (std::size_t k = 0; k < 3; ++k) { | |
| 98 | 1421346 | const std::uint32_t v = mesh.triangles[f][k]; | |
| 99 | 1421346 | Vec3d sum{}; | |
| 100 | 9870108 | for (const std::size_t g : around[v]) { | |
| 101 | 8448762 | if (Dot(unitNormals[f], unitNormals[g]) >= cosine) { | |
| 102 | 8342414 | sum = sum + areaNormals[g]; | |
| 103 | } | ||
| 104 | } | ||
| 105 | 1421346 | const Vec3d normal = Normalized(sum); | |
| 106 | 1421346 | const auto key = std::make_tuple(v, normal.x, normal.y, normal.z); | |
| 107 | 1421346 | const auto found = made.find(key); | |
| 108 | 1421346 | if (found != made.end()) { | |
| 109 | 1162439 | triangle[k] = found->second; | |
| 110 | 1162439 | continue; | |
| 111 | } | ||
| 112 | 258907 | const auto index = static_cast<std::uint32_t>(shaded.mesh.positions.size()); | |
| 113 | 258907 | shaded.mesh.positions.push_back(mesh.positions[v]); | |
| 114 | 258907 | shaded.normals.push_back(normal); | |
| 115 | 258907 | made.emplace(key, index); | |
| 116 | 258907 | triangle[k] = index; | |
| 117 | } | ||
| 118 | 473782 | shaded.mesh.triangles.push_back(triangle); | |
| 119 | } | ||
| 120 | 830 | return shaded; | |
| 121 | 415 | } | |
| 122 | |||
| 123 | 21911773 | Vec3d RigidTransform::Apply(const Vec3d& p) const { | |
| 124 | 21911773 | const auto& r = rotation; | |
| 125 | 21911773 | return {r[0] * p.x + r[1] * p.y + r[2] * p.z + translation.x, | |
| 126 | 21911773 | r[3] * p.x + r[4] * p.y + r[5] * p.z + translation.y, | |
| 127 | 43823546 | r[6] * p.x + r[7] * p.y + r[8] * p.z + translation.z}; | |
| 128 | } | ||
| 129 | |||
| 130 | 33345 | RigidTransform RigidTransform::After(const RigidTransform& first) const { | |
| 131 | 33345 | RigidTransform combined; | |
| 132 | 133380 | for (std::size_t i = 0; i < 3; ++i) { | |
| 133 | 400140 | for (std::size_t j = 0; j < 3; ++j) { | |
| 134 | 300105 | double sum = 0.0; | |
| 135 | 1200420 | for (std::size_t k = 0; k < 3; ++k) { | |
| 136 | 900315 | sum += rotation[3 * i + k] * first.rotation[3 * k + j]; | |
| 137 | } | ||
| 138 | 300105 | combined.rotation[3 * i + j] = sum; | |
| 139 | } | ||
| 140 | } | ||
| 141 | 33345 | combined.translation = Apply(first.translation); | |
| 142 | 33345 | return combined; | |
| 143 | } | ||
| 144 | |||
| 145 | 11154 | RigidTransform RigidTransform::RotationX(double angle) { | |
| 146 | 11154 | const double c = std::cos(angle); | |
| 147 | 11154 | const double s = std::sin(angle); | |
| 148 | 11154 | return {{1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c}, {}}; | |
| 149 | } | ||
| 150 | |||
| 151 | 45 | RigidTransform RigidTransform::RotationY(double angle) { | |
| 152 | 45 | const double c = std::cos(angle); | |
| 153 | 45 | const double s = std::sin(angle); | |
| 154 | 45 | return {{c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c}, {}}; | |
| 155 | } | ||
| 156 | |||
| 157 | 11092 | RigidTransform RigidTransform::RotationZ(double angle) { | |
| 158 | 11092 | const double c = std::cos(angle); | |
| 159 | 11092 | const double s = std::sin(angle); | |
| 160 | 11092 | return {{c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0}, {}}; | |
| 161 | } | ||
| 162 | |||
| 163 | 6 | RigidTransform RigidTransform::Translation(const Vec3d& offset) { | |
| 164 | 6 | RigidTransform transform; | |
| 165 | 6 | transform.translation = offset; | |
| 166 | 6 | return transform; | |
| 167 | } | ||
| 168 | |||
| 169 | 500 | void RequireRotation(const std::array<double, 9>& r) { | |
| 170 | 1997 | for (std::size_t i = 0; i < 3; ++i) { | |
| 171 | 5989 | for (std::size_t j = 0; j < 3; ++j) { | |
| 172 | 4492 | double dot = 0.0; | |
| 173 | 17968 | for (std::size_t k = 0; k < 3; ++k) { | |
| 174 | 13476 | dot += r[3 * i + k] * r[3 * j + k]; | |
| 175 | } | ||
| 176 | 4492 | if (std::fabs(dot - (i == j ? 1.0 : 0.0)) > 1e-9) { | |
| 177 | 1 | throw std::invalid_argument("A pose must be a rotation"); | |
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | 499 | const double det = r[0] * (r[4] * r[8] - r[5] * r[7]) - r[1] * (r[3] * r[8] - r[5] * r[6]) + | |
| 182 | 499 | r[2] * (r[3] * r[7] - r[4] * r[6]); | |
| 183 | 499 | if (det < 0.0) { | |
| 184 | 2 | throw std::invalid_argument("A pose must be a rotation, not a reflection"); | |
| 185 | } | ||
| 186 | 497 | } | |
| 187 | |||
| 188 | 365 | std::array<double, 4> RotationQuaternion(const std::array<double, 9>& r) { | |
| 189 | 365 | const double trace = r[0] + r[4] + r[8]; | |
| 190 | 365 | if (trace > 0.0) { | |
| 191 | 273 | const double s = 2.0 * std::sqrt(1.0 + trace); | |
| 192 | 273 | return {(r[7] - r[5]) / s, (r[2] - r[6]) / s, (r[3] - r[1]) / s, 0.25 * s}; | |
| 193 | } | ||
| 194 | 92 | if (r[0] > r[4] && r[0] > r[8]) { | |
| 195 | 2 | const double s = 2.0 * std::sqrt(1.0 + r[0] - r[4] - r[8]); | |
| 196 | 2 | return {0.25 * s, (r[1] + r[3]) / s, (r[2] + r[6]) / s, (r[7] - r[5]) / s}; | |
| 197 | } | ||
| 198 | 90 | if (r[4] > r[8]) { | |
| 199 | 87 | const double s = 2.0 * std::sqrt(1.0 + r[4] - r[0] - r[8]); | |
| 200 | 87 | return {(r[1] + r[3]) / s, 0.25 * s, (r[5] + r[7]) / s, (r[2] - r[6]) / s}; | |
| 201 | } | ||
| 202 | 3 | const double s = 2.0 * std::sqrt(1.0 + r[8] - r[0] - r[4]); | |
| 203 | 3 | return {(r[2] + r[6]) / s, (r[5] + r[7]) / s, 0.25 * s, (r[3] - r[1]) / s}; | |
| 204 | } | ||
| 205 | |||
| 206 | 2 | TriangleMesh Transformed(const TriangleMesh& mesh, const RigidTransform& transform) { | |
| 207 | 2 | TriangleMesh moved = mesh; | |
| 208 | 18 | for (Vec3d& p : moved.positions) { | |
| 209 | 16 | p = transform.Apply(p); | |
| 210 | } | ||
| 211 | 2 | return moved; | |
| 212 | } | ||
| 213 | |||
| 214 | 3334 | TriangleMesh Scaled(const TriangleMesh& mesh, const Vec3d& scale) { | |
| 215 | 13333 | for (const double s : {scale.x, scale.y, scale.z}) { | |
| 216 | 10000 | if (!(std::isfinite(s) && s != 0.0)) { | |
| 217 | 1 | throw std::invalid_argument("Scale factors must be finite and not zero"); | |
| 218 | } | ||
| 219 | } | ||
| 220 | 3333 | TriangleMesh scaled = mesh; | |
| 221 | 772731 | for (Vec3d& p : scaled.positions) { | |
| 222 | 769398 | p = {p.x * scale.x, p.y * scale.y, p.z * scale.z}; | |
| 223 | } | ||
| 224 | 3333 | if (scale.x * scale.y * scale.z < 0.0) { | |
| 225 | 13 | for (auto& t : scaled.triangles) { | |
| 226 | 12 | std::swap(t[1], t[2]); | |
| 227 | } | ||
| 228 | } | ||
| 229 | 3333 | return scaled; | |
| 230 | } | ||
| 231 | |||
| 232 | } // namespace terraingen | ||
| 233 |