| … | … | @@ -10,6 +10,7 @@ |
| 10 | 10 | #include "dc_mesher.h" |
| 11 | 11 | |
| 12 | 12 | #include <godot_cpp/variant/utility_functions.hpp> |
| 13 | #include <godot_cpp/classes/time.hpp> |
| 13 | 14 | |
| 14 | 15 | #include <thread> |
| 15 | 16 | #include <vector> |
| … | … | @@ -35,6 +36,7 @@ MeshToSDFGridNode::MeshToSDFGridNode() { |
| 35 | 36 | } |
| 36 | 37 | |
| 37 | 38 | void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 39 | const uint64_t t_node0 = Time::get_singleton()->get_ticks_usec(); |
| 38 | 40 | Ref<ProcGeometry> in_geo = p_ctx.read_geometry(p_node_idx, 0); |
| 39 | 41 | Ref<ProcGeometry> out_geo; |
| 40 | 42 | out_geo.instantiate(); |
| … | … | @@ -157,6 +159,7 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 157 | 159 | "faces point, and the extracted surface will follow it."); |
| 158 | 160 | } |
| 159 | 161 | |
| 162 | const uint64_t t_setup = Time::get_singleton()->get_ticks_usec(); |
| 160 | 163 | DCGeneratorMeshSDF sdf; |
| 161 | 164 | if (!sdf.load_arrays(pos.data(), vcount, idx.data(), (int)(idx.size() / 3))) { |
| 162 | 165 | UtilityFunctions::push_error( |
| … | … | @@ -219,12 +222,21 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 219 | 222 | // grid boundary. `signed_volume` already says whether it is closed, and an |
| 220 | 223 | // open one falls back to computing every voxel, which is what this has |
| 221 | 224 | // always done. |
| 225 | const uint64_t t_bvh = Time::get_singleton()->get_ticks_usec(); |
| 226 | const uint64_t t_start = t_bvh; |
| 227 | uint64_t t_stamp = t_start, t_dilate = t_start, t_query = t_start; |
| 222 | 228 | const bool closed = Math::abs(signed_volume) >= 1e-9; |
| 223 | 229 | const int bandv = MAX(1, (int)band); |
| 224 | 230 | |
| 225 | 231 | std::vector<uint8_t> in_band; |
| 226 | 232 | if (closed) { |
| 227 | 233 | in_band.assign((size_t)total, 0); |
| 234 | // The marked voxels, kept as a WORKLIST. Dilating from a list touches |
| 235 | // only the band; the separable three-pass version this replaced copied |
| 236 | // the whole 10.2M mask twice and scanned it three times to find the |
| 237 | // ~62k voxels that were actually set. |
| 238 | std::vector<int64_t> marked; |
| 239 | marked.reserve(1 << 16); |
| 228 | 240 | // 1. Stamp each triangle's voxel AABB. No expansion here: dilating |
| 229 | 241 | // afterwards is O(grid) once instead of O(triangles x band^3), and |
| 230 | 242 | // these triangles are far smaller than a voxel -- expanding each one |
| … | … | @@ -243,41 +255,50 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 243 | 255 | const int z1 = CLAMP((int)Math::floor((hi.z - mn[2]) / voxel), 0, (int)nz - 1); |
| 244 | 256 | for (int z = z0; z <= z1; z++) { |
| 245 | 257 | for (int y = y0; y <= y1; y++) { |
| 246 | | uint8_t *row = &in_band[(size_t)at(0, y, z)]; |
| 258 | const int64_t base = at(0, y, z); |
| 247 | 259 | for (int x = x0; x <= x1; x++) { |
| 248 | | row[x] = 1; |
| 260 | if (!in_band[(size_t)(base + x)]) { |
| 261 | in_band[(size_t)(base + x)] = 1; |
| 262 | marked.push_back(base + x); |
| 263 | } |
| 249 | 264 | } |
| 250 | 265 | } |
| 251 | 266 | } |
| 252 | 267 | } |
| 253 | | // 2. Dilate by `band` voxels, separably: three 1D max-filters rather |
| 254 | | // than one 3D neighbourhood, so the cost is 3 x O(grid) x band. |
| 255 | | std::vector<uint8_t> tmp(in_band.size()); |
| 256 | | auto dilate_axis = [&](int axis) { |
| 257 | | tmp = in_band; |
| 258 | | for (int64_t z = 0; z < nz; z++) { |
| 259 | | for (int64_t y = 0; y < ny; y++) { |
| 260 | | for (int64_t x = 0; x < nx; x++) { |
| 261 | | if (tmp[(size_t)at(x, y, z)] == 0) { |
| 262 | | continue; |
| 263 | | } |
| 264 | | for (int d = -bandv; d <= bandv; d++) { |
| 265 | | int64_t xx = x, yy = y, zz = z; |
| 266 | | if (axis == 0) xx += d; else if (axis == 1) yy += d; else zz += d; |
| 267 | | if (xx < 0 || xx >= nx || yy < 0 || yy >= ny || zz < 0 || zz >= nz) { |
| 268 | | continue; |
| 269 | | } |
| 270 | | in_band[(size_t)at(xx, yy, zz)] = 1; |
| 271 | | } |
| 268 | t_stamp = Time::get_singleton()->get_ticks_usec(); |
| 269 | // 2. Dilate by `band` voxels, separably, over the WORKLIST. Three 1D |
| 270 | // passes rather than one 3D neighbourhood, and each pass walks only |
| 271 | // the voxels currently in the band -- appending as it goes and |
| 272 | // iterating to the size the pass started at, which is exactly |
| 273 | // "dilate the current set along this axis". The mask dedupes, so a |
| 274 | // voxel is visited once however many neighbours reach it. |
| 275 | for (int axis = 0; axis < 3; axis++) { |
| 276 | const size_t cur = marked.size(); |
| 277 | for (size_t k = 0; k < cur; k++) { |
| 278 | const int64_t o = marked[k]; |
| 279 | const int64_t x = o % nx; |
| 280 | const int64_t y = (o / nx) % ny; |
| 281 | const int64_t z = o / (nx * ny); |
| 282 | for (int d = -bandv; d <= bandv; d++) { |
| 283 | if (d == 0) { |
| 284 | continue; |
| 285 | } |
| 286 | int64_t xx = x, yy = y, zz = z; |
| 287 | if (axis == 0) xx += d; else if (axis == 1) yy += d; else zz += d; |
| 288 | if (xx < 0 || xx >= nx || yy < 0 || yy >= ny || zz < 0 || zz >= nz) { |
| 289 | continue; |
| 290 | } |
| 291 | const int64_t nb = at(xx, yy, zz); |
| 292 | if (!in_band[(size_t)nb]) { |
| 293 | in_band[(size_t)nb] = 1; |
| 294 | marked.push_back(nb); |
| 272 | 295 | } |
| 273 | 296 | } |
| 274 | 297 | } |
| 275 | | }; |
| 276 | | dilate_axis(0); |
| 277 | | dilate_axis(1); |
| 278 | | dilate_axis(2); |
| 298 | } |
| 279 | 299 | } |
| 280 | 300 | |
| 301 | t_dilate = Time::get_singleton()->get_ticks_usec(); |
| 281 | 302 | // ⛔ THREADED, because this is the whole cost of the node. libdc parallelises |
| 282 | 303 | // its equivalent loops with `#pragma omp parallel for`, but proc-nodes builds |
| 283 | 304 | // without -fopenmp so those pragmas are silently ignored and every libdc loop |
| … | … | @@ -326,6 +347,7 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 326 | 347 | } |
| 327 | 348 | } |
| 328 | 349 | |
| 350 | t_query = Time::get_singleton()->get_ticks_usec(); |
| 329 | 351 | // ── OUTSIDE BY FLOOD, INSIDE BY WHAT IS LEFT ──────────────────────────── |
| 330 | 352 | // |
| 331 | 353 | // A voxel outside the band cannot contain a sign change, so it needs only |
| … | … | @@ -377,6 +399,21 @@ void MeshToSDFGridNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 377 | 399 | } |
| 378 | 400 | } |
| 379 | 401 | |
| 402 | { |
| 403 | // WHERE THE TIME GOES, per stage. "MeshToSDFGrid: 12.4 s" says nothing |
| 404 | // about whether that is the band being too wide, the queries being slow, |
| 405 | // or the bookkeeping around them. |
| 406 | const uint64_t t_end = Time::get_singleton()->get_ticks_usec(); |
| 407 | int64_t band_n = 0; |
| 408 | for (size_t i = 0; i < in_band.size(); i++) { |
| 409 | band_n += in_band[i] ? 1 : 0; |
| 410 | } |
| 411 | UtilityFunctions::print(" [SDF] prep ", (t_setup - t_node0) / 1000, "ms bvh ", (t_bvh - t_setup) / 1000, "ms grid ", (int64_t)nx, "x", (int64_t)ny, "x", (int64_t)nz, |
| 412 | " = ", total, " voxels, band ", band_n, |
| 413 | band_n > 0 ? String(" (") + String::num((double)band_n * 100.0 / (double)total, 1) + "%)" : String(""), |
| 414 | " stamp ", (t_stamp - t_start) / 1000, "ms dilate ", (t_dilate - t_stamp) / 1000, |
| 415 | "ms query ", (t_query - t_dilate) / 1000, "ms flood ", (t_end - t_query) / 1000, "ms"); |
| 416 | } |
| 380 | 417 | out_geo->set_volume(vol); |
| 381 | 418 | p_ctx.write_geometry(p_node_idx, 0, out_geo); |
| 382 | 419 | } |