layers/boulders/boulder_fragments.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/layers/boulders/boulder_fragments.h" | ||
| 2 | |||
| 3 | #include "terraingen/random/stream.h" | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <cmath> | ||
| 7 | #include <numbers> | ||
| 8 | #include <stdexcept> | ||
| 9 | |||
| 10 | namespace terraingen { | ||
| 11 | |||
| 12 | namespace { | ||
| 13 | |||
| 14 | constexpr std::uint32_t kFragmentStream = 0x46524147u; | ||
| 15 | |||
| 16 | /// The seed points of `pieces` pieces of a shape whose box is `box`: around the middle, a third | ||
| 17 | /// of the way out, a little above or below it so the cracks tilt. | ||
| 18 | 135 | std::vector<Vec3d> SeedPoints(const Box3d& box, int pieces, RandomStream& random) { | |
| 19 | 135 | const Vec3d centre = (box.min + box.max) * 0.5; | |
| 20 | 135 | const Vec3d half = (box.max - box.min) * 0.5; | |
| 21 | 135 | const double turn = 2.0 * std::numbers::pi / pieces; | |
| 22 | 135 | const double start = turn * random.NextDouble(); | |
| 23 | 135 | std::vector<Vec3d> seeds; | |
| 24 | 668 | for (int k = 0; k < pieces; ++k) { | |
| 25 | 533 | const double angle = start + turn * (k + 0.3 * (random.NextDouble() - 0.5)); | |
| 26 | 533 | const double out = 0.35 + 0.15 * random.NextDouble(); | |
| 27 | 533 | const double rise = 0.2 * (random.NextDouble() - 0.5); | |
| 28 | 533 | seeds.push_back(centre + Vec3d{out * half.x * std::cos(angle), | |
| 29 | 533 | out * half.y * std::sin(angle), rise * half.z}); | |
| 30 | } | ||
| 31 | 270 | return seeds; | |
| 32 | } | ||
| 33 | |||
| 34 | /// The seed points of `pieces` slabs of a shape whose box is `box`: spread along one horizontal | ||
| 35 | /// line through the middle, a little off it and above or below it, so the cracks between them run | ||
| 36 | /// roughly parallel right through the shape. | ||
| 37 | 202 | std::vector<Vec3d> SlabSeedPoints(const Box3d& box, int pieces, RandomStream& random) { | |
| 38 | 202 | const Vec3d centre = (box.min + box.max) * 0.5; | |
| 39 | 202 | const Vec3d half = (box.max - box.min) * 0.5; | |
| 40 | 202 | const double angle = std::numbers::pi * random.NextDouble(); | |
| 41 | 202 | const Vec3d along{std::cos(angle), std::sin(angle), 0.0}; | |
| 42 | 202 | const Vec3d across{-along.y, along.x, 0.0}; | |
| 43 | 202 | const double reach = half.x * std::fabs(along.x) + half.y * std::fabs(along.y); | |
| 44 | 202 | const double side = half.x * std::fabs(across.x) + half.y * std::fabs(across.y); | |
| 45 | 202 | const double step = 2.0 * reach / pieces; | |
| 46 | 202 | std::vector<Vec3d> seeds; | |
| 47 | 927 | for (int k = 0; k < pieces; ++k) { | |
| 48 | 725 | const double at = (k - 0.5 * (pieces - 1) + 0.4 * (random.NextDouble() - 0.5)) * step; | |
| 49 | 725 | const double off = 0.3 * side * (random.NextDouble() - 0.5); | |
| 50 | 725 | const double rise = 0.3 * half.z * (random.NextDouble() - 0.5); | |
| 51 | 725 | seeds.push_back(centre + along * at + across * off + Vec3d{0.0, 0.0, rise}); | |
| 52 | } | ||
| 53 | 404 | return seeds; | |
| 54 | } | ||
| 55 | |||
| 56 | /// `mesh` cut to the cell of seed `k` among `seeds`: every vertex beyond a plane halfway to | ||
| 57 | /// another seed is moved along its ray from seed `k` onto that plane. | ||
| 58 | 1258 | TriangleMesh CutToCell(const TriangleMesh& mesh, const std::vector<Vec3d>& seeds, std::size_t k) { | |
| 59 | 1258 | TriangleMesh piece = mesh; | |
| 60 | 6424 | for (std::size_t j = 0; j < seeds.size(); ++j) { | |
| 61 | 5166 | if (j == k) { | |
| 62 | 1258 | continue; | |
| 63 | } | ||
| 64 | 3908 | const Vec3d between = seeds[j] - seeds[k]; | |
| 65 | 3908 | const double length = Length(between); | |
| 66 | 3908 | const Vec3d normal = between * (1.0 / length); | |
| 67 | 3908 | const double plane = 0.5 * length; | |
| 68 | 1967692 | for (Vec3d& p : piece.positions) { | |
| 69 | 1963784 | const Vec3d ray = p - seeds[k]; | |
| 70 | 1963784 | const double along = Dot(normal, ray); | |
| 71 | 1963784 | if (along > plane) { | |
| 72 | 710301 | p = seeds[k] + ray * (plane / along); | |
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | 1258 | return piece; | |
| 77 | } | ||
| 78 | |||
| 79 | } // namespace | ||
| 80 | |||
| 81 | 339 | std::vector<BoulderFragment> FragmentBoulderShape(const BoulderShape& parent, int pieces, | |
| 82 | std::uint64_t seed, BoulderCracks cracks) { | ||
| 83 | 339 | if (pieces < 2 || pieces > 12) { | |
| 84 | 2 | throw std::invalid_argument("A boulder breaks into 2 to 12 pieces"); | |
| 85 | } | ||
| 86 | RandomStream random(static_cast<std::uint32_t>(seed), kFragmentStream, | ||
| 87 | 337 | static_cast<std::uint32_t>(seed >> 32)); | |
| 88 | 337 | const Box3d box = Bounds(parent.mesh); | |
| 89 | const std::vector<Vec3d> seeds = cracks == BoulderCracks::Slabs | ||
| 90 | 337 | ? SlabSeedPoints(box, pieces, random) | |
| 91 | 337 | : SeedPoints(box, pieces, random); | |
| 92 | 337 | std::vector<BoulderFragment> fragments; | |
| 93 | 1595 | for (std::size_t k = 0; k < seeds.size(); ++k) { | |
| 94 | 1258 | const TriangleMesh cut = CutToCell(parent.mesh, seeds, k); | |
| 95 | 1258 | const Box3d bounds = Bounds(cut); | |
| 96 | 1258 | const Vec3d extents = bounds.max - bounds.min; | |
| 97 | 1258 | BoulderFragment fragment; | |
| 98 | 1258 | fragment.centre = (bounds.min + bounds.max) * 0.5; | |
| 99 | 1258 | fragment.scale = std::fmax(extents.x, extents.y); | |
| 100 | 1258 | fragment.shape.mesh.triangles = cut.triangles; | |
| 101 | 638462 | for (const Vec3d& p : cut.positions) { | |
| 102 | 637204 | fragment.shape.mesh.positions.push_back((p - fragment.centre) * (1.0 / fragment.scale)); | |
| 103 | } | ||
| 104 | 1258 | fragment.shape.style = parent.style; | |
| 105 | 1258 | fragment.shape.intermediate = std::fmin(extents.x, extents.y) / fragment.scale; | |
| 106 | 1258 | fragment.shape.shortAxis = extents.z / fragment.scale; | |
| 107 | 1258 | fragment.shape.material = parent.material; | |
| 108 | 1258 | fragments.push_back(std::move(fragment)); | |
| 109 | 1258 | } | |
| 110 | 674 | return fragments; | |
| 111 | 337 | } | |
| 112 | |||
| 113 | } // namespace terraingen | ||
| 114 |