19#include <unordered_set>
25 std::vector<glm::ivec3> crack;
26 float band_outer = 0.0f;
28 const char* stop =
"ok";
31 size_t band_voxels = 0;
34inline constexpr float k_min_crack_half_width = 0.5f * 1.7320508f;
37constexpr auto patternShatters(FracturePattern pattern) ->
bool {
38 return pattern != FracturePattern::ductile;
42inline auto crackThickness() ->
float {
43 return 2.0f * k_min_crack_half_width * voxel::k_voxel_size;
47inline auto minChunkVoxels(
float crack_thickness) -> uint32_t {
48 const float side = (2.0f * crack_thickness) / voxel::k_voxel_size;
49 return static_cast<uint32_t
>(std::ceil(side * side * side));
53inline auto craterVolume(
const Crater& crater) ->
float {
54 return capsuleVolume(crater.radius, glm::length(crater.end - crater.start));
58inline auto planShatter(
59 const voxel::Volume& volume,
const voxel::Palette& palette,
const voxel::MaterialLibrary& materials,
const Crater& crater,
63 out.band_outer = craterMaxRadius(crater);
66 if (not crater.breaks || not(crater.shatter_energy > 0.0f) || not patternShatters(crater.pattern) ||
67 not(crater.radius > 0.0f) || materials.materials.empty()) {
68 out.stop =
"preconditions";
72 const float crater_volume = craterVolume(crater);
73 out.density = crater.shatter_energy / crater_volume;
74 if (out.density < values.shatter_threshold * crater.toughness) {
75 out.stop =
"energy density below the threshold of the material";
79 const float threshold = crackThickness();
80 const float min_width = 2.0f * threshold;
81 const float max_width = std::max(tunables().fracture_max_shatter_distance, min_width);
90 std::vector<BandVoxel> band;
91 float toughness_sum = 0.0f;
92 const float reach = craterMaxRadius(crater) + max_width;
95 glm::min(crater.start, crater.end) - glm::vec3(reach),
96 glm::max(crater.start, crater.end) + glm::vec3(reach),
97 [&](glm::ivec3 v, glm::vec3 center, uint8_t index) {
98 const glm::vec3 axis_point = closestPointOnSegment(center, crater.start, crater.end);
99 const float outside = glm::length(center - axis_point) - craterRadiusAt(crater, center);
100 if (outside <= 0.0f || outside > max_width) {
103 const voxel::PhysicalMaterial& material = materials.materials[voxel::resolveMaterialIndex(palette, materials, index)];
104 if (not material.breakableBy(max_hardness) || not damageReaches(crater.reachable, volume, v, axis_point, center)) {
107 band.push_back({.voxel = v, .center = center, .outside = outside});
108 toughness_sum += material.toughness;
112 out.stop =
"no breakable material around the crater";
116 const float cell_size = std::max(values.shatter_cell_size, threshold);
117 const float mean_toughness = toughness_sum /
static_cast<float>(band.size());
118 const float length = glm::length(crater.end - crater.start);
119 const float base_volume = capsuleVolume(crater.radius, length);
120 const float fill = std::min(1.0f, 3.0f * threshold / cell_size);
121 const auto band_volume_of = [&](
float w) {
return capsuleVolume(crater.radius + w, length) - base_volume; };
122 const auto cost_of = [&](
float w) {
return mean_toughness * values.shatter_crack_cost *
fill * band_volume_of(w); };
123 float width = min_width;
124 if (cost_of(max_width) <= crater.shatter_energy) {
126 }
else if (cost_of(min_width) < crater.shatter_energy) {
127 float low = min_width;
128 float high = max_width;
129 for (
int i = 0; i < 24; ++i) {
130 const float middle = 0.5f * (low + high);
131 (cost_of(middle) <= crater.shatter_energy ? low : high) = middle;
136 out.band_outer = craterMaxRadius(crater) + width;
137 std::erase_if(band, [width](
const BandVoxel& b) {
return b.outside > width; });
138 out.band_voxels = band.size();
139 if (band.size() < 2) {
140 out.stop =
"no breakable material around the crater";
144 const float cells_wanted = std::clamp(
145 band_volume_of(width) / (cell_size * cell_size * cell_size),
147 static_cast<float>(std::max(tunables().fracture_max_shatter_cells, 2u))
149 const auto cell_count =
static_cast<uint32_t
>(cells_wanted);
152 Rng rng {streamSeed(crater.seed, FractureStream::shatter_points)};
153 const float min_gap = 2.0f * threshold;
154 std::vector<glm::vec3> seeds;
155 for (uint32_t attempt = 0; attempt < cell_count * 12 && seeds.size() < cell_count; ++attempt) {
156 const glm::vec3 point = band[rng.below(
static_cast<uint32_t
>(band.size()))].center;
157 const bool crowded = std::ranges::any_of(seeds, [&](
const glm::vec3& s) {
return glm::distance(s, point) < min_gap; });
159 seeds.push_back(point);
162 if (seeds.size() < 2) {
163 out.stop =
"fewer than 2 seeds";
166 out.cells =
static_cast<uint32_t
>(seeds.size());
169 const bool fibrous = crater.pattern == FracturePattern::fibrous;
170 const glm::uvec3 dims = volume.voxelDims();
171 glm::vec3 grain {0.0f};
172 if (dims.x >= dims.y && dims.x >= dims.z) {
175 grain[(dims.y >= dims.z ? 1 : 2)] = 1.0f;
177 const auto metric = [&](
const glm::vec3& offset) {
179 return glm::length(offset);
181 const float along = glm::dot(offset, grain);
182 const glm::vec3 lateral = offset - (grain * along);
183 const float stretched = along / std::max(values.shatter_stretch, 1.0f);
184 return std::sqrt(glm::dot(lateral, lateral) + (stretched * stretched));
187 std::unordered_set<uint64_t> in_band;
188 in_band.reserve(band.size());
189 for (
const BandVoxel& b : band) {
190 in_band.insert(voxelKey(b.voxel));
194 const glm::ivec3 dims_i = glm::ivec3(dims);
195 const std::array<glm::ivec3, k_unglue_neighbor_count> k_faces {
196 glm::ivec3 { 1, 0, 0},
197 glm::ivec3 {-1, 0, 0},
198 glm::ivec3 { 0, 1, 0},
199 glm::ivec3 { 0, -1, 0},
200 glm::ivec3 { 0, 0, 1},
201 glm::ivec3 { 0, 0, -1},
203 const auto touches_wall = [&](
const glm::ivec3& v) {
204 for (
const glm::ivec3& offset : k_faces) {
205 const glm::ivec3 n = v + offset;
206 if (glm::any(glm::lessThan(n, glm::ivec3(0))) || glm::any(glm::greaterThanEqual(n, dims_i)) ||
207 volume.materialAt(n) == voxel::k_empty_palette_index || in_band.contains(voxelKey(n))) {
210 const glm::vec3 center = (glm::vec3 {n} + 0.5f) * voxel::k_voxel_size;
211 const float radius = craterRadiusAt(crater, center);
212 if (pointSegmentDistanceSquared(center, crater.start, crater.end) > radius * radius) {
225 std::vector<Owner> owners(band.size());
226 std::vector<uint32_t> cell_voxels(seeds.size(), 0);
227 for (
size_t b = 0; b < band.size(); ++b) {
228 float d1 = std::numeric_limits<float>::max();
229 float d2 = std::numeric_limits<float>::max();
230 Owner& owner = owners[b];
231 for (
size_t i = 0; i < seeds.size(); ++i) {
232 const float d = metric(band[b].center - seeds[i]);
235 owner.second = owner.nearest;
244 ++cell_voxels[owner.nearest];
247 const uint32_t min_cell_voxels = minChunkVoxels(threshold);
248 const float release = std::clamp(values.shatter_release, 0.0f, 1.0f);
249 std::vector<bool> released(seeds.size(),
false);
250 std::vector<size_t> eligible;
251 for (
size_t i = 0; i < seeds.size(); ++i) {
252 if (cell_voxels[i] >= min_cell_voxels) {
253 eligible.push_back(i);
254 released[i] = rng.chance(release);
257 if (eligible.empty()) {
258 out.stop =
"cells too small to come off";
261 if (std::ranges::none_of(eligible, [&](
size_t i) {
return released[i]; })) {
262 released[eligible[rng.below(
static_cast<uint32_t
>(eligible.size()))]] =
true;
265 const uint64_t crumble_seed = streamSeed(crater.seed, FractureStream::granular);
266 for (
size_t b = 0; b < band.size(); ++b) {
267 const Owner& owner = owners[b];
268 const bool mine = released[owner.nearest];
269 const bool border = owner.gap < threshold && (mine || released[owner.second]);
271 crack = crack || (mine && touches_wall(band[b].voxel));
273 (mine && crater.pattern == FracturePattern::granular && hash01(crumble_seed, band[b].voxel) < values.shatter_crumble);
275 out.crack.push_back(band[b].voxel);
282 glm::ivec3 voxel {0};
283 glm::vec3 center {0.0f};
287 std::vector<glm::ivec3> crack;
289 float density = 0.0f;
293inline auto planCoreSplit(std::span<const CoreVoxel> core,
const Crater& crater) ->
CoreSplit {
296 if (not(crater.fragment_energy > 0.0f) || not(crater.radius > 0.0f)) {
299 out.density = crater.fragment_energy / craterVolume(crater);
300 const float part_density = std::max(values.fragment_threshold * crater.toughness, 1.0e-9f);
301 if (out.density < part_density) {
305 Rng rng {streamSeed(crater.seed, FractureStream::core_split)};
306 const float doublings = std::log2(out.density / part_density);
307 const float parts_wanted =
308 std::clamp(2.0f + std::floor(doublings), 2.0f,
static_cast<float>(std::max(values.fragment_max_parts, 2u)));
310 const float threshold = crackThickness();
311 const auto room =
static_cast<uint32_t
>(core.size() / minChunkVoxels(threshold));
312 const uint32_t parts = std::min(
static_cast<uint32_t
>(parts_wanted), room);
317 std::vector<glm::vec3> seeds {core[rng.below(
static_cast<uint32_t
>(core.size()))].center};
318 while (seeds.size() < parts) {
319 glm::vec3 best = seeds.front();
320 float best_gap = -1.0f;
321 for (
int attempt = 0; attempt < 12; ++attempt) {
322 const glm::vec3 point = core[rng.below(
static_cast<uint32_t
>(core.size()))].center;
323 float gap = std::numeric_limits<float>::max();
324 for (
const glm::vec3& s : seeds) {
325 gap = std::min(gap, glm::distance(s, point));
327 if (gap > best_gap) {
332 if (best_gap < 2.0f * threshold) {
335 seeds.push_back(best);
337 if (seeds.size() < 2) {
340 out.parts =
static_cast<uint32_t
>(seeds.size());
342 for (
const CoreVoxel& c : core) {
343 float d1 = std::numeric_limits<float>::max();
344 float d2 = std::numeric_limits<float>::max();
345 for (
const glm::vec3& seed_point : seeds) {
346 const float d = glm::distance(c.center, seed_point);
354 if (d2 - d1 < threshold) {
355 out.crack.push_back(c.voxel);
How much of a voxel volume a hit breaks.
Definition shatter.hpp:286
Definition shatter.hpp:281
Definition fracture.hpp:75
Definition fracture_profile.hpp:17
Definition fracture_random.hpp:99
Definition shatter.hpp:24
@ release
the input went from on to off
Definition value.hpp:79
@ fill
Adds its voxels with its write mode.
Definition voxel_piece.hpp:27