FCP: collision and inside-outside queries straight from the descriptor

fcp_2910_collision · branch v5.31 · GTX 1650 (4 GB), 12 CPU threads · 6144 atoms, 128 layers · generated 2026-10-29 11:47

Question. Can the descriptor answer "is this point inside?" and "do these two placed objects collide?" directly, without decoding to a mesh, and faster than the standard and state-of-the-art mesh methods? Seven watertight objects (8k–444k faces), each a 2048-atom descriptor at 128 layers, encoded once and cached. The encode time is not counted, because the descriptor is the stored form. The baselines run on the original meshes.
How FCP answers (scripts/fcpq.py). A point is inside when the signed sum over the parts spanning its height of "inside the part's ring at that height" is positive (hole parts count −1). The ring at height s comes from one of two modes: table evaluates the rings once at the layer centres (128 vertices each, from the stored polynomial: about 5 ms per object, no mesh, no grid). A query blends the two neighbouring rings vertex by vertex and runs an even-odd crossing test. Edges are pre-binned in y (32 bins per layer interval), so a query reads only the few edges that its ray can cross. poly evaluates the stored Chebyshev × Fourier polynomial at the exact height for every query (2K coefficients, then the NT ring vertices), followed by the same crossing test. Collision: the table ring vertices of each object, optionally strided in layers × vertices, are mapped into the other's canonical frame and tested. A pose collides when any vertex is inside. All poses run in one CUDA launch (or a numba prange). CPU and GPU give the same answers.
Findings.
Caveats. FCL, Coal and Warp test surface intersection only. Containment (one object fully inside the other) is added to the ground truth, but it never occurred in these poses. FCL, Coal and PyBullet are timed through their Python bindings, one pose per call, which adds a few µs of overhead per pose. The GPU methods batch all 2000 poses. The Warp collision kernel tests every triangle of A against B's BVH, so it scales with A's faces (the 444k-face dress: 225 µs/pose). The PyBullet "build" of mug-bowl and mug-mug reads as 0 because the V-HACD output was cached from a smoke run (the uncached mug + bowl took 27 s). The first FCP build of each run includes CuPy's first transfer (mug-bowl 105 ms; the others take 6–10 ms). FCP's collision samples are the side-surface ring vertices: a penetration shallower than the sample spacing, or one only through a flat cap, can be missed. GPU: a GTX 1650, which is small. Larger GPUs would favour both GPU methods.

1. Everyday objects

1a. Inside-outside point queries

Near the surface (±2 % of the diagonal), 1M points

methoddevicemug
ns/pt
bowl
ns/pt
dino
ns/pt
elephant
ns/pt
shoe
ns/pt
horse
ns/pt
dress
ns/pt
geo-mean
ns/pt
× vs
Open3D
build ms
(mean)
wrong vs
mesh %
wrong vs
decoded %
FCP table (CUDA)gpu3.583.834.064.053.943.724.433.9414.14.992.42
FCP table (CPU)cpu1917.22219.119.316.91918.92.94.992.42
FCP table, no bins (CUDA)gpu51.15529.932.34130.532.137.81.54.992.42
Open3D occupancy (Embree)cpu55.16647.860.455.140.569.355.51.00.6440.00
Warp BVH winding number (CUDA)gpu1171632284312701598292530.23.90.00
libigl fast winding numbercpu8627136306208555535776770.129.60.00
FCP poly (CPU)cpu1.02e+031.05e+036386047496166097350.14.993.01
FCP poly (CUDA)gpu8315651.36e+031.41e+031.43e+031.26e+031.25e+031.11e+030.14.993.01

Uniform in the box padded by 10 %, 1M points

methoddevicemug
ns/pt
bowl
ns/pt
dino
ns/pt
elephant
ns/pt
shoe
ns/pt
horse
ns/pt
dress
ns/pt
geo-mean
ns/pt
× vs
Open3D
build ms
(mean)
wrong vs
mesh %
wrong vs
decoded %
FCP table (CUDA)gpu2.191.991.021.892.051.141.541.6318.54.990.28
FCP table (CPU)cpu10.39.729.2610.511.77.928.259.583.14.990.28
FCP table, no bins (CUDA)gpu28.729.62.9110.218.16.627.5611.32.74.990.28
Open3D occupancy (Embree)cpu32.341.420.731.732.621.936.130.11.00.6440.00
FCP poly (CPU)cpu47357187.91823141761352280.14.990.36
libigl fast winding numbercpu3712711202104131862692440.129.60.00
Warp BVH winding number (CUDA)gpu1222502255572491671.05e+032890.13.90.00
FCP poly (CUDA)gpu5843722966318515134775060.14.990.36

Small batches: near the surface, 10k points

