35

I need to pack triangles into a box as tightly as reasonable, as part of a 3D optimization (I'm stuffing alpha-using segments of different textures into a single different texture, for use with depth-sorting, so textures don't switch with every new tri)

Is there an algorithm for doing this? The triangles themselves can be made to be plyable (transformable to be right angles, effectively making this a box-stuffing algorithm instead), but I would like to avoid this if possible, as it would distort the underlying texture art.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Anne Quinn
  • 12,609
  • 8
  • 54
  • 101
  • 1
    Intriguing problem. Are reflections and arbitrary rotations of the triangles allowed? – j_random_hacker Jul 14 '14 at 11:57
  • @j_random_hacker - reflections along the x and y axis would be okay, but any rotations besides 90 degrees would blur the underlying texture unfortunately – Anne Quinn Jul 14 '14 at 12:04
  • I see. How likely is it that a typical input can be decomposed into pairs of triangles in which each pair, when its triangles are placed next to each other somehow, fits fairly tightly into a rectangle? Triangles can be optimally paired up with a maximum weighted matching algorithm in which the weight between 2 triangles A and B is the maximum usage (fraction of occupied area) in the bounding box, across all ways of placing A and B alongside each other (there are at most 16; I can elaborate). The idea being to simplify the problem to "box-stuffing" without much loss of efficiency. – j_random_hacker Jul 14 '14 at 20:17
  • Also how many triangles, roughly? – j_random_hacker Jul 14 '14 at 20:17
  • 1
    Hmm, there are 32 (not 16) ways of packing 2 triangles A and B together that avoid extending 1 of the 4 edges of A's bounding box: 4 90-degree rotations of B w.r.t. A, and the following 8 "layouts" for each of these: A to the {left, right} of B with their {top, bottom}most points aligned; A {above, below} B with their {left, right}most points aligned. But I don't think it's sufficient to consider only these unfortunately -- it might be possible to get a smaller overall bounding box by placing B in some "intermediate" position... :( – j_random_hacker Jul 14 '14 at 20:45
  • j_random_hacker - aw, I wish I could, but splitting up triangles would require adding vertices to the mesh, which would be fairly costly – Anne Quinn Jul 15 '14 at 00:51
  • Sorry, I wasn't clear -- I was talking about pairing up triangles, not splitting them in two. (There will be 1 left over if there are an odd number, but that's no big deal.) – j_random_hacker Jul 15 '14 at 05:29
  • @j_random_hacker - Oh! Pairing them up is okay! Though... I actually found another way around this tonight. I'll keep the question open if you wanted to answer, though otherwise I'll probably close it (since it has close votes for some reason) – Anne Quinn Jul 15 '14 at 07:04
  • 1
    I'm afraid I don't have a "full" answer that I'm planning to give, but OTOH please ignore the close votes -- they accrue on almost every question these days, perhaps because they can only be voted "up". This is IMHO a very interesting question and would be best left open :) – j_random_hacker Jul 15 '14 at 09:58
  • To clarify, you have a set of arbitrarily-shaped triangles and you want to pack them into the smallest possible space without any of them overlapping? – Richard Oct 08 '15 at 20:32
  • Smells like NP-Completeness. – Gabriel Nov 09 '15 at 15:47
  • 10
    I am pretty sure this problem is NP-Complete. Doing a quick search I found [a 5 page paper](http://www.researchgate.net/publication/228619388_Heuristic_Algorithm_for_Packing_Triangles_into_a_Square_Container) that describes a heuristic algorithm for packing triangles into a square, but I don't think you will be able to find a perfect polynomial time solution. – daxvena Nov 21 '15 at 03:03
  • 1
    Just for clarification, is `box` in this context 3-dimensional (cuboid) or 2-dimensional (square/rectangle)? I'm used to seeing heuristics for this in 2D to pack triangles or quads into a texture coordinate UV space, but not one that operates in 3 dimensions. –  Nov 24 '15 at 16:54
  • Not sure about this, but do you imply that you have a list of triangles predefined and you want to staff the all inside a box? It's not clear from the question what you are asking. Also the problem setting is a bit open ended when you say "as tightly as reasonable" . Would be nice to give a hint there. – g24l Nov 30 '15 at 13:59
  • And what do you need to optimize? the number of triangles? – Alexander Leon VI Nov 30 '15 at 21:07

1 Answers1

1

"tight as reasonable" -> Something working is better than nothing.

These code fragments provide a simple solution to stuff shapes (also triangles) band by band into a rectangle.

public abstract class Shape {
    protected Point offset = new Point();
    public abstract int getHeight();
    public abstract int getWidth();
}

public class Triangle extends Shape {
    // all points are relative to offset (from Shape)
    Point top = new Point(); // top.y is always 0, left.y >= 0 right.y >= 0 
    Point left = new Point(); // left.x < right.x
    Point right = new Point();

    public int getHeight() {
        return left.y >= right.y ? left.y : right.y;
    }

    public int getWidth() {
        int xmin = left.x <= top.x ? left.x : top.x;
        int xmax = right.x >= top.x ? right.x : top.x;
        return xmax - xmin;
    }
}

public class StuffRectangle extends Shape {
    private Point ww = new Point();

    private ArrayList<Shape> maintained = new ArrayList<Shape>();
    private int insx;
    private int insy;
    private int maxy;

    public int getHeight() {
        return ww.y;
    }

    public int getWidth() {
        return ww.x;
    }

    public void clear() {
        insx = 0;
        insy = 0;
        maxy = 0;
        maintained.clear();
    }

    /**
     * Fill the rectangle band by band.
     * 
     * The inserted shapes are removed from the provided shape collection.
     * 
     * @param shapes
     *            the shapes to insert
     * @return the count of inserted shapes.
     */
    public int stuff(Collection<Shape> shapes) {
        int inserted = 0;

        for (;;) {
            int insertedInPass = 0;
            for (Iterator<Shape> i = shapes.iterator(); i.hasNext();) {
                Shape shape = i.next();

                // does the shape fit into current band?
                int sx = shape.getWidth();
                if (insx + sx > getWidth())
                    continue;
                int sy = shape.getHeight();
                if (insy + sy > getHeight())
                    continue;

                // does fit
                ++insertedInPass;

                // remove from shapes
                i.remove();

                // add to maintained and adjust offset
                maintained.add(shape);
                shape.offset.x = insx;
                shape.offset.y = insy;

                insx += sx;
                if (sy > maxy)
                    maxy = sy;

            }
            inserted += insertedInPass;
            if (shapes.isEmpty())
                break;
            // nothing fits current band - try a new band
            if (insertedInPass == 0) {
                // already a new band - does not fit at all
                if (insx == 0)
                    break;

                // start new band
                insx = 0;
                insy += maxy;
                maxy = 0;
                continue;
            }
        }

        return inserted;
    }
}
bebbo
  • 2,830
  • 1
  • 32
  • 37