Skip to content
Snippets Groups Projects
InnerFirstVectorOptimizer.java 5.75 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * This file is part of LibLaserCut.
     * Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de>
     * RWTH Aachen University - 52062 Aachen, Germany
     *
     * LibLaserCut is free software: you can redistribute it and/or modify
     * it under the terms of the GNU Lesser General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * LibLaserCut is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     * GNU Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public License
     * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>.
     *
     */
    package com.t_oster.liblasercut.vectoroptimizers;
    
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     *
     * @author Thomas Oster <thomas.oster@rwth-aachen.de>
     */
    public class InnerFirstVectorOptimizer extends VectorOptimizer
    {
    
      // helper classes:
      private abstract class ElementValueComparator implements Comparator<Element>
      {
    
        /**
         * get one integer from the element
         * order ascending by this integer
         * inside objects should have the lowest values
         */
        abstract int getValue(Element e);
    
        /**
         * compare by getValue()
         */
        @Override
        public int compare(Element a, Element b)
        {
          Integer av = new Integer(getValue(a));
          Integer bv = new Integer(getValue(b));
          return av.compareTo(bv);
        }
      }
    
      private class XMinComparator extends ElementValueComparator
      {
        // compare by XMin a>b
        @Override
        int getValue(Element e)
        {
          return -e.boundingBox().getXMin();
        }
      }
    
      private class YMinComparator extends ElementValueComparator
      {
        // compare by YMin a>b
        @Override
        int getValue(Element e)
        {
          return -e.boundingBox().getYMin();
        }
      }
    
      private class XMaxComparator extends ElementValueComparator
      {
        // compare by XMax a<b
        @Override
        int getValue(Element e)
        {
          return e.boundingBox().getXMax();
        }
      }
    
      private class YMaxComparator extends ElementValueComparator
      {
        // compare by YMax a<b
        @Override
        int getValue(Element e)
        {
          return e.boundingBox().getYMax();
        }
      }
    
      @Override
      protected List<Element> sort(List<Element> e)
      {
        List<Element> result = new LinkedList<Element>();
        if (e.isEmpty())
        {
          return result;
        }
        /**
         * cut inside parts first, outside parts later
         * this algorithm is very robust, it works even for unconnected paths that
         * are split into individual lines (e.g. from some DXF imports)
         * it is not completely perfect, as it only considers the bounding-box and
         * not the individual path
         *
         * see below for documentation of the inner workings
         */
        result.addAll(e);
        /**
         * HEURISTIC:
         * this algorithm is based on the following observation:
         * let I and O be rectangles, I inside O
         * for explanations, assume that:
         * - the X-axis goes from left to right
         * - the Y-axis goes from bottom to top
         *
         * ---------------- O: outside rectangle
         * | |
         * | ---- |
         * y axis | |in| I |
         * ^ | ---- |
         * | | |
         * | ----------------
         * |
         * ------> x axis
         *
         * look at each border:
         * right border: I.getXMax() < O.getXMax()
         * left border: I.getXMin() > O.getXMin()
         * top border: I.getYMax() < O.getYMax()
         * bottom border: I.getYMin() > O.getYMin()
         *
         * If we now SORT BY ymax ASCENDING, ymin DESCENDING, xmax ASCENDING, xmin
         * DESCENDING
         * (higher sorting priority listed first)
         * we get the rectangles sorted inside-out:
         * 1. I
         * 2. O
         *
         * Because we sort by four values, this still works if
         * the two rectangles start at the same corner and have the same width,
         * but only differ in height.
         *
         * If each rectangle is split into four separate lines
         * (e.g. because of a bad DXF import),
         * this still mostly works:
         * 1. O: bottom line
         * 2. I: bottom
         * 3. I: top, left, right (both have same YMax, but top has a higher YMin)
         * 4: O: top, left, right (both have same YMax, but top has a higher YMin)
         *
         * TRADEOFFS AND LIMITATIONS:
         * This algorithm does not work for paths that have the same bounding-box
         * (e.g. a circle inscribed to a square)
         *
         * For concave polygons with the same bounding-box,
         * many simple Polygon-inside-Polygon algorithms also fail
         * (or have a useless definition of "inside" that matches the misbehaviour):
         * Draw a concave polygon, remove one point at a concave edge.
         * The resulting polygon is clearly outside the original, although every
         * edge of it is inside the original!
         *
         * FUTURE WORK:
         * It would also be nice to sort intersecting polygons, where one polygon
         * is "90% inside" and "10% outside" the other.
         * Real-world example:_A circular hole at the border of a rectangle.
         * Due to rounding errors, it may appear slightly outside the rectangle.
         * Mathematically, it is neither fully inside nor fully outside, but the
         * user clearly wants it to be counted as "inside".
         *
         * POSSIBLE LIBRARIES:
         * http://sourceforge.net/projects/geom-java/
         * http://sourceforge.net/projects/jts-topo-suite
         *
         * USEFUL METHODS:
         * Element.isClosedPath()
         */
        // do the work:
        Collections.sort(result, new XMinComparator());
        Collections.sort(result, new YMinComparator());
        Collections.sort(result, new XMaxComparator());
        Collections.sort(result, new YMaxComparator());
        return result;
      }
    }