methoddevicemug
ns/pt
bowl
ns/pt
dino
ns/pt
elephant
ns/pt
shoe
ns/pt
horse
ns/pt
dress
ns/pt
geo-mean
ns/pt
× vs
Open3D
build ms
(mean)
wrong vs
mesh %
wrong vs
decoded %
FCP table (CUDA)gpu6.887.167.738.677.616.759.117.6610.94.992.531.11
FCP table (CPU)cpu20.31820.820.520.519.722028.13.04.992.531.11
FCP table, no bins (CUDA)gpu58.360.436.743.348.535.938.845.11.84.992.531.11
Open3D occupancy (Embree)cpu88.887.575.294.583.26296.683.21.00.6440.002.03
Warp BVH winding number (CUDA)gpu1251532074002401699482500.33.90.002.03
FCP poly (CPU)cpu1.04e+031.06e+036386137448816137780.14.993.102.01
FCP poly (CUDA)gpu1.1e+037801.51e+031.65e+031.62e+031.63e+031.5e+031.36e+030.14.993.102.01
trimesh contains (Embree)cpu1.95e+031.87e+031.64e+032.46e+032.21e+031.7e+032.71e+032.05e+030.070.50.002.03
libigl fast winding numbercpu1.4e+031.52e+031.87e+032.47e+032.35e+031.78e+031.48e+042.5e+030.029.60.002.03

Where FCP is wrong (near-surface points in a thin slab; red = FCP disagrees with the mesh)

1b. Mesh-mesh collision

methoddevicemug-bowl
µs/pose
dino-elephant
µs/pose
shoe-horse
µs/pose
mug-mug
µs/pose
dress-horse
µs/pose
geo-mean
µs/pose
× vs
Coal
build ms
(mean)
accuracy
vs mesh %
missedfalseaccuracy vs
decoded %
FCP stride 4x4 (CUDA)gpu1.480.8670.751.270.6470.95319.728.399.4945699.65
FCP stride 2x2 (CUDA)gpu2.111.71.341.71.331.6111.728.399.64251199.78
FCP stride 4x4 (CPU)cpu1.824.742.41.42.812.417.828.399.4945699.65
FCP stride 1x1 (CUDA)gpu4.253.563.73.533.93.785.028.399.70161499.80
FCP stride 2x2 (CPU)cpu5.9614.212.14.679.128.472.228.399.64251199.78
FCL (BVH, tri-tri)cpu21.81613.820.212.616.51.1379.8100.000099.80
Coal (BVH, tri-tri)cpu24.717.515.223.615.118.81.0279.1100.000099.80
FCP stride 1x1 (CPU)cpu21.150.123.716.831.626.60.728.399.70161499.80
Warp (GPU BVH, tri-tri)gpu22.824.928.420.322637.50.54.265100.000099.80
PyBullet (V-HACD + GJK)cpu3372341853611572420.10.0295998.67013398.49

mug-bowl: 8454 / 14030 faces, 66 % of 2000 poses collide; dino-elephant: 19690 / 52216 faces, 40 % of 2000 poses collide; shoe-horse: 24314 / 13840 faces, 39 % of 2000 poses collide; mug-mug: 8454 / 8454 faces, 62 % of 2000 poses collide; dress-horse: 444020 / 13840 faces, 31 % of 2000 poses collide

How wrong are the misses

Example poses (decoded meshes for display; blue = A fixed, orange = B placed)

2. Concave, interleaved and perforated shapes

2a. Inside-outside point queries

Near the surface (±2 % of the diagonal), 1M points

methoddevicepan_holes
ns/pt
dog
ns/pt
knot
ns/pt
spring
ns/pt
sponge
ns/pt
gear
ns/pt
geo-mean
ns/pt
× vs
Open3D
build ms
(mean)
wrong vs
mesh %
wrong vs
decoded %
FCP table (CUDA)gpu5.884.693.176.737.331.694.411.56.358.04
FCP table (CPU)cpu20.321.11520.536.98.9218.82.76.358.04
FCP table, no bins (CUDA)gpu41.532.828.930.94827.434.21.56.358.04
Open3D occupancy (Embree)cpu49.67350.258.150.231.650.61.00.320.00
Warp BVH winding number (CUDA)gpu16858029968613942.42210.23.040.00
FCP poly (CPU)cpu1.02e+035677335847254656610.16.357.52
FCP poly (CUDA)gpu6741.46e+031.34e+036152.65e+032669110.16.357.52
libigl fast winding numbercpu1.57e+037874585841.47e+031.81e+039800.1130.00

Uniform in the box padded by 10 %, 1M points

