ConvexHull generation from certain input points causing infinite loop
Glidias opened this issue · comments
This is a showstopper for me. I'm not sure where the loop goes endless from the YUKA.ConvexHull codebase yet. Example point input below for reference.
It'd be good to have a test unit/case for this.
To trigger endless loop, use the code below:.
var jsonArr = [{"x":-55.53499860610813,"y":73.11029657847513,"z":-63.59099818190188},{"x":-55.7849986005202,"y":73.11029657847513,"z":-69.34100186807655},{"x":-58.53499853905291,"y":73.11029657847513,"z":-68.84100187925242},{"x":-58.03499855022878,"y":73.11029657847513,"z":-63.59099818190188},{"x":-55.53499860610813,"y":73.01029657847513,"z":-63.59099818190188},{"x":-55.7849986005202,"y":73.01029657847513,"z":-69.34100186807655},{"x":-58.53499853905291,"y":73.01029657847513,"z":-68.84100187925242},{"x":-58.03499855022878,"y":73.01029657847513,"z":-63.59099818190188},{"x":-9.78499962870032,"y":74.11029655612339,"z":-64.84100196865938},{"x":-10.034999623112384,"y":74.11029655612339,"z":-64.09100198542319},{"x":-9.034999645464126,"y":73.92279656031434,"z":-63.59099818190188},{"x":-9.78499962870032,"y":74.0102965561234,"z":-64.84100196865938},{"x":-10.034999623112384,"y":74.0102965561234,"z":-64.09100198542319},{"x":-9.034999645464126,"y":73.82279656031434,"z":-63.59099818190188}];
new YUKA.ConvexHull().fromPoints(jsonArr.map((p)=>{return new YUKA.Vector3(p.x, p.y, p.z)}));
Screenshot of the above 14 input points below (shown as small green cubes) floating above the navmesh regions highlighted in red. Looks "innocent" enough. So this seems like a serious bug.
expected hull output:
I can reproduce. It looks like this happens in ConvexHull._mergeFaces()
. Investigating...
I've provided a fix in a separate branch: a2cbee5
Can you please test this build in your project? It was quite complicated to understand what's going wrong and I'm still considering a different type of solution. However, the commit should be okay as a first fix.
It'd be good to have a test unit/case for this.
Will do this if you confirm that the commit works.
For the above case, it appears to work fine. As for the rest, (I'm doing pairwise region-to-region tests across a section of the navmesh of 298 regions, total 41616 times n(n-1)/2
),. including intersection tests with potential occluding polygon-extruded convex hulls vs region-to-region convex hull. The first set of region to region tests appears to work fine now, but not sure about the polygon-extruded cases (but i decided not to do that anymore and decided for a more optimized convex hull vs polygon intersection test.
THe funny thing is if you run the same set of faces (non-merged) through NavMesh, there is no infinite loop. The loop happens when it appears that after some merging, there is no more loop back to starting polygon.edge through the linked list thus the polygon.convex()
check method hangs. I'm not sure about the geometric premise behind the dot product check >=0 used to determine CCW vs CW preference as well which differs form the other navmesh implementation.
On a sidenote, merging faces could be an optional thing, right? I wonder is skipping the need to merge faces and testing the intersections and other methods with non-merged faces is viable (i think it is?), and whether, on the whole, skipping the merging faces is better for performance as well. If it's not going to be frequent multiple tests with the same convex hull, but only a one-off case, i wonder if just sticking to non-merged faces would be more performant overall even as the sample size is marginally larger.
On a sidenote, merging faces could be an optional thing, right?
Yes, but I do think that a convex hull with less faces is in general preferable. In our previous scenarios, we usually generate the convex hull once and then use it for intersection tests. The code is actually not optimized if you generate hulls over and over again.
The merge code in ConvexHull
and NavMesh
is more or less equal and will fail if it encounters the above test case. Unfortunately, the merge can produce a polygon which edges are not correctly linked. So it's not possible to use edge.next
to move along the contour. But only if both original convex polygons are already connected. I'm still trying to understand what that means and if it is possibly a degenerated case produced elsewhere in the code.
I generally use my own personal branch build atm (that includes cherry picked changes), with Convexhull containing the option to skip merging faces and such, including your mergePossible method. Same thing likewise for Navmeshing, which also allows me an option to skip generating graph as well. Yup, part of the reason why i was considering of not merging faces was in the case where I'm not storing it since it's just a 1 off precomputation volumetric visiblity cast, . Actually, exploiting the fact that there are 2 known convex solids already and i just need to join them, it could use a faster algorithm though but overall the bottleneck actually lies more in the fact that my polygon soup is currently too big and needs some actual CSG rundowned objects and merged faces, which would then be suitable to store actual properly saved Convex hulls for obstacles/occluders within BVH which aren't too numerous.
Okay. In any event, having a flag that controls the merge might be useful in the future (if only for debugging^^).
Regarding your mergeFaces check, woudln't it skip the remaining lines in _mergeFaces()
which are important?
// recompute centroid of faces
for ( let i = 0, l = faces.length; i < l; i ++ ) {
faces[ i ].computeCentroid();
}
// compute centroid of convex hull and the final edge and vertex list
this.computeCentroid();
this.computeUniqueEdges();
this.computeUniqueVertices();
If you already know beforehand that a set of polygons, forms a convex hull, is it possible to form a ConvexHull from existing faces skipping the fromPoints
generation part?
Regarding your mergeFaces check, woudln't it skip the remaining lines in _mergeFaces() which are important?
Good point. Refactored the code a little bit so these lines are not skipped anymore.
If you already know beforehand that a set of polygons, forms a convex hull, is it possible to form a ConvexHull from existing faces skipping the fromPoints generation part?
What is the use case of doing this? I guess the method would look like so:
fromFaces( faces ) {
this.faces = faces;
this.computeCentroid();
this.computeUniqueEdges();
this.computeUniqueVertices();
return this;
}
The original issue should be resolved via 7fd0d29.