MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
MarchingCube.cpp
Go to the documentation of this file.
2
3#include "KMarchingTable.hpp"
5
6#ifdef MAYAFLUX_ARCH_X64
7#include <immintrin.h>
8#endif
9#ifdef MAYAFLUX_ARCH_ARM64
10#include <arm_neon.h>
11#endif
12
13namespace MayaFlux::Kinesis {
14
15[[nodiscard]] inline uint32_t compute_cube_index(const float* v, float iso) noexcept
16{
17#ifdef MAYAFLUX_ARCH_X64
18 const __m256 vals = _mm256_loadu_ps(v);
19 const __m256 threshold = _mm256_set1_ps(iso);
20 const __m256 cmp = _mm256_cmp_ps(vals, threshold, _CMP_LT_OS);
21 return static_cast<uint32_t>(_mm256_movemask_ps(cmp));
22#elif defined(MAYAFLUX_ARCH_ARM64)
23 const float32x4_t lo = vld1q_f32(v);
24 const float32x4_t hi = vld1q_f32(v + 4);
25 const float32x4_t thresh = vdupq_n_f32(iso);
26
27 // vcltq_f32 -> 0xFFFFFFFF per lane where v[i] < iso, 0x00000000 otherwise.
28 // Shift each lane right by (31 - bit_position) to land the set bit at the
29 // correct position in the final scalar, then sum across lanes.
30 const uint32x4_t lo_mask = vcltq_f32(lo, thresh);
31 const uint32x4_t hi_mask = vcltq_f32(hi, thresh);
32
33 // Isolate bit 0 of each lane (the sign bit after the comparison saturates).
34 // vshrq_n_u32 by 31 collapses each 0xFFFFFFFF to 1 and 0x00000000 to 0.
35 alignas(16) static const uint32_t k_lo_shifts[4] = { 31, 30, 29, 28 };
36 alignas(16) static const uint32_t k_hi_shifts[4] = { 27, 26, 25, 24 };
37
38 const uint32x4_t lo_bits = vshlq_u32(vshrq_n_u32(lo_mask, 31),
39 vld1q_s32(reinterpret_cast<const int32_t*>(k_lo_shifts)));
40 const uint32x4_t hi_bits = vshlq_u32(vshrq_n_u32(hi_mask, 31),
41 vld1q_s32(reinterpret_cast<const int32_t*>(k_hi_shifts)));
42
43 const uint32x4_t combined = vorrq_u32(lo_bits, hi_bits);
44 return vaddvq_u32(combined);
45#else
46 uint32_t idx = 0;
47 idx |= static_cast<uint32_t>(v[0] < iso) << 0;
48 idx |= static_cast<uint32_t>(v[1] < iso) << 1;
49 idx |= static_cast<uint32_t>(v[2] < iso) << 2;
50 idx |= static_cast<uint32_t>(v[3] < iso) << 3;
51 idx |= static_cast<uint32_t>(v[4] < iso) << 4;
52 idx |= static_cast<uint32_t>(v[5] < iso) << 5;
53 idx |= static_cast<uint32_t>(v[6] < iso) << 6;
54 idx |= static_cast<uint32_t>(v[7] < iso) << 7;
55 return idx;
56#endif
57}
58
59// =============================================================================
60// generate_sdf_mesh
61// =============================================================================
62
64 glm::vec3 position;
65 glm::vec3 normal;
66};
67
69 const Kinesis::SpatialField& field,
70 const glm::vec3& bounds_min,
71 const glm::vec3& bounds_max,
72 uint32_t res_x,
73 uint32_t res_y,
74 uint32_t res_z,
75 float iso_level)
76{
77 res_x = std::max(res_x, 1U);
78 res_y = std::max(res_y, 1U);
79 res_z = std::max(res_z, 1U);
80
81 const glm::vec3 extent = bounds_max - bounds_min;
82 const glm::vec3 step {
83 extent.x / static_cast<float>(res_x),
84 extent.y / static_cast<float>(res_y),
85 extent.z / static_cast<float>(res_z),
86 };
87
88 const uint32_t nx = res_x + 1;
89 const uint32_t ny = res_y + 1;
90 const uint32_t nz = res_z + 1;
91
92 const uint32_t px = nx + 2;
93 const uint32_t py = ny + 2;
94 const uint32_t pz = nz + 2;
95 const size_t num_voxels = static_cast<size_t>(px) * py * pz;
96
97 const size_t sx = 1;
98 const size_t sy = px;
99 const size_t sz = static_cast<size_t>(py) * px;
100
101 std::vector<float> vals(num_voxels);
102 for (uint32_t iz = 0; iz < pz; ++iz) {
103 float z = bounds_min.z + static_cast<float>(static_cast<int32_t>(iz) - 1) * step.z;
104 for (uint32_t iy = 0; iy < py; ++iy) {
105 float y = bounds_min.y + static_cast<float>(static_cast<int32_t>(iy) - 1) * step.y;
106 size_t base_idx = (static_cast<size_t>(iz) * py + iy) * px;
107 for (uint32_t ix = 0; ix < px; ++ix) {
108 float x = bounds_min.x + static_cast<float>(static_cast<int32_t>(ix) - 1) * step.x;
109 vals[base_idx + ix] = field(glm::vec3(x, y, z));
110 }
111 }
112 }
113
114 std::vector<Kakshya::MeshVertex> verts;
115 std::vector<uint32_t> indices;
116 verts.reserve(static_cast<size_t>(res_x * res_y * res_z) * 3);
117 indices.reserve(static_cast<size_t>(res_x * res_y * res_z) * 3);
118
119 auto grad_at = [&](size_t ptr) -> glm::vec3 {
120 glm::vec3 n {
121 (vals[ptr + sx] - vals[ptr - sx]) / step.x,
122 (vals[ptr + sy] - vals[ptr - sy]) / step.y,
123 (vals[ptr + sz] - vals[ptr - sz]) / step.z
124 };
125 float len = glm::length(n);
126 return len > 1e-7F ? (n / len) : glm::vec3(0.0F, 1.0F, 0.0F);
127 };
128
129 for (uint32_t iz = 0; iz < res_z; ++iz) {
130 float z0 = bounds_min.z + static_cast<float>(iz) * step.z;
131 float z1 = z0 + step.z;
132
133 for (uint32_t iy = 0; iy < res_y; ++iy) {
134 float y0 = bounds_min.y + static_cast<float>(iy) * step.y;
135 float y1 = y0 + step.y;
136
137 size_t voxel_ptr = (static_cast<size_t>(iz + 1) * py + (iy + 1)) * px + 1;
138
139 for (uint32_t ix = 0; ix < res_x; ++ix, ++voxel_ptr) {
140 const float v[8] = {
141 vals[voxel_ptr], vals[voxel_ptr + sx], vals[voxel_ptr + sy + sx], vals[voxel_ptr + sy],
142 vals[voxel_ptr + sz], vals[voxel_ptr + sz + sx], vals[voxel_ptr + sz + sy + sx], vals[voxel_ptr + sz + sy]
143 };
144
145 const uint32_t cube_idx = compute_cube_index(v, iso_level);
146 const uint16_t edges = k_edge_table[cube_idx];
147 if (edges == 0)
148 continue;
149
150 float x0 = bounds_min.x + static_cast<float>(ix) * step.x;
151 float x1 = x0 + step.x;
152 const glm::vec3 c[8] = {
153 { x0, y0, z0 }, { x1, y0, z0 }, { x1, y1, z0 }, { x0, y1, z0 },
154 { x0, y0, z1 }, { x1, y0, z1 }, { x1, y1, z1 }, { x0, y1, z1 }
155 };
156
157 const glm::vec3 nrm[8] = {
158 grad_at(voxel_ptr), grad_at(voxel_ptr + sx), grad_at(voxel_ptr + sy + sx), grad_at(voxel_ptr + sy),
159 grad_at(voxel_ptr + sz), grad_at(voxel_ptr + sz + sx), grad_at(voxel_ptr + sz + sy + sx), grad_at(voxel_ptr + sz + sy)
160 };
161
162 InterpolatedEdge ep[12];
163
164 auto interp_edge = [&](int i1, int i2) -> InterpolatedEdge {
165 float va = v[i1], vb = v[i2];
166 float d = vb - va;
167 float t = (std::abs(d) < 1e-7F) ? 0.5F : ((iso_level - va) / d);
168
169 return {
170 .position = c[i1] + t * (c[i2] - c[i1]),
171 .normal = glm::normalize(nrm[i1] + t * (nrm[i2] - nrm[i1]))
172 };
173 };
174
175 if (edges & 0x001)
176 ep[0] = interp_edge(0, 1);
177 if (edges & 0x002)
178 ep[1] = interp_edge(1, 2);
179 if (edges & 0x004)
180 ep[2] = interp_edge(2, 3);
181 if (edges & 0x008)
182 ep[3] = interp_edge(3, 0);
183 if (edges & 0x010)
184 ep[4] = interp_edge(4, 5);
185 if (edges & 0x020)
186 ep[5] = interp_edge(5, 6);
187 if (edges & 0x040)
188 ep[6] = interp_edge(6, 7);
189 if (edges & 0x080)
190 ep[7] = interp_edge(7, 4);
191 if (edges & 0x100)
192 ep[8] = interp_edge(0, 4);
193 if (edges & 0x200)
194 ep[9] = interp_edge(1, 5);
195 if (edges & 0x400)
196 ep[10] = interp_edge(2, 6);
197 if (edges & 0x800)
198 ep[11] = interp_edge(3, 7);
199
200 const auto& tri = k_tri_table[cube_idx];
201
202 for (int i = 0; tri[i] != -1; i += 3) {
203 const auto base = static_cast<uint32_t>(verts.size());
204
205 for (int j = 0; j < 3; ++j) {
206 const auto& edge_data = ep[tri[i + j]];
207 glm::vec2 uv((edge_data.position.x - bounds_min.x) / extent.x,
208 (edge_data.position.y - bounds_min.y) / extent.y);
209
210 verts.push_back({ .position = edge_data.position,
211 .uv = uv,
212 .normal = edge_data.normal });
213 }
214 indices.insert(indices.end(), { base, base + 1, base + 2 });
215 }
216 }
217 }
218 }
219
220 if (verts.empty())
222
223 auto data = Kakshya::MeshData::empty();
224 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
225 ins.insert_flat(
226 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()), verts.size() * sizeof(Kakshya::MeshVertex)),
227 std::span<const uint32_t>(indices),
229
231 data.layout.vertex_count = static_cast<uint32_t>(verts.size());
232 return data;
233}
234
235} // namespace MayaFlux::Kinesis
const uint8_t * ptr
void insert_flat(std::span< const uint8_t > vertex_bytes, std::span< const uint32_t > index_data, const VertexLayout &layout)
Insert a single flat mesh (no submesh tracking).
Write counterpart to MeshAccess.
uint32_t compute_cube_index(const float *v, float iso) noexcept
Tendency< D, float > threshold(const Tendency< D, float > &t, float thresh)
Zero output below threshold, pass through above.
Definition Tendency.hpp:141
constexpr std::array< uint16_t, 256 > k_edge_table
MAYAFLUX_API Kakshya::MeshData generate_sdf_mesh(const Kinesis::SpatialField &field, const glm::vec3 &bounds_min, const glm::vec3 &bounds_max, uint32_t res_x, uint32_t res_y, uint32_t res_z, float iso_level=0.0F)
Extract an isosurface from a scalar field using marching cubes.
constexpr std::array< std::array< int8_t, 16 >, 256 > k_tri_table
static MeshData empty()
Construct an empty MeshData with the canonical 60-byte mesh layout.
Definition MeshData.hpp:49
Owning CPU-side representation of a loaded or generated mesh.
Definition MeshData.hpp:33
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22