methoddevicepan_holes
ns/pt
dog
ns/pt
knot
ns/pt
spring
ns/pt
sponge
ns/pt
gear
ns/pt
geo-mean
ns/pt
× vs
Open3D
build ms
(mean)
wrong vs
mesh %
wrong vs
decoded %
FCP table (CUDA)gpu2.661.971.083.885.551.262.3114.26.351.44
FCP table (CPU)cpu10.511.56.6112.129.511.512.22.76.351.44
FCP table, no bins (CUDA)gpu30.211.45.0618.230.521.316.62.06.351.44
Open3D occupancy (Embree)cpu31.435.225.538.446.124.932.81.00.320.00
Warp BVH winding number (CUDA)gpu14653238259910939.92060.23.040.00
FCP poly (CPU)cpu4921882653424763483340.16.351.29
libigl fast winding numbercpu1.11e+032401592491.38e+031.53e+035300.1130.00
FCP poly (CUDA)gpu4946406995431.63e+032055850.16.351.29

Small batches: near the surface, 10k points

methoddevicepan_holes
ns/pt
dog
ns/pt
knot
ns/pt
spring
ns/pt
sponge
ns/pt
gear
ns/pt
geo-mean
ns/pt
× vs
Open3D
build ms
(mean)
wrong vs
mesh %
wrong vs
decoded %
FCP table (CUDA)gpu10.38.737.0510.611.94.698.59.56.358.112.10
FCP table (CPU)cpu22.622.416.421.236.79.3419.84.16.358.112.10
FCP table, no bins (CUDA)gpu48.340.835.436.359.932.941.31.96.358.112.10
Open3D occupancy (Embree)cpu81.611179.187.382.15380.51.00.320.007.33
Warp BVH winding number (CUDA)gpu19252627565118244.12290.43.040.007.33
FCP poly (CPU)cpu1e+035717555817624696690.16.357.572.01
FCP poly (CUDA)gpu9591.64e+031.55e+039082.82e+034041.17e+030.16.357.572.01
trimesh contains (Embree)cpu2.2e+032.91e+031.63e+032.68e+032.28e+031.1e+032.03e+030.027.70.007.33
libigl fast winding numbercpu2.59e+033.24e+031.91e+033.43e+031.59e+031.89e+032.34e+030.0130.007.33

Where FCP is wrong (near-surface points in a thin slab; red = FCP disagrees with the mesh)

2b. Mesh-mesh collision

methoddevicespring-spring
µs/pose
knot-knot
µs/pose
sponge-sponge
µs/pose
gear-gear
µs/pose
pan_holes-dog
µs/pose
knot-spring
µs/pose
geo-mean
µs/pose
× vs
Coal
build ms
(mean)
accuracy
vs mesh %
missedfalseaccuracy vs
decoded %
FCP stride 4x4 (CUDA)gpu1.961.082.780.5661.161.881.3916.628.6798.72154099.63
FCP stride 4x4 (CPU)cpu0.6453.977.350.8031.532.031.912.128.6798.72154099.63
FCP stride 2x2 (CUDA)gpu2.522.034.810.8651.672.822.1610.728.6798.97123199.82
FCP stride 1x1 (CUDA)gpu3.026.9814.31.752.965.044.465.228.6799.05111399.90
FCP stride 2x2 (CPU)cpu1.7613.822.42.774.256.855.943.928.6798.97123199.82
FCL (BVH, tri-tri)cpu24.615.332.814.623.917.820.61.1206.1100.000099.05
FCP stride 1x1 (CPU)cpu5.9251.474.310.314.225.120.91.128.6799.05111399.90
Coal (BVH, tri-tri)cpu26.116.140.115.429.319.5231.0150.8100.000099.05
Warp (GPU BVH, tri-tri)gpu51.728.113.811.38234.229.30.87.797100.000099.05
PyBullet (V-HACD + GJK)cpu4851807012873562743460.11.356e+0498.77014797.84

spring-spring: 76796 / 76796 faces, 77 % of 2000 poses collide; knot-knot: 28800 / 28800 faces, 52 % of 2000 poses collide; sponge-sponge: 1300 / 1300 faces, 75 % of 2000 poses collide; gear-gear: 992 / 992 faces, 76 % of 2000 poses collide; pan_holes-dog: 16092 / 67204 faces, 57 % of 2000 poses collide; knot-spring: 28800 / 76796 faces, 59 % of 2000 poses collide

How wrong are the misses

Example poses (decoded meshes for display; blue = A fixed, orange = B placed)

Files

Scripts in ../scripts (fcpq.py = the descriptor queries, points_bench.py, collide_bench.py (FCP_SET=complex for section 2), make_shapes.py, renders.py, report.py); raw numbers in points*.csv, collide*.csv, logs alongside. The first run at 2048 atoms: a2048/.