4

Does anyone know of Multiple Bounding Boxes containment detection algorithm (or a implementation references) with the following description:

  1. Lets have collection of Axis Aligned Bounding Boxes, some of them may intersect
  2. and a simple 3D shape, for example a sphere (or it can be another AABB).
  3. I need algorithm that can detect if the shape is fully contained in the AABB-s. In other words no parts of the sphere are outside AABB-s.

The problem is: It is easy to test for a containment in a single AABB, however there is a case where the shape may be split between multiple AABB-s and even a case where it can intersect multiple AABB-s but some parts of sphere are outside.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Ross
  • 2,079
  • 2
  • 22
  • 26
  • You could clip the query shape at the outside of each AABB and see if what remains is the empty set. Simpler if the query shape is an AABB, a bit trickier for spheres. – Nico Schertler Jun 02 '18 at 11:09

3 Answers3

3

IMO, you can do that by a sweep-plane approach.

Sort all AABB by their top and bottom "applicates" (z coordinate). Now consider an horizontal plane that moves from face to face downward, every time updating an active list (i.e. those boxes that meet the plane). A box enters the list on its top face and leaves it on its bottom face.

The section of the scene by a plane will consist of a set of rectangles and possibly a circle. On every step, the circle needs to be wholly contained in the union of the rectangles.

Notice that you also need to stop at the plane by the equator (which will not modify the active list), as the sphere is the "largest" there.

Doing so, you rd the initial problem to a set of 2D containment subproblems with rectangles and circles.

Following the same principle, you can address the latter by a sweepline technique, sorting the rectangles by ordinates of their top/bottom sides and moving an active list. On every step, the section by an iso-y line defines a set of segments, one per rectangle and possibly one for the circle, and inclusion is easily proven by sorting the bounds on x.

enter image description here

Said differently, by the 3D sweep process, you decompose space in prismatic slabs and build the intersections with the sphere. And by the 2D sweep process, you decompose the plane in rectangular slabs and build the intersections with the section disks.

  • +1 for the elegant idea of applying a recursive sweep plane algorithm. But since your current version does not work for several reasons (consideration of sphere/box intersections missing, cutting at the borders instead of inside the boxes) I have added a new solution addressing these issues. – coproc Jun 05 '18 at 01:23
  • Very promising approach. I will try it. Also, It is not necessary to apply the sweep line for all rectangles. A fast test for single sphere-rectangle containment will provide the list of rectangles that have to be used for the sweep step. – Ross Jun 05 '18 at 04:15
  • I tried to come up with a (counter)example, where looking at the sphere/box intersections is necessary - but was not successful yet. It looks like that instead of looking at the sphere/box intersections it really suffices to cut through at the sphere center - which gives a huge performance gain. It would be nice to have a sound argument though ... – coproc Jun 05 '18 at 08:18
  • @coproc: there is no escape, every time the section changes you need to perform a circle/rectangles containment test. There are no 3D sphere/box tests required at all. –  Jun 05 '18 at 08:24
  • I implemented the sweep-plane approach. Since I have static bounding boxes and many 3D shapes, I have preparation step where the sections are computed once and then the query step for each shape. @coproc 12 boxes cover takes 6ms on unoptimized first version. – Ross Jun 22 '18 at 15:40
  • @Ross yes, sweep plane is a great idea for this problem. Also looking at equator instead of box-circle intersections. But I do not see how the algorithm as described here would work for the box -0.5 < z < 2 (-2 < x,y < 2) and the unit sphere. At the equator (z=0) and the height of intersection (z=-.5) the cut out circle is covered, but the unit sphere is not covered by this box. – coproc Jun 22 '18 at 19:11
  • @coproc at -0.5 you have another section. In z direction you have intervals where the sphere exists but box does not. Algo is: You have list of (coordinate, true/false) pairs in x dir. For each pair of x you have list of pairs in y dir, and for each pair in y a list of pairs in z. Then go recursively and check if there are intervals between pairs where the sphere exist but previous pair is false (no boxes parts in this interval). and for each point in y list of intervals in z. – Ross Jun 23 '18 at 04:33
  • (coordinate, true/false) pairs are calculated by scanning in x,y,z direction and calculating how many boxes sections are after given coordinate. true means some number of box sections, false means 0 sections. – Ross Jun 23 '18 at 04:41
  • @Ross Ok, then let me add another box to my example: -2 < z < 2 (-0.2 < x,y < 0.2). Now there is also a box for z<-0.5. But the sphere is still not covered completely. How are you going to detect that at z=-0.5? My point is: Yes, you need the z-values of the box limits. But I do not see how intersecting at these heights (interval end points) really works out. When intersecting *between* these heights (at sample points inside the intervals) everything is straight forward (see my solution - and ignore the intersections between boxes and sphere/circle). – coproc Jun 23 '18 at 11:46
  • @coproc In z dir. interval is-0.5 0.5 and have to check from the point just before -0.5 (that is -2) what is the set status, to the point 0.5. If there where at least one point where the status is "false" (set of events is empty) then the sphere is outside in this interval. Implementing this actualy helps to understand it. I have 1D, 2D and 3D vectors of pairs. The algo finds the status of coordinate < sphere.min. If it is true, the algo checks it is true in all points until sphere.max. Otherwise it is outside. – Ross Jun 23 '18 at 16:35
1

Algebraically this problem can be expressed as a constrained satisfaction problem over the reals. The condition for a point (x,y,z) being inside a circle with center coordinates (cx,cy,cz) and radius r is:

C :=  (x-cx)^2 + (y-cy)^2 + (z-cz)^2 - r^2 <= 0

The condition for a point being inside an AABB is:

B :=  x0 <= x /\ x <= x1 /\ y0 <= x /\ y <= y1 /\ z0 <= z /\ z <= z1

where /\ means 'and' and x0, x1, ..., z1 are real numbers.

Now given a circle and several bounding boxes the question is whether the list of constraints

C /\ !(B1 \/ ... \/ Bn)

can be satisfied. If yes, there is a point inside the sphere but not inside any AABB. Since there are only three variables x,y,z and polynomials of degree of at most 2 existing algorithms/libraries can efficiently solve this problem. (e.g. Z3, see this intro).

coproc
  • 6,027
  • 2
  • 20
  • 31
  • Thank you for the answer, very interesting approach. I doubt it can be quite slow since the CSP solvers are very general solvers. – Ross Jun 03 '18 at 05:02
  • There are special solvers for special cases. For this problem the "virtual term substitution" (VST) can be applied, which should be much faster than a general solver. Z3 has implemented this algorithm for quadratic polynomials and seems to detect automatically whether it can be applied. Your problem is still more specialized than the general setting allowed by VST, but on the other hand a coverage of a sphere by AABBs can still be quite sophisticated, so some algebra will be needed. – coproc Jun 03 '18 at 11:11
  • @Ross checking whether 12 boxes cover the unit sphere takes 30 - 50ms on my computer (using the Python interface of Z3) – coproc Jun 03 '18 at 19:29
  • 50ms for containment test seems a lot compared to the intersection test. 12 boxes-1sphere intersection on my old laptop is in order of 0.2ms. I guess part of it is due to Python interface and solver initialization. – Ross Jun 04 '18 at 04:57
0

Inspired by Yves idea of a recursive sweep-plane algorithm here is a more elaborate version for trying to find a point inside a sphere that is not covered by any of the given boxes.

First we have to find all values of the z-coordinate where full coverage in the corresponding plane can change when moving along the z-axis. This can happen at

  • the maximal and minimal z-coordinate of the sphere (i.e. z_center +/- radius)
  • the maximal and minimal z-coordinates of all boxes
  • the maximal and minimal z-coordinates of all intersection circles/arcs of the sphere with all box faces

Once these z-values are collected, sorted and limited to the z-range of the sphere we have a partition of the z-range of the sphere into intervals. We choose a value inside each z-interval (e.g. the center) to check the coverage in the corresponding plane. Each 2D cutout can be solved analogously to the 3D problem - thus reducing the coverage check to a set of 1D-problems. In the 1D-case we have an interval instead of a sphere or circle and we also have intervals instead of boxes or rectangles. Thus the coverage problem of a sphere against boxes is reduced to a set of trivial coverage problems of one interval against a set of intervals.

An implementation of the main function could look like this:

# if the n-dimensional sphere is not fully covered by the boxes
# find a point inside the sphere but outside the boxes
# by a recursive sweep-plane algorithm.
# center: n-dimensional point
# radius: real value
# boxes: list of n-dimensional boxes (each box is a list of n intervals)
def getUncoveredSphereWitness(center, radius, boxes):
    sphereLimitsN = [center[-1]-radius, center[-1]+radius]
    if len(center) == 1: 
        # 1D case
        witness = getUncoveredIntervalWitness(sphereLimitsN, [box[0] for box in boxes])
        return [witness] if witness is not None else None

    boxLimitsN = sum([b[-1] for b in boxes], [])
    cutLimitsN = getCutLimitsN_boxes(center, radius, boxes)
    limitsN = list(set(sphereLimitsN + boxLimitsN + cutLimitsN))
    limitsN.sort()

    # get centers of relevant intervals
    coordNValsToCheck = []
    for b in limitsN:
        if b > sphereLimitsN[1]: break
        if b > sphereLimitsN[0]:
            coordNValsToCheck.append((bPrev+b)/2.)
        bPrev = b

    for z in coordNValsToCheck:
        # reduce to a problem of with 1 dimension less
        centerN1, radiusN1 = cutSphereN(center, radius, z)
        boxesN1 = cutBoxesN(boxes, z)
        witness = getUncoveredSphereWitness(centerN1, radiusN1, boxesN1)
        if witness is not None:
            return witness+[z] # lift witness to full dimension by appending coordinate

    return None
coproc
  • 6,027
  • 2
  • 20
  • 31
  • Thank you both Yves Daoust and coproc for providing this solution. This looks very promising and I will try it. – Ross Jun 05 '18 at 04:21