| … | … | @@ -11,6 +11,7 @@ |
| 11 | 11 | |
| 12 | 12 | #include <godot_cpp/variant/utility_functions.hpp> |
| 13 | 13 | |
| 14 | #include <thread> |
| 14 | 15 | #include <vector> |
| 15 | 16 | |
| 16 | 17 | namespace godot { |
| … | … | @@ -20,9 +21,16 @@ namespace godot { |
| 20 | 21 | void MeshToSDFGridNode::_bind_methods() {} |
| 21 | 22 | |
| 22 | 23 | MeshToSDFGridNode::MeshToSDFGridNode() { |
| 24 | // ⛔ THE DEFAULTS ARE BLENDER'S, and that is not cosmetic. The bridge sends |
| 25 | // only MODIFIED defaults, so a socket left alone in Blender arrives carrying |
| 26 | // OURS. Voxel Size was 0.1 against Blender's 0.30 -- three times the |
| 27 | // resolution on every axis, TWENTY-SEVEN times the samples, and every one of |
| 28 | // them a BVH closest-point query. That alone was most of the 75 s the |
| 29 | // bridged mountain spent in this node. Band Width is an INT on Blender's |
| 30 | // node, not a float. |
| 23 | 31 | input_defs.push_back({ "Mesh", SocketType::Geometry, Variant() }); // 0 |
| 24 | | input_defs.push_back({ "Voxel Size", SocketType::Float, 0.1f }); // 1 |
| 25 | | input_defs.push_back({ "Band Width", SocketType::Float, 3.0f }); // 2 |
| 32 | input_defs.push_back({ "Voxel Size", SocketType::Float, 0.3f }); // 1 |
| 33 | input_defs.push_back({ "Band Width", SocketType::Int, 3 }); // 2 |
| 26 | 34 | output_defs.push_back({ "Volume", SocketType::Geometry, Variant() }); |
| 27 | 35 | } |
| 28 | 36 | |
| … | … | @@ -47,7 +55,7 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 47 | 55 | |
| 48 | 56 | float voxel = p_ctx.read_float(p_node_idx, 1); |
| 49 | 57 | if (voxel <= 0.0f) voxel = 0.1f; |
| 50 | | float band = p_ctx.read_float(p_node_idx, 2); |
| 58 | float band = (float)p_ctx.read_int(p_node_idx, 2); |
| 51 | 59 | if (band < 0.0f) band = 0.0f; |
| 52 | 60 | |
| 53 | 61 | // libdc's evaluator, not a second one: the pseudonormal method with a BVH |
| … | … | @@ -185,12 +193,51 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 185 | 193 | vol->grid_size = Vector3(res[0] * voxel, res[1] * voxel, res[2] * voxel); |
| 186 | 194 | vol->density_grid.resize(res[0] * res[1] * res[2]); |
| 187 | 195 | float *w = vol->density_grid.ptrw(); |
| 188 | | for (int z = 0; z < res[2]; z++) { |
| 189 | | for (int y = 0; y < res[1]; y++) { |
| 190 | | for (int x = 0; x < res[0]; x++) { |
| 191 | | w[(z * res[1] + y) * res[0] + x] = sdf_sign * sdf.evaluate( |
| 192 | | mn[0] + x * voxel, mn[1] + y * voxel, mn[2] + z * voxel, voxel); |
| 196 | // ⛔ THREADED, because this is the whole cost of the node. Every sample is a |
| 197 | // closest-point query against the BVH, and the grid is capped at 256 per |
| 198 | // axis -- 16.7 million of them for the bridged mountain, which took 75.7 s |
| 199 | // serially and ran TWICE per evaluation, which is what "the mountain did not |
| 200 | // come across" looked like from the outside. |
| 201 | // |
| 202 | // libdc parallelises the equivalent loops with `#pragma omp parallel for`, |
| 203 | // but proc-nodes builds without -fopenmp, so those pragmas are silently |
| 204 | // ignored here and every libdc loop this extension compiles is serial. |
| 205 | // std::thread rather than adding the flag: it changes one loop instead of |
| 206 | // every libdc loop's behaviour inside this library, and pulls in no runtime. |
| 207 | // |
| 208 | // Safe to share `sdf`: evaluate() is non-const by declaration only -- it |
| 209 | // calls closest_distance_sq/query_bvh, both const, and the BVH is immutable |
| 210 | // after load_arrays. |
| 211 | { |
| 212 | const int nz = res[2]; |
| 213 | unsigned hw = std::thread::hardware_concurrency(); |
| 214 | int nthreads = (int)(hw > 0 ? hw : 1u); |
| 215 | if (nthreads > nz) nthreads = nz; |
| 216 | if (nthreads < 1) nthreads = 1; |
| 217 | auto slab = [&](int z0, int z1) { |
| 218 | for (int z = z0; z < z1; z++) { |
| 219 | for (int y = 0; y < res[1]; y++) { |
| 220 | for (int x = 0; x < res[0]; x++) { |
| 221 | w[(z * res[1] + y) * res[0] + x] = sdf_sign * sdf.evaluate( |
| 222 | mn[0] + x * voxel, mn[1] + y * voxel, |
| 223 | mn[2] + z * voxel, voxel); |
| 224 | } |
| 225 | } |
| 193 | 226 | } |
| 227 | }; |
| 228 | if (nthreads == 1) { |
| 229 | slab(0, nz); |
| 230 | } else { |
| 231 | std::vector<std::thread> pool; |
| 232 | pool.reserve(nthreads); |
| 233 | const int per = (nz + nthreads - 1) / nthreads; |
| 234 | for (int t = 0; t < nthreads; t++) { |
| 235 | const int z0 = t * per; |
| 236 | const int z1 = MIN(z0 + per, nz); |
| 237 | if (z0 >= z1) break; |
| 238 | pool.emplace_back(slab, z0, z1); |
| 239 | } |
| 240 | for (std::thread &th : pool) th.join(); |
| 194 | 241 | } |
| 195 | 242 | } |
| 196 | 243 | out_geo->set_volume(vol); |
| … | … | @@ -202,9 +249,14 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 202 | 249 | void SDFGridFilletNode::_bind_methods() {} |
| 203 | 250 | |
| 204 | 251 | SDFGridFilletNode::SDFGridFilletNode() { |
| 205 | | input_defs.push_back({ "Volume", SocketType::Geometry, Variant() }); // 0 |
| 206 | | input_defs.push_back({ "Radius", SocketType::Float, 1.0f }); // 1 |
| 207 | | output_defs.push_back({ "Volume", SocketType::Geometry, Variant() }); |
| 252 | // ⛔ ITERATIONS, not a radius. Blender's node is `Grid` + `Iterations` (INT, |
| 253 | // default 1); this had `Volume` + `Radius` (float, default 1.0), so the |
| 254 | // bridge could only resolve socket 1 by INDEX, and even then it meant a |
| 255 | // different thing -- a Blender `Iterations = 5` landed as radius 5.0 and |
| 256 | // became 5/voxel passes. The socket now says what it does. |
| 257 | input_defs.push_back({ "Grid", SocketType::Geometry, Variant() }); // 0 |
| 258 | input_defs.push_back({ "Iterations", SocketType::Int, 1 }); // 1 |
| 259 | output_defs.push_back({ "Grid", SocketType::Geometry, Variant() }); |
| 208 | 260 | } |
| 209 | 261 | |
| 210 | 262 | void SDFGridFilletNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| … | … | @@ -229,16 +281,14 @@ void SDFGridFilletNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 229 | 281 | // operator and will not match Blender value for value. |
| 230 | 282 | Ref<ProcVolume> vol = in_vol->duplicate_volume(); |
| 231 | 283 | const Vector3i r = vol->grid_resolution; |
| 232 | | float radius = p_ctx.read_float(p_node_idx, 1); |
| 233 | | if (radius <= 0.0f) { |
| 284 | int iters = p_ctx.read_int(p_node_idx, 1); |
| 285 | if (iters <= 0) { |
| 234 | 286 | p_ctx.write_geometry(p_node_idx, 0, out_geo); |
| 235 | 287 | out_geo->set_volume(vol); |
| 236 | 288 | return; |
| 237 | 289 | } |
| 238 | | // Radius in voxels -> iterations. Each pass smooths about one voxel. |
| 239 | | const float voxel = (r.x > 0) ? (vol->grid_size.x / (float)r.x) : 1.0f; |
| 240 | | int iters = (int)(radius / MAX(voxel, 1e-6f)); |
| 241 | | if (iters < 1) iters = 1; |
| 290 | // Each pass smooths about one voxel, which is what Blender's iteration |
| 291 | // count buys too -- the operator differs, the unit does not. |
| 242 | 292 | if (iters > 32) iters = 32; |
| 243 | 293 | |
| 244 | 294 | PackedFloat32Array next = vol->density_grid; |
| … | … | @@ -270,8 +320,13 @@ void SDFGridFilletNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 270 | 320 | void GridToMeshNode::_bind_methods() {} |
| 271 | 321 | |
| 272 | 322 | GridToMeshNode::GridToMeshNode() { |
| 273 | | input_defs.push_back({ "Volume", SocketType::Geometry, Variant() }); // 0 |
| 274 | | input_defs.push_back({ "Threshold", SocketType::Float, 0.0f }); // 1 |
| 323 | // Blender's node is `Grid` + `Threshold` (0.10) + `Adaptivity`. Adaptivity |
| 324 | // has no socket here on purpose -- Dual Contouring's simplifier is |
| 325 | // dc_simplify, which proc-nodes deliberately does not compile (it needs |
| 326 | // meshoptimizer). A Blender graph carrying one lands on nothing, which is |
| 327 | // the same outcome as ignoring it and is visible in the import log. |
| 328 | input_defs.push_back({ "Grid", SocketType::Geometry, Variant() }); // 0 |
| 329 | input_defs.push_back({ "Threshold", SocketType::Float, 0.1f }); // 1 |
| 275 | 330 | output_defs.push_back({ "Mesh", SocketType::Geometry, Variant() }); |
| 276 | 331 | } |
| 277 | 332 | |