All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.skjolber.packing.ep.points2d.ExtremePoints2D Maven / Gradle / Ivy

There is a newer version: 3.0.9
Show newest version
package com.github.skjolber.packing.ep.points2d;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import org.eclipse.collections.api.block.comparator.primitive.IntComparator;
import org.eclipse.collections.impl.list.mutable.primitive.IntArrayList;

import com.github.skjolber.packing.api.Placement2D;
import com.github.skjolber.packing.api.ep.ExtremePoints;
import com.github.skjolber.packing.api.ep.Point2D;

/**
 * 
 * Implementation of so-called extreme points in 2D.
 *
 */

public class ExtremePoints2D

implements ExtremePoints> { public static final Comparator> COMPARATOR_X = new Comparator>() { @Override public int compare(Point2D o1, Point2D o2) { return Integer.compare(o1.getMinX(), o2.getMinX()); } }; protected int containerMaxX; protected int containerMaxY; protected final Point2DFlagList

values = new Point2DFlagList<>(); protected final List

placements = new ArrayList<>(); // reuse working variables protected final Point2DList> addXX = new Point2DList<>(); protected final Point2DList> addYY = new Point2DList<>(); protected final IntArrayList moveToYY = new IntArrayList(); protected final IntArrayList moveToXX = new IntArrayList(); protected P containerPlacement; protected long minAreaLimit = 0; protected final boolean cloneOnConstrain; private IntComparator COMPARATOR_MOVE_TO_YY = (a, b) -> { return Point2D.COMPARATOR_MOVE_YY.compare(values.get(a), values.get(b)); }; private IntComparator COMPARATOR_MOVE_TO_XX = (a, b) -> { return Point2D.COMPARATOR_MOVE_XX.compare(values.get(a), values.get(b)); }; public ExtremePoints2D(int dx, int dy) { this(dx, dy, false); } public ExtremePoints2D(int dx, int dy, boolean cloneOnConstrain) { setSize(dx, dy); this.cloneOnConstrain = cloneOnConstrain; addFirstPoint(); } private void setSize(int dx, int dy) { this.containerMaxX = dx - 1; this.containerMaxY = dy - 1; this.containerPlacement = (P) new DefaultPlacement2D(0, 0, containerMaxX, containerMaxY); } private void addFirstPoint() { values.add(new DefaultXYSupportPoint2D

(0, 0, containerMaxX, containerMaxY, containerPlacement, containerPlacement)); } public boolean add(int index, P placement) { // overall approach: // Do not iterate over placements to find point max / mins, rather // project existing points. // // project points swallowed by the placement, then delete them // project points shadowed by the placement to the other side // add points shadowed by the two new points (if they could be moved in the negative direction) // remove points which are eclipsed by others // keep track of placement borders, where possible Point2D

source = values.get(index); boolean xSupport = source.isXSupport(source.getMinX()); boolean ySupport = source.isYSupport(source.getMinY()); int xx = placement.getAbsoluteEndX() + 1; int yy = placement.getAbsoluteEndY() + 1; values.flag(index); // | // | // | |---------------| // | // | | | // | // minY | x════════════════ <---- support for a range of x (at minY) // | // | // |-------------------------- // minX maxX boolean xxSupport = xSupport && source.isXSupport(xx); // i.e. is source minY also minY at XX? // // vmaxY | // | // yy | ║ - - - | // | ║ // | ║ | // | ║ <-- support for a range of Y (at minX) // | ║ | // | ║ // minY | x - - - | // | // |----------------------------- // minX maxX // boolean yySupport = ySupport && source.isYSupport(yy); // i.e. is source minX also minX at YY? // y // | | // | | // | | // yy | |-------| | // | a | b | | // | | b | | // | a | | | // | a | b | | // a---------b-------| | // | | c | // | | c | // | | c | // | | c | // |---------c----------------|--- x // xx // // a - shadowed x // b - swallowed // c - shadowed y // // Copy maxX and maxY from existing points: // a & b used to determine maxX at yy // b & c used to determine maxY at xx // determine start and end index based on previous sort (in x direction) int pointIndex; if(yySupport) { // b and c only // already have index for point at absoluteX pointIndex = index; while(pointIndex > 0 && values.get(pointIndex - 1).getMinX() == placement.getAbsoluteX()) { pointIndex--; } } else { pointIndex = 0; } int endIndex = binarySearchPlusMinX(placement.getAbsoluteEndX()); moveToXX.ensureCapacity(endIndex); moveToYY.ensureCapacity(endIndex); addXX.ensureAdditionalCapacity(values.size()); addYY.ensureAdditionalCapacity(values.size()); for(int i = pointIndex; i < endIndex; i++) { Point2D

point = values.get(i); if(point.getMinY() > placement.getAbsoluteEndY()) { // // | // | // | * * * // | // | |------| // | | | * // | |------| // | // | * // | // --------------------------- // continue; } // Points within (xx, yy, zz) // // | // | // | // | * * |------| // | | * *| // | * |------| // | * // | * * * // | * * // --------------------------- // if(point.getMinX() >= source.getMinX() && point.getMinY() >= source.getMinY()) { // // | // | // | // | // | |------| // | | * * | // | |------| // | // | // | // --------------------------- // if(canMoveX(point, xx)) { moveToXX.add(i); } if(canMoveY(point, yy)) { moveToYY.add(i); } values.flag(i); continue; } if(xxSupport && yySupport) { // do not move points to xx, yy, or zz // // | // | ║ // | ║ // | ║------| // | * ║ | // | * ║══════════ // | // | * * * // | // --------------------------- // continue; } // Points within (xx, yy, zz), excluding the placement itself // // | // | // | // | * * |------| // | | | // | * |------| // | * // | * * * // | * * // --------------------------- // // does any point intersect the xx, yy or zz planes? if(canMoveX(point, xx)) { // yz plane moveToXX.add(i); } if(canMoveY(point, yy)) { // xz plane moveToYY.add(i); } } if(!moveToXX.isEmpty()) { moveToXX.sortThis(COMPARATOR_MOVE_TO_XX); add: for(int i = 0; i < moveToXX.size(); i++) { Point2D

p = values.get(moveToXX.get(i)); // add point on the other side // with x support for(int k = 0; k < addXX.size(); k++) { Point2D

add = addXX.get(k); if(add.eclipsesMovedX(p, xx)) { continue add; } } // note: the new point might shadow one of the previous points Point2D

moveX = p.moveX(xx, p.getMaxX(), p.getMaxY(), placement); addXX.add(moveX); } } if(!moveToYY.isEmpty()) { moveToYY.sortThis(COMPARATOR_MOVE_TO_YY); add: for(int i = 0; i < moveToYY.size(); i++) { Point2D

p = values.get(moveToYY.get(i)); // add point on the other side // with x support for(int k = 0; k < addYY.size(); k++) { Point2D

add = addYY.get(k); if(add.eclipsesMovedY(p, yy)) { continue add; } } Point2D

moveY = p.moveY(yy, p.getMaxX(), p.getMaxY(), placement); addYY.add(moveY); } } if(xxSupport && yySupport) { // no constrain necessary } else if(yySupport) { // // vmaxY | // | // yy | ║ - - - | // | ║ // | ║ | // | ║ // | ║ | // | ║ // minY | x - - - | // | * * // | * * // | * * // |----------------------------- // minX maxX // // so only those points between 0 and minY // and between minX and maxX need to be constrained if(cloneOnConstrain) { constrainMaxYWithClone(placement, pointIndex, endIndex); } else { constrainMaxY(placement, pointIndex, endIndex); } } else if(xxSupport) { // | // | // | * |---------------| // | * // | * | | // | * // minY | * x════════════════ // | // | // |-------------------------- // minX maxX // // so only those points between 0 and minX // and between minY and maxY need to be constrained pointIndex = index; while(pointIndex > 0 && values.get(pointIndex - 1).getMinX() == placement.getAbsoluteX()) { pointIndex--; } if(cloneOnConstrain) { constrainMaxXWithClone(placement, 0, pointIndex); } else { constrainMaxX(placement, 0, pointIndex); } } else { if(cloneOnConstrain) { constrainFloatingMaxWithClone(placement, endIndex); } else { constrainFloatingMax(placement, endIndex); } } endIndex -= values.removeFlagged(); placements.add(placement); int added = addXX.size() + addYY.size(); // the new points have x coordinate between zero and xx. // insert them at the start of the existing data // so that the sorting algorithm does not have to do a full sort // rather only sort points with x coordinates from 0 to xx. values.ensureAdditionalCapacity(added); // insert xx last, because it has the highest x coordinate values.move(added); values.setAll(addYY, 0); values.setAll(addXX, addYY.size()); removeEclipsed(added); endIndex += added - values.removeFlagged(); // make sure to capture all point <= xx while(endIndex < values.size() && values.get(endIndex).getMinX() <= xx) { endIndex++; } values.sort(Point2D.COMPARATOR_X_THEN_Y, endIndex); moveToXX.clear(); moveToYY.clear(); addXX.clear(); addYY.clear(); return !values.isEmpty(); } private void constrainMaxXWithClone(P placement, int pointIndex, int endIndex) { for (int i = pointIndex; i < endIndex; i++) { if(values.isFlag(i)) { continue; } Point2D

point = values.get(i); if(point.getMinX() < placement.getAbsoluteX() && withinY(point.getMinY(), placement)) { if(point.getMaxX() >= placement.getAbsoluteX()) { int limitX = placement.getAbsoluteX() - 1; if(!isConstrainedAtMaxX(point, limitX)) { Point2D

clone = point.clone(limitX, point.getMaxY()); addXX.add(clone); } values.flag(i); } } } } private void constrainMaxYWithClone(P placement, int pointIndex, int endIndex) { for (int i = pointIndex; i < endIndex; i++) { if(values.isFlag(i)) { continue; } Point2D

point = values.get(i); if(point.getMinY() < placement.getAbsoluteY() && withinX(point.getMinX(), placement)) { if(point.getMaxY() >= placement.getAbsoluteY()) { int limitY = placement.getAbsoluteY() - 1; if(!isConstrainedAtMaxY(point, limitY)) { Point2D

clone = point.clone(point.getMaxX(), limitY); addXX.add(clone); } values.flag(i); } } } } private void constrainMaxX(P placement, int pointIndex, int endIndex) { for (int i = pointIndex; i < endIndex; i++) { if(values.isFlag(i)) { continue; } Point2D

point = values.get(i); if(point.getMinX() < placement.getAbsoluteX() && withinY(point.getMinY(), placement)) { if(point.getMaxX() >= placement.getAbsoluteX()) { point.setMaxX(placement.getAbsoluteX() - 1); if(point.getArea() < minAreaLimit) { values.flag(i); } } } } } private void constrainMaxY(P placement, int pointIndex, int endIndex) { for (int i = pointIndex; i < endIndex; i++) { if(values.isFlag(i)) { continue; } Point2D

point = values.get(i); if(point.getMinY() < placement.getAbsoluteY() && withinX(point.getMinX(), placement)) { if(point.getMaxY() >= placement.getAbsoluteY()) { point.setMaxY(placement.getAbsoluteY() - 1); if(point.getArea() < minAreaLimit) { values.flag(i); } } } } } protected void removeEclipsed(int limit) { // unsorted sorted // | new | existing / current | // |----------|----------------------|--> x Point2DFlagList

values = this.values; int size = values.size(); added: for (int i = 0; i < limit; i++) { if(values.isFlag(i) ) { continue; } Point2D

unsorted = values.get(i); for(int index = limit; index < size; index++) { if(values.isFlag(index) ) { continue; } Point2D

sorted = values.get(index); if(sorted.getMinX() > unsorted.getMinX()) { // so sorted cannot contain unsorted // at this index or later break; } if(unsorted.getArea() <= sorted.getArea()) { if(sorted.eclipses(unsorted)) { values.flag(i); continue added; } } } // all new points are the result of moving or constraining // existing points, so none of the new points // can contain the old, less the previous points would // already have contained them. } } protected void constrainFloatingMaxWithClone(P placement, int limit) { Point2DFlagList

values = this.values; Point2DList> addXX = this.addXX; Point2DList> addYY = this.addYY; addXX.ensureAdditionalCapacity(limit); addYY.ensureAdditionalCapacity(limit); for (int i = 0; i < limit; i++) { if(values.isFlag(i)) { continue; } Point2D

point = values.get(i); if( placement.getAbsoluteEndY() < point.getMinY() || placement.getAbsoluteEndX() < point.getMinX() || placement.getAbsoluteX() > point.getMaxX() || placement.getAbsoluteY() > point.getMaxY() ) { continue; } // Points eclipsed by others: // before add // // | // |--------| // | | // |--------| // | // | // | // a * * |---------| // | | | // | | | // *--------*------|---------|----- // c b // after add // // | |---------| // |--------| | // | | | // |--------| | // | | | // | | | // a * |------|--|------| // | | | // | | | // *--------*------|---------|----- // c b // // Point c is split in two, each of which eclipse a or b // // So that we end up with // // | |---------| // |--------| | // | | | // |--------| | // | | | // | | | // | |------|--|------| // | | | // | | | // *---------------|---------|----- // c addX: if(point.getMinX() < placement.getAbsoluteX()) { int limitX = placement.getAbsoluteX() - 1; if(!isConstrainedAtMaxX(point, limitX)) { Point2D

clone = point.clone(limitX, point.getMaxY()); // is the point now eclipsed by current points? for (int j = 0; j < i - 1; j++) { if(values.isFlag(j)) { continue; } Point2D

point3d = values.get(j); if(point3d.getMinX() > clone.getMinX()) { break; } if(point3d.getArea() >= clone.getArea()) { if(point3d.eclipses(clone)) { break addX; } } } // is the point now eclipsed by new points? for (int j = 0; j < addXX.size(); j++) { Point2D

point3d = addXX.get(j); if(point3d.getArea() >= clone.getArea()) { if(point3d.eclipses(clone)) { break addX; } } } addXX.add(clone); } } addY: if(point.getMinY() < placement.getAbsoluteY()) { int limitY = placement.getAbsoluteY() - 1; if(!isConstrainedAtMaxY(point, limitY)) { Point2D

clone = point.clone(point.getMaxX(), limitY); // is the point now eclipsed by current points? for (int j = 0; j < i - 1; j++) { if(values.isFlag(j)) { continue; } Point2D

point3d = values.get(j); if(point3d.getMinX() > clone.getMinX()) { break; } if(point3d.getArea() >= clone.getArea()) { if(point3d.eclipses(clone)) { break addY; } } } // is the point now eclipsed by new points? for (int j = 0; j < addYY.size(); j++) { Point2D

point3d = addYY.get(j); if(point3d.getArea() >= clone.getArea()) { if(point3d.eclipses(clone)) { break addY; } } } addYY.add(clone); } } values.flag(i); } } protected void constrainFloatingMax(P placement, int limit) { Point2DFlagList

values = this.values; Point2DList> addXX = this.addXX; Point2DList> addYY = this.addYY; long minAreaLimit = this.minAreaLimit; addXX.ensureAdditionalCapacity(limit); addYY.ensureAdditionalCapacity(limit); int startAddXX = addXX.size(); int startAddYY = addYY.size(); boolean splitXX = false; boolean splitYY = false; limitLoop: for (int i = 0; i < limit; i++) { if(values.isFlag(i)) { continue; } Point2D

point = values.get(i); if( placement.getAbsoluteEndY() < point.getMinY() || placement.getAbsoluteEndX() < point.getMinX() || placement.getAbsoluteX() > point.getMaxX() || placement.getAbsoluteY() > point.getMaxY() ) { continue; } if(point.getMinY() >= placement.getAbsoluteY()) { // point is to the left of placement // adjusting max x is sufficient if(point.getMinX() < placement.getAbsoluteX()) { point.setMaxX(placement.getAbsoluteX() - 1); if(point.getArea() < minAreaLimit) { values.flag(i); continue; } // is the point now eclipsed by current points? for (int j = 0; j < i - 1; j++) { if(values.isFlag(j)) { continue; } Point2D

point3d = values.get(j); if(point3d.getMinX() > point.getMinX()) { break; } if(point3d.getArea() >= point.getArea()) { if(point3d.eclipses(point)) { values.flag(i); continue limitLoop; } } } if(splitXX) { // is the point now eclipsed by new points? for (int j = startAddXX; j < addXX.size(); j++) { Point2D

point3d = addXX.get(j); if(point3d.getArea() >= point.getArea()) { if(point3d.eclipses(point)) { values.flag(i); break; } } } } } else { values.flag(i); } continue; } if(point.getMinX() >= placement.getAbsoluteX()) { // point is below placement // adjusting max y is sufficient if(point.getMinY() < placement.getAbsoluteY()) { point.setMaxY(placement.getAbsoluteY() - 1); if(point.getArea() < minAreaLimit) { values.flag(i); continue; } // is the point now eclipsed by current points? for (int j = 0; j < i - 1; j++) { if(values.isFlag(j)) { continue; } Point2D

point3d = values.get(j); if(point3d.getMinX() > point.getMinX()) { break; } if(point3d.getArea() >= point.getArea()) { if(point3d.eclipses(point)) { values.flag(i); continue limitLoop; } } } if(splitYY) { // is the point now eclipsed by new points? for (int j = startAddYY; j < addYY.size(); j++) { Point2D

point3d = addYY.get(j); if(point3d.getArea() >= point.getArea()) { if(point3d.eclipses(point)) { values.flag(i); break; } } } } } else { values.flag(i); } continue; } // before add // // | // |--------| // | | // |--------| // | // | // | // a * * |---------| // | | | // | | | // *--------*------|---------|----- // c b // after add // // | |---------| // |--------| | // | | | // |--------| | // | | | // | | | // a * |------|--|------| // | | | // | | | // *--------*------|---------|----- // c b // // Point c is split in two, each of which eclipse a or b // // So that we end up with // // | |---------| // |--------| | // | | | // |--------| | // | | | // | | | // | |------|--|------| // | | | // | | | // *---------------|---------|----- // c // // i.e. with c // // |--------| // | | // | | // | | // | | // | | // *--------|---------------------- // // and // // | // | // | // |---------------| // | | // | | // *---------------|-------------- // if(!isConstrainedAtMaxX(point, placement.getAbsoluteX() - 1)) { Point2D

