1use glam::{Vec3, Vec4};
11use super::entity_field::{MetaballEntity, FieldSource};
12
13pub struct GpuMarchingCubes {
15 pub resolution: u32,
17 pub field_eval_shader: Option<u32>,
19 pub classify_shader: Option<u32>,
21 pub vertex_gen_shader: Option<u32>,
23 pub field_texture: Option<u32>,
25 pub vertex_ssbo: Option<u32>,
27 pub index_ssbo: Option<u32>,
29 pub indirect_buffer: Option<u32>,
31 pub max_vertices: u32,
33 pub last_vertex_count: u32,
35 pub last_triangle_count: u32,
37}
38
39impl GpuMarchingCubes {
40 pub fn new(resolution: u32) -> Self {
41 Self {
42 resolution,
43 field_eval_shader: None,
44 classify_shader: None,
45 vertex_gen_shader: None,
46 field_texture: None,
47 vertex_ssbo: None,
48 index_ssbo: None,
49 indirect_buffer: None,
50 max_vertices: resolution * resolution * resolution * 5 * 3, last_vertex_count: 0,
52 last_triangle_count: 0,
53 }
54 }
55
56 pub fn is_initialized(&self) -> bool {
58 self.field_eval_shader.is_some()
59 }
60}
61
62#[derive(Debug, Clone)]
65#[repr(C)]
66pub struct GpuFieldSource {
67 pub position: [f32; 4], pub strength_radius: [f32; 4], pub color: [f32; 4], pub emission_pad: [f32; 4], }
72
73impl GpuFieldSource {
74 pub fn from_source(source: &FieldSource, hp_ratio: f32) -> Self {
75 let falloff_id = match &source.falloff {
76 super::entity_field::FalloffType::InverseSquare => 0.0,
77 super::entity_field::FalloffType::Gaussian => 1.0,
78 super::entity_field::FalloffType::Wyvill => 2.0,
79 super::entity_field::FalloffType::Linear => 3.0,
80 super::entity_field::FalloffType::SmoothPoly => 4.0,
81 super::entity_field::FalloffType::Attractor(_) => 2.0, };
83 Self {
84 position: [source.position.x, source.position.y, source.position.z, 0.0],
85 strength_radius: [source.effective_strength(hp_ratio), source.radius, falloff_id, 0.0],
86 color: source.color.to_array(),
87 emission_pad: [source.emission, 0.0, 0.0, 0.0],
88 }
89 }
90}
91
92#[derive(Debug, Clone)]
94#[repr(C)]
95pub struct FieldEvalUniforms {
96 pub bounds_min: [f32; 4],
97 pub bounds_max: [f32; 4],
98 pub resolution: [u32; 4], pub threshold: [f32; 4], }
101
102impl FieldEvalUniforms {
103 pub fn from_entity(entity: &MetaballEntity) -> Self {
104 let (bmin, bmax) = entity.bounds();
105 Self {
106 bounds_min: [bmin.x, bmin.y, bmin.z, 0.0],
107 bounds_max: [bmax.x, bmax.y, bmax.z, 0.0],
108 resolution: [entity.grid_resolution, entity.grid_resolution, entity.grid_resolution, entity.active_source_count() as u32],
109 threshold: [entity.threshold, 0.0, 0.0, 0.0],
110 }
111 }
112}
113
114pub const FIELD_EVAL_COMPUTE: &str = r#"
118#version 430 core
119layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
120
121struct FieldSource {
122 vec4 position;
123 vec4 strength_radius; // strength, radius, falloff_type, 0
124 vec4 color;
125 vec4 emission_pad;
126};
127
128layout(std430, binding = 0) readonly buffer Sources { FieldSource sources[]; };
129layout(std430, binding = 1) writeonly buffer FieldValues { float field_values[]; };
130layout(std430, binding = 2) writeonly buffer FieldColors { vec4 field_colors[]; };
131
132uniform vec3 u_bounds_min;
133uniform vec3 u_bounds_max;
134uniform uint u_resolution;
135uniform uint u_source_count;
136
137float wyvill_falloff(float r, float R) {
138 if (r >= R) return 0.0;
139 float t = r * r / (R * R);
140 float v = 1.0 - t;
141 return v * v * v;
142}
143
144float gaussian_falloff(float r, float R) {
145 float sigma = R * 0.4;
146 return exp(-r * r / (2.0 * sigma * sigma));
147}
148
149float inverse_square_falloff(float r, float R) {
150 return 1.0 / (1.0 + (r * r) / (R * R));
151}
152
153void main() {
154 uvec3 gid = gl_GlobalInvocationID;
155 if (any(greaterThanEqual(gid, uvec3(u_resolution)))) return;
156
157 uint idx = gid.z * u_resolution * u_resolution + gid.y * u_resolution + gid.x;
158 vec3 step = (u_bounds_max - u_bounds_min) / float(u_resolution - 1u);
159 vec3 pos = u_bounds_min + vec3(gid) * step;
160
161 float total = 0.0;
162 vec4 weighted_color = vec4(0.0);
163
164 for (uint i = 0u; i < u_source_count; ++i) {
165 vec3 sp = sources[i].position.xyz;
166 float strength = sources[i].strength_radius.x;
167 float radius = sources[i].strength_radius.y;
168 float falloff_type = sources[i].strength_radius.z;
169
170 float dist = distance(pos, sp);
171 float contrib = 0.0;
172
173 if (falloff_type < 0.5) contrib = strength * inverse_square_falloff(dist, radius);
174 else if (falloff_type < 1.5) contrib = strength * gaussian_falloff(dist, radius);
175 else if (falloff_type < 2.5) contrib = strength * wyvill_falloff(dist, radius);
176 else contrib = strength * max(0.0, 1.0 - dist / radius);
177
178 total += contrib;
179 if (contrib > 0.0) {
180 weighted_color += sources[i].color * contrib;
181 }
182 }
183
184 field_values[idx] = total;
185 field_colors[idx] = total > 0.0 ? weighted_color / total : vec4(0.5, 0.5, 0.5, 1.0);
186}
187"#;
188
189pub const CLASSIFY_COMPUTE: &str = r#"
191#version 430 core
192layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
193
194layout(std430, binding = 0) readonly buffer FieldValues { float field_values[]; };
195layout(std430, binding = 1) buffer VertexCounts { uint vertex_counts[]; };
196
197uniform uint u_resolution;
198uniform float u_threshold;
199
200// Edge table and vertex count per configuration
201// (vertex_count_per_config[i] = number of vertices for cube config i)
202// Precomputed from the tri table: count non-(-1) entries / 1
203layout(std430, binding = 2) readonly buffer VertCountLUT { uint vert_count_lut[256]; };
204
205uint field_index(uint x, uint y, uint z) {
206 return z * u_resolution * u_resolution + y * u_resolution + x;
207}
208
209void main() {
210 uvec3 gid = gl_GlobalInvocationID;
211 uint res_m1 = u_resolution - 1u;
212 if (any(greaterThanEqual(gid, uvec3(res_m1)))) return;
213
214 uint x = gid.x, y = gid.y, z = gid.z;
215 uint cube_index = 0u;
216 float corners[8];
217 corners[0] = field_values[field_index(x, y, z)];
218 corners[1] = field_values[field_index(x+1, y, z)];
219 corners[2] = field_values[field_index(x+1, y+1, z)];
220 corners[3] = field_values[field_index(x, y+1, z)];
221 corners[4] = field_values[field_index(x, y, z+1)];
222 corners[5] = field_values[field_index(x+1, y, z+1)];
223 corners[6] = field_values[field_index(x+1, y+1, z+1)];
224 corners[7] = field_values[field_index(x, y+1, z+1)];
225
226 for (uint i = 0u; i < 8u; ++i) {
227 if (corners[i] >= u_threshold) cube_index |= (1u << i);
228 }
229
230 uint cell_idx = z * res_m1 * res_m1 + y * res_m1 + x;
231 vertex_counts[cell_idx] = vert_count_lut[cube_index];
232}
233"#;
234
235pub const VERTEX_GEN_COMPUTE: &str = r#"
237#version 430 core
238layout(local_size_x = 64) in;
239
240struct MCVertex {
241 vec4 position;
242 vec4 normal;
243 vec4 color;
244 vec4 emission; // emission in x, unused yzw
245};
246
247layout(std430, binding = 0) readonly buffer FieldValues { float field_values[]; };
248layout(std430, binding = 1) readonly buffer FieldColors { vec4 field_colors[]; };
249layout(std430, binding = 2) readonly buffer PrefixSums { uint prefix_sums[]; };
250layout(std430, binding = 3) writeonly buffer Vertices { MCVertex vertices[]; };
251layout(std430, binding = 4) readonly buffer TriTable { int tri_table[4096]; }; // 256 * 16
252layout(std430, binding = 5) readonly buffer EdgeTable { uint edge_table[256]; };
253
254uniform uint u_resolution;
255uniform float u_threshold;
256uniform vec3 u_bounds_min;
257uniform vec3 u_bounds_max;
258
259// ... (vertex generation kernel omitted for brevity — mirrors CPU marching cubes logic)
260void main() {
261 // Each work item processes one cell, looks up its prefix sum offset,
262 // and writes vertices to that offset in the SSBO.
263 // Full implementation mirrors the CPU ExtractedMesh generation.
264}
265"#;
266
267#[derive(Debug, Clone, Default)]
271pub struct GpuMCStats {
272 pub resolution: u32,
273 pub source_count: u32,
274 pub vertex_count: u32,
275 pub triangle_count: u32,
276 pub field_eval_time_us: u32,
277 pub classify_time_us: u32,
278 pub vertex_gen_time_us: u32,
279 pub total_time_us: u32,
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn gpu_field_source_from_source() {
288 let source = FieldSource::new(Vec3::new(1.0, 2.0, 3.0), 0.8, 1.5)
289 .with_color(Vec4::new(1.0, 0.0, 0.0, 1.0));
290 let gpu = GpuFieldSource::from_source(&source, 1.0);
291 assert_eq!(gpu.position[0], 1.0);
292 assert_eq!(gpu.strength_radius[0], 0.8);
293 assert_eq!(gpu.color[0], 1.0); }
295
296 #[test]
297 fn field_eval_uniforms_from_entity() {
298 let mut e = MetaballEntity::new(0.5, 32);
299 e.add_source(FieldSource::new(Vec3::ZERO, 1.0, 2.0));
300 let uniforms = FieldEvalUniforms::from_entity(&e);
301 assert_eq!(uniforms.resolution[0], 32);
302 assert_eq!(uniforms.resolution[3], 1); }
304
305 #[test]
306 fn gpu_mc_new() {
307 let gpu = GpuMarchingCubes::new(32);
308 assert_eq!(gpu.resolution, 32);
309 assert!(!gpu.is_initialized());
310 }
311}