Solving Tangrams Using JTS

The project, 2dfit, solves Tangram puzzles using the Java Topology Suite (JTS). The algorithm implementation is based on what I outlined in item 3 of the post “http://bloggingmath.wordpress.com/2007/05/28/tangram-puzzle/“.

The implementation difficulties are from using floating point arithmetic, which is not robust for geometric operations. The JTS library attempts to minimize this by a coordinate snapping technique. But for the operations used in solving Tangrams the provided snapping was not sufficient.

There’s an option in JTS to specify the snapping tolerance (it has a fairly small default). I added small wrapper functions for the two operations of Boolean intersection and Boolean difference. The wrapper functions apply successively larger snapping tolerances up to a factor of epsilon, where epsilon = 1e-5. The below code shows the wrapper functions (in the code, g1 is the Tangram and g2 is a puzzle piece).

    public static Geometry SemiRobustGeoOp(Geometry g1, Geometry g2, int op) throws Exception {
        double e1 = EPSILON/10;
        double snapTolerance = GeometrySnapper.computeOverlaySnapTolerance(g1, g2);
        while (snapTolerance < e1) {
            try {
                Geometry[] geos = GeometrySnapper.snap(g1, g2, snapTolerance);
                switch (op) {                    
                case DIF_OP:   // difference
                    return geos[0].difference(geos[1]);
                case UNION_OP: // union
                    return geos[0].union(geos[1]);
                default:
                    throw new Exception("unhandled semirobustgeoop: " + op);
                }
            } catch (TopologyException e){
                snapTolerance *= 2;
            }
        }
        return null;
    }

    public static boolean SemiRobustGeoPred(Geometry g1, Geometry g2, int pred) throws Exception {
        double e1 = EPSILON/10;
        double snapTolerance = GeometrySnapper.computeOverlaySnapTolerance(g1, g2);
        while (snapTolerance < e1) {
            try {
                Geometry[] geos = GeometrySnapper.snap(g1, g2, snapTolerance);
                switch (pred) {                    
                case COVER_PRED: // 
                    return geos[0].covers(geos[1]);
                default:
                    throw new Exception("unhandled semirobustgeopred: " + pred);
                }
            } catch (TopologyException e){
                snapTolerance *= 2;
            }
        }
        return false;
    }

Using the wrapper functions was key to a more robust implementation. The below figure shows a solved Tangram puzzle (from Test.java:FitTest_ToSingleLargeTriangle()), in the figure the puzzle pieces are labeled l1.dat, l2.dat,…, l7.dat (I was lazy in naming the files). It’s the result of running FitTest_ToSingleLargeTriange() in Test.java and plotting the result using gnuplot.

I used the symmetry of each puzzle piece and a heuristic for choosing which puzzle piece to fit in reducing the number of permutations used for solving a Tangram.

For the puzzle piece symmetry, I used that the square is completely symmetric so only its first line segment needs to be used when fitting it. The triangle pieces are only partially symmetric so two of their three line segments need to be tried.

The below figure illustrates the symmetry of the square:

I used two heuristics for which pieces to try fitting, first try larger pieces before smaller ones and two skip a piece if another identical piece has already failed to be fitted (there are two identical small triangles and similarly two identical large triangles).

With the above two optimizations it takes ~1min to solve a Tangram. Without the optimizations the algorithm did not complete for the Tangrams I tried.

Solving Tangrams Using the Java Topology Suite (jts)

I’ve been using the Java Topology Suite (jts) to try and solve the Tangram puzzle. The basic algorithm is the same as I posted previously (it’s item 3 on the list of algorithms). The JTS is much easer to use and the source code is actually readable compared to CGAL. But on the downside JTS does not enforce rigorous geometry like CGAL does. This has some downsides mainly I had to modify the basic algorithm for shape fitting.

The main issue is that when trying to fit for example a triangle, t_1, inside another polygon say s_1. We can have roundoff errors such as in the following image.

round off error image

One slightly add-hoc way to solve this issue is to multiply t_1 by a scaling factor 1+\epsilon where epsilon is a small constant. Then when subtracting the new triangle (1+\epsilon)t_1 from s_1 we won’t have the issue of a small corner of s_1 being left over. But it does mean that we subtract slightly too much area from s_1. Which could cause an issue when we try to fit say another triangle, t_2, inside of s_2=s_1/(1+\epsilon)t_1 (e.g. t_2 won’t fit). To ensure t_2 fits what we can do is create a buffer of size #pieces \cdot \epsilon around s_2 before testing if t_2 is inside of s_2. Please note that #pieces is the number of for instance triangles we’re trying to fit inside of s_1 (so in this case #pieces = 2 since we’re trying to fit t_1 and t_2 inside of s_1). With the addition of the buffer even if s_2 is slightly too small the union of s_2 and its buffer will be large enough to cover t_2. I’ve had pretty good luck using this method with the JTS library.

You can see my code at the GitHub 2dfit site.

But a new problem is that solving the tangram puzzle takes a really long time since the algorithm goes through a large number of possible solutions before finding the correct one. Even after using the heuristic of fitting the largest shapes first the algorithm still takes a lot of time (in fact I’m still waiting for it to complete while writing this post). So I’ve been thinking of possible ways to speed up the algorithm. Below I’ve listed an algorithm that might be of some help in this.

Given two simple polygons called the silhouette and the puzzle piece. What we want is the algorithm to have two possible outcomes (1) it says “does not fit” in which case the puzzle piece cannot possible fit inside the silhouette and (2) “don’t know” in which case the puzzle piece could possible fit inside the silhouette but it’s also possible that it does not fit inside the silhouette.

In the below image I’ve shown the puzzle piece, p, which is a square and the silhouette, s, which is a simple polygon.

pieces

One way to determine that the puzzle piece, p, won’t fit is to start by constructing a bounding circle around it. As in the following figure.

bounding circle

What we do is calculate the %area of the circle filled by the puzzle piece (in this case a square). I’ve done the calculation and it’s \frac{2}{\pi}. Now suppose we wanted to test if the
square would not fit in the silhouette, s. Now also suppose that we try putting the bounding circle that we used for the square on the silhouette, s, as in the following figure.

bound circle 2

As is obvious the area of the bounding circle filled by the silhouette, s, is much less than \frac{2}{\pi}. Infact if we tried all possible placements of the bounding circle on the silhouette, s, we would find that the %area of the bounding circle filled by the silhouette is always less than \frac{2}{\pi}. This implies that the square cannot fit inside the silhouette, s. Now we need to determine what placements to use of the bounding circle to let us conclude that there is no possible placement that would give an %area of \frac{2}{\pi} filled by the silhouette s.