clone = point.clone(placement.getAbsoluteX() - 1, point.getMaxY()); addXX.add(clone); splitXX = true; } if(!isConstrainedAtMaxY(point, placement.getAbsoluteY() - 1)) { Point2D

clone = point.clone(point.getMaxX(), placement.getAbsoluteY() - 1); addYY.add(clone); splitYY = true; } values.flag(i); } } private boolean isConstrainedAtMaxX(Point2D

p, int maxX) { return p.getAreaAtMaxX(maxX) < minAreaLimit; } private boolean isConstrainedAtMaxY(Point2D

p, int maxY) { return p.getAreaAtMaxY(maxY) < minAreaLimit; } protected boolean withinX(int x, P placement) { return placement.getAbsoluteX() <= x && x <= placement.getAbsoluteEndX(); } protected boolean withinY(int y, P placement) { return placement.getAbsoluteY() <= y && y <= placement.getAbsoluteEndY(); } public int getDepth() { return containerMaxY + 1; } public int getWidth() { return containerMaxX + 1; } @Override public String toString() { return "ExtremePoints2D [" + containerMaxX + "x" + containerMaxY + ": " + values + "]"; } public List

getPlacements() { return placements; } public Point2D

getValue(int i) { return values.get(i); } public List> getValues() { return values.toList(); } @Override public int getValueCount() { return values.size(); } public int getMinY() { int min = 0; for (int i = 1; i < values.size(); i++) { Point2D

point = values.get(i); if(point.getMinY() < values.get(min).getMinY()) { min = i; } } return min; } public int getMinX() { int min = 0; for (int i = 1; i < values.size(); i++) { Point2D

point = values.get(i); if(point.getMinX() < values.get(min).getMinX()) { min = i; } } return min; } public boolean isEmpty() { return values.isEmpty(); } public long getMaxArea() { long maxPointArea = -1L; for(int i = 0; i < values.size(); i++) { Point2D

point = values.get(i); if(maxPointArea < point.getArea()) { maxPointArea = point.getArea(); } } return maxPointArea; } public void redo() { values.clear(); placements.clear(); addFirstPoint(); } @Override public void reset(int dx, int dy, int dz) { setSize(dx, dy); redo(); } public int findPoint(int x, int y) { for(int i = 0; i < values.size(); i++) { Point2D

point2d = values.get(i); if(point2d.getMinX() == x && point2d.getMinY() == y) { return i; } } return -1; } public int binarySearchPlusMinX(int key) { // return exclusive result int low = 0; int high = values.size() - 1; while (low <= high) { int mid = (low + high) >>> 1; // 0 if x == y // -1 if x < y // 1 if x > y int midVal = values.get(mid).getMinX(); int cmp = Integer.compare(midVal, key); if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { high = mid - 1; } else { // key found do { mid++; } while(mid < values.size() && values.get(mid).getMinX() == key); // so if there was multiple points at key, we are at the index of the last of them, plus one. return mid; } } // key not found return low; } public int binarySearchMinusMinX(int key) { // return inclusive result int low = 0; int high = values.size() - 1; while (low <= high) { int mid = (low + high) >>> 1; // 0 if x == y // -1 if x < y // 1 if x > y int midVal = values.get(mid).getMinX(); int cmp = Integer.compare(midVal, key); if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { high = mid - 1; } else { // key found while(mid > 0 && values.get(mid - 1).getMinX() == key) { mid--; } return mid; } } // key not found return low; } public void setMinArea(long minArea) { this.minAreaLimit = minArea; } private boolean canMoveX(Point2D

p, int xx) { if(p.getMaxX() < xx) { return false; } return p.getAreaAtX(xx) >= minAreaLimit; } private boolean canMoveY(Point2D

p, int yy) { if(p.getMaxY() < yy) { return false; } return p.getAreaAtY(yy) >= minAreaLimit; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy