protected boolean fit2D(List<Box> containerProducts, Container holder, Box usedSpace, Space freeSpace, BooleanSupplier interrupt) {

		if(rotate3D) {
			// minimize footprint
			usedSpace.fitRotate3DSmallestFootprint(freeSpace);
		}

		// add used space box now, but possibly rotate later - this depends on the actual remaining free space selected further down
		// there is up to possible 4 free spaces, 2 in which the used space box is rotated
		holder.add(new Placement(freeSpace, usedSpace));

		if(containerProducts.isEmpty()) {
			// no additional boxes
			// just make sure the used space fits in the free space
			usedSpace.fitRotate2D(freeSpace);

			return true;
		}

		if(interrupt.getAsBoolean()) {
			return false;
		}

		Space[] spaces = getFreespaces(freeSpace, usedSpace);

		Placement nextPlacement = getBestBoxAndSpace(containerProducts, spaces, holder.getFreeWeight());
		if(nextPlacement == null) {
			// no additional boxes along the level floor (x,y)
			// just make sure the used space fits in the free space
			usedSpace.fitRotate2D(freeSpace);
		} else {
			// check whether the selected free space requires the used space box to be rotated
			if(nextPlacement.getSpace() == spaces[2] || nextPlacement.getSpace() == spaces[3]) {
				// the desired space implies that we rotate the used space box
				usedSpace.rotate2D();
			}
	
			// holder.validateCurrentLevel(); // uncomment for debugging
	
			removeIdentical(containerProducts, nextPlacement.getBox());
	
			// attempt to fit in the remaining (usually smaller) space first
	
			// stack in the 'sibling' space - the space left over between the used box and the selected free space
			Space remainder = nextPlacement.getSpace().getRemainder();
			if(remainder.nonEmpty()) {
				Box box = getBestBoxForSpace(containerProducts, remainder, holder.getFreeWeight());
				if(box != null) {
					removeIdentical(containerProducts, box);
	
					if(!fit2D(containerProducts, holder, box, remainder, interrupt)) {
						return false;
					}
				}
			}
			// double check that there is still free weight
			// TODO possibly rewind if the value of the boxes in the remainder is lower than the one in next placement
			if(holder.getFreeWeight() >= nextPlacement.getBox().getWeight()) {
				if(!fit2D(containerProducts, holder, nextPlacement.getBox(), nextPlacement.getSpace(), interrupt)) {
					return false;
				}
			}
		} 
		
		// also use the space above the placed box, if any.
		if(freeSpace.getHeight() > usedSpace.getHeight()) {
			// so there is some free room; between the used space and the level height

			// the level by level approach is somewhat crude, but at least some of the inefficiency
			// can be avoided this way
			Space above;
			if(nextPlacement == null) {
				// full width / depth
				above = new Space(
						freeSpace.getWidth(), 
						freeSpace.getDepth(), 
						freeSpace.getHeight() - usedSpace.getHeight(),
						freeSpace.getX(),
						freeSpace.getY(),
						freeSpace.getZ() + usedSpace.getHeight()
						);
			} else {
				// just directly above the used space
				
				// TODO possible include the sibling space if no box was fitted there
				above = new Space(
						usedSpace.getWidth(), 
						usedSpace.getDepth(), 
						freeSpace.getHeight() - usedSpace.getHeight(),
						freeSpace.getX(),
						freeSpace.getY(),
						freeSpace.getZ() + usedSpace.getHeight()
						);
			}
			int currentIndex = getBestBox(holder, above, containerProducts);

			if(currentIndex != -1) {
				// should be within weight already
				Box currentBox = containerProducts.get(currentIndex); 
				
				containerProducts.remove(currentIndex);
				
				if(!fit2D(containerProducts, holder, currentBox, above, interrupt)) {
					return false;
				}
			}
		}
		
		return true;
	}
protected boolean fit2D ( List < Box > containerProducts , Container holder , Box usedSpace , Space freeSpace , BooleanSupplier interrupt ) { if ( rotate3D ) { usedSpace . fitRotate3DSmallestFootprint ( freeSpace ) ; } holder . add ( new Placement ( freeSpace , usedSpace ) ) ; if ( containerProducts . isEmpty ( ) ) { usedSpace . fitRotate2D ( freeSpace ) ; return true ; } if ( interrupt . getAsBoolean ( ) ) { return false ; } Space [ ] spaces = getFreespaces ( freeSpace , usedSpace ) ; Placement nextPlacement = getBestBoxAndSpace ( containerProducts , spaces , holder . getFreeWeight ( ) ) ; if ( nextPlacement == null ) { usedSpace . fitRotate2D ( freeSpace ) ; } else { if ( nextPlacement . getSpace ( ) == spaces [ 2 ] || nextPlacement . getSpace ( ) == spaces [ 3 ] ) { usedSpace . rotate2D ( ) ; } removeIdentical ( containerProducts , nextPlacement . getBox ( ) ) ; Space remainder = nextPlacement . getSpace ( ) . getRemainder ( ) ; if ( remainder . nonEmpty ( ) ) { Box box = getBestBoxForSpace ( containerProducts , remainder , holder . getFreeWeight ( ) ) ; if ( box != null ) { removeIdentical ( containerProducts , box ) ; if ( ! fit2D ( containerProducts , holder , box , remainder , interrupt ) ) { return false ; } } } if ( holder . getFreeWeight ( ) >= nextPlacement . getBox ( ) . getWeight ( ) ) { if ( ! fit2D ( containerProducts , holder , nextPlacement . getBox ( ) , nextPlacement . getSpace ( ) , interrupt ) ) { return false ; } } } if ( freeSpace . getHeight ( ) > usedSpace . getHeight ( ) ) { Space above ; if ( nextPlacement == null ) { above = new Space ( freeSpace . getWidth ( ) , freeSpace . getDepth ( ) , freeSpace . getHeight ( ) - usedSpace . getHeight ( ) , freeSpace . getX ( ) , freeSpace . getY ( ) , freeSpace . getZ ( ) + usedSpace . getHeight ( ) ) ; } else { above = new Space ( usedSpace . getWidth ( ) , usedSpace . getDepth ( ) , freeSpace . getHeight ( ) - usedSpace . getHeight ( ) , freeSpace . getX ( ) , freeSpace . getY ( ) , freeSpace . getZ ( ) + usedSpace . getHeight ( ) ) ; } int currentIndex = getBestBox ( holder , above , containerProducts ) ; if ( currentIndex != - 1 ) { Box currentBox = containerProducts . get ( currentIndex ) ; containerProducts . remove ( currentIndex ) ; if ( ! fit2D ( containerProducts , holder , currentBox , above , interrupt ) ) { return false ; } } } return true ; }
Fit in two dimensions

@param containerProducts products to fit
@param holder target container
@param usedSpace space to subtract
@param freeSpace available space
@param interrupt interrupt
@return false if interrupted