GWN point slicer: inside / outside from the winding number

2026-10-28 13:01 · fcp v5.2 (CUDA), package untouched, prototype in scripts/gwn.py · the same 10 objects, 180 clouds, metric and references as fcp_2710_tests' synthesis (its eps and local prototypes are run again here for the normals) · tables from report/bench2.csv by scripts/make_page.py

Findings

gwn has the lowest error on all 18 cloud types and the best normals on 17 of 18; with the closing (recommended) its part count at zero noise is within a few parts of the mesh route's on every object but the open box. Objects: the ten of fcp_2710_tests, the open box in place of the loaf pan (2026-10-28). eps and local are fcp_2710_tests' prototypes, run again here with the same σ. One setting for every cloud; σ = 0 unless the per-point noise is known.

Without σ (row 1 of the ablation: global iso, no splats) the winding number is still accurate on noisy clouds (1.2 %: 0.57× the package's error) but makes 4.6× the parts: σ buys the part count.

The pieces that matter, from the ablation: the local iso-value (thin parts survive), the tangent splats (walls of a spacing stay closed: clean 10k parts 262 → 176 with the loaf pan), the patch flips (density: the striped battery's normals 44 % inverted → 2.5 %), off when σ is given (1.2 % 10k error 14.8 → 9.2), and the 2D closing (fewer parts at zero noise, below).

All clouds: package, eps, local, gwn

Parts against the mesh route's on the mesh the cloud came from (the noisy mesh for mesh noise, else the clean mesh). Error: mean symmetric nearest-point distance to the clean mesh / diagonal (geometric mean over objects; ratio to the package per object, geometric mean). Normals: the descriptor's SDF normal (Shape.normal(method="sdf")) at 3000 points of the clean mesh against the outward face normal, oriented. 1024 atoms, 128 layers, up +z. sigma (the per-point noise) is given to eps, local and gwn on the point-noise clouds, 0 elsewhere. Green: best per cloud (parts ratio nearest 1).

cloudslicerpartsmeshmedian parts / meshobjects within ±25 %error ×10⁻³error / packagenormal median °PGP10 %PGP30 %emptyencode s
clean · 10000package2091341.4343.751.008.650.977.8False0.17
eps1851341.0963.801.018.449.977.6False1.28
local1801341.2463.270.878.849.778.6False1.97
gwn1361340.9483.010.807.256.283.0False1.81
gwn + closing1281340.8683.090.827.257.084.4False3.89
mesh noise 0.2 % · 10000package2822651.1073.961.0012.244.572.7False0.15
eps2582650.9573.991.0112.543.573.1False1.17
local2662651.0073.780.9511.442.373.1False1.63
gwn2132650.8473.540.899.648.879.0False1.60
gwn + closing1892750.7253.490.889.951.080.1False2.54
mesh noise 0.6 % · 10000package2732241.0876.381.0020.831.065.3False0.17
eps2482241.0056.431.0121.230.465.9False1.20
local2692241.0656.270.9821.330.765.1False1.75
gwn2012240.8675.730.9015.735.871.7False1.63
gwn + closing1812210.8585.580.8715.036.473.4False3.59
mesh noise 1 % · 10000package2371940.9988.261.0025.326.059.5False0.16
eps2151940.9278.271.0025.925.158.7False1.16
local2201940.9488.841.0727.024.557.6False1.56
gwn1781940.8387.740.9417.129.965.2False1.85
gwn + closing1702000.8388.311.0116.828.363.8False2.46
point noise 0.12 % · 10000package2491341.6734.001.009.547.575.7False0.18
eps2261341.5434.041.019.846.875.7False1.25
local2161341.4733.900.9810.245.273.9False1.64
gwn1461340.9473.060.767.855.282.0False1.71
gwn + closing1311340.9483.290.828.155.582.4False2.98
point noise 0.6 % · 10000package13981349.5908.971.0055.112.830.1False0.27
eps3491342.3336.990.7830.224.451.6False1.17
local3641342.4536.510.7329.125.353.8False2.69
gwn2841341.2724.680.5222.132.962.6False2.49
gwn + closing2641341.5025.040.5622.533.163.1False4.21
point noise 1.2 % · 10000package321613426.52013.421.0072.48.020.9False0.39
eps1651341.4209.500.7129.321.449.9False1.14
local1701341.44010.010.7533.719.146.4False6.81
gwn1881340.6918.840.6633.822.050.2False3.51
gwn + closing1831340.6918.790.6633.122.551.0False5.38
density gradient · 10000package8871346.4407.291.0048.024.041.4False0.26
eps8271346.0407.250.9948.523.641.0False1.79
local2481341.9644.420.6117.837.266.1False2.29
gwn2001341.4933.720.5110.448.174.0False2.48
gwn + closing1911341.4643.980.5513.648.674.3False3.08
density stripes · 10000package13231349.8509.591.0057.114.030.1False0.28
eps11991348.8909.150.9558.413.429.5False1.98
local6071344.1017.160.7542.626.147.9False2.74
gwn2731341.6714.370.4612.144.871.0False2.85
gwn + closing2431341.5433.770.3914.046.572.7False3.42
clean · 2048package2731342.4936.861.0028.430.756.3False0.15
eps2371342.2317.391.0828.026.853.8False0.70
local2221341.7816.570.9630.428.555.4False0.84
gwn1711341.3914.740.6918.243.069.8False0.29
gwn + closing1431341.0434.670.6818.742.670.6False0.71
mesh noise 0.2 % · 2048package3192651.1555.851.0025.027.654.7False0.14
eps2802651.0456.241.0728.024.953.1False0.67
local2582651.0176.781.1626.228.556.7False0.84
gwn2162650.8564.920.8419.138.266.3False0.29
gwn + closing1622750.6644.810.8214.039.969.4False0.51
mesh noise 0.6 % · 2048package3102241.1748.561.0032.719.247.4False0.14
eps2702241.0038.661.0131.918.747.7False0.70
local2782240.9757.990.9332.720.549.2False0.86
gwn2102240.7637.460.8725.226.859.3False0.32
gwn + closing1732210.7247.330.8622.329.563.1False0.49
mesh noise 1 % · 2048package2941941.30510.371.0034.717.845.2False0.15
eps2611941.12410.571.0234.017.545.2False0.74
local2841941.05510.541.0236.516.043.2False0.85
gwn2001940.91510.501.0131.122.654.7False0.32
gwn + closing1842000.8879.960.9625.324.759.0False0.74
point noise 0.12 % · 2048package2781342.4336.021.0026.028.354.8False0.15
eps2491342.0126.431.0728.925.551.2False0.76
local2501342.1516.611.1031.426.352.8False0.86
gwn1851341.2435.400.9018.439.666.3False0.29
gwn + closing1711341.1234.640.7718.340.168.3False0.53
point noise 0.6 % · 2048package4541343.7409.041.0050.013.033.5False0.16
eps3291343.1019.441.0439.514.337.9False0.70
local3231342.9918.820.9846.913.537.1False0.99
gwn2111341.6517.080.7827.619.851.1False0.32
gwn + closing1981341.4027.710.8529.422.152.2False0.41
point noise 1.2 % · 2048package7621346.23010.831.0066.47.321.6False0.18
eps1871341.85313.681.2648.410.733.5False0.72
local1931341.96213.311.2351.010.232.7False1.16
gwn1571341.01210.720.9940.715.443.6False0.35
gwn + closing1501340.92110.750.9941.815.043.2False1.03
density gradient · 2048package5381343.6527.611.0041.817.037.3False0.22
eps4871343.3927.741.0244.717.137.0False0.92
local2401342.1127.040.9228.824.551.8False1.10
gwn2361341.9336.040.7921.633.660.3False0.32
gwn + closing1921341.5836.160.8120.735.863.6False0.56
density stripes · 2048package5541344.6417.681.0051.217.537.7False0.26
eps4761344.0207.711.0053.216.836.9False0.84
local3541343.2927.280.9546.919.541.7False1.18
gwn2241341.5945.520.7221.234.261.8False0.33
gwn + closing1821341.1525.140.6717.538.767.5False0.69

Per object

clean · 10000

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped46 · 3.46 · 3°4 · 3.53 · 3°5 · 3.53 · 3°4 · 3.42 · 2°3 · 3.43 · 3°
dress1717 · 3.19 · 29°13 · 3.11 · 28°16 · 3.02 · 27°13 · 2.52 · 24°14 · 2.53 · 24°
elephant2732 · 4.29 · 27°28 · 4.44 · 26°25 · 4.51 · 26°19 · 4.00 · 23°19 · 3.98 · 23°
bowl_of514 · 2.69 · 2°14 · 2.69 · 2°14 · 2.68 · 2°4 · 2.65 · 1°4 · 2.65 · 1°
bowl_gso620 · 2.75 · 4°20 · 2.75 · 4°19 · 2.79 · 4°6 · 2.65 · 3°6 · 2.65 · 3°
mug813 · 3.00 · 3°12 · 3.05 · 3°12 · 3.10 · 4°9 · 2.93 · 2°8 · 2.91 · 2°
dino2527 · 2.47 · 16°21 · 2.67 · 18°22 · 2.65 · 17°21 · 2.42 · 15°21 · 2.38 · 15°
shoe2230 · 7.74 · 29°25 · 7.81 · 28°27 · 4.92 · 31°24 · 4.38 · 24°22 · 6.13 · 16°
horse1816 · 2.33 · 13°14 · 2.32 · 13°14 · 2.35 · 13°16 · 2.22 · 11°16 · 2.22 · 12°
open_box234 · 11.85 · 3°34 · 11.85 · 3°26 · 4.05 · 5°20 · 3.63 · 1°15 · 3.50 · 2°

clean · 2048

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped416 · 4.56 · 7°14 · 4.81 · 9°15 · 5.62 · 13°6 · 4.07 · 4°6 · 4.05 · 3°
dress1712 · 4.10 · 35°7 · 4.36 · 33°12 · 4.52 · 34°7 · 3.53 · 29°6 · 3.49 · 29°
elephant2721 · 5.58 · 36°15 · 5.62 · 29°17 · 5.64 · 32°12 · 4.93 · 29°9 · 4.87 · 25°
bowl_of521 · 18.81 · 5°18 · 17.30 · 17°22 · 7.53 · 7°13 · 3.15 · 2°11 · 3.14 · 2°
bowl_gso642 · 14.52 · 31°37 · 15.19 · 37°32 · 9.57 · 39°22 · 3.81 · 7°14 · 3.93 · 7°
mug826 · 5.34 · 19°24 · 8.33 · 26°18 · 5.34 · 11°12 · 3.60 · 4°9 · 3.58 · 4°
dino2522 · 3.81 · 25°19 · 3.93 · 27°20 · 4.56 · 29°17 · 4.51 · 22°16 · 4.56 · 22°
shoe2238 · 7.43 · 36°32 · 8.03 · 38°29 · 9.82 · 47°28 · 7.39 · 26°21 · 7.24 · 26°
horse1815 · 3.63 · 20°13 · 3.84 · 22°10 · 3.68 · 22°16 · 3.22 · 14°16 · 3.33 · 15°
open_box260 · 14.71 · 71°58 · 15.53 · 75°47 · 16.42 · 70°38 · 17.47 · 40°35 · 14.91 · 42°

point noise 0.6 % · 10000

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped482 · 5.45 · 20°14 · 5.29 · 13°14 · 5.32 · 14°6 · 4.20 · 7°8 · 4.16 · 7°
dress1760 · 5.36 · 51°11 · 4.73 · 32°13 · 4.80 · 32°8 · 4.16 · 28°9 · 4.11 · 28°
elephant2758 · 6.68 · 49°14 · 5.35 · 28°15 · 5.14 · 28°8 · 4.38 · 23°8 · 4.40 · 23°
bowl_of5138 · 15.38 · 73°26 · 5.09 · 8°23 · 7.33 · 8°21 · 3.79 · 3°18 · 4.67 · 3°
bowl_gso6246 · 18.72 · 87°52 · 11.86 · 61°48 · 11.48 · 44°49 · 6.01 · 36°44 · 11.10 · 39°
mug888 · 14.15 · 80°35 · 11.65 · 39°32 · 6.16 · 30°27 · 4.65 · 14°25 · 4.23 · 10°
dino25119 · 5.08 · 43°20 · 6.36 · 28°20 · 6.41 · 26°13 · 4.10 · 21°12 · 4.17 · 22°
shoe22180 · 9.03 · 59°25 · 7.93 · 38°31 · 5.69 · 35°23 · 4.98 · 25°21 · 5.05 · 27°
horse1896 · 4.87 · 38°21 · 5.44 · 23°22 · 5.27 · 22°18 · 3.14 · 14°18 · 3.15 · 15°
open_box2331 · 19.08 · 68°131 · 10.79 · 72°146 · 10.45 · 69°111 · 9.67 · 75°101 · 9.72 · 75°

point noise 1.2 % · 10000

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped4265 · 9.89 · 47°9 · 5.93 · 10°9 · 6.55 · 14°3 · 5.71 · 7°3 · 5.76 · 8°
dress17145 · 7.67 · 66°4 · 9.18 · 43°7 · 9.42 · 44°3 · 8.51 · 39°3 · 8.51 · 39°
elephant27266 · 9.75 · 57°4 · 7.19 · 29°5 · 7.46 · 30°6 · 6.62 · 24°6 · 6.61 · 24°
bowl_of5389 · 24.00 · 79°16 · 9.57 · 18°18 · 9.16 · 29°20 · 8.26 · 20°21 · 8.19 · 20°
bowl_gso6476 · 25.35 · 80°22 · 8.93 · 26°26 · 9.39 · 36°30 · 9.57 · 41°29 · 9.62 · 40°
mug8268 · 16.88 · 82°28 · 8.93 · 40°27 · 11.26 · 40°35 · 9.64 · 47°32 · 9.33 · 37°
dino25267 · 8.33 · 58°12 · 14.04 · 36°11 · 13.77 · 37°8 · 7.18 · 29°8 · 7.18 · 29°
shoe22430 · 14.59 · 81°13 · 6.67 · 25°14 · 7.58 · 32°14 · 9.23 · 41°14 · 9.25 · 38°
horse18310 · 9.12 · 51°10 · 17.67 · 30°11 · 17.34 · 30°8 · 15.84 · 19°8 · 15.86 · 19°
open_box2400 · 22.48 · 87°47 · 12.05 · 69°42 · 12.55 · 75°61 · 11.33 · 78°59 · 11.03 · 72°

point noise 1.2 % · 2048

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped436 · 8.16 · 30°11 · 8.23 · 22°11 · 8.22 · 22°5 · 7.14 · 15°5 · 7.20 · 15°
dress1746 · 8.48 · 62°6 · 13.67 · 50°4 · 14.52 · 50°3 · 9.64 · 41°3 · 9.64 · 41°
elephant2749 · 8.90 · 56°8 · 10.83 · 39°6 · 10.74 · 38°5 · 8.24 · 27°4 · 8.22 · 27°
bowl_of573 · 15.91 · 74°25 · 12.99 · 59°25 · 13.38 · 62°27 · 11.13 · 54°27 · 11.15 · 54°
bowl_gso6111 · 13.53 · 81°25 · 15.15 · 46°27 · 13.33 · 55°28 · 10.78 · 54°28 · 11.08 · 56°
mug859 · 14.99 · 78°26 · 15.66 · 63°27 · 14.45 · 62°22 · 12.73 · 47°23 · 12.71 · 47°
dino2571 · 8.68 · 59°19 · 17.17 · 47°14 · 17.22 · 47°9 · 10.68 · 31°9 · 10.67 · 31°
shoe22112 · 10.37 · 71°21 · 12.65 · 52°24 · 11.62 · 52°17 · 9.90 · 41°13 · 9.72 · 43°
horse1863 · 8.77 · 55°17 · 16.57 · 44°21 · 16.96 · 45°10 · 14.37 · 23°9 · 14.49 · 24°
open_box2142 · 14.16 · 87°29 · 16.97 · 83°34 · 15.56 · 79°31 · 15.33 · 78°29 · 15.35 · 76°

density stripes · 10000

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped4178 · 14.48 · 88°157 · 12.47 · 88°145 · 13.55 · 87°20 · 4.33 · 6°16 · 3.93 · 3°
dress17116 · 6.25 · 55°100 · 6.20 · 55°82 · 6.82 · 60°18 · 2.91 · 26°19 · 2.80 · 26°
elephant27177 · 12.20 · 62°160 · 11.23 · 62°92 · 11.88 · 70°37 · 5.61 · 36°38 · 5.38 · 32°
bowl_of561 · 10.25 · 62°58 · 10.23 · 62°21 · 3.63 · 11°12 · 2.77 · 2°10 · 2.67 · 1°
bowl_gso680 · 7.32 · 54°75 · 7.29 · 58°24 · 13.86 · 15°11 · 2.76 · 4°10 · 2.89 · 4°
mug840 · 11.04 · 59°39 · 10.99 · 59°16 · 3.66 · 8°12 · 2.99 · 3°10 · 3.02 · 3°
dino25156 · 8.40 · 52°143 · 7.79 · 51°30 · 3.49 · 27°33 · 3.14 · 21°29 · 2.99 · 19°
shoe22233 · 8.75 · 61°209 · 8.57 · 62°42 · 6.95 · 41°66 · 9.34 · 53°58 · 7.70 · 48°
horse18164 · 8.78 · 48°149 · 8.13 · 45°81 · 6.89 · 44°23 · 2.79 · 17°23 · 2.56 · 15°
open_box2118 · 11.19 · 23°109 · 10.61 · 33°74 · 10.48 · 65°41 · 19.28 · 7°30 · 7.09 · 13°

density gradient · 10000

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh's, lowest error.

objectmesh partspackageepslocalgwngwn + closing
battery_stepped4161 · 16.19 · 92°151 · 15.59 · 92°23 · 6.96 · 22°9 · 4.07 · 4°8 · 4.04 · 3°
dress1769 · 6.75 · 57°65 · 6.71 · 57°17 · 3.20 · 29°25 · 3.81 · 32°24 · 3.53 · 30°
elephant27113 · 11.89 · 69°106 · 11.67 · 69°25 · 4.82 · 29°29 · 4.39 · 26°32 · 4.51 · 29°
bowl_of541 · 3.99 · 19°40 · 4.00 · 18°16 · 3.78 · 8°9 · 2.74 · 2°8 · 2.73 · 1°
bowl_gso651 · 6.81 · 35°46 · 6.76 · 35°18 · 3.91 · 9°9 · 2.67 · 3°9 · 2.68 · 3°
mug812 · 3.21 · 4°11 · 3.29 · 5°15 · 3.44 · 5°8 · 3.01 · 3°7 · 3.01 · 3°
dino2598 · 6.87 · 52°93 · 7.05 · 53°25 · 3.07 · 20°26 · 2.58 · 17°26 · 2.61 · 16°
shoe22165 · 11.60 · 70°157 · 11.57 · 69°45 · 8.41 · 38°37 · 8.14 · 41°34 · 5.90 · 33°
horse1897 · 7.34 · 44°89 · 7.19 · 44°14 · 2.61 · 15°23 · 2.84 · 17°17 · 2.71 · 15°
open_box280 · 6.38 · 20°69 · 6.30 · 25°50 · 7.79 · 15°25 · 5.67 · 4°26 · 16.90 · 12°

Dense clean clouds (50 000 points): the exact end

The exact end: with 50 000 clean points (σ = 0, no closing) gwn gives 126 parts against the mesh route's 134 (package 145): within two on every object but the elephant (22 against 27); mean error 2.80 against 2.79 (package 2.87), normal median 6.9° against 6.8°. The open box: mesh 2, gwn 4, package 6.

parts · error ×10⁻³ (geometric mean on the last row) · normal median · encode seconds.

objectmesh routepackagegwn
battery_stepped4 · 3.29 · 0.5° · 0.7 s5 · 3.33 · 2.0° · 0.5 s4 · 3.35 · 1.9° · 39.4 s
dress17 · 2.19 · 22.5° · 0.5 s20 · 2.41 · 23.6° · 0.5 s18 · 2.27 · 22.8° · 11.3 s
elephant27 · 4.10 · 25.0° · 0.1 s24 · 4.24 · 27.5° · 0.3 s22 · 3.92 · 23.7° · 23.5 s
bowl_of5 · 2.63 · 1.2° · 0.1 s5 · 2.63 · 1.2° · 0.4 s4 · 2.64 · 1.4° · 53.7 s
bowl_gso6 · 2.62 · 2.7° · 0.2 s6 · 2.63 · 2.9° · 0.5 s5 · 2.63 · 2.7° · 52.3 s
mug8 · 2.92 · 2.3° · 0.2 s8 · 2.93 · 2.5° · 0.4 s7 · 2.94 · 2.6° · 25.3 s
dino25 · 2.23 · 13.9° · 0.1 s26 · 2.32 · 15.0° · 0.3 s23 · 2.22 · 14.0° · 20.1 s
shoe22 · 2.99 · 10.9° · 0.2 s28 · 3.27 · 14.9° · 0.3 s22 · 3.04 · 12.1° · 31.5 s
horse18 · 2.17 · 11.1° · 0.1 s17 · 2.20 · 11.3° · 0.3 s17 · 2.17 · 11.0° · 25.2 s
open_box2 · 3.31 · 0.7° · 0.5 s6 · 3.34 · 1.4° · 0.9 s4 · 3.36 · 0.9° · 38.5 s
all134 · 2.79 · 6.8° · 0.2 s145 · 2.87 · 7.1° · 0.4 s126 · 2.80 · 6.9° · 28.4 s

Fewer parts at zero noise

Goal: at zero noise, as few parts as the mesh route gives on the same clean mesh (the reference, per object; the elephant left out, its mesh-route encode is poor). The extra parts came from walls of about a spacing that open at single layers (open-box study). The fix in the slicer: a morphological closing of each layer's inside region by 0.75 spacings; the pixels it adds (gaps under 1.5 spacings) become just inside, the rest of the field is kept, so the contour keeps its sub-pixel position. Nine objects, mesh 107 parts: 10k 109 parts, sum |parts − mesh| 24 (gwn 117 / 32, package 177 / 74), error 3.01 (2.91 / 3.70); 2048 134 / 73 (159 / 92, package 252 / 167). On every cloud type it gives fewer parts at about the same error (main table, "gwn + closing").

A 3D closing (all layers on one grid, the layer step as the third spacing; 1 spacing, with the 2D one) gets 2048 to 105 parts (|Δ| 58) but fills concave steps (the battery's ledge at 2048: 4 → 2 parts, error ×2.4): not recommended. No better: larger splats (1.75, 2.5 spacings), a larger mollifier (0.75 spacings), larger 2D closings.

256 layers do not help. The mesh route goes from 107 to 139 parts and the clouds fall further behind (10k with closing 158, |Δ| 55; 2048 189, |Δ| 108): thin and flat-walled objects get twice the layers to break on while the cut tolerance stays one spacing; the organic ones stay at or under the mesh's count.

parts · error ×10⁻³; bold: parts nearest the mesh route's on the clean mesh (same layers); last row: total parts, sum of |parts − mesh parts| over the objects, geometric-mean error.

128 layers · 10000 points

objectmeshpackagegwngwn + 2D closing 0.75 sgwn + 3D closing 1 s + 2D 0.75 s
battery_stepped46 · 3.464 · 3.423 · 3.432 · 3.51
dress1717 · 3.1913 · 2.5214 · 2.5313 · 2.60
bowl_of514 · 2.694 · 2.654 · 2.654 · 2.65
bowl_gso620 · 2.756 · 2.656 · 2.656 · 2.64
mug813 · 3.009 · 2.938 · 2.917 · 2.96
dino2527 · 2.4721 · 2.4221 · 2.3821 · 2.44
shoe2230 · 7.7424 · 4.3822 · 6.1321 · 3.44
horse1816 · 2.3316 · 2.2216 · 2.2216 · 2.24
open_box234 · 11.8520 · 3.6315 · 3.5018 · 3.49
all107177 (|Δ| 74) · 3.70117 (|Δ| 32) · 2.91109 (|Δ| 24) · 3.01108 (|Δ| 31) · 2.85

128 layers · 2048 points

objectmeshpackagegwngwn + 2D closing 0.75 sgwn + 3D closing 1 s + 2D 0.75 s
battery_stepped416 · 4.566 · 4.076 · 4.052 · 9.84
dress1712 · 4.107 · 3.536 · 3.497 · 3.65
bowl_of521 · 18.8113 · 3.1511 · 3.1410 · 3.34
bowl_gso642 · 14.5222 · 3.8114 · 3.9313 · 3.77
mug826 · 5.3412 · 3.609 · 3.588 · 3.59
dino2522 · 3.8117 · 4.5116 · 4.5616 · 4.59
shoe2238 · 7.4328 · 7.3921 · 7.2415 · 4.96
horse1815 · 3.6316 · 3.2216 · 3.3316 · 3.34
open_box260 · 14.7138 · 17.4735 · 14.9118 · 15.01
all107252 (|Δ| 167) · 7.02159 (|Δ| 92) · 4.72134 (|Δ| 73) · 4.65105 (|Δ| 58) · 4.97

parts · error ×10⁻³; bold: parts nearest the mesh route's on the clean mesh (same layers); last row: total parts, sum of |parts − mesh parts| over the objects, geometric-mean error.

256 layers · 10000 points

objectmeshpackagegwngwn + 2D closing 0.75 sgwn + 3D closing 1 s + 2D 0.75 s
battery_stepped421 · 3.507 · 3.377 · 3.407 · 3.41
dress2343 · 3.2524 · 2.6118 · 2.7216 · 2.61
bowl_of522 · 2.729 · 2.648 · 2.648 · 2.65
bowl_gso729 · 2.8015 · 2.6614 · 2.6613 · 2.65
mug1020 · 3.0712 · 2.9011 · 2.9011 · 2.90
dino3439 · 2.6729 · 2.5028 · 2.5527 · 2.52
shoe3449 · 8.6438 · 4.4030 · 4.4319 · 3.51
horse2019 · 2.4117 · 2.3117 · 2.3816 · 2.35
open_box254 · 6.1332 · 4.3725 · 3.6625 · 3.59
all139296 (|Δ| 159) · 3.56183 (|Δ| 60) · 3.00158 (|Δ| 55) · 2.98142 (|Δ| 69) · 2.88

256 layers · 2048 points

objectmeshpackagegwngwn + 2D closing 0.75 sgwn + 3D closing 1 s + 2D 0.75 s
battery_stepped423 · 4.828 · 4.109 · 4.362 · 9.76
dress2324 · 4.2510 · 3.4012 · 3.617 · 3.80
bowl_of534 · 9.5217 · 3.1915 · 3.2513 · 3.29
bowl_gso761 · 7.5628 · 6.3518 · 3.9616 · 3.87
mug1037 · 6.5820 · 3.6216 · 3.6016 · 3.63
dino3435 · 4.0526 · 4.5022 · 4.6018 · 4.83
shoe3452 · 7.6836 · 6.1331 · 7.2520 · 7.43
horse2016 · 3.7118 · 3.4017 · 3.4017 · 3.42
open_box2125 · 13.0674 · 17.6249 · 16.5521 · 15.63
all139407 (|Δ| 276) · 6.25237 (|Δ| 144) · 4.92189 (|Δ| 108) · 4.81130 (|Δ| 93) · 5.30

Thin walls: open boxes

Is the loaf pan's part count the mesh or the wall? Open boxes written by hand (exact, watertight, no lid), walls and floor of t point spacings at 10k points (scripts/openbox.py): a cube and a box of the loaf pan's proportions. The loaf pan's own wall is 1.45 % of the diagonal (median, by ray casting): 1.2 spacings at 10k, 0.5 at 2048, 2.6 at 50k (where gwn gives its 7 parts).

parts · error ×10⁻³ · normal median; bold: parts nearest the mesh route's, lowest error.

boxcloudmesh routepackageepslocalgwn
cube_t0.5sclean_100002 · 4.22 · 1°61 · 35.76 · 86°61 · 35.76 · 86°55 · 32.75 · 85°49 · 23.86 · 82°
cube_t0.5sclean_2048107 · 20.68 · 89°93 · 21.41 · 88°63 · 24.07 · 89°3 · 13.18 · 46°
cube_t0.5spn0p6_10000160 · 24.64 · 90°69 · 29.21 · 87°70 · 23.37 · 86°38 · 14.78 · 71°
cube_t1sclean_100002 · 4.23 · 1°54 · 17.55 · 70°54 · 17.55 · 70°52 · 17.79 · 65°16 · 5.41 · 8°
cube_t1sclean_204858 · 16.98 · 83°57 · 16.90 · 84°44 · 24.52 · 84°32 · 18.82 · 62°
cube_t1spn0p6_10000178 · 22.49 · 86°90 · 19.37 · 82°81 · 19.53 · 83°90 · 15.09 · 83°
cube_t2sclean_100002 · 4.10 · 1°10 · 4.47 · 4°10 · 4.47 · 4°10 · 4.45 · 5°4 · 4.41 · 3°
cube_t2sclean_204833 · 14.43 · 28°30 · 15.12 · 33°27 · 12.55 · 32°11 · 5.24 · 4°
cube_t2spn0p6_1000080 · 22.79 · 72°18 · 5.85 · 9°19 · 6.28 · 13°9 · 4.92 · 5°
cube_t4sclean_100002 · 4.03 · 1°5 · 4.36 · 4°4 · 4.40 · 5°5 · 4.37 · 4°3 · 4.14 · 3°
cube_t4sclean_204811 · 5.28 · 5°9 · 6.24 · 8°8 · 5.99 · 8°7 · 4.59 · 3°
cube_t4spn0p6_1000027 · 5.63 · 13°7 · 5.61 · 10°7 · 5.62 · 9°5 · 4.86 · 6°
pan_t0.5sclean_100002 · 3.39 · 0°123 · 16.21 · 80°120 · 15.96 · 79°95 · 22.88 · 85°70 · 18.28 · 77°
pan_t0.5sclean_204889 · 22.66 · 90°75 · 22.60 · 90°63 · 15.24 · 90°9 · 16.74 · 41°
pan_t0.5spn0p6_10000275 · 17.82 · 88°93 · 14.58 · 85°91 · 26.47 · 86°96 · 16.41 · 87°
pan_t1sclean_100002 · 3.40 · 0°55 · 18.53 · 73°55 · 18.53 · 73°46 · 7.27 · 4°29 · 5.37 · 1°
pan_t1sclean_204889 · 15.55 · 79°82 · 15.98 · 82°68 · 18.15 · 88°44 · 9.23 · 7°
pan_t1spn0p6_10000313 · 16.62 · 84°123 · 12.29 · 79°123 · 15.59 · 80°126 · 9.64 · 66°
pan_t2sclean_100002 · 3.34 · 0°13 · 3.72 · 3°12 · 3.74 · 3°10 · 3.91 · 4°8 · 3.52 · 2°
pan_t2sclean_204854 · 8.91 · 13°51 · 10.31 · 31°44 · 12.14 · 21°24 · 6.08 · 7°
pan_t2spn0p6_10000229 · 19.65 · 88°61 · 7.95 · 19°63 · 11.45 · 19°57 · 5.02 · 7°

Decoding a prefix (95 / 99 / 99.5 % of the atom energy)

Does decoding fewer atoms (the shortest prefix holding 95 / 99 / 99.5 / 99.9 % of the descriptor's atom energy, the sum of squared atom values) give a better surface or normals? No, from 6144 atoms (the most a descriptor holds) as from 1024: on all ten objects and every cloud, clean or noisy (0.12 / 0.6 % with σ given), the full descriptor has the best normals and, but for one tie, the lowest error. From 6144: 99.9 % (300–800 atoms) costs 1.1–1.45× the error and 2.6–5.7° of normal median; 99 % 1.3–3.2×. The one tie is 0.6 % noise at 2048 points, where the error is flat from 99.9 % (and the first 1024 atoms are 3 % better, 4 objects of 10) while the normals still want every atom (+6° at 1024). The energy sits in ring means and low harmonics; detail and normals need the small atoms. And 6144 atoms beat 1024 on normals everywhere (1–6° of median) and on error everywhere but 0.6 % noise at 2048 (6–12 % lower) (clean 10k 7.2° → 5.5°, 0.6 % 10k 22.5° → 16.2°). The rest of this page encodes 1024 atoms, the package default, as fcp_2710_tests' ablation. scripts/energy_prefix.py (FCP_ATOMS=6144).

From 6144 atoms

descriptorcloudenergyatoms (median)error ×10⁻³error / fullobjects better (2 %)normal median °PGP10 %
mesh routecleanfirst 1024 atoms10242.791.1206.861.4
mesh routeclean95 %2718.727.55039.520.4
mesh routeclean99 %667.933.20018.034.2
mesh routeclean99.5 %1025.632.27011.444.2
mesh routeclean99.9 %2853.611.4508.452.8
mesh routecleanfull61442.481.0004.570.5
gwn+closeclean · 10000first 1024 atoms10243.091.1007.257.0
gwn+closeclean · 1000095 %2517.446.20036.217.6
gwn+closeclean · 1000099 %747.992.84020.935.4
gwn+closeclean · 1000099.5 %1116.302.24017.241.9
gwn+closeclean · 1000099.9 %2843.891.38011.749.9
gwn+closeclean · 10000full61442.811.0005.564.1
gwn+closeclean · 2048first 1024 atoms10244.671.06018.742.6
gwn+closeclean · 204895 %3618.694.26039.817.1
gwn+closeclean · 204899 %1588.751.99027.631.1
gwn+closeclean · 204899.5 %2707.011.60025.135.4
gwn+closeclean · 204899.9 %7165.121.16020.240.4
gwn+closeclean · 2048full61444.391.00013.747.2
gwn+closepn0p12 · 10000first 1024 atoms10243.291.0908.155.5
gwn+closepn0p12 · 1000095 %2917.125.65039.413.4
gwn+closepn0p12 · 1000099 %748.922.95018.534.1
gwn+closepn0p12 · 1000099.5 %1126.492.14016.339.8
gwn+closepn0p12 · 1000099.9 %3174.071.35111.347.4
gwn+closepn0p12 · 10000full61443.031.0006.961.5
gwn+closepn0p12 · 2048first 1024 atoms10244.641.07118.340.1
gwn+closepn0p12 · 204895 %4818.194.20039.018.2
gwn+closepn0p12 · 204899 %1688.812.03027.530.5
gwn+closepn0p12 · 204899.5 %2717.031.62025.431.4
gwn+closepn0p12 · 204899.9 %7334.921.14119.937.5
gwn+closepn0p12 · 2048full61444.331.00014.045.1
gwn+closepn0p6 · 10000first 1024 atoms10245.041.06222.533.1
gwn+closepn0p6 · 1000095 %4319.904.20042.814.1
gwn+closepn0p6 · 1000099 %1579.231.95131.323.5
gwn+closepn0p6 · 1000099.5 %2487.151.51128.725.6
gwn+closepn0p6 · 1000099.9 %6345.271.11123.731.0
gwn+closepn0p6 · 10000full61444.741.00016.237.2
gwn+closepn0p6 · 2048first 1024 atoms10247.710.97429.422.1
gwn+closepn0p6 · 204895 %3920.482.57049.89.4
gwn+closepn0p6 · 204899 %15910.291.29135.417.1
gwn+closepn0p6 · 204899.5 %2828.961.13233.218.5
gwn+closepn0p6 · 204899.9 %8267.931.00428.922.3
gwn+closepn0p6 · 2048full61447.971.00020.925.0

From 1024 atoms (the bench's encodes)

descriptorcloudenergyatoms (median)error ×10⁻³error / fullobjects better (2 %)normal median °PGP10 %
mesh routeclean95 %2718.816.74039.520.4
mesh routeclean99 %658.032.88018.634.0
mesh routeclean99.5 %995.752.06011.943.9
mesh routecleanfull10242.791.0006.861.4
gwn+closeclean · 1000095 %2517.575.68036.417.6
gwn+closeclean · 1000099 %738.132.63020.934.9
gwn+closeclean · 1000099.5 %1086.452.09017.441.5
gwn+closeclean · 10000full10243.091.0007.257.0
gwn+closeclean · 204895 %3618.724.00040.617.1
gwn+closeclean · 204899 %1518.871.90027.631.6
gwn+closeclean · 204899.5 %2507.231.55026.035.5
gwn+closeclean · 2048full10244.671.00018.742.6
gwn+closepn0p12 · 1000095 %2917.175.21039.813.5
gwn+closepn0p12 · 1000099 %749.032.74018.633.6
gwn+closepn0p12 · 1000099.5 %1106.662.02016.339.4
gwn+closepn0p12 · 10000full10243.291.0008.155.5
gwn+closepn0p12 · 204895 %4818.303.94039.118.1
gwn+closepn0p12 · 204899 %1629.051.95027.630.7
gwn+closepn0p12 · 204899.5 %2557.341.58025.631.3
gwn+closepn0p12 · 2048full10244.641.00018.340.1
gwn+closepn0p6 · 1000095 %4320.083.98043.214.0
gwn+closepn0p6 · 1000099 %1539.591.90031.023.5
gwn+closepn0p6 · 1000099.5 %2397.601.51029.025.6
gwn+closepn0p6 · 10000full10245.041.00022.533.1
gwn+closepn0p6 · 204895 %3820.702.69049.99.2
gwn+closepn0p6 · 204899 %15210.521.36035.816.9
gwn+closepn0p6 · 204899.5 %2619.191.19133.218.0
gwn+closepn0p6 · 2048full10247.711.00029.422.1

Renders

Decoded at 96 cells per axis for the picture (the scores from the decode at the layers), coloured by part.

clean_10000

clean_2048

pn0p6_10000

The knee of the atom energy

Your knee (docs/noisy_clouds.md §3 in fcp_devel): with the noise in the atoms (a noisy mesh, exact slices), |v| in selection order stays high, drops, then decays as T−1: the tail is the noise. Here on point clouds, 6144 atoms, gwn + closing, the knee by Kneedle (the atom farthest below the chord of log |v|, running median 64, against log T; the last 5 % left out), scripts/knee.py.

descriptorknee (median atoms)energy at the kneetail slopeChamfer knee / fullnormal knee / full °best normal (atoms)objects: normal better at the kneeobjects: Chamfer better at the knee
mesh route (clean mesh)11899.73 %-1.555.06 / 2.4812.8 / 4.54.5 (6144)0 of 100 of 10
0.12 %, σ given11299.61 %-1.576.39 / 3.0314.1 / 6.96.6 (4146)0 of 100 of 10
0.6 %, σ given13199.36 %-1.688.81 / 4.7422.5 / 16.216.2 (5081)0 of 100 of 10
0.6 %, σ = 016599.38 %-1.239.42 / 5.1130.2 / 24.923.7 (2147)3 of 100 of 10
1.2 %, σ given7299.04 %-1.7614.65 / 8.7643.7 / 23.923.8 (5236)0 of 100 of 10
1.2 %, σ = 026998.78 %-1.0913.65 / 8.2546.4 / 46.443.6 (3258)4 of 100 of 10

σ = 0 per object

knee atoms (energy): Chamfer knee / full · normal median knee / full.

object0.6 %, σ = 01.2 %, σ = 0
battery_stepped121 (99.4 %): 7.45 / 4.17 · 5° / 9°241 (98.6 %): 8.80 / 6.27 · 9° / 28°
bowl_gso5835 (100.0 %): 9.86 / 9.85 · 25° / 24°5616 (100.0 %): 7.50 / 7.52 · 53° / 53°
bowl_of92 (97.4 %): 17.61 / 3.82 · 13° / 13°5834 (100.0 %): 26.74 / 26.86 · 45° / 45°
dino267 (99.6 %): 5.65 / 3.88 · 30° / 30°297 (99.1 %): 8.32 / 7.28 · 40° / 47°
dress155 (97.7 %): 6.36 / 3.63 · 38° / 34°192 (95.7 %): 8.91 / 6.81 · 47° / 53°
elephant100 (97.8 %): 12.01 / 3.88 · 37° / 23°203 (98.2 %): 10.82 / 6.44 · 39° / 38°
horse169 (99.4 %): 5.68 / 3.32 · 23° / 26°354 (99.0 %): 8.11 / 7.20 · 38° / 44°
mug161 (97.5 %): 17.16 / 4.16 · 32° / 17°64 (69.2 %): 61.09 / 7.67 · 83° / 42°
open_box5835 (100.0 %): 23.24 / 23.56 · 55° / 54°5826 (100.0 %): 9.57 / 9.52 · 67° / 66°
shoe5831 (100.0 %): 4.36 / 4.37 · 31° / 31°64 (60.3 %): 33.48 / 6.86 · 84° / 53°

Chamfer against the energy not yet decoded

Renders: pn0p6_10000

Renders: pn1p2_10000

The tolerance knob: eps = max(0.5 local spacing, C sigma)

C sets how far the tolerance reaches once σ is given: larger C gives fewer parts and smoother surfaces, and loses detail under C σ (the horse's legs at 1.2 % noise). gwn uses C = 3.

cloudslicerpartsmeshmedian parts / meshobjects within ±25 %error ×10⁻³error / packagenormal median °PGP10 %PGP30 %emptyencode s
point noise 0.12 % · 10000C = 3 (gwn)1461340.9473.060.767.855.282.0False1.71
point noise 0.6 % · 10000C = 3 (gwn)2841341.2724.680.5222.132.962.6False2.49
point noise 1.2 % · 10000C = 3 (gwn)1881340.6918.840.6633.822.050.2False3.51
point noise 0.12 % · 2048C = 3 (gwn)1851341.2435.400.9018.439.666.3False0.29
point noise 0.6 % · 2048C = 3 (gwn)2111341.6517.080.7827.619.851.1False0.32
point noise 1.2 % · 2048C = 3 (gwn)1571341.01210.720.9940.715.443.6False0.35

What each piece does

Each row adds one piece (σ given on the point-noise clouds from row 6). Tried and dropped, numbers in report/bench.csv: per-point flips by the jump of w (they flip thin walls); a thin-sheet band from a normalized point density, flagged by cancelling normals or by low winding contrast (more parts and error everywhere, 3x the error under noise: noisy normals cancel like a thin wall); a noise estimate from w itself (reads 1/3 of σ on thin-walled objects, overlaps clean and noisy at 2048: report/sigma_gwn_v*.txt); the cut tolerance following σ (no change); the point-test cut rule (about 10 % fewer parts, slightly more error).

cloudslicerpartsmeshmedian parts / meshobjects within ±25 %error ×10⁻³error / packagenormal median °PGP10 %PGP30 %emptyencode s
clean · 10000package2091341.4343.751.008.650.977.8False0.17
7. gwn: + patch flips only when sigma = 01361340.9483.010.807.256.283.0False1.81
clean · 2048package2731342.4936.861.0028.430.756.3False0.15
7. gwn: + patch flips only when sigma = 01711341.3914.740.6918.243.069.8False0.29
point noise 0.6 % · 10000package13981349.5908.971.0055.112.830.1False0.27
7. gwn: + patch flips only when sigma = 02841341.2724.680.5222.132.962.6False2.49
point noise 1.2 % · 10000package321613426.52013.421.0072.48.020.9False0.39
7. gwn: + patch flips only when sigma = 01881340.6918.840.6633.822.050.2False3.51
density stripes · 10000package13231349.8509.591.0057.114.030.1False0.28
7. gwn: + patch flips only when sigma = 02731341.6714.370.4612.144.871.0False2.85

Method

One global field replaces the per-layer flood. For the oriented, area-weighted points the mollified generalized winding number

w(x) = 1/(4π) Σi ai ni·(pi − x) / (|pi − x|² + εi²)3/2

is about 1 inside and 0 outside, and each layer's rings are its iso-contour on that layer's raster (surf's own marching squares and loop chaining). The steps, each with the literature it comes from:

  1. Area weights ai = π r16²/15 (the package's pose weights): the field does not depend on the density (fast winding numbers for point clouds, Barill et al. 2018). This is what fixes the variable-density clouds.
  2. Directions and orientation: surf's robust directions and leak-proof flood, then patch flips: patches of the k-NN graph along one sheet are flipped greedily to make w two-valued on probes off the surface (a double well w²(1−w)², and w = 0 beyond the cloud's box), plus one global check (the jump of w across the points must be positive). This is the GCNO objective (Xu et al. 2023) on patches, a discrete version of the linear orientation of Gotsman & Hormann 2024. Used only when σ = 0 (measured: under noise they hurt).
  3. Tangent splats: each dipole is spread over a disc in its tangent plane (centre + hexagon of radius 1.25 local spacings, a/7 each) with a small mollifier (0.25 local spacings): wide along the surface, so the gaps between samples close; sharp across it, so a wall of about one spacing keeps its two faces apart (surfel / disc winding numbers).
  4. Local iso-value: the contour is w = τ(x), τ the Shepard average of w at the points (weights a/(d²+ε²)²), clamped to [0.05, 0.95]. The contour passes through the points, as an interpolating implicit (VIPSS, Huang et al. 2019); a global median of w at the points (DWG's iso-value, Liu et al. 2025) loses thin parts.
  5. One scale εi = max(0.5 local spacingi, C σ), C = 3: the mollifier, the splats' mollifier, the Shepard width and the minimum loop (2 max(s, ε)²) all follow it. σ = 0 is the exact end, σ > 0 the tolerant one.

Everything after the slicer is the package's (tracking, tracked-fit cuts with fit_tol="auto", atoms). The field is a direct O(points × pixels) sum on the GPU (CuPy kernels in gwn.py).

Open items

  1. Speed. The field sums every splat at every pixel (O(7 n × pixels)). Barnes-Hut / fast winding numbers (Barill 2018: exact near, cluster dipoles far) and evaluating the far pixels on a coarse grid (the contour lies within a few ε of the points) should bring 50k to about a second. Then a numba twin for the CPU, as surf has.
  2. σ is a parameter. The estimate from w itself (each point's offset through the flat-sheet profile, gradient normals) separates clean from noisy at 10k but reads about a third of σ on thin-walled objects and overlaps clean and noisy at 2048 (sigma_gwn_v2.txt).
  3. Thin walls under heavy noise. When σ is far above the wall's thickness the two faces' dipoles cancel whatever their orientation (mug, bowls at 1.2 %: 30–50 parts against 6–8). Both thin-sheet bands tried failed (noisy normals cancel like thin walls).
  4. Thin features under the tolerance go (the horse's legs at 1.2 % with C = 3): C = 2.5 keeps more (knob table), at more parts.
  5. The package change is a decision for api/DECISIONS.md: a new slicer (and encoder version), noise= (σ) as the encoder's tolerance parameter.

Slices

Points within half a layer (grey), the mesh's section (black), the package's rings (blue), the GWN variants' rings (orange), at the layers with the most GWN rings. bowl_gso 2048: the rim wall is far thinner than the spacing, the earliest GWN breaks it into beads; mug, point noise 1.2 %: with σ and the first orientation the outside read as inside (the raster border is a contour), fixed by the outside probes and the global check; loaf pan 10k: a wall of about a spacing, fragmented before the splats.

slices_bowl_gso_2048_thin

slices_bowl_gso_2048_v1

slices_loaf_pan_10000

slices_loaf_pan_10000_close

slices_mug_pn1p2_10000

Rendered before the open box replaced the loaf pan, and without the closing.

clean_10000

clean_2048

dstripe_10000

pn0p6_10000

pn1p2_10000