mesh/io/icq.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/mesh/io/icq.h" | ||
| 2 | |||
| 3 | #include "terraingen/mesh/shape_model.h" | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <cctype> | ||
| 7 | #include <cmath> | ||
| 8 | #include <cstdint> | ||
| 9 | #include <cstdio> | ||
| 10 | #include <fstream> | ||
| 11 | #include <numbers> | ||
| 12 | #include <sstream> | ||
| 13 | #include <stdexcept> | ||
| 14 | #include <string> | ||
| 15 | |||
| 16 | namespace terraingen { | ||
| 17 | |||
| 18 | namespace { | ||
| 19 | |||
| 20 | 28 | void CheckModel(const IcqModel& model) { | |
| 21 | 28 | if (model.q == 0) { | |
| 22 | 1 | throw std::invalid_argument("An ICQ model needs Q > 0"); | |
| 23 | } | ||
| 24 | 27 | const std::size_t side = model.q + 1; | |
| 25 | 27 | if (model.vertices.size() != 6 * side * side) { | |
| 26 | 1 | throw std::invalid_argument("An ICQ model needs 6 (Q + 1)^2 vertices"); | |
| 27 | } | ||
| 28 | 37973 | for (const Vec3d& v : model.vertices) { | |
| 29 | 37948 | if (!(std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z))) { | |
| 30 | 1 | throw std::invalid_argument("An ICQ model's vertices must be finite"); | |
| 31 | } | ||
| 32 | } | ||
| 33 | 25 | } | |
| 34 | |||
| 35 | 7932 | double CrossSquared(const Vec3d& a, const Vec3d& b) { | |
| 36 | 7932 | const Vec3d c = Cross(a, b); | |
| 37 | 7932 | return Dot(c, c); | |
| 38 | } | ||
| 39 | |||
| 40 | } // namespace | ||
| 41 | |||
| 42 | 27 | bool IsIcqPath(const std::filesystem::path& path) { | |
| 43 | 27 | std::string extension = path.extension().string(); | |
| 44 | 135 | for (char& c : extension) { | |
| 45 | 108 | c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); | |
| 46 | } | ||
| 47 | 54 | return extension == ".icq"; | |
| 48 | 27 | } | |
| 49 | |||
| 50 | 36446 | Vec3d IcqDirection(std::size_t q, std::size_t i, std::size_t j, std::size_t face) { | |
| 51 | 36446 | return RadialShape::LabelDirection(q, i, j, face); | |
| 52 | } | ||
| 53 | |||
| 54 | 17 | IcqModel ReadIcq(const std::filesystem::path& path, double metresPerUnit) { | |
| 55 | 17 | std::ifstream file(path); | |
| 56 | 17 | if (!file) { | |
| 57 | 1 | throw std::runtime_error("Cannot read ICQ file " + path.string()); | |
| 58 | } | ||
| 59 | 16 | std::string line; | |
| 60 | 16 | long long q = 0; | |
| 61 | 16 | if (!std::getline(file, line) || !(std::istringstream(line) >> q) || q <= 0 || q > 1000000) { | |
| 62 | 3 | throw std::runtime_error("ICQ file " + path.string() + " does not start with Q > 0"); | |
| 63 | } | ||
| 64 | 13 | IcqModel model; | |
| 65 | 13 | model.q = static_cast<std::size_t>(q); | |
| 66 | 13 | const std::size_t count = 6 * (model.q + 1) * (model.q + 1); | |
| 67 | 13 | model.vertices.reserve(count); | |
| 68 | 13 | std::size_t number = 1; | |
| 69 | 5474 | while (model.vertices.size() < count) { | |
| 70 | 5463 | if (!std::getline(file, line)) { | |
| 71 | 2 | throw std::runtime_error("ICQ file " + path.string() + " has " + | |
| 72 | 4 | std::to_string(model.vertices.size()) + " of " + | |
| 73 | 3 | std::to_string(count) + " vertices"); | |
| 74 | } | ||
| 75 | 5462 | ++number; | |
| 76 | 5462 | std::istringstream values(line); | |
| 77 | 5462 | Vec3d v; | |
| 78 | 10923 | if (!(values >> v.x >> v.y >> v.z) || | |
| 79 | 5461 | !(std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z))) { | |
| 80 | 2 | throw std::runtime_error("ICQ file " + path.string() + " line " + | |
| 81 | 3 | std::to_string(number) + " is not three finite numbers"); | |
| 82 | } | ||
| 83 | 5461 | model.vertices.push_back({v.x * metresPerUnit, v.y * metresPerUnit, v.z * metresPerUnit}); | |
| 84 | 5462 | } | |
| 85 | 22 | return model; | |
| 86 | 24 | } | |
| 87 | |||
| 88 | 11 | void WriteIcq(const std::filesystem::path& path, const IcqModel& model, double metresPerUnit) { | |
| 89 | 11 | CheckModel(model); | |
| 90 | 9 | std::ofstream file(path); | |
| 91 | 9 | if (!file) { | |
| 92 | 1 | throw std::runtime_error("Cannot write ICQ file " + path.string()); | |
| 93 | } | ||
| 94 | 8 | std::array<char, 64> buffer{}; | |
| 95 | 24 | std::snprintf(buffer.data(), buffer.size(), "%12zu\n", model.q); | |
| 96 | 8 | file << buffer.data(); | |
| 97 | 28616 | for (const Vec3d& v : model.vertices) { | |
| 98 | 57216 | std::snprintf(buffer.data(), buffer.size(), "%18.9f%18.9f%18.9f\n", v.x / metresPerUnit, | |
| 99 | 28608 | v.y / metresPerUnit, v.z / metresPerUnit); | |
| 100 | 28608 | file << buffer.data(); | |
| 101 | } | ||
| 102 | 8 | if (!file) { | |
| 103 | ✗ | throw std::runtime_error("Cannot write ICQ file " + path.string()); | |
| 104 | } | ||
| 105 | 9 | } | |
| 106 | |||
| 107 | 10 | TriangleMesh IcqToMesh(const IcqModel& model) { | |
| 108 | 10 | CheckModel(model); | |
| 109 | 9 | const std::size_t q = model.q; | |
| 110 | 9 | std::vector<std::size_t> n(model.vertices.size()); | |
| 111 | 4833 | for (std::size_t k = 0; k < n.size(); ++k) { | |
| 112 | 4824 | n[k] = k; | |
| 113 | } | ||
| 114 | 41340 | const auto at = [&](std::size_t i, std::size_t j, std::size_t f) -> std::size_t& { | |
| 115 | 41340 | return n[model.Index(i, j, f)]; | |
| 116 | 9 | }; | |
| 117 | 67 | for (std::size_t i = 1; i < q; ++i) { | |
| 118 | 58 | at(i, q, 6) = at(q - i, q, 4); | |
| 119 | 58 | at(i, 0, 6) = at(i, q, 2); | |
| 120 | 58 | at(i, 0, 5) = at(q, q - i, 1); | |
| 121 | 58 | at(i, 0, 4) = at(q - i, 0, 1); | |
| 122 | 58 | at(i, 0, 3) = at(0, i, 1); | |
| 123 | 58 | at(i, 0, 2) = at(i, q, 1); | |
| 124 | } | ||
| 125 | 67 | for (std::size_t j = 1; j < q; ++j) { | |
| 126 | 58 | at(q, j, 6) = at(j, q, 5); | |
| 127 | 58 | at(q, j, 5) = at(0, j, 4); | |
| 128 | 58 | at(q, j, 4) = at(0, j, 3); | |
| 129 | 58 | at(q, j, 3) = at(0, j, 2); | |
| 130 | 58 | at(0, j, 6) = at(q - j, q, 3); | |
| 131 | 58 | at(0, j, 5) = at(q, j, 2); | |
| 132 | } | ||
| 133 | 9 | at(0, 0, 3) = at(0, 0, 1); | |
| 134 | 9 | at(q, 0, 4) = at(0, 0, 1); | |
| 135 | 9 | at(0, 0, 2) = at(0, q, 1); | |
| 136 | 9 | at(q, 0, 3) = at(0, q, 1); | |
| 137 | 9 | at(0, 0, 4) = at(q, 0, 1); | |
| 138 | 9 | at(q, 0, 5) = at(q, 0, 1); | |
| 139 | 9 | at(0, 0, 5) = at(q, q, 1); | |
| 140 | 9 | at(q, 0, 2) = at(q, q, 1); | |
| 141 | 9 | at(0, 0, 6) = at(0, q, 2); | |
| 142 | 9 | at(q, q, 3) = at(0, q, 2); | |
| 143 | 9 | at(0, q, 5) = at(q, q, 2); | |
| 144 | 9 | at(q, 0, 6) = at(q, q, 2); | |
| 145 | 9 | at(q, q, 4) = at(0, q, 3); | |
| 146 | 9 | at(0, q, 6) = at(0, q, 3); | |
| 147 | 9 | at(q, q, 5) = at(0, q, 4); | |
| 148 | 9 | at(q, q, 6) = at(0, q, 4); | |
| 149 | |||
| 150 | 9 | TriangleMesh mesh; | |
| 151 | 9 | std::vector<std::uint32_t> compact(model.vertices.size(), UINT32_MAX); | |
| 152 | 23796 | const auto vertex = [&](std::size_t i, std::size_t j, std::size_t f) { | |
| 153 | 23796 | const std::size_t k = at(i, j, f); | |
| 154 | 23796 | if (compact[k] == UINT32_MAX) { | |
| 155 | 3984 | compact[k] = static_cast<std::uint32_t>(mesh.positions.size()); | |
| 156 | 3984 | mesh.positions.push_back(model.vertices[k]); | |
| 157 | } | ||
| 158 | 23796 | return compact[k]; | |
| 159 | 9 | }; | |
| 160 | 15864 | const auto v = [&](std::size_t i, std::size_t j, std::size_t f) { | |
| 161 | 15864 | return model.vertices[at(i, j, f)]; | |
| 162 | 9 | }; | |
| 163 | 9 | mesh.triangles.reserve(12 * q * q); | |
| 164 | 63 | for (std::size_t f = 1; f <= 6; ++f) { | |
| 165 | 456 | for (std::size_t i = 0; i < q; ++i) { | |
| 166 | 4368 | for (std::size_t j = 0; j < q; ++j) { | |
| 167 | 3966 | const double z1 = CrossSquared(v(i, j, f), v(i + 1, j + 1, f)); | |
| 168 | 3966 | const double z2 = CrossSquared(v(i + 1, j, f), v(i, j + 1, f)); | |
| 169 | 3966 | if (z1 <= z2) { | |
| 170 | ✗ | mesh.triangles.push_back( | |
| 171 | 1985 | {vertex(i, j, f), vertex(i + 1, j + 1, f), vertex(i + 1, j, f)}); | |
| 172 | 1985 | mesh.triangles.push_back( | |
| 173 | 1985 | {vertex(i, j, f), vertex(i, j + 1, f), vertex(i + 1, j + 1, f)}); | |
| 174 | } else { | ||
| 175 | ✗ | mesh.triangles.push_back( | |
| 176 | 1981 | {vertex(i, j, f), vertex(i, j + 1, f), vertex(i + 1, j, f)}); | |
| 177 | 1981 | mesh.triangles.push_back( | |
| 178 | 1981 | {vertex(i + 1, j, f), vertex(i, j + 1, f), vertex(i + 1, j + 1, f)}); | |
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | 18 | return mesh; | |
| 184 | 9 | } | |
| 185 | |||
| 186 | 7 | RadialShape ToRadialShape(const IcqModel& model) { | |
| 187 | 7 | CheckModel(model); | |
| 188 | 7 | constexpr double kRadial = 1e-6; | |
| 189 | 7 | std::vector<double> radii(model.vertices.size()); | |
| 190 | 43 | for (std::size_t face = 1; face <= 6; ++face) { | |
| 191 | 409 | for (std::size_t j = 0; j <= model.q; ++j) { | |
| 192 | 4345 | for (std::size_t i = 0; i <= model.q; ++i) { | |
| 193 | 3973 | const std::size_t k = model.Index(i, j, face); | |
| 194 | 3973 | const Vec3d& v = model.vertices[k]; | |
| 195 | 3973 | const double r = Length(v); | |
| 196 | 3973 | const Vec3d d = IcqDirection(model.q, i, j, face); | |
| 197 | 3973 | if (!(r > 0.0) || Length(Cross(v, d)) > kRadial * r || Dot(v, d) < 0.0) { | |
| 198 | 1 | return ShapeModelFromMesh(IcqToMesh(model), model.q); | |
| 199 | } | ||
| 200 | 3972 | radii[k] = r; | |
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | 6 | return RadialShape(model.q, std::move(radii)); | |
| 205 | 7 | } | |
| 206 | |||
| 207 | 3 | IcqModel ToIcq(const RadialShape& shape) { | |
| 208 | 3 | IcqModel model; | |
| 209 | 3 | model.q = shape.Q(); | |
| 210 | 3 | model.vertices.resize(shape.Radii().size()); | |
| 211 | 21 | for (std::size_t face = 1; face <= 6; ++face) { | |
| 212 | 204 | for (std::size_t j = 0; j <= model.q; ++j) { | |
| 213 | 2172 | for (std::size_t i = 0; i <= model.q; ++i) { | |
| 214 | 1986 | const std::size_t k = model.Index(i, j, face); | |
| 215 | 1986 | model.vertices[k] = IcqDirection(model.q, i, j, face) * shape.Radii()[k]; | |
| 216 | } | ||
| 217 | } | ||
| 218 | } | ||
| 219 | 3 | return model; | |
| 220 | } | ||
| 221 | |||
| 222 | } // namespace terraingen | ||
| 223 |