Toast Engine
Loading...
Searching...
No Matches
voxel_smash.hpp
Go to the documentation of this file.
1
6
7#pragma once
8
10
11#include <algorithm>
12#include <cmath>
13#include <glm/glm.hpp>
14#include <limits>
15#include <toast/voxel/brick.hpp>
20#include <utility>
21#include <vector>
22
23namespace physics {
24
25inline constexpr float k_no_floor = -1.0e30f;
26
27[[nodiscard]]
28inline auto segmentBoxDistanceSquared(const glm::vec3& from, const glm::vec3& to, const glm::vec3& center, float half_extent)
29 -> float {
30 const auto squared = [&](float t) {
31 const glm::vec3 gap = glm::max(glm::abs(glm::mix(from, to, t) - center) - glm::vec3(half_extent), glm::vec3(0.0f));
32 return glm::dot(gap, gap);
33 };
34
35 float low = 0.0f;
36 float high = 1.0f;
37 for (int i = 0; i < 32; ++i) {
38 const float first = low + ((high - low) / 3.0f);
39 const float second = high - ((high - low) / 3.0f);
40 if (squared(first) < squared(second)) {
41 high = second;
42 } else {
43 low = first;
44 }
45 }
46 return squared(0.5f * (low + high));
47}
48
50 glm::vec3 axis_start {};
51 glm::vec3 axis_end {};
52 glm::vec3 sweep {};
53 float reach = 0.0f;
54 glm::vec3 up {0.0f, 0.0f, 1.0f};
55 float floor = k_no_floor;
56 bool touching = false;
57
58 [[nodiscard]]
59 auto poseCount() const -> int {
60 if (touching || not(reach > 0.0f)) {
61 return 1;
62 }
63 return std::max(1, static_cast<int>(std::ceil(glm::length(sweep) / (0.25f * reach))));
64 }
65
66 [[nodiscard]]
67 auto contains(const glm::vec3& point, int poses) const -> bool {
68 if (glm::dot(point, up) < floor) {
69 return false;
70 }
71 if (touching) {
72 return segmentBoxDistanceSquared(axis_start, axis_start + sweep, point, 0.5f * voxel::k_voxel_size) <=
73 (reach * reach) + 1.0e-8f;
74 }
75
76 const glm::vec3 axis = axis_end - axis_start;
77 const float axis_squared = glm::dot(axis, axis);
78 const float reach_squared = reach * reach;
79 for (int pose = 0; pose <= poses; ++pose) {
80 const glm::vec3 offset = point - (axis_start + (sweep * (static_cast<float>(pose) / static_cast<float>(poses))));
81 const float along = axis_squared > 0.0f ? glm::clamp(glm::dot(offset, axis) / axis_squared, 0.0f, 1.0f) : 0.0f;
82 const glm::vec3 from_axis = offset - (axis * along);
83 if (glm::dot(from_axis, from_axis) <= reach_squared) {
84 return true;
85 }
86 }
87 return false;
88 }
89
90 [[nodiscard]]
91 auto bounds() const -> std::pair<glm::vec3, glm::vec3> {
92 const glm::vec3 low = glm::min(axis_start, axis_end);
93 const glm::vec3 high = glm::max(axis_start, axis_end);
94 return {glm::min(low, low + sweep) - glm::vec3(reach), glm::max(high, high + sweep) + glm::vec3(reach)};
95 }
96};
97
99 std::vector<DetachedComponent> pieces;
100 uint32_t voxels = 0;
101 glm::vec3 index_sum {0.0f};
102};
103
104template<typename Breaks>
105[[nodiscard]]
106auto collectBrokenPieces(
107 const voxel::Volume& volume, const voxel::Palette& palette, const voxel::MaterialLibrary& materials, const glm::vec3& low,
108 const glm::vec3& high, Breaks&& breaks
109) -> SmashPieces {
110 SmashPieces out;
111 if (materials.materials.empty()) {
112 return out;
113 }
114
115 const glm::ivec3 voxel_dims = glm::ivec3(volume.voxelDims());
116 glm::ivec3 first = glm::ivec3(glm::floor(low / voxel::k_voxel_size));
117 glm::ivec3 last = glm::ivec3(glm::ceil(high / voxel::k_voxel_size)) - 1;
118 first = glm::clamp(first, glm::ivec3(0), voxel_dims - 1);
119 last = glm::clamp(last, glm::ivec3(0), voxel_dims - 1);
120 if (first.x > last.x || first.y > last.y || first.z > last.z) {
121 return out;
122 }
123
124 const auto brick_dim = static_cast<int32_t>(voxel::k_brick_dim);
125 const glm::ivec3 first_brick = first / brick_dim;
126 const glm::ivec3 last_brick = last / brick_dim;
127
128 for (int32_t bz = first_brick.z; bz <= last_brick.z; ++bz) {
129 for (int32_t by = first_brick.y; by <= last_brick.y; ++by) {
130 for (int32_t bx = first_brick.x; bx <= last_brick.x; ++bx) {
131 const glm::ivec3 brick {bx, by, bz};
132 const voxel::BrickOccupancy* occupancy = volume.occupancyPointer(brick);
133 if (occupancy == nullptr) {
134 continue;
135 }
136
137 voxel::BrickOccupancy mask {};
138 const glm::ivec3 brick_first = glm::max(first, brick * brick_dim);
139 const glm::ivec3 brick_last = glm::min(last, (brick * brick_dim) + (brick_dim - 1));
140 for (int32_t z = brick_first.z; z <= brick_last.z; ++z) {
141 for (int32_t y = brick_first.y; y <= brick_last.y; ++y) {
142 for (int32_t x = brick_first.x; x <= brick_last.x; ++x) {
143 const glm::ivec3 voxel {x, y, z};
144 const glm::ivec3 local = voxel - (brick * brick_dim);
145 if (not voxel::isSolid(
146 *occupancy, static_cast<uint32_t>(local.x), static_cast<uint32_t>(local.y), static_cast<uint32_t>(local.z)
147 )) {
148 continue;
149 }
150
151 const glm::vec3 center = (glm::vec3 {voxel} + 0.5f) * voxel::k_voxel_size;
152 const uint8_t palette_index = volume.materialAt(voxel);
153 const voxel::PhysicalMaterial& material =
154 materials.materials[voxel::resolveMaterialIndex(palette, materials, palette_index)];
155 if (material.isIndestructible() || not breaks(center, material)) {
156 continue;
157 }
158
159 voxel::setSolid(
160 mask, static_cast<uint32_t>(local.x), static_cast<uint32_t>(local.y), static_cast<uint32_t>(local.z), true
161 );
162 out.index_sum += glm::vec3(voxel);
163 ++out.voxels;
164 }
165 }
166 }
167
168 if (voxel::isEmpty(mask)) {
169 continue;
170 }
171 for (const voxel::BrickOccupancy& region : voxel::brickComponents(mask)) {
172 DetachedComponent piece;
173 piece.component = static_cast<uint32_t>(out.pieces.size());
174 piece.voxel_count = voxel::popCount(region);
175 piece.pieces.push_back(voxel::BrickPiece {.brick = brick, .voxels = region, .component = piece.component});
176 out.pieces.push_back(std::move(piece));
177 }
178 }
179 }
180 }
181
182 return out;
183}
184
185[[nodiscard]]
186inline auto collectSmashPieces(
187 const voxel::Volume& volume, const voxel::Palette& palette, const voxel::MaterialLibrary& materials, const SmashVolume& smash,
188 float energy_density, Hardness max_hardness = k_hardest
189) -> SmashPieces {
190 const auto [low, high] = smash.bounds();
191 const int poses = smash.poseCount();
192 return collectBrokenPieces(
193 volume, palette, materials, low, high, [&](const glm::vec3& center, const voxel::PhysicalMaterial& material) {
194 return material.breakableBy(max_hardness) && material.toughness <= energy_density && smash.contains(center, poses);
195 }
196 );
197}
198
199}
Definition voxel_volume.hpp:21
TODO: Brief description of the file's purpose.
auto resolveMaterialIndex(const Palette &palette, const MaterialLibrary &library, uint8_t palette_index) noexcept -> uint32_t
The library must not be empty.
Definition palette.hpp:149
Definition component_classification.hpp:26
Definition voxel_smash.hpp:98
Definition voxel_smash.hpp:49
Definition palette.hpp:143
Entry 0 is the empty voxel and must stay all zero.
Definition palette.hpp:59
float toughness
kJ/m³ it takes to break per m3
Definition palette.hpp:106