mergeVertices truncates coordinates which misses some neighbors
See original GitHub issueDescribe the bug
BufferGeometryUtils.mergeVertices()
truncates all coordinates causing it to miss some neighboring vertices which are closer than the threshold sensitivity given to mergeVertices()
.
Two vertices should merge when all their coordinates (x, y, z, u, v, nx, etc…) have values closer than the threshold. But if one vertex has at least one coordinate with value that is lower than an integer multiplication of the threshold, they would not be merged. For example if the threshold is 1e-4 and vertices a and b are the same except x:
- if a.x = 1 and b.x = 1.000001 they would merge.
- if a.x = 1 and b.x = 0.999999 they would not merge.
This is caused when creating the hash for each vertex using ~~ to truncate/floor each of the vertices coordinates.
(wanted to know what are anyone’s thoughts about the matter before i might just solve this in a PR)
To Reproduce
Steps to reproduce the behavior:
- Go to https://codesandbox.io/s/threejs-cubes-merge-problem-zhtbtt?file=/src/index.js
- See the lower cube is not merged well.
Code
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00, side: THREE.DoubleSide });
{
let geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.deleteAttribute("normal");
geometry.deleteAttribute("uv");
geometry.attributes.position.array[0] += 0.000001;
geometry = BufferGeometryUtils.mergeVertices(geometry);
geometry.computeVertexNormals();
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 2;
scene.add(cube);
}
{
let geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.deleteAttribute("normal");
geometry.deleteAttribute("uv");
geometry = BufferGeometryUtils.mergeVertices(geometry);
geometry.computeVertexNormals();
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 0;
scene.add(cube);
}
{
let geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.deleteAttribute("normal");
geometry.deleteAttribute("uv");
geometry.attributes.position.array[0] -= 0.000001;
geometry = BufferGeometryUtils.mergeVertices(geometry);
geometry.computeVertexNormals();
const cube = new THREE.Mesh(geometry, material);
cube.position.y = -2;
scene.add(cube);
}
Expected behavior
There are almost three identical cubes, applying mergeVertices()
on their positions (after removing normals and uvs).
The difference between them is the x-coordinate of the first vertex: the top is 0.500001, the middle is 0.5 and the bottom has 0.499999.
Since the difference is well below the 1e-4 default threshold of mergeVertices() we would expect the vertices to be merged the same way.
But the top and middle vertex is merged to its neighbors (since they are truncated to 0.5) and the bottom is not merged (since it is truncated to 0.4999)
Screenshots
Platform:
- Device: Desktop
- OS: MacOS
- Browser: Chrome
- Three.js version: r144
(the problem exists on all platforms)
Issue Analytics
- State:
- Created a year ago
- Comments:9 (2 by maintainers)
Top GitHub Comments
It sounds like we should be explicitly rounding to N digits, rather than truncating? That sounds like a bug to me as well, and a PR to fix it would be welcome I think. Thank you!
FWIW I recently implemented a version of this that explicitly compares against the tolerance value rather than rounding or truncating. It isn’t as slow as N^2 if you sort the vertices first and are selective about which comparisons you make using sort order, but it’s still quite a bit slower than what three.js currently does.
This will have the same problem - both truncating and rounding bin the space and merge vertices in common bins. There will always be cases where two numbers are very close to each other but otherwise fall on opposite sides of the binning operation (rounding or truncation in this case).
Performance is the reason I chose to use the truncation method. If there are other methods that are comparably performant I can support a change (or possibly an option if the performance is noticeably worse). I’ve been wondering if there’s a way to rely on two binning methods (ie rounding and truncation) to enable a fast search for neighbor vertices without falling into this issue.