Approximate collision and inside-outside on the CPU, straight from the descriptor
Package v5.5 (commit 7ccd2a39, parts="tracked-fit", interp="poly"). 12 objects (household: mug, mug ace, bowl, bowl serving, bowl dog, flower pot, shoe, jogger; figurines: dino, horse; synthetic: spring, knot; 4.6k–77k faces; the household objects and figurines from Google Scanned Objects), encoded at 128, 256 and 512 layers
(N 128, M 64, 2048 samples per ring, small islands and one-layer pieces kept where the package drops them, every atom,
cut at 1e-8 of the energy) and queried without decoding: a point is inside when the rings at its
nearest layer centre contain it (512 vertices per ring); two placed objects collide when a ring vertex of one lies inside
the other. CPU: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz, FCP in numba with 12 threads and with 1. References on the same CPU: Embree (Open3D
RaycastingScene occupancy, its own threads) for points, FCL (BVH, triangle-triangle, one call per pose through python-fcl,
1 thread) for collision. The error bound is the largest distance from the true surface at which an answer was still wrong:
over 1M points within ±2 % of the diagonal of the surface plus 1M uniform points per object, and over 2000 poses near
contact per pair (11 pairs; the true depth or gap of every pose from the meshes). Beyond it, every answer was right.
| layers | size, MB median (max) | points: error bound, % diag | points: wrong, % (±2 % band) | points: FCP, ns/point (12 threads) | points: FCP, ns/point (1 thread) | points: Embree, ns/point | collision: deepest miss, % diag | collision: widest false hit, % diag | collision: correct, % of poses | collision: FCP, µs/pose (12 threads) | collision: FCP, µs/pose (1 thread) | collision: FCL, µs/pose |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 128 | 0.20 (0.55) | 0.55 | 1.25 | 27.9 | 212 | 52.4 | 0.15 | 0.17 | 99.86 | 22.7 | 127 | 18.4 |
| 256 | 0.27 (0.99) | 0.34 | 0.64 | 24.6 | 200 | 52.4 | 0.00 | 0.05 | 99.94 | 24.0 | 132 | 18.4 |
| 512 | 0.37 (1.76) | 0.29 | 0.37 | 25.9 | 204 | 52.4 | 0.00 | 0.03 | 99.95 | 24.5 | 142 | 18.4 |
Points: FCP answers in 24.6–27.9 ns per point with 12 threads, 1.9× faster than Embree on the same CPU, at every layer count. The error bound is 0.29–0.55 % of the diagonal; its worst case comes from a handful of points at sharp details and pieces that start or end between two slices (below), and it falls with the layers; the share of wrong answers near the surface falls 1.25 → 0.37 %. Collision: no missed contact deeper than 0.15 % of the diagonal and no false one wider than 0.17 % at 128 layers (0.05 % from 256 layers); 99.86–99.95 % of poses correct. On the CPU, FCP is not faster than FCL at this sampling: 22.7–24.5 µs per pose with 12 threads and 127–142 µs with 1, against 18.4 µs for FCL on one thread; with every other sample FCP takes 6.2–7.0 µs (99.86–99.92 % correct, misses up to 0.62 %). FCP's collision speed-up is a GPU result (the poses in one batch).
Solid properties straight from the descriptor
The 12 objects of the collision section; CPU: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz, one thread. Volume from the stored atoms in one contraction (no rings, no grid); convex hull from the rings (prototype, not in the package). Volume error against the source mesh's volume (volume rows), its convex hull's volume (hull rows), or the reference bound's volume (box and sphere rows; the sphere's from its radius; trimesh's minimum sphere is up to 0.4 % too large and slower, so the exact ball is the reference); the boxes and the sphere are taken from the vertices of the descriptor's hull (M = 64), and their FCP time includes that hull; the hull gap from 20k points on the true hull to the descriptor's hull. The 6144-atom runs from points ran at a load average of about 7: their times are upper bounds.
| property | layers | atoms | directions M | FCP, ms median (range) | reference on the source mesh | reference, ms median (range) | decode + reference, ms median | FCP faster than the reference | volume error, % median (worst) | hull surface gap, % diag: median of means (max) | farthest mesh vertex outside the bound, % diag (max) |
|---|---|---|---|---|---|---|---|---|---|---|---|
| volume (encoded from the mesh) | 128 | 1024 | – | 0.028 (0.019–0.046) | exact mesh volume (divergence theorem, numba) | 0.044 (0.014–0.235) | 309 | 10 / 12 | -0.47 (-0.88) | – | – |
| volume (encoded from the mesh) | 128 | 2048 | – | 0.038 (0.023–0.061) | exact mesh volume (tetrahedron sum, numba) | 0.052 (0.027–0.235) | – | 8 / 12 | -0.31 (-0.74) | – | – |
| volume (encoded from the mesh) | 128 | 6144 | – | 0.042 (0.024–0.063) | exact mesh volume (divergence theorem, numba) | 0.044 (0.014–0.235) | 354 | 6 / 12 | -0.17 (-0.74) | – | – |
| volume (encoded from the mesh) | 256 | 6144 | – | 0.044 (0.025–0.084) | exact mesh volume (divergence theorem, numba) | 0.044 (0.014–0.235) | 1401 | 3 / 12 | -0.13 (-0.71) | – | – |
| volume (encoded from 20k points) | 128 | 2048 | – | 0.032 (0.020–0.057) | exact mesh volume (tetrahedron sum, numba) | 0.052 (0.027–0.235) | – | 9 / 12 | -0.16 (+1.76) | – | – |
| volume (encoded from 20k points) | 128 | 6144 | – | 0.041 (0.020–0.069) | exact mesh volume (tetrahedron sum, numba) | 0.052 (0.027–0.235) | – | 8 / 12 | -0.15 (+1.82) | – | – |
| volume (encoded from 20k points) | 256 | 6144 | – | 0.046 (0.019–0.061) | exact mesh volume (tetrahedron sum, numba) | 0.052 (0.027–0.235) | – | 9 / 12 | -0.16 (+1.87) | – | – |
| convex hull | 128 | 1024 | 64 | 3.8 (2.6–13.9) | qhull on the mesh's vertices | 4.0 (2.1–16.3) | 116 | 6 / 12 | -0.95 (-3.88) | 0.166 (1.31) | – |
| convex hull | 128 | 6144 | 64 | 4.0 (2.3–13.9) | qhull on the mesh's vertices | 4.0 (2.1–16.3) | 107 | 7 / 12 | -1.04 (-3.78) | 0.131 (1.21) | – |
| convex hull | 128 | 6144 | 128 | 5.8 (4.8–26.7) | qhull on the mesh's vertices | 4.0 (2.1–16.3) | 107 | 1 / 12 | -0.66 (-3.52) | 0.115 (1.14) | – |
| convex hull | 256 | 6144 | 128 | 7.9 (6.2–51.5) | qhull on the mesh's vertices | 4.0 (2.1–16.3) | 407 | 0 / 12 | -0.46 (-3.51) | 0.110 (1.14) | – |
| axis-aligned box | 128 | 2048 | 64 | 2.7 (2.0–13.9) | the mesh vertices' min / max | 0.3 (0.1–1.8) | – | 0 / 12 | +0.06 (-2.16) | – | 0.52 |
| axis-aligned box | 128 | 6144 | 64 | 2.7 (2.0–13.8) | the mesh vertices' min / max | 0.3 (0.1–1.8) | – | 0 / 12 | -0.20 (-2.13) | – | 0.52 |
| axis-aligned box | 256 | 6144 | 64 | 4.5 (3.5–26.0) | the mesh vertices' min / max | 0.3 (0.1–1.8) | – | 0 / 12 | -0.34 (-2.28) | – | 0.53 |
| oriented box | 128 | 2048 | 64 | 6.3 (5.1–18.6) | minimum-volume OBB (trimesh) | 79.7 (20.4–150.9) | – | 12 / 12 | +0.27 (-2.32) | – | 0.49 |
| oriented box | 128 | 6144 | 64 | 6.4 (5.1–18.6) | minimum-volume OBB (trimesh) | 79.7 (20.4–150.9) | – | 12 / 12 | +0.06 (-2.27) | – | 0.40 |
| oriented box | 256 | 6144 | 64 | 10.5 (7.3–33.9) | minimum-volume OBB (trimesh) | 79.7 (20.4–150.9) | – | 12 / 12 | +0.28 (-2.28) | – | 0.39 |
| smallest sphere | 128 | 2048 | 64 | 2.8 (2.0–14.2) | exact smallest ball (qhull, then the minimum ball) | 3.9 (2.1–18.2) | – | 10 / 12 | +0.53 (+2.21) | – | 0.15 |
| smallest sphere | 128 | 6144 | 64 | 2.8 (2.0–14.2) | exact smallest ball (qhull, then the minimum ball) | 3.9 (2.1–18.2) | – | 10 / 12 | +0.66 (+2.25) | – | 0.13 |
| smallest sphere | 256 | 6144 | 64 | 4.6 (3.5–26.4) | exact smallest ball (qhull, then the minimum ball) | 3.9 (2.1–18.2) | – | 3 / 12 | +0.11 (-1.71) | – | 0.34 |
Convex hull straight from the descriptor
Package v5.31 (commit db3e6ceb; the working tree only raises MAX_ATOMS to 12288; defaults: parts="tracked-fit", interp="poly"), the 12 objects
of the collision section, encoded at 128 layers (1024 or 6144 atoms) and at 256 layers (6144 atoms). The hull is built from the
descriptor without a decode grid, a mesh or qhull: the solid rings are evaluated from the Fourier and Chebyshev
coefficients (holes are inside the solid and never touch the hull), each part's end rings are held to the part's
outer edges, and per level (a layer centre or a part edge) one numba kernel takes the 2D hull of the ring samples and
its support point in M directions. In each direction a hull vertex must lie on the upper concave envelope of
(height, support) over the levels; neighbouring directions' envelopes are joined by height and the end levels capped,
which gives a closed triangle surface in O(levels × M). Reference: scipy's qhull on the source mesh's vertices,
and the route through the decoded mesh (fcp.decode, then qhull). CPU: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz, one thread for both
(median of 21 runs; the decode route of 7). Distances in % of the source mesh's diagonal, from 20k points on the
source mesh's hull to the descriptor's hull; volume relative to the source mesh's hull.
| layers | atoms | directions M | FCP hull, ms (median) | qhull on the mesh, ms (median) | decode + qhull, ms (median) | FCP faster than qhull | speed-up, median (min–max) | volume / true hull, median (min) | gap, mean % diag | gap, max % diag | non-convex dent, max % diag | hulls with no over-shared edge |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 128 | 1024 | 64 | 3.8 | 4.0 | 116 | 6 / 12 | 0.98 (0.45–1.89) | 0.991 (0.961) | 0.162 | 1.31 | 0.59 | 7 / 12 |
| 128 | 6144 | 64 | 4.0 | 4.0 | 107 | 7 / 12 | 1.06 (0.43–2.36) | 0.990 (0.962) | 0.128 | 1.21 | 0.71 | 8 / 12 |
| 128 | 6144 | 128 | 5.8 | 4.0 | 107 | 1 / 12 | 0.60 (0.26–1.05) | 0.993 (0.965) | 0.111 | 1.14 | 0.35 | 7 / 12 |
| 256 | 6144 | 128 | 7.9 | 4.0 | 407 | 0 / 12 | 0.44 (0.15–0.89) | 0.995 (0.965) | 0.104 | 1.14 | 0.24 | 5 / 12 |
Time: at 128 layers and M = 64 the descriptor's hull takes 2.3–13.9 ms, against 2.1–16.3 ms for qhull on these meshes (2.3k–38k vertices): about even, faster on 7 of 12 (mug, bowl, bowl serving, shoe, jogger, spring, knot), at most 2.36×. The gain is in not having the mesh: the decode route is 69–195 ms, 13–66× slower. qhull's cost grows with the hull it outputs, and the descriptor's hull has more vertices than the mesh's (the rings are dense where the mesh is coarse), so a large source mesh is where the descriptor wins outright (a 222k-vertex dress outside this set: 3.5 ms against 39 ms); the knot and spring, with many parts per level, are the slowest descriptor hulls. M = 128 and 256 layers cost 1.5× and 2.0× the time (medians). Accuracy: the volume is 0.962–0.998 of the true hull at 128 layers, the mean gap 0.05–0.29 % of the diagonal, the largest 1.21 % (mug). Nearly all of it is the encoding, not the hull: the decoded mesh's hull (qhull) is as far from the true hull (mean 0.02–0.25 %, max up to 1.15 %), and the descriptor's hull is within a mean 0.02–0.07 % of it (max 0.18–0.72 %, the spring and knot at the top: 64 directions are too few there; M = 128 lowers their max gap, knot 0.58 → 0.15 %, spring 0.74 → 0.33 % (decoded mesh's hull: 0.09, 0.24 %)). A flat top or bottom also moves: a part ends at a layer edge, so a cap sits up to half a layer from the true one (the mug's top: 0.19 % of the diagonal). 256 layers lower the gap only where the layers were the limit (horse, mug ace, spring); elsewhere it is the rings' own error. Defects: the surface is convex only up to the M directions (dents up to 0.71 % of the diagonal at M = 64, on the spring); and where one pair of support points lies on the envelopes of two non-neighbouring directions, the bands on both sides use that edge: no open edge on any of the 48 hulls, but 0–11 edges shared by four triangles (a fold, not a hole; the volume is unaffected). A final qhull over the descriptor hull's own vertices would remove both (1–6 ms on 500–2400 such vertices in the first trials), giving up most of the gain. Prototype, not in the package.
| object (128 layers, 6144 atoms, M = 64) | mesh vertices | atoms held | FCP hull, ms | qhull, ms | decode + qhull, ms | FCP hull vertices | true hull vertices | volume / true | gap, mean % | gap, max % | decoded mesh's hull: gap, mean % | its gap, max % | FCP hull to decoded hull, mean % |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| mug | 4227 | 5515 | 3.9 | 5.0 | 189 | 1819 | 792 | 0.992 | 0.137 | 1.21 | 0.128 | 1.15 | 0.040 |
| mug ace | 5612 | 6144 | 4.3 | 2.8 | 81 | 983 | 610 | 0.998 | 0.054 | 0.34 | 0.035 | 0.33 | 0.025 |
| bowl | 7017 | 2562 | 2.3 | 5.5 | 149 | 2945 | 1151 | 0.962 | 0.285 | 0.96 | 0.253 | 0.95 | 0.034 |
| bowl serving | 2975 | 2945 | 2.9 | 4.0 | 195 | 3188 | 1356 | 0.977 | 0.157 | 0.72 | 0.132 | 0.71 | 0.037 |
| bowl dog | 2308 | 2246 | 3.1 | 2.9 | 103 | 2181 | 953 | 0.992 | 0.177 | 0.87 | 0.169 | 0.84 | 0.045 |
| flower pot | 3884 | 4542 | 3.5 | 3.2 | 69 | 1855 | 881 | 0.992 | 0.135 | 0.94 | 0.114 | 0.87 | 0.046 |
| shoe | 12133 | 6144 | 4.1 | 4.1 | 130 | 1133 | 601 | 0.981 | 0.140 | 0.60 | 0.123 | 0.54 | 0.032 |
| jogger | 10041 | 6144 | 3.6 | 4.0 | 108 | 1055 | 683 | 0.984 | 0.128 | 0.70 | 0.103 | 0.70 | 0.031 |
| dino | 9845 | 6144 | 4.9 | 2.1 | 98 | 348 | 213 | 0.986 | 0.103 | 0.52 | 0.116 | 0.65 | 0.045 |
| horse | 6922 | 6144 | 4.9 | 2.2 | 103 | 560 | 350 | 0.988 | 0.082 | 0.55 | 0.058 | 0.58 | 0.040 |
| spring | 38400 | 6144 | 13.9 | 16.3 | 176 | 2765 | 3638 | 0.997 | 0.080 | 0.74 | 0.023 | 0.24 | 0.066 |
| knot | 14400 | 6144 | 7.7 | 8.6 | 105 | 4658 | 2313 | 0.993 | 0.063 | 0.58 | 0.020 | 0.09 | 0.046 |
Volume straight from the descriptor
Package v5.31 (commit db3e6ceb; the working tree only raises MAX_ATOMS), defaults
(parts="tracked-fit", interp="poly", the gwn point slicer), 128 layers, 2048 atoms; the 12 objects
of the collision section. The exact volume is the source mesh's (tetrahedra over its faces). From each mesh, 20k
area-uniform surface points (seed 3) with the true normals; every point method gets the same points.
FCP's volume is one contraction of the stored atoms: a ring's area is π Σk k (ax,kby,k − bx,kay,k)
in its Fourier coefficients, and the height atoms are orthonormal on a part's layer centres, so the sum of the ring areas
over the layers is the same expression in the atom vectors: V = dz · ∏scale · π Σparts sign · |Σk k (〈ax,k, by,k〉 − 〈bx,k, ay,k〉)|
(the midpoint rule over the layers; no ring is evaluated, no grid, no mesh; one numba loop). Standard route from points:
screened Poisson reconstruction (Open3D 0.20, depth 8), then its mesh's volume, with the true normals (the best case) and
with normals estimated from the points (what a scan gives). CPU: Intel i5-10400F; FCP's encode on numba's 12 threads and on a
GTX 1650; Poisson on one thread (its threaded run was slower here). Times are medians over the objects.
| method | volume error, median |%| | max |%| | objects within 1 % | building the representation, ms | the volume, µs |
|---|---|---|---|---|---|
| exact: tetrahedron sum over the source mesh | exact | exact | 12 / 12 | – | 51.9 |
| FCP from the mesh (encode 2048 atoms, 128 layers) | 0.31 | 0.74 | 12 / 12 | 124 (87 ms on CUDA) | 38.0 |
| FCP from 20k points (same encode, no normals) | 0.25 | 1.76 | 11 / 12 | 2791 (981 ms on CUDA) | 32.2 |
| FCP from the mesh (128 layers, 6144 atoms) | 0.17 | 0.74 | 12 / 12 | 111 (63 ms on CUDA) | 48.8 |
| FCP from 20k points (128 layers, 6144 atoms) | 0.28 | 1.82 | 11 / 12 | 2430 (914 ms on CUDA) | 41.4 |
| FCP from the mesh (256 layers, 6144 atoms) | 0.13 | 0.71 | 12 / 12 | 199 (126 ms on CUDA) | 48.4 |
| FCP from 20k points (256 layers, 6144 atoms) | 0.24 | 1.87 | 11 / 12 | 4237 (1430 ms on CUDA) | 45.6 |
| screened Poisson (depth 8) from 20k points, true normals | 0.22 | 0.90 | 12 / 12 | 1484 (1 thread) | 6306.4 |
| screened Poisson from 20k points, normals estimated (30 neighbours, tangent-plane orientation) | 71.62 | 151.84 | 2 / 12 | 2291 (1 thread, normals included) | 7526.9 |
Accuracy: FCP is within 0.74 % of the exact volume from the mesh (median 0.31 %) and within 1.76 % from the points (median 0.25 %), without normals; Poisson with the true normals is as close (median 0.22 %, max 0.90 %). With estimated normals Poisson is off by more than 5 % on 10 of 12 objects (bowl +152 %, bowl serving +145 %, mug +141 %, bowl dog +125 %, flower pot +109 %, mug ace +85 %, shoe +58 %, jogger +19 %, dino +6 %, spring -20 %): on the thin-walled ones the orientation flips across a wall and the surface closes over the opening (renders). More atoms, more layers: 6144 atoms at 128 layers and at 256 layers move the error little: from the mesh the median falls 0.31 → 0.17 → 0.13 % but the worst stays 0.71 %; from the points it is 0.25 / 0.28 / 0.24 %. The per-object errors keep their sign and size across the settings (mugs and bowls 0.2–0.7 % low, the spring from points 1.8 % high): a bias of the encoding and of the point slicer, not a shortage of atoms. The 6144-atom runs had other jobs on the machine (load average about 7, against about 0 for the 2048 run): their times in the first table are upper bounds. Time: once encoded, FCP's volume takes 20–57 µs, about the cost of the tetrahedron sum over the source mesh itself (27–235 µs, numba): faster on 9 of 12 objects, the gain growing with the mesh (spring, 77k faces: 5.1×); the volume of a Poisson mesh takes 3.3–12.7 ms (NumPy). The cost from points is the encode: 2.8 s on the CPU and 1.0 s on CUDA (median), against 1.5 s for Poisson on one thread; the descriptor, once made, also answers the other queries of this report. Caveats: clean, complete, uniformly sampled points; Poisson's meshes are not watertight by Open3D's strict test (the volume is taken as is, its sign ignored); learned reconstruction is not compared.
| object | mesh faces | FCP from mesh, error % (128 layers, 2048 atoms) | FCP from points, error % (128, 2048) | from mesh (128, 6144) | from points (128, 6144) | from mesh (256, 6144) | from points (256, 6144) | Poisson, true normals, error % | Poisson, estimated normals, error % | exact volume, µs | FCP volume, µs (128, 2048) | FCP encode from mesh, ms (CPU; 128, 2048) | FCP encode from points, ms (CPU; 128, 2048) | FCP encode from points, ms (CUDA; 128, 2048) | Poisson, true normals, ms | Poisson, estimated normals, ms |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| mug | 8454 | -0.53 | -0.40 | -0.52 | -0.40 | -0.52 | -0.43 | 0.44 | 140.96 | 27 | 29 | 82 | 2523 | 951 | 1469 | 2280 |
| mug ace | 11224 | -0.09 | 0.00 | -0.09 | -0.00 | -0.01 | 0.04 | 0.58 | 84.92 | 34 | 35 | 147 | 3620 | 1317 | 1499 | 2276 |
| bowl | 14030 | -0.39 | -0.18 | -0.39 | -0.18 | -0.42 | -0.19 | 0.76 | 151.84 | 79 | 32 | 99 | 2840 | 996 | 1323 | 2076 |
| bowl serving | 5946 | -0.49 | -0.30 | -0.48 | -0.30 | -0.48 | -0.30 | 0.90 | 145.35 | 34 | 20 | 117 | 2256 | 956 | 1197 | 1965 |
| bowl dog | 4612 | -0.74 | -0.37 | -0.74 | -0.37 | -0.71 | -0.38 | 0.44 | 124.99 | 33 | 24 | 117 | 3017 | 1069 | 1469 | 2302 |
| flower pot | 7768 | -0.44 | -0.35 | -0.44 | -0.34 | -0.48 | -0.33 | 0.30 | 108.71 | 44 | 27 | 162 | 3251 | 1230 | 1531 | 2333 |
| shoe | 24314 | -0.28 | 0.20 | -0.25 | 0.26 | -0.19 | -0.08 | 0.10 | 58.32 | 79 | 39 | 197 | 2189 | 788 | 1112 | 2889 |
| jogger | 20090 | -0.09 | -0.15 | -0.06 | -0.12 | -0.08 | -0.13 | -0.02 | 18.81 | 66 | 32 | 121 | 2342 | 901 | 2741 | 2195 |
| dino | 19690 | -0.33 | 0.02 | -0.04 | 0.17 | -0.07 | 0.18 | 0.15 | 6.09 | 60 | 57 | 127 | 2742 | 965 | 3110 | 2815 |
| horse | 13840 | -0.10 | -0.04 | -0.05 | 0.01 | -0.02 | 0.03 | 0.12 | 0.18 | 44 | 46 | 101 | 2024 | 718 | 3801 | 3940 |
| spring | 76796 | -0.10 | 1.76 | -0.04 | 1.82 | -0.02 | 1.87 | 0.10 | -20.04 | 235 | 46 | 302 | 3092 | 1245 | 1778 | 4709 |
| knot | 28800 | 0.01 | -0.31 | 0.01 | -0.30 | 0.00 | -0.29 | -0.09 | -0.08 | 86 | 32 | 215 | 3629 | 1135 | 1113 | 2